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
This commit is contained in:
Jesse Trinity 2020-10-19 09:53:58 -07:00 committed by GitHub
parent 3918e6c535
commit 08e4f369fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 162 additions and 48 deletions

View File

@ -1134,7 +1134,11 @@ namespace ts {
name: "exclude",
type: "string"
}
}
},
{
name: "disableFilenameBasedTypeAcquisition",
type: "boolean",
},
];
/* @internal */

View File

@ -5855,7 +5855,8 @@ namespace ts {
enable?: boolean;
include?: string[];
exclude?: string[];
[option: string]: string[] | boolean | undefined;
disableFilenameBasedTypeAcquisition?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
export enum ModuleKind {

View File

@ -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>(

View File

@ -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<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
*/
@ -982,11 +994,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
@ -995,10 +1008,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) {
@ -1015,6 +1030,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);
@ -2298,13 +2314,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);
}
@ -3513,8 +3534,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 [];
}

View File

@ -249,6 +249,8 @@ namespace ts.server {
private symlinks: SymlinkCache | undefined;
/*@internal*/
autoImportProviderHost: AutoImportProviderProject | false | undefined;
/*@internal*/
protected typeAcquisition: TypeAcquisition | undefined;
/*@internal*/
constructor(
@ -703,12 +705,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) };
}
@ -1411,6 +1412,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 =
@ -1786,7 +1795,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,
@ -1799,6 +1809,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);
@ -1844,7 +1855,7 @@ namespace ts.server {
}
getTypeAcquisition(): TypeAcquisition {
return {
return this.typeAcquisition || {
enable: allRootFilesAreJsOrDts(this),
include: ts.emptyArray,
exclude: ts.emptyArray
@ -2026,7 +2037,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;
@ -2238,14 +2248,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(
@ -2364,7 +2366,6 @@ namespace ts.server {
*/
export class ExternalProject extends Project {
excludedFiles: readonly NormalizedPath[] = [];
private typeAcquisition: TypeAcquisition | undefined;
/*@internal*/
constructor(public externalProjectName: string,
projectService: ProjectService,
@ -2398,18 +2399,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 */

View File

@ -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.

View File

@ -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",
@ -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

View File

@ -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<string>;
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<any> | undefined): void;
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | 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;

View File

@ -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,