Skip to content

Commit 08e4f36

Browse files
authored
Add editor configurable filename-based ATA (#40952)
* add typeAcquisition:inferTypings * remove unused property * handle inferred and external projects separately * update missed rename * fix tests * pass as external compilerOption * update test * remove hostConfig reference * change option name * remove extraneous property * add inferredProjectCompilerOptions
1 parent 3918e6c commit 08e4f36

File tree

9 files changed

+162
-48
lines changed

9 files changed

+162
-48
lines changed

src/compiler/commandLineParser.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,11 @@ namespace ts {
11341134
name: "exclude",
11351135
type: "string"
11361136
}
1137-
}
1137+
},
1138+
{
1139+
name: "disableFilenameBasedTypeAcquisition",
1140+
type: "boolean",
1141+
},
11381142
];
11391143

11401144
/* @internal */

src/compiler/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5855,7 +5855,8 @@ namespace ts {
58555855
enable?: boolean;
58565856
include?: string[];
58575857
exclude?: string[];
5858-
[option: string]: string[] | boolean | undefined;
5858+
disableFilenameBasedTypeAcquisition?: boolean;
5859+
[option: string]: CompilerOptionsValue | undefined;
58595860
}
58605861

58615862
export enum ModuleKind {

src/jsTyping/jsTyping.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,9 @@ namespace ts.JsTyping {
149149
const nodeModulesPath = combinePaths(searchDir, "node_modules");
150150
getTypingNamesFromPackagesFolder(nodeModulesPath, filesToWatch);
151151
});
152-
getTypingNamesFromSourceFileNames(fileNames);
153-
152+
if(!typeAcquisition.disableFilenameBasedTypeAcquisition) {
153+
getTypingNamesFromSourceFileNames(fileNames);
154+
}
154155
// add typings for unresolved imports
155156
if (unresolvedImports) {
156157
const module = deduplicate<string>(

src/server/editorServices.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,16 @@ namespace ts.server {
263263
return result;
264264
}
265265

266+
export function convertTypeAcquisition(protocolOptions: protocol.InferredProjectCompilerOptions): TypeAcquisition | undefined {
267+
let result: TypeAcquisition | undefined;
268+
typeAcquisitionDeclarations.forEach((option) => {
269+
const propertyValue = protocolOptions[option.name];
270+
if (propertyValue === undefined) return;
271+
(result || (result = {}))[option.name] = propertyValue;
272+
});
273+
return result;
274+
}
275+
266276
export function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind {
267277
return isString(scriptKindName) ? convertScriptKindName(scriptKindName) : scriptKindName;
268278
}
@@ -642,6 +652,8 @@ namespace ts.server {
642652
private compilerOptionsForInferredProjectsPerProjectRoot = new Map<string, CompilerOptions>();
643653
private watchOptionsForInferredProjects: WatchOptions | undefined;
644654
private watchOptionsForInferredProjectsPerProjectRoot = new Map<string, WatchOptions | false>();
655+
private typeAcquisitionForInferredProjects: TypeAcquisition | undefined;
656+
private typeAcquisitionForInferredProjectsPerProjectRoot = new Map<string, TypeAcquisition | undefined>();
645657
/**
646658
* Project size for configured or external projects
647659
*/
@@ -982,11 +994,12 @@ namespace ts.server {
982994
}
983995
}
984996

985-
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void {
997+
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.InferredProjectCompilerOptions, projectRootPath?: string): void {
986998
Debug.assert(projectRootPath === undefined || this.useInferredProjectPerProjectRoot, "Setting compiler options per project root path is only supported when useInferredProjectPerProjectRoot is enabled");
987999

9881000
const compilerOptions = convertCompilerOptions(projectCompilerOptions);
9891001
const watchOptions = convertWatchOptions(projectCompilerOptions);
1002+
const typeAcquisition = convertTypeAcquisition(projectCompilerOptions);
9901003

9911004
// always set 'allowNonTsExtensions' for inferred projects since user cannot configure it from the outside
9921005
// previously we did not expose a way for user to change these settings and this option was enabled by default
@@ -995,10 +1008,12 @@ namespace ts.server {
9951008
if (canonicalProjectRootPath) {
9961009
this.compilerOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, compilerOptions);
9971010
this.watchOptionsForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, watchOptions || false);
1011+
this.typeAcquisitionForInferredProjectsPerProjectRoot.set(canonicalProjectRootPath, typeAcquisition);
9981012
}
9991013
else {
10001014
this.compilerOptionsForInferredProjects = compilerOptions;
10011015
this.watchOptionsForInferredProjects = watchOptions;
1016+
this.typeAcquisitionForInferredProjects = typeAcquisition;
10021017
}
10031018

10041019
for (const project of this.inferredProjects) {
@@ -1015,6 +1030,7 @@ namespace ts.server {
10151030
!project.projectRootPath || !this.compilerOptionsForInferredProjectsPerProjectRoot.has(project.projectRootPath)) {
10161031
project.setCompilerOptions(compilerOptions);
10171032
project.setWatchOptions(watchOptions);
1033+
project.setTypeAcquisition(typeAcquisition);
10181034
project.compileOnSaveEnabled = compilerOptions.compileOnSave!;
10191035
project.markAsDirty();
10201036
this.delayUpdateProjectGraph(project);
@@ -2298,13 +2314,18 @@ namespace ts.server {
22982314
private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: NormalizedPath): InferredProject {
22992315
const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects!; // TODO: GH#18217
23002316
let watchOptions: WatchOptions | false | undefined;
2317+
let typeAcquisition: TypeAcquisition | undefined;
23012318
if (projectRootPath) {
23022319
watchOptions = this.watchOptionsForInferredProjectsPerProjectRoot.get(projectRootPath);
2320+
typeAcquisition = this.typeAcquisitionForInferredProjectsPerProjectRoot.get(projectRootPath);
23032321
}
23042322
if (watchOptions === undefined) {
23052323
watchOptions = this.watchOptionsForInferredProjects;
23062324
}
2307-
const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides);
2325+
if (typeAcquisition === undefined) {
2326+
typeAcquisition = this.typeAcquisitionForInferredProjects;
2327+
}
2328+
const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptions || undefined, projectRootPath, currentDirectory, this.currentPluginConfigOverrides, typeAcquisition);
23082329
if (isSingleInferredProject) {
23092330
this.inferredProjects.unshift(project);
23102331
}
@@ -3513,8 +3534,8 @@ namespace ts.server {
35133534
const { rootFiles } = proj;
35143535
const typeAcquisition = proj.typeAcquisition!;
35153536
Debug.assert(!!typeAcquisition, "proj.typeAcquisition should be set by now");
3516-
// If type acquisition has been explicitly disabled, do not exclude anything from the project
3517-
if (typeAcquisition.enable === false) {
3537+
3538+
if (typeAcquisition.enable === false || typeAcquisition.disableFilenameBasedTypeAcquisition) {
35183539
return [];
35193540
}
35203541

src/server/project.ts

+16-27
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ namespace ts.server {
249249
private symlinks: SymlinkCache | undefined;
250250
/*@internal*/
251251
autoImportProviderHost: AutoImportProviderProject | false | undefined;
252+
/*@internal*/
253+
protected typeAcquisition: TypeAcquisition | undefined;
252254

253255
/*@internal*/
254256
constructor(
@@ -703,12 +705,11 @@ namespace ts.server {
703705
getProjectName() {
704706
return this.projectName;
705707
}
706-
abstract getTypeAcquisition(): TypeAcquisition;
707708

708-
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition): TypeAcquisition {
709+
protected removeLocalTypingsFromTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): TypeAcquisition {
709710
if (!newTypeAcquisition || !newTypeAcquisition.include) {
710711
// Nothing to filter out, so just return as-is
711-
return newTypeAcquisition;
712+
return newTypeAcquisition || {};
712713
}
713714
return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
714715
}
@@ -1411,6 +1412,14 @@ namespace ts.server {
14111412
return this.watchOptions;
14121413
}
14131414

1415+
setTypeAcquisition(newTypeAcquisition: TypeAcquisition | undefined): void {
1416+
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
1417+
}
1418+
1419+
getTypeAcquisition() {
1420+
return this.typeAcquisition || {};
1421+
}
1422+
14141423
/* @internal */
14151424
getChangesSinceVersion(lastKnownVersion?: number, includeProjectReferenceRedirectInfo?: boolean): ProjectFilesWithTSDiagnostics {
14161425
const includeProjectReferenceRedirectInfoIfRequested =
@@ -1786,7 +1795,8 @@ namespace ts.server {
17861795
watchOptions: WatchOptions | undefined,
17871796
projectRootPath: NormalizedPath | undefined,
17881797
currentDirectory: string | undefined,
1789-
pluginConfigOverrides: ESMap<string, any> | undefined) {
1798+
pluginConfigOverrides: ESMap<string, any> | undefined,
1799+
typeAcquisition: TypeAcquisition | undefined) {
17901800
super(InferredProject.newName(),
17911801
ProjectKind.Inferred,
17921802
projectService,
@@ -1799,6 +1809,7 @@ namespace ts.server {
17991809
watchOptions,
18001810
projectService.host,
18011811
currentDirectory);
1812+
this.typeAcquisition = typeAcquisition;
18021813
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
18031814
if (!projectRootPath && !projectService.useSingleInferredProject) {
18041815
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
@@ -1844,7 +1855,7 @@ namespace ts.server {
18441855
}
18451856

18461857
getTypeAcquisition(): TypeAcquisition {
1847-
return {
1858+
return this.typeAcquisition || {
18481859
enable: allRootFilesAreJsOrDts(this),
18491860
include: ts.emptyArray,
18501861
exclude: ts.emptyArray
@@ -2026,7 +2037,6 @@ namespace ts.server {
20262037
* Otherwise it will create an InferredProject.
20272038
*/
20282039
export class ConfiguredProject extends Project {
2029-
private typeAcquisition: TypeAcquisition | undefined;
20302040
/* @internal */
20312041
configFileWatcher: FileWatcher | undefined;
20322042
private directoriesWatchedForWildcards: ESMap<string, WildcardDirectoryWatcher> | undefined;
@@ -2238,14 +2248,6 @@ namespace ts.server {
22382248
this.projectErrors = projectErrors;
22392249
}
22402250

2241-
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
2242-
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
2243-
}
2244-
2245-
getTypeAcquisition() {
2246-
return this.typeAcquisition || {};
2247-
}
2248-
22492251
/*@internal*/
22502252
watchWildcards(wildcardDirectories: ESMap<string, WatchDirectoryFlags>) {
22512253
updateWatchingWildcardDirectories(
@@ -2364,7 +2366,6 @@ namespace ts.server {
23642366
*/
23652367
export class ExternalProject extends Project {
23662368
excludedFiles: readonly NormalizedPath[] = [];
2367-
private typeAcquisition: TypeAcquisition | undefined;
23682369
/*@internal*/
23692370
constructor(public externalProjectName: string,
23702371
projectService: ProjectService,
@@ -2398,18 +2399,6 @@ namespace ts.server {
23982399
getExcludedFiles() {
23992400
return this.excludedFiles;
24002401
}
2401-
2402-
getTypeAcquisition() {
2403-
return this.typeAcquisition || {};
2404-
}
2405-
2406-
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
2407-
Debug.assert(!!newTypeAcquisition, "newTypeAcquisition may not be null/undefined");
2408-
Debug.assert(!!newTypeAcquisition.include, "newTypeAcquisition.include may not be null/undefined");
2409-
Debug.assert(!!newTypeAcquisition.exclude, "newTypeAcquisition.exclude may not be null/undefined");
2410-
Debug.assert(typeof newTypeAcquisition.enable === "boolean", "newTypeAcquisition.enable may not be null/undefined");
2411-
this.typeAcquisition = this.removeLocalTypingsFromTypeAcquisition(newTypeAcquisition);
2412-
}
24132402
}
24142403

24152404
/* @internal */

src/server/protocol.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,11 @@ namespace ts.server.protocol {
17621762
closedFiles?: string[];
17631763
}
17641764

1765+
/**
1766+
* External projects have a typeAcquisition option so they need to be added separately to compiler options for inferred projects.
1767+
*/
1768+
export type InferredProjectCompilerOptions = ExternalProjectCompilerOptions & TypeAcquisition;
1769+
17651770
/**
17661771
* Request to set compiler options for inferred projects.
17671772
* External projects are opened / closed explicitly.
@@ -1783,7 +1788,7 @@ namespace ts.server.protocol {
17831788
/**
17841789
* Compiler options to be used with inferred projects.
17851790
*/
1786-
options: ExternalProjectCompilerOptions;
1791+
options: InferredProjectCompilerOptions;
17871792

17881793
/**
17891794
* Specifies the project root path used to scope compiler options.

src/testRunner/unittests/tsserver/typingsInstaller.ts

+89
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,50 @@ namespace ts.projectSystem {
207207
checkProjectActualFiles(p, [file1.path, jquery.path]);
208208
});
209209

210+
it("inferred project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
211+
// Tests:
212+
// Exclude file with disableFilenameBasedTypeAcquisition:true
213+
const jqueryJs = {
214+
path: "/a/b/jquery.js",
215+
content: ""
216+
};
217+
218+
const messages: string[] = [];
219+
const host = createServerHost([jqueryJs]);
220+
const installer = new (class extends Installer {
221+
constructor() {
222+
super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
223+
}
224+
enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
225+
super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
226+
}
227+
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
228+
const installedTypings: string[] = [];
229+
const typingFiles: File[] = [];
230+
executeCommand(this, host, installedTypings, typingFiles, cb);
231+
}
232+
})();
233+
234+
const projectService = createProjectService(host, { typingsInstaller: installer });
235+
projectService.setCompilerOptionsForInferredProjects({
236+
allowJs: true,
237+
enable: true,
238+
disableFilenameBasedTypeAcquisition: true
239+
});
240+
projectService.openClientFile(jqueryJs.path);
241+
242+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
243+
const p = projectService.inferredProjects[0];
244+
checkProjectActualFiles(p, [jqueryJs.path]);
245+
246+
installer.installAll(/*expectedCount*/ 0);
247+
host.checkTimeoutQueueLengthAndRun(2);
248+
checkNumberOfProjects(projectService, { inferredProjects: 1 });
249+
// files should not be removed from project if ATA is skipped
250+
checkProjectActualFiles(p, [jqueryJs.path]);
251+
assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
252+
});
253+
210254
it("external project - no type acquisition, no .d.ts/js files", () => {
211255
const file1 = {
212256
path: "/a/b/app.ts",
@@ -434,6 +478,51 @@ namespace ts.projectSystem {
434478

435479
installer.checkPendingCommands(/*expectedCount*/ 0);
436480
});
481+
482+
it("external project - type acquisition with disableFilenameBasedTypeAcquisition:true", () => {
483+
// Tests:
484+
// Exclude file with disableFilenameBasedTypeAcquisition:true
485+
const jqueryJs = {
486+
path: "/a/b/jquery.js",
487+
content: ""
488+
};
489+
490+
const messages: string[] = [];
491+
const host = createServerHost([jqueryJs]);
492+
const installer = new (class extends Installer {
493+
constructor() {
494+
super(host, { typesRegistry: createTypesRegistry("jquery") }, { isEnabled: () => true, writeLine: msg => messages.push(msg) });
495+
}
496+
enqueueInstallTypingsRequest(project: server.Project, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>) {
497+
super.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
498+
}
499+
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
500+
const installedTypings: string[] = [];
501+
const typingFiles: File[] = [];
502+
executeCommand(this, host, installedTypings, typingFiles, cb);
503+
}
504+
})();
505+
506+
const projectFileName = "/a/app/test.csproj";
507+
const projectService = createProjectService(host, { typingsInstaller: installer });
508+
projectService.openExternalProject({
509+
projectFileName,
510+
options: { allowJS: true, moduleResolution: ModuleResolutionKind.NodeJs },
511+
rootFiles: [toExternalFile(jqueryJs.path)],
512+
typeAcquisition: { enable: true, disableFilenameBasedTypeAcquisition: true }
513+
});
514+
515+
const p = projectService.externalProjects[0];
516+
projectService.checkNumberOfProjects({ externalProjects: 1 });
517+
checkProjectActualFiles(p, [jqueryJs.path]);
518+
519+
installer.installAll(/*expectedCount*/ 0);
520+
projectService.checkNumberOfProjects({ externalProjects: 1 });
521+
// files should not be removed from project if ATA is skipped
522+
checkProjectActualFiles(p, [jqueryJs.path]);
523+
assert.isTrue(messages.indexOf("No new typings were requested as a result of typings discovery") > 0, "Should not request filename-based typings");
524+
});
525+
437526
it("external project - no type acquisition, with js & ts files", () => {
438527
// Tests:
439528
// 1. No typings are included for JS projects when the project contains ts files

0 commit comments

Comments
 (0)