• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: services:: cancellableLanguageServiceOperations", () => {
3        const file = `
4        function foo(): void;
5        function foo<T>(x: T): T;
6        function foo<T>(x?: T): T | void {}
7        foo(f);
8        `;
9        it("can cancel signature help mid-request", () => {
10            verifyOperationCancelledAfter(file, 4, service => // Two calls are top-level in services, one is the root type, and the second should be for the parameter type
11                service.getSignatureHelpItems("file.ts", file.lastIndexOf("f"), emptyOptions)!, r => assert.exists(r.items[0])
12            );
13        });
14
15        it("can cancel find all references mid-request", () => {
16            verifyOperationCancelledAfter(file, 3, service => // Two calls are top-level in services, one is the root type
17                service.findReferences("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r[0].definition)
18            );
19        });
20
21        it("can cancel quick info mid-request", () => {
22            verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for quickinfo, so the first check is within the checker
23                service.getQuickInfoAtPosition("file.ts", file.lastIndexOf("o"))!, r => assert.exists(r.displayParts)
24            );
25        });
26
27        it("can cancel completion entry details mid-request", () => {
28            const options: FormatCodeSettings = {
29                indentSize: 4,
30                tabSize: 4,
31                newLineCharacter: "\n",
32                convertTabsToSpaces: true,
33                indentStyle: IndentStyle.Smart,
34                insertSpaceAfterConstructor: false,
35                insertSpaceAfterCommaDelimiter: true,
36                insertSpaceAfterSemicolonInForStatements: true,
37                insertSpaceBeforeAndAfterBinaryOperators: true,
38                insertSpaceAfterKeywordsInControlFlowStatements: true,
39                insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
40                insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
41                insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
42                insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
43                insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
44                insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
45                insertSpaceBeforeFunctionParenthesis: false,
46                placeOpenBraceOnNewLineForFunctions: false,
47                placeOpenBraceOnNewLineForControlBlocks: false,
48            };
49            verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for completion entry details, so the first check is within the checker
50                service.getCompletionEntryDetails("file.ts", file.lastIndexOf("f"), "foo", options, /*content*/ undefined, {})!, r => assert.exists(r.displayParts)
51            );
52        });
53
54        it("can cancel suggestion diagnostics mid-request", () => {
55            verifyOperationCancelledAfter(file, 1, service => // The LS doesn't do any top-level checks on the token for suggestion diagnostics, so the first check is within the checker
56                service.getSuggestionDiagnostics("file.js"), r => assert.notEqual(r.length, 0), "file.js", "function foo() { let a = 10; }", { allowJs: true }
57            );
58        });
59    });
60
61    function verifyOperationCancelledAfter<T>(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: CompilerOptions) {
62        let checks = 0;
63        const token: HostCancellationToken = {
64            isCancellationRequested() {
65                checks++;
66                const result = checks >= cancelAfter;
67                if (result) {
68                    checks = -Infinity; // Cancel just once, then disable cancellation, effectively
69                }
70                return result;
71            }
72        };
73        const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options);
74        const host = adapter.getHost();
75        host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true);
76        const service = adapter.getLanguageService();
77        assertCancelled(() => operation(service));
78        validator(operation(service));
79    }
80
81    /**
82     * We don't just use `assert.throws` because it doesn't validate instances for thrown objects which do not inherit from `Error`
83     */
84    function assertCancelled(cb: () => void) {
85        let caught: any;
86        try {
87            cb();
88        }
89        catch (e) {
90            caught = e;
91        }
92        assert.exists(caught, "Expected operation to be cancelled, but was not");
93        assert.instanceOf(caught, OperationCanceledException);
94    }
95}
96