diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 825519bfb057c..638b086081781 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -902,7 +902,7 @@ namespace ts { basePath: string, errors: Diagnostic[], configFileName?: string): CompilerOptions { const options: CompilerOptions = getBaseFileName(configFileName) === "jsconfig.json" - ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true } + ? { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, skipLibCheck: true } : {}; convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors); return options; diff --git a/src/harness/unittests/convertCompilerOptionsFromJson.ts b/src/harness/unittests/convertCompilerOptionsFromJson.ts index 73ee8669adb11..3967d97fec8c6 100644 --- a/src/harness/unittests/convertCompilerOptionsFromJson.ts +++ b/src/harness/unittests/convertCompilerOptionsFromJson.ts @@ -405,6 +405,7 @@ namespace ts { allowJs: true, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, + skipLibCheck: true, module: ModuleKind.CommonJS, target: ScriptTarget.ES5, noImplicitAny: false, @@ -433,6 +434,7 @@ namespace ts { allowJs: false, maxNodeModuleJsDepth: 2, allowSyntheticDefaultImports: true, + skipLibCheck: true, module: ModuleKind.CommonJS, target: ScriptTarget.ES5, noImplicitAny: false, @@ -456,7 +458,8 @@ namespace ts { { allowJs: true, maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true + allowSyntheticDefaultImports: true, + skipLibCheck: true }, errors: [{ file: undefined, @@ -477,7 +480,8 @@ namespace ts { { allowJs: true, maxNodeModuleJsDepth: 2, - allowSyntheticDefaultImports: true + allowSyntheticDefaultImports: true, + skipLibCheck: true }, errors: [] } diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index f389375395a84..3d38be867dbc5 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -3,6 +3,7 @@ namespace ts.projectSystem { import TI = server.typingsInstaller; + import protocol = server.protocol; import CommandNames = server.CommandNames; const safeList = { @@ -125,7 +126,7 @@ namespace ts.projectSystem { return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); } - export function toExternalFile(fileName: string): server.protocol.ExternalFile { + export function toExternalFile(fileName: string): protocol.ExternalFile { return { fileName }; } @@ -551,7 +552,7 @@ namespace ts.projectSystem { } export function makeSessionRequest(command: string, args: T) { - const newRequest: server.protocol.Request = { + const newRequest: protocol.Request = { seq: 0, type: "request", command, @@ -562,7 +563,7 @@ namespace ts.projectSystem { export function openFilesForSession(files: FileOrFolder[], session: server.Session) { for (const file of files) { - const request = makeSessionRequest(CommandNames.Open, { file: file.path }); + const request = makeSessionRequest(CommandNames.Open, { file: file.path }); session.executeCommand(request); } } @@ -1775,7 +1776,7 @@ namespace ts.projectSystem { }); describe("navigate-to for javascript project", () => { - function containsNavToItem(items: server.protocol.NavtoItem[], itemName: string, itemKind: string) { + function containsNavToItem(items: protocol.NavtoItem[], itemName: string, itemKind: string) { return find(items, item => item.name === itemName && item.kind === itemKind) !== undefined; } @@ -1793,12 +1794,12 @@ namespace ts.projectSystem { openFilesForSession([file1], session); // Try to find some interface type defined in lib.d.ts - const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); - const items: server.protocol.NavtoItem[] = session.executeCommand(libTypeNavToRequest).response; + const libTypeNavToRequest = makeSessionRequest(CommandNames.Navto, { searchValue: "Document", file: file1.path, projectFileName: configFile.path }); + const items: protocol.NavtoItem[] = session.executeCommand(libTypeNavToRequest).response; assert.isFalse(containsNavToItem(items, "Document", "interface"), `Found lib.d.ts symbol in JavaScript project nav to request result.`); - const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); - const items2: server.protocol.NavtoItem[] = session.executeCommand(localFunctionNavToRequst).response; + const localFunctionNavToRequst = makeSessionRequest(CommandNames.Navto, { searchValue: "foo", file: file1.path, projectFileName: configFile.path }); + const items2: protocol.NavtoItem[] = session.executeCommand(localFunctionNavToRequst).response; assert.isTrue(containsNavToItem(items2, "foo", "function"), `Cannot find function symbol "foo".`); }); }); @@ -2245,7 +2246,82 @@ namespace ts.projectSystem { // verify content const snap2 = projectServiice.getScriptInfo(f1.path).snap(); assert.equal(snap2.getText(0, snap2.getLength()), f1.content, "content should be equal to the content of original file"); + }); + }); + + describe("skipLibCheck", () => { + it("should be turned on for js-only inferred projects", () => { + const file1 = { + path: "/a/b/file1.js", + content: ` + /// + var x = 1;` + }; + const file2 = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([file1, file2]); + const session = createSession(host); + openFilesForSession([file1, file2], session); + + const file2GetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: file2.path } + ); + let errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + const closeFileRequest = makeSessionRequest(CommandNames.Close, { file: file1.path }); + session.executeCommand(closeFileRequest); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length !== 0); + + openFilesForSession([file1], session); + errorResult = session.executeCommand(file2GetErrRequest).response; + assert.isTrue(errorResult.length === 0); + }); + + it("should be turned on for js-only external projects", () => { + const jsFile = { + path: "/a/b/file1.js", + content: "let x =1;" + }; + const dTsFile = { + path: "/a/b/file2.d.ts", + content: ` + interface T { + name: string; + }; + interface T { + name: number; + };` + }; + const host = createServerHost([jsFile, dTsFile]); + const session = createSession(host); + + const openExternalProjectRequest = makeSessionRequest( + CommandNames.OpenExternalProject, + { + projectFileName: "project1", + rootFiles: toExternalFiles([jsFile.path, dTsFile.path]), + options: {} + } + ); + session.executeCommand(openExternalProjectRequest); + + const dTsFileGetErrRequest = makeSessionRequest( + CommandNames.SemanticDiagnosticsSync, + { file: dTsFile.path } + ); + const errorResult = session.executeCommand(dTsFileGetErrRequest).response; + assert.isTrue(errorResult.length === 0); }); }); } \ No newline at end of file diff --git a/src/server/client.ts b/src/server/client.ts index 5f8faa7b3ad1a..fb152814e9d96 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -397,11 +397,41 @@ namespace ts.server { } getSyntacticDiagnostics(fileName: string): Diagnostic[] { - throw new Error("Not Implemented Yet."); + const args: protocol.SyntacticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true }; + + const request = this.processRequest(CommandNames.SyntacticDiagnosticsSync, args); + const response = this.processResponse(request); + + return (response.body).map(entry => this.convertDiagnostic(entry, fileName)); } getSemanticDiagnostics(fileName: string): Diagnostic[] { - throw new Error("Not Implemented Yet."); + const args: protocol.SemanticDiagnosticsSyncRequestArgs = { file: fileName, includeLinePosition: true }; + + const request = this.processRequest(CommandNames.SemanticDiagnosticsSync, args); + const response = this.processResponse(request); + + return (response.body).map(entry => this.convertDiagnostic(entry, fileName)); + } + + convertDiagnostic(entry: protocol.DiagnosticWithLinePosition, fileName: string): Diagnostic { + let category: DiagnosticCategory; + for (const id in DiagnosticCategory) { + if (typeof id === "string" && entry.category === id.toLowerCase()) { + category = (DiagnosticCategory)[id]; + } + } + + Debug.assert(category !== undefined, "convertDiagnostic: category should not be undefined"); + + return { + file: undefined, + start: entry.start, + length: entry.length, + messageText: entry.message, + category: category, + code: entry.code + }; } getCompilerOptionsDiagnostics(): Diagnostic[] { diff --git a/src/server/project.ts b/src/server/project.ts index 62f6d578e4381..aae8498c4c218 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -20,16 +20,42 @@ namespace ts.server { } } - function isJsOrDtsFile(info: ScriptInfo) { - return info.scriptKind === ScriptKind.JS || info.scriptKind == ScriptKind.JSX || fileExtensionIs(info.fileName, ".d.ts"); + function countEachFileTypes(infos: ScriptInfo[]): { js: number, jsx: number, ts: number, tsx: number, dts: number } { + const result = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 }; + for (const info of infos) { + switch (info.scriptKind) { + case ScriptKind.JS: + result.js += 1; + break; + case ScriptKind.JSX: + result.jsx += 1; + break; + case ScriptKind.TS: + fileExtensionIs(info.fileName, ".d.ts") + ? result.dts += 1 + : result.ts += 1; + break; + case ScriptKind.TSX: + result.tsx += 1; + break; + } + } + return result; + } + + function hasOneOrMoreJsAndNoTsFiles(project: Project) { + const counts = countEachFileTypes(project.getScriptInfos()); + return counts.js > 0 && counts.ts === 0 && counts.tsx === 0; } export function allRootFilesAreJsOrDts(project: Project): boolean { - return project.getRootScriptInfos().every(isJsOrDtsFile); + const counts = countEachFileTypes(project.getRootScriptInfos()); + return counts.ts === 0 && counts.tsx === 0; } export function allFilesAreJsOrDts(project: Project): boolean { - return project.getScriptInfos().every(isJsOrDtsFile); + const counts = countEachFileTypes(project.getScriptInfos()); + return counts.ts === 0 && counts.tsx === 0; } export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles { @@ -71,11 +97,16 @@ namespace ts.server { public typesVersion = 0; - public isJsOnlyProject() { + public isNonTsProject() { this.updateGraph(); return allFilesAreJsOrDts(this); } + public isJsOnlyProject() { + this.updateGraph(); + return hasOneOrMoreJsAndNoTsFiles(this); + } + constructor( readonly projectKind: ProjectKind, readonly projectService: ProjectService, diff --git a/src/server/session.ts b/src/server/session.ts index 6817624e131cd..742fc6b967aca 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -14,6 +14,17 @@ namespace ts.server { return ((1e9 * seconds) + nanoseconds) / 1000000.0; } + function shouldSkipSemanticCheck(project: Project) { + if (project.getCompilerOptions().skipLibCheck !== undefined) { + return false; + } + + if ((project.projectKind === ProjectKind.Inferred || project.projectKind === ProjectKind.External) && project.isJsOnlyProject()) { + return true; + } + return false; + } + interface FileStart { file: string; start: ILineInfo; @@ -255,12 +266,13 @@ namespace ts.server { private semanticCheck(file: NormalizedPath, project: Project) { try { - const diags = project.getLanguageService().getSemanticDiagnostics(file); - - if (diags) { - const bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); - this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag"); + let diags: Diagnostic[] = []; + if (!shouldSkipSemanticCheck(project)) { + diags = project.getLanguageService().getSemanticDiagnostics(file); } + + const bakedDiags = diags.map((diag) => formatDiag(file, project, diag)); + this.event({ file: file, diagnostics: bakedDiags }, "semanticDiag"); } catch (err) { this.logError(err, "semantic check"); @@ -371,8 +383,11 @@ namespace ts.server { }); } - private getDiagnosticsWorker(args: protocol.FileRequestArgs, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) { + private getDiagnosticsWorker(args: protocol.FileRequestArgs, isSemantic: boolean, selector: (project: Project, file: string) => Diagnostic[], includeLinePosition: boolean) { const { project, file } = this.getFileAndProject(args); + if (isSemantic && shouldSkipSemanticCheck(project)) { + return []; + } const scriptInfo = project.getScriptInfoForNormalizedPath(file); const diagnostics = selector(project, file); return includeLinePosition @@ -451,11 +466,11 @@ namespace ts.server { } private getSyntacticDiagnosticsSync(args: protocol.SyntacticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] { - return this.getDiagnosticsWorker(args, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition); + return this.getDiagnosticsWorker(args, /*isSemantic*/ false, (project, file) => project.getLanguageService().getSyntacticDiagnostics(file), args.includeLinePosition); } private getSemanticDiagnosticsSync(args: protocol.SemanticDiagnosticsSyncRequestArgs): protocol.Diagnostic[] | protocol.DiagnosticWithLinePosition[] { - return this.getDiagnosticsWorker(args, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition); + return this.getDiagnosticsWorker(args, /*isSemantic*/ true, (project, file) => project.getLanguageService().getSemanticDiagnostics(file), args.includeLinePosition); } private getDocumentHighlights(args: protocol.DocumentHighlightsRequestArgs, simplifiedResult: boolean): protocol.DocumentHighlightsItem[] | DocumentHighlights[] { @@ -1122,7 +1137,7 @@ namespace ts.server { return combineProjectOutput( projects, project => { - const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, /*excludeDts*/ project.isJsOnlyProject()); + const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, /*excludeDts*/ project.isNonTsProject()); if (!navItems) { return []; } @@ -1160,7 +1175,7 @@ namespace ts.server { else { return combineProjectOutput( projects, - project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, /*excludeDts*/ project.isJsOnlyProject()), + project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, /*excludeDts*/ project.isNonTsProject()), /*comparer*/ undefined, navigateToItemIsEqualTo); } diff --git a/tests/cases/fourslash/server/getJavaScriptSyntacticDiagnostics01.ts b/tests/cases/fourslash/server/getJavaScriptSyntacticDiagnostics01.ts new file mode 100644 index 0000000000000..0aa88ebd90c90 --- /dev/null +++ b/tests/cases/fourslash/server/getJavaScriptSyntacticDiagnostics01.ts @@ -0,0 +1,23 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// var ===; + +verify.getSyntacticDiagnostics(`[ + { + "message": "Variable declaration expected.", + "start": 4, + "length": 3, + "category": "error", + "code": 1134 + }, + { + "message": "Expression expected.", + "start": 7, + "length": 1, + "category": "error", + "code": 1109 + } +]`); +verify.getSemanticDiagnostics(`[]`); \ No newline at end of file