From 06ca68f259ba75a710f9d5b157c39c0538902af5 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 19 Aug 2021 16:44:32 -0700 Subject: [PATCH 1/2] Let AutoImportProviderProject resolve JS when allowJs and maxNodeModulesJsDepth allows --- src/compiler/moduleNameResolver.ts | 4 +- src/harness/client.ts | 7 ++++ src/harness/fourslashImpl.ts | 9 ++++- src/harness/fourslashInterfaceImpl.ts | 6 ++- src/harness/harnessLanguageService.ts | 2 +- src/server/project.ts | 23 ++++++++---- tests/cases/fourslash/fourslash.ts | 19 +++++++++- .../server/autoImportSymlinkedJsPackages.ts | 37 +++++++++++++++++++ .../cases/fourslash/server/configurePlugin.ts | 2 +- 9 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 tests/cases/fourslash/server/autoImportSymlinkedJsPackages.ts diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 6a07e4758e866..0f8e3f1f53a4c 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1078,9 +1078,9 @@ namespace ts { } /* @internal */ - export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost): string | undefined { + export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); - return resolvedModule && resolvedModule.resolvedFileName; + return resolvedModule; } const jsOnlyExtensions = [Extensions.JavaScript]; diff --git a/src/harness/client.ts b/src/harness/client.ts index c72cfe681a725..d1f7fe69bbd82 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -138,6 +138,13 @@ namespace ts.server { this.processResponse(request, /*expectEmptyBody*/ true); } + /*@internal*/ + setCompilerOptionsForInferredProjects(options: protocol.CompilerOptions) { + const args: protocol.SetCompilerOptionsForInferredProjectsArgs = { options }; + const request = this.processRequest(CommandNames.CompilerOptionsForInferredProjects, args); + this.processResponse(request, /*expectEmptyBody*/ false); + } + openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName }; this.processRequest(CommandNames.Open, args); diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 79cd980083dfc..e1e96e051dc58 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3935,6 +3935,11 @@ namespace FourSlash { (this.languageService as ts.server.SessionClient).configurePlugin(pluginName, configuration); } + public setCompilerOptionsForInferredProjects(options: ts.server.protocol.CompilerOptions) { + ts.Debug.assert(this.testType === FourSlashTestType.Server); + (this.languageService as ts.server.SessionClient).setCompilerOptionsForInferredProjects(options); + } + public toggleLineComment(newFileContent: string): void { const changes: ts.TextChange[] = []; for (const range of this.getRanges()) { @@ -4077,7 +4082,7 @@ namespace FourSlash { try { const test = new FourSlashInterface.Test(state); const goTo = new FourSlashInterface.GoTo(state); - const plugins = new FourSlashInterface.Plugins(state); + const config = new FourSlashInterface.Config(state); const verify = new FourSlashInterface.Verify(state); const edit = new FourSlashInterface.Edit(state); const debug = new FourSlashInterface.Debug(state); @@ -4085,7 +4090,7 @@ namespace FourSlash { const cancellation = new FourSlashInterface.Cancellation(state); // eslint-disable-next-line no-eval const f = eval(wrappedCode); - f(test, goTo, plugins, verify, edit, debug, format, cancellation, FourSlashInterface.classification, FourSlashInterface.Completion, verifyOperationIsCancelled); + f(test, goTo, config, verify, edit, debug, format, cancellation, FourSlashInterface.classification, FourSlashInterface.Completion, verifyOperationIsCancelled); } catch (err) { // ensure 'source-map-support' is triggered while we still have the handler attached by accessing `error.stack`. diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index ef3471866e223..5b4a3fe30aaef 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -48,13 +48,17 @@ namespace FourSlashInterface { } } - export class Plugins { + export class Config { constructor(private state: FourSlash.TestState) { } public configurePlugin(pluginName: string, configuration: any): void { this.state.configurePlugin(pluginName, configuration); } + + public setCompilerOptionsForInferredProjects(options: ts.server.protocol.CompilerOptions): void { + this.state.setCompilerOptionsForInferredProjects(options); + } } export class GoTo { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index d067e1c5addb8..85bd824da9c45 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -829,7 +829,7 @@ namespace Harness.LanguageService { setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { // eslint-disable-next-line no-restricted-globals - return setTimeout(callback, ms, args); + return setTimeout(callback, ms, ...args); } clearTimeout(timeoutId: any): void { diff --git a/src/server/project.ts b/src/server/project.ts index b1c864ceecfbe..bbc0c5624197f 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1919,16 +1919,25 @@ namespace ts.server { } if (dependencyNames) { - const resolutions = map(arrayFrom(dependencyNames.keys()), name => resolveTypeReferenceDirective( - name, - rootFileName, - compilerOptions, - moduleResolutionHost)); + const resolutions = mapDefined(arrayFrom(dependencyNames.keys()), name => { + const types = resolveTypeReferenceDirective( + name, + rootFileName, + compilerOptions, + moduleResolutionHost); + + if (types.resolvedTypeReferenceDirective) { + return types.resolvedTypeReferenceDirective; + } + if (compilerOptions.allowJs && compilerOptions.maxNodeModuleJsDepth) { + return tryResolveJSModule(name, hostProject.currentDirectory, moduleResolutionHost); + } + }); const symlinkCache = hostProject.getSymlinkCache(); for (const resolution of resolutions) { - if (!resolution.resolvedTypeReferenceDirective?.resolvedFileName) continue; - const { resolvedFileName, originalPath } = resolution.resolvedTypeReferenceDirective; + if (!resolution.resolvedFileName) continue; + const { resolvedFileName, originalPath } = resolution; if (!program.getSourceFile(resolvedFileName) && (!originalPath || !program.getSourceFile(originalPath))) { rootNames = append(rootNames, resolvedFileName); // Avoid creating a large project that would significantly slow down time to editor interactivity diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 3a1ff85432604..273adfb9707fe 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -111,6 +111,20 @@ declare module ts { exportName: string; } + interface CompilerOptions { + module?: string; + target?: string; + jsx?: string; + allowJs?: boolean; + maxNodeModulesJsDepth?: number; + strictNullChecks?: boolean; + sourceMap?: boolean; + allowSyntheticDefaultImports?: boolean; + allowNonTsExtensions?: boolean; + resolveJsonModule?: boolean; + [key: string]: string | number | boolean | undefined; + } + function flatMap(array: ReadonlyArray, mapfn: (x: T, i: number) => U | ReadonlyArray | undefined): U[]; } @@ -200,8 +214,9 @@ declare namespace FourSlashInterface { symbolsInScope(range: Range): any[]; setTypesRegistry(map: { [key: string]: void }): void; } - class plugins { + class config { configurePlugin(pluginName: string, configuration: any): void; + setCompilerOptionsForInferredProjects(options: ts.CompilerOptions) } class goTo { marker(name?: string | Marker): void; @@ -810,7 +825,7 @@ declare namespace FourSlashInterface { declare function ignoreInterpolations(diagnostic: string | ts.DiagnosticMessage): FourSlashInterface.DiagnosticIgnoredInterpolations; declare function verifyOperationIsCancelled(f: any): void; declare var test: FourSlashInterface.test_; -declare var plugins: FourSlashInterface.plugins; +declare var config: FourSlashInterface.config; declare var goTo: FourSlashInterface.goTo; declare var verify: FourSlashInterface.verify; declare var edit: FourSlashInterface.edit; diff --git a/tests/cases/fourslash/server/autoImportSymlinkedJsPackages.ts b/tests/cases/fourslash/server/autoImportSymlinkedJsPackages.ts new file mode 100644 index 0000000000000..c44b4aa831f82 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportSymlinkedJsPackages.ts @@ -0,0 +1,37 @@ +/// + +// @Filename: /packages/a/package.json +//// { +//// "name": "package-a", +//// "dependencies": { +//// "package-b": "*" +//// } +//// } + +// @Filename: /packages/a/index.js +//// packageB/**/ + +// @Filename: /packages/b/package.json +//// { "name": "package-b", "main": "index.js" } + +// @Filename: /packages/b/index.js +//// export const packageB = "package-b"; + +// @link: /packages/b -> /packages/a/node_modules/package-b + +config.setCompilerOptionsForInferredProjects({ module: "commonjs", allowJs: true, maxNodeModulesJsDepth: 2 }); +goTo.marker(""); +verify.completions({ + marker: "", + includes: [{ + name: "packageB", + source: "package-b", + sourceDisplay: "package-b", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + } +}); diff --git a/tests/cases/fourslash/server/configurePlugin.ts b/tests/cases/fourslash/server/configurePlugin.ts index 90745f9314613..251a343217125 100644 --- a/tests/cases/fourslash/server/configurePlugin.ts +++ b/tests/cases/fourslash/server/configurePlugin.ts @@ -18,5 +18,5 @@ // Test that plugin adds an error message which is able to be configured goTo.marker(); verify.getSemanticDiagnostics([{ message: "configured error", code: 9999, range: { pos: 0, end: 3, fileName: "a.ts" } }]); -plugins.configurePlugin("configurable-diagnostic-adder", { message: "new error" }); +config.configurePlugin("configurable-diagnostic-adder", { message: "new error" }); verify.getSemanticDiagnostics([{ message: "new error", code: 9999, range: { pos: 0, end: 3, fileName: "a.ts" } }]); From c7cca6e6a88cb10b171df1e85cbb3c87d277cbbc Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 19 Aug 2021 16:48:41 -0700 Subject: [PATCH 2/2] Simplify function --- src/compiler/moduleNameResolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 0f8e3f1f53a4c..a1c993dc57e74 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1079,8 +1079,7 @@ namespace ts { /* @internal */ export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { - const { resolvedModule } = tryResolveJSModuleWorker(moduleName, initialDir, host); - return resolvedModule; + return tryResolveJSModuleWorker(moduleName, initialDir, host).resolvedModule; } const jsOnlyExtensions = [Extensions.JavaScript];