Migrate typescript scopes (#2040)

Fixes #1892

## Checklist

- [x] I have added
[tests](https://www.cursorless.org/docs/contributing/test-case-recorder/)
- [-] I have updated the
[docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and
[cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet)
- [-] I have not broken the cheatsheet

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Pokey Rule <755842+pokey@users.noreply.github.com>
This commit is contained in:
Andreas Arvidsson 2023-11-25 14:24:17 +01:00 committed by GitHub
parent 0e266f0be3
commit 2af63fdcfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 415 additions and 222 deletions

View File

@ -8,7 +8,6 @@ import { getNodeMatcher } from "./getNodeMatcher";
import { stringTextFragmentExtractor as htmlStringTextFragmentExtractor } from "./html";
import { stringTextFragmentExtractor as rubyStringTextFragmentExtractor } from "./ruby";
import { stringTextFragmentExtractor as scssStringTextFragmentExtractor } from "./scss";
import { stringTextFragmentExtractor as typescriptStringTextFragmentExtractor } from "./typescript";
export type TextFragmentExtractor = (
node: SyntaxNode,
@ -131,14 +130,6 @@ const textFragmentExtractors: Record<
"html",
htmlStringTextFragmentExtractor,
),
javascript: constructDefaultTextFragmentExtractor(
"javascript",
typescriptStringTextFragmentExtractor,
),
javascriptreact: constructDefaultTextFragmentExtractor(
"javascriptreact",
typescriptStringTextFragmentExtractor,
),
latex: fullDocumentTextFragmentExtractor,
markdown: fullDocumentTextFragmentExtractor,
ruby: constructDefaultTextFragmentExtractor(
@ -154,14 +145,6 @@ const textFragmentExtractors: Record<
scssStringTextFragmentExtractor,
),
rust: constructDefaultTextFragmentExtractor("rust"),
typescript: constructDefaultTextFragmentExtractor(
"typescript",
typescriptStringTextFragmentExtractor,
),
typescriptreact: constructDefaultTextFragmentExtractor(
"typescriptreact",
typescriptStringTextFragmentExtractor,
),
xml: constructDefaultTextFragmentExtractor(
"xml",
htmlStringTextFragmentExtractor,

View File

@ -1,160 +1,12 @@
import { SimpleScopeTypeType } from "@cursorless/common";
import type { SyntaxNode } from "web-tree-sitter";
import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types";
import { patternFinder } from "../util/nodeFinders";
import {
argumentMatcher,
cascadingMatcher,
conditionMatcher,
createPatternMatchers,
matcher,
patternMatcher,
trailingMatcher,
} from "../util/nodeMatchers";
import {
childRangeSelector,
extendForwardPastOptional,
getNodeInternalRange,
getNodeRange,
unwrapSelectionExtractor,
} from "../util/nodeSelectors";
import { branchMatcher } from "./branchMatcher";
import { elseExtractor, elseIfExtractor } from "./elseIfExtractor";
import { ternaryBranchMatcher } from "./ternaryBranchMatcher";
// Generated by the following command:
// > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-typescript/4c20b54771e4b390ee058af2930feb2cd55f2bf8/typescript/src/node-types.json \
// | jq '[.[] | select(.type == "statement" or .type == "declaration") | .subtypes[].type]'
const STATEMENT_TYPES = [
"abstract_class_declaration",
"ambient_declaration",
"break_statement",
"class_declaration",
"continue_statement",
"debugger_statement",
"declaration",
"do_statement",
"empty_statement",
"enum_declaration",
"export_statement",
"expression_statement",
"for_in_statement",
"for_statement",
"function_declaration",
"function_signature",
"generator_function_declaration",
"if_statement",
"import_alias",
"import_statement",
"interface_declaration",
"internal_module",
"labeled_statement",
"lexical_declaration",
"module",
"return_statement",
// "statement_block", This is disabled since we want the whole statement and not just the block
"switch_statement",
"throw_statement",
"try_statement",
"type_alias_declaration",
"variable_declaration",
"while_statement",
"with_statement",
];
const mapTypes = ["object", "object_pattern"];
const listTypes = ["array", "array_pattern"];
import { NodeMatcherAlternative } from "../typings/Types";
import { argumentMatcher, createPatternMatchers } from "../util/nodeMatchers";
const nodeMatchers: Partial<
Record<SimpleScopeTypeType, NodeMatcherAlternative>
> = {
map: mapTypes,
list: listTypes,
string: ["string", "template_string"],
collectionItem: "jsx_attribute",
collectionKey: trailingMatcher(
[
"pair[key]",
"jsx_attribute.property_identifier!",
"object_type.property_signature[name]!",
"shorthand_property_identifier",
],
[":"],
),
ifStatement: "if_statement",
comment: "comment",
regularExpression: "regex",
className: ["class_declaration[name]", "class[name]"],
functionCall: ["call_expression", "new_expression"],
functionCallee: cascadingMatcher(
patternMatcher("call_expression[function]"),
matcher(
patternFinder("new_expression"),
childRangeSelector(["arguments"], []),
),
),
statement: cascadingMatcher(
matcher(
patternFinder(
"property_signature",
"public_field_definition",
"abstract_method_signature",
),
extendForwardPastOptional(";"),
),
patternMatcher(
...STATEMENT_TYPES.map((type) => `export_statement?.${type}`),
"method_definition",
),
),
condition: cascadingMatcher(
patternMatcher("ternary_expression[condition]"),
conditionMatcher(
"if_statement[condition]",
"for_statement[condition]",
"while_statement[condition]",
"do_statement[condition]",
),
),
["private.switchStatementSubject"]: matcher(
patternFinder("switch_statement[value]"),
unwrapSelectionExtractor,
),
branch: cascadingMatcher(
patternMatcher("switch_case"),
matcher(patternFinder("else_clause"), elseExtractor("if_statement")),
matcher(patternFinder("if_statement"), elseIfExtractor()),
branchMatcher("try_statement", ["catch_clause", "finally_clause"]),
ternaryBranchMatcher("ternary_expression", [1, 2]),
),
class: [
"export_statement?.class_declaration", // export class | class
"export_statement?.abstract_class_declaration", // export abstract class | abstract class
"export_statement.class", // export default class
],
argumentOrParameter: argumentMatcher("formal_parameters", "arguments"),
// XML, JSX
attribute: ["jsx_attribute"],
};
export const patternMatchers = createPatternMatchers(nodeMatchers);
export function stringTextFragmentExtractor(
node: SyntaxNode,
_selection: SelectionWithEditor,
) {
if (
node.type === "string_fragment" ||
node.type === "regex_pattern" ||
node.type === "jsx_text"
) {
return getNodeRange(node);
}
if (node.type === "template_string") {
// Exclude starting and ending quotation marks
return getNodeInternalRange(node);
}
return null;
}

View File

@ -13,7 +13,7 @@ initialState:
active: {line: 0, character: 24}
marks: {}
finalState:
documentContents: let test = new ()
documentContents: let test = ()
selections:
- anchor: {line: 0, character: 15}
active: {line: 0, character: 15}
- anchor: {line: 0, character: 11}
active: {line: 0, character: 11}

View File

@ -13,7 +13,7 @@ initialState:
active: {line: 0, character: 28}
marks: {}
finalState:
documentContents: let test = new ()
documentContents: let test = ()
selections:
- anchor: {line: 0, character: 15}
active: {line: 0, character: 15}
- anchor: {line: 0, character: 11}
active: {line: 0, character: 11}

View File

@ -13,7 +13,7 @@ initialState:
active: {line: 0, character: 24}
marks: {}
finalState:
documentContents: let test = new ()
documentContents: let test = ()
selections:
- anchor: {line: 0, character: 15}
active: {line: 0, character: 15}
- anchor: {line: 0, character: 11}
active: {line: 0, character: 11}

View File

@ -0,0 +1,23 @@
languageId: typescript
command:
version: 6
spokenForm: chuck type
action:
name: remove
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: type}
usePrePhraseSnapshot: true
initialState:
documentContents: <Type>foo
selections:
- anchor: {line: 0, character: 1}
active: {line: 0, character: 1}
marks: {}
finalState:
documentContents: foo
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}

View File

@ -1,8 +1,8 @@
languageId: typescript
command:
version: 1
spokenForm: take every key
action: setSelection
spokenForm: change every key
action: clearAndSetSelection
targets:
- type: primitive
modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true}
@ -19,11 +19,11 @@ initialState:
finalState:
documentContents: |-
const value = {
key1: "hello",
key2: "there",
: "hello",
: "there",
};
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 8}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 8}
active: {line: 2, character: 4}

View File

@ -1,8 +1,8 @@
languageId: typescript
command:
version: 1
spokenForm: take every key
action: setSelection
spokenForm: change every key
action: clearAndSetSelection
targets:
- type: primitive
modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true}
@ -19,11 +19,11 @@ initialState:
finalState:
documentContents: |-
{
foo: "hello",
bar,
: "hello",
,
}
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 7}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 7}
active: {line: 2, character: 4}

View File

@ -352,20 +352,9 @@
(#not-parent-type? @_.domain expression_statement)
)
;; Match nodes at field `value` of their parent node, setting leading delimiter
;; to be the range until the previous named node
(
(_
(_)? @value.leading.start.endOf
.
value: (_) @value @value.leading.end.startOf
) @_.domain
(#not-type? @_.domain variable_declarator)
)
;;!! const aaa = {bbb};
;;! ^^^
(shorthand_property_identifier) @value
(shorthand_property_identifier) @collectionKey @value
;;!! return 0;
;;! ^
@ -415,6 +404,263 @@
;;!! const aaa = {bbb: 0, ccc: 0};
;;! **************
(object
"{" @value.iteration.start.endOf
"}" @value.iteration.end.startOf
"{" @collectionKey.iteration.start.endOf @value.iteration.start.endOf
"}" @collectionKey.iteration.end.startOf @value.iteration.end.startOf
)
;;!! "string"
;;!! `string`
;;! ^^^^^^^^
[
(string)
(template_string)
] @string
;;!! // comment
;;! ^^^^^^^^^^
(comment) @comment
;;!! /\w+/
;;! ^^^^^
(regex) @regularExpression
[
(string_fragment)
(comment)
(regex_pattern)
] @textFragment
(
(template_string) @textFragment
(#child-range! @textFragment 0 -1 true true)
)
;;!! { value: 0 }
;;! ^^^^^^^^^^^^
[
(object)
(object_pattern)
] @map
;;!! [ 0 ]
;;! ^^^^^
[
(array)
(array_pattern)
] @list
;;!! if () {}
;;! ^^^^^^^^
(if_statement) @ifStatement
;;!! switch (value) {}
;;! ^^^^^
;;! -----------------
(switch_statement
value: (_) @private.switchStatementSubject
(#child-range! @private.switchStatementSubject 0 -1 true true)
) @_.domain
;;!! foo()
;;! ^^^^^
;;!! new Foo()
;;! ^^^^^^^^^
[
(call_expression)
(new_expression)
] @functionCall
;;!! foo()
;;! ^^^
;;! -----
(call_expression
function: (_) @functionCallee
) @_.domain
;;!! new Foo()
;;! ^^^^^^^
;;! ---------
(new_expression
(arguments) @functionCallee.end.startOf
) @functionCallee.start.startOf @_.domain
;;!! class Foo {}
;;! ^^^^^^^^^^^^
(
[
(class_declaration
name: (_) @className
)
(class
name: (_) @className
)
] @class @_.domain
(#not-parent-type? @class export_statement)
)
;;!! export class Foo {}
;;! ^^^^^^^^^^^^^^^^^^^
(export_statement
[
(class_declaration
name: (_) @className
)
(class
name: (_) @className
)
]
) @class @_.domain
;;!! true ? 0 : 1;
;;! ^^^^
;;! ^ ^
;;! --------------
(ternary_expression
condition: (_) @condition
consequence: (_) @branch
) @condition.domain
(ternary_expression
alternative: (_) @branch
)
;;!! for (let i = 0; i < 2; ++i) {}
;;! ^^^^^
;;! ------------------------------
(for_statement
condition: (_) @condition
(#child-range! @condition 0 -1 false true)
) @_.domain
;;!! while (true) {}
;;! ^^^^
(while_statement
condition: (_) @condition
(#child-range! @condition 0 -1 true true)
) @_.domain
;;!! do {} while (true);
;;! ^^^^
(do_statement
condition: (_) @condition
(#child-range! @condition 0 -1 true true)
) @_.domain
;;!! case 0: {}
;;! ^^^^^^^^^^
;;! ^
(switch_case
value: (_) @condition
) @branch @condition.domain
;;!! switch () {}
;;! ^^^^^^^^^^^^
(switch_statement) @branch.iteration @condition.iteration
;;!! if () {}
;;! ^^^^^^^^
(
(if_statement
condition: (_) @condition
consequence: (_) @branch.end.endOf @branch.removal.end.endOf
alternative: (_
(if_statement) @branch.removal.end.startOf
)?
) @branch.start.startOf @branch.removal.start.startOf @condition.domain
(#not-parent-type? @condition.domain "else_clause")
(#child-range! @condition 0 -1 true true)
)
;;!! else if () {}
;;! ^^^^^^^^^^^^^
(else_clause
(if_statement
condition: (_) @condition
consequence: (_) @branch.end.endOf @condition.domain.end.endOf
)
(#child-range! @condition 0 -1 true true)
) @branch.start.startOf @condition.domain.start.startOf
;;!! else {}
;;! ^^^^^^^
(else_clause
(statement_block)
) @branch
;;!! if () {} else if () {} else {}
;;! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(
(if_statement) @branch.iteration
(#not-parent-type? @branch.iteration "else_clause")
)
;;!! try () {}
;;! ^^^^^^^^^
(try_statement
"try" @branch.start
body: (_) @branch.end
)
;;!! catch () {}
;;! ^^^^^^^^^^^
(try_statement
handler: (_) @branch
)
;;!! finally {}
;;! ^^^^^^^^^^
(try_statement
finalizer: (_) @branch
)
;;!! try () {} catch () {} finally {}
;;! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(try_statement) @branch.iteration
;;!! { value: 0 }
;;! ^^^^^
;;! xxxxxxx
;;!! { value: 0 }
;;! ^
;;! xxx
;;! --------
(pair
key: (_) @collectionKey @collectionKey.trailing.start.endOf @value.leading.start.endOf
value: (_) @value @collectionKey.trailing.end.startOf @value.leading.end.startOf
) @_.domain
;; Statements that are not a child of an export statement
;; Generated by the following command:
;; > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-typescript/4c20b54771e4b390ee058af2930feb2cd55f2bf8/typescript/src/node-types.json \
;; | jq '[.[] | select(.type == "statement" or .type == "declaration") | .subtypes[].type]'
(
[
(break_statement)
(class_declaration)
(continue_statement)
(debugger_statement)
(declaration)
(do_statement)
(empty_statement)
(export_statement)
(expression_statement)
(for_in_statement)
(for_statement)
(generator_function_declaration)
(if_statement)
(import_statement)
(labeled_statement)
(lexical_declaration)
(return_statement)
;; (statement_block), This is disabled since we want the whole statement and not just the block
(switch_statement)
(throw_statement)
(try_statement)
(variable_declaration)
(while_statement)
(with_statement)
;; Manually added1
(method_definition)
] @statement
(#not-parent-type? @statement export_statement)
)

View File

@ -82,6 +82,16 @@
">" @name.startOf @_.domain.end
)
;;!! <aaa bbb="ccc" />
;;! ^^^^^^^^^
(jsx_attribute) @attribute
;;!! <aaa bbb="ccc" />
;;! ^^^
(jsx_attribute
(property_identifier) @collectionKey
) @_.domain
;;!! <aaa bbb="ccc" />
;;! ^^^^^
;;! xxxxxx
@ -91,12 +101,20 @@
(_) @value @value.leading.end.startOf
) @_.domain
;;!! <aaa />
;;! ^^^^
(jsx_self_closing_element
"<" @value.iteration.start.endOf
"/" @value.iteration.end.startOf
"<" @attribute.iteration.start.endOf @collectionKey.iteration.start.endOf @value.iteration.start.endOf
"/" @attribute.iteration.end.startOf @collectionKey.iteration.end.startOf @value.iteration.end.startOf
)
;;!! <aaa></aaa>
;;! ^^^
(jsx_opening_element
"<" @value.iteration.start.endOf
">" @value.iteration.end.startOf
"<" @attribute.iteration.start.endOf @collectionKey.iteration.start.endOf @value.iteration.start.endOf
">" @attribute.iteration.end.startOf @collectionKey.iteration.end.startOf @value.iteration.end.startOf
)
;;!! <div>text</div>
;;! ^^^^
(jsx_text) @textFragment

View File

@ -151,21 +151,28 @@
(#has-multiple-children-of-type? @dummy variable_declarator)
)
;; Generic type matcher
(
;;!! function ccc(aaa: string, bbb?: string) {}
;;! ^^^^^^ ^^^^^^
(formal_parameters
(_
[
type: (_
(_) @type
)
return_type: (_
(_) @type
)
] @type.removal
pattern: (_) @type.leading.start.endOf
type: (_
":"
(_) @type @type.leading.end.startOf
)
) @_.domain
(#not-type? @_.domain variable_declarator)
)
;;!! function ccc(): string {}
;;! ^^^^^^
(function_declaration
parameters: (_) @type.leading.end.endOf
return_type: (_
":"
(_) @type @type.leading.end.startOf
)
) @_.domain
;;!! new Aaa<Bbb>()
;;! ^^^^^^^^
(new_expression
@ -180,7 +187,8 @@
;;! ^^^^^^ ^^^^^^
(type_arguments
(_) @type
)
(#not-parent-type? @dummy type_assertion)
) @dummy
;;!! function foo<A>() {}
;;! ^
@ -229,3 +237,69 @@
(predefined_type)
] @type @_.leading.end.startOf
) @_.domain
;;!! abstract class MyClass {}
;;! ^^^^^^^^^^^^^^^^^^^^^^^^^
(
(abstract_class_declaration
name: (_) @className
) @class @_.domain
(#not-parent-type? @class export_statement)
)
;;!! export abstract class MyClass {}
;;! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(export_statement
(abstract_class_declaration
name: (_) @className
)
) @class @_.domain
;;!! class MyClass {}
;;! ^^^^^^^
;;! ----------------
(abstract_class_declaration
name: (_) @name
) @_.domain
;;!! interface Type { name: string; }
;;! ^^^^
;;! xxxxxx
;;! ------------
(property_signature
name: (_) @collectionKey @collectionKey.trailing.start.endOf @type.leading.start.endOf
type: (_
":"
(_) @type @collectionKey.trailing.end.startOf @type.leading.end.startOf
)
) @_.domain
;;!! interface Type { name: string; }
;;! ^^^^^^^^^^^^^^^^^
(object_type) @collectionKey.iteration
;; Non-exported statements
(
[
(ambient_declaration)
(abstract_class_declaration)
(enum_declaration)
(function_signature)
(import_alias)
(interface_declaration)
(internal_module)
(module)
(type_alias_declaration)
] @statement
(#not-parent-type? @statement export_statement)
)
;; Statements with optional trailing `;`
(
[
(property_signature)
(public_field_definition)
(abstract_method_signature)
] @statement.start
";"? @statement.end
)

View File

@ -8,9 +8,6 @@
;;! --------
(type_assertion
(type_arguments
[
(generic_type)
(predefined_type)
] @type
(_) @type
) @_.removal
) @_.domain