From 12ba0dfde19e3f36fa518b34cd9947c6e32ad9ab Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Wed, 30 Jun 2021 15:38:14 -0700 Subject: [PATCH 01/11] Add name field transformation --- src/languages/getPojoMatchers.ts | 3 ++- src/treeSitterUtils.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index ff110ce27a..9c241c0c78 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -8,7 +8,7 @@ import { childNodeMatcher, getNodeWithLeadingDelimiter, } from "../nodeMatchers"; -import { getKeyNode, getValueNode } from "../treeSitterUtils"; +import { getKeyNode, getNameNode, getValueNode } from "../treeSitterUtils"; export function getPojoMatchers( dictionaryTypes: string[], @@ -30,6 +30,7 @@ export function getPojoMatchers( return simpleSelectionExtractor(getKeyNode(node)!); }, value: childNodeMatcher(getValueNode, getNodeWithLeadingDelimiter), + name: childNodeMatcher(getNameNode, simpleSelectionExtractor), list: hasType(...listTypes), listElement: delimitedMatcher( (node) => diff --git a/src/treeSitterUtils.ts b/src/treeSitterUtils.ts index f04ee4ff16..0b1f45357e 100644 --- a/src/treeSitterUtils.ts +++ b/src/treeSitterUtils.ts @@ -3,6 +3,8 @@ import { SyntaxNode } from "web-tree-sitter"; export const getValueNode = (node: SyntaxNode) => node.childForFieldName("value"); +export const getNameNode = (node: SyntaxNode) => node.childForFieldName("name"); + export const getKeyNode = (node: SyntaxNode) => node.childForFieldName("key"); export const getDefinitionNode = (node: SyntaxNode) => From 79ed9440bd85a766f481c9e26be93495b47d9edb Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Wed, 30 Jun 2021 13:43:31 -0700 Subject: [PATCH 02/11] Add AST debug logging --- src/extension.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index 39373c0f27..f2daefdede 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,7 @@ import { SelectionWithEditor, } from "./Types"; import makeGraph from "./makeGraph"; +import { SyntaxNode } from "web-tree-sitter"; export async function activate(context: vscode.ExtensionContext) { const fontMeasurements = new FontMeasurements(context); @@ -197,6 +198,25 @@ export async function activate(context: vscode.ExtensionContext) { addDecorations(); }; + function logBranchTypes(event: vscode.TextEditorSelectionChangeEvent) { + const getBranch = (branch: SyntaxNode[]): SyntaxNode[] => { + if (branch[0].parent) { + return getBranch([branch[0].parent, ...branch]); + } + return branch; + }; + + const location = new vscode.Location( + vscode.window.activeTextEditor!.document.uri, + event.selections[0] + ); + const leaf: SyntaxNode = getNodeAtLocation(location); + const branch = getBranch([leaf]); + branch.forEach((node, i) => console.log(">".repeat(i + 1), node.type)); + const leafText = leaf.text.replace(/\s+/g, " ").substring(0, 100); + console.log(">".repeat(branch.length), `"${leafText}"`); + } + context.subscriptions.push( cursorlessCommandDisposable, toggleDecorationsDisposable, @@ -206,6 +226,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.onDidChangeActiveTextEditor(addDecorationsDebounced), vscode.window.onDidChangeVisibleTextEditors(addDecorationsDebounced), vscode.window.onDidChangeTextEditorSelection(addDecorationsDebounced), + vscode.window.onDidChangeTextEditorSelection(logBranchTypes), vscode.workspace.onDidChangeTextDocument(handleEdit), { dispose() { From 0a96484aa27ec155b287b008d198699063b207f2 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Wed, 30 Jun 2021 16:40:15 -0700 Subject: [PATCH 03/11] Add basic name transformation --- src/Types.ts | 1 + src/languages/getPojoMatchers.ts | 3 +-- src/languages/json.ts | 1 + src/languages/python.ts | 15 ++++++++++++++- src/treeSitterUtils.ts | 2 ++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index abd35dd470..cc21151cd2 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -53,6 +53,7 @@ export type ScopeType = | "ifStatement" | "list" | "listElement" + | "name" | "namedFunction" | "pair" | "pairKey" diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index 9c241c0c78..3ff2d53f4f 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -4,11 +4,10 @@ import { delimitedMatcher, hasType, simpleSelectionExtractor, - makeRange, childNodeMatcher, getNodeWithLeadingDelimiter, } from "../nodeMatchers"; -import { getKeyNode, getNameNode, getValueNode } from "../treeSitterUtils"; +import { getNameNode, getKeyNode, getValueNode } from "../treeSitterUtils"; export function getPojoMatchers( dictionaryTypes: string[], diff --git a/src/languages/json.ts b/src/languages/json.ts index b005f5f681..3c092ec249 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -32,6 +32,7 @@ const nodeMatchers: Record = { namedFunction: notSupported, comment: notSupported, type: notSupported, + name: notSupported, }; export default nodeMatchers; diff --git a/src/languages/python.ts b/src/languages/python.ts index bdfe1bea00..0fd92f6554 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -1,14 +1,20 @@ import { SyntaxNode } from "web-tree-sitter"; import { getPojoMatchers } from "./getPojoMatchers"; import { + cascadingMatcher, childNodeMatcher, delimitedMatcher, getNodeWithLeadingDelimiter, hasType, possiblyWrappedNode, + simpleSelectionExtractor, } from "../nodeMatchers"; import { NodeMatcher, ScopeType } from "../Types"; -import { getDefinitionNode } from "../treeSitterUtils"; +import { + getDefinitionNode, + getLeftNode, + getNameNode, +} from "../treeSitterUtils"; // TODO figure out how to properly use super types // Generated by the following command: @@ -133,6 +139,13 @@ const nodeMatchers: Record = { ifStatement: hasType("if_statement"), class: possiblyDecoratedDefinition("class_definition"), statement: hasType(...STATEMENT_TYPES), + name: cascadingMatcher( + childNodeMatcher(getNameNode, simpleSelectionExtractor), + childNodeMatcher( + (node) => (node.type === "assignment" ? getLeftNode(node) : null), + simpleSelectionExtractor + ) + ), arrowFunction: hasType("lambda"), functionCall: hasType("call"), argumentOrParameter: delimitedMatcher( diff --git a/src/treeSitterUtils.ts b/src/treeSitterUtils.ts index 0b1f45357e..893bc8d143 100644 --- a/src/treeSitterUtils.ts +++ b/src/treeSitterUtils.ts @@ -3,6 +3,8 @@ import { SyntaxNode } from "web-tree-sitter"; export const getValueNode = (node: SyntaxNode) => node.childForFieldName("value"); +export const getLeftNode = (node: SyntaxNode) => node.childForFieldName("left"); + export const getNameNode = (node: SyntaxNode) => node.childForFieldName("name"); export const getKeyNode = (node: SyntaxNode) => node.childForFieldName("key"); From 89e0c85ea4b1a94bf178fe7b27ec6645db711576 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Sat, 3 Jul 2021 06:50:15 -0700 Subject: [PATCH 04/11] Refactor matchers to add function name transform --- src/Types.ts | 10 +- src/languages/getPojoMatchers.ts | 54 ++++---- src/languages/python.ts | 69 +++++----- src/languages/typescript.ts | 121 ++++++++++------- src/nodeFinders.ts | 53 ++++++++ src/nodeMatchers.ts | 216 +++++-------------------------- src/nodeSelectors.ts | 129 ++++++++++++++++++ 7 files changed, 355 insertions(+), 297 deletions(-) create mode 100644 src/nodeFinders.ts create mode 100644 src/nodeSelectors.ts diff --git a/src/Types.ts b/src/Types.ts index cc21151cd2..16d8c23662 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -50,6 +50,7 @@ export type ScopeType = | "comment" | "dictionary" | "functionCall" + | "functionName" | "ifStatement" | "list" | "listElement" @@ -254,4 +255,11 @@ export interface DecorationColorSetting { dark: string; light: string; highContrast: string; -} \ No newline at end of file +} + +export type NodeFinder = (node: SyntaxNode) => SyntaxNode | null; + +export type SelectionExtractor = ( + editor: vscode.TextEditor, + node: SyntaxNode +) => SelectionWithContext | null; diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index 3ff2d53f4f..27f97298d1 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -1,13 +1,8 @@ import { SyntaxNode } from "web-tree-sitter"; -import { TextEditor } from "vscode"; -import { - delimitedMatcher, - hasType, - simpleSelectionExtractor, - childNodeMatcher, - getNodeWithLeadingDelimiter, -} from "../nodeMatchers"; import { getNameNode, getKeyNode, getValueNode } from "../treeSitterUtils"; +import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; +import { composedMatcher, matcher, typeMatcher } from "../nodeMatchers"; +import { findNode, findNodeOfType } from "../nodeFinders"; export function getPojoMatchers( dictionaryTypes: string[], @@ -15,28 +10,29 @@ export function getPojoMatchers( listElementMatcher: (node: SyntaxNode) => boolean ) { return { - dictionary: hasType(...dictionaryTypes), - pair: delimitedMatcher( - (node) => node.type === "pair", - (node) => node.type === "," || node.type === "}" || node.type === "{", - ", " + dictionary: typeMatcher(...dictionaryTypes), + pair: matcher( + findNode((node) => node.type === "pair"), + selectDelimited( + (node) => node.type === "," || node.type === "}" || node.type === "{", + ", " + ) ), - pairKey(editor: TextEditor, node: SyntaxNode) { - if (node.type !== "pair") { - return null; - } - - return simpleSelectionExtractor(getKeyNode(node)!); - }, - value: childNodeMatcher(getValueNode, getNodeWithLeadingDelimiter), - name: childNodeMatcher(getNameNode, simpleSelectionExtractor), - list: hasType(...listTypes), - listElement: delimitedMatcher( - (node) => - listTypes.includes(node.parent?.type ?? "") && listElementMatcher(node), - (node) => node.type === "," || node.type === "[" || node.type === "]", - ", " + pairKey: composedMatcher([findNodeOfType("pair"), getKeyNode]), + value: matcher(getValueNode, selectWithLeadingDelimiter), + name: matcher(getNameNode), + list: typeMatcher(...listTypes), + listElement: matcher( + findNode( + (node) => + listTypes.includes(node.parent?.type ?? "") && + listElementMatcher(node) + ), + selectDelimited( + (node) => node.type === "," || node.type === "[" || node.type === "]", + ", " + ) ), - string: hasType("string"), + string: typeMatcher("string"), }; } diff --git a/src/languages/python.ts b/src/languages/python.ts index 0fd92f6554..beebe7d662 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -2,19 +2,22 @@ import { SyntaxNode } from "web-tree-sitter"; import { getPojoMatchers } from "./getPojoMatchers"; import { cascadingMatcher, - childNodeMatcher, - delimitedMatcher, - getNodeWithLeadingDelimiter, - hasType, - possiblyWrappedNode, - simpleSelectionExtractor, + matcher, + notSupported, + typeMatcher, } from "../nodeMatchers"; -import { NodeMatcher, ScopeType } from "../Types"; +import { NodeFinder, NodeMatcher, ScopeType } from "../Types"; import { getDefinitionNode, getLeftNode, getNameNode, } from "../treeSitterUtils"; +import { + findNode, + findNodeOfType, + findPossiblyWrappedNode, +} from "../nodeFinders"; +import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; // TODO figure out how to properly use super types // Generated by the following command: @@ -119,10 +122,10 @@ const ARGUMENT_TYPES = [ "keyword_argument", ]; -function possiblyDecoratedDefinition(...typeNames: string[]): NodeMatcher { - return possiblyWrappedNode( - (node) => node.type === "decorated_definition", - (node) => typeNames.includes(node.type), +function possiblyDecoratedDefinition(...typeNames: string[]): NodeFinder { + return findPossiblyWrappedNode( + findNodeOfType("decorated_definition"), + findNodeOfType(...typeNames), (node) => [getDefinitionNode(node)] ); } @@ -136,30 +139,32 @@ const nodeMatchers: Record = { ["list", "list_comprehension"], (node) => LIST_ELEMENT_TYPES.includes(node.type) ), - ifStatement: hasType("if_statement"), - class: possiblyDecoratedDefinition("class_definition"), - statement: hasType(...STATEMENT_TYPES), + ifStatement: typeMatcher("if_statement"), + class: matcher(possiblyDecoratedDefinition("class_definition")), + statement: typeMatcher(...STATEMENT_TYPES), name: cascadingMatcher( - childNodeMatcher(getNameNode, simpleSelectionExtractor), - childNodeMatcher( - (node) => (node.type === "assignment" ? getLeftNode(node) : null), - simpleSelectionExtractor - ) + matcher(getNameNode), + matcher((node) => (node.type === "assignment" ? getLeftNode(node) : null)) ), - arrowFunction: hasType("lambda"), - functionCall: hasType("call"), - argumentOrParameter: delimitedMatcher( - (node) => - (node.parent?.type === "argument_list" && - ARGUMENT_TYPES.includes(node.type)) || - (PARAMETER_LIST_TYPES.includes(node.parent?.type ?? "") && - PARAMETER_TYPES.includes(node.type)), - (node) => node.type === "," || node.type === "(" || node.type === ")", - ", " + functionName: notSupported, + arrowFunction: typeMatcher("lambda"), + functionCall: typeMatcher("call"), + argumentOrParameter: matcher( + findNode( + (node) => + (node.parent?.type === "argument_list" && + ARGUMENT_TYPES.includes(node.type)) || + (PARAMETER_LIST_TYPES.includes(node.parent?.type ?? "") && + PARAMETER_TYPES.includes(node.type)) + ), + selectDelimited( + (node) => node.type === "," || node.type === "(" || node.type === ")", + ", " + ) ), - namedFunction: possiblyDecoratedDefinition("function_definition"), - comment: hasType("comment"), - type: childNodeMatcher(getTypeNode, getNodeWithLeadingDelimiter), + namedFunction: matcher(possiblyDecoratedDefinition("function_definition")), + comment: typeMatcher("comment"), + type: matcher(getTypeNode, selectWithLeadingDelimiter), }; export default nodeMatchers; diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index f72f2dc7e7..5b513dd4cc 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -1,17 +1,23 @@ import { SyntaxNode } from "web-tree-sitter"; -import { TextEditor } from "vscode"; import { getPojoMatchers } from "./getPojoMatchers"; import { cascadingMatcher, - delimitedMatcher, - hasType, - possiblyWrappedNode, - simpleSelectionExtractor, - getNodeWithLeadingDelimiter, - childNodeMatcher, + composedMatcher, + matcher, + typeMatcher, } from "../nodeMatchers"; -import { NodeMatcher, ScopeType } from "../Types"; -import { getDeclarationNode, getValueNode } from "../treeSitterUtils"; +import { NodeMatcher, ScopeType, NodeFinder } from "../Types"; +import { + getDeclarationNode, + getNameNode, + getValueNode, +} from "../treeSitterUtils"; +import { + findNode, + findNodeOfType, + findPossiblyWrappedNode, +} from "../nodeFinders"; +import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; // TODO figure out how to properly use super types // Generated by the following command: @@ -101,28 +107,34 @@ const STATEMENT_TYPES = [ "with_statement", ]; -function possiblyExportedDeclaration(...typeNames: string[]): NodeMatcher { - return possiblyWrappedNode( - (node) => node.type === "export_statement", - (node) => typeNames.includes(node.type), +function possiblyExportedDeclaration(...typeNames: string[]): NodeFinder { + return findPossiblyWrappedNode( + findNodeOfType("export_statement"), + findNodeOfType(...typeNames), (node) => [getDeclarationNode(node), getValueNode(node)] ); } -const isNamedArrowFunction = (node: SyntaxNode) => { +const findNamedArrowFunction = (node: SyntaxNode) => { if (node.type !== "lexical_declaration" || node.namedChildCount !== 1) { - return false; + return null; } const child = node.firstNamedChild!; - return ( - child.type === "variable_declarator" && + return child.type === "variable_declarator" && getValueNode(child)!.type === "arrow_function" - ); + ? node + : null; }; -export const getTypeNode = (node: SyntaxNode) => { +const findClassPropertyArrowFunction = (node: SyntaxNode) => + node.type === "public_field_definition" && + getValueNode(node)!.type === "arrow_function" + ? node + : null; + +export const findTypeNode = (node: SyntaxNode) => { const typeAnnotationNode = node.children.find((child) => ["type_annotation", "opting_type_annotation"].includes(child.type) ); @@ -135,55 +147,68 @@ const nodeMatchers: Record = { ["array"], (node) => isExpression(node) || node.type === "spread_element" ), - ifStatement: hasType("if_statement"), - class: possiblyExportedDeclaration("class_declaration", "class"), - statement: possiblyExportedDeclaration(...STATEMENT_TYPES), - arrowFunction: hasType("arrow_function"), - functionCall: hasType("call_expression", "new_expression"), + ifStatement: typeMatcher("if_statement"), + class: matcher(possiblyExportedDeclaration("class_declaration", "class")), + statement: matcher(possiblyExportedDeclaration(...STATEMENT_TYPES)), + arrowFunction: typeMatcher("arrow_function"), + functionCall: typeMatcher("call_expression", "new_expression"), + functionName: cascadingMatcher( + composedMatcher([ + findNodeOfType("function_declaration", "method_definition"), + getNameNode, + ]), + composedMatcher([findClassPropertyArrowFunction, getNameNode]), + composedMatcher([findNamedArrowFunction, getNameNode]) + ), type: cascadingMatcher( // Typed parameters, properties, and functions - childNodeMatcher(getTypeNode, getNodeWithLeadingDelimiter), - + matcher(findTypeNode, selectWithLeadingDelimiter), // Type alias/interface declarations - possiblyExportedDeclaration( - "type_alias_declaration", - "interface_declaration" + matcher( + possiblyExportedDeclaration( + "type_alias_declaration", + "interface_declaration" + ) ) ), - argumentOrParameter: delimitedMatcher( - (node) => - (node.parent?.type === "arguments" && - (isExpression(node) || node.type === "spread_element")) || - node.type === "optional_parameter" || - node.type === "required_parameter", - (node) => node.type === "," || node.type === "(" || node.type === ")", - ", " + argumentOrParameter: matcher( + findNode( + (node) => + (node.parent?.type === "arguments" && + (isExpression(node) || node.type === "spread_element")) || + node.type === "optional_parameter" || + node.type === "required_parameter" + ), + selectDelimited( + (node) => node.type === "," || node.type === "(" || node.type === ")", + ", " + ) ), namedFunction: cascadingMatcher( // Simple case, eg // function foo() {} - possiblyExportedDeclaration("function_declaration", "method_definition"), + matcher( + possiblyExportedDeclaration("function_declaration", "method_definition") + ), // Class property defined as field definition with arrow // eg: // class Foo { // bar = () => "hello"; // } - (editor: TextEditor, node: SyntaxNode) => - node.type === "public_field_definition" && - getValueNode(node)!.type === "arrow_function" - ? simpleSelectionExtractor(node) - : null, + matcher(findClassPropertyArrowFunction), // eg: // const foo = () => "hello" - possiblyWrappedNode( - (node) => node.type === "export_statement", - isNamedArrowFunction, - (node) => [getDeclarationNode(node)] + matcher( + findPossiblyWrappedNode( + findNodeOfType("export_statement"), + findNamedArrowFunction, + (node) => [getDeclarationNode(node)] + ) ) ), - comment: hasType("comment"), + comment: matcher(findNodeOfType("comment")), }; export default nodeMatchers; diff --git a/src/nodeFinders.ts b/src/nodeFinders.ts new file mode 100644 index 0000000000..6bd61574d9 --- /dev/null +++ b/src/nodeFinders.ts @@ -0,0 +1,53 @@ +import { SyntaxNode } from "web-tree-sitter"; +import { NodeFinder } from "./Types"; + +export const findNode = + (isTargetNode: (node: SyntaxNode) => boolean): NodeFinder => + (node: SyntaxNode) => { + return isTargetNode(node) ? node : null; + }; + +export const findNodeOfType = + (...typeNames: string[]): NodeFinder => + (node: SyntaxNode) => { + return typeNames.includes(node.type) ? node : null; + }; + +/** + * Creates a matcher that can match potentially wrapped nodes. For example + * typescript export statements or python decorators + * @param isWrapperNode Returns node if the given node has the right type to be + * a wrapper node + * @param isTargetNode Returns node if the given node has the right type to be + * the target + * @param getWrappedNodes Given a wrapper node returns a list of possible + * target nodes + * @returns A matcher that will return the given target node or the wrapper + * node, if it is wrapping a target node + */ +export function findPossiblyWrappedNode( + isWrapperNode: NodeFinder, + isTargetNode: NodeFinder, + getWrappedNodes: (node: SyntaxNode) => (SyntaxNode | null)[] +): NodeFinder { + return (node: SyntaxNode) => { + if (node.parent != null && isWrapperNode(node.parent)) { + // We don't want to return the target node if it is wrapped. We return + // null, knowing that the ancestor walk will call us again with the + // wrapper node + return null; + } + + if (isWrapperNode(node)) { + const isWrappingTargetNode = getWrappedNodes(node).some( + (node) => node != null && isTargetNode(node) + ); + + if (isWrappingTargetNode) { + return node; + } + } + + return isTargetNode(node) ? node : null; + }; +} diff --git a/src/nodeMatchers.ts b/src/nodeMatchers.ts index 17d92c810b..9b7c8e2aa5 100644 --- a/src/nodeMatchers.ts +++ b/src/nodeMatchers.ts @@ -1,200 +1,35 @@ -import { SyntaxNode, Point } from "web-tree-sitter"; -import { Position, Range, Selection, TextEditor } from "vscode"; -import { SelectionWithContext, NodeMatcher } from "./Types"; - -export function hasType(...typeNames: string[]): NodeMatcher { - return (editor: TextEditor, node: SyntaxNode) => - typeNames.includes(node.type) ? simpleSelectionExtractor(node) : null; -} - -export function childNodeMatcher( - getMatchingChildNode: (node: SyntaxNode) => SyntaxNode | null, - extractor: (node: SyntaxNode) => SelectionWithContext +import { SyntaxNode } from "web-tree-sitter"; +import { TextEditor } from "vscode"; +import { NodeMatcher, NodeFinder, SelectionExtractor } from "./Types"; +import { simpleSelectionExtractor } from "./nodeSelectors"; +import { findNodeOfType } from "./nodeFinders"; + +export function matcher( + finder: NodeFinder, + selector: SelectionExtractor = simpleSelectionExtractor ): NodeMatcher { - return (editor: TextEditor, node: SyntaxNode) => { - const returnNode = getMatchingChildNode(node); - - if (returnNode == null) { - return null; - } - - return extractor(returnNode); + return function (editor: TextEditor, node: SyntaxNode) { + const targetNode = finder(node); + return targetNode ? selector(editor, targetNode) : null; }; } -export function getNodeWithLeadingDelimiter( - node: SyntaxNode -): SelectionWithContext { - const leadingDelimiterToken = node.previousSibling!; - - const leadingDelimiterRange = makeRange( - leadingDelimiterToken.startPosition, - node.startPosition - ); - - return { - ...simpleSelectionExtractor(node), - context: { - leadingDelimiterRange, - }, - }; -} - -export const notSupported: NodeMatcher = ( - editor: TextEditor, - node: SyntaxNode -) => { - throw new Error("Node type not supported"); -}; - -function getNextNonDelimiterNode( - startNode: SyntaxNode, - isDelimiterNode: (node: SyntaxNode) => boolean -): SyntaxNode | null { - var node = startNode.nextSibling; - - while (node != null) { - if (!isDelimiterNode(node)) { - return node; - } - - node = node.nextSibling; - } - - return node; -} - -function getPreviousNonDelimiterNode( - startNode: SyntaxNode, - isDelimiterNode: (node: SyntaxNode) => boolean -): SyntaxNode | null { - var node = startNode.previousSibling; - - while (node != null) { - if (!isDelimiterNode(node)) { - return node; - } - - node = node.previousSibling; - } - - return node; -} - -export function delimitedMatcher( - nodeMatches: (node: SyntaxNode) => boolean, - isDelimiterNode: (node: SyntaxNode) => boolean, - defaultDelimiter: string +export function composedMatcher( + finders: NodeFinder[], + selector: SelectionExtractor = simpleSelectionExtractor ): NodeMatcher { - return (editor: TextEditor, node: SyntaxNode) => { - if (!nodeMatches(node)) { - return null; - } - - var containingListDelimiter: string | null = null; - var leadingDelimiterRange: Range | null = null; - var trailingDelimiterRange: Range | null = null; - - const nextNonDelimiterNode = getNextNonDelimiterNode(node, isDelimiterNode); - const previousNonDelimiterNode = getPreviousNonDelimiterNode( - node, - isDelimiterNode - ); - - if (nextNonDelimiterNode != null) { - trailingDelimiterRange = makeRange( - node.endPosition, - nextNonDelimiterNode.startPosition - ); - - containingListDelimiter = editor.document.getText(trailingDelimiterRange); + return function (editor: TextEditor, initialNode: SyntaxNode) { + let returnNode: SyntaxNode | null = initialNode; + for (const finder of finders) { + returnNode = returnNode ? finder(returnNode) : null; } - if (previousNonDelimiterNode != null) { - leadingDelimiterRange = makeRange( - previousNonDelimiterNode.endPosition, - node.startPosition - ); - - if (containingListDelimiter == null) { - containingListDelimiter = editor.document.getText( - leadingDelimiterRange - ); - } - } - - if (containingListDelimiter == null) { - containingListDelimiter = defaultDelimiter; - } - - return { - ...simpleSelectionExtractor(node), - context: { - isInDelimitedList: true, - containingListDelimiter, - leadingDelimiterRange, - trailingDelimiterRange, - }, - }; - }; -} - -export function makeRange(startPosition: Point, endPosition: Point) { - return new Range( - new Position(startPosition.row, startPosition.column), - new Position(endPosition.row, endPosition.column) - ); -} - -export function simpleSelectionExtractor( - node: SyntaxNode -): SelectionWithContext { - return { - selection: new Selection( - new Position(node.startPosition.row, node.startPosition.column), - new Position(node.endPosition.row, node.endPosition.column) - ), - context: {}, + return returnNode ? selector(editor, returnNode) : null; }; } -/** - * Creates a matcher that can match potentially wrapped nodes. For example - * typescript export statements or python decorators - * @param isWrapperNode Returns true if the given node has the right type to be - * a wrapper node - * @param isTargetNode Returns true if the given node has the right type to be - * the target - * @param getWrappedNodes Given a wrapper node returns a list of possible - * target nodes - * @returns A matcher that will return the given target node or the wrapper - * node, if it is wrapping a target node - */ -export function possiblyWrappedNode( - isWrapperNode: (node: SyntaxNode) => boolean, - isTargetNode: (node: SyntaxNode) => boolean, - getWrappedNodes: (node: SyntaxNode) => (SyntaxNode | null)[] -): NodeMatcher { - return (editor: TextEditor, node: SyntaxNode) => { - if (node.parent != null && isWrapperNode(node.parent)) { - // We don't want to return the target node if it is wrapped. We return - // null, knowing that the ancestor walk will call us again with the - // wrapper node - return null; - } - - if (isWrapperNode(node)) { - const isWrappingTargetNode = getWrappedNodes(node).some( - (node) => node != null && isTargetNode(node) - ); - - if (isWrappingTargetNode) { - return simpleSelectionExtractor(node); - } - } - - return isTargetNode(node) ? simpleSelectionExtractor(node) : null; - }; +export function typeMatcher(...typeNames: string[]) { + return matcher(findNodeOfType(...typeNames)); } /** @@ -216,3 +51,10 @@ export function cascadingMatcher(...matchers: NodeMatcher[]): NodeMatcher { return null; }; } + +export const notSupported: NodeMatcher = ( + editor: TextEditor, + node: SyntaxNode +) => { + throw new Error("Node type not supported"); +}; diff --git a/src/nodeSelectors.ts b/src/nodeSelectors.ts new file mode 100644 index 0000000000..6984941d49 --- /dev/null +++ b/src/nodeSelectors.ts @@ -0,0 +1,129 @@ +import { SyntaxNode, Point } from "web-tree-sitter"; +import { Position, Range, Selection, TextEditor } from "vscode"; +import { SelectionWithContext, SelectionExtractor } from "./Types"; + +export function makeRange(startPosition: Point, endPosition: Point) { + return new Range( + new Position(startPosition.row, startPosition.column), + new Position(endPosition.row, endPosition.column) + ); +} + +export function simpleSelectionExtractor( + editor: TextEditor, + node: SyntaxNode +): SelectionWithContext { + return { + selection: new Selection( + new Position(node.startPosition.row, node.startPosition.column), + new Position(node.endPosition.row, node.endPosition.column) + ), + context: {}, + }; +} + +export function selectWithLeadingDelimiter( + editor: TextEditor, + node: SyntaxNode +): SelectionWithContext { + const leadingDelimiterToken = node.previousSibling!; + + const leadingDelimiterRange = makeRange( + leadingDelimiterToken.startPosition, + node.startPosition + ); + + return { + ...simpleSelectionExtractor(editor, node), + context: { + leadingDelimiterRange, + }, + }; +} + +function getNextNonDelimiterNode( + startNode: SyntaxNode, + isDelimiterNode: (node: SyntaxNode) => boolean +): SyntaxNode | null { + var node = startNode.nextSibling; + + while (node != null) { + if (!isDelimiterNode(node)) { + return node; + } + + node = node.nextSibling; + } + + return node; +} + +function getPreviousNonDelimiterNode( + startNode: SyntaxNode, + isDelimiterNode: (node: SyntaxNode) => boolean +): SyntaxNode | null { + var node = startNode.previousSibling; + + while (node != null) { + if (!isDelimiterNode(node)) { + return node; + } + + node = node.previousSibling; + } + + return node; +} + +export function selectDelimited( + isDelimiterNode: (node: SyntaxNode) => boolean, + defaultDelimiter: string +): SelectionExtractor { + return (editor: TextEditor, node: SyntaxNode) => { + var containingListDelimiter: string | null = null; + var leadingDelimiterRange: Range | null = null; + var trailingDelimiterRange: Range | null = null; + + const nextNonDelimiterNode = getNextNonDelimiterNode(node, isDelimiterNode); + const previousNonDelimiterNode = getPreviousNonDelimiterNode( + node, + isDelimiterNode + ); + + if (nextNonDelimiterNode != null) { + trailingDelimiterRange = makeRange( + node.endPosition, + nextNonDelimiterNode.startPosition + ); + + containingListDelimiter = editor.document.getText(trailingDelimiterRange); + } + + if (previousNonDelimiterNode != null) { + leadingDelimiterRange = makeRange( + previousNonDelimiterNode.endPosition, + node.startPosition + ); + + if (containingListDelimiter == null) { + containingListDelimiter = editor.document.getText( + leadingDelimiterRange + ); + } + } + + if (containingListDelimiter == null) { + containingListDelimiter = defaultDelimiter; + } + + return { + ...simpleSelectionExtractor(editor, node), + context: { + isInDelimitedList: true, + containingListDelimiter, + leadingDelimiterRange, + trailingDelimiterRange, + }, + }; + }; +} From cd7c36aa148f2f2a5a1a0579ddffeb6a9dd46769 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sat, 3 Jul 2021 18:54:31 +0100 Subject: [PATCH 05/11] Add missing function name --- src/languages/json.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/json.ts b/src/languages/json.ts index 3c092ec249..aae7326acf 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -30,6 +30,7 @@ const nodeMatchers: Record = { functionCall: notSupported, argumentOrParameter: notSupported, namedFunction: notSupported, + functionName: notSupported, comment: notSupported, type: notSupported, name: notSupported, From b1061eb7f23b97d28ab4768f3b5473fccee38664 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:38:04 -0700 Subject: [PATCH 06/11] Clean up transformations refactor --- src/Types.ts | 15 ++++++++++----- src/debug.ts | 23 +++++++++++++++++++++++ src/extension.ts | 25 ++++--------------------- src/languages/getPojoMatchers.ts | 13 ++++++++----- src/languages/python.ts | 13 ++++++++----- src/languages/typescript.ts | 19 +++++++++++-------- src/nodeFinders.ts | 16 ++++++++-------- src/nodeMatchers.ts | 14 +++++++++----- src/nodeSelectors.ts | 2 +- 9 files changed, 82 insertions(+), 58 deletions(-) create mode 100644 src/debug.ts diff --git a/src/Types.ts b/src/Types.ts index 16d8c23662..29f5ab9618 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -246,19 +246,24 @@ export interface Graph { readonly editStyles: EditStyles; } -export type NodeMatcher = ( - editor: vscode.TextEditor, - node: SyntaxNode -) => SelectionWithContext | null; - export interface DecorationColorSetting { dark: string; light: string; highContrast: string; } +export type NodeMatcher = ( + editor: vscode.TextEditor, + node: SyntaxNode +) => SelectionWithContext | null; + +/** + * Returns the desired relative of the provided node. + * Returns null if matching node not found. + **/ export type NodeFinder = (node: SyntaxNode) => SyntaxNode | null; +/** Returns a selection for a given SyntaxNode */ export type SelectionExtractor = ( editor: vscode.TextEditor, node: SyntaxNode diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000000..9b22177b2e --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,23 @@ +import * as vscode from "vscode"; +import { SyntaxNode } from "web-tree-sitter"; + +export function logBranchTypes(getNodeAtLocation: any) { + return (event: vscode.TextEditorSelectionChangeEvent) => { + const getBranch = (branch: SyntaxNode[]): SyntaxNode[] => { + if (branch[0].parent) { + return getBranch([branch[0].parent, ...branch]); + } + return branch; + }; + + const location = new vscode.Location( + vscode.window.activeTextEditor!.document.uri, + event.selections[0] + ); + const leaf: SyntaxNode = getNodeAtLocation(location); + const branch = getBranch([leaf]); + branch.forEach((node, i) => console.debug(">".repeat(i + 1), node.type)); + const leafText = leaf.text.replace(/\s+/g, " ").substring(0, 100); + console.debug(">".repeat(branch.length), `"${leafText}"`); + }; +} diff --git a/src/extension.ts b/src/extension.ts index f2daefdede..0c9f1d8135 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,7 +14,7 @@ import { SelectionWithEditor, } from "./Types"; import makeGraph from "./makeGraph"; -import { SyntaxNode } from "web-tree-sitter"; +import { logBranchTypes } from "./debug"; export async function activate(context: vscode.ExtensionContext) { const fontMeasurements = new FontMeasurements(context); @@ -198,25 +198,6 @@ export async function activate(context: vscode.ExtensionContext) { addDecorations(); }; - function logBranchTypes(event: vscode.TextEditorSelectionChangeEvent) { - const getBranch = (branch: SyntaxNode[]): SyntaxNode[] => { - if (branch[0].parent) { - return getBranch([branch[0].parent, ...branch]); - } - return branch; - }; - - const location = new vscode.Location( - vscode.window.activeTextEditor!.document.uri, - event.selections[0] - ); - const leaf: SyntaxNode = getNodeAtLocation(location); - const branch = getBranch([leaf]); - branch.forEach((node, i) => console.log(">".repeat(i + 1), node.type)); - const leafText = leaf.text.replace(/\s+/g, " ").substring(0, 100); - console.log(">".repeat(branch.length), `"${leafText}"`); - } - context.subscriptions.push( cursorlessCommandDisposable, toggleDecorationsDisposable, @@ -226,7 +207,9 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.onDidChangeActiveTextEditor(addDecorationsDebounced), vscode.window.onDidChangeVisibleTextEditors(addDecorationsDebounced), vscode.window.onDidChangeTextEditorSelection(addDecorationsDebounced), - vscode.window.onDidChangeTextEditorSelection(logBranchTypes), + vscode.window.onDidChangeTextEditorSelection( + logBranchTypes(getNodeAtLocation) + ), vscode.workspace.onDidChangeTextDocument(handleEdit), { dispose() { diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index 27f97298d1..b012ad813b 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -1,8 +1,11 @@ import { SyntaxNode } from "web-tree-sitter"; import { getNameNode, getKeyNode, getValueNode } from "../treeSitterUtils"; -import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; +import { + delimitedSelector, + selectWithLeadingDelimiter, +} from "../nodeSelectors"; import { composedMatcher, matcher, typeMatcher } from "../nodeMatchers"; -import { findNode, findNodeOfType } from "../nodeFinders"; +import { findNode, typedNodeFinder } from "../nodeFinders"; export function getPojoMatchers( dictionaryTypes: string[], @@ -13,12 +16,12 @@ export function getPojoMatchers( dictionary: typeMatcher(...dictionaryTypes), pair: matcher( findNode((node) => node.type === "pair"), - selectDelimited( + delimitedSelector( (node) => node.type === "," || node.type === "}" || node.type === "{", ", " ) ), - pairKey: composedMatcher([findNodeOfType("pair"), getKeyNode]), + pairKey: composedMatcher([typedNodeFinder("pair"), getKeyNode]), value: matcher(getValueNode, selectWithLeadingDelimiter), name: matcher(getNameNode), list: typeMatcher(...listTypes), @@ -28,7 +31,7 @@ export function getPojoMatchers( listTypes.includes(node.parent?.type ?? "") && listElementMatcher(node) ), - selectDelimited( + delimitedSelector( (node) => node.type === "," || node.type === "[" || node.type === "]", ", " ) diff --git a/src/languages/python.ts b/src/languages/python.ts index beebe7d662..b828eba698 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -14,10 +14,13 @@ import { } from "../treeSitterUtils"; import { findNode, - findNodeOfType, + typedNodeFinder, findPossiblyWrappedNode, } from "../nodeFinders"; -import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; +import { + delimitedSelector, + selectWithLeadingDelimiter, +} from "../nodeSelectors"; // TODO figure out how to properly use super types // Generated by the following command: @@ -124,8 +127,8 @@ const ARGUMENT_TYPES = [ function possiblyDecoratedDefinition(...typeNames: string[]): NodeFinder { return findPossiblyWrappedNode( - findNodeOfType("decorated_definition"), - findNodeOfType(...typeNames), + typedNodeFinder("decorated_definition"), + typedNodeFinder(...typeNames), (node) => [getDefinitionNode(node)] ); } @@ -157,7 +160,7 @@ const nodeMatchers: Record = { (PARAMETER_LIST_TYPES.includes(node.parent?.type ?? "") && PARAMETER_TYPES.includes(node.type)) ), - selectDelimited( + delimitedSelector( (node) => node.type === "," || node.type === "(" || node.type === ")", ", " ) diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index 5b513dd4cc..ae169c7fb6 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -14,10 +14,13 @@ import { } from "../treeSitterUtils"; import { findNode, - findNodeOfType, + typedNodeFinder, findPossiblyWrappedNode, } from "../nodeFinders"; -import { selectDelimited, selectWithLeadingDelimiter } from "../nodeSelectors"; +import { + delimitedSelector, + selectWithLeadingDelimiter, +} from "../nodeSelectors"; // TODO figure out how to properly use super types // Generated by the following command: @@ -109,8 +112,8 @@ const STATEMENT_TYPES = [ function possiblyExportedDeclaration(...typeNames: string[]): NodeFinder { return findPossiblyWrappedNode( - findNodeOfType("export_statement"), - findNodeOfType(...typeNames), + typedNodeFinder("export_statement"), + typedNodeFinder(...typeNames), (node) => [getDeclarationNode(node), getValueNode(node)] ); } @@ -154,7 +157,7 @@ const nodeMatchers: Record = { functionCall: typeMatcher("call_expression", "new_expression"), functionName: cascadingMatcher( composedMatcher([ - findNodeOfType("function_declaration", "method_definition"), + typedNodeFinder("function_declaration", "method_definition"), getNameNode, ]), composedMatcher([findClassPropertyArrowFunction, getNameNode]), @@ -179,7 +182,7 @@ const nodeMatchers: Record = { node.type === "optional_parameter" || node.type === "required_parameter" ), - selectDelimited( + delimitedSelector( (node) => node.type === "," || node.type === "(" || node.type === ")", ", " ) @@ -202,13 +205,13 @@ const nodeMatchers: Record = { // const foo = () => "hello" matcher( findPossiblyWrappedNode( - findNodeOfType("export_statement"), + typedNodeFinder("export_statement"), findNamedArrowFunction, (node) => [getDeclarationNode(node)] ) ) ), - comment: matcher(findNodeOfType("comment")), + comment: matcher(typedNodeFinder("comment")), }; export default nodeMatchers; diff --git a/src/nodeFinders.ts b/src/nodeFinders.ts index 6bd61574d9..3e5bba4754 100644 --- a/src/nodeFinders.ts +++ b/src/nodeFinders.ts @@ -1,17 +1,17 @@ import { SyntaxNode } from "web-tree-sitter"; import { NodeFinder } from "./Types"; -export const findNode = - (isTargetNode: (node: SyntaxNode) => boolean): NodeFinder => - (node: SyntaxNode) => { +export const findNode = ( + isTargetNode: (node: SyntaxNode) => boolean +): NodeFinder => { + return (node: SyntaxNode) => { return isTargetNode(node) ? node : null; }; +}; -export const findNodeOfType = - (...typeNames: string[]): NodeFinder => - (node: SyntaxNode) => { - return typeNames.includes(node.type) ? node : null; - }; +export const typedNodeFinder = (...typeNames: string[]): NodeFinder => { + return findNode((node) => typeNames.includes(node.type)); +}; /** * Creates a matcher that can match potentially wrapped nodes. For example diff --git a/src/nodeMatchers.ts b/src/nodeMatchers.ts index 9b7c8e2aa5..68712b5476 100644 --- a/src/nodeMatchers.ts +++ b/src/nodeMatchers.ts @@ -2,7 +2,7 @@ import { SyntaxNode } from "web-tree-sitter"; import { TextEditor } from "vscode"; import { NodeMatcher, NodeFinder, SelectionExtractor } from "./Types"; import { simpleSelectionExtractor } from "./nodeSelectors"; -import { findNodeOfType } from "./nodeFinders"; +import { typedNodeFinder } from "./nodeFinders"; export function matcher( finder: NodeFinder, @@ -19,17 +19,21 @@ export function composedMatcher( selector: SelectionExtractor = simpleSelectionExtractor ): NodeMatcher { return function (editor: TextEditor, initialNode: SyntaxNode) { - let returnNode: SyntaxNode | null = initialNode; + let returnNode: SyntaxNode = initialNode; for (const finder of finders) { - returnNode = returnNode ? finder(returnNode) : null; + const foundNode = finder(returnNode); + if (foundNode == null) { + return null; + } + returnNode = foundNode; } - return returnNode ? selector(editor, returnNode) : null; + return selector(editor, returnNode); }; } export function typeMatcher(...typeNames: string[]) { - return matcher(findNodeOfType(...typeNames)); + return matcher(typedNodeFinder(...typeNames)); } /** diff --git a/src/nodeSelectors.ts b/src/nodeSelectors.ts index 6984941d49..052e4b7ef3 100644 --- a/src/nodeSelectors.ts +++ b/src/nodeSelectors.ts @@ -75,7 +75,7 @@ function getPreviousNonDelimiterNode( return node; } -export function selectDelimited( +export function delimitedSelector( isDelimiterNode: (node: SyntaxNode) => boolean, defaultDelimiter: string ): SelectionExtractor { From c77d9f074b330d9c07bb258386062b025fbb1917 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:56:10 -0700 Subject: [PATCH 07/11] Refactor branch debug logging --- src/debug.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/debug.ts b/src/debug.ts index 9b22177b2e..cd8756a40d 100644 --- a/src/debug.ts +++ b/src/debug.ts @@ -3,21 +3,22 @@ import { SyntaxNode } from "web-tree-sitter"; export function logBranchTypes(getNodeAtLocation: any) { return (event: vscode.TextEditorSelectionChangeEvent) => { - const getBranch = (branch: SyntaxNode[]): SyntaxNode[] => { - if (branch[0].parent) { - return getBranch([branch[0].parent, ...branch]); - } - return branch; - }; - const location = new vscode.Location( vscode.window.activeTextEditor!.document.uri, event.selections[0] ); - const leaf: SyntaxNode = getNodeAtLocation(location); - const branch = getBranch([leaf]); - branch.forEach((node, i) => console.debug(">".repeat(i + 1), node.type)); - const leafText = leaf.text.replace(/\s+/g, " ").substring(0, 100); - console.debug(">".repeat(branch.length), `"${leafText}"`); + + const ancestors: SyntaxNode[] = []; + let node: SyntaxNode = getNodeAtLocation(location); + while (node.parent) { + ancestors.unshift(node.parent); + node = node.parent; + } + + ancestors.forEach((node, i) => console.debug(">".repeat(i + 1), node.type)); + const leafText = ancestors[ancestors.length - 1].text + .replace(/\s+/g, " ") + .substring(0, 100); + console.debug(">".repeat(ancestors.length), `"${leafText}"`); }; } From 9c3b2f12472f526eb9c2d0af31b70d10779eb889 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Tue, 6 Jul 2021 12:27:18 -0700 Subject: [PATCH 08/11] Fix purple color setting name --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ae18c1d1d5..ba42d753d4 100644 --- a/package.json +++ b/package.json @@ -151,8 +151,8 @@ "highContrast": "#f0b800" } }, - "cursorless.colors.mauve": { - "description": "Color to use for mauve symbols", + "cursorless.colors.purple": { + "description": "Color to use for purple symbols", "type": "object", "default": { "dark": "#de25ff", From 3a79d5d875204e9e650d84579bc6d11412259d99 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Tue, 6 Jul 2021 13:12:40 -0700 Subject: [PATCH 09/11] Add function and class name scopes --- src/Types.ts | 1 + src/languages/json.ts | 1 + src/languages/python.ts | 10 +++++++++- src/languages/typescript.ts | 4 ++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Types.ts b/src/Types.ts index 29f5ab9618..56b7257ea1 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -47,6 +47,7 @@ export type ScopeType = | "argumentOrParameter" | "arrowFunction" | "class" + | "className" | "comment" | "dictionary" | "functionCall" diff --git a/src/languages/json.ts b/src/languages/json.ts index aae7326acf..e10a6f725a 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -25,6 +25,7 @@ const nodeMatchers: Record = { ...getPojoMatchers(["object"], ["array"], isValue), ifStatement: notSupported, class: notSupported, + className: notSupported, statement: notSupported, arrowFunction: notSupported, functionCall: notSupported, diff --git a/src/languages/python.ts b/src/languages/python.ts index b828eba698..6338de0928 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -2,6 +2,7 @@ import { SyntaxNode } from "web-tree-sitter"; import { getPojoMatchers } from "./getPojoMatchers"; import { cascadingMatcher, + composedMatcher, matcher, notSupported, typeMatcher, @@ -149,7 +150,14 @@ const nodeMatchers: Record = { matcher(getNameNode), matcher((node) => (node.type === "assignment" ? getLeftNode(node) : null)) ), - functionName: notSupported, + functionName: composedMatcher([ + possiblyDecoratedDefinition("function_definition"), + getNameNode, + ]), + className: composedMatcher([ + possiblyDecoratedDefinition("class_definition"), + getNameNode, + ]), arrowFunction: typeMatcher("lambda"), functionCall: typeMatcher("call"), argumentOrParameter: matcher( diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index ae169c7fb6..f40d7b57b4 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -163,6 +163,10 @@ const nodeMatchers: Record = { composedMatcher([findClassPropertyArrowFunction, getNameNode]), composedMatcher([findNamedArrowFunction, getNameNode]) ), + className: composedMatcher([ + possiblyExportedDeclaration("class_declaration", "class"), + getNameNode, + ]), type: cascadingMatcher( // Typed parameters, properties, and functions matcher(findTypeNode, selectWithLeadingDelimiter), From 8365b2c5a485f1d5ecd92054e03e7f305d36e37d Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:02:21 -0700 Subject: [PATCH 10/11] Fixed name matching for exported/decorated nodes --- src/languages/python.ts | 8 ++++---- src/languages/typescript.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/languages/python.ts b/src/languages/python.ts index 6338de0928..e7c38b8cfe 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -14,7 +14,7 @@ import { getNameNode, } from "../treeSitterUtils"; import { - findNode, + nodeFinder, typedNodeFinder, findPossiblyWrappedNode, } from "../nodeFinders"; @@ -151,17 +151,17 @@ const nodeMatchers: Record = { matcher((node) => (node.type === "assignment" ? getLeftNode(node) : null)) ), functionName: composedMatcher([ - possiblyDecoratedDefinition("function_definition"), + typedNodeFinder("function_definition"), getNameNode, ]), className: composedMatcher([ - possiblyDecoratedDefinition("class_definition"), + typedNodeFinder("class_definition"), getNameNode, ]), arrowFunction: typeMatcher("lambda"), functionCall: typeMatcher("call"), argumentOrParameter: matcher( - findNode( + nodeFinder( (node) => (node.parent?.type === "argument_list" && ARGUMENT_TYPES.includes(node.type)) || diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index f40d7b57b4..0a0f0f9231 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -13,7 +13,7 @@ import { getValueNode, } from "../treeSitterUtils"; import { - findNode, + nodeFinder, typedNodeFinder, findPossiblyWrappedNode, } from "../nodeFinders"; @@ -164,7 +164,7 @@ const nodeMatchers: Record = { composedMatcher([findNamedArrowFunction, getNameNode]) ), className: composedMatcher([ - possiblyExportedDeclaration("class_declaration", "class"), + typedNodeFinder("class_declaration", "class"), getNameNode, ]), type: cascadingMatcher( @@ -179,7 +179,7 @@ const nodeMatchers: Record = { ) ), argumentOrParameter: matcher( - findNode( + nodeFinder( (node) => (node.parent?.type === "arguments" && (isExpression(node) || node.type === "spread_element")) || From 91f4ade4ae3e44242d36bac5f6a0893fb5332556 Mon Sep 17 00:00:00 2001 From: Brock McElroy <28877984+brxck@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:02:54 -0700 Subject: [PATCH 11/11] Clean up transformations refactor (again) --- src/debug.ts | 2 +- src/languages/getPojoMatchers.ts | 10 +++++----- src/languages/typescript.ts | 1 + src/nodeFinders.ts | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/debug.ts b/src/debug.ts index cd8756a40d..6a55eacd40 100644 --- a/src/debug.ts +++ b/src/debug.ts @@ -10,7 +10,7 @@ export function logBranchTypes(getNodeAtLocation: any) { const ancestors: SyntaxNode[] = []; let node: SyntaxNode = getNodeAtLocation(location); - while (node.parent) { + while (node.parent != null) { ancestors.unshift(node.parent); node = node.parent; } diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index b012ad813b..17ccea0b2e 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -1,12 +1,13 @@ import { SyntaxNode } from "web-tree-sitter"; -import { getNameNode, getKeyNode, getValueNode } from "../treeSitterUtils"; +import { getKeyNode, getValueNode } from "../treeSitterUtils"; import { delimitedSelector, selectWithLeadingDelimiter, } from "../nodeSelectors"; import { composedMatcher, matcher, typeMatcher } from "../nodeMatchers"; -import { findNode, typedNodeFinder } from "../nodeFinders"; +import { nodeFinder, typedNodeFinder } from "../nodeFinders"; +// Matchers for "plain old javascript objects", like those found in JSON export function getPojoMatchers( dictionaryTypes: string[], listTypes: string[], @@ -15,7 +16,7 @@ export function getPojoMatchers( return { dictionary: typeMatcher(...dictionaryTypes), pair: matcher( - findNode((node) => node.type === "pair"), + nodeFinder((node) => node.type === "pair"), delimitedSelector( (node) => node.type === "," || node.type === "}" || node.type === "{", ", " @@ -23,10 +24,9 @@ export function getPojoMatchers( ), pairKey: composedMatcher([typedNodeFinder("pair"), getKeyNode]), value: matcher(getValueNode, selectWithLeadingDelimiter), - name: matcher(getNameNode), list: typeMatcher(...listTypes), listElement: matcher( - findNode( + nodeFinder( (node) => listTypes.includes(node.parent?.type ?? "") && listElementMatcher(node) diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index 0a0f0f9231..d2fc604c3e 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -155,6 +155,7 @@ const nodeMatchers: Record = { statement: matcher(possiblyExportedDeclaration(...STATEMENT_TYPES)), arrowFunction: typeMatcher("arrow_function"), functionCall: typeMatcher("call_expression", "new_expression"), + name: matcher(getNameNode), functionName: cascadingMatcher( composedMatcher([ typedNodeFinder("function_declaration", "method_definition"), diff --git a/src/nodeFinders.ts b/src/nodeFinders.ts index 3e5bba4754..b0e89531fb 100644 --- a/src/nodeFinders.ts +++ b/src/nodeFinders.ts @@ -1,7 +1,7 @@ import { SyntaxNode } from "web-tree-sitter"; import { NodeFinder } from "./Types"; -export const findNode = ( +export const nodeFinder = ( isTargetNode: (node: SyntaxNode) => boolean ): NodeFinder => { return (node: SyntaxNode) => { @@ -10,7 +10,7 @@ export const findNode = ( }; export const typedNodeFinder = (...typeNames: string[]): NodeFinder => { - return findNode((node) => typeNames.includes(node.type)); + return nodeFinder((node) => typeNames.includes(node.type)); }; /**