From 5ce32ccbe3ef50101f7d1745a4bf64a7eb7a88ff Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Wed, 3 May 2017 13:13:28 -0700 Subject: [PATCH] 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 {