mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 11:35:42 -06:00
use unresolved imports as a source of used typings (#11828)
This commit is contained in:
parent
d0170d1ac8
commit
7890f63250
@ -456,6 +456,44 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, equaler?: (a: T, b: T) => boolean): boolean {
|
||||
if (!array1 || !array2) {
|
||||
return array1 === array2;
|
||||
}
|
||||
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < array1.length; i++) {
|
||||
const equals = equaler ? equaler(array1[i], array2[i]) : array1[i] === array2[i];
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function changesAffectModuleResolution(oldOptions: CompilerOptions, newOptions: CompilerOptions): boolean {
|
||||
return !oldOptions ||
|
||||
(oldOptions.module !== newOptions.module) ||
|
||||
(oldOptions.moduleResolution !== newOptions.moduleResolution) ||
|
||||
(oldOptions.noResolve !== newOptions.noResolve) ||
|
||||
(oldOptions.target !== newOptions.target) ||
|
||||
(oldOptions.noLib !== newOptions.noLib) ||
|
||||
(oldOptions.jsx !== newOptions.jsx) ||
|
||||
(oldOptions.allowJs !== newOptions.allowJs) ||
|
||||
(oldOptions.rootDir !== newOptions.rootDir) ||
|
||||
(oldOptions.configFilePath !== newOptions.configFilePath) ||
|
||||
(oldOptions.baseUrl !== newOptions.baseUrl) ||
|
||||
(oldOptions.maxNodeModuleJsDepth !== newOptions.maxNodeModuleJsDepth) ||
|
||||
!arrayIsEqualTo(oldOptions.lib, newOptions.lib) ||
|
||||
!arrayIsEqualTo(oldOptions.typeRoots, newOptions.typeRoots) ||
|
||||
!arrayIsEqualTo(oldOptions.rootDirs, newOptions.rootDirs) ||
|
||||
!equalOwnProperties(oldOptions.paths, newOptions.paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compacts an array, removing any falsey elements.
|
||||
*/
|
||||
@ -821,7 +859,7 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function extend<T1, T2>(first: T1 , second: T2): T1 & T2 {
|
||||
export function extend<T1, T2>(first: T1, second: T2): T1 & T2 {
|
||||
const result: T1 & T2 = <any>{};
|
||||
for (const id in second) if (hasOwnProperty.call(second, id)) {
|
||||
(result as any)[id] = (second as any)[id];
|
||||
@ -974,8 +1012,8 @@ namespace ts {
|
||||
Debug.assert(length >= 0, "length must be non-negative, is " + length);
|
||||
|
||||
if (file) {
|
||||
Debug.assert(start <= file.text.length, `start must be within the bounds of the file. ${ start } > ${ file.text.length }`);
|
||||
Debug.assert(end <= file.text.length, `end must be the bounds of the file. ${ end } > ${ file.text.length }`);
|
||||
Debug.assert(start <= file.text.length, `start must be within the bounds of the file. ${start} > ${file.text.length}`);
|
||||
Debug.assert(end <= file.text.length, `end must be the bounds of the file. ${end} > ${file.text.length}`);
|
||||
}
|
||||
|
||||
let text = getLocaleSpecificMessage(message);
|
||||
@ -1525,7 +1563,7 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
|
||||
const replaceWildcardCharacter = usage === "files" ? replaceWildCardCharacterFiles : replaceWildCardCharacterOther;
|
||||
const singleAsteriskRegexFragment = usage === "files" ? singleAsteriskRegexFragmentFiles : singleAsteriskRegexFragmentOther;
|
||||
|
||||
/**
|
||||
@ -1767,7 +1805,7 @@ namespace ts {
|
||||
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
|
||||
export const supportedTypescriptExtensionsForExtractExtension = [".d.ts", ".ts", ".tsx"];
|
||||
export const supportedJavascriptExtensions = [".js", ".jsx"];
|
||||
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);
|
||||
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);
|
||||
|
||||
export function getSupportedExtensions(options?: CompilerOptions): string[] {
|
||||
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
|
||||
|
||||
@ -462,21 +462,7 @@ namespace ts {
|
||||
// check properties that can affect structure of the program or module resolution strategy
|
||||
// if any of these properties has changed - structure cannot be reused
|
||||
const oldOptions = oldProgram.getCompilerOptions();
|
||||
if ((oldOptions.module !== options.module) ||
|
||||
(oldOptions.moduleResolution !== options.moduleResolution) ||
|
||||
(oldOptions.noResolve !== options.noResolve) ||
|
||||
(oldOptions.target !== options.target) ||
|
||||
(oldOptions.noLib !== options.noLib) ||
|
||||
(oldOptions.jsx !== options.jsx) ||
|
||||
(oldOptions.allowJs !== options.allowJs) ||
|
||||
(oldOptions.rootDir !== options.rootDir) ||
|
||||
(oldOptions.configFilePath !== options.configFilePath) ||
|
||||
(oldOptions.baseUrl !== options.baseUrl) ||
|
||||
(oldOptions.maxNodeModuleJsDepth !== options.maxNodeModuleJsDepth) ||
|
||||
!arrayIsEqualTo(oldOptions.lib, options.lib) ||
|
||||
!arrayIsEqualTo(oldOptions.typeRoots, options.typeRoots) ||
|
||||
!arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) ||
|
||||
!equalOwnProperties(oldOptions.paths, options.paths)) {
|
||||
if (changesAffectModuleResolution(oldOptions, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -3041,6 +3041,7 @@ namespace ts {
|
||||
packageNameToTypingLocation: Map<string>; // The map of package names to their cached typing locations
|
||||
typingOptions: TypingOptions; // Used to customize the typing inference process
|
||||
compilerOptions: CompilerOptions; // Used as a source for typing inference
|
||||
unresolvedImports: ReadonlyArray<string>; // List of unresolved module ids from imports
|
||||
}
|
||||
|
||||
export enum ModuleKind {
|
||||
|
||||
@ -83,25 +83,6 @@ namespace ts {
|
||||
return node.end - node.pos;
|
||||
}
|
||||
|
||||
export function arrayIsEqualTo<T>(array1: ReadonlyArray<T>, array2: ReadonlyArray<T>, equaler?: (a: T, b: T) => boolean): boolean {
|
||||
if (!array1 || !array2) {
|
||||
return array1 === array2;
|
||||
}
|
||||
|
||||
if (array1.length !== array2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < array1.length; i++) {
|
||||
const equals = equaler ? equaler(array1[i], array2[i]) : array1[i] === array2[i];
|
||||
if (!equals) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function hasResolvedModule(sourceFile: SourceFile, moduleNameText: string): boolean {
|
||||
return !!(sourceFile && sourceFile.resolvedModules && sourceFile.resolvedModules[moduleNameText]);
|
||||
}
|
||||
|
||||
@ -94,8 +94,8 @@ namespace ts.projectSystem {
|
||||
this.projectService.updateTypingsForProject(response);
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
|
||||
const request = server.createInstallTypingsRequest(project, typingOptions, this.globalTypingsCacheLocation);
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions, unresolvedImports: server.SortedReadonlyArray<string>) {
|
||||
const request = server.createInstallTypingsRequest(project, typingOptions, unresolvedImports, this.globalTypingsCacheLocation);
|
||||
this.install(request);
|
||||
}
|
||||
|
||||
|
||||
@ -217,9 +217,9 @@ namespace ts.projectSystem {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions, unresolvedImports: server.SortedReadonlyArray<string>) {
|
||||
enqueueIsCalled = true;
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions, unresolvedImports);
|
||||
}
|
||||
executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings = ["@types/jquery"];
|
||||
@ -319,9 +319,9 @@ namespace ts.projectSystem {
|
||||
constructor() {
|
||||
super(host);
|
||||
}
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions) {
|
||||
enqueueInstallTypingsRequest(project: server.Project, typingOptions: TypingOptions, unresolvedImports: server.SortedReadonlyArray<string>) {
|
||||
enqueueIsCalled = true;
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
super.enqueueInstallTypingsRequest(project, typingOptions, unresolvedImports);
|
||||
}
|
||||
executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction): void {
|
||||
const installedTypings: string[] = [];
|
||||
@ -724,7 +724,7 @@ namespace ts.projectSystem {
|
||||
it("Malformed package.json should be watched", () => {
|
||||
const f = {
|
||||
path: "/a/b/app.js",
|
||||
content: "var x = require('commander')"
|
||||
content: "var x = 1"
|
||||
};
|
||||
const brokenPackageJson = {
|
||||
path: "/a/b/package.json",
|
||||
@ -763,6 +763,133 @@ namespace ts.projectSystem {
|
||||
service.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
checkProjectActualFiles(service.inferredProjects[0], [f.path, commander.path]);
|
||||
});
|
||||
|
||||
it("should install typings for unresolved imports", () => {
|
||||
const file = {
|
||||
path: "/a/b/app.js",
|
||||
content: `
|
||||
import * as fs from "fs";
|
||||
import * as commander from "commander";`
|
||||
};
|
||||
const cachePath = "/a/cache";
|
||||
const node = {
|
||||
path: cachePath + "/node_modules/@types/node/index.d.ts",
|
||||
content: "export let x: number"
|
||||
};
|
||||
const commander = {
|
||||
path: cachePath + "/node_modules/@types/commander/index.d.ts",
|
||||
content: "export let y: string"
|
||||
};
|
||||
const host = createServerHost([file]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: cachePath });
|
||||
}
|
||||
executeRequest(requestKind: TI.RequestKind, _requestId: number, _args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
const installedTypings = ["@types/node", "@types/commander"];
|
||||
const typingFiles = [node, commander];
|
||||
executeCommand(this, host, installedTypings, typingFiles, requestKind, cb);
|
||||
}
|
||||
})();
|
||||
const service = createProjectService(host, { typingsInstaller: installer });
|
||||
service.openClientFile(file.path);
|
||||
|
||||
service.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
checkProjectActualFiles(service.inferredProjects[0], [file.path]);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest, TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
|
||||
assert.isTrue(host.fileExists(node.path), "typings for 'node' should be created");
|
||||
assert.isTrue(host.fileExists(commander.path), "typings for 'commander' should be created");
|
||||
|
||||
checkProjectActualFiles(service.inferredProjects[0], [file.path, node.path, commander.path]);
|
||||
});
|
||||
|
||||
it("should pick typing names from non-relative unresolved imports", () => {
|
||||
const f1 = {
|
||||
path: "/a/b/app.js",
|
||||
content: `
|
||||
import * as a from "foo/a/a";
|
||||
import * as b from "foo/a/b";
|
||||
import * as c from "foo/a/c";
|
||||
import * as d from "@bar/router/";
|
||||
import * as e from "@bar/common/shared";
|
||||
import * as e from "@bar/common/apps";
|
||||
import * as f from "./lib"
|
||||
`
|
||||
};
|
||||
|
||||
const host = createServerHost([f1]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation: "/tmp" });
|
||||
}
|
||||
executeRequest(requestKind: TI.RequestKind, _requestId: number, args: string[], _cwd: string, cb: server.typingsInstaller.RequestCompletedAction) {
|
||||
if (requestKind === TI.NpmViewRequest) {
|
||||
// args should have only non-scoped packages - scoped packages are not yet supported
|
||||
assert.deepEqual(args, ["foo"]);
|
||||
}
|
||||
executeCommand(this, host, ["foo"], [], requestKind, cb);
|
||||
}
|
||||
})();
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openClientFile(f1.path);
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
|
||||
const proj = projectService.inferredProjects[0];
|
||||
proj.updateGraph();
|
||||
|
||||
assert.deepEqual(
|
||||
proj.getCachedUnresolvedImportsPerFile_TestOnly().get(<Path>f1.path),
|
||||
["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"]
|
||||
);
|
||||
|
||||
installer.installAll([TI.NpmViewRequest], [TI.NpmInstallRequest]);
|
||||
});
|
||||
|
||||
it("cached unresolved typings are not recomputed if program structure did not change", () => {
|
||||
const host = createServerHost([]);
|
||||
const session = createSession(host);
|
||||
const f = {
|
||||
path: "/a/app.js",
|
||||
content: `
|
||||
import * as fs from "fs";
|
||||
import * as cmd from "commander
|
||||
`
|
||||
};
|
||||
session.executeCommand(<server.protocol.OpenRequest>{
|
||||
seq: 1,
|
||||
type: "request",
|
||||
command: "open",
|
||||
arguments: {
|
||||
file: f.path,
|
||||
fileContent: f.content
|
||||
}
|
||||
});
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const proj = projectService.inferredProjects[0];
|
||||
const version1 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
|
||||
|
||||
// make a change that should not affect the structure of the program
|
||||
session.executeCommand(<server.protocol.ChangeRequest>{
|
||||
seq: 2,
|
||||
type: "request",
|
||||
command: "change",
|
||||
arguments: {
|
||||
file: f.path,
|
||||
insertString: "\nlet x = 1;",
|
||||
line: 2,
|
||||
offset: 0,
|
||||
endLine: 2,
|
||||
endOffset: 0
|
||||
}
|
||||
});
|
||||
host.checkTimeoutQueueLength(1);
|
||||
host.runQueuedTimeoutCallbacks();
|
||||
const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
|
||||
assert.equal(version1, version2, "set of unresolved imports should not change");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Validate package name:", () => {
|
||||
@ -820,4 +947,35 @@ namespace ts.projectSystem {
|
||||
assert.isTrue(messages.indexOf("Package name '; say ‘Hello from TypeScript!’ #' contains non URI safe characters") > 0, "should find package with invalid name");
|
||||
});
|
||||
});
|
||||
|
||||
describe("discover typings", () => {
|
||||
it("should return node for core modules", () => {
|
||||
const f = {
|
||||
path: "/a/b/app.js",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([f]);
|
||||
const cache = createMap<string>();
|
||||
for (const name of JsTyping.nodeCoreModuleList) {
|
||||
const result = JsTyping.discoverTypings(host, [f.path], getDirectoryPath(<Path>f.path), /*safeListPath*/ undefined, cache, { enableAutoDiscovery: true }, [name, "somename"]);
|
||||
assert.deepEqual(result.newTypingNames.sort(), ["node", "somename"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("should use cached locaitons", () => {
|
||||
const f = {
|
||||
path: "/a/b/app.js",
|
||||
content: ""
|
||||
};
|
||||
const node = {
|
||||
path: "/a/b/node.d.ts",
|
||||
content: ""
|
||||
};
|
||||
const host = createServerHost([f, node]);
|
||||
const cache = createMap<string>({ "node": node.path });
|
||||
const result = JsTyping.discoverTypings(host, [f.path], getDirectoryPath(<Path>f.path), /*safeListPath*/ undefined, cache, { enableAutoDiscovery: true }, ["fs", "bar"]);
|
||||
assert.deepEqual(result.cachedTypingPaths, [node.path]);
|
||||
assert.deepEqual(result.newTypingNames, ["bar"]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -287,13 +287,13 @@ namespace ts.server {
|
||||
}
|
||||
switch (response.kind) {
|
||||
case "set":
|
||||
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.typings);
|
||||
project.updateGraph();
|
||||
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typingOptions, response.unresolvedImports, response.typings);
|
||||
break;
|
||||
case "invalidate":
|
||||
this.typingsCache.invalidateCachedTypingsForProject(project);
|
||||
this.typingsCache.deleteTypingsForProject(response.projectName);
|
||||
break;
|
||||
}
|
||||
project.updateGraph();
|
||||
}
|
||||
|
||||
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions): void {
|
||||
|
||||
@ -9,6 +9,8 @@ namespace ts.server {
|
||||
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
|
||||
private readonly getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
private filesWithChangedSetOfUnresolvedImports: Path[];
|
||||
|
||||
private readonly resolveModuleName: typeof resolveModuleName;
|
||||
readonly trace: (s: string) => void;
|
||||
|
||||
@ -52,12 +54,23 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
public startRecordingFilesWithChangedResolutions() {
|
||||
this.filesWithChangedSetOfUnresolvedImports = [];
|
||||
}
|
||||
|
||||
public finishRecordingFilesWithChangedResolutions() {
|
||||
const collected = this.filesWithChangedSetOfUnresolvedImports;
|
||||
this.filesWithChangedSetOfUnresolvedImports = undefined;
|
||||
return collected;
|
||||
}
|
||||
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R extends { resolvedFileName?: string }>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R): R[] {
|
||||
getResult: (s: T) => R,
|
||||
logChanges: boolean): R[] {
|
||||
|
||||
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
@ -79,6 +92,11 @@ namespace ts.server {
|
||||
else {
|
||||
newResolutions[name] = resolution = loader(name, containingFile, compilerOptions, this);
|
||||
}
|
||||
if (logChanges && this.filesWithChangedSetOfUnresolvedImports && !resolutionIsEqualTo(existingResolution, resolution)) {
|
||||
this.filesWithChangedSetOfUnresolvedImports.push(path);
|
||||
// reset log changes to avoid recording the same file multiple times
|
||||
logChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
@ -90,6 +108,24 @@ namespace ts.server {
|
||||
cache.set(path, newResolutions);
|
||||
return resolvedModules;
|
||||
|
||||
function resolutionIsEqualTo(oldResolution: T, newResolution: T): boolean {
|
||||
if (oldResolution === newResolution) {
|
||||
return true;
|
||||
}
|
||||
if (!oldResolution || !newResolution) {
|
||||
return false;
|
||||
}
|
||||
const oldResult = getResult(oldResolution);
|
||||
const newResult = getResult(newResolution);
|
||||
if (oldResult === newResult) {
|
||||
return true;
|
||||
}
|
||||
if (!oldResult || !newResult) {
|
||||
return false;
|
||||
}
|
||||
return oldResult.resolvedFileName === newResult.resolvedFileName;
|
||||
}
|
||||
|
||||
function moduleResolutionIsValid(resolution: T): boolean {
|
||||
if (!resolution) {
|
||||
return false;
|
||||
@ -126,11 +162,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective, /*logChanges*/ false);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule);
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, this.resolveModuleName, m => m.resolvedModule, /*logChanges*/ true);
|
||||
}
|
||||
|
||||
getDefaultLibFileName() {
|
||||
@ -197,10 +233,11 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
setCompilationSettings(opt: ts.CompilerOptions) {
|
||||
if (changesAffectModuleResolution(this.compilationSettings, opt)) {
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
this.compilationSettings = opt;
|
||||
// conservatively assume that changing compiler options might affect module resolution strategy
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,12 +62,43 @@ namespace ts.server {
|
||||
projectErrors: Diagnostic[];
|
||||
}
|
||||
|
||||
export class UnresolvedImportsMap {
|
||||
readonly perFileMap = createFileMap<ReadonlyArray<string>>();
|
||||
private version = 0;
|
||||
|
||||
public clear() {
|
||||
this.perFileMap.clear();
|
||||
this.version = 0;
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public remove(path: Path) {
|
||||
this.perFileMap.remove(path);
|
||||
this.version++;
|
||||
}
|
||||
|
||||
public get(path: Path) {
|
||||
return this.perFileMap.get(path);
|
||||
}
|
||||
|
||||
public set(path: Path, value: ReadonlyArray<string>) {
|
||||
this.perFileMap.set(path, value);
|
||||
this.version++;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
private lsHost: ServerLanguageServiceHost;
|
||||
private program: ts.Program;
|
||||
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
|
||||
private languageService: LanguageService;
|
||||
builder: Builder;
|
||||
/**
|
||||
@ -91,7 +122,7 @@ namespace ts.server {
|
||||
*/
|
||||
private projectStateVersion = 0;
|
||||
|
||||
private typingFiles: TypingsArray;
|
||||
private typingFiles: SortedReadonlyArray<string>;
|
||||
|
||||
protected projectErrors: Diagnostic[];
|
||||
|
||||
@ -107,6 +138,10 @@ namespace ts.server {
|
||||
return hasOneOrMoreJsAndNoTsFiles(this);
|
||||
}
|
||||
|
||||
public getCachedUnresolvedImportsPerFile_TestOnly() {
|
||||
return this.cachedUnresolvedImportsPerFile;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
@ -326,6 +361,7 @@ namespace ts.server {
|
||||
removeFile(info: ScriptInfo, detachFromProject = true) {
|
||||
this.removeRootFileIfNecessary(info);
|
||||
this.lsHost.notifyFileRemoved(info);
|
||||
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
||||
|
||||
if (detachFromProject) {
|
||||
info.detachFromProject(this);
|
||||
@ -338,6 +374,38 @@ namespace ts.server {
|
||||
this.projectStateVersion++;
|
||||
}
|
||||
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[]) {
|
||||
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
|
||||
if (cached) {
|
||||
// found cached result - use it and return
|
||||
for (const f of cached) {
|
||||
result.push(f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let unresolvedImports: string[];
|
||||
if (file.resolvedModules) {
|
||||
for (const name in file.resolvedModules) {
|
||||
// pick unresolved non-relative names
|
||||
if (!file.resolvedModules[name] && !isExternalModuleNameRelative(name)) {
|
||||
// for non-scoped names extract part up-to the first slash
|
||||
// for scoped names - extract up to the second slash
|
||||
let trimmed = name.trim();
|
||||
let i = trimmed.indexOf("/");
|
||||
if (i !== -1 && trimmed.charCodeAt(0) === CharacterCodes.at) {
|
||||
i = trimmed.indexOf("/", i + 1);
|
||||
}
|
||||
if (i !== -1) {
|
||||
trimmed = trimmed.substr(0, i);
|
||||
}
|
||||
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
|
||||
result.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates set of files that contribute to this project
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
@ -346,8 +414,35 @@ namespace ts.server {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.lsHost.startRecordingFilesWithChangedResolutions();
|
||||
|
||||
let hasChanges = this.updateGraphWorker();
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, hasChanges);
|
||||
|
||||
const changedFiles: ReadonlyArray<Path> = this.lsHost.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
||||
|
||||
for (const file of changedFiles) {
|
||||
// delete cached information for changed files
|
||||
this.cachedUnresolvedImportsPerFile.remove(file);
|
||||
}
|
||||
|
||||
// 1. no changes in structure, no changes in unresolved imports - do nothing
|
||||
// 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
|
||||
// (can reuse cached imports for files that were not changed)
|
||||
// 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
|
||||
// (can reuse cached imports for files that were not changed)
|
||||
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
|
||||
let unresolvedImports: SortedReadonlyArray<string>;
|
||||
if (hasChanges || changedFiles.length) {
|
||||
const result: string[] = [];
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result);
|
||||
}
|
||||
unresolvedImports = this.lastCachedUnresolvedImportsList;
|
||||
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, unresolvedImports, hasChanges);
|
||||
if (this.setTypings(cachedTypings)) {
|
||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||
}
|
||||
@ -357,7 +452,7 @@ namespace ts.server {
|
||||
return !hasChanges;
|
||||
}
|
||||
|
||||
private setTypings(typings: TypingsArray): boolean {
|
||||
private setTypings(typings: SortedReadonlyArray<string>): boolean {
|
||||
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
||||
return false;
|
||||
}
|
||||
@ -430,6 +525,11 @@ namespace ts.server {
|
||||
compilerOptions.allowJs = true;
|
||||
}
|
||||
compilerOptions.allowNonTsExtensions = true;
|
||||
if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) {
|
||||
// reset cached unresolved imports if changes in compiler options affected module resolution
|
||||
this.cachedUnresolvedImportsPerFile.clear();
|
||||
this.lastCachedUnresolvedImportsList = undefined;
|
||||
}
|
||||
this.compilerOptions = compilerOptions;
|
||||
this.lsHost.setCompilationSettings(compilerOptions);
|
||||
|
||||
|
||||
@ -181,12 +181,15 @@ namespace ts.server {
|
||||
private installer: NodeChildProcess;
|
||||
private socket: NodeSocket;
|
||||
private projectService: ProjectService;
|
||||
private throttledOperations: ThrottledOperations;
|
||||
|
||||
constructor(
|
||||
private readonly logger: server.Logger,
|
||||
host: ServerHost,
|
||||
eventPort: number,
|
||||
readonly globalTypingsCacheLocation: string,
|
||||
private newLine: string) {
|
||||
this.throttledOperations = new ThrottledOperations(host);
|
||||
if (eventPort) {
|
||||
const s = net.connect({ port: eventPort }, () => {
|
||||
this.socket = s;
|
||||
@ -231,12 +234,19 @@ namespace ts.server {
|
||||
this.installer.send({ projectName: p.getProjectName(), kind: "closeProject" });
|
||||
}
|
||||
|
||||
enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions): void {
|
||||
const request = createInstallTypingsRequest(project, typingOptions);
|
||||
enqueueInstallTypingsRequest(project: Project, typingOptions: TypingOptions, unresolvedImports: SortedReadonlyArray<string>): void {
|
||||
const request = createInstallTypingsRequest(project, typingOptions, unresolvedImports);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`);
|
||||
}
|
||||
}
|
||||
this.installer.send(request);
|
||||
this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => {
|
||||
if (this.logger.hasLevel(LogLevel.verbose)) {
|
||||
this.logger.info(`Sending request: ${JSON.stringify(request)}`);
|
||||
}
|
||||
this.installer.send(request);
|
||||
});
|
||||
}
|
||||
|
||||
private handleMessage(response: SetTypings | InvalidateCachedTypings) {
|
||||
@ -266,7 +276,7 @@ namespace ts.server {
|
||||
useSingleInferredProject,
|
||||
disableAutomaticTypingAcquisition
|
||||
? nullTypingsInstaller
|
||||
: new NodeTypingsInstaller(logger, installerEventPort, globalTypingsCacheLocation, host.newLine),
|
||||
: new NodeTypingsInstaller(logger, host, installerEventPort, globalTypingsCacheLocation, host.newLine),
|
||||
Buffer.byteLength,
|
||||
process.hrtime,
|
||||
logger,
|
||||
|
||||
16
src/server/types.d.ts
vendored
16
src/server/types.d.ts
vendored
@ -18,6 +18,10 @@ declare namespace ts.server {
|
||||
trace?(s: string): void;
|
||||
}
|
||||
|
||||
export interface SortedReadonlyArray<T> extends ReadonlyArray<T> {
|
||||
" __sortedReadonlyArrayBrand": any;
|
||||
}
|
||||
|
||||
export interface TypingInstallerRequest {
|
||||
readonly projectName: string;
|
||||
readonly kind: "discover" | "closeProject";
|
||||
@ -26,8 +30,9 @@ declare namespace ts.server {
|
||||
export interface DiscoverTypings extends TypingInstallerRequest {
|
||||
readonly fileNames: string[];
|
||||
readonly projectRootPath: ts.Path;
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly cachePath?: string;
|
||||
readonly kind: "discover";
|
||||
}
|
||||
@ -36,20 +41,23 @@ declare namespace ts.server {
|
||||
readonly kind: "closeProject";
|
||||
}
|
||||
|
||||
export type SetRequest = "set";
|
||||
export type InvalidateRequest = "invalidate";
|
||||
export interface TypingInstallerResponse {
|
||||
readonly projectName: string;
|
||||
readonly kind: "set" | "invalidate";
|
||||
readonly kind: SetRequest | InvalidateRequest;
|
||||
}
|
||||
|
||||
export interface SetTypings extends TypingInstallerResponse {
|
||||
readonly typingOptions: ts.TypingOptions;
|
||||
readonly compilerOptions: ts.CompilerOptions;
|
||||
readonly typings: string[];
|
||||
readonly kind: "set";
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
readonly kind: SetRequest;
|
||||
}
|
||||
|
||||
export interface InvalidateCachedTypings extends TypingInstallerResponse {
|
||||
readonly kind: "invalidate";
|
||||
readonly kind: InvalidateRequest;
|
||||
}
|
||||
|
||||
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
namespace ts.server {
|
||||
export interface ITypingsInstaller {
|
||||
enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions): void;
|
||||
enqueueInstallTypingsRequest(p: Project, typingOptions: TypingOptions, unresolvedImports: SortedReadonlyArray<string>): void;
|
||||
attach(projectService: ProjectService): void;
|
||||
onProjectClosed(p: Project): void;
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
@ -18,7 +18,9 @@ namespace ts.server {
|
||||
class TypingsCacheEntry {
|
||||
readonly typingOptions: TypingOptions;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typings: TypingsArray;
|
||||
readonly typings: SortedReadonlyArray<string>;
|
||||
readonly unresolvedImports: SortedReadonlyArray<string>;
|
||||
/* mainly useful for debugging */
|
||||
poisoned: boolean;
|
||||
}
|
||||
|
||||
@ -61,13 +63,11 @@ namespace ts.server {
|
||||
return opt1.allowJs != opt2.allowJs;
|
||||
}
|
||||
|
||||
export interface TypingsArray extends ReadonlyArray<string> {
|
||||
" __typingsArrayBrand": any;
|
||||
}
|
||||
|
||||
function toTypingsArray(arr: string[]): TypingsArray {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
function unresolvedImportsChanged(imports1: SortedReadonlyArray<string>, imports2: SortedReadonlyArray<string>): boolean {
|
||||
if (imports1 === imports2) {
|
||||
return false;
|
||||
}
|
||||
return !arrayIsEqualTo(imports1, imports2);
|
||||
}
|
||||
|
||||
export class TypingsCache {
|
||||
@ -76,7 +76,7 @@ namespace ts.server {
|
||||
constructor(private readonly installer: ITypingsInstaller) {
|
||||
}
|
||||
|
||||
getTypingsForProject(project: Project, forceRefresh: boolean): TypingsArray {
|
||||
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string> {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
|
||||
if (!typingOptions || !typingOptions.enableAutoDiscovery) {
|
||||
@ -84,39 +84,41 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
const entry = this.perProjectCache[project.getProjectName()];
|
||||
const result: TypingsArray = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh || !entry || typingOptionsChanged(typingOptions, entry.typingOptions) || compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions)) {
|
||||
const result: SortedReadonlyArray<string> = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh ||
|
||||
!entry ||
|
||||
typingOptionsChanged(typingOptions, entry.typingOptions) ||
|
||||
compilerOptionsChanged(project.getCompilerOptions(), entry.compilerOptions) ||
|
||||
unresolvedImportsChanged(unresolvedImports, entry.unresolvedImports)) {
|
||||
// Note: entry is now poisoned since it does not really contain typings for a given combination of compiler options\typings options.
|
||||
// instead it acts as a placeholder to prevent issuing multiple requests
|
||||
this.perProjectCache[project.getProjectName()] = {
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
typings: result,
|
||||
unresolvedImports,
|
||||
poisoned: true
|
||||
};
|
||||
// something has been changed, issue a request to update typings
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions, unresolvedImports);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
invalidateCachedTypingsForProject(project: Project) {
|
||||
const typingOptions = project.getTypingOptions();
|
||||
if (!typingOptions.enableAutoDiscovery) {
|
||||
return;
|
||||
}
|
||||
this.installer.enqueueInstallTypingsRequest(project, typingOptions);
|
||||
}
|
||||
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, newTypings: string[]) {
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typingOptions: TypingOptions, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]) {
|
||||
this.perProjectCache[projectName] = {
|
||||
compilerOptions,
|
||||
typingOptions,
|
||||
typings: toTypingsArray(newTypings),
|
||||
typings: toSortedReadonlyArray(newTypings),
|
||||
unresolvedImports,
|
||||
poisoned: false
|
||||
};
|
||||
}
|
||||
|
||||
deleteTypingsForProject(projectName: string) {
|
||||
delete this.perProjectCache[projectName];
|
||||
}
|
||||
|
||||
onProjectClosed(project: Project) {
|
||||
delete this.perProjectCache[project.getProjectName()];
|
||||
this.installer.onProjectClosed(project);
|
||||
|
||||
@ -26,6 +26,7 @@ namespace ts.server.typingsInstaller {
|
||||
export enum PackageNameValidationResult {
|
||||
Ok,
|
||||
ScopedPackagesNotSupported,
|
||||
EmptyName,
|
||||
NameTooLong,
|
||||
NameStartsWithDot,
|
||||
NameStartsWithUnderscore,
|
||||
@ -38,7 +39,9 @@ namespace ts.server.typingsInstaller {
|
||||
* Validates package name using rules defined at https://docs.npmjs.com/files/package.json
|
||||
*/
|
||||
export function validatePackageName(packageName: string): PackageNameValidationResult {
|
||||
Debug.assert(!!packageName, "Package name is not specified");
|
||||
if (!packageName) {
|
||||
return PackageNameValidationResult.EmptyName;
|
||||
}
|
||||
if (packageName.length > MaxPackageNameLength) {
|
||||
return PackageNameValidationResult.NameTooLong;
|
||||
}
|
||||
@ -145,7 +148,8 @@ namespace ts.server.typingsInstaller {
|
||||
req.projectRootPath,
|
||||
this.safeListPath,
|
||||
this.packageNameToTypingLocation,
|
||||
req.typingOptions);
|
||||
req.typingOptions,
|
||||
req.unresolvedImports);
|
||||
|
||||
if (this.log.isEnabled()) {
|
||||
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
|
||||
@ -238,6 +242,9 @@ namespace ts.server.typingsInstaller {
|
||||
this.missingTypingsSet[typing] = true;
|
||||
if (this.log.isEnabled()) {
|
||||
switch (validationResult) {
|
||||
case PackageNameValidationResult.EmptyName:
|
||||
this.log.writeLine(`Package name '${typing}' cannot be empty`);
|
||||
break;
|
||||
case PackageNameValidationResult.NameTooLong:
|
||||
this.log.writeLine(`Package name '${typing}' should be less than ${MaxPackageNameLength} characters`);
|
||||
break;
|
||||
@ -397,6 +404,7 @@ namespace ts.server.typingsInstaller {
|
||||
typingOptions: request.typingOptions,
|
||||
compilerOptions: request.compilerOptions,
|
||||
typings,
|
||||
unresolvedImports: request.unresolvedImports,
|
||||
kind: "set"
|
||||
};
|
||||
}
|
||||
|
||||
@ -45,12 +45,13 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, cachePath?: string): DiscoverTypings {
|
||||
export function createInstallTypingsRequest(project: Project, typingOptions: TypingOptions, unresolvedImports: SortedReadonlyArray<string>, cachePath?: string): DiscoverTypings {
|
||||
return {
|
||||
projectName: project.getProjectName(),
|
||||
fileNames: project.getFileNames(),
|
||||
compilerOptions: project.getCompilerOptions(),
|
||||
typingOptions,
|
||||
unresolvedImports,
|
||||
projectRootPath: getProjectRootPath(project),
|
||||
cachePath,
|
||||
kind: "discover"
|
||||
@ -209,11 +210,15 @@ namespace ts.server {
|
||||
export interface ServerLanguageServiceHost {
|
||||
setCompilationSettings(options: CompilerOptions): void;
|
||||
notifyFileRemoved(info: ScriptInfo): void;
|
||||
startRecordingFilesWithChangedResolutions(): void;
|
||||
finishRecordingFilesWithChangedResolutions(): Path[];
|
||||
}
|
||||
|
||||
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
|
||||
setCompilationSettings: () => undefined,
|
||||
notifyFileRemoved: () => undefined
|
||||
notifyFileRemoved: () => undefined,
|
||||
startRecordingFilesWithChangedResolutions: () => undefined,
|
||||
finishRecordingFilesWithChangedResolutions: () => undefined
|
||||
};
|
||||
|
||||
export interface ProjectOptions {
|
||||
@ -240,6 +245,11 @@ namespace ts.server {
|
||||
return `/dev/null/inferredProject${counter}*`;
|
||||
}
|
||||
|
||||
export function toSortedReadonlyArray(arr: string[]): SortedReadonlyArray<string> {
|
||||
arr.sort();
|
||||
return <any>arr;
|
||||
}
|
||||
|
||||
export class ThrottledOperations {
|
||||
private pendingTimeouts: Map<any> = createMap<any>();
|
||||
constructor(private readonly host: ServerHost) {
|
||||
|
||||
@ -31,6 +31,17 @@ namespace ts.JsTyping {
|
||||
|
||||
const EmptySafeList: Map<string> = createMap<string>();
|
||||
|
||||
/* @internal */
|
||||
export const nodeCoreModuleList: ReadonlyArray<string> = [
|
||||
"buffer", "querystring", "events", "http", "cluster",
|
||||
"zlib", "os", "https", "punycode", "repl", "readline",
|
||||
"vm", "child_process", "url", "dns", "net",
|
||||
"dgram", "fs", "path", "string_decoder", "tls",
|
||||
"crypto", "stream", "util", "assert", "tty", "domain",
|
||||
"constants", "process", "v8", "timers", "console"];
|
||||
|
||||
const nodeCoreModules = arrayToMap(<string[]>nodeCoreModuleList, x => x);
|
||||
|
||||
/**
|
||||
* @param host is the object providing I/O related operations.
|
||||
* @param fileNames are the file names that belong to the same project
|
||||
@ -46,7 +57,8 @@ namespace ts.JsTyping {
|
||||
projectRootPath: Path,
|
||||
safeListPath: Path,
|
||||
packageNameToTypingLocation: Map<string>,
|
||||
typingOptions: TypingOptions):
|
||||
typingOptions: TypingOptions,
|
||||
unresolvedImports: ReadonlyArray<string>):
|
||||
{ cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } {
|
||||
|
||||
// A typing name to typing file path mapping
|
||||
@ -92,6 +104,15 @@ namespace ts.JsTyping {
|
||||
}
|
||||
getTypingNamesFromSourceFileNames(fileNames);
|
||||
|
||||
// add typings for unresolved imports
|
||||
if (unresolvedImports) {
|
||||
for (const moduleId of unresolvedImports) {
|
||||
const typingName = moduleId in nodeCoreModules ? "node" : moduleId;
|
||||
if (!(typingName in inferredTypings)) {
|
||||
inferredTypings[typingName] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the cached typing locations for inferred typings that are already installed
|
||||
for (const name in packageNameToTypingLocation) {
|
||||
if (name in inferredTypings && !inferredTypings[name]) {
|
||||
|
||||
@ -1168,7 +1168,8 @@ namespace ts {
|
||||
toPath(info.projectRootPath, info.projectRootPath, getCanonicalFileName),
|
||||
toPath(info.safeListPath, info.safeListPath, getCanonicalFileName),
|
||||
info.packageNameToTypingLocation,
|
||||
info.typingOptions);
|
||||
info.typingOptions,
|
||||
info.unresolvedImports);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user