From 7c0fef43dc635c6d0b2f9180185dbd05bdcc2891 Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Fri, 14 Jul 2023 12:32:12 +1000 Subject: [PATCH 01/26] Fix extension replacement in auto-import for `package.json#exports` --- src/compiler/moduleSpecifiers.ts | 17 ++++++------ ...portPackageJsonExportsSpecifierEndsInTs.ts | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/autoImportPackageJsonExportsSpecifierEndsInTs.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index eeeeb75615994..a870e103ff634 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -819,6 +819,10 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s } break; case MatchingMode.Directory: + if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, /*ignoreCase*/ false); + return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; + } if (containsPath(pathOrPattern, targetFilePath)) { const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; @@ -828,14 +832,14 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s const starPos = pathOrPattern.indexOf("*"); const leadingSlice = pathOrPattern.slice(0, starPos); const trailingSlice = pathOrPattern.slice(starPos + 1); - if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { - const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } + if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { + const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } break; } } @@ -973,10 +977,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan ? tryGetModuleNameFromExports(options, path, packageRootPath, packageName, packageJsonContent.exports, conditions) : undefined; if (fromExports) { - const withJsExtension = !hasTSFileExtension(fromExports.moduleFileToTry) - ? fromExports - : { moduleFileToTry: removeFileExtension(fromExports.moduleFileToTry) + tryGetJSExtensionForFile(fromExports.moduleFileToTry, options) }; - return { ...withJsExtension, verbatimFromExports: true }; + return { ...fromExports, verbatimFromExports: true }; } if (packageJsonContent.exports) { return { moduleFileToTry: path, blockedByExports: true }; diff --git a/tests/cases/fourslash/autoImportPackageJsonExportsSpecifierEndsInTs.ts b/tests/cases/fourslash/autoImportPackageJsonExportsSpecifierEndsInTs.ts new file mode 100644 index 0000000000000..7784b0334f929 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonExportsSpecifierEndsInTs.ts @@ -0,0 +1,27 @@ +/// + +// @module: nodenext + +// @Filename: /node_modules/pkg/package.json +//// { +//// "name": "pkg", +//// "version": "1.0.0", +//// "exports": { +//// "./something.ts": "./a.js" +//// } +//// } + +// @Filename: /node_modules/pkg/a.d.ts +//// export function foo(): void; + +// @Filename: /package.json +//// { +//// "dependencies": { +//// "pkg": "*" +//// } +//// } + +// @Filename: /index.ts +//// foo/**/ + +verify.importFixModuleSpecifiers("", ["pkg/something.ts"]); From 60ec33b3b8b1ea8dafab91055e31872c6c698b58 Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Sat, 27 May 2023 13:00:29 +1000 Subject: [PATCH 02/26] Add auto-import for package.json `imports` field --- src/compiler/moduleSpecifiers.ts | 95 +++++++++++++------ .../autoImportPackageJsonImportsLength1.ts | 18 ++++ .../autoImportPackageJsonImportsLength2.ts | 18 ++++ .../autoImportPackageJsonImportsPattern.ts | 18 ++++ .../autoImportPackageJsonImportsPattern_js.ts | 18 ++++ ...toImportPackageJsonImportsPattern_js_ts.ts | 18 ++++ .../autoImportPackageJsonImportsPattern_ts.ts | 18 ++++ ...toImportPackageJsonImportsPattern_ts_js.ts | 18 ++++ ...toImportPackageJsonImportsPattern_ts_ts.ts | 18 ++++ .../autoImportPackageJsonImports_js.ts | 18 ++++ .../autoImportPackageJsonImports_ts.ts | 18 ++++ 11 files changed, 245 insertions(+), 30 deletions(-) create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsLength1.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsLength2.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern_js.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern_js_ts.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_js.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_ts.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImports_js.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImports_ts.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index a870e103ff634..bf76dc9970467 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -49,6 +49,7 @@ import { getRelativePathFromDirectory, getRelativePathToDirectoryOrUrl, getResolvePackageJsonExports, + getResolvePackageJsonImports, getSourceFileOfModule, getSupportedExtensions, getTextOfIdentifierOrLiteral, @@ -89,6 +90,7 @@ import { pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, + readJson, removeExtension, removeFileExtension, removeSuffix, @@ -456,7 +458,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt const allowedEndings = getAllowedEndingsInPrefererredOrder(importMode); const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName, allowedEndings, compilerOptions) || processEnding(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), allowedEndings, compilerOptions); - if (!baseUrl && !paths || relativePreference === RelativePreference.Relative) { + if (!baseUrl && !paths && !getResolvePackageJsonImports(compilerOptions) || relativePreference === RelativePreference.Relative) { return pathsOnly ? undefined : relativePath; } @@ -466,12 +468,14 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt return pathsOnly ? undefined : relativePath; } - const fromPaths = paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions); + const fromPackageJsonImports = pathsOnly ? undefined : tryGetModuleNameFromPackageJsonImports(moduleFileName, sourceDirectory, compilerOptions, host, importMode); + + const fromPaths = pathsOnly || fromPackageJsonImports === undefined ? paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions) : undefined; if (pathsOnly) { return fromPaths; } - const maybeNonRelative = fromPaths === undefined && baseUrl !== undefined ? processEnding(relativeToBaseUrl, allowedEndings, compilerOptions) : fromPaths; + const maybeNonRelative = fromPackageJsonImports ?? (fromPaths === undefined && baseUrl !== undefined ? processEnding(relativeToBaseUrl, allowedEndings, compilerOptions) : fromPaths); if (!maybeNonRelative) { return relativePath; } @@ -538,8 +542,8 @@ function getNearestAncestorDirectoryWithPackageJson(host: ModuleSpecifierResolut if (host.getNearestAncestorDirectoryWithPackageJson) { return host.getNearestAncestorDirectoryWithPackageJson(fileName); } - return !!forEachAncestorDirectory(fileName, directory => { - return host.fileExists(combinePaths(directory, "package.json")) ? true : undefined; + return forEachAncestorDirectory(fileName, directory => { + return host.fileExists(combinePaths(directory, "package.json")) ? directory : undefined; }); } @@ -808,7 +812,7 @@ const enum MatchingMode { Pattern } -function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode = MatchingMode.Exact): { moduleFileToTry: string } | undefined { +function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode): { moduleFileToTry: string } | undefined { if (typeof exports === "string") { const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; @@ -844,32 +848,16 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s } } else if (Array.isArray(exports)) { - return forEach(exports, e => tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, e, conditions)); + return forEach(exports, e => tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, e, conditions, mode)); } else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null - if (allKeysStartWithDot(exports as MapLike)) { - // sub-mappings - // 3 cases: - // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) - // * pattern mappings (contains a *) - // * exact mappings (no *, does not end with /) - return forEach(getOwnKeys(exports as MapLike), k => { - const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); - const mode = endsWith(k, "/") ? MatchingMode.Directory - : stringContains(k, "*") ? MatchingMode.Pattern - : MatchingMode.Exact; - return tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); - }); - } - else { - // conditional mapping - for (const key of getOwnKeys(exports as MapLike)) { - if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { - const subTarget = (exports as MapLike)[key]; - const result = tryGetModuleNameFromExports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode); - if (result) { - return result; - } + // conditional mapping + for (const key of getOwnKeys(exports as MapLike)) { + if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { + const subTarget = (exports as MapLike)[key]; + const result = tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode); + if (result) { + return result; } } } @@ -877,6 +865,53 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s return undefined; } +function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[]): { moduleFileToTry: string } | undefined { + if (typeof exports === "object" && exports !== null && !Array.isArray(exports) && allKeysStartWithDot(exports as MapLike)) { // eslint-disable-line no-null/no-null + // sub-mappings + // 3 cases: + // * directory mappings (legacyish, key ends with / (technically allows index/extension resolution under cjs mode)) + // * pattern mappings (contains a *) + // * exact mappings (no *, does not end with /) + return forEach(getOwnKeys(exports as MapLike), k => { + const subPackageName = getNormalizedAbsolutePath(combinePaths(packageName, k), /*currentDirectory*/ undefined); + const mode = endsWith(k, "/") ? MatchingMode.Directory + : stringContains(k, "*") ? MatchingMode.Pattern + : MatchingMode.Exact; + return tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); + }); + } + return tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact); +} + +function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode) { + if (!host.readFile || !getResolvePackageJsonImports(options)) { + return undefined; + } + + const ancestorDirectoryWithPackageJson = getNearestAncestorDirectoryWithPackageJson(host, sourceDirectory); + if (!ancestorDirectoryWithPackageJson) { + return undefined; + } + const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); + const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); + if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined) { + return undefined; + } + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || readJson(packageJsonPath, host as { readFile(fileName: string): string | undefined }); + const imports = (packageJsonContent as any).imports; + if (!imports) { + return undefined; + } + const conditions = getConditions(options, importMode === ModuleKind.ESNext); + return forEach(getOwnKeys(imports as MapLike), k => { + if (!startsWith(k, "#") || k === "#" || startsWith(k, "#/")) return undefined; + const mode = endsWith(k, "/") ? MatchingMode.Directory + : stringContains(k, "*") ? MatchingMode.Pattern + : MatchingMode.Exact; + return tryGetModuleNameFromExportsOrImports(options, moduleFileName, sourceDirectory, k, (imports as MapLike)[k], conditions, mode); + })?.moduleFileToTry; +} + function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string, allowedEndings: readonly ModuleSpecifierEnding[], compilerOptions: CompilerOptions): string | undefined { const normalizedTargetPaths = getPathsRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName); if (normalizedTargetPaths === undefined) { diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsLength1.ts b/tests/cases/fourslash/autoImportPackageJsonImportsLength1.ts new file mode 100644 index 0000000000000..ae0999fb7a3b0 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsLength1.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/a/b/c/something.ts +//// export function something(name: string): any; + +// @Filename: /src/a/b/c/d.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["./something"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsLength2.ts b/tests/cases/fourslash/autoImportPackageJsonImportsLength2.ts new file mode 100644 index 0000000000000..5b54f6b3149c4 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsLength2.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/a/b/c/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#a/b/c/something"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern.ts new file mode 100644 index 0000000000000..70aeabcfdaaea --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something.js"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js.ts new file mode 100644 index 0000000000000..a5955a65c9275 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.js" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js_ts.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js_ts.ts new file mode 100644 index 0000000000000..bf9b3305d77bb --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_js_ts.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*.js": "./src/*.ts" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something.js"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts.ts new file mode 100644 index 0000000000000..a48ef7d4bc720 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_js.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_js.ts new file mode 100644 index 0000000000000..701b8fbd978b8 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_js.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*.ts": "./src/*.js" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something.ts"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_ts.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_ts.ts new file mode 100644 index 0000000000000..19171deea3b58 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPattern_ts_ts.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*.ts": "./src/*.ts" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#something.ts"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImports_js.ts b/tests/cases/fourslash/autoImportPackageJsonImports_js.ts new file mode 100644 index 0000000000000..1056d13dd5076 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImports_js.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#thing": "./src/something.js" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#thing"]); diff --git a/tests/cases/fourslash/autoImportPackageJsonImports_ts.ts b/tests/cases/fourslash/autoImportPackageJsonImports_ts.ts new file mode 100644 index 0000000000000..a8e8b13b3d6e4 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImports_ts.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#thing": "./src/something.ts" +//// } +//// } + +// @Filename: /src/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#thing"]); From faef5fdabe89fc016de546db589920015bb7767b Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Wed, 16 Aug 2023 15:06:19 +1000 Subject: [PATCH 03/26] Fix using incorrect directory and add more tests --- src/compiler/moduleSpecifiers.ts | 2 +- ...autoImportPackageJsonImportsPreference1.ts | 20 +++++++++++++++++++ ...autoImportPackageJsonImportsPreference2.ts | 20 +++++++++++++++++++ ...autoImportPackageJsonImportsPreference3.ts | 20 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPreference1.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPreference2.ts create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsPreference3.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index bf76dc9970467..4dcf24ea4b4a9 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -908,7 +908,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi const mode = endsWith(k, "/") ? MatchingMode.Directory : stringContains(k, "*") ? MatchingMode.Pattern : MatchingMode.Exact; - return tryGetModuleNameFromExportsOrImports(options, moduleFileName, sourceDirectory, k, (imports as MapLike)[k], conditions, mode); + return tryGetModuleNameFromExportsOrImports(options, moduleFileName, ancestorDirectoryWithPackageJson, k, (imports as MapLike)[k], conditions, mode); })?.moduleFileToTry; } diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPreference1.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPreference1.ts new file mode 100644 index 0000000000000..dfac2a3ab55d0 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPreference1.ts @@ -0,0 +1,20 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/a/b/c/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["./src/a/b/c/something"], { + importModuleSpecifierPreference: "relative" +}); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPreference2.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPreference2.ts new file mode 100644 index 0000000000000..affd548cfa3ce --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPreference2.ts @@ -0,0 +1,20 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/a/b/c/something.ts +//// export function something(name: string): any; + +// @Filename: /a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["./src/a/b/c/something"], { + importModuleSpecifierPreference: "project-relative" +}); diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsPreference3.ts b/tests/cases/fourslash/autoImportPackageJsonImportsPreference3.ts new file mode 100644 index 0000000000000..3113af1bab627 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsPreference3.ts @@ -0,0 +1,20 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#*": "./src/*.ts" +//// } +//// } + +// @Filename: /src/a/b/c/something.ts +//// export function something(name: string): any; + +// @Filename: /src/a/b/c/d.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#a/b/c/something"], { + importModuleSpecifierPreference: "non-relative" +}); From 4e676a3f98436b9a2b629a1ea833c9e5467f1633 Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Wed, 16 Aug 2023 15:56:19 +1000 Subject: [PATCH 04/26] Use JSON.parse diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 4dcf24ea4b..d6022afe4b 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -90,7 +90,6 @@ import { pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, - readJson, removeExtension, removeFileExtension, removeSuffix, @@ -894,11 +893,11 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi } const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined) { + if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || readJson(packageJsonPath, host as { readFile(fileName: string): string | undefined }); - const imports = (packageJsonContent as any).imports; + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile(packageJsonPath)!); + const imports = packageJsonContent?.imports; if (!imports) { return undefined; } --- src/compiler/moduleSpecifiers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 4dcf24ea4b4a9..d6022afe4bad6 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -90,7 +90,6 @@ import { pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, - readJson, removeExtension, removeFileExtension, removeSuffix, @@ -894,11 +893,11 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi } const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined) { + if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || readJson(packageJsonPath, host as { readFile(fileName: string): string | undefined }); - const imports = (packageJsonContent as any).imports; + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile(packageJsonPath)!); + const imports = packageJsonContent?.imports; if (!imports) { return undefined; } From 01ceb652a5b9fd32f8d5f80b938bbd65e6297ecc Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Thu, 17 Aug 2023 10:42:59 +1000 Subject: [PATCH 05/26] Use readJson again so they can all be replaced at the same time --- src/compiler/moduleSpecifiers.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 0afff755d15b7..484dd069f4fed 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -90,6 +90,7 @@ import { pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, + readJson, removeExtension, removeFileExtension, removeSuffix, @@ -907,11 +908,11 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi } const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { + if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile(packageJsonPath)!); - const imports = packageJsonContent?.imports; + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || readJson(packageJsonPath, host as { readFile(path: string): string | undefined; }); + const imports = (packageJsonContent as any).imports; if (!imports) { return undefined; } From 864b9a302c2bae22f3fbcd71a87db36dd4ff3b06 Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Tue, 12 Sep 2023 09:18:02 +1000 Subject: [PATCH 06/26] Add test with conditions --- .../autoImportPackageJsonImportsConditions.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/cases/fourslash/autoImportPackageJsonImportsConditions.ts diff --git a/tests/cases/fourslash/autoImportPackageJsonImportsConditions.ts b/tests/cases/fourslash/autoImportPackageJsonImportsConditions.ts new file mode 100644 index 0000000000000..71fd139167ca9 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImportsConditions.ts @@ -0,0 +1,22 @@ +/// + +// @module: nodenext + +// @Filename: /package.json +//// { +//// "imports": { +//// "#thing": { +//// "types": { "import": "./types-esm/thing.d.mts", "require": "./types/thing.d.ts" }, +//// "default": { "import": "./esm/thing.mjs", "require": "./dist/thing.js" } +//// } +//// } +//// } + + +// @Filename: /src/.ts +//// something/*a*/ + +// @Filename: /types/thing.d.ts +//// export function something(name: string): any; + +verify.importFixModuleSpecifiers("a", ["#thing"]); From 8f781c59d6303f73b03505f824ed74e2a97b0860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 10 Oct 2023 23:51:28 +0200 Subject: [PATCH 07/26] Revert "Use readJson again so they can all be replaced at the same time" This reverts commit 01ceb652a5b9fd32f8d5f80b938bbd65e6297ecc. --- src/compiler/moduleSpecifiers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index fab3af6b6ae90..69443b03012c9 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -90,7 +90,6 @@ import { pathIsBareSpecifier, pathIsRelative, PropertyAccessExpression, - readJson, removeExtension, removeFileExtension, removeSuffix, @@ -918,11 +917,11 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi } const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined) { + if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || readJson(packageJsonPath, host as { readFile(path: string): string | undefined; }); - const imports = (packageJsonContent as any).imports; + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile(packageJsonPath)!); + const imports = packageJsonContent?.imports; if (!imports) { return undefined; } From a2aa5aea288b2622a9144ebc58d395f798a326ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 11 Oct 2023 00:03:23 +0200 Subject: [PATCH 08/26] wrap `JSON.parse` calls with try/catch --- src/compiler/moduleSpecifiers.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 69443b03012c9..04cde228c341f 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -112,6 +112,15 @@ import { UserPreferences, } from "./_namespaces/ts"; +function safeJsonRead(packageJsonPath: string, readFile: (path: string) => string | undefined) { + try { + return JSON.parse(readFile(packageJsonPath)!); + } + catch { + return {}; + } +} + // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. const enum RelativePreference { @@ -920,7 +929,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile(packageJsonPath)!); + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || safeJsonRead(packageJsonPath, host.readFile); const imports = packageJsonContent?.imports; if (!imports) { return undefined; @@ -1022,7 +1031,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan let maybeBlockedByTypesVersions = false; const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || JSON.parse(host.readFile!(packageJsonPath)!); + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || safeJsonRead(packageJsonPath, host.readFile!); const importMode = overrideMode || importingSourceFile.impliedNodeFormat; if (getResolvePackageJsonExports(options)) { // The package name that we found in node_modules could be different from the package From 051239b35f7ba74be721c9060bc77b4d6894882e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 30 Oct 2023 22:42:49 +0100 Subject: [PATCH 09/26] Add support for output file remapping --- src/compiler/moduleSpecifiers.ts | 62 ++- .../autoImportProvider_importsMap1.js | 357 ++++++++++++++++++ .../autoImportProvider_importsMap2.js | 321 ++++++++++++++++ .../autoImportProvider_importsMap3.js | 321 ++++++++++++++++ .../server/autoImportProvider_importsMap1.ts | 32 ++ .../server/autoImportProvider_importsMap2.ts | 26 ++ .../server/autoImportProvider_importsMap3.ts | 26 ++ 7 files changed, 1134 insertions(+), 11 deletions(-) create mode 100644 tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js create mode 100644 tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap2.js create mode 100644 tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap3.js create mode 100644 tests/cases/fourslash/server/autoImportProvider_importsMap1.ts create mode 100644 tests/cases/fourslash/server/autoImportProvider_importsMap2.ts create mode 100644 tests/cases/fourslash/server/autoImportProvider_importsMap3.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 04cde228c341f..beab6c1d78c72 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -4,6 +4,7 @@ import { AmbientModuleDeclaration, append, arrayFrom, + changeExtension, CharacterCodes, combinePaths, compareBooleans, @@ -34,6 +35,7 @@ import { forEachAncestorDirectory, getBaseFileName, GetCanonicalFileName, + getCommonSourceDirectory, getConditions, getDirectoryPath, getEmitModuleResolutionKind, @@ -42,6 +44,7 @@ import { getModuleSpecifierEndingPreference, getNodeModulePathParts, getNormalizedAbsolutePath, + getOutputExtension, getOwnKeys, getPackageJsonTypesVersionsPaths, getPackageNameFromTypesPackageName, @@ -497,7 +500,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt return pathsOnly ? undefined : relativePath; } - const fromPackageJsonImports = pathsOnly ? undefined : tryGetModuleNameFromPackageJsonImports(moduleFileName, sourceDirectory, compilerOptions, host, importMode); + const fromPackageJsonImports = pathsOnly ? undefined : tryGetModuleNameFromPackageJsonImports(moduleFileName, sourceDirectory, compilerOptions, host, getCanonicalFileName, importMode); const fromPaths = pathsOnly || fromPackageJsonImports === undefined ? paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions) : undefined; if (pathsOnly) { @@ -844,17 +847,50 @@ const enum MatchingMode { Pattern, } -function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode): { moduleFileToTry: string; } | undefined { +function getOutputPath(targetFilePath: string, options: CompilerOptions, commonDir: string) { + if (options.emitDeclarationOnly) { + return; + } + return changeExtension( + options.outDir ? + resolvePath( + options.outDir, + getRelativePathFromDirectory(commonDir, targetFilePath, /*ignoreCase*/ false), + ) : + targetFilePath, + getOutputExtension(targetFilePath, options), + ); +} + +function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode, isImports: boolean): { moduleFileToTry: string; } | undefined { if (typeof exports === "string") { + const currentDirectory = host.getCurrentDirectory(); + const commonDir = getNormalizedAbsolutePath( + getCommonSourceDirectory( + options, + () => [], + currentDirectory, + getCanonicalFileName, + ), + currentDirectory, + ); + + const outputFile = getOutputPath(targetFilePath, options, commonDir); + const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; + switch (mode) { case MatchingMode.Exact: - if (comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { + if (outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { return { moduleFileToTry: packageName }; } break; case MatchingMode.Directory: + if (outputFile && containsPath(pathOrPattern, outputFile)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, outputFile, /*ignoreCase*/ false); + return { moduleFileToTry: combinePaths(packageName, fragment) }; + } if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget)) { const fragment = getRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; @@ -868,6 +904,10 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, targetFi const starPos = pathOrPattern.indexOf("*"); const leadingSlice = pathOrPattern.slice(0, starPos); const trailingSlice = pathOrPattern.slice(starPos + 1); + if (outputFile && startsWith(outputFile, leadingSlice) && endsWith(outputFile, trailingSlice)) { + const starReplacement = outputFile.slice(leadingSlice.length, outputFile.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; @@ -880,14 +920,14 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, targetFi } } else if (Array.isArray(exports)) { - return forEach(exports, e => tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, e, conditions, mode)); + return forEach(exports, e => tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, e, conditions, mode, isImports)); } else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null // conditional mapping for (const key of getOwnKeys(exports as MapLike)) { if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { const subTarget = (exports as MapLike)[key]; - const result = tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode); + const result = tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode, isImports); if (result) { return result; } @@ -897,7 +937,7 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, targetFi return undefined; } -function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[]): { moduleFileToTry: string; } | undefined { +function tryGetModuleNameFromExports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[]): { moduleFileToTry: string; } | undefined { if (typeof exports === "object" && exports !== null && !Array.isArray(exports) && allKeysStartWithDot(exports as MapLike)) { // eslint-disable-line no-null/no-null // sub-mappings // 3 cases: @@ -909,13 +949,13 @@ function tryGetModuleNameFromExports(options: CompilerOptions, targetFilePath: s const mode = endsWith(k, "/") ? MatchingMode.Directory : k.includes("*") ? MatchingMode.Pattern : MatchingMode.Exact; - return tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode); + return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode, /*isImports*/ false); }); } - return tryGetModuleNameFromExportsOrImports(options, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact); + return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact, /*isImports*/ false); } -function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode) { +function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, importMode: ResolutionMode) { if (!host.readFile || !getResolvePackageJsonImports(options)) { return undefined; } @@ -940,7 +980,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi const mode = endsWith(k, "/") ? MatchingMode.Directory : k.includes("*") ? MatchingMode.Pattern : MatchingMode.Exact; - return tryGetModuleNameFromExportsOrImports(options, moduleFileName, ancestorDirectoryWithPackageJson, k, (imports as MapLike)[k], conditions, mode); + return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, moduleFileName, ancestorDirectoryWithPackageJson, k, (imports as MapLike)[k], conditions, mode, /*isImports*/ true); })?.moduleFileToTry; } @@ -1041,7 +1081,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); const conditions = getConditions(options, importMode === ModuleKind.ESNext); const fromExports = packageJsonContent.exports - ? tryGetModuleNameFromExports(options, path, packageRootPath, packageName, packageJsonContent.exports, conditions) + ? tryGetModuleNameFromExports(options, host, getCanonicalFileName, path, packageRootPath, packageName, packageJsonContent.exports, conditions) : undefined; if (fromExports) { return { ...fromExports, verbatimFromExports: true }; diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js new file mode 100644 index 0000000000000..28bbf58340b78 --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap1.js @@ -0,0 +1,357 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/package.json] +{ + "type": "module", + "imports": { + "#is-browser": { + "browser": "./dist/env/browser.js", + "default": "./dist/env/node.js" + } + } +} + +//// [/src/a.ts] +isBrowser + +//// [/src/env/browser.ts] +export const isBrowser = true; + +//// [/src/env/node.ts] +export const isBrowser = false; + +//// [/tsconfig.json] +{ + "compilerOptions": { + "module": "nodenext", + "rootDir": "src", + "outDir": "dist" + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/tsconfig.json" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /tsconfig.json :: Config file name: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /tsconfig.json to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/lib.d.ts", + "/lib.decorators.d.ts", + "/lib.decorators.legacy.d.ts", + "/src/a.ts", + "/src/env/browser.ts", + "/src/env/node.ts" + ], + "options": { + "module": 199, + "rootDir": "/src", + "outDir": "/dist", + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/env/browser.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/env/node.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (6) + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /lib.d.ts Text-1 lib.d.ts-Text + /src/a.ts Text-1 "isBrowser" + /src/env/browser.ts Text-1 "export const isBrowser = true;" + /src/env/node.ts Text-1 "export const isBrowser = false;" + + + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.d.ts + Matched by default include pattern '**/*' + src/a.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/env/browser.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/env/node.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/tsconfig.json", + "configFile": "/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /tsconfig.json SVC-1-0 "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"rootDir\": \"src\",\n \"outDir\": \"dist\"\n }\n}" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + tsconfig.json + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /package.json 250 undefined WatchType: package.json file +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (6) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /tsconfig.json ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +After Request +watchedFiles:: +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/package.json: *new* + {"pollingInterval":250} +/src/a.ts: *new* + {"pollingInterval":500} +/src/env/browser.ts: *new* + {"pollingInterval":500} +/src/env/node.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +watchedDirectoriesRecursive:: +: *new* + {} + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "preferences": { + "includeCompletionsForModuleExports": true, + "includeCompletionsWithInsertText": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "syntacticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "syntacticDiagnosticsSync", + "request_seq": 2, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 3, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "semanticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "semanticDiagnosticsSync", + "request_seq": 3, + "success": true, + "body": [ + { + "message": "Cannot find name 'isBrowser'.", + "start": 0, + "length": 9, + "category": "error", + "code": 2304, + "startLocation": { + "line": 1, + "offset": 1 + }, + "endLocation": { + "line": 1, + "offset": 10 + } + } + ] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 4, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "suggestionDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "suggestionDiagnosticsSync", + "request_seq": 4, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 5, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 10, + "errorCodes": [ + 2304 + ] + }, + "command": "getCodeFixes" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getCodeFixes", + "request_seq": 5, + "success": true, + "body": [ + { + "fixName": "import", + "description": "Add import from \"./env/browser.js\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { isBrowser } from \"./env/browser.js\";\r\n\r\n" + } + ] + } + ] + }, + { + "fixName": "import", + "description": "Add import from \"#is-browser\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { isBrowser } from \"#is-browser\";\r\n\r\n" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap2.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap2.js new file mode 100644 index 0000000000000..0faa37e2e0945 --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap2.js @@ -0,0 +1,321 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/package.json] +{ + "type": "module", + "imports": { + "#internal/*": "./dist/internal/*" + } +} + +//// [/src/a.ts] +something + +//// [/src/internal/foo.ts] +export function something(name: string) {} + +//// [/tsconfig.json] +{ + "compilerOptions": { + "module": "nodenext", + "rootDir": "src", + "outDir": "dist" + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/tsconfig.json" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /tsconfig.json :: Config file name: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /tsconfig.json to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/lib.d.ts", + "/lib.decorators.d.ts", + "/lib.decorators.legacy.d.ts", + "/src/a.ts", + "/src/internal/foo.ts" + ], + "options": { + "module": 199, + "rootDir": "/src", + "outDir": "/dist", + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/internal/foo.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /lib.d.ts Text-1 lib.d.ts-Text + /src/a.ts Text-1 "something" + /src/internal/foo.ts Text-1 "export function something(name: string) {}" + + + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.d.ts + Matched by default include pattern '**/*' + src/a.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/internal/foo.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/tsconfig.json", + "configFile": "/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /tsconfig.json SVC-1-0 "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"rootDir\": \"src\",\n \"outDir\": \"dist\"\n }\n}" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + tsconfig.json + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /package.json 250 undefined WatchType: package.json file +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /tsconfig.json ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +After Request +watchedFiles:: +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/package.json: *new* + {"pollingInterval":250} +/src/a.ts: *new* + {"pollingInterval":500} +/src/internal/foo.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +watchedDirectoriesRecursive:: +: *new* + {} + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "preferences": { + "includeCompletionsForModuleExports": true, + "includeCompletionsWithInsertText": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "syntacticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "syntacticDiagnosticsSync", + "request_seq": 2, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 3, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "semanticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "semanticDiagnosticsSync", + "request_seq": 3, + "success": true, + "body": [ + { + "message": "Cannot find name 'something'.", + "start": 0, + "length": 9, + "category": "error", + "code": 2304, + "startLocation": { + "line": 1, + "offset": 1 + }, + "endLocation": { + "line": 1, + "offset": 10 + } + } + ] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 4, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "suggestionDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "suggestionDiagnosticsSync", + "request_seq": 4, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 5, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 10, + "errorCodes": [ + 2304 + ] + }, + "command": "getCodeFixes" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getCodeFixes", + "request_seq": 5, + "success": true, + "body": [ + { + "fixName": "import", + "description": "Add import from \"#internal/foo.js\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { something } from \"#internal/foo.js\";\r\n\r\n" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap3.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap3.js new file mode 100644 index 0000000000000..b2621096bd175 --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap3.js @@ -0,0 +1,321 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/package.json] +{ + "type": "module", + "imports": { + "#internal/": "./dist/internal/" + } +} + +//// [/src/a.ts] +something + +//// [/src/internal/foo.ts] +export function something(name: string) {} + +//// [/tsconfig.json] +{ + "compilerOptions": { + "module": "nodenext", + "rootDir": "src", + "outDir": "dist" + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/tsconfig.json" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /tsconfig.json :: Config file name: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /tsconfig.json to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/lib.d.ts", + "/lib.decorators.d.ts", + "/lib.decorators.legacy.d.ts", + "/src/a.ts", + "/src/internal/foo.ts" + ], + "options": { + "module": 199, + "rootDir": "/src", + "outDir": "/dist", + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/internal/foo.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /lib.d.ts Text-1 lib.d.ts-Text + /src/a.ts Text-1 "something" + /src/internal/foo.ts Text-1 "export function something(name: string) {}" + + + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.d.ts + Matched by default include pattern '**/*' + src/a.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/internal/foo.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/tsconfig.json", + "configFile": "/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /tsconfig.json SVC-1-0 "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"rootDir\": \"src\",\n \"outDir\": \"dist\"\n }\n}" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + tsconfig.json + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /package.json 250 undefined WatchType: package.json file +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /tsconfig.json ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +After Request +watchedFiles:: +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/package.json: *new* + {"pollingInterval":250} +/src/a.ts: *new* + {"pollingInterval":500} +/src/internal/foo.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +watchedDirectoriesRecursive:: +: *new* + {} + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "preferences": { + "includeCompletionsForModuleExports": true, + "includeCompletionsWithInsertText": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "syntacticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "syntacticDiagnosticsSync", + "request_seq": 2, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 3, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "semanticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "semanticDiagnosticsSync", + "request_seq": 3, + "success": true, + "body": [ + { + "message": "Cannot find name 'something'.", + "start": 0, + "length": 9, + "category": "error", + "code": 2304, + "startLocation": { + "line": 1, + "offset": 1 + }, + "endLocation": { + "line": 1, + "offset": 10 + } + } + ] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 4, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "suggestionDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "suggestionDiagnosticsSync", + "request_seq": 4, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 5, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 10, + "errorCodes": [ + 2304 + ] + }, + "command": "getCodeFixes" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getCodeFixes", + "request_seq": 5, + "success": true, + "body": [ + { + "fixName": "import", + "description": "Add import from \"#internal/foo.js\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { something } from \"#internal/foo.js\";\r\n\r\n" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts new file mode 100644 index 0000000000000..02f6072753d16 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap1.ts @@ -0,0 +1,32 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "nodenext", +//// "rootDir": "src", +//// "outDir": "dist" +//// } +//// } + +// @Filename: /package.json +//// { +//// "type": "module", +//// "imports": { +//// "#is-browser": { +//// "browser": "./dist/env/browser.js", +//// "default": "./dist/env/node.js" +//// } +//// } +//// } + +// @Filename: /src/env/browser.ts +//// export const isBrowser = true; + +// @Filename: /src/env/node.ts +//// export const isBrowser = false; + +// @Filename: /src/a.ts +//// isBrowser/**/ + +verify.importFixModuleSpecifiers("", ["./env/browser.js", "#is-browser"]); \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap2.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap2.ts new file mode 100644 index 0000000000000..934823091e79b --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap2.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "nodenext", +//// "rootDir": "src", +//// "outDir": "dist" +//// } +//// } + +// @Filename: /package.json +//// { +//// "type": "module", +//// "imports": { +//// "#internal/*": "./dist/internal/*" +//// } +//// } + +// @Filename: /src/internal/foo.ts +//// export function something(name: string) {} + +// @Filename: /src/a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#internal/foo.js"]); \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap3.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap3.ts new file mode 100644 index 0000000000000..d10e697685315 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap3.ts @@ -0,0 +1,26 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "nodenext", +//// "rootDir": "src", +//// "outDir": "dist" +//// } +//// } + +// @Filename: /package.json +//// { +//// "type": "module", +//// "imports": { +//// "#internal/": "./dist/internal/" +//// } +//// } + +// @Filename: /src/internal/foo.ts +//// export function something(name: string) {} + +// @Filename: /src/a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#internal/foo.js"]); \ No newline at end of file From b3c5ca4620b76fe9dc3e652fbb2bb73c36437bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 30 Oct 2023 23:48:36 +0100 Subject: [PATCH 10/26] limit the behavior to imports --- src/compiler/moduleSpecifiers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index beab6c1d78c72..8bdda78baa131 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -875,7 +875,7 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo currentDirectory, ); - const outputFile = getOutputPath(targetFilePath, options, commonDir); + const outputFile = isImports && getOutputPath(targetFilePath, options, commonDir); const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; From ea677660cc7e435a94b3d843b2827146790d5af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 00:04:25 +0100 Subject: [PATCH 11/26] add extra test case --- .../autoImportProvider_importsMap4.js | 324 ++++++++++++++++++ .../server/autoImportProvider_importsMap4.ts | 29 ++ 2 files changed, 353 insertions(+) create mode 100644 tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap4.js create mode 100644 tests/cases/fourslash/server/autoImportProvider_importsMap4.ts diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap4.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap4.js new file mode 100644 index 0000000000000..e25af521ae67b --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap4.js @@ -0,0 +1,324 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/package.json] +{ + "type": "module", + "imports": { + "#is-browser": { + "types": "./dist/env/browser.d.ts", + "default": "./dist/env/browser.js" + } + } +} + +//// [/src/a.ts] +isBrowser + +//// [/src/env/browser.ts] +export const isBrowser = true; + +//// [/tsconfig.json] +{ + "compilerOptions": { + "module": "nodenext", + "rootDir": "src", + "outDir": "dist" + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/tsconfig.json" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /tsconfig.json :: Config file name: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /tsconfig.json to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/lib.d.ts", + "/lib.decorators.d.ts", + "/lib.decorators.legacy.d.ts", + "/src/a.ts", + "/src/env/browser.ts" + ], + "options": { + "module": 199, + "rootDir": "/src", + "outDir": "/dist", + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/env/browser.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /lib.d.ts Text-1 lib.d.ts-Text + /src/a.ts Text-1 "isBrowser" + /src/env/browser.ts Text-1 "export const isBrowser = true;" + + + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.d.ts + Matched by default include pattern '**/*' + src/a.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/env/browser.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/tsconfig.json", + "configFile": "/tsconfig.json", + "diagnostics": [] + } + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /tsconfig.json SVC-1-0 "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"rootDir\": \"src\",\n \"outDir\": \"dist\"\n }\n}" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + tsconfig.json + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /package.json 250 undefined WatchType: package.json file +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /tsconfig.json ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +After Request +watchedFiles:: +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/package.json: *new* + {"pollingInterval":250} +/src/a.ts: *new* + {"pollingInterval":500} +/src/env/browser.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +watchedDirectoriesRecursive:: +: *new* + {} + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "preferences": { + "includeCompletionsForModuleExports": true, + "includeCompletionsWithInsertText": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "syntacticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "syntacticDiagnosticsSync", + "request_seq": 2, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 3, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "semanticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "semanticDiagnosticsSync", + "request_seq": 3, + "success": true, + "body": [ + { + "message": "Cannot find name 'isBrowser'.", + "start": 0, + "length": 9, + "category": "error", + "code": 2304, + "startLocation": { + "line": 1, + "offset": 1 + }, + "endLocation": { + "line": 1, + "offset": 10 + } + } + ] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 4, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "suggestionDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "suggestionDiagnosticsSync", + "request_seq": 4, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 5, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 10, + "errorCodes": [ + 2304 + ] + }, + "command": "getCodeFixes" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getCodeFixes", + "request_seq": 5, + "success": true, + "body": [ + { + "fixName": "import", + "description": "Add import from \"#is-browser\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { isBrowser } from \"#is-browser\";\r\n\r\n" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap4.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap4.ts new file mode 100644 index 0000000000000..11921f3fe52e6 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap4.ts @@ -0,0 +1,29 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "nodenext", +//// "rootDir": "src", +//// "outDir": "dist" +//// } +//// } + +// @Filename: /package.json +//// { +//// "type": "module", +//// "imports": { +//// "#is-browser": { +//// "types": "./dist/env/browser.d.ts", +//// "default": "./dist/env/browser.js" +//// } +//// } +//// } + +// @Filename: /src/env/browser.ts +//// export const isBrowser = true; + +// @Filename: /src/a.ts +//// isBrowser/**/ + +verify.importFixModuleSpecifiers("", ["#is-browser"]); \ No newline at end of file From 6a72a51a6a04a0b0fdffbc9857ccf682d2526ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 00:09:17 +0100 Subject: [PATCH 12/26] fixed conflict with the recent changes on main --- src/compiler/moduleSpecifiers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index d40bcda568246..a406e4576cb53 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -974,7 +974,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi if (!imports) { return undefined; } - const conditions = getConditions(options, importMode === ModuleKind.ESNext); + const conditions = getConditions(options, importMode); return forEach(getOwnKeys(imports as MapLike), k => { if (!startsWith(k, "#") || k === "#" || startsWith(k, "#/")) return undefined; const mode = endsWith(k, "/") ? MatchingMode.Directory From 9903d72fcb873be7651bb5af1a420f415457aca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 00:10:55 +0100 Subject: [PATCH 13/26] Remove the `emitDeclarationOnly` check --- src/compiler/moduleSpecifiers.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index a406e4576cb53..467083095953c 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -848,9 +848,6 @@ const enum MatchingMode { } function getOutputPath(targetFilePath: string, options: CompilerOptions, commonDir: string) { - if (options.emitDeclarationOnly) { - return; - } return changeExtension( options.outDir ? resolvePath( From 9e2df9485c297cf9315957b42083744ed23f54b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 19:37:21 +0100 Subject: [PATCH 14/26] Move `getCommonSourceDirectory` to the `ModuleSpecifierResolutionHost` --- src/compiler/checker.ts | 2 +- src/compiler/moduleSpecifiers.ts | 34 +++++++++++--------------------- src/compiler/types.ts | 1 + src/services/utilities.ts | 16 +++++++++++++++ 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ac1038a4cdbe..d819d51629fee 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -50411,7 +50411,7 @@ export function signatureHasLiteralTypes(s: Signature) { return !!(s.flags & SignatureFlags.HasLiteralTypes); } -function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHost): ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string; } { +function createBasicNodeBuilderModuleSpecifierResolutionHost(host: TypeCheckerHost): ModuleSpecifierResolutionHost { return { getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", getCurrentDirectory: () => host.getCurrentDirectory(), diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 467083095953c..fe58f18368e8e 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -35,7 +35,6 @@ import { forEachAncestorDirectory, getBaseFileName, GetCanonicalFileName, - getCommonSourceDirectory, getConditions, getDirectoryPath, getEmitModuleResolutionKind, @@ -500,7 +499,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt return pathsOnly ? undefined : relativePath; } - const fromPackageJsonImports = pathsOnly ? undefined : tryGetModuleNameFromPackageJsonImports(moduleFileName, sourceDirectory, compilerOptions, host, getCanonicalFileName, importMode); + const fromPackageJsonImports = pathsOnly ? undefined : tryGetModuleNameFromPackageJsonImports(moduleFileName, sourceDirectory, compilerOptions, host, importMode); const fromPaths = pathsOnly || fromPackageJsonImports === undefined ? paths && tryGetModuleNameFromPaths(relativeToBaseUrl, paths, allowedEndings, host, compilerOptions) : undefined; if (pathsOnly) { @@ -859,20 +858,9 @@ function getOutputPath(targetFilePath: string, options: CompilerOptions, commonD ); } -function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode, isImports: boolean): { moduleFileToTry: string; } | undefined { +function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode, isImports: boolean): { moduleFileToTry: string; } | undefined { if (typeof exports === "string") { - const currentDirectory = host.getCurrentDirectory(); - const commonDir = getNormalizedAbsolutePath( - getCommonSourceDirectory( - options, - () => [], - currentDirectory, - getCanonicalFileName, - ), - currentDirectory, - ); - - const outputFile = isImports && getOutputPath(targetFilePath, options, commonDir); + const outputFile = isImports && getOutputPath(targetFilePath, options, host.getCommonSourceDirectory()); const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; @@ -917,14 +905,14 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo } } else if (Array.isArray(exports)) { - return forEach(exports, e => tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, e, conditions, mode, isImports)); + return forEach(exports, e => tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, e, conditions, mode, isImports)); } else if (typeof exports === "object" && exports !== null) { // eslint-disable-line no-null/no-null // conditional mapping for (const key of getOwnKeys(exports as MapLike)) { if (key === "default" || conditions.indexOf(key) >= 0 || isApplicableVersionedTypesKey(conditions, key)) { const subTarget = (exports as MapLike)[key]; - const result = tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode, isImports); + const result = tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, subTarget, conditions, mode, isImports); if (result) { return result; } @@ -934,7 +922,7 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo return undefined; } -function tryGetModuleNameFromExports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[]): { moduleFileToTry: string; } | undefined { +function tryGetModuleNameFromExports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[]): { moduleFileToTry: string; } | undefined { if (typeof exports === "object" && exports !== null && !Array.isArray(exports) && allKeysStartWithDot(exports as MapLike)) { // eslint-disable-line no-null/no-null // sub-mappings // 3 cases: @@ -946,13 +934,13 @@ function tryGetModuleNameFromExports(options: CompilerOptions, host: ModuleSpeci const mode = endsWith(k, "/") ? MatchingMode.Directory : k.includes("*") ? MatchingMode.Pattern : MatchingMode.Exact; - return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode, /*isImports*/ false); + return tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, subPackageName, (exports as MapLike)[k], conditions, mode, /*isImports*/ false); }); } - return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact, /*isImports*/ false); + return tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact, /*isImports*/ false); } -function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, getCanonicalFileName: GetCanonicalFileName, importMode: ResolutionMode) { +function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode) { if (!host.readFile || !getResolvePackageJsonImports(options)) { return undefined; } @@ -977,7 +965,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi const mode = endsWith(k, "/") ? MatchingMode.Directory : k.includes("*") ? MatchingMode.Pattern : MatchingMode.Exact; - return tryGetModuleNameFromExportsOrImports(options, host, getCanonicalFileName, moduleFileName, ancestorDirectoryWithPackageJson, k, (imports as MapLike)[k], conditions, mode, /*isImports*/ true); + return tryGetModuleNameFromExportsOrImports(options, host, moduleFileName, ancestorDirectoryWithPackageJson, k, (imports as MapLike)[k], conditions, mode, /*isImports*/ true); })?.moduleFileToTry; } @@ -1078,7 +1066,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); const conditions = getConditions(options, importMode); const fromExports = packageJsonContent.exports - ? tryGetModuleNameFromExports(options, host, getCanonicalFileName, path, packageRootPath, packageName, packageJsonContent.exports, conditions) + ? tryGetModuleNameFromExports(options, host, path, packageRootPath, packageName, packageJsonContent.exports, conditions) : undefined; if (fromExports) { return { ...fromExports, verbatimFromExports: true }; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 927720e580da8..e05302d1c381e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -9671,6 +9671,7 @@ export interface ModuleSpecifierResolutionHost { getProjectReferenceRedirect(fileName: string): string | undefined; isSourceOfProjectReferenceRedirect(fileName: string): boolean; getFileIncludeReasons(): MultiMap; + getCommonSourceDirectory(): string; } /** @internal */ diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b14d16bd671c2..775d82244411b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -30,6 +30,7 @@ import { ConditionalExpression, contains, ContextFlags, + createGetCanonicalFileName, createPrinterWithRemoveCommentsOmitTrailingSemicolon, createRange, createScanner, @@ -88,6 +89,7 @@ import { FunctionLikeDeclaration, getAssignmentDeclarationKind, getCombinedNodeFlagsAlwaysIncludeJSDoc, + getCommonSourceDirectory, getDirectoryPath, getEmitModuleKind, getEmitScriptTarget, @@ -102,6 +104,7 @@ import { getModuleInstanceState, getNameOfDeclaration, getNodeId, + getNormalizedAbsolutePath, getPackageNameFromTypesPackageName, getPathComponents, getRootDeclaration, @@ -270,6 +273,7 @@ import { LiteralExpression, map, maybeBind, + memoize, Modifier, ModifierFlags, ModuleDeclaration, @@ -2470,6 +2474,18 @@ export function createModuleSpecifierResolutionHost(program: Program, host: Lang isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), getFileIncludeReasons: () => program.getFileIncludeReasons(), + getCommonSourceDirectory: memoize(() => { + const currentDirectory = host.getCurrentDirectory(); + return getNormalizedAbsolutePath( + getCommonSourceDirectory( + program.getCompilerOptions(), + () => [], + currentDirectory, + createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true), + ), + currentDirectory, + ); + }), }; } From c4df857167643ae842fb7337361a43464bd68583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 19:43:03 +0100 Subject: [PATCH 15/26] Reuse `getCommonSourceDirectory` available in the `Program` --- src/services/utilities.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 775d82244411b..ab0c97e86c706 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -30,7 +30,6 @@ import { ConditionalExpression, contains, ContextFlags, - createGetCanonicalFileName, createPrinterWithRemoveCommentsOmitTrailingSemicolon, createRange, createScanner, @@ -89,7 +88,6 @@ import { FunctionLikeDeclaration, getAssignmentDeclarationKind, getCombinedNodeFlagsAlwaysIncludeJSDoc, - getCommonSourceDirectory, getDirectoryPath, getEmitModuleKind, getEmitScriptTarget, @@ -104,7 +102,6 @@ import { getModuleInstanceState, getNameOfDeclaration, getNodeId, - getNormalizedAbsolutePath, getPackageNameFromTypesPackageName, getPathComponents, getRootDeclaration, @@ -273,7 +270,6 @@ import { LiteralExpression, map, maybeBind, - memoize, Modifier, ModifierFlags, ModuleDeclaration, @@ -2474,18 +2470,7 @@ export function createModuleSpecifierResolutionHost(program: Program, host: Lang isSourceOfProjectReferenceRedirect: fileName => program.isSourceOfProjectReferenceRedirect(fileName), getNearestAncestorDirectoryWithPackageJson: maybeBind(host, host.getNearestAncestorDirectoryWithPackageJson), getFileIncludeReasons: () => program.getFileIncludeReasons(), - getCommonSourceDirectory: memoize(() => { - const currentDirectory = host.getCurrentDirectory(); - return getNormalizedAbsolutePath( - getCommonSourceDirectory( - program.getCompilerOptions(), - () => [], - currentDirectory, - createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true), - ), - currentDirectory, - ); - }), + getCommonSourceDirectory: () => program.getCommonSourceDirectory(), }; } From 566e543d7b852e20a3f4c0c88217467c62837870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 20:31:56 +0100 Subject: [PATCH 16/26] support declaration remapping --- src/compiler/moduleSpecifiers.ts | 37 +- .../autoImportProvider_importsMap5.js | 341 ++++++++++++++++++ .../server/autoImportProvider_importsMap5.ts | 30 ++ 3 files changed, 404 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap5.js create mode 100644 tests/cases/fourslash/server/autoImportProvider_importsMap5.ts diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index fe58f18368e8e..fd6708230b3cd 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -36,6 +36,7 @@ import { getBaseFileName, GetCanonicalFileName, getConditions, + getDeclarationEmitExtensionForPath, getDirectoryPath, getEmitModuleResolutionKind, getModeForResolutionAtIndex, @@ -846,28 +847,48 @@ const enum MatchingMode { Pattern, } -function getOutputPath(targetFilePath: string, options: CompilerOptions, commonDir: string) { +function getOutputPath(targetFilePath: string, options: CompilerOptions, commonSourceDirectory: string) { return changeExtension( options.outDir ? resolvePath( options.outDir, - getRelativePathFromDirectory(commonDir, targetFilePath, /*ignoreCase*/ false), + getRelativePathFromDirectory(commonSourceDirectory, targetFilePath, /*ignoreCase*/ false), ) : targetFilePath, getOutputExtension(targetFilePath, options), ); } +function getOutputDeclarationPath(targetFilePath: string, options: CompilerOptions, commonSourceDirectory: string) { + const declarationDir = options.declarationDir || options.outDir; + return changeExtension( + declarationDir ? + resolvePath( + declarationDir, + getRelativePathFromDirectory(commonSourceDirectory, targetFilePath, /*ignoreCase*/ false), + ) : + targetFilePath, + getDeclarationEmitExtensionForPath(targetFilePath), + ); +} + function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode, isImports: boolean): { moduleFileToTry: string; } | undefined { if (typeof exports === "string") { - const outputFile = isImports && getOutputPath(targetFilePath, options, host.getCommonSourceDirectory()); + const commonSourceDirectory = host.getCommonSourceDirectory(); + const outputFile = isImports && getOutputPath(targetFilePath, options, commonSourceDirectory); + const declarationFile = isImports && getOutputDeclarationPath(targetFilePath, options, commonSourceDirectory); const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; switch (mode) { case MatchingMode.Exact: - if (outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || (extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo)) { + if ( + outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || + declarationFile && comparePaths(declarationFile, pathOrPattern) === Comparison.EqualTo || + extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo || + comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo + ) { return { moduleFileToTry: packageName }; } break; @@ -876,6 +897,10 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo const fragment = getRelativePathFromDirectory(pathOrPattern, outputFile, /*ignoreCase*/ false); return { moduleFileToTry: combinePaths(packageName, fragment) }; } + if (declarationFile && containsPath(pathOrPattern, declarationFile)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, declarationFile, /*ignoreCase*/ false); + return { moduleFileToTry: combinePaths(packageName, fragment) }; + } if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget)) { const fragment = getRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; @@ -893,6 +918,10 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo const starReplacement = outputFile.slice(leadingSlice.length, outputFile.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } + if (declarationFile && startsWith(declarationFile, leadingSlice) && endsWith(declarationFile, trailingSlice)) { + const starReplacement = declarationFile.slice(leadingSlice.length, declarationFile.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; diff --git a/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap5.js b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap5.js new file mode 100644 index 0000000000000..9440f3f24757c --- /dev/null +++ b/tests/baselines/reference/tsserver/fourslashServer/autoImportProvider_importsMap5.js @@ -0,0 +1,341 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Info seq [hh:mm:ss:mss] Provided types map file "/typesMap.json" doesn't exist +//// [/lib.d.ts] +lib.d.ts-Text + +//// [/lib.decorators.d.ts] +lib.decorators.d.ts-Text + +//// [/lib.decorators.legacy.d.ts] +lib.decorators.legacy.d.ts-Text + +//// [/package.json] +{ + "type": "module", + "imports": { + "#is-browser": { + "types": "./types/env/browser.d.ts", + "default": "./not-dist-on-purpose/env/browser.js" + } + } +} + +//// [/src/a.ts] +isBrowser + +//// [/src/env/browser.ts] +export const isBrowser = true; + +//// [/tsconfig.json] +{ + "compilerOptions": { + "module": "nodenext", + "rootDir": "src", + "outDir": "dist", + "declarationDir": "types", + } +} + + +Info seq [hh:mm:ss:mss] request: + { + "seq": 0, + "type": "request", + "arguments": { + "file": "/tsconfig.json" + }, + "command": "open" + } +Info seq [hh:mm:ss:mss] Search path: / +Info seq [hh:mm:ss:mss] For info: /tsconfig.json :: Config file name: /tsconfig.json +Info seq [hh:mm:ss:mss] Creating configuration project /tsconfig.json +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /tsconfig.json 2000 undefined Project: /tsconfig.json WatchType: Config file +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingStart", + "body": { + "projectName": "/tsconfig.json", + "reason": "Creating possible configured project for /tsconfig.json to open" + } + } +Info seq [hh:mm:ss:mss] Config: /tsconfig.json : { + "rootNames": [ + "/lib.d.ts", + "/lib.decorators.d.ts", + "/lib.decorators.legacy.d.ts", + "/src/a.ts", + "/src/env/browser.ts" + ], + "options": { + "module": 199, + "rootDir": "/src", + "outDir": "/dist", + "declarationDir": "/types", + "configFilePath": "/tsconfig.json" + } +} +Info seq [hh:mm:ss:mss] DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Added:: WatchInfo: 1 undefined Config: /tsconfig.json WatchType: Wild card directory +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /lib.decorators.legacy.d.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/a.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /src/env/browser.ts 500 undefined WatchType: Closed Script info +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /tsconfig.json +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /tsconfig.json Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /lib.d.ts Text-1 lib.d.ts-Text + /src/a.ts Text-1 "isBrowser" + /src/env/browser.ts Text-1 "export const isBrowser = true;" + + + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + Matched by default include pattern '**/*' + lib.d.ts + Matched by default include pattern '**/*' + src/a.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + src/env/browser.ts + Matched by default include pattern '**/*' + File is ECMAScript module because 'package.json' has field "type" with value "module" + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "projectLoadingFinish", + "body": { + "projectName": "/tsconfig.json" + } + } +Info seq [hh:mm:ss:mss] event: + { + "seq": 0, + "type": "event", + "event": "configFileDiag", + "body": { + "triggerFile": "/tsconfig.json", + "configFile": "/tsconfig.json", + "diagnostics": [ + { + "start": { + "line": 6, + "offset": 5 + }, + "end": { + "line": 6, + "offset": 21 + }, + "text": "Option 'declarationDir' cannot be specified without specifying option 'declaration' or option 'composite'.", + "code": 5069, + "category": "error", + "fileName": "/tsconfig.json" + } + ] + } + } +Info seq [hh:mm:ss:mss] Starting updateGraphWorker: Project: /dev/null/inferredProject1* +Info seq [hh:mm:ss:mss] Finishing updateGraphWorker: Project: /dev/null/inferredProject1* Version: 1 structureChanged: true structureIsReused:: Not Elapsed:: *ms +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + /lib.d.ts Text-1 lib.d.ts-Text + /lib.decorators.d.ts Text-1 lib.decorators.d.ts-Text + /lib.decorators.legacy.d.ts Text-1 lib.decorators.legacy.d.ts-Text + /tsconfig.json SVC-1-0 "{\n \"compilerOptions\": {\n \"module\": \"nodenext\",\n \"rootDir\": \"src\",\n \"outDir\": \"dist\",\n \"declarationDir\": \"types\",\n }\n}" + + + lib.d.ts + Default library for target 'es5' + lib.decorators.d.ts + Library referenced via 'decorators' from file 'lib.d.ts' + lib.decorators.legacy.d.ts + Library referenced via 'decorators.legacy' from file 'lib.d.ts' + tsconfig.json + Root file specified for compilation + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] FileWatcher:: Added:: WatchInfo: /package.json 250 undefined WatchType: package.json file +Info seq [hh:mm:ss:mss] Project '/tsconfig.json' (Configured) +Info seq [hh:mm:ss:mss] Files (5) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Project '/dev/null/inferredProject1*' (Inferred) +Info seq [hh:mm:ss:mss] Files (4) + +Info seq [hh:mm:ss:mss] ----------------------------------------------- +Info seq [hh:mm:ss:mss] Open files: +Info seq [hh:mm:ss:mss] FileName: /tsconfig.json ProjectRootPath: undefined +Info seq [hh:mm:ss:mss] Projects: /dev/null/inferredProject1* +After Request +watchedFiles:: +/lib.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.d.ts: *new* + {"pollingInterval":500} +/lib.decorators.legacy.d.ts: *new* + {"pollingInterval":500} +/package.json: *new* + {"pollingInterval":250} +/src/a.ts: *new* + {"pollingInterval":500} +/src/env/browser.ts: *new* + {"pollingInterval":500} +/tsconfig.json: *new* + {"pollingInterval":2000} + +watchedDirectoriesRecursive:: +: *new* + {} + +Info seq [hh:mm:ss:mss] request: + { + "seq": 1, + "type": "request", + "arguments": { + "preferences": { + "includeCompletionsForModuleExports": true, + "includeCompletionsWithInsertText": true + } + }, + "command": "configure" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "configure", + "request_seq": 1, + "success": true + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 2, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "syntacticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "syntacticDiagnosticsSync", + "request_seq": 2, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 3, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "semanticDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "semanticDiagnosticsSync", + "request_seq": 3, + "success": true, + "body": [ + { + "message": "Cannot find name 'isBrowser'.", + "start": 0, + "length": 9, + "category": "error", + "code": 2304, + "startLocation": { + "line": 1, + "offset": 1 + }, + "endLocation": { + "line": 1, + "offset": 10 + } + } + ] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 4, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "includeLinePosition": true + }, + "command": "suggestionDiagnosticsSync" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "suggestionDiagnosticsSync", + "request_seq": 4, + "success": true, + "body": [] + } +Info seq [hh:mm:ss:mss] request: + { + "seq": 5, + "type": "request", + "arguments": { + "file": "/src/a.ts", + "startLine": 1, + "startOffset": 1, + "endLine": 1, + "endOffset": 10, + "errorCodes": [ + 2304 + ] + }, + "command": "getCodeFixes" + } +Info seq [hh:mm:ss:mss] response: + { + "seq": 0, + "type": "response", + "command": "getCodeFixes", + "request_seq": 5, + "success": true, + "body": [ + { + "fixName": "import", + "description": "Add import from \"#is-browser\"", + "changes": [ + { + "fileName": "/src/a.ts", + "textChanges": [ + { + "start": { + "line": 1, + "offset": 1 + }, + "end": { + "line": 1, + "offset": 1 + }, + "newText": "import { isBrowser } from \"#is-browser\";\r\n\r\n" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/tests/cases/fourslash/server/autoImportProvider_importsMap5.ts b/tests/cases/fourslash/server/autoImportProvider_importsMap5.ts new file mode 100644 index 0000000000000..6f360f6b3bf51 --- /dev/null +++ b/tests/cases/fourslash/server/autoImportProvider_importsMap5.ts @@ -0,0 +1,30 @@ +/// + +// @Filename: /tsconfig.json +//// { +//// "compilerOptions": { +//// "module": "nodenext", +//// "rootDir": "src", +//// "outDir": "dist", +//// "declarationDir": "types", +//// } +//// } + +// @Filename: /package.json +//// { +//// "type": "module", +//// "imports": { +//// "#is-browser": { +//// "types": "./types/env/browser.d.ts", +//// "default": "./not-dist-on-purpose/env/browser.js" +//// } +//// } +//// } + +// @Filename: /src/env/browser.ts +//// export const isBrowser = true; + +// @Filename: /src/a.ts +//// isBrowser/**/ + +verify.importFixModuleSpecifiers("", ["#is-browser"]); \ No newline at end of file From 1f9f21420766a1aa1d4e611b304c19760496283f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 21:31:56 +0100 Subject: [PATCH 17/26] reprioritize checks --- src/compiler/moduleSpecifiers.ts | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index fd6708230b3cd..8c53f85ad275e 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -884,23 +884,15 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo switch (mode) { case MatchingMode.Exact: if ( - outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || - declarationFile && comparePaths(declarationFile, pathOrPattern) === Comparison.EqualTo || extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo || - comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo + comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || + outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || + declarationFile && comparePaths(declarationFile, pathOrPattern) === Comparison.EqualTo ) { return { moduleFileToTry: packageName }; } break; case MatchingMode.Directory: - if (outputFile && containsPath(pathOrPattern, outputFile)) { - const fragment = getRelativePathFromDirectory(pathOrPattern, outputFile, /*ignoreCase*/ false); - return { moduleFileToTry: combinePaths(packageName, fragment) }; - } - if (declarationFile && containsPath(pathOrPattern, declarationFile)) { - const fragment = getRelativePathFromDirectory(pathOrPattern, declarationFile, /*ignoreCase*/ false); - return { moduleFileToTry: combinePaths(packageName, fragment) }; - } if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget)) { const fragment = getRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; @@ -909,19 +901,19 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; } + if (outputFile && containsPath(pathOrPattern, outputFile)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, outputFile, /*ignoreCase*/ false); + return { moduleFileToTry: combinePaths(packageName, fragment) }; + } + if (declarationFile && containsPath(pathOrPattern, declarationFile)) { + const fragment = getRelativePathFromDirectory(pathOrPattern, declarationFile, /*ignoreCase*/ false); + return { moduleFileToTry: combinePaths(packageName, fragment) }; + } break; case MatchingMode.Pattern: const starPos = pathOrPattern.indexOf("*"); const leadingSlice = pathOrPattern.slice(0, starPos); const trailingSlice = pathOrPattern.slice(starPos + 1); - if (outputFile && startsWith(outputFile, leadingSlice) && endsWith(outputFile, trailingSlice)) { - const starReplacement = outputFile.slice(leadingSlice.length, outputFile.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } - if (declarationFile && startsWith(declarationFile, leadingSlice) && endsWith(declarationFile, trailingSlice)) { - const starReplacement = declarationFile.slice(leadingSlice.length, declarationFile.length - trailingSlice.length); - return { moduleFileToTry: packageName.replace("*", starReplacement) }; - } if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; @@ -930,6 +922,14 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } + if (outputFile && startsWith(outputFile, leadingSlice) && endsWith(outputFile, trailingSlice)) { + const starReplacement = outputFile.slice(leadingSlice.length, outputFile.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } + if (declarationFile && startsWith(declarationFile, leadingSlice) && endsWith(declarationFile, trailingSlice)) { + const starReplacement = declarationFile.slice(leadingSlice.length, declarationFile.length - trailingSlice.length); + return { moduleFileToTry: packageName.replace("*", starReplacement) }; + } break; } } From 7a5c1644ff6132260db49b7c25d7b92829050631 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 15 Nov 2023 12:29:56 -0800 Subject: [PATCH 18/26] Deduplicate some emitter code --- src/compiler/emitter.ts | 33 ++++++++++++++++++++++-------- src/compiler/moduleSpecifiers.ts | 35 +++++++------------------------- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 463651bdb0049..090e46b2fa23c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -582,35 +582,50 @@ export function getOutputExtension(fileName: string, options: CompilerOptions): Extension.Js; } -function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) { +function getOutputPathWithoutChangingExt( + inputFileName: string, + ignoreCase: boolean, + outputDir: string | undefined, + getCommonSourceDirectory: () => string, +): string { return outputDir ? resolvePath( outputDir, - getRelativePathFromDirectory(getCommonSourceDirectory ? getCommonSourceDirectory() : getCommonSourceDirectoryOfConfig(configFile, ignoreCase), inputFileName, ignoreCase), + getRelativePathFromDirectory(getCommonSourceDirectory(), inputFileName, ignoreCase), ) : inputFileName; } /** @internal */ -export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { +export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory = () => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)) { + return getOutputDeclarationFileNameWorker(inputFileName, configFile.options, ignoreCase, getCommonSourceDirectory); +} + +/** @internal */ +export function getOutputDeclarationFileNameWorker(inputFileName: string, options: CompilerOptions, ignoreCase: boolean, getCommonSourceDirectory: () => string) { return changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory), + getOutputPathWithoutChangingExt(inputFileName, ignoreCase, options.declarationDir || options.outDir, getCommonSourceDirectory), getDeclarationEmitExtensionForPath(inputFileName), ); } -function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) { +function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory = () => getCommonSourceDirectoryOfConfig(configFile, ignoreCase)) { if (configFile.options.emitDeclarationOnly) return undefined; const isJsonFile = fileExtensionIs(inputFileName, Extension.Json); - const outputFileName = changeExtension( - getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory), - getOutputExtension(inputFileName, configFile.options), - ); + const outputFileName = getOutputJSFileNameWorker(inputFileName, configFile.options, ignoreCase, getCommonSourceDirectory); return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ? outputFileName : undefined; } +/** @internal */ +export function getOutputJSFileNameWorker(inputFileName: string, options: CompilerOptions, ignoreCase: boolean, getCommonSourceDirectory: () => string): string { + return changeExtension( + getOutputPathWithoutChangingExt(inputFileName, ignoreCase, options.outDir, getCommonSourceDirectory), + getOutputExtension(inputFileName, options), + ); +} + function createAddOutput() { let outputs: string[] | undefined; return { addOutput, getOutputs }; diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 8c53f85ad275e..2f269928589b8 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -44,7 +44,9 @@ import { getModuleSpecifierEndingPreference, getNodeModulePathParts, getNormalizedAbsolutePath, + getOutputDeclarationFileNameWorker, getOutputExtension, + getOutputJSFileNameWorker, getOwnKeys, getPackageJsonTypesVersionsPaths, getPackageNameFromTypesPackageName, @@ -59,6 +61,7 @@ import { hasJSFileExtension, hasTSFileExtension, hostGetCanonicalFileName, + hostUsesCaseSensitiveFileNames, Identifier, isAmbientModule, isApplicableVersionedTypesKey, @@ -847,36 +850,12 @@ const enum MatchingMode { Pattern, } -function getOutputPath(targetFilePath: string, options: CompilerOptions, commonSourceDirectory: string) { - return changeExtension( - options.outDir ? - resolvePath( - options.outDir, - getRelativePathFromDirectory(commonSourceDirectory, targetFilePath, /*ignoreCase*/ false), - ) : - targetFilePath, - getOutputExtension(targetFilePath, options), - ); -} - -function getOutputDeclarationPath(targetFilePath: string, options: CompilerOptions, commonSourceDirectory: string) { - const declarationDir = options.declarationDir || options.outDir; - return changeExtension( - declarationDir ? - resolvePath( - declarationDir, - getRelativePathFromDirectory(commonSourceDirectory, targetFilePath, /*ignoreCase*/ false), - ) : - targetFilePath, - getDeclarationEmitExtensionForPath(targetFilePath), - ); -} - function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: ModuleSpecifierResolutionHost, targetFilePath: string, packageDirectory: string, packageName: string, exports: unknown, conditions: string[], mode: MatchingMode, isImports: boolean): { moduleFileToTry: string; } | undefined { if (typeof exports === "string") { - const commonSourceDirectory = host.getCommonSourceDirectory(); - const outputFile = isImports && getOutputPath(targetFilePath, options, commonSourceDirectory); - const declarationFile = isImports && getOutputDeclarationPath(targetFilePath, options, commonSourceDirectory); + const ignoreCase = !hostUsesCaseSensitiveFileNames(host); + const getCommonSourceDirectory = () => host.getCommonSourceDirectory(); + const outputFile = isImports && getOutputJSFileNameWorker(targetFilePath, options, ignoreCase, getCommonSourceDirectory); + const declarationFile = isImports && getOutputDeclarationFileNameWorker(targetFilePath, options, ignoreCase, getCommonSourceDirectory); const pathOrPattern = getNormalizedAbsolutePath(combinePaths(packageDirectory, exports), /*currentDirectory*/ undefined); const extensionSwappedTarget = hasTSFileExtension(targetFilePath) ? removeFileExtension(targetFilePath) + tryGetJSExtensionForFile(targetFilePath, options) : undefined; From 45194b22773ceb5ec1fc2838b1a478e65f1bb920 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 15 Nov 2023 12:30:47 -0800 Subject: [PATCH 19/26] Fix lints --- src/compiler/moduleSpecifiers.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 2f269928589b8..5b2d89d61d1ba 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -4,7 +4,6 @@ import { AmbientModuleDeclaration, append, arrayFrom, - changeExtension, CharacterCodes, combinePaths, compareBooleans, @@ -36,7 +35,6 @@ import { getBaseFileName, GetCanonicalFileName, getConditions, - getDeclarationEmitExtensionForPath, getDirectoryPath, getEmitModuleResolutionKind, getModeForResolutionAtIndex, @@ -45,7 +43,6 @@ import { getNodeModulePathParts, getNormalizedAbsolutePath, getOutputDeclarationFileNameWorker, - getOutputExtension, getOutputJSFileNameWorker, getOwnKeys, getPackageJsonTypesVersionsPaths, From 8f2d0c3a8f9b2fda48d0c81125ff840c0818bf9a Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 15 Nov 2023 12:46:25 -0800 Subject: [PATCH 20/26] Add failing test for when full path contains capital letters --- .../autoImportPackageJsonImports_capsInPath.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts diff --git a/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts b/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts new file mode 100644 index 0000000000000..81b698ebd4d4c --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /Dev/package.json +//// { +//// "imports": { +//// "#thing": "./src/something.js" +//// } +//// } + +// @Filename: /Dev/src/something.ts +//// export function something(name: string): any; + +// @Filename: /Dev/a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#thing"]); From 8bef6902e3c62abea0cf4777eff8b548556d83c4 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Wed, 15 Nov 2023 13:53:02 -0800 Subject: [PATCH 21/26] Move tryParseJson to be usable in compiler --- src/compiler/moduleSpecifiers.ts | 24 ++++++++---------------- src/compiler/utilities.ts | 10 ++++++++++ src/services/utilities.ts | 9 --------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 5b2d89d61d1ba..09c5b3a65c4a5 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -110,20 +110,12 @@ import { SymbolFlags, toPath, tryGetExtensionFromPath, + tryParseJson, tryParsePatterns, TypeChecker, UserPreferences, } from "./_namespaces/ts"; -function safeJsonRead(packageJsonPath: string, readFile: (path: string) => string | undefined) { - try { - return JSON.parse(readFile(packageJsonPath)!); - } - catch { - return {}; - } -} - // Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers. const enum RelativePreference { @@ -959,7 +951,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { return undefined; } - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || safeJsonRead(packageJsonPath, host.readFile); + const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || tryParseJson(host.readFile(packageJsonPath)!); const imports = packageJsonContent?.imports; if (!imports) { return undefined; @@ -1061,7 +1053,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan let maybeBlockedByTypesVersions = false; const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); if (typeof cachedPackageJson === "object" || cachedPackageJson === undefined && host.fileExists(packageJsonPath)) { - const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || safeJsonRead(packageJsonPath, host.readFile!); + const packageJsonContent: Record | undefined = cachedPackageJson?.contents.packageJsonContent || tryParseJson(host.readFile!(packageJsonPath)!); const importMode = overrideMode || importingSourceFile.impliedNodeFormat; if (getResolvePackageJsonExports(options)) { // The package name that we found in node_modules could be different from the package @@ -1070,17 +1062,17 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan const nodeModulesDirectoryName = packageRootPath.substring(parts.topLevelPackageNameIndex + 1); const packageName = getPackageNameFromTypesPackageName(nodeModulesDirectoryName); const conditions = getConditions(options, importMode); - const fromExports = packageJsonContent.exports + const fromExports = packageJsonContent?.exports ? tryGetModuleNameFromExports(options, host, path, packageRootPath, packageName, packageJsonContent.exports, conditions) : undefined; if (fromExports) { return { ...fromExports, verbatimFromExports: true }; } - if (packageJsonContent.exports) { + if (packageJsonContent?.exports) { return { moduleFileToTry: path, blockedByExports: true }; } } - const versionPaths = packageJsonContent.typesVersions + const versionPaths = packageJsonContent?.typesVersions ? getPackageJsonTypesVersionsPaths(packageJsonContent.typesVersions) : undefined; if (versionPaths) { @@ -1100,7 +1092,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan } } // If the file is the main module, it can be imported by the package name - const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main || "index.js"; + const mainFileRelative = packageJsonContent?.typings || packageJsonContent?.types || packageJsonContent?.main || "index.js"; if (isString(mainFileRelative) && !(maybeBlockedByTypesVersions && matchPatternOrExact(tryParsePatterns(versionPaths!.paths), mainFileRelative))) { // The 'main' file is also subject to mapping through typesVersions, and we couldn't come up with a path // explicitly through typesVersions, so if it matches a key in typesVersions now, it's not reachable. @@ -1115,7 +1107,7 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan return { packageRootPath, moduleFileToTry }; } else if ( - packageJsonContent.type !== "module" && + packageJsonContent?.type !== "module" && !fileExtensionIsOneOf(canonicalModuleFileToTry, extensionsNotSupportingExtensionlessResolution) && startsWith(canonicalModuleFileToTry, mainExportFile) && getDirectoryPath(canonicalModuleFileToTry) === removeTrailingDirectorySeparator(mainExportFile) && diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ac4e6cb95d575..c4e42dbb98522 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7470,6 +7470,16 @@ export function readJson(path: string, host: { readFile(fileName: string): strin return readJsonOrUndefined(path, host) || {}; } +/** @internal */ +export function tryParseJson(text: string) { + try { + return JSON.parse(text); + } + catch { + return undefined; + } +} + /** @internal */ export function directoryProbablyExists(directoryName: string, host: { directoryExists?: (directoryName: string) => boolean; }): boolean { // if host does not support 'directoryExists' assume that directory will exist diff --git a/src/services/utilities.ts b/src/services/utilities.ts index ab0c97e86c706..0ba09dd2eee72 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3886,15 +3886,6 @@ export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: } } -function tryParseJson(text: string) { - try { - return JSON.parse(text); - } - catch { - return undefined; - } -} - /** @internal */ export function consumesNodeCoreModules(sourceFile: SourceFile): boolean { return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text)); From cd841621ffd246c35f6e1e6996281d1c4e382322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 15 Nov 2023 23:57:50 +0100 Subject: [PATCH 22/26] import `tryParseJson` after it got moved --- src/services/utilities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 0ba09dd2eee72..90adc11914176 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -359,6 +359,7 @@ import { tokenToString, toPath, tryCast, + tryParseJson, Type, TypeChecker, TypeFlags, From 8d683921abbbb1c6c2cdf181fc517c86b00dae24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 16 Nov 2023 00:31:57 +0100 Subject: [PATCH 23/26] Fixed casing comparisons in `MatchingMode.Exact` and `MatchingMode.Directory` cases --- src/compiler/moduleSpecifiers.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index 09c5b3a65c4a5..bc41a0b3319dc 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -852,28 +852,28 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo switch (mode) { case MatchingMode.Exact: if ( - extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern) === Comparison.EqualTo || - comparePaths(targetFilePath, pathOrPattern) === Comparison.EqualTo || - outputFile && comparePaths(outputFile, pathOrPattern) === Comparison.EqualTo || - declarationFile && comparePaths(declarationFile, pathOrPattern) === Comparison.EqualTo + extensionSwappedTarget && comparePaths(extensionSwappedTarget, pathOrPattern, ignoreCase) === Comparison.EqualTo || + comparePaths(targetFilePath, pathOrPattern, ignoreCase) === Comparison.EqualTo || + outputFile && comparePaths(outputFile, pathOrPattern, ignoreCase) === Comparison.EqualTo || + declarationFile && comparePaths(declarationFile, pathOrPattern, ignoreCase) === Comparison.EqualTo ) { return { moduleFileToTry: packageName }; } break; case MatchingMode.Directory: - if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget)) { + if (extensionSwappedTarget && containsPath(pathOrPattern, extensionSwappedTarget, ignoreCase)) { const fragment = getRelativePathFromDirectory(pathOrPattern, extensionSwappedTarget, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; } - if (containsPath(pathOrPattern, targetFilePath)) { + if (containsPath(pathOrPattern, targetFilePath, ignoreCase)) { const fragment = getRelativePathFromDirectory(pathOrPattern, targetFilePath, /*ignoreCase*/ false); return { moduleFileToTry: getNormalizedAbsolutePath(combinePaths(combinePaths(packageName, exports), fragment), /*currentDirectory*/ undefined) }; } - if (outputFile && containsPath(pathOrPattern, outputFile)) { + if (outputFile && containsPath(pathOrPattern, outputFile, ignoreCase)) { const fragment = getRelativePathFromDirectory(pathOrPattern, outputFile, /*ignoreCase*/ false); return { moduleFileToTry: combinePaths(packageName, fragment) }; } - if (declarationFile && containsPath(pathOrPattern, declarationFile)) { + if (declarationFile && containsPath(pathOrPattern, declarationFile, ignoreCase)) { const fragment = getRelativePathFromDirectory(pathOrPattern, declarationFile, /*ignoreCase*/ false); return { moduleFileToTry: combinePaths(packageName, fragment) }; } From a9634edf0afe290729099e9008585132b1b39f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 16 Nov 2023 10:02:59 +0100 Subject: [PATCH 24/26] fixed the casing issue in the `MatchingMode.Pattern` case --- src/compiler/core.ts | 14 ++++++++++---- src/compiler/moduleSpecifiers.ts | 8 ++++---- ...utoImportPackageJsonImports_capsInPath1.ts} | 0 ...autoImportPackageJsonImports_capsInPath2.ts | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 8 deletions(-) rename tests/cases/fourslash/{autoImportPackageJsonImports_capsInPath.ts => autoImportPackageJsonImports_capsInPath1.ts} (100%) create mode 100644 tests/cases/fourslash/autoImportPackageJsonImports_capsInPath2.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 21eae040ba922..49839661084c1 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2400,9 +2400,13 @@ function levenshteinWithMax(s1: string, s2: string, max: number): number | undef } /** @internal */ -export function endsWith(str: string, suffix: string): boolean { +export function endsWith(str: string, suffix: string, ignoreCase?: boolean): boolean { const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; + return expectedPos >= 0 && ( + ignoreCase + ? equateStringsCaseInsensitive(str.slice(-suffix.length), suffix) + : str.indexOf(suffix, expectedPos) === expectedPos + ); } /** @internal */ @@ -2579,8 +2583,10 @@ export function findBestPatternMatch(values: readonly T[], getPattern: (value } /** @internal */ -export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; +export function startsWith(str: string, prefix: string, ignoreCase?: boolean): boolean { + return ignoreCase + ? equateStringsCaseInsensitive(str.slice(0, prefix.length), prefix) + : str.lastIndexOf(prefix, 0) === 0; } /** @internal */ diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index bc41a0b3319dc..fdb37c74407cc 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -882,19 +882,19 @@ function tryGetModuleNameFromExportsOrImports(options: CompilerOptions, host: Mo const starPos = pathOrPattern.indexOf("*"); const leadingSlice = pathOrPattern.slice(0, starPos); const trailingSlice = pathOrPattern.slice(starPos + 1); - if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice) && endsWith(extensionSwappedTarget, trailingSlice)) { + if (extensionSwappedTarget && startsWith(extensionSwappedTarget, leadingSlice, ignoreCase) && endsWith(extensionSwappedTarget, trailingSlice, ignoreCase)) { const starReplacement = extensionSwappedTarget.slice(leadingSlice.length, extensionSwappedTarget.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } - if (startsWith(targetFilePath, leadingSlice) && endsWith(targetFilePath, trailingSlice)) { + if (startsWith(targetFilePath, leadingSlice, ignoreCase) && endsWith(targetFilePath, trailingSlice, ignoreCase)) { const starReplacement = targetFilePath.slice(leadingSlice.length, targetFilePath.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } - if (outputFile && startsWith(outputFile, leadingSlice) && endsWith(outputFile, trailingSlice)) { + if (outputFile && startsWith(outputFile, leadingSlice, ignoreCase) && endsWith(outputFile, trailingSlice, ignoreCase)) { const starReplacement = outputFile.slice(leadingSlice.length, outputFile.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } - if (declarationFile && startsWith(declarationFile, leadingSlice) && endsWith(declarationFile, trailingSlice)) { + if (declarationFile && startsWith(declarationFile, leadingSlice, ignoreCase) && endsWith(declarationFile, trailingSlice, ignoreCase)) { const starReplacement = declarationFile.slice(leadingSlice.length, declarationFile.length - trailingSlice.length); return { moduleFileToTry: packageName.replace("*", starReplacement) }; } diff --git a/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts b/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath1.ts similarity index 100% rename from tests/cases/fourslash/autoImportPackageJsonImports_capsInPath.ts rename to tests/cases/fourslash/autoImportPackageJsonImports_capsInPath1.ts diff --git a/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath2.ts b/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath2.ts new file mode 100644 index 0000000000000..eb0b0f6838140 --- /dev/null +++ b/tests/cases/fourslash/autoImportPackageJsonImports_capsInPath2.ts @@ -0,0 +1,18 @@ +/// + +// @module: nodenext + +// @Filename: /Dev/package.json +//// { +//// "imports": { +//// "#thing/*": "./src/*.js" +//// } +//// } + +// @Filename: /Dev/src/something.ts +//// export function something(name: string): any; + +// @Filename: /Dev/a.ts +//// something/**/ + +verify.importFixModuleSpecifiers("", ["#thing/something"]); From 4aab191cadef06cc3898d2dbb9d63508688709ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 16 Nov 2023 10:54:30 +0100 Subject: [PATCH 25/26] fixed `endsWith` --- src/compiler/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 49839661084c1..9738920df7527 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2404,7 +2404,7 @@ export function endsWith(str: string, suffix: string, ignoreCase?: boolean): boo const expectedPos = str.length - suffix.length; return expectedPos >= 0 && ( ignoreCase - ? equateStringsCaseInsensitive(str.slice(-suffix.length), suffix) + ? equateStringsCaseInsensitive(str.slice(expectedPos), suffix) : str.indexOf(suffix, expectedPos) === expectedPos ); } From 4f29d3ab206dea847da42ec5a90a0a5ff15a834f Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Thu, 21 Dec 2023 09:27:53 -0800 Subject: [PATCH 26/26] Fix bad merge artifacts --- src/compiler/moduleNameResolver.ts | 5 +++++ src/compiler/moduleSpecifiers.ts | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 67f821b09213b..4f128f1e6aa11 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -915,6 +915,11 @@ export function isPackageJsonInfo(entry: PackageJsonInfoCacheEntry | undefined): return !!(entry as PackageJsonInfo | undefined)?.contents; } +/** @internal */ +export function isMissingPackageJsonInfo(entry: PackageJsonInfoCacheEntry | undefined): entry is MissingPackageJsonInfo { + return !!entry && !(entry as PackageJsonInfo).contents; +} + export interface PackageJsonInfoCache { /** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfoCacheEntry | undefined; /** @internal */ setPackageJsonInfo(packageJsonPath: string, info: PackageJsonInfoCacheEntry): void; diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index dc41b926e5e7f..1da0c9e834be3 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -65,6 +65,7 @@ import { isDeclarationFileName, isExternalModuleAugmentation, isExternalModuleNameRelative, + isMissingPackageJsonInfo, isModuleBlock, isModuleDeclaration, isNonGlobalAmbientModule, @@ -948,7 +949,7 @@ function tryGetModuleNameFromExports(options: CompilerOptions, host: ModuleSpeci return tryGetModuleNameFromExportsOrImports(options, host, targetFilePath, packageDirectory, packageName, exports, conditions, MatchingMode.Exact, /*isImports*/ false); } -function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: Path, options: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode) { +function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDirectory: string, options: CompilerOptions, host: ModuleSpecifierResolutionHost, importMode: ResolutionMode) { if (!host.readFile || !getResolvePackageJsonImports(options)) { return undefined; } @@ -959,7 +960,7 @@ function tryGetModuleNameFromPackageJsonImports(moduleFileName: string, sourceDi } const packageJsonPath = combinePaths(ancestorDirectoryWithPackageJson, "package.json"); const cachedPackageJson = host.getPackageJsonInfoCache?.()?.getPackageJsonInfo(packageJsonPath); - if (typeof cachedPackageJson !== "object" && cachedPackageJson !== undefined || !host.fileExists(packageJsonPath)) { + if (isMissingPackageJsonInfo(cachedPackageJson) || !host.fileExists(packageJsonPath)) { return undefined; } const packageJsonContent = cachedPackageJson?.contents.packageJsonContent || tryParseJson(host.readFile(packageJsonPath)!);