mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-13 14:09:06 -05:00
Merge pull request #23438 from Microsoft/typingsFiles
Better handling of typing installer events and consuming typing files in tsserver
This commit is contained in:
@@ -2987,18 +2987,19 @@ namespace ts {
|
||||
}
|
||||
|
||||
/** Remove the *first* occurrence of `item` from the array. */
|
||||
export function unorderedRemoveItem<T>(array: T[], item: T): void {
|
||||
unorderedRemoveFirstItemWhere(array, element => element === item);
|
||||
export function unorderedRemoveItem<T>(array: T[], item: T) {
|
||||
return unorderedRemoveFirstItemWhere(array, element => element === item);
|
||||
}
|
||||
|
||||
/** Remove the *first* element satisfying `predicate`. */
|
||||
function unorderedRemoveFirstItemWhere<T>(array: T[], predicate: (element: T) => boolean): void {
|
||||
function unorderedRemoveFirstItemWhere<T>(array: T[], predicate: (element: T) => boolean) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (predicate(array[i])) {
|
||||
unorderedRemoveItemAt(array, i);
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export type GetCanonicalFileName = (fileName: string) => string;
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace ts {
|
||||
|
||||
invalidateResolutionOfFile(filePath: Path): void;
|
||||
removeResolutionsOfFile(filePath: Path): void;
|
||||
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map<ReadonlyArray<string>>): void;
|
||||
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
|
||||
|
||||
startCachingPerDirectoryResolution(): void;
|
||||
@@ -74,6 +75,7 @@ namespace ts {
|
||||
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache {
|
||||
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
|
||||
let filesWithInvalidatedResolutions: Map<true> | undefined;
|
||||
let filesWithInvalidatedNonRelativeUnresolvedImports: Map<ReadonlyArray<string>> | undefined;
|
||||
let allFilesHaveInvalidatedResolution = false;
|
||||
|
||||
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
|
||||
@@ -122,6 +124,7 @@ namespace ts {
|
||||
resolveTypeReferenceDirectives,
|
||||
removeResolutionsOfFile,
|
||||
invalidateResolutionOfFile,
|
||||
setFilesWithInvalidatedNonRelativeUnresolvedImports,
|
||||
createHasInvalidatedResolution,
|
||||
updateTypeRootsWatch,
|
||||
closeTypeRootsWatch,
|
||||
@@ -165,6 +168,16 @@ namespace ts {
|
||||
return collected;
|
||||
}
|
||||
|
||||
function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path) {
|
||||
if (!filesWithInvalidatedNonRelativeUnresolvedImports) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invalidated if file has unresolved imports
|
||||
const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path);
|
||||
return value && !!value.length;
|
||||
}
|
||||
|
||||
function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
|
||||
if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) {
|
||||
// Any file asked would have invalidated resolution
|
||||
@@ -173,7 +186,8 @@ namespace ts {
|
||||
}
|
||||
const collected = filesWithInvalidatedResolutions;
|
||||
filesWithInvalidatedResolutions = undefined;
|
||||
return path => collected && collected.has(path);
|
||||
return path => (collected && collected.has(path)) ||
|
||||
isFileWithInvalidatedNonRelativeUnresolvedImports(path);
|
||||
}
|
||||
|
||||
function clearPerDirectoryResolutions() {
|
||||
@@ -184,6 +198,7 @@ namespace ts {
|
||||
|
||||
function finishCachingPerDirectoryResolution() {
|
||||
allFilesHaveInvalidatedResolution = false;
|
||||
filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
|
||||
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
|
||||
if (watcher.refCount === 0) {
|
||||
directoryWatchesOfFailedLookups.delete(path);
|
||||
@@ -237,13 +252,15 @@ namespace ts {
|
||||
|
||||
const resolvedModules: R[] = [];
|
||||
const compilerOptions = resolutionHost.getCompilationSettings();
|
||||
|
||||
const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
|
||||
const seenNamesInFile = createMap<true>();
|
||||
for (const name of names) {
|
||||
let resolution = resolutionsInFile.get(name);
|
||||
// Resolution is valid if it is present and not invalidated
|
||||
if (!seenNamesInFile.has(name) &&
|
||||
allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated) {
|
||||
allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated ||
|
||||
// If the name is unresolved import that was invalidated, recalculate
|
||||
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) {
|
||||
const existingResolution = resolution;
|
||||
const resolutionInDirectory = perDirectoryResolution.get(name);
|
||||
if (resolutionInDirectory) {
|
||||
@@ -284,7 +301,7 @@ namespace ts {
|
||||
if (oldResolution === newResolution) {
|
||||
return true;
|
||||
}
|
||||
if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
|
||||
if (!oldResolution || !newResolution) {
|
||||
return false;
|
||||
}
|
||||
const oldResult = getResolutionWithResolvedFileName(oldResolution);
|
||||
@@ -577,6 +594,11 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map<ReadonlyArray<string>>) {
|
||||
Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
|
||||
filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
|
||||
}
|
||||
|
||||
function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
|
||||
let isChangedFailedLookupLocation: (location: string) => boolean;
|
||||
if (isCreatingWatchedDirectory) {
|
||||
|
||||
@@ -7294,7 +7294,6 @@ namespace ts.projectSystem {
|
||||
const host = createServerHost(files);
|
||||
const session = createSession(host);
|
||||
const projectService = session.getProjectService();
|
||||
debugger;
|
||||
session.executeCommandSeq<protocol.OpenRequest>({
|
||||
command: protocol.CommandTypes.Open,
|
||||
arguments: {
|
||||
|
||||
@@ -999,14 +999,14 @@ namespace ts.projectSystem {
|
||||
proj.updateGraph();
|
||||
|
||||
assert.deepEqual(
|
||||
proj.getCachedUnresolvedImportsPerFile_TestOnly().get(<Path>f1.path),
|
||||
proj.cachedUnresolvedImportsPerFile.get(<Path>f1.path),
|
||||
["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"]
|
||||
);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
});
|
||||
|
||||
it("should recompute resolutions after typings are installed", () => {
|
||||
it("cached unresolved typings are not recomputed if program structure did not change", () => {
|
||||
const host = createServerHost([]);
|
||||
const session = createSession(host);
|
||||
const f = {
|
||||
@@ -1029,7 +1029,7 @@ namespace ts.projectSystem {
|
||||
const projectService = session.getProjectService();
|
||||
checkNumberOfProjects(projectService, { inferredProjects: 1 });
|
||||
const proj = projectService.inferredProjects[0];
|
||||
const version1 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
|
||||
const version1 = proj.lastCachedUnresolvedImportsList;
|
||||
|
||||
// make a change that should not affect the structure of the program
|
||||
const changeRequest: server.protocol.ChangeRequest = {
|
||||
@@ -1047,8 +1047,8 @@ namespace ts.projectSystem {
|
||||
};
|
||||
session.executeCommand(changeRequest);
|
||||
host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects
|
||||
const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
|
||||
assert.notEqual(version1, version2, "set of unresolved imports should change");
|
||||
const version2 = proj.lastCachedUnresolvedImportsList;
|
||||
assert.strictEqual(version1, version2, "set of unresolved imports should change");
|
||||
});
|
||||
|
||||
it("expired cache entry (inferred project, should install typings)", () => {
|
||||
@@ -1621,4 +1621,75 @@ namespace ts.projectSystem {
|
||||
assert.deepEqual(commands, expectedCommands, "commands");
|
||||
});
|
||||
});
|
||||
|
||||
describe("recomputing resolutions of unresolved imports", () => {
|
||||
const globalTypingsCacheLocation = "/tmp";
|
||||
const appPath = "/a/b/app.js" as Path;
|
||||
const foooPath = "/a/b/node_modules/fooo/index.d.ts";
|
||||
function verifyResolvedModuleOfFooo(project: server.Project) {
|
||||
const foooResolution = project.getLanguageService().getProgram().getSourceFileByPath(appPath).resolvedModules.get("fooo");
|
||||
assert.equal(foooResolution.resolvedFileName, foooPath);
|
||||
return foooResolution;
|
||||
}
|
||||
|
||||
function verifyUnresolvedImportResolutions(appContents: string, typingNames: string[], typingFiles: FileOrFolder[]) {
|
||||
const app: FileOrFolder = {
|
||||
path: appPath,
|
||||
content: `${appContents}import * as x from "fooo";`
|
||||
};
|
||||
const fooo: FileOrFolder = {
|
||||
path: foooPath,
|
||||
content: `export var x: string;`
|
||||
};
|
||||
const host = createServerHost([app, fooo]);
|
||||
const installer = new (class extends Installer {
|
||||
constructor() {
|
||||
super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("foo") });
|
||||
}
|
||||
installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
|
||||
executeCommand(this, host, typingNames, typingFiles, cb);
|
||||
}
|
||||
})();
|
||||
const projectService = createProjectService(host, { typingsInstaller: installer });
|
||||
projectService.openClientFile(app.path);
|
||||
projectService.checkNumberOfProjects({ inferredProjects: 1 });
|
||||
|
||||
const proj = projectService.inferredProjects[0];
|
||||
checkProjectActualFiles(proj, [app.path, fooo.path]);
|
||||
const foooResolution1 = verifyResolvedModuleOfFooo(proj);
|
||||
|
||||
installer.installAll(/*expectedCount*/ 1);
|
||||
host.checkTimeoutQueueLengthAndRun(2);
|
||||
checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path));
|
||||
const foooResolution2 = verifyResolvedModuleOfFooo(proj);
|
||||
assert.strictEqual(foooResolution1, foooResolution2);
|
||||
}
|
||||
|
||||
it("correctly invalidate the resolutions with typing names", () => {
|
||||
verifyUnresolvedImportResolutions('import * as a from "foo";', ["foo"], [{
|
||||
path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
|
||||
content: "export function a(): void;"
|
||||
}]);
|
||||
});
|
||||
|
||||
it("correctly invalidate the resolutions with typing names that are trimmed", () => {
|
||||
const fooAA: FileOrFolder = {
|
||||
path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`,
|
||||
content: "export function a (): void;"
|
||||
};
|
||||
const fooAB: FileOrFolder = {
|
||||
path: `${globalTypingsCacheLocation}/node_modules/foo/a/b.d.ts`,
|
||||
content: "export function b (): void;"
|
||||
};
|
||||
const fooAC: FileOrFolder = {
|
||||
path: `${globalTypingsCacheLocation}/node_modules/foo/a/c.d.ts`,
|
||||
content: "export function c (): void;"
|
||||
};
|
||||
verifyUnresolvedImportResolutions(`
|
||||
import * as a from "foo/a/a";
|
||||
import * as b from "foo/a/b";
|
||||
import * as c from "foo/a/c";
|
||||
`, ["foo"], [fooAA, fooAB, fooAC]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -312,7 +312,8 @@ namespace ts.server {
|
||||
|
||||
export class ProjectService {
|
||||
|
||||
public readonly typingsCache: TypingsCache;
|
||||
/*@internal*/
|
||||
readonly typingsCache: TypingsCache;
|
||||
|
||||
private readonly documentRegistry: DocumentRegistry;
|
||||
|
||||
@@ -523,13 +524,13 @@ namespace ts.server {
|
||||
}
|
||||
switch (response.kind) {
|
||||
case ActionSet:
|
||||
project.resolutionCache.clear();
|
||||
this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings);
|
||||
// Update the typing files and update the project
|
||||
project.updateTypingFiles(this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings));
|
||||
break;
|
||||
case ActionInvalidate:
|
||||
project.resolutionCache.clear();
|
||||
this.typingsCache.deleteTypingsForProject(response.projectName);
|
||||
break;
|
||||
// Do not clear resolution cache, there was changes detected in typings, so enque typing request and let it get us correct results
|
||||
this.typingsCache.enqueueInstallTypingsForProject(project, project.lastCachedUnresolvedImportsList, /*forceRefresh*/ true);
|
||||
return;
|
||||
}
|
||||
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
|
||||
}
|
||||
|
||||
@@ -55,34 +55,6 @@ namespace ts.server {
|
||||
projectErrors: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
|
||||
export class UnresolvedImportsMap {
|
||||
readonly perFileMap = createMap<ReadonlyArray<string>>();
|
||||
private version = 0;
|
||||
|
||||
public clear() {
|
||||
this.perFileMap.clear();
|
||||
this.version = 0;
|
||||
}
|
||||
|
||||
public getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public remove(path: Path) {
|
||||
this.perFileMap.delete(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 interface PluginCreateInfo {
|
||||
project: Project;
|
||||
languageService: LanguageService;
|
||||
@@ -116,8 +88,18 @@ namespace ts.server {
|
||||
private missingFilesMap: Map<FileWatcher>;
|
||||
private plugins: PluginModule[] = [];
|
||||
|
||||
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
||||
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
/*@internal*/
|
||||
/**
|
||||
* This is map from files to unresolved imports in it
|
||||
* Maop does not contain entries for files that do not have unresolved imports
|
||||
* This helps in containing the set of files to invalidate
|
||||
*/
|
||||
cachedUnresolvedImportsPerFile = createMap<ReadonlyArray<string>>();
|
||||
|
||||
/*@internal*/
|
||||
lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
||||
/*@internal*/
|
||||
private hasAddedorRemovedFiles = false;
|
||||
|
||||
private lastFileExceededProgramSize: string | undefined;
|
||||
|
||||
@@ -149,10 +131,10 @@ namespace ts.server {
|
||||
*/
|
||||
private lastReportedVersion = 0;
|
||||
/**
|
||||
* Current project structure version.
|
||||
* Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one)
|
||||
* This property is changed in 'updateGraph' based on the set of files in program
|
||||
*/
|
||||
private projectStructureVersion = 0;
|
||||
private projectProgramVersion = 0;
|
||||
/**
|
||||
* Current version of the project state. It is changed when:
|
||||
* - new root file was added/removed
|
||||
@@ -167,7 +149,8 @@ namespace ts.server {
|
||||
/*@internal*/
|
||||
hasChangedAutomaticTypeDirectiveNames = false;
|
||||
|
||||
private typingFiles: SortedReadonlyArray<string>;
|
||||
/*@internal*/
|
||||
typingFiles: SortedReadonlyArray<string> = emptyArray;
|
||||
|
||||
private readonly cancellationToken: ThrottledCancellationToken;
|
||||
|
||||
@@ -181,10 +164,6 @@ namespace ts.server {
|
||||
return hasOneOrMoreJsAndNoTsFiles(this);
|
||||
}
|
||||
|
||||
public getCachedUnresolvedImportsPerFile_TestOnly() {
|
||||
return this.cachedUnresolvedImportsPerFile;
|
||||
}
|
||||
|
||||
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} {
|
||||
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
|
||||
log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
|
||||
@@ -742,7 +721,7 @@ namespace ts.server {
|
||||
else {
|
||||
this.resolutionCache.invalidateResolutionOfFile(info.path);
|
||||
}
|
||||
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
||||
this.cachedUnresolvedImportsPerFile.delete(info.path);
|
||||
|
||||
if (detachFromProject) {
|
||||
info.detachFromProject(this);
|
||||
@@ -763,16 +742,13 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push<string>, ambientModules: string[]) {
|
||||
private extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: string[]): ReadonlyArray<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;
|
||||
// found cached result, return
|
||||
return cached;
|
||||
}
|
||||
let unresolvedImports: string[];
|
||||
let unresolvedImports: string[] | undefined;
|
||||
if (file.resolvedModules) {
|
||||
file.resolvedModules.forEach((resolvedModule, name) => {
|
||||
// pick unresolved non-relative names
|
||||
@@ -788,17 +764,23 @@ namespace ts.server {
|
||||
trimmed = trimmed.substr(0, i);
|
||||
}
|
||||
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
|
||||
result.push(trimmed);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
||||
return unresolvedImports || emptyArray;
|
||||
|
||||
function isAmbientlyDeclaredModule(name: string) {
|
||||
return ambientModules.some(m => m === name);
|
||||
}
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
onFileAddedOrRemoved() {
|
||||
this.hasAddedorRemovedFiles = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates set of files that contribute to this project
|
||||
* @returns: true if set of files in the project stays the same and false - otherwise.
|
||||
@@ -806,13 +788,15 @@ namespace ts.server {
|
||||
updateGraph(): boolean {
|
||||
this.resolutionCache.startRecordingFilesWithChangedResolutions();
|
||||
|
||||
let hasChanges = this.updateGraphWorker();
|
||||
const hasNewProgram = this.updateGraphWorker();
|
||||
const hasAddedorRemovedFiles = this.hasAddedorRemovedFiles;
|
||||
this.hasAddedorRemovedFiles = false;
|
||||
|
||||
const changedFiles: ReadonlyArray<Path> = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
||||
|
||||
for (const file of changedFiles) {
|
||||
// delete cached information for changed files
|
||||
this.cachedUnresolvedImportsPerFile.remove(file);
|
||||
this.cachedUnresolvedImportsPerFile.delete(file);
|
||||
}
|
||||
|
||||
// update builder only if language service is enabled
|
||||
@@ -824,30 +808,35 @@ namespace ts.server {
|
||||
// 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
|
||||
if (hasChanges || changedFiles.length) {
|
||||
const result: string[] = [];
|
||||
if (hasNewProgram || changedFiles.length) {
|
||||
let result: string[] | undefined;
|
||||
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
|
||||
for (const sourceFile of this.program.getSourceFiles()) {
|
||||
this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
|
||||
const unResolved = this.extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules);
|
||||
if (unResolved !== emptyArray) {
|
||||
(result || (result = [])).push(...unResolved);
|
||||
}
|
||||
}
|
||||
this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result);
|
||||
this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray;
|
||||
}
|
||||
|
||||
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasChanges);
|
||||
if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) {
|
||||
this.typingFiles = cachedTypings;
|
||||
this.markAsDirty();
|
||||
hasChanges = this.updateGraphWorker() || hasChanges;
|
||||
}
|
||||
this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles);
|
||||
}
|
||||
else {
|
||||
this.lastCachedUnresolvedImportsList = undefined;
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
this.projectStructureVersion++;
|
||||
if (hasNewProgram) {
|
||||
this.projectProgramVersion++;
|
||||
}
|
||||
return !hasChanges;
|
||||
return !hasNewProgram;
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
updateTypingFiles(typingFiles: SortedReadonlyArray<string>) {
|
||||
this.typingFiles = typingFiles;
|
||||
// Invalidate files with unresolved imports
|
||||
this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -876,9 +865,9 @@ namespace ts.server {
|
||||
// bump up the version if
|
||||
// - oldProgram is not set - this is a first time updateGraph is called
|
||||
// - newProgram is different from the old program and structure of the old program was not reused.
|
||||
const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
|
||||
const hasNewProgram = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
|
||||
this.hasChangedAutomaticTypeDirectiveNames = false;
|
||||
if (hasChanges) {
|
||||
if (hasNewProgram) {
|
||||
if (oldProgram) {
|
||||
for (const f of oldProgram.getSourceFiles()) {
|
||||
if (this.program.getSourceFileByPath(f.path)) {
|
||||
@@ -916,8 +905,8 @@ namespace ts.server {
|
||||
removed => this.detachScriptInfoFromProject(removed)
|
||||
);
|
||||
const elapsed = timestamp() - start;
|
||||
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);
|
||||
return hasChanges;
|
||||
this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasNewProgram} Elapsed: ${elapsed}ms`);
|
||||
return hasNewProgram;
|
||||
}
|
||||
|
||||
private detachScriptInfoFromProject(uncheckedFileName: string) {
|
||||
@@ -985,15 +974,13 @@ namespace ts.server {
|
||||
setCompilerOptions(compilerOptions: CompilerOptions) {
|
||||
if (compilerOptions) {
|
||||
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;
|
||||
}
|
||||
const oldOptions = this.compilerOptions;
|
||||
this.compilerOptions = compilerOptions;
|
||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||
if (changesAffectModuleResolution(oldOptions, compilerOptions)) {
|
||||
// reset cached unresolved imports if changes in compiler options affected module resolution
|
||||
this.cachedUnresolvedImportsPerFile.clear();
|
||||
this.lastCachedUnresolvedImportsList = undefined;
|
||||
this.resolutionCache.clear();
|
||||
}
|
||||
this.markAsDirty();
|
||||
@@ -1006,7 +993,7 @@ namespace ts.server {
|
||||
|
||||
const info: protocol.ProjectVersionInfo = {
|
||||
projectName: this.getProjectName(),
|
||||
version: this.projectStructureVersion,
|
||||
version: this.projectProgramVersion,
|
||||
isInferred: this.projectKind === ProjectKind.Inferred,
|
||||
options: this.getCompilationSettings(),
|
||||
languageServiceDisabled: !this.languageServiceEnabled,
|
||||
@@ -1017,7 +1004,7 @@ namespace ts.server {
|
||||
// check if requested version is the same that we have reported last time
|
||||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
||||
// if current structure version is the same - return info without any changes
|
||||
if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
|
||||
if (this.projectProgramVersion === this.lastReportedVersion && !updatedFileNames) {
|
||||
return { info, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
// compute and return the difference
|
||||
@@ -1040,7 +1027,7 @@ namespace ts.server {
|
||||
}
|
||||
});
|
||||
this.lastReportedFileNames = currentFiles;
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
this.lastReportedVersion = this.projectProgramVersion;
|
||||
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
else {
|
||||
@@ -1049,7 +1036,7 @@ namespace ts.server {
|
||||
const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f));
|
||||
const allFiles = projectFileNames.concat(externalFiles);
|
||||
this.lastReportedFileNames = arrayToSet(allFiles);
|
||||
this.lastReportedVersion = this.projectStructureVersion;
|
||||
this.lastReportedVersion = this.projectProgramVersion;
|
||||
return { info, files: allFiles, projectErrors: this.getGlobalProjectErrors() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +304,7 @@ namespace ts.server {
|
||||
const isNew = !this.isAttached(project);
|
||||
if (isNew) {
|
||||
this.containingProjects.push(project);
|
||||
project.onFileAddedOrRemoved();
|
||||
if (!project.getCompilerOptions().preserveSymlinks) {
|
||||
this.ensureRealPath();
|
||||
}
|
||||
@@ -328,19 +329,24 @@ namespace ts.server {
|
||||
return;
|
||||
case 1:
|
||||
if (this.containingProjects[0] === project) {
|
||||
project.onFileAddedOrRemoved();
|
||||
this.containingProjects.pop();
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this.containingProjects[0] === project) {
|
||||
project.onFileAddedOrRemoved();
|
||||
this.containingProjects[0] = this.containingProjects.pop();
|
||||
}
|
||||
else if (this.containingProjects[1] === project) {
|
||||
project.onFileAddedOrRemoved();
|
||||
this.containingProjects.pop();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unorderedRemoveItem(this.containingProjects, project);
|
||||
if (unorderedRemoveItem(this.containingProjects, project)) {
|
||||
project.onFileAddedOrRemoved();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace ts.server {
|
||||
globalTypingsCacheLocation: undefined
|
||||
};
|
||||
|
||||
class TypingsCacheEntry {
|
||||
interface TypingsCacheEntry {
|
||||
readonly typeAcquisition: TypeAcquisition;
|
||||
readonly compilerOptions: CompilerOptions;
|
||||
readonly typings: SortedReadonlyArray<string>;
|
||||
@@ -80,6 +80,7 @@ namespace ts.server {
|
||||
return !arrayIsEqualTo(imports1, imports2);
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
export class TypingsCache {
|
||||
private readonly perProjectCache: Map<TypingsCacheEntry> = createMap<TypingsCacheEntry>();
|
||||
|
||||
@@ -94,15 +95,14 @@ namespace ts.server {
|
||||
return this.installer.installPackage(options);
|
||||
}
|
||||
|
||||
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string> {
|
||||
enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean) {
|
||||
const typeAcquisition = project.getTypeAcquisition();
|
||||
|
||||
if (!typeAcquisition || !typeAcquisition.enable) {
|
||||
return <any>emptyArray;
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = this.perProjectCache.get(project.getProjectName());
|
||||
const result: SortedReadonlyArray<string> = entry ? entry.typings : <any>emptyArray;
|
||||
if (forceRefresh ||
|
||||
!entry ||
|
||||
typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) ||
|
||||
@@ -113,28 +113,25 @@ namespace ts.server {
|
||||
this.perProjectCache.set(project.getProjectName(), {
|
||||
compilerOptions: project.getCompilationSettings(),
|
||||
typeAcquisition,
|
||||
typings: result,
|
||||
typings: entry ? entry.typings : emptyArray,
|
||||
unresolvedImports,
|
||||
poisoned: true
|
||||
});
|
||||
// something has been changed, issue a request to update typings
|
||||
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]) {
|
||||
const typings = toSortedArray(newTypings);
|
||||
this.perProjectCache.set(projectName, {
|
||||
compilerOptions,
|
||||
typeAcquisition,
|
||||
typings: toSortedArray(newTypings),
|
||||
typings,
|
||||
unresolvedImports,
|
||||
poisoned: false
|
||||
});
|
||||
}
|
||||
|
||||
deleteTypingsForProject(projectName: string) {
|
||||
this.perProjectCache.delete(projectName);
|
||||
return !typeAcquisition || !typeAcquisition.enable ? emptyArray : typings;
|
||||
}
|
||||
|
||||
onProjectClosed(project: Project) {
|
||||
|
||||
@@ -64,11 +64,13 @@ namespace ts.server.typingsInstaller {
|
||||
onRequestCompleted: RequestCompletedAction;
|
||||
}
|
||||
|
||||
type ProjectWatchers = Map<FileWatcher> & { isInvoked?: boolean; };
|
||||
|
||||
export abstract class TypingsInstaller {
|
||||
private readonly packageNameToTypingLocation: Map<JsTyping.CachedTyping> = createMap<JsTyping.CachedTyping>();
|
||||
private readonly missingTypingsSet: Map<true> = createMap<true>();
|
||||
private readonly knownCachesSet: Map<true> = createMap<true>();
|
||||
private readonly projectWatchers = createMap<Map<FileWatcher>>();
|
||||
private readonly projectWatchers = createMap<ProjectWatchers>();
|
||||
private safeList: JsTyping.SafeList | undefined;
|
||||
readonly pendingRunRequests: PendingRequest[] = [];
|
||||
|
||||
@@ -378,8 +380,8 @@ namespace ts.server.typingsInstaller {
|
||||
this.projectWatchers.set(projectName, watchers);
|
||||
}
|
||||
|
||||
watchers.isInvoked = false;
|
||||
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
|
||||
let isInvoked = false;
|
||||
const isLoggingEnabled = this.log.isEnabled();
|
||||
mutateMap(
|
||||
watchers,
|
||||
@@ -392,11 +394,11 @@ namespace ts.server.typingsInstaller {
|
||||
}
|
||||
const watcher = this.installTypingHost.watchFile(file, (f, eventKind) => {
|
||||
if (isLoggingEnabled) {
|
||||
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${isInvoked}'`);
|
||||
this.log.writeLine(`FileWatcher:: Triggered with ${f} eventKind: ${FileWatcherEventKind[eventKind]}:: WatchInfo: ${file}:: handler is already invoked '${watchers.isInvoked}'`);
|
||||
}
|
||||
if (!isInvoked) {
|
||||
if (!watchers.isInvoked) {
|
||||
watchers.isInvoked = true;
|
||||
this.sendResponse({ projectName, kind: ActionInvalidate });
|
||||
isInvoked = true;
|
||||
}
|
||||
}, /*pollingInterval*/ 2000);
|
||||
return isLoggingEnabled ? {
|
||||
|
||||
@@ -7608,17 +7608,6 @@ declare namespace ts.server {
|
||||
readonly globalTypingsCacheLocation: string;
|
||||
}
|
||||
const nullTypingsInstaller: ITypingsInstaller;
|
||||
class TypingsCache {
|
||||
private readonly installer;
|
||||
private readonly perProjectCache;
|
||||
constructor(installer: ITypingsInstaller);
|
||||
isKnownTypesPackageName(name: string): boolean;
|
||||
installPackage(options: InstallPackageOptionsWithProject): Promise<ApplyCodeActionCommandResult>;
|
||||
getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray<string>, forceRefresh: boolean): SortedReadonlyArray<string>;
|
||||
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray<string>, newTypings: string[]): void;
|
||||
deleteTypingsForProject(projectName: string): void;
|
||||
onProjectClosed(project: Project): void;
|
||||
}
|
||||
}
|
||||
declare namespace ts.server {
|
||||
enum ProjectKind {
|
||||
@@ -7628,15 +7617,6 @@ declare namespace ts.server {
|
||||
}
|
||||
function allRootFilesAreJsOrDts(project: Project): boolean;
|
||||
function allFilesAreJsOrDts(project: Project): boolean;
|
||||
class UnresolvedImportsMap {
|
||||
readonly perFileMap: Map<ReadonlyArray<string>>;
|
||||
private version;
|
||||
clear(): void;
|
||||
getVersion(): number;
|
||||
remove(path: Path): void;
|
||||
get(path: Path): ReadonlyArray<string>;
|
||||
set(path: Path, value: ReadonlyArray<string>): void;
|
||||
}
|
||||
interface PluginCreateInfo {
|
||||
project: Project;
|
||||
languageService: LanguageService;
|
||||
@@ -7669,8 +7649,6 @@ declare namespace ts.server {
|
||||
private externalFiles;
|
||||
private missingFilesMap;
|
||||
private plugins;
|
||||
private cachedUnresolvedImportsPerFile;
|
||||
private lastCachedUnresolvedImportsList;
|
||||
private lastFileExceededProgramSize;
|
||||
protected languageService: LanguageService;
|
||||
languageServiceEnabled: boolean;
|
||||
@@ -7690,10 +7668,10 @@ declare namespace ts.server {
|
||||
*/
|
||||
private lastReportedVersion;
|
||||
/**
|
||||
* Current project structure version.
|
||||
* Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one)
|
||||
* This property is changed in 'updateGraph' based on the set of files in program
|
||||
*/
|
||||
private projectStructureVersion;
|
||||
private projectProgramVersion;
|
||||
/**
|
||||
* Current version of the project state. It is changed when:
|
||||
* - new root file was added/removed
|
||||
@@ -7701,11 +7679,9 @@ declare namespace ts.server {
|
||||
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
|
||||
*/
|
||||
private projectStateVersion;
|
||||
private typingFiles;
|
||||
private readonly cancellationToken;
|
||||
isNonTsProject(): boolean;
|
||||
isJsOnlyProject(): boolean;
|
||||
getCachedUnresolvedImportsPerFile_TestOnly(): UnresolvedImportsMap;
|
||||
static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {};
|
||||
isKnownTypesPackageName(name: string): boolean;
|
||||
installPackage(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
|
||||
@@ -7966,7 +7942,6 @@ declare namespace ts.server {
|
||||
syntaxOnly?: boolean;
|
||||
}
|
||||
class ProjectService {
|
||||
readonly typingsCache: TypingsCache;
|
||||
private readonly documentRegistry;
|
||||
/**
|
||||
* Container of all known scripts
|
||||
|
||||
Reference in New Issue
Block a user