Skip to content

Add editor configurable filename-based ATA #40952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,11 @@ namespace ts {
name: "exclude",
type: "string"
}
}
},
{
name: "disableFilenameBasedTypeAcquisition",
type: "boolean",
},
];

/* @internal */
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/jsTyping/jsTyping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>(
Expand Down
29 changes: 25 additions & 4 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -642,6 +652,8 @@ namespace ts.server {
private compilerOptionsForInferredProjectsPerProjectRoot = new Map<string, CompilerOptions>();
private watchOptionsForInferredProjects: WatchOptions | undefined;
private watchOptionsForInferredProjectsPerProjectRoot = new Map<string, WatchOptions | false>();
private typeAcquisitionForInferredProjects: TypeAcquisition | undefined;
private typeAcquisitionForInferredProjectsPerProjectRoot = new Map<string, TypeAcquisition | undefined>();
/**
* Project size for configured or external projects
*/
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 [];
}

Expand Down
43 changes: 16 additions & 27 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ namespace ts.server {
private symlinks: SymlinkCache | undefined;
/*@internal*/
autoImportProviderHost: AutoImportProviderProject | false | undefined;
/*@internal*/
protected typeAcquisition: TypeAcquisition | undefined;

/*@internal*/
constructor(
Expand Down Expand Up @@ -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) };
}
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -1770,7 +1779,8 @@ namespace ts.server {
watchOptions: WatchOptions | undefined,
projectRootPath: NormalizedPath | undefined,
currentDirectory: string | undefined,
pluginConfigOverrides: ESMap<string, any> | undefined) {
pluginConfigOverrides: ESMap<string, any> | undefined,
typeAcquisition: TypeAcquisition | undefined) {
super(InferredProject.newName(),
ProjectKind.Inferred,
projectService,
Expand All @@ -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);
Expand Down Expand Up @@ -1828,7 +1839,7 @@ namespace ts.server {
}

getTypeAcquisition(): TypeAcquisition {
return {
return this.typeAcquisition || {
enable: allRootFilesAreJsOrDts(this),
include: ts.emptyArray,
exclude: ts.emptyArray
Expand Down Expand Up @@ -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<string, WildcardDirectoryWatcher> | undefined;
Expand Down Expand Up @@ -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<string, WatchDirectoryFlags>) {
updateWatchingWildcardDirectories(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 */
Expand Down
7 changes: 6 additions & 1 deletion src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
89 changes: 89 additions & 0 deletions src/testRunner/unittests/tsserver/typingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) {
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",
Expand Down Expand Up @@ -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<string>) {
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
Expand Down
Loading