diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 04834a11a29d4..5f42c634329d2 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -1133,7 +1133,11 @@ namespace ts { name: "exclude", type: "string" } - } + }, + { + name: "disableFilenameBasedTypeAcquisition", + type: "boolean", + }, ]; /* @internal */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dbc916cd56756..9109d1c4af9f3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5844,7 +5844,8 @@ namespace ts { enable?: boolean; include?: string[]; exclude?: string[]; - [option: string]: string[] | boolean | undefined; + disableFilenameBasedTypeAcquisition?: boolean; + [option: string]: CompilerOptionsValue | undefined; } export enum ModuleKind { diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index ca765bd776fd3..10ef0cf957a97 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -149,8 +149,9 @@ namespace ts.JsTyping { const nodeModulesPath = combinePaths(searchDir, "node_modules"); getTypingNamesFromPackagesFolder(nodeModulesPath, filesToWatch); }); - getTypingNamesFromSourceFileNames(fileNames); - + if(!typeAcquisition.disableFilenameBasedTypeAcquisition) { + getTypingNamesFromSourceFileNames(fileNames); + } // add typings for unresolved imports if (unresolvedImports) { const module = deduplicate( diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d0b4891c96324..20e426c0561f1 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -263,6 +263,16 @@ namespace ts.server { return result; } + export function convertTypeAcquisition(protocolOptions: protocol.InferredProjectCompilerOptions): TypeAcquisition | undefined { + let result: TypeAcquisition | undefined; + typeAcquisitionDeclarations.forEach((option) => { + const propertyValue = protocolOptions[option.name]; + if (propertyValue === undefined) return; + (result || (result = {}))[option.name] = propertyValue; + }); + return result; + } + export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind { return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName; } @@ -642,6 +652,8 @@ namespace ts.server { private compilerOptionsForInferredProjectsPerProjectRoot = new Map(); private watchOptionsForInferredProjects: WatchOptions | undefined; private watchOptionsForInferredProjectsPerProjectRoot = new Map(); + private typeAcquisitionForInferredProjects: TypeAcquisition | undefined; + private typeAcquisitionForInferredProjectsPerProjectRoot = new Map(); /** * Project size for configured or external projects */ @@ -983,11 +995,12 @@ namespace ts.server { } } - setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void { + setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.InferredProjectCompilerOptions, projectRootPath?: string): void { Debug.assert(projectRootPath === undefined || this.useInferredProjectPerProjectRoot, "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled"); const compilerOptions = convertCompilerOptions(projectCompilerOptions); const watchOptions = convertWatchOptions(projectCompilerOptions); + const typeAcquisition = convertTypeAcquisition(projectCompilerOptions); // always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside // previously we did not expose a way for user to change these settings and this option was enabled by default @@ -996,10 +1009,12 @@ namespace ts.server { if (canonicalProjectRootPath) { this.compilerOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, compilerOptions); this.watchOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, watchOptions || false); + this.typeAcquisitionForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, typeAcquisition); } else { this.compilerOptionsForInferredProjects = compilerOptions; this.watchOptionsForInferredProjects = watchOptions; + this.typeAcquisitionForInferredProjects = typeAcquisition; } for (const project of this.inferredProjects) { @@ -1016,6 +1031,7 @@ namespace ts.server { !project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) { project.setCompilerOptions(compilerOptions); project.setWatchOptions(watchOptions); + project.setTypeAcquisition(typeAcquisition); project.compileOnSaveEnabled = compilerOptions.compileOnSave!; project.markAsDirty(); this.delayUpdateProjectGraph(project); @@ -2299,13 +2315,18 @@ namespace ts.server { private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject { const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects!; // TODO: GH#18217 let watchOptions: WatchOptions | false | undefined; + let typeAcquisition: TypeAcquisition | undefined; if (projectRootPath) { watchOptions = this.watchOptionsForInferredProjectsPerProjectRoot.get(projectRootPath); + typeAcquisition = this.typeAcquisitionForInferredProjectsPerProjectRoot.get(projectRootPath); } if (watchOptions === undefined) { watchOptions = this.watchOptionsForInferredProjects; } - const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides); + if (typeAcquisition === undefined) { + typeAcquisition = this.typeAcquisitionForInferredProjects; + } + const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides, typeAcquisition); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } @@ -3514,8 +3535,8 @@ namespace ts.server { const { rootFiles } = proj; const typeAcquisition = proj.typeAcquisition!; Debug.assert(!!typeAcquisition, "proj.typeAcquisition should be set by now"); - // If type acquisition has been explicitly disabled, do not exclude anything from the project - if (typeAcquisition.enable === false) { + + if (typeAcquisition.enable === false || typeAcquisition.disableFilenameBasedTypeAcquisition) { return []; } diff --git a/src/server/project.ts b/src/server/project.ts index e372038354320..7477ef5e67c86 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -249,6 +249,8 @@ namespace ts.server { private symlinks: SymlinkCache | undefined; /*@internal*/ autoImportProviderHost: AutoImportProviderProject | false | undefined; + /*@internal*/ + protected typeAcquisition: TypeAcquisition | undefined; /*@internal*/ constructor( @@ -692,12 +694,11 @@ namespace ts.server { getProjectName() { return this.projectName; } - abstract getTypeAcquisition(): TypeAcquisition; - protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition { + protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): TypeAcquisition { if (!newTypeAcquisition || !newTypeAcquisition.include) { // Nothing to filter out, so just return as-is - return newTypeAcquisition; + return newTypeAcquisition || {}; } return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) }; } @@ -1400,6 +1401,14 @@ namespace ts.server { return this.watchOptions; } + setTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): void { + this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); + } + + getTypeAcquisition() { + return this.typeAcquisition || {}; + } + /* @internal */ getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics { const includeProjectReferenceRedirectInfoIfRequested = @@ -1770,7 +1779,8 @@ namespace ts.server { watchOptions: WatchOptions | undefined, projectRootPath: NormalizedPath | undefined, currentDirectory: string | undefined, - pluginConfigOverrides: ESMap | undefined) { + pluginConfigOverrides: ESMap | undefined, + typeAcquisition: TypeAcquisition | undefined) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -1783,6 +1793,7 @@ namespace ts.server { watchOptions, projectService.host, currentDirectory); + this.typeAcquisition = typeAcquisition; this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath); if (!projectRootPath && !projectService.useSingleInferredProject) { this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory); @@ -1828,7 +1839,7 @@ namespace ts.server { } getTypeAcquisition(): TypeAcquisition { - return { + return this.typeAcquisition || { enable: allRootFilesAreJsOrDts(this), include: ts.emptyArray, exclude: ts.emptyArray @@ -2010,7 +2021,6 @@ namespace ts.server { * Otherwise it will create an InferredProject. */ export class ConfiguredProject extends Project { - private typeAcquisition: TypeAcquisition | undefined; /* @internal */ configFileWatcher: FileWatcher | undefined; private directoriesWatchedForWildcards: ESMap | undefined; @@ -2222,14 +2232,6 @@ namespace ts.server { this.projectErrors = projectErrors; } - setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void { - this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); - } - - getTypeAcquisition() { - return this.typeAcquisition || {}; - } - /*@internal*/ watchWildcards(wildcardDirectories: ESMap) { updateWatchingWildcardDirectories( @@ -2348,7 +2350,6 @@ namespace ts.server { */ export class ExternalProject extends Project { excludedFiles: readonly NormalizedPath[] = []; - private typeAcquisition: TypeAcquisition | undefined; /*@internal*/ constructor(public externalProjectName: string, projectService: ProjectService, @@ -2382,18 +2383,6 @@ namespace ts.server { getExcludedFiles() { return this.excludedFiles; } - - getTypeAcquisition() { - return this.typeAcquisition || {}; - } - - setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void { - Debug.assert(!!newTypeAcquisition, "newTypeAcquisition may not be null/undefined"); - Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined"); - Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined"); - Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined"); - this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition); - } } /* @internal */ diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 6a52afc81757c..3706f5b85ecc5 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1762,6 +1762,11 @@ namespace ts.server.protocol { closedFiles?: string[]; } + /** + * External projects have a typeAcquisition option so they need to be added separately to compiler options for inferred projects. + */ + export type InferredProjectCompilerOptions = ExternalProjectCompilerOptions & TypeAcquisition; + /** * Request to set compiler options for inferred projects. * External projects are opened / closed explicitly. @@ -1783,7 +1788,7 @@ namespace ts.server.protocol { /** * Compiler options to be used with inferred projects. */ - options: ExternalProjectCompilerOptions; + options: InferredProjectCompilerOptions; /** * Specifies the project root path used to scope compiler options. diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index e9c77566b70ba..26ce8eca6dde4 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -207,6 +207,50 @@ namespace ts.projectSystem { checkProjectActualFiles(p, [file1.path, jquery.path]); }); + it("inferred project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => { + // Tests: + // Exclude file with disableFilenameBasedTypeAcquisition:true + const jqueryJs = { + path: "/a/b/jquery.js", + content: "" + }; + + const messages: string[] = []; + const host = createServerHost([jqueryJs]); + const installer = new (class extends Installer { + constructor() { + super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); + } + enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { + super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + const installedTypings: string[] = []; + const typingFiles: File[] = []; + executeCommand(this, host, installedTypings, typingFiles, cb); + } + })(); + + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.setCompilerOptionsForInferredProjects({ + allowJs: true, + enable: true, + disableFilenameBasedTypeAcquisition: true + }); + projectService.openClientFile(jqueryJs.path); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const p = projectService.inferredProjects[0]; + checkProjectActualFiles(p, [jqueryJs.path]); + + installer.installAll(/*expectedCount*/ 0); + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + // files should not be removed from project if ATA is skipped + checkProjectActualFiles(p, [jqueryJs.path]); + assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings"); + }); + it("external project - no type acquisition, no .d.ts/js files", () => { const file1 = { path: "/a/b/app.ts", @@ -434,6 +478,51 @@ namespace ts.projectSystem { installer.checkPendingCommands(/*expectedCount*/ 0); }); + + it("external project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => { + // Tests: + // Exclude file with disableFilenameBasedTypeAcquisition:true + const jqueryJs = { + path: "/a/b/jquery.js", + content: "" + }; + + const messages: string[] = []; + const host = createServerHost([jqueryJs]); + const installer = new (class extends Installer { + constructor() { + super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) }); + } + enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray) { + super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports); + } + installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void { + const installedTypings: string[] = []; + const typingFiles: File[] = []; + executeCommand(this, host, installedTypings, typingFiles, cb); + } + })(); + + const projectFileName = "/a/app/test.csproj"; + const projectService = createProjectService(host, { typingsInstaller: installer }); + projectService.openExternalProject({ + projectFileName, + options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs }, + rootFiles: [toExternalFile(jqueryJs.path)], + typeAcquisition: { enable: true, disableFilenameBasedTypeAcquisition: true } + }); + + const p = projectService.externalProjects[0]; + projectService.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(p, [jqueryJs.path]); + + installer.installAll(/*expectedCount*/ 0); + projectService.checkNumberOfProjects({ externalProjects: 1 }); + // files should not be removed from project if ATA is skipped + checkProjectActualFiles(p, [jqueryJs.path]); + assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings"); + }); + it("external project - no type acquisition, with js & ts files", () => { // Tests: // 1. No typings are included for JS projects when the project contains ts files diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 15653ecacf258..f9f821331ba44 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2891,7 +2891,8 @@ declare namespace ts { enable?: boolean; include?: string[]; exclude?: string[]; - [option: string]: string[] | boolean | undefined; + disableFilenameBasedTypeAcquisition?: boolean; + [option: string]: CompilerOptionsValue | undefined; } export enum ModuleKind { None = 0, @@ -7754,6 +7755,10 @@ declare namespace ts.server.protocol { */ closedFiles?: string[]; } + /** + * External projects have a typeAcquisition option so they need to be added separately to compiler options for inferred projects. + */ + type InferredProjectCompilerOptions = ExternalProjectCompilerOptions & TypeAcquisition; /** * Request to set compiler options for inferred projects. * External projects are opened / closed explicitly. @@ -7774,7 +7779,7 @@ declare namespace ts.server.protocol { /** * Compiler options to be used with inferred projects. */ - options: ExternalProjectCompilerOptions; + options: InferredProjectCompilerOptions; /** * Specifies the project root path used to scope compiler options. * It is an error to provide this property if the server has not been started with @@ -9300,8 +9305,7 @@ declare namespace ts.server { enableLanguageService(): void; disableLanguageService(lastFileExceededProgramSize?: string): void; getProjectName(): string; - abstract getTypeAcquisition(): TypeAcquisition; - protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition; + protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): TypeAcquisition; getExternalFiles(): SortedReadonlyArray; getSourceFile(path: Path): SourceFile | undefined; close(): void; @@ -9339,6 +9343,8 @@ declare namespace ts.server { getScriptInfo(uncheckedFileName: string): ScriptInfo | undefined; filesToString(writeProjectFileNames: boolean): string; setCompilerOptions(compilerOptions: CompilerOptions): void; + setTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): void; + getTypeAcquisition(): TypeAcquisition; protected removeRoot(info: ScriptInfo): void; protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map | undefined): void; protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map | undefined): void; @@ -9384,7 +9390,6 @@ declare namespace ts.server { * Otherwise it will create an InferredProject. */ class ConfiguredProject extends Project { - private typeAcquisition; private directoriesWatchedForWildcards; readonly canonicalConfigFilePath: NormalizedPath; /** Ref count to the project when opened from external project */ @@ -9408,8 +9413,6 @@ declare namespace ts.server { */ getAllProjectErrors(): readonly Diagnostic[]; setProjectErrors(projectErrors: Diagnostic[]): void; - setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; - getTypeAcquisition(): TypeAcquisition; close(): void; getEffectiveTypeRoots(): string[]; } @@ -9421,11 +9424,8 @@ declare namespace ts.server { externalProjectName: string; compileOnSaveEnabled: boolean; excludedFiles: readonly NormalizedPath[]; - private typeAcquisition; updateGraph(): boolean; getExcludedFiles(): readonly NormalizedPath[]; - getTypeAcquisition(): TypeAcquisition; - setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; } } declare namespace ts.server { @@ -9559,6 +9559,7 @@ declare namespace ts.server { export function convertFormatOptions(protocolOptions: protocol.FormatCodeSettings): FormatCodeSettings; export function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin; export function convertWatchOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): WatchOptions | undefined; + export function convertTypeAcquisition(protocolOptions: protocol.InferredProjectCompilerOptions): TypeAcquisition | undefined; export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind; export function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX; export interface HostConfiguration { @@ -9627,6 +9628,8 @@ declare namespace ts.server { private compilerOptionsForInferredProjectsPerProjectRoot; private watchOptionsForInferredProjects; private watchOptionsForInferredProjectsPerProjectRoot; + private typeAcquisitionForInferredProjects; + private typeAcquisitionForInferredProjectsPerProjectRoot; /** * Project size for configured or external projects */ @@ -9672,7 +9675,7 @@ declare namespace ts.server { updateTypingsForProject(response: SetTypings | InvalidateCachedTypings | PackageInstalledResponse): void; private delayUpdateProjectGraph; private delayUpdateProjectGraphs; - setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void; + setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.InferredProjectCompilerOptions, projectRootPath?: string): void; findProject(projectName: string): Project | undefined; getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean): Project | undefined; private doEnsureDefaultProjectForFile; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 26cdf6ba46056..5ae7f935e21bf 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2891,7 +2891,8 @@ declare namespace ts { enable?: boolean; include?: string[]; exclude?: string[]; - [option: string]: string[] | boolean | undefined; + disableFilenameBasedTypeAcquisition?: boolean; + [option: string]: CompilerOptionsValue | undefined; } export enum ModuleKind { None = 0,