diff --git a/src/Types.ts b/src/Types.ts index 5f6587e21c..79d38783d2 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -58,6 +58,7 @@ export type ScopeType = | "pairKey" | "statement" | "string" + | "type" | "value"; export type PieceType = "subtoken" | "character"; diff --git a/src/languages/getPojoMatchers.ts b/src/languages/getPojoMatchers.ts index e45e5635c7..ff110ce27a 100644 --- a/src/languages/getPojoMatchers.ts +++ b/src/languages/getPojoMatchers.ts @@ -5,6 +5,8 @@ import { hasType, simpleSelectionExtractor, makeRange, + childNodeMatcher, + getNodeWithLeadingDelimiter, } from "../nodeMatchers"; import { getKeyNode, getValueNode } from "../treeSitterUtils"; @@ -27,27 +29,7 @@ export function getPojoMatchers( return simpleSelectionExtractor(getKeyNode(node)!); }, - value(editor: TextEditor, node: SyntaxNode) { - const valueNode = getValueNode(node); - - if (valueNode == null) { - return null; - } - - const leadingDelimiterToken = valueNode.previousSibling!; - - const leadingDelimiterRange = makeRange( - leadingDelimiterToken.startPosition, - valueNode.startPosition - ); - - return { - ...simpleSelectionExtractor(valueNode), - context: { - leadingDelimiterRange, - }, - }; - }, + value: childNodeMatcher(getValueNode, getNodeWithLeadingDelimiter), list: hasType(...listTypes), listElement: delimitedMatcher( (node) => diff --git a/src/languages/json.ts b/src/languages/json.ts index d703932243..b005f5f681 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -31,6 +31,7 @@ const nodeMatchers: Record = { argumentOrParameter: notSupported, namedFunction: notSupported, comment: notSupported, + type: notSupported, }; export default nodeMatchers; diff --git a/src/languages/python.ts b/src/languages/python.ts index 445e03e35a..bdfe1bea00 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -1,13 +1,13 @@ import { SyntaxNode } from "web-tree-sitter"; import { getPojoMatchers } from "./getPojoMatchers"; import { + childNodeMatcher, delimitedMatcher, + getNodeWithLeadingDelimiter, hasType, possiblyWrappedNode, - simpleSelectionExtractor, } from "../nodeMatchers"; import { NodeMatcher, ScopeType } from "../Types"; -import { TextEditor } from "vscode"; import { getDefinitionNode } from "../treeSitterUtils"; // TODO figure out how to properly use super types @@ -121,6 +121,9 @@ function possiblyDecoratedDefinition(...typeNames: string[]): NodeMatcher { ); } +export const getTypeNode = (node: SyntaxNode) => + node.children.find((child) => child.type === "type") ?? null; + const nodeMatchers: Record = { ...getPojoMatchers( ["dictionary", "dictionary_comprehension"], @@ -143,6 +146,7 @@ const nodeMatchers: Record = { ), namedFunction: possiblyDecoratedDefinition("function_definition"), comment: hasType("comment"), + type: childNodeMatcher(getTypeNode, getNodeWithLeadingDelimiter), }; export default nodeMatchers; diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index fb016219c3..f72f2dc7e7 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -7,6 +7,8 @@ import { hasType, possiblyWrappedNode, simpleSelectionExtractor, + getNodeWithLeadingDelimiter, + childNodeMatcher, } from "../nodeMatchers"; import { NodeMatcher, ScopeType } from "../Types"; import { getDeclarationNode, getValueNode } from "../treeSitterUtils"; @@ -120,6 +122,13 @@ const isNamedArrowFunction = (node: SyntaxNode) => { ); }; +export const getTypeNode = (node: SyntaxNode) => { + const typeAnnotationNode = node.children.find((child) => + ["type_annotation", "opting_type_annotation"].includes(child.type) + ); + return typeAnnotationNode?.lastChild ?? null; +}; + const nodeMatchers: Record = { ...getPojoMatchers( ["object"], @@ -131,6 +140,16 @@ const nodeMatchers: Record = { statement: possiblyExportedDeclaration(...STATEMENT_TYPES), arrowFunction: hasType("arrow_function"), functionCall: hasType("call_expression", "new_expression"), + type: cascadingMatcher( + // Typed parameters, properties, and functions + childNodeMatcher(getTypeNode, getNodeWithLeadingDelimiter), + + // Type alias/interface declarations + possiblyExportedDeclaration( + "type_alias_declaration", + "interface_declaration" + ) + ), argumentOrParameter: delimitedMatcher( (node) => (node.parent?.type === "arguments" && diff --git a/src/nodeMatchers.ts b/src/nodeMatchers.ts index 8feceb949f..17d92c810b 100644 --- a/src/nodeMatchers.ts +++ b/src/nodeMatchers.ts @@ -7,6 +7,39 @@ export function hasType(...typeNames: string[]): NodeMatcher { typeNames.includes(node.type) ? simpleSelectionExtractor(node) : null; } +export function childNodeMatcher( + getMatchingChildNode: (node: SyntaxNode) => SyntaxNode | null, + extractor: (node: SyntaxNode) => SelectionWithContext +): NodeMatcher { + return (editor: TextEditor, node: SyntaxNode) => { + const returnNode = getMatchingChildNode(node); + + if (returnNode == null) { + return null; + } + + return extractor(returnNode); + }; +} + +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