From f20a3306777e83ff2ca6ef3baecb4cbed05b0456 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 2 May 2017 16:19:26 -0700 Subject: [PATCH 1/8] add tests --- .../importNameCodeFixNewImportFromAtTypes.ts | 13 +++++++++++++ ...tNameCodeFixNewImportFromAtTypesScopedPackage.ts | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/cases/fourslash/importNameCodeFixNewImportFromAtTypes.ts create mode 100644 tests/cases/fourslash/importNameCodeFixNewImportFromAtTypesScopedPackage.ts diff --git a/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypes.ts b/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypes.ts new file mode 100644 index 00000000000..bca8f716200 --- /dev/null +++ b/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypes.ts @@ -0,0 +1,13 @@ +/// + +//// [|f1/*0*/();|] + +// @Filename: node_modules/@types/myLib/index.d.ts +//// export function f1() {} +//// export var v1 = 5; + +verify.importFixAtPosition([ +`import { f1 } from "myLib"; + +f1();` +]); \ No newline at end of file diff --git a/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypesScopedPackage.ts b/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypesScopedPackage.ts new file mode 100644 index 00000000000..3cf6cf8a32e --- /dev/null +++ b/tests/cases/fourslash/importNameCodeFixNewImportFromAtTypesScopedPackage.ts @@ -0,0 +1,13 @@ +/// + +//// [|f1/*0*/();|] + +// @Filename: node_modules/@types/myLib__scoped/index.d.ts +//// export function f1() {} +//// export var v1 = 5; + +verify.importFixAtPosition([ +`import { f1 } from "@myLib/scoped"; + +f1();` +]); \ No newline at end of file From 5444f3cbd4c33c3c67823c7f8f5f2d3050a90768 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Tue, 2 May 2017 17:19:14 -0700 Subject: [PATCH 2/8] handle @types and scoped @types --- src/services/codefixes/importFixes.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 78c27a1276b..287e504e49f 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -500,6 +500,15 @@ namespace ts.codefix { } relativeFileName = removeFileExtension(relativeFileName); + + if (startsWith(relativeFileName, "@types/")) { + relativeFileName = relativeFileName.substr(/*"@types".length*/ 7); + if (relativeFileName.indexOf("__") !== -1) { + // Double underscores are used in DefinitelyTyped to delimit scoped packages. + relativeFileName = "@" + relativeFileName.replace("__", "/"); + } + } + if (endsWith(relativeFileName, "/index")) { relativeFileName = getDirectoryPath(relativeFileName); } From 5ce32ccbe3ef50101f7d1745a4bf64a7eb7a88ff Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:13:28 -0700 Subject: [PATCH 3/8] factor out helpers + cleanup `startsWith` and friends --- src/compiler/core.ts | 13 +++++++++---- src/compiler/moduleNameResolver.ts | 15 ++++++++++++++- src/services/codefixes/importFixes.ts | 10 ++-------- src/services/pathCompletions.ts | 15 ++++++--------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 321cc06f765..2f945fb7526 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1730,13 +1730,18 @@ namespace ts { /* @internal */ export function startsWith(str: string, prefix: string): boolean { - return str.lastIndexOf(prefix, 0) === 0; + return str.indexOf(prefix) === 0; + } + + /* @internal */ + export function removePrefix(str: string, prefix: string | undefined): string { + return startsWith(str, prefix) ? str.substr(prefix.length) : str; } /* @internal */ export function endsWith(str: string, suffix: string): boolean { const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; + return expectedPos >= 0 && str.lastIndexOf(suffix, expectedPos) === expectedPos; } export function hasExtension(fileName: string): boolean { @@ -1747,7 +1752,7 @@ namespace ts { return path.length > extension.length && endsWith(path, extension); } - export function fileExtensionIsAny(path: string, extensions: string[]): boolean { + export function fileExtensionIsOneOf(path: string, extensions: string[]): boolean { for (const extension of extensions) { if (fileExtensionIs(path, extension)) { return true; @@ -1947,7 +1952,7 @@ namespace ts { for (const current of files) { const name = combinePaths(path, current); const absoluteName = combinePaths(absolutePath, current); - if (extensions && !fileExtensionIsAny(name, extensions)) continue; + if (extensions && !fileExtensionIsOneOf(name, extensions)) continue; if (excludeRegex && excludeRegex.test(absoluteName)) continue; if (!includeFileRegexes) { results[0].push(name); diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index d511b31b331..b1d0f2ab7b2 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -958,10 +958,13 @@ namespace ts { } } + /** Double underscores are used in DefinitelyTyped to delimit scoped packages. */ + const mangledScopedPackageSeparator = "__"; + /** For a scoped package, we must look in `@types/foo__bar` instead of `@types/@foo/bar`. */ function mangleScopedPackage(moduleName: string, state: ModuleResolutionState): string { if (startsWith(moduleName, "@")) { - const replaceSlash = moduleName.replace(ts.directorySeparator, "__"); + const replaceSlash = moduleName.replace(ts.directorySeparator, mangledScopedPackageSeparator); if (replaceSlash !== moduleName) { const mangled = replaceSlash.slice(1); // Take off the "@" if (state.traceEnabled) { @@ -973,6 +976,16 @@ namespace ts { return moduleName; } + export function getPackageNameFromAtTypesDirectory(mangledName: string): string { + const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); + if (withoutAtTypePrefix !== mangledName) { + return withoutAtTypePrefix.indexOf("__") !== -1 ? + "@" + withoutAtTypePrefix.replace(mangledScopedPackageSeparator, ts.directorySeparator) : + withoutAtTypePrefix; + } + return mangledName; + } + function tryFindNonRelativeModuleNameInCache(cache: PerModuleNameCache | undefined, moduleName: string, containingDirectory: string, traceEnabled: boolean, host: ModuleResolutionHost): SearchResult { const result = cache && cache.get(containingDirectory); if (result) { diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 7885fe66c2a..b91ee2a3041 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -501,14 +501,6 @@ namespace ts.codefix { relativeFileName = getRelativePath(moduleFileName, sourceDirectory); } - if (startsWith(relativeFileName, "@types/")) { - relativeFileName = relativeFileName.substr(/*"@types".length*/ 7); - if (relativeFileName.indexOf("__") !== -1) { - // Double underscores are used in DefinitelyTyped to delimit scoped packages. - relativeFileName = "@" + relativeFileName.replace("__", "/"); - } - } - relativeFileName = removeFileExtension(relativeFileName); if (endsWith(relativeFileName, "/index")) { relativeFileName = getDirectoryPath(relativeFileName); @@ -530,6 +522,8 @@ namespace ts.codefix { catch (e) { } } + relativeFileName = getPackageNameFromAtTypesDirectory(relativeFileName); + return relativeFileName; } } diff --git a/src/services/pathCompletions.ts b/src/services/pathCompletions.ts index 2b45457c147..e97c3e0c655 100644 --- a/src/services/pathCompletions.ts +++ b/src/services/pathCompletions.ts @@ -245,18 +245,15 @@ namespace ts.Completions.PathCompletions { // Get modules that the type checker picked up const ambientModules = map(typeChecker.getAmbientModules(), sym => stripQuotes(sym.name)); - let nonRelativeModules = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); + let nonRelativeModuleNames = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); // Nested modules of the form "module-name/sub" need to be adjusted to only return the string // after the last '/' that appears in the fragment because that's where the replacement span // starts if (isNestedModule) { const moduleNameWithSeperator = ensureTrailingDirectorySeparator(moduleNameFragment); - nonRelativeModules = map(nonRelativeModules, moduleName => { - if (startsWith(fragment, moduleNameWithSeperator)) { - return moduleName.substr(moduleNameWithSeperator.length); - } - return moduleName; + nonRelativeModuleNames = map(nonRelativeModuleNames, nonRelativeModuleName => { + return removePrefix(nonRelativeModuleName, moduleNameWithSeperator); }); } @@ -264,7 +261,7 @@ namespace ts.Completions.PathCompletions { if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { if (!isNestedModule) { - nonRelativeModules.push(visibleModule.moduleName); + nonRelativeModuleNames.push(visibleModule.moduleName); } else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/ undefined, /*include*/ ["./*"]); @@ -272,14 +269,14 @@ namespace ts.Completions.PathCompletions { for (let f of nestedFiles) { f = normalizePath(f); const nestedModule = removeFileExtension(getBaseFileName(f)); - nonRelativeModules.push(nestedModule); + nonRelativeModuleNames.push(nestedModule); } } } } } - return deduplicate(nonRelativeModules); + return deduplicate(nonRelativeModuleNames); } export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): CompletionInfo { From 69e7841cda3045a03a596ef0b8506bd3e4e99a69 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:22:01 -0700 Subject: [PATCH 4/8] revert noop change --- 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 2f945fb7526..6de1d62c14e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1741,7 +1741,7 @@ namespace ts { /* @internal */ export function endsWith(str: string, suffix: string): boolean { const expectedPos = str.length - suffix.length; - return expectedPos >= 0 && str.lastIndexOf(suffix, expectedPos) === expectedPos; + return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos; } export function hasExtension(fileName: string): boolean { From d38ee1eaf71fb6f92418ba5c2bf53b7b08366a17 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:26:24 -0700 Subject: [PATCH 5/8] restore `startsWith` --- src/compiler/core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 6de1d62c14e..de560d89d40 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1729,8 +1729,8 @@ namespace ts { } /* @internal */ - export function startsWith(str: string, prefix: string): boolean { - return str.indexOf(prefix) === 0; + export function startsWith(str: string, prefix: string | undefined): boolean { + return str.lastIndexOf(prefix, 0) === 0; } /* @internal */ From 5eff8c206bc9c937564078676990778b694e0486 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:29:24 -0700 Subject: [PATCH 6/8] revert core changes --- src/compiler/core.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index de560d89d40..654e6b00c1a 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1729,12 +1729,12 @@ namespace ts { } /* @internal */ - export function startsWith(str: string, prefix: string | undefined): boolean { + export function startsWith(str: string, prefix: string): boolean { return str.lastIndexOf(prefix, 0) === 0; } /* @internal */ - export function removePrefix(str: string, prefix: string | undefined): string { + export function removePrefix(str: string, prefix: string): string { return startsWith(str, prefix) ? str.substr(prefix.length) : str; } From 0b68b934ef5983314e71e57ed461da7785af4808 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:30:53 -0700 Subject: [PATCH 7/8] respond to comments --- src/services/codefixes/importFixes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index b91ee2a3041..99aaec476cd 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -522,9 +522,7 @@ namespace ts.codefix { catch (e) { } } - relativeFileName = getPackageNameFromAtTypesDirectory(relativeFileName); - - return relativeFileName; + return getPackageNameFromAtTypesDirectory(relativeFileName); } } From 7282b9f0ce4af2b20c39da6f89f41b8ae239ff81 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 14:12:24 -0700 Subject: [PATCH 8/8] make internal --- src/compiler/core.ts | 1 + src/compiler/moduleNameResolver.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 654e6b00c1a..357bd0699e3 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1752,6 +1752,7 @@ namespace ts { return path.length > extension.length && endsWith(path, extension); } + /* @internal */ export function fileExtensionIsOneOf(path: string, extensions: string[]): boolean { for (const extension of extensions) { if (fileExtensionIs(path, extension)) { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index b1d0f2ab7b2..5c2e2dcea8e 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -976,6 +976,7 @@ namespace ts { return moduleName; } + /* @internal */ export function getPackageNameFromAtTypesDirectory(mangledName: string): string { const withoutAtTypePrefix = removePrefix(mangledName, "@types/"); if (withoutAtTypePrefix !== mangledName) {