diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index a3e86c208b4..d4ef32dab84 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -1314,6 +1314,109 @@ namespace ts.projectSystem { }); + describe("ignoreConfigFiles", () => { + it("external project including config file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const externalProjectName = "externalproject"; + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, config1.path]), + options: {}, + projectFileName: externalProjectName + }); + + checkNumberOfProjects(projectService, { externalProjects: 1 }); + const proj = projectService.externalProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + + it("loose file included in config file (openClientFile)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.openClientFile(file1.path, file1.content); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + + it("loose file included in config file (applyCodeChanges)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + + const host = createServerHost([file1, config1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + projectService.applyChangesInOpenFiles([{ fileName: file1.path, content: file1.content }], [], []); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isTrue(proj.fileExists(file1.path)); + }); + }); + + it("disable inferred project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + + const host = createServerHost([file1]); + const projectService = createProjectService(host, { useSingleInferredProject: true }, { syntaxOnly: true }); + + projectService.openClientFile(file1.path, file1.content); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const proj = projectService.inferredProjects[0]; + assert.isDefined(proj); + + assert.isFalse(proj.languageServiceEnabled); + }); + it("reload regular file after closing", () => { const f1 = { path: "/a/b/app.ts", diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 386aa262964..55c5212a354 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -311,6 +311,7 @@ namespace ts.server { pluginProbeLocations?: ReadonlyArray; allowLocalPluginLoads?: boolean; typesMapLocation?: string; + syntaxOnly?: boolean; } function getDetailWatchInfo(watchType: WatchType, project: Project | undefined) { @@ -400,6 +401,8 @@ namespace ts.server { public readonly allowLocalPluginLoads: boolean; public readonly typesMapLocation: string | undefined; + public readonly syntaxOnly?: boolean; + /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects = createMap(); @@ -420,6 +423,7 @@ namespace ts.server { this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray; this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads; this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation; + this.syntaxOnly = opts.syntaxOnly; Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); if (this.host.realpath) { @@ -1197,6 +1201,11 @@ namespace ts.server { private forEachConfigFileLocation(info: ScriptInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void, projectRootPath?: NormalizedPath) { + + if (this.syntaxOnly) { + return undefined; + } + let searchPath = asNormalizedPath(getDirectoryPath(info.fileName)); while (!projectRootPath || containsPath(projectRootPath, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames)) { @@ -2004,7 +2013,7 @@ namespace ts.server { const info = this.getOrCreateScriptInfoOpenedByClientForNormalizedPath(fileName, projectRootPath ? this.getNormalizedAbsolutePath(projectRootPath) : this.currentDirectory, fileContent, scriptKind, hasMixedContent); let project: ConfiguredProject | ExternalProject = this.findExternalProjetContainingOpenScriptInfo(info); - if (!project) { + if (!project && !this.syntaxOnly) { // Checking syntaxOnly is an optimization configFileName = this.getConfigFileNameForFile(info, projectRootPath); if (configFileName) { project = this.findConfiguredProjectByProjectName(configFileName); @@ -2309,7 +2318,7 @@ namespace ts.server { for (const file of proj.rootFiles) { const normalized = toNormalizedPath(file.fileName); if (getBaseConfigFileName(normalized)) { - if (this.host.fileExists(normalized)) { + if (!this.syntaxOnly && this.host.fileExists(normalized)) { (tsConfigFiles || (tsConfigFiles = [])).push(normalized); } } diff --git a/src/server/project.ts b/src/server/project.ts index c7239fdc94d..442c7e3f5ad 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -131,7 +131,7 @@ namespace ts.server { // wrapper over the real language service that will suppress all semantic operations protected languageService: LanguageService; - public languageServiceEnabled = true; + public languageServiceEnabled: boolean; readonly trace?: (s: string) => void; readonly realpath?: (path: string) => string; @@ -240,6 +240,8 @@ namespace ts.server { this.compilerOptions.allowNonTsExtensions = true; } + this.languageServiceEnabled = !projectService.syntaxOnly; + this.setInternalCompilerOptionsForEmittingJsFiles(); const host = this.projectService.host; if (this.projectService.logger.loggingEnabled()) { @@ -255,7 +257,7 @@ namespace ts.server { // Use the current directory as resolution root only if the project created using current directory string this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory, /*logChangesWhenResolvingModule*/ true); - this.languageService = createLanguageService(this, this.documentRegistry); + this.languageService = createLanguageService(this, this.documentRegistry, projectService.syntaxOnly); if (lastFileExceededProgramSize) { this.disableLanguageService(lastFileExceededProgramSize); } @@ -506,7 +508,7 @@ namespace ts.server { } enableLanguageService() { - if (this.languageServiceEnabled) { + if (this.languageServiceEnabled || this.projectService.syntaxOnly) { return; } this.languageServiceEnabled = true; @@ -518,6 +520,7 @@ namespace ts.server { if (!this.languageServiceEnabled) { return; } + Debug.assert(!this.projectService.syntaxOnly); this.languageService.cleanupSemanticCache(); this.languageServiceEnabled = false; this.lastFileExceededProgramSize = lastFileExceededProgramSize; @@ -875,10 +878,12 @@ namespace ts.server { this.dirty = false; this.resolutionCache.finishCachingPerDirectoryResolution(); + Debug.assert(oldProgram === undefined || this.program !== undefined); + // bump up the version if // - oldProgram is not set - this is a first time updateGraph is called // - newProgram is different from the old program and structure of the old program was not reused. - const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)); + const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely))); this.hasChangedAutomaticTypeDirectiveNames = false; if (hasChanges) { if (oldProgram) { diff --git a/src/server/server.ts b/src/server/server.ts index 2e30fd51340..d863fe0bde6 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -513,6 +513,7 @@ namespace ts.server { logger, canUseEvents: true, suppressDiagnosticEvents, + syntaxOnly, globalPlugins, pluginProbeLocations, allowLocalPluginLoads, @@ -945,6 +946,7 @@ namespace ts.server { const useInferredProjectPerProjectRoot = hasArgument("--useInferredProjectPerProjectRoot"); const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition"); const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents"); + const syntaxOnly = hasArgument("--syntaxOnly"); const telemetryEnabled = hasArgument(Arguments.EnableTelemetry); logger.info(`Starting TS Server`); diff --git a/src/server/session.ts b/src/server/session.ts index 4d39b4d0cf9..1c5abc8a96b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -297,6 +297,7 @@ namespace ts.server { eventHandler?: ProjectServiceEventHandler; /** Has no effect if eventHandler is also specified. */ suppressDiagnosticEvents?: boolean; + syntaxOnly?: boolean; throttleWaitMilliseconds?: number; globalPlugins?: ReadonlyArray; @@ -359,7 +360,8 @@ namespace ts.server { suppressDiagnosticEvents: this.suppressDiagnosticEvents, globalPlugins: opts.globalPlugins, pluginProbeLocations: opts.pluginProbeLocations, - allowLocalPluginLoads: opts.allowLocalPluginLoads + allowLocalPluginLoads: opts.allowLocalPluginLoads, + syntaxOnly: opts.syntaxOnly, }; this.projectService = new ProjectService(settings); this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger); diff --git a/src/services/services.ts b/src/services/services.ts index 45d505466fa..d6405b8e732 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1152,8 +1152,10 @@ namespace ts { }; } - export function createLanguageService(host: LanguageServiceHost, - documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService { + export function createLanguageService( + host: LanguageServiceHost, + documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), + syntaxOnly = false): LanguageService { const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); let program: Program; @@ -1188,6 +1190,8 @@ namespace ts { } function synchronizeHostData(): void { + Debug.assert(!syntaxOnly); + // perform fast check if host supports it if (host.getProjectVersion) { const hostProjectVersion = host.getProjectVersion(); @@ -1363,6 +1367,11 @@ namespace ts { } function getProgram(): Program { + if (syntaxOnly) { + Debug.assert(program === undefined); + return undefined; + } + synchronizeHostData(); return program; diff --git a/src/services/shims.ts b/src/services/shims.ts index 42311843ea1..a96db02f1e6 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -1197,7 +1197,7 @@ namespace ts { this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()); } const hostAdapter = new LanguageServiceShimHostAdapter(host); - const languageService = createLanguageService(hostAdapter, this.documentRegistry); + const languageService = createLanguageService(hostAdapter, this.documentRegistry, /*syntaxOnly*/ false); return new LanguageServiceShimObject(this, host, languageService); } catch (err) { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3bb826b0fef..f897b3bf84e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4867,7 +4867,7 @@ declare namespace ts { function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; let disableIncrementalParsing: boolean; function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; - function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; + function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService; /** * Get the path of the default library files (lib.d.ts) as distributed with the typescript * node package. @@ -7295,6 +7295,7 @@ declare namespace ts.server { eventHandler?: ProjectServiceEventHandler; /** Has no effect if eventHandler is also specified. */ suppressDiagnosticEvents?: boolean; + syntaxOnly?: boolean; throttleWaitMilliseconds?: number; globalPlugins?: ReadonlyArray; pluginProbeLocations?: ReadonlyArray; @@ -7864,6 +7865,7 @@ declare namespace ts.server { pluginProbeLocations?: ReadonlyArray; allowLocalPluginLoads?: boolean; typesMapLocation?: string; + syntaxOnly?: boolean; } class ProjectService { readonly typingsCache: TypingsCache; @@ -7930,6 +7932,7 @@ declare namespace ts.server { readonly pluginProbeLocations: ReadonlyArray; readonly allowLocalPluginLoads: boolean; readonly typesMapLocation: string | undefined; + readonly syntaxOnly?: boolean; /** Tracks projects that we have already sent telemetry for. */ private readonly seenProjects; constructor(opts: ProjectServiceOptions); diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 99804672930..1148b5ba9ea 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5120,7 +5120,7 @@ declare namespace ts { function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile; let disableIncrementalParsing: boolean; function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile; - function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry): LanguageService; + function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService; /** * Get the path of the default library files (lib.d.ts) as distributed with the typescript * node package.