Skip to content

Commit b24f061

Browse files
authored
Add integration test setup; check for legacy/new scope duplicates (#1428)
This PR adds a test to ensure that we don't try to define the same scope type both using our legacy node matchers and the new query scope handlers I have mixed feelings about the way I made this work, but I wasn't sure how else to do it. The test needs to be defined in the engine, because otherwise we'd need to expose lots of stuff to cursorless-vscode-e2e, but it needs a full tree-sitter instance, so can't be run as a unit test. This file is the compromise I came up with 🤷‍♂️. We should maybe revise once we have tree-sitter integrated with this repo I don't like that this code gets rolled up into our production bundle, but probably ok for this one-off. If we find this file growing, we should revisit It's also possible that once we get rid of our `ide` singleton, we can relax the restriction that our test harness doesn't import from `cursorless-engine`. That would enable us to just import what we need from the engine directly in the test harness to run this test 🤔 - Depends on #629 ## Checklist - [ ] 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
1 parent 1786978 commit b24f061

File tree

9 files changed

+104
-26
lines changed

9 files changed

+104
-26
lines changed

packages/cursorless-engine/src/cursorlessEngine.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { HatTokenMapImpl } from "./core/HatTokenMapImpl";
55
import { Snippets } from "./core/Snippets";
66
import { RangeUpdater } from "./core/updateSelections/RangeUpdater";
77
import { LanguageDefinitions } from "./languages/LanguageDefinitions";
8+
import { runIntegrationTests } from "./runIntegrationTests";
89
import { injectIde } from "./singletons/ide.singleton";
910
import { ensureCommandShape } from "./core/commandVersionUpgrades/ensureCommandShape";
1011
import { runCommand } from "./runCommand";
@@ -72,6 +73,8 @@ export function createCursorlessEngine(
7273
hatTokenMap,
7374
snippets,
7475
injectIde,
76+
runIntegrationTests: () =>
77+
runIntegrationTests(treeSitter, languageDefinitions),
7578
};
7679
}
7780

@@ -96,4 +99,5 @@ export interface CursorlessEngine {
9699
hatTokenMap: HatTokenMapImpl;
97100
snippets: Snippets;
98101
injectIde: (ide: IDE | undefined) => void;
102+
runIntegrationTests: () => Promise<void>;
99103
}

packages/cursorless-engine/src/languages/LegacyLanguageId.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,30 @@
22
* The language IDs that we have full tree-sitter support for using our legacy
33
* modifiers.
44
*/
5-
export type LegacyLanguageId =
6-
| "c"
7-
| "clojure"
8-
| "cpp"
9-
| "css"
10-
| "csharp"
11-
| "go"
12-
| "html"
13-
| "java"
14-
| "javascript"
15-
| "javascriptreact"
16-
| "json"
17-
| "jsonc"
18-
| "latex"
19-
| "markdown"
20-
| "php"
21-
| "python"
22-
| "ruby"
23-
| "scala"
24-
| "scss"
25-
| "rust"
26-
| "typescript"
27-
| "typescriptreact"
28-
| "xml";
5+
export const legacyLanguageIds = [
6+
"c",
7+
"clojure",
8+
"cpp",
9+
"css",
10+
"csharp",
11+
"go",
12+
"html",
13+
"java",
14+
"javascript",
15+
"javascriptreact",
16+
"json",
17+
"jsonc",
18+
"latex",
19+
"markdown",
20+
"php",
21+
"python",
22+
"ruby",
23+
"scala",
24+
"scss",
25+
"rust",
26+
"typescript",
27+
"typescriptreact",
28+
"xml",
29+
];
30+
31+
export type LegacyLanguageId = (typeof legacyLanguageIds)[number];

packages/cursorless-engine/src/languages/getNodeMatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function getNodeMatcher(
5050
return matcher;
5151
}
5252

53-
const languageMatchers: Record<
53+
export const languageMatchers: Record<
5454
LegacyLanguageId,
5555
Record<SimpleScopeTypeType, NodeMatcher>
5656
> = {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import assert = require("assert");
2+
import { languageMatchers } from "./languages/getNodeMatcher";
3+
import { TreeSitter } from "./typings/TreeSitter";
4+
import { legacyLanguageIds } from "./languages/LegacyLanguageId";
5+
import { LanguageDefinitions } from "./languages/LanguageDefinitions";
6+
7+
/**
8+
* Run tests that require multiple components to be instantiated, as well as a
9+
* full context, eg including tree sitter, but that are too closely tied to the
10+
* engine to be defined in cursorless-vscode-e2e
11+
*
12+
* @param treeSitter The tree sitter instance
13+
* @param languageDefinitions The language definitions instance
14+
*/
15+
export async function runIntegrationTests(
16+
treeSitter: TreeSitter,
17+
languageDefinitions: LanguageDefinitions,
18+
) {
19+
await assertNoScopesBothLegacyAndNew(treeSitter, languageDefinitions);
20+
}
21+
22+
async function assertNoScopesBothLegacyAndNew(
23+
treeSitter: TreeSitter,
24+
languageDefinitions: LanguageDefinitions,
25+
) {
26+
const errors: string[] = [];
27+
for (const languageId of legacyLanguageIds) {
28+
await treeSitter.loadLanguage(languageId);
29+
30+
Object.keys(languageMatchers[languageId]).map((scopeTypeType) => {
31+
if (
32+
languageDefinitions.get(languageId)?.getScopeHandler({
33+
type: scopeTypeType,
34+
}) != null
35+
) {
36+
errors.push(
37+
`Scope '${scopeTypeType}' defined as both legacy and new for language ${languageId}`,
38+
);
39+
}
40+
});
41+
}
42+
43+
assert.deepStrictEqual(errors, []);
44+
}

packages/cursorless-engine/src/typings/TreeSitter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@ export interface TreeSitter {
1515
/**
1616
* Gets a language if it is loaded
1717
*
18-
* @param languageId The language id of the language to load
18+
* @param languageId The language id of the language to get
1919
* @returns The language if it is already loaded
2020
*/
2121
getLanguage(languageId: string): Language | undefined;
22+
23+
/**
24+
* Loads a language, returning true if it was successfully loaded
25+
*
26+
* @param languageId The language id of the language to load
27+
* @returns `true` if the language was successfully loaded
28+
*/
29+
loadLanguage(languageId: string): Promise<boolean>;
2230
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { getCursorlessApi } from "@cursorless/vscode-common";
2+
import { endToEndTestSetup } from "../endToEndTestSetup";
3+
4+
suite("Cursorless engine integration", async function () {
5+
endToEndTestSetup(this);
6+
7+
test("integration", async () => {
8+
const cursorlessApi = await getCursorlessApi();
9+
const { runIntegrationTests } = cursorlessApi.testHelpers!;
10+
await runIntegrationTests();
11+
});
12+
});

packages/cursorless-vscode/src/constructTestHelpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function constructTestHelpers(
2828
vscodeIDE: VscodeIDE,
2929
normalizedIde: NormalizedIDE,
3030
injectIde: (ide: IDE) => void,
31+
runIntegrationTests: () => Promise<void>,
3132
): TestHelpers | undefined {
3233
return {
3334
commandServerApi: commandServerApi!,
@@ -72,5 +73,6 @@ export function constructTestHelpers(
7273
);
7374
},
7475
hatTokenMap,
76+
runIntegrationTests,
7577
};
7678
}

packages/cursorless-vscode/src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export async function activate(
6565
hatTokenMap,
6666
snippets,
6767
injectIde,
68+
runIntegrationTests,
6869
} = createCursorlessEngine(
6970
treeSitter,
7071
normalizedIde ?? vscodeIDE,
@@ -93,6 +94,7 @@ export async function activate(
9394
vscodeIDE,
9495
normalizedIde!,
9596
injectIde,
97+
runIntegrationTests,
9698
)
9799
: undefined,
98100

@@ -129,6 +131,7 @@ function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter {
129131
return parseTreeApi.getTreeForUri(document.uri);
130132
},
131133

134+
loadLanguage: parseTreeApi.loadLanguage,
132135
getLanguage: parseTreeApi.getLanguage,
133136
};
134137
}

packages/vscode-common/src/getExtensionApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export interface TestHelpers {
4040
marks: SerializedMarks | undefined,
4141
forceRealClipboard: boolean,
4242
): Promise<TestCaseSnapshot>;
43+
44+
runIntegrationTests(): Promise<void>;
4345
}
4446

4547
export interface CursorlessApi {

0 commit comments

Comments
 (0)