From 65fab51e0bb76a2555883d9a28c0fbce25da432a Mon Sep 17 00:00:00 2001
From: Pokey Rule <755842+pokey@users.noreply.github.com>
Date: Tue, 6 Jun 2023 08:12:18 -0400
Subject: [PATCH] Support jsx tag scopes using tree-sitter queries (#1508)
- Fixes #1478
- Depends on https://github.com/cursorless-dev/cursorless/pull/1507
## Checklist
- [x] Support "every name" while we're here
- [x] Arg lists
- [x] Functions in class
- [x] Assignment statements in function body
- [x] Assignment statements at top level
- [x] Properties in class
- [x] Members of interface
- [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>
---
.../src/languages/typescript.ts | 59 +-----------------
.../src/util/nodeSelectors.ts | 19 ------
.../typescript/clearEveryElement.yml | 32 ++++++++++
.../typescript/clearEveryElement2.yml | 32 ++++++++++
.../typescript/clearEveryElement3.yml | 32 ++++++++++
.../typescript/clearEveryElement4.yml | 32 ++++++++++
.../languages/typescript/clearEveryEndTag.yml | 32 ++++++++++
.../typescript/clearEveryEndTag2.yml | 32 ++++++++++
.../languages/typescript/clearEveryName.yml | 28 +++++++++
.../languages/typescript/clearEveryName2.yml | 32 ++++++++++
.../languages/typescript/clearEveryName3.yml | 28 +++++++++
.../languages/typescript/clearEveryName4.yml | 36 +++++++++++
.../languages/typescript/clearEveryName5.yml | 24 ++++++++
.../languages/typescript/clearEveryName6.yml | 30 ++++++++++
.../typescript/clearEveryStartTag.yml | 32 ++++++++++
.../typescript/clearEveryStartTag2.yml | 32 ++++++++++
.../languages/typescript/clearEveryTags.yml | 36 +++++++++++
.../languages/typescript/clearEveryTags2.yml | 36 +++++++++++
.../languages/typescript/clearNameTags.yml | 26 ++++++++
queries/javascript.core.scm | 29 +++++++++
queries/javascript.jsx.scm | 60 +++++++++++++++++++
queries/javascript.scm | 5 ++
queries/javascriptreact.scm | 1 +
queries/typescript.scm | 13 ++++
queries/typescriptreact.scm | 2 +
25 files changed, 644 insertions(+), 76 deletions(-)
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement2.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement3.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement4.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag2.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName2.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName3.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName4.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName5.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName6.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag2.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags2.yml
create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearNameTags.yml
create mode 100644 queries/javascript.core.scm
create mode 100644 queries/javascript.jsx.scm
create mode 100644 queries/javascript.scm
create mode 100644 queries/javascriptreact.scm
create mode 100644 queries/typescript.scm
create mode 100644 queries/typescriptreact.scm
diff --git a/packages/cursorless-engine/src/languages/typescript.ts b/packages/cursorless-engine/src/languages/typescript.ts
index 206e22d378..04d8bca7da 100644
--- a/packages/cursorless-engine/src/languages/typescript.ts
+++ b/packages/cursorless-engine/src/languages/typescript.ts
@@ -1,11 +1,11 @@
-import { Selection, SimpleScopeTypeType, TextEditor } from "@cursorless/common";
+import { SimpleScopeTypeType } from "@cursorless/common";
import type { SyntaxNode } from "web-tree-sitter";
import {
NodeMatcher,
NodeMatcherAlternative,
SelectionWithEditor,
} from "../typings/Types";
-import { patternFinder, typedNodeFinder } from "../util/nodeFinders";
+import { patternFinder } from "../util/nodeFinders";
import {
argumentMatcher,
cascadingMatcher,
@@ -20,12 +20,10 @@ import {
extendForwardPastOptional,
getNodeInternalRange,
getNodeRange,
- jsxFragmentExtractor,
pairSelectionExtractor,
selectWithLeadingDelimiter,
simpleSelectionExtractor,
unwrapSelectionExtractor,
- xmlElementExtractor,
} from "../util/nodeSelectors";
import { branchMatcher } from "./branchMatcher";
import { elseExtractor, elseIfExtractor } from "./elseIfExtractor";
@@ -71,42 +69,6 @@ const STATEMENT_TYPES = [
"with_statement",
];
-/** Handles jsx fragment start or end tag, eg the `<>` in `<>foo>` **/
-function getJsxFragmentTag(isStartTag: boolean): NodeMatcher {
- return matcher(
- typedNodeFinder("jsx_fragment"),
- (editor: TextEditor, node: SyntaxNode) => {
- const [start, end] = isStartTag
- ? [node.children[0], node.children[1]]
- : [node.children.at(-3)!, node.children.at(-1)!];
- return {
- selection: new Selection(
- start.startPosition.row,
- start.startPosition.column,
- end.endPosition.row,
- end.endPosition.column,
- ),
- context: {},
- };
- },
- );
-}
-
-const getStartTag = cascadingMatcher(
- patternMatcher("jsx_element.jsx_opening_element!"),
- getJsxFragmentTag(true),
-);
-const getEndTag = cascadingMatcher(
- patternMatcher("jsx_element.jsx_closing_element!"),
- getJsxFragmentTag(false),
-);
-
-const getTags = (selection: SelectionWithEditor, node: SyntaxNode) => {
- const startTag = getStartTag(selection, node);
- const endTag = getEndTag(selection, node);
- return startTag != null && endTag != null ? startTag.concat(endTag) : null;
-};
-
function typeMatcher(): NodeMatcher {
const delimiterSelector = selectWithLeadingDelimiter(":");
return function (selection: SelectionWithEditor, node: SyntaxNode) {
@@ -214,13 +176,6 @@ const nodeMatchers: Partial<
),
ifStatement: "if_statement",
anonymousFunction: ["arrow_function", "function"],
- name: [
- "*[name]",
- "optional_parameter.identifier!",
- "required_parameter.identifier!",
- "augmented_assignment_expression[left]",
- "assignment_expression[left]",
- ],
comment: "comment",
regularExpression: "regex",
className: ["class_declaration[name]", "class[name]"],
@@ -337,16 +292,6 @@ const nodeMatchers: Partial<
argumentOrParameter: argumentMatcher("formal_parameters", "arguments"),
// XML, JSX
attribute: ["jsx_attribute"],
- xmlElement: cascadingMatcher(
- matcher(
- typedNodeFinder("jsx_element", "jsx_self_closing_element"),
- xmlElementExtractor,
- ),
- matcher(typedNodeFinder("jsx_fragment"), jsxFragmentExtractor),
- ),
- xmlBothTags: getTags,
- xmlStartTag: getStartTag,
- xmlEndTag: getEndTag,
};
export const patternMatchers = createPatternMatchers(nodeMatchers);
diff --git a/packages/cursorless-engine/src/util/nodeSelectors.ts b/packages/cursorless-engine/src/util/nodeSelectors.ts
index 54ba7e6e25..23e5f77244 100644
--- a/packages/cursorless-engine/src/util/nodeSelectors.ts
+++ b/packages/cursorless-engine/src/util/nodeSelectors.ts
@@ -427,25 +427,6 @@ export function xmlElementExtractor(
return selection;
}
-export function jsxFragmentExtractor(
- editor: TextEditor,
- node: SyntaxNode,
-): SelectionWithContext {
- const selection = simpleSelectionExtractor(editor, node);
-
- // Interior range for an element is found by excluding the start and end nodes.
- const startPosition = node.children[1].endPosition;
- const endPosition = node.children.at(-3)!.startPosition;
- selection.context.interiorRange = new Range(
- startPosition.row,
- startPosition.column,
- endPosition.row,
- endPosition.column,
- );
-
- return selection;
-}
-
export function getInsertionDelimiter(
editor: TextEditor,
leadingDelimiterRange: Range | undefined,
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement.yml
new file mode 100644
index 0000000000..2781402059
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every element
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlElement}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+
+
+
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement2.yml
new file mode 100644
index 0000000000..e55b5bb079
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement2.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every element
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlElement}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ <>
+ ccc
+ ddd
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+ <>
+
+
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement3.yml
new file mode 100644
index 0000000000..0cb1aab6f1
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement3.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every element
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlElement}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ <>
+ <>ccc>
+ <>ddd>
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+ <>
+
+
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement4.yml
new file mode 100644
index 0000000000..3e7b333ad8
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryElement4.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every element
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlElement}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+
+ <>ccc>
+ <>ddd>
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+
+
+
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag.yml
new file mode 100644
index 0000000000..8d617f046b
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every end tag
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlEndTag}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ <>
+ <>ccc>
+ <>ddd>
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+ <>
+ <>ccc
+ <>ddd
+ >
+ selections:
+ - anchor: {line: 1, character: 9}
+ active: {line: 1, character: 9}
+ - anchor: {line: 2, character: 9}
+ active: {line: 2, character: 9}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag2.yml
new file mode 100644
index 0000000000..e0cf807f97
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryEndTag2.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every end tag
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlEndTag}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 12}
+ active: {line: 1, character: 12}
+ - anchor: {line: 2, character: 12}
+ active: {line: 2, character: 12}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName.yml
new file mode 100644
index 0000000000..308c953690
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName.yml
@@ -0,0 +1,28 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ const aaa = "bbb";
+ const ccc = "ddd";
+ selections:
+ - anchor: {line: 1, character: 18}
+ active: {line: 1, character: 18}
+ marks: {}
+finalState:
+ documentContents: |-
+ const = "bbb";
+ const = "ddd";
+ selections:
+ - anchor: {line: 0, character: 6}
+ active: {line: 0, character: 6}
+ - anchor: {line: 1, character: 6}
+ active: {line: 1, character: 6}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName2.yml
new file mode 100644
index 0000000000..0dc9c29572
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName2.yml
@@ -0,0 +1,32 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ function eee() {
+ const aaa = "bbb";
+ const ccc = "ddd";
+ }
+ selections:
+ - anchor: {line: 2, character: 22}
+ active: {line: 2, character: 22}
+ marks: {}
+finalState:
+ documentContents: |-
+ function eee() {
+ const = "bbb";
+ const = "ddd";
+ }
+ selections:
+ - anchor: {line: 1, character: 10}
+ active: {line: 1, character: 10}
+ - anchor: {line: 2, character: 10}
+ active: {line: 2, character: 10}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName3.yml
new file mode 100644
index 0000000000..c10620d5f4
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName3.yml
@@ -0,0 +1,28 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ function eee() {}
+ function fff() {}
+ selections:
+ - anchor: {line: 1, character: 17}
+ active: {line: 1, character: 17}
+ marks: {}
+finalState:
+ documentContents: |-
+ function () {}
+ function () {}
+ selections:
+ - anchor: {line: 0, character: 9}
+ active: {line: 0, character: 9}
+ - anchor: {line: 1, character: 9}
+ active: {line: 1, character: 9}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName4.yml
new file mode 100644
index 0000000000..5e6295a034
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName4.yml
@@ -0,0 +1,36 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ class Aaa {
+ bbb = 0;
+ eee() {}
+ fff() {}
+ }
+ selections:
+ - anchor: {line: 3, character: 12}
+ active: {line: 3, character: 12}
+ marks: {}
+finalState:
+ documentContents: |-
+ class Aaa {
+ = 0;
+ () {}
+ () {}
+ }
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
+ - anchor: {line: 3, character: 4}
+ active: {line: 3, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName5.yml
new file mode 100644
index 0000000000..16a7b4e0d7
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName5.yml
@@ -0,0 +1,24 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: "function aaa(bbb: string, ccc: string) {}"
+ selections:
+ - anchor: {line: 0, character: 37}
+ active: {line: 0, character: 37}
+ marks: {}
+finalState:
+ documentContents: "function aaa(: string, : string) {}"
+ selections:
+ - anchor: {line: 0, character: 13}
+ active: {line: 0, character: 13}
+ - anchor: {line: 0, character: 23}
+ active: {line: 0, character: 23}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName6.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName6.yml
new file mode 100644
index 0000000000..75566e697e
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryName6.yml
@@ -0,0 +1,30 @@
+languageId: typescript
+command:
+ version: 5
+ spokenForm: clear every name
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: name}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ class Ccc {
+ constructor(private aaa: string, bbb: string) {}
+ }
+ selections:
+ - anchor: {line: 1, character: 48}
+ active: {line: 1, character: 48}
+ marks: {}
+finalState:
+ documentContents: |-
+ class Ccc {
+ constructor(private : string, : string) {}
+ }
+ selections:
+ - anchor: {line: 1, character: 24}
+ active: {line: 1, character: 24}
+ - anchor: {line: 1, character: 34}
+ active: {line: 1, character: 34}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag.yml
new file mode 100644
index 0000000000..e78be18880
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every start tag
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlStartTag}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ <>
+ <>ccc>
+ <>ddd>
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+ <>
+ ccc>
+ ddd>
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag2.yml
new file mode 100644
index 0000000000..a864a4987d
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryStartTag2.yml
@@ -0,0 +1,32 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every start tag
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlStartTag}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags.yml
new file mode 100644
index 0000000000..e6cfa27321
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags.yml
@@ -0,0 +1,36 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every tags
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlBothTags}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+ <>
+ <>ccc>
+ <>ddd>
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+ <>
+ ccc
+ ddd
+ >
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 1, character: 7}
+ active: {line: 1, character: 7}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
+ - anchor: {line: 2, character: 7}
+ active: {line: 2, character: 7}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags2.yml
new file mode 100644
index 0000000000..ab3f030013
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearEveryTags2.yml
@@ -0,0 +1,36 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear every tags
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: everyScope
+ scopeType: {type: xmlBothTags}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ marks: {}
+finalState:
+ documentContents: |-
+
+ ccc
+ ddd
+
+ selections:
+ - anchor: {line: 1, character: 4}
+ active: {line: 1, character: 4}
+ - anchor: {line: 1, character: 7}
+ active: {line: 1, character: 7}
+ - anchor: {line: 2, character: 4}
+ active: {line: 2, character: 4}
+ - anchor: {line: 2, character: 7}
+ active: {line: 2, character: 7}
diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearNameTags.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearNameTags.yml
new file mode 100644
index 0000000000..b9deee7194
--- /dev/null
+++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/clearNameTags.yml
@@ -0,0 +1,26 @@
+languageId: typescriptreact
+command:
+ version: 5
+ spokenForm: clear name tags
+ action: {name: clearAndSetSelection}
+ targets:
+ - type: primitive
+ modifiers:
+ - type: containingScope
+ scopeType: {type: name}
+ - type: containingScope
+ scopeType: {type: xmlBothTags}
+ usePrePhraseSnapshot: true
+initialState:
+ documentContents: <>aaa>
+ selections:
+ - anchor: {line: 0, character: 4}
+ active: {line: 0, character: 4}
+ marks: {}
+finalState:
+ documentContents: <>aaa>
+ selections:
+ - anchor: {line: 0, character: 1}
+ active: {line: 0, character: 1}
+ - anchor: {line: 0, character: 7}
+ active: {line: 0, character: 7}
diff --git a/queries/javascript.core.scm b/queries/javascript.core.scm
new file mode 100644
index 0000000000..93ec9109aa
--- /dev/null
+++ b/queries/javascript.core.scm
@@ -0,0 +1,29 @@
+(_
+ name: (_) @name
+) @_.domain
+
+(augmented_assignment_expression
+ left: (_) @name
+) @_.domain
+
+(assignment_expression
+ left: (_) @name
+) @_.domain
+
+[
+ (program)
+ (formal_parameters)
+] @name.iteration
+
+(
+ (_
+ body: (_
+ .
+ "{" @name.iteration.start
+ "}" @name.iteration.end
+ .
+ )
+ )
+ (#end-position! @name.iteration.start)
+ (#start-position! @name.iteration.end)
+)
diff --git a/queries/javascript.jsx.scm b/queries/javascript.jsx.scm
new file mode 100644
index 0000000000..84b95ceba4
--- /dev/null
+++ b/queries/javascript.jsx.scm
@@ -0,0 +1,60 @@
+(
+ (jsx_element) @xmlElement @_.interior @_.iteration
+ (#child-range! @_.interior 0 -1 true true)
+ (#child-range! @_.iteration 0 -1 true true)
+)
+
+(
+ (jsx_element) @xmlStartTag.iteration @xmlEndTag.iteration @xmlBothTags.iteration
+ (#child-range! @xmlStartTag.iteration 0 -1 true true)
+ (#child-range! @xmlEndTag.iteration 0 -1 true true)
+ (#child-range! @xmlBothTags.iteration 0 -1 true true)
+)
+
+(jsx_element
+ (jsx_opening_element) @xmlStartTag @xmlBothTags
+ (#allow-multiple! @xmlBothTags)
+) @_.domain
+
+(jsx_element
+ (jsx_closing_element) @xmlEndTag @xmlBothTags
+ (#allow-multiple! @xmlBothTags)
+) @_.domain
+
+(jsx_self_closing_element) @xmlElement
+
+;; JSX fragments, eg <>foo>
+(
+ (jsx_fragment) @xmlElement @_.interior @_.iteration
+ (#child-range! @_.interior 1 -3 true true)
+ (#child-range! @_.iteration 1 -3 true true)
+)
+
+(
+ (jsx_fragment) @xmlStartTag.iteration @xmlEndTag.iteration @xmlBothTags.iteration
+ (#child-range! @xmlStartTag.iteration 1 -3 true true)
+ (#child-range! @xmlEndTag.iteration 1 -3 true true)
+ (#child-range! @xmlBothTags.iteration 1 -3 true true)
+)
+
+(
+ (jsx_fragment) @xmlStartTag @xmlBothTags @_.domain
+ (#child-range! @xmlStartTag 0 1)
+ (#child-range! @xmlBothTags 0 1)
+ (#allow-multiple! @xmlBothTags)
+)
+
+(
+ (jsx_fragment) @xmlEndTag @xmlBothTags @_.domain
+ (#child-range! @xmlEndTag -3)
+ (#child-range! @xmlBothTags -3)
+ (#allow-multiple! @xmlBothTags)
+)
+
+(
+ (jsx_fragment
+ "<" @_.domain.start
+ ">" @name @_.domain.end
+ )
+ (#start-position! @name)
+)
diff --git a/queries/javascript.scm b/queries/javascript.scm
new file mode 100644
index 0000000000..1c1e8ff4a2
--- /dev/null
+++ b/queries/javascript.scm
@@ -0,0 +1,5 @@
+;; We include javascript.jsx.scm because jsx scopes technically work in
+;; javascript files even if they're not technically javascriptreact file type.
+
+;; import javascript.jsx.scm
+;; import javascript.core.scm
diff --git a/queries/javascriptreact.scm b/queries/javascriptreact.scm
new file mode 100644
index 0000000000..cf0dc39773
--- /dev/null
+++ b/queries/javascriptreact.scm
@@ -0,0 +1 @@
+;; import javascript.scm
diff --git a/queries/typescript.scm b/queries/typescript.scm
new file mode 100644
index 0000000000..df1cd0f456
--- /dev/null
+++ b/queries/typescript.scm
@@ -0,0 +1,13 @@
+;; Note that we don't just import `javascript.scm` because that includes
+;; `javascript.jsx.scm`, and tree-sitter would complain because those node
+;; types are not defined in the typescript grammar.
+
+;; import javascript.core.scm
+
+(optional_parameter
+ (identifier) @name
+) @_.domain
+
+(required_parameter
+ (identifier) @name
+) @_.domain
diff --git a/queries/typescriptreact.scm b/queries/typescriptreact.scm
new file mode 100644
index 0000000000..792c4b5133
--- /dev/null
+++ b/queries/typescriptreact.scm
@@ -0,0 +1,2 @@
+;; import javascriptreact.scm
+;; import typescript.scm