Skip to content

Commit 24814ec

Browse files
authored
Merge pull request #15260 from Microsoft/Fix15200
Fix #15200: Query for semantic errors on .js files with '@ts-check' with no config file
2 parents b267492 + 420908e commit 24814ec

File tree

5 files changed

+146
-10
lines changed

5 files changed

+146
-10
lines changed

src/compiler/program.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,13 @@ namespace ts {
908908

909909
function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] {
910910
return runWithCancellationToken(() => {
911+
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
912+
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
913+
// '/// <reference no-default-lib="true"/>' directive.
914+
if (options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib) {
915+
return emptyArray;
916+
}
917+
911918
const typeChecker = getDiagnosticsProducingTypeChecker();
912919

913920
Debug.assert(!!sourceFile.bindDiagnostics);

src/harness/unittests/tsserverProjectSystem.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3204,6 +3204,122 @@ namespace ts.projectSystem {
32043204
const errorResult = <protocol.Diagnostic[]>session.executeCommand(dTsFileGetErrRequest).response;
32053205
assert.isTrue(errorResult.length === 0);
32063206
});
3207+
3208+
it("should not report bind errors for declaration files with skipLibCheck=true", () => {
3209+
const jsconfigFile = {
3210+
path: "/a/jsconfig.json",
3211+
content: "{}"
3212+
};
3213+
const jsFile = {
3214+
path: "/a/jsFile.js",
3215+
content: "let x = 1;"
3216+
};
3217+
const dTsFile1 = {
3218+
path: "/a/dTsFile1.d.ts",
3219+
content: `
3220+
declare var x: number;`
3221+
};
3222+
const dTsFile2 = {
3223+
path: "/a/dTsFile2.d.ts",
3224+
content: `
3225+
declare var x: string;`
3226+
};
3227+
const host = createServerHost([jsconfigFile, jsFile, dTsFile1, dTsFile2]);
3228+
const session = createSession(host);
3229+
openFilesForSession([jsFile], session);
3230+
3231+
const dTsFile1GetErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
3232+
CommandNames.SemanticDiagnosticsSync,
3233+
{ file: dTsFile1.path }
3234+
);
3235+
const error1Result = <protocol.Diagnostic[]>session.executeCommand(dTsFile1GetErrRequest).response;
3236+
assert.isTrue(error1Result.length === 0);
3237+
3238+
const dTsFile2GetErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
3239+
CommandNames.SemanticDiagnosticsSync,
3240+
{ file: dTsFile2.path }
3241+
);
3242+
const error2Result = <protocol.Diagnostic[]>session.executeCommand(dTsFile2GetErrRequest).response;
3243+
assert.isTrue(error2Result.length === 0);
3244+
});
3245+
3246+
it("should report semanitc errors for loose JS files with '// @ts-check' and skipLibCheck=true", () => {
3247+
const jsFile = {
3248+
path: "/a/jsFile.js",
3249+
content: `
3250+
// @ts-check
3251+
let x = 1;
3252+
x === "string";`
3253+
};
3254+
3255+
const host = createServerHost([jsFile]);
3256+
const session = createSession(host);
3257+
openFilesForSession([jsFile], session);
3258+
3259+
const getErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
3260+
CommandNames.SemanticDiagnosticsSync,
3261+
{ file: jsFile.path }
3262+
);
3263+
const errorResult = <protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
3264+
assert.isTrue(errorResult.length === 1);
3265+
assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code);
3266+
});
3267+
3268+
it("should report semanitc errors for configured js project with '// @ts-check' and skipLibCheck=true", () => {
3269+
const jsconfigFile = {
3270+
path: "/a/jsconfig.json",
3271+
content: "{}"
3272+
};
3273+
3274+
const jsFile = {
3275+
path: "/a/jsFile.js",
3276+
content: `
3277+
// @ts-check
3278+
let x = 1;
3279+
x === "string";`
3280+
};
3281+
3282+
const host = createServerHost([jsconfigFile, jsFile]);
3283+
const session = createSession(host);
3284+
openFilesForSession([jsFile], session);
3285+
3286+
const getErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
3287+
CommandNames.SemanticDiagnosticsSync,
3288+
{ file: jsFile.path }
3289+
);
3290+
const errorResult = <protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
3291+
assert.isTrue(errorResult.length === 1);
3292+
assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code);
3293+
});
3294+
3295+
it("should report semanitc errors for configured js project with checkJs=true and skipLibCheck=true", () => {
3296+
const jsconfigFile = {
3297+
path: "/a/jsconfig.json",
3298+
content: JSON.stringify({
3299+
compilerOptions: {
3300+
checkJs: true,
3301+
skipLibCheck: true
3302+
},
3303+
})
3304+
};
3305+
const jsFile = {
3306+
path: "/a/jsFile.js",
3307+
content: `let x = 1;
3308+
x === "string";`
3309+
};
3310+
3311+
const host = createServerHost([jsconfigFile, jsFile]);
3312+
const session = createSession(host);
3313+
openFilesForSession([jsFile], session);
3314+
3315+
const getErrRequest = makeSessionRequest<protocol.SemanticDiagnosticsSyncRequestArgs>(
3316+
CommandNames.SemanticDiagnosticsSync,
3317+
{ file: jsFile.path }
3318+
);
3319+
const errorResult = <protocol.Diagnostic[]>session.executeCommand(getErrRequest).response;
3320+
assert.isTrue(errorResult.length === 1);
3321+
assert.equal(errorResult[0].code, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2.code);
3322+
});
32073323
});
32083324

32093325
describe("non-existing directories listed in config file input array", () => {

src/server/session.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,26 @@ namespace ts.server {
2525
return ((1e9 * seconds) + nanoseconds) / 1000000.0;
2626
}
2727

28-
function shouldSkipSemanticCheck(project: Project) {
29-
if (project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) {
30-
return project.isJsOnlyProject();
31-
}
32-
else {
33-
// For configured projects, require that skipLibCheck be set also
34-
const options = project.getCompilerOptions();
35-
return options.skipLibCheck && !options.checkJs && project.isJsOnlyProject();
28+
function isDeclarationFileInJSOnlyNonConfiguredProject(project: Project, file: NormalizedPath) {
29+
// Checking for semantic diagnostics is an expensive process. We want to avoid it if we
30+
// know for sure it is not needed.
31+
// For instance, .d.ts files injected by ATA automatically do not produce any relevant
32+
// errors to a JS- only project.
33+
//
34+
// Note that configured projects can set skipLibCheck (on by default in jsconfig.json) to
35+
// disable checking for declaration files. We only need to verify for inferred projects (e.g.
36+
// miscellaneous context in VS) and external projects(e.g.VS.csproj project) with only JS
37+
// files.
38+
//
39+
// We still want to check .js files in a JS-only inferred or external project (e.g. if the
40+
// file has '// @ts-check').
41+
42+
if ((project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) &&
43+
project.isJsOnlyProject()) {
44+
const scriptInfo = project.getScriptInfoForNormalizedPath(file);
45+
return scriptInfo && !scriptInfo.isJavaScript();
3646
}
47+
return false;
3748
}
3849

3950
interface FileStart {
@@ -489,7 +500,7 @@ namespace ts.server {
489500
private semanticCheck(file: NormalizedPath, project: Project) {
490501
try {
491502
let diags: Diagnostic[] = [];
492-
if (!shouldSkipSemanticCheck(project)) {
503+
if (!isDeclarationFileInJSOnlyNonConfiguredProject(project, file)) {
493504
diags = project.getLanguageService().getSemanticDiagnostics(file);
494505
}
495506

@@ -597,7 +608,7 @@ namespace ts.server {
597608

598609
private getDiagnosticsWorker(args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) {
599610
const { project, file } = this.getFileAndProject(args);
600-
if (isSemantic && shouldSkipSemanticCheck(project)) {
611+
if (isSemantic && isDeclarationFileInJSOnlyNonConfiguredProject(project, file)) {
601612
return [];
602613
}
603614
const scriptInfo = project.getScriptInfoForNormalizedPath(file);

tests/cases/compiler/noDefaultLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @skipDefaultLibCheck: false
12
/// <reference no-default-lib="true"/>
23
var x;
34

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
// @skipDefaultLibCheck: false
12
"use strict";
23
var eval;

0 commit comments

Comments
 (0)