Skip to content

Commit e21eae3

Browse files
authored
Merge pull request vuejs#2332 from yoyo930021/impl-cancellationToken
Stop computing outdated diagnostics with CancellationToken
2 parents b624efb + c314a09 commit e21eae3

File tree

9 files changed

+128
-23
lines changed

9 files changed

+128
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- 🙌 Complete with `?.` for optional properies in completion. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #2326 and #2357.
1616
- 🙌 Respect typescript language settings. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #2109 and #2375.
1717
- 🙌 Slim syntax highlighting. Thanks to contribution from [@Antti](https://github.com/Antti).
18+
- 🙌 Stop computing outdated diagnostics with CancellationToken. Thanks to contribution from [@yoyo930021](https://github.com/yoyo930021). #1263 and #2332.
1819

1920
### 0.28.0 | 2020-09-23 | [VSIX](https://marketplace.visualstudio.com/_apis/public/gallery/publishers/octref/vsextensions/vetur/0.28.0/vspackage)
2021

server/src/embeddedSupport/languageModes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { getServiceHost, IServiceHost } from '../services/typescriptService/serv
3838
import { VLSFullConfig } from '../config';
3939
import { SassLanguageMode } from '../modes/style/sass/sassLanguageMode';
4040
import { getPugMode } from '../modes/pug';
41+
import { VCancellationToken } from '../utils/cancellationToken';
4142

4243
export interface VLSServices {
4344
infoService?: VueInfoService;
@@ -49,7 +50,7 @@ export interface LanguageMode {
4950
configure?(options: VLSFullConfig): void;
5051
updateFileInfo?(doc: TextDocument): void;
5152

52-
doValidation?(document: TextDocument): Diagnostic[];
53+
doValidation?(document: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]>;
5354
getCodeActions?(
5455
document: TextDocument,
5556
range: Range,

server/src/modes/script/javascript.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { RefactorAction } from '../../types';
4646
import { IServiceHost } from '../../services/typescriptService/serviceHost';
4747
import { toCompletionItemKind, toSymbolKind } from '../../services/typescriptService/util';
4848
import * as Previewer from './previewer';
49+
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';
4950

5051
// Todo: After upgrading to LS server 4.0, use CompletionContext for filtering trigger chars
5152
// https://microsoft.github.io/language-server-protocol/specification#completion-request-leftwards_arrow_with_hook
@@ -145,18 +146,38 @@ export async function getJavascriptMode(
145146
}
146147
},
147148

148-
doValidation(doc: TextDocument): Diagnostic[] {
149+
async doValidation(doc: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]> {
150+
if (await isVCancellationRequested(cancellationToken)) {
151+
return [];
152+
}
149153
const { scriptDoc, service } = updateCurrentVueTextDocument(doc);
150154
if (!languageServiceIncludesFile(service, doc.uri)) {
151155
return [];
152156
}
153157

158+
if (await isVCancellationRequested(cancellationToken)) {
159+
return [];
160+
}
154161
const fileFsPath = getFileFsPath(doc.uri);
155-
const rawScriptDiagnostics = [
156-
...service.getSyntacticDiagnostics(fileFsPath),
157-
...service.getSemanticDiagnostics(fileFsPath)
162+
const program = service.getProgram();
163+
const sourceFile = program?.getSourceFile(fileFsPath);
164+
if (!program || !sourceFile) {
165+
return [];
166+
}
167+
168+
let rawScriptDiagnostics = [
169+
...program.getSyntacticDiagnostics(sourceFile, cancellationToken?.tsToken),
170+
...program.getSemanticDiagnostics(sourceFile, cancellationToken?.tsToken)
158171
];
159172

173+
const compilerOptions = program.getCompilerOptions();
174+
if (compilerOptions.declaration || compilerOptions.composite) {
175+
rawScriptDiagnostics = [
176+
...rawScriptDiagnostics,
177+
...program.getDeclarationDiagnostics(sourceFile, cancellationToken?.tsToken)
178+
];
179+
}
180+
160181
return rawScriptDiagnostics.map(diag => {
161182
const tags: DiagnosticTag[] = [];
162183

server/src/modes/style/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function getStyleMode(
6969
languageService.configure(c && c.css);
7070
config = c;
7171
},
72-
doValidation(document) {
72+
async doValidation(document) {
7373
if (languageId === 'postcss') {
7474
return [];
7575
} else {

server/src/modes/template/htmlMode.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { getComponentInfoTagProvider } from './tagProviders/componentInfoTagProv
2626
import { VueVersion } from '../../services/typescriptService/vueVersion';
2727
import { doPropValidation } from './services/vuePropValidation';
2828
import { getFoldingRanges } from './services/htmlFolding';
29+
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';
2930

3031
export class HTMLMode implements LanguageMode {
3132
private tagProviderSettings: CompletionConfiguration;
@@ -60,16 +61,22 @@ export class HTMLMode implements LanguageMode {
6061
this.config = c;
6162
}
6263

63-
doValidation(document: TextDocument) {
64+
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) {
6465
const diagnostics = [];
6566

67+
if (await isVCancellationRequested(cancellationToken)) {
68+
return [];
69+
}
6670
if (this.config.vetur.validation.templateProps) {
6771
const info = this.vueInfoService ? this.vueInfoService.getInfo(document) : undefined;
6872
if (info && info.componentInfo.childComponents) {
6973
diagnostics.push(...doPropValidation(document, this.vueDocuments.refreshAndGet(document), info));
7074
}
7175
}
7276

77+
if (await isVCancellationRequested(cancellationToken)) {
78+
return diagnostics;
79+
}
7380
if (this.config.vetur.validation.template) {
7481
const embedded = this.embeddedDocuments.refreshAndGet(document);
7582
diagnostics.push(...doESLintValidation(embedded, this.lintEngine));

server/src/modes/template/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IServiceHost } from '../../services/typescriptService/serviceHost';
1818
import { T_TypeScript } from '../../services/dependencyService';
1919
import { HTMLDocument, parseHTMLDocument } from './parser/htmlParser';
2020
import { inferVueVersion } from '../../services/typescriptService/vueVersion';
21+
import { VCancellationToken } from '../../utils/cancellationToken';
2122

2223
type DocumentRegionCache = LanguageModelCache<VueDocumentRegions>;
2324

@@ -47,8 +48,11 @@ export class VueHTMLMode implements LanguageMode {
4748
queryVirtualFileInfo(fileName: string, currFileText: string) {
4849
return this.vueInterpolationMode.queryVirtualFileInfo(fileName, currFileText);
4950
}
50-
doValidation(document: TextDocument) {
51-
return this.htmlMode.doValidation(document).concat(this.vueInterpolationMode.doValidation(document));
51+
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken) {
52+
return Promise.all([
53+
this.vueInterpolationMode.doValidation(document, cancellationToken),
54+
this.htmlMode.doValidation(document, cancellationToken)
55+
]).then(result => [...result[0], ...result[1]]);
5256
}
5357
doComplete(document: TextDocument, position: Position) {
5458
const htmlList = this.htmlMode.doComplete(document, position);

server/src/modes/template/interpolationMode.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { mapBackRange, mapFromPositionToOffset } from '../../services/typescript
2323
import { createTemplateDiagnosticFilter } from '../../services/typescriptService/templateDiagnosticFilter';
2424
import { toCompletionItemKind } from '../../services/typescriptService/util';
2525
import { VueInfoService } from '../../services/vueInfoService';
26+
import { isVCancellationRequested, VCancellationToken } from '../../utils/cancellationToken';
2627
import { getFileFsPath } from '../../utils/paths';
2728
import { NULL_COMPLETION } from '../nullMode';
2829
import { languageServiceIncludesFile } from '../script/javascript';
@@ -52,14 +53,18 @@ export class VueInterpolationMode implements LanguageMode {
5253
return this.serviceHost.queryVirtualFileInfo(fileName, currFileText);
5354
}
5455

55-
doValidation(document: TextDocument): Diagnostic[] {
56+
async doValidation(document: TextDocument, cancellationToken?: VCancellationToken): Promise<Diagnostic[]> {
5657
if (
5758
!_.get(this.config, ['vetur', 'experimental', 'templateInterpolationService'], true) ||
5859
!this.config.vetur.validation.interpolation
5960
) {
6061
return [];
6162
}
6263

64+
if (await isVCancellationRequested(cancellationToken)) {
65+
return [];
66+
}
67+
6368
// Add suffix to process this doc as vue template.
6469
const templateDoc = TextDocument.create(
6570
document.uri + '.template',
@@ -81,6 +86,10 @@ export class VueInterpolationMode implements LanguageMode {
8186
return [];
8287
}
8388

89+
if (await isVCancellationRequested(cancellationToken)) {
90+
return [];
91+
}
92+
8493
const templateFileFsPath = getFileFsPath(templateDoc.uri);
8594
// We don't need syntactic diagnostics because
8695
// compiled template is always valid JavaScript syntax.

server/src/services/vls.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
CompletionTriggerKind,
2222
ExecuteCommandParams,
2323
ApplyWorkspaceEditRequest,
24-
FoldingRangeParams
24+
FoldingRangeParams,
25+
CancellationTokenSource
2526
} from 'vscode-languageserver';
2627
import {
2728
ColorInformation,
@@ -46,7 +47,7 @@ import { URI } from 'vscode-uri';
4647
import { LanguageModes, LanguageModeRange, LanguageMode } from '../embeddedSupport/languageModes';
4748
import { NULL_COMPLETION, NULL_HOVER, NULL_SIGNATURE } from '../modes/nullMode';
4849
import { VueInfoService } from './vueInfoService';
49-
import { DependencyService } from './dependencyService';
50+
import { DependencyService, State } from './dependencyService';
5051
import * as _ from 'lodash';
5152
import { DocumentContext, RefactorAction } from '../types';
5253
import { DocumentService } from './documentService';
@@ -55,6 +56,7 @@ import { logger } from '../log';
5556
import { getDefaultVLSConfig, VLSFullConfig, VLSConfig } from '../config';
5657
import { LanguageId } from '../embeddedSupport/embeddedSupport';
5758
import { APPLY_REFACTOR_COMMAND } from '../modes/script/javascript';
59+
import { VCancellationToken, VCancellationTokenSource } from '../utils/cancellationToken';
5860

5961
export class VLS {
6062
// @Todo: Remove this and DocumentContext
@@ -67,6 +69,7 @@ export class VLS {
6769
private languageModes: LanguageModes;
6870

6971
private pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {};
72+
private cancellationTokenValidationRequests: { [uri: string]: VCancellationTokenSource } = {};
7073
private validationDelayMs = 200;
7174
private validation: { [k: string]: boolean } = {
7275
'vue-html': true,
@@ -173,10 +176,11 @@ export class VLS {
173176
return (this.languageModes.getMode('vue-html') as VueHTMLMode).queryVirtualFileInfo(fileName, currFileText);
174177
});
175178

176-
this.lspConnection.onRequest('$/getDiagnostics', params => {
179+
this.lspConnection.onRequest('$/getDiagnostics', async params => {
177180
const doc = this.documentService.getDocument(params.uri);
178181
if (doc) {
179-
return this.doValidate(doc);
182+
const diagnostics = await this.doValidate(doc);
183+
return diagnostics ?? [];
180184
}
181185
return [];
182186
});
@@ -509,12 +513,26 @@ export class VLS {
509513
}
510514

511515
this.cleanPendingValidation(textDocument);
516+
this.cancelPastValidation(textDocument);
512517
this.pendingValidationRequests[textDocument.uri] = setTimeout(() => {
513518
delete this.pendingValidationRequests[textDocument.uri];
514-
this.validateTextDocument(textDocument);
519+
const tsDep = this.dependencyService.getDependency('typescript');
520+
if (tsDep?.state === State.Loaded) {
521+
this.cancellationTokenValidationRequests[textDocument.uri] = new VCancellationTokenSource(tsDep.module);
522+
this.validateTextDocument(textDocument, this.cancellationTokenValidationRequests[textDocument.uri].token);
523+
}
515524
}, this.validationDelayMs);
516525
}
517526

527+
cancelPastValidation(textDocument: TextDocument): void {
528+
const source = this.cancellationTokenValidationRequests[textDocument.uri];
529+
if (source) {
530+
source.cancel();
531+
source.dispose();
532+
delete this.cancellationTokenValidationRequests[textDocument.uri];
533+
}
534+
}
535+
518536
cleanPendingValidation(textDocument: TextDocument): void {
519537
const request = this.pendingValidationRequests[textDocument.uri];
520538
if (request) {
@@ -523,25 +541,30 @@ export class VLS {
523541
}
524542
}
525543

526-
validateTextDocument(textDocument: TextDocument): void {
527-
const diagnostics: Diagnostic[] = this.doValidate(textDocument);
528-
this.lspConnection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
544+
async validateTextDocument(textDocument: TextDocument, cancellationToken?: VCancellationToken) {
545+
const diagnostics = await this.doValidate(textDocument, cancellationToken);
546+
if (diagnostics) {
547+
this.lspConnection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
548+
}
529549
}
530550

531-
doValidate(doc: TextDocument): Diagnostic[] {
551+
async doValidate(doc: TextDocument, cancellationToken?: VCancellationToken) {
532552
const diagnostics: Diagnostic[] = [];
533553
if (doc.languageId === 'vue') {
534-
this.languageModes.getAllLanguageModeRangesInDocument(doc).forEach(lmr => {
554+
for (const lmr of this.languageModes.getAllLanguageModeRangesInDocument(doc)) {
535555
if (lmr.mode.doValidation) {
536556
if (this.validation[lmr.mode.getId()]) {
537-
pushAll(diagnostics, lmr.mode.doValidation(doc));
557+
pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken));
538558
}
539559
// Special case for template type checking
540560
else if (lmr.mode.getId() === 'vue-html' && this.templateInterpolationValidation) {
541-
pushAll(diagnostics, lmr.mode.doValidation(doc));
561+
pushAll(diagnostics, await lmr.mode.doValidation(doc, cancellationToken));
542562
}
543563
}
544-
});
564+
}
565+
}
566+
if (cancellationToken?.isCancellationRequested) {
567+
return null;
545568
}
546569
return diagnostics;
547570
}

server/src/utils/cancellationToken.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { T_TypeScript } from '../services/dependencyService';
2+
import { CancellationToken as TSCancellationToken } from 'typescript';
3+
import { CancellationTokenSource, CancellationToken as LSPCancellationToken } from 'vscode-languageserver';
4+
5+
export interface VCancellationToken extends LSPCancellationToken {
6+
tsToken: TSCancellationToken;
7+
}
8+
9+
export class VCancellationTokenSource extends CancellationTokenSource {
10+
constructor(private tsModule: T_TypeScript) {
11+
super();
12+
}
13+
14+
get token(): VCancellationToken {
15+
const operationCancelException = this.tsModule.OperationCanceledException;
16+
const token = super.token as VCancellationToken;
17+
token.tsToken = {
18+
isCancellationRequested() {
19+
return token.isCancellationRequested;
20+
},
21+
throwIfCancellationRequested() {
22+
if (token.isCancellationRequested) {
23+
throw new operationCancelException();
24+
}
25+
}
26+
};
27+
return token;
28+
}
29+
}
30+
31+
export function isVCancellationRequested(token?: VCancellationToken) {
32+
return new Promise(resolve => {
33+
if (!token) {
34+
resolve(false);
35+
} else {
36+
setImmediate(() => resolve(token.isCancellationRequested));
37+
}
38+
});
39+
}

0 commit comments

Comments
 (0)