Merge pull request #24529 from Microsoft/suggestionDiagnosticsToken
Suggestion diagnostics to wire cancellationToken
This commit is contained in:
commit
997991fdcb
7 changed files with 91 additions and 61 deletions
|
@ -310,21 +310,34 @@ namespace ts {
|
||||||
const node = getParseTreeNode(nodeIn, isTypeNode);
|
const node = getParseTreeNode(nodeIn, isTypeNode);
|
||||||
return node && getTypeArgumentConstraint(node);
|
return node && getTypeArgumentConstraint(node);
|
||||||
},
|
},
|
||||||
|
getSuggestionDiagnostics: (file, ct) => {
|
||||||
|
let diagnostics: DiagnosticWithLocation[] | undefined;
|
||||||
|
try {
|
||||||
|
// Record the cancellation token so it can be checked later on during checkSourceElement.
|
||||||
|
// Do this in a finally block so we can ensure that it gets reset back to nothing after
|
||||||
|
// this call is done.
|
||||||
|
cancellationToken = ct;
|
||||||
|
|
||||||
getSuggestionDiagnostics: file => {
|
// Ensure file is type checked
|
||||||
return (suggestionDiagnostics.get(file.fileName) || emptyArray).concat(getUnusedDiagnostics());
|
|
||||||
function getUnusedDiagnostics(): ReadonlyArray<DiagnosticWithLocation> {
|
|
||||||
if (file.isDeclarationFile) return emptyArray;
|
|
||||||
|
|
||||||
checkSourceFile(file);
|
checkSourceFile(file);
|
||||||
const diagnostics: DiagnosticWithLocation[] = [];
|
|
||||||
Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked));
|
Debug.assert(!!(getNodeLinks(file).flags & NodeCheckFlags.TypeChecked));
|
||||||
|
|
||||||
|
diagnostics = addRange(diagnostics, suggestionDiagnostics.get(file.fileName));
|
||||||
|
if (!file.isDeclarationFile && (!unusedIsError(UnusedKind.Local) || !unusedIsError(UnusedKind.Parameter))) {
|
||||||
|
addUnusedDiagnostics();
|
||||||
|
}
|
||||||
|
return diagnostics || emptyArray;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
cancellationToken = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUnusedDiagnostics() {
|
||||||
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (kind, diag) => {
|
checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(file), (kind, diag) => {
|
||||||
if (!unusedIsError(kind)) {
|
if (!unusedIsError(kind)) {
|
||||||
diagnostics.push({ ...diag, category: DiagnosticCategory.Suggestion });
|
(diagnostics || (diagnostics = [])).push({ ...diag, category: DiagnosticCategory.Suggestion });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return diagnostics;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -683,6 +683,7 @@ namespace ts {
|
||||||
getOptionsDiagnostics,
|
getOptionsDiagnostics,
|
||||||
getGlobalDiagnostics,
|
getGlobalDiagnostics,
|
||||||
getSemanticDiagnostics,
|
getSemanticDiagnostics,
|
||||||
|
getSuggestionDiagnostics,
|
||||||
getDeclarationDiagnostics,
|
getDeclarationDiagnostics,
|
||||||
getTypeChecker,
|
getTypeChecker,
|
||||||
getClassifiableNames,
|
getClassifiableNames,
|
||||||
|
@ -1422,6 +1423,12 @@ namespace ts {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): ReadonlyArray<DiagnosticWithLocation> {
|
||||||
|
return runWithCancellationToken(() => {
|
||||||
|
return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines
|
* Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2755,6 +2755,7 @@ namespace ts {
|
||||||
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
|
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
|
||||||
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||||
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
|
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
|
||||||
|
/* @internal */ getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a type checker that can be used to semantically analyze source files in the program.
|
* Gets a type checker that can be used to semantically analyze source files in the program.
|
||||||
|
@ -3076,7 +3077,7 @@ namespace ts {
|
||||||
* Does *not* get *all* suggestion diagnostics, just the ones that were convenient to report in the checker.
|
* Does *not* get *all* suggestion diagnostics, just the ones that were convenient to report in the checker.
|
||||||
* Others are added in computeSuggestionDiagnostics.
|
* Others are added in computeSuggestionDiagnostics.
|
||||||
*/
|
*/
|
||||||
/* @internal */ getSuggestionDiagnostics(file: SourceFile): ReadonlyArray<DiagnosticWithLocation>;
|
/* @internal */ getSuggestionDiagnostics(file: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<DiagnosticWithLocation>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Depending on the operation performed, it may be appropriate to throw away the checker
|
* Depending on the operation performed, it may be appropriate to throw away the checker
|
||||||
|
|
|
@ -56,9 +56,19 @@ namespace ts {
|
||||||
r => assert.exists(r.displayParts)
|
r => assert.exists(r.displayParts)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can cancel suggestion diagnostics mid-request", () => {
|
||||||
|
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
|
||||||
|
service.getSuggestionDiagnostics("file.js"),
|
||||||
|
r => assert.notEqual(r.length, 0),
|
||||||
|
"file.js",
|
||||||
|
"function foo() { let a = 10; }",
|
||||||
|
{ allowJs: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function verifyOperationCancelledAfter<T>(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void) {
|
function verifyOperationCancelledAfter<T>(content: string, cancelAfter: number, operation: (service: LanguageService) => T, validator: (arg: T) => void, fileName?: string, fileContent?: string, options?: CompilerOptions) {
|
||||||
let checks = 0;
|
let checks = 0;
|
||||||
const token: HostCancellationToken = {
|
const token: HostCancellationToken = {
|
||||||
isCancellationRequested() {
|
isCancellationRequested() {
|
||||||
|
@ -70,9 +80,9 @@ namespace ts {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token);
|
const adapter = new Harness.LanguageService.NativeLanguageServiceAdapter(token, options);
|
||||||
const host = adapter.getHost();
|
const host = adapter.getHost();
|
||||||
host.addScript("file.ts", content, /*isRootFile*/ true);
|
host.addScript(fileName || "file.ts", fileContent || content, /*isRootFile*/ true);
|
||||||
const service = adapter.getLanguageService();
|
const service = adapter.getLanguageService();
|
||||||
assertCancelled(() => operation(service));
|
assertCancelled(() => operation(service));
|
||||||
validator(operation(service));
|
validator(operation(service));
|
||||||
|
|
|
@ -86,8 +86,8 @@ namespace ts {
|
||||||
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
|
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void {
|
function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void {
|
||||||
for (const diag of program.getSemanticDiagnostics(sourceFile).concat(computeSuggestionDiagnostics(sourceFile, program))) {
|
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
|
||||||
if (contains(errorCodes, diag.code)) {
|
if (contains(errorCodes, diag.code)) {
|
||||||
cb(diag as DiagnosticWithLocation);
|
cb(diag as DiagnosticWithLocation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1415,7 +1415,7 @@ namespace ts {
|
||||||
|
|
||||||
function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] {
|
function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] {
|
||||||
synchronizeHostData();
|
synchronizeHostData();
|
||||||
return computeSuggestionDiagnostics(getValidSourceFile(fileName), program);
|
return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCompilerOptionsDiagnostics() {
|
function getCompilerOptionsDiagnostics() {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
/* @internal */
|
/* @internal */
|
||||||
namespace ts {
|
namespace ts {
|
||||||
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program): DiagnosticWithLocation[] {
|
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
|
||||||
program.getSemanticDiagnostics(sourceFile);
|
program.getSemanticDiagnostics(sourceFile, cancellationToken);
|
||||||
const checker = program.getDiagnosticsProducingTypeChecker();
|
|
||||||
const diags: DiagnosticWithLocation[] = [];
|
const diags: DiagnosticWithLocation[] = [];
|
||||||
|
|
||||||
if (sourceFile.commonJsModuleIndicator &&
|
if (sourceFile.commonJsModuleIndicator &&
|
||||||
|
@ -13,49 +12,8 @@ namespace ts {
|
||||||
|
|
||||||
const isJsFile = isSourceFileJavaScript(sourceFile);
|
const isJsFile = isSourceFileJavaScript(sourceFile);
|
||||||
|
|
||||||
function check(node: Node) {
|
|
||||||
if (isJsFile) {
|
|
||||||
switch (node.kind) {
|
|
||||||
case SyntaxKind.FunctionExpression:
|
|
||||||
const decl = getDeclarationOfJSInitializer(node);
|
|
||||||
if (decl) {
|
|
||||||
const symbol = decl.symbol;
|
|
||||||
if (symbol && (symbol.exports && symbol.exports.size || symbol.members && symbol.members.size)) {
|
|
||||||
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// falls through if no diagnostic was created
|
|
||||||
case SyntaxKind.FunctionDeclaration:
|
|
||||||
const symbol = node.symbol;
|
|
||||||
if (symbol.members && (symbol.members.size > 0)) {
|
|
||||||
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isJsFile && codefix.parameterShouldGetTypeFromJSDoc(node)) {
|
|
||||||
diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types));
|
|
||||||
}
|
|
||||||
|
|
||||||
node.forEachChild(check);
|
|
||||||
}
|
|
||||||
check(sourceFile);
|
check(sourceFile);
|
||||||
|
|
||||||
if (!isJsFile) {
|
|
||||||
for (const statement of sourceFile.statements) {
|
|
||||||
if (isVariableStatement(statement) &&
|
|
||||||
statement.declarationList.flags & NodeFlags.Const &&
|
|
||||||
statement.declarationList.declarations.length === 1) {
|
|
||||||
const init = statement.declarationList.declarations[0].initializer;
|
|
||||||
if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) {
|
|
||||||
diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
|
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
|
||||||
for (const moduleSpecifier of sourceFile.imports) {
|
for (const moduleSpecifier of sourceFile.imports) {
|
||||||
const importNode = importFromModuleSpecifier(moduleSpecifier);
|
const importNode = importFromModuleSpecifier(moduleSpecifier);
|
||||||
|
@ -70,7 +28,48 @@ namespace ts {
|
||||||
}
|
}
|
||||||
|
|
||||||
addRange(diags, sourceFile.bindSuggestionDiagnostics);
|
addRange(diags, sourceFile.bindSuggestionDiagnostics);
|
||||||
return diags.concat(checker.getSuggestionDiagnostics(sourceFile)).sort((d1, d2) => d1.start - d2.start);
|
addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken));
|
||||||
|
return diags.sort((d1, d2) => d1.start - d2.start);
|
||||||
|
|
||||||
|
function check(node: Node) {
|
||||||
|
if (isJsFile) {
|
||||||
|
switch (node.kind) {
|
||||||
|
case SyntaxKind.FunctionExpression:
|
||||||
|
const decl = getDeclarationOfJSInitializer(node);
|
||||||
|
if (decl) {
|
||||||
|
const symbol = decl.symbol;
|
||||||
|
if (symbol && (symbol.exports && symbol.exports.size || symbol.members && symbol.members.size)) {
|
||||||
|
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// falls through if no diagnostic was created
|
||||||
|
case SyntaxKind.FunctionDeclaration:
|
||||||
|
const symbol = node.symbol;
|
||||||
|
if (symbol.members && (symbol.members.size > 0)) {
|
||||||
|
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isVariableStatement(node) &&
|
||||||
|
node.parent === sourceFile &&
|
||||||
|
node.declarationList.flags & NodeFlags.Const &&
|
||||||
|
node.declarationList.declarations.length === 1) {
|
||||||
|
const init = node.declarationList.declarations[0].initializer;
|
||||||
|
if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) {
|
||||||
|
diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codefix.parameterShouldGetTypeFromJSDoc(node)) {
|
||||||
|
diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.forEachChild(check);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertToEs6Module only works on top-level, so don't trigger it if commonjs code only appears in nested scopes.
|
// convertToEs6Module only works on top-level, so don't trigger it if commonjs code only appears in nested scopes.
|
||||||
|
|
Loading…
Reference in a new issue