Updated: Only auto-import from package.json (#32517)

* Move package.json related utils to utilities

* Add failing test

* Make first test pass

* Don’t filter when there’s no package.json, fix scoped package imports

* Use type acquisition as a heuristic for whether a JS project is using node core

* Make same fix in getCompletionDetails

* Fix re-exporting

* Change JS node core module heuristic to same-file utilization

* Remove unused method

* Remove other unused method

* Remove unused triple-slash ref

* Update comment

* Refactor findAlias to forEachAlias to reduce iterations

* Really fix re-exporting

* Use getModuleSpecifier instead of custom hack

* Fix offering auto imports to paths within node modules

* Rename things and make comments better

* Add another reexport test

* Inline `symbolHasBeenSeen`

* Simplify forEachAlias to findAlias

* Add note that symbols is mutated

* Symbol order doesn’t matter here

* Style nits

* Add test with nested package.jsons

* Fix and add tests for export * re-exports

* Don’t fail when alias isn’t found

* Make some easy optimizations

* Clean up memoization when done

* Remove unnecessary semicolon

* Make getSymbolsFromOtherSourceFileExports pure

* Cache auto imports

* Revert "Cache auto imports"

This reverts commit 8ea4829587.

* Handle merged symbols through cache

* Be safer with symbol declarations, add logging

* Improve cache invalidation for imports and exports

* Check symbol presence first

* Only run cache invalidation logic if there’s something to clear

* Consolidate cache invalidation logic

* Fix reuseProgramStructure test

* Add more logging

* Only clear cache if symbols are different

* Refactor ambient module handling

* Start caching package.json stuff

* Support package.json searching in fourslash

* Move import suggestions cache to Project

* Start making more module specifier work available without having the importing file

* Going to backtrack some from here

* Get rid of dumb cache, fix node core modules stuff

* Start determining changes to a file have invalidated its own auto imports

* Move package.json related utils to utilities

* Add failing test

* Make first test pass

* Don’t filter when there’s no package.json, fix scoped package imports

* Use type acquisition as a heuristic for whether a JS project is using node core

* Make same fix in getCompletionDetails

* Fix re-exporting

* Change JS node core module heuristic to same-file utilization

* Remove unused method

* Remove other unused method

* Remove unused triple-slash ref

* Update comment

* Refactor findAlias to forEachAlias to reduce iterations

* Really fix re-exporting

* Use getModuleSpecifier instead of custom hack

* Fix offering auto imports to paths within node modules

* Rename things and make comments better

* Add another reexport test

* Inline `symbolHasBeenSeen`

* Simplify forEachAlias to findAlias

* Add note that symbols is mutated

* Symbol order doesn’t matter here

* Style nits

* Add test with nested package.jsons

* Fix and add tests for export * re-exports

* Don’t fail when alias isn’t found

* Make some easy optimizations

* Clean up memoization when done

* Remove unnecessary semicolon

* Make getSymbolsFromOtherSourceFileExports pure

* Cache auto imports

* Revert "Cache auto imports"

This reverts commit 8ea4829587.

* Handle merged symbols through cache

* Be safer with symbol declarations, add logging

* Improve cache invalidation for imports and exports

* Check symbol presence first

* Only run cache invalidation logic if there’s something to clear

* Consolidate cache invalidation logic

* Fix reuseProgramStructure test

* Add more logging

* Only clear cache if symbols are different

* Refactor ambient module handling

* Finish(?) sourceFileHasChangedOwnImportSuggestions

* Make package.json info model better

* Fix misplaced paren

* Use file structure cache for package.json detection when possible

* Revert unnecessary changes in moduleSpecifiers

* Revert more unnecessary changes

* Don’t watch package.jsons inside node_modules, fix tests

* Work around declaration emit bug

* Sync submodules?

* Delete unused type

* Add server cache tests

* Fix server fourslash editing

* Fix packageJsonInfo tests

* Add node core modules cache test and fix more fourslash

* Clean up symlink caching

* Improve logging

* Function name doesn’t make any sense anymore

* Move symlinks cache to host

* Fix markFileAsDirty from ScriptInfo

* Mark new Project members internal

* Use Path instead of fileName

* Rename AutoImportSuggestionsCache

* Improve WatchType description

* Remove entries() from packageJsonCache

* Fix path/fileName bug

* Also cache symlinks on Program for benefit of d.ts emit

* Let language service use Program’s symlink cache
This commit is contained in:
Andrew Branch
2019-09-27 13:38:31 -07:00
committed by GitHub
parent 558ece72cb
commit 304fcee09b
42 changed files with 1963 additions and 210 deletions
+2
View File
@@ -140,6 +140,7 @@
"unittests/tsserver/getEditsForFileRename.ts",
"unittests/tsserver/getExportReferences.ts",
"unittests/tsserver/importHelpers.ts",
"unittests/tsserver/importSuggestionsCache.ts",
"unittests/tsserver/inferredProjects.ts",
"unittests/tsserver/languageService.ts",
"unittests/tsserver/maxNodeModuleJsDepth.ts",
@@ -147,6 +148,7 @@
"unittests/tsserver/navTo.ts",
"unittests/tsserver/occurences.ts",
"unittests/tsserver/openFile.ts",
"unittests/tsserver/packageJsonInfo.ts",
"unittests/tsserver/projectErrors.ts",
"unittests/tsserver/projectReferenceCompileOnSave.ts",
"unittests/tsserver/projectReferenceErrors.ts",
@@ -0,0 +1,60 @@
namespace ts.projectSystem {
const aTs: File = {
path: "/a.ts",
content: "export const foo = 0;",
};
const bTs: File = {
path: "/b.ts",
content: "foo",
};
const tsconfig: File = {
path: "/tsconfig.json",
content: "{}",
};
const ambientDeclaration: File = {
path: "/ambient.d.ts",
content: "declare module 'ambient' {}"
};
describe("unittests:: tsserver:: importSuggestionsCache", () => {
it("caches auto-imports in the same file", () => {
const { importSuggestionsCache, checker } = setup();
assert.ok(importSuggestionsCache.get(bTs.path, checker));
});
it("invalidates the cache when new files are added", () => {
const { host, importSuggestionsCache, checker } = setup();
host.reloadFS([aTs, bTs, ambientDeclaration, tsconfig, { ...aTs, path: "/src/a2.ts" }]);
host.runQueuedTimeoutCallbacks();
assert.isUndefined(importSuggestionsCache.get(bTs.path, checker));
});
it("invalidates the cache when files are deleted", () => {
const { host, projectService, importSuggestionsCache, checker } = setup();
projectService.closeClientFile(aTs.path);
host.reloadFS([bTs, ambientDeclaration, tsconfig]);
host.runQueuedTimeoutCallbacks();
assert.isUndefined(importSuggestionsCache.get(bTs.path, checker));
});
});
function setup() {
const host = createServerHost([aTs, bTs, ambientDeclaration, tsconfig]);
const session = createSession(host);
openFilesForSession([aTs, bTs], session);
const projectService = session.getProjectService();
const project = configuredProjectAt(projectService, 0);
const requestLocation: protocol.FileLocationRequestArgs = {
file: bTs.path,
line: 1,
offset: 3,
};
executeSessionRequest<protocol.CompletionsRequest, protocol.CompletionInfoResponse>(session, protocol.CommandTypes.CompletionInfo, {
...requestLocation,
includeExternalModuleExports: true,
prefix: "foo",
});
const checker = project.getLanguageService().getProgram()!.getTypeChecker();
return { host, project, projectService, importSuggestionsCache: project.getImportSuggestionsCache(), checker };
}
}
@@ -0,0 +1,84 @@
namespace ts.projectSystem {
const tsConfig: File = {
path: "/tsconfig.json",
content: "{}"
};
const packageJsonContent = {
dependencies: {
redux: "*"
},
peerDependencies: {
react: "*"
},
optionalDependencies: {
typescript: "*"
},
devDependencies: {
webpack: "*"
}
};
const packageJson: File = {
path: "/package.json",
content: JSON.stringify(packageJsonContent, undefined, 2)
};
describe("unittests:: tsserver:: packageJsonInfo", () => {
it("detects new package.json files that are added, caches them, and watches them", () => {
// Initialize project without package.json
const { project, host } = setup([tsConfig]);
assert.isUndefined(project.packageJsonCache.getInDirectory("/" as Path));
// Add package.json
host.reloadFS([tsConfig, packageJson]);
let packageJsonInfo = project.packageJsonCache.getInDirectory("/" as Path)!;
assert.ok(packageJsonInfo);
assert.ok(packageJsonInfo.dependencies);
assert.ok(packageJsonInfo.devDependencies);
assert.ok(packageJsonInfo.peerDependencies);
assert.ok(packageJsonInfo.optionalDependencies);
// Edit package.json
host.reloadFS([
tsConfig,
{
...packageJson,
content: JSON.stringify({
...packageJsonContent,
dependencies: undefined
})
}
]);
packageJsonInfo = project.packageJsonCache.getInDirectory("/" as Path)!;
assert.isUndefined(packageJsonInfo.dependencies);
});
it("finds package.json on demand, watches for deletion, and removes them from cache", () => {
// Initialize project with package.json
const { project, host } = setup();
project.getPackageJsonsVisibleToFile("/src/whatever/blah.ts" as Path);
assert.ok(project.packageJsonCache.getInDirectory("/" as Path));
// Delete package.json
host.reloadFS([tsConfig]);
assert.isUndefined(project.packageJsonCache.getInDirectory("/" as Path));
});
it("finds multiple package.json files when present", () => {
// Initialize project with package.json at root
const { project, host } = setup();
// Add package.json in /src
host.reloadFS([tsConfig, packageJson, { ...packageJson, path: "/src/package.json" }]);
assert.lengthOf(project.getPackageJsonsVisibleToFile("/a.ts" as Path), 1);
assert.lengthOf(project.getPackageJsonsVisibleToFile("/src/b.ts" as Path), 2);
});
});
function setup(files: readonly File[] = [tsConfig, packageJson]) {
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
projectService.openClientFile(files[0].path);
const project = configuredProjectAt(projectService, 0);
return { host, session, project, projectService };
}
}
@@ -840,6 +840,7 @@ namespace ts.projectSystem {
const watchedFilesExpected = createMap<number>();
watchedFilesExpected.set(jsconfig.path, 1); // project files
watchedFilesExpected.set(libFile.path, 1); // project files
watchedFilesExpected.set(combinePaths(installer.globalTypingsCacheLocation, "package.json"), 1);
checkWatchedFilesDetailed(host, watchedFilesExpected);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);