-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Clean up code for nonrelative path completions #23150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -137,7 +137,6 @@ namespace ts.Completions.PathCompletions { | |
if (directories) { | ||
for (const directory of directories) { | ||
const directoryName = getBaseFileName(normalizePath(directory)); | ||
|
||
result.push(nameAndKind(directoryName, ScriptElementKind.directory)); | ||
} | ||
} | ||
|
@@ -177,19 +176,33 @@ namespace ts.Completions.PathCompletions { | |
} | ||
} | ||
|
||
if (compilerOptions.moduleResolution === ModuleResolutionKind.NodeJs) { | ||
forEachAncestorDirectory(scriptPath, ancestor => { | ||
const nodeModules = combinePaths(ancestor, "node_modules"); | ||
if (host.directoryExists(nodeModules)) { | ||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result); | ||
} | ||
}); | ||
const fragmentDirectory = containsSlash(fragment) ? getDirectoryPath(fragment) : undefined; | ||
for (const ambientName of getAmbientModuleCompletions(fragment, fragmentDirectory, typeChecker)) { | ||
result.push(nameAndKind(ambientName, ScriptElementKind.externalModuleName)); | ||
} | ||
|
||
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, result); | ||
|
||
for (const moduleName of enumeratePotentialNonRelativeModules(fragment, scriptPath, compilerOptions, typeChecker, host)) { | ||
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName)); | ||
if (getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs) { | ||
// If looking for a global package name, don't just include everything in `node_modules` because that includes dependencies' own dependencies. | ||
// (But do if we didn't find anything, e.g. 'package.json' missing.) | ||
let foundGlobal = false; | ||
if (fragmentDirectory === undefined) { | ||
const oldLength = result.length; | ||
getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, result); | ||
for (const moduleName of getNamesFromVisibleNodeModules(fragmentDirectory, scriptPath, host)) { | ||
if (!result.some(entry => entry.name === moduleName)) { | ||
result.push(nameAndKind(moduleName, ScriptElementKind.externalModuleName)); | ||
} | ||
} | ||
foundGlobal = result.length !== oldLength; | ||
} | ||
if (!foundGlobal) { | ||
forEachAncestorDirectory(scriptPath, ancestor => { | ||
const nodeModules = combinePaths(ancestor, "node_modules"); | ||
if (host.directoryExists(nodeModules)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
getCompletionEntriesForDirectoryFragment(fragment, nodeModules, fileExtensions, /*includeExtensions*/ false, host, /*exclude*/ undefined, result); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
return result; | ||
|
@@ -228,7 +241,7 @@ namespace ts.Completions.PathCompletions { | |
const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix); | ||
const normalizedPrefixBase = getBaseFileName(normalizedPrefix); | ||
|
||
const fragmentHasPath = stringContains(fragment, directorySeparator); | ||
const fragmentHasPath = containsSlash(fragment); | ||
|
||
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call | ||
const expandedPrefixDirectory = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory; | ||
|
@@ -262,45 +275,31 @@ namespace ts.Completions.PathCompletions { | |
return path[0] === directorySeparator ? path.slice(1) : path; | ||
} | ||
|
||
function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string, options: CompilerOptions, typeChecker: TypeChecker, host: LanguageServiceHost): string[] { | ||
// Check If this is a nested module | ||
const isNestedModule = stringContains(fragment, directorySeparator); | ||
const moduleNameFragment = isNestedModule ? fragment.substr(0, fragment.lastIndexOf(directorySeparator)) : undefined; | ||
|
||
function getAmbientModuleCompletions(fragment: string, fragmentDirectory: string | undefined, checker: TypeChecker): ReadonlyArray<string> { | ||
// Get modules that the type checker picked up | ||
const ambientModules = map(typeChecker.getAmbientModules(), sym => stripQuotes(sym.name)); | ||
let nonRelativeModuleNames = filter(ambientModules, moduleName => startsWith(moduleName, fragment)); | ||
const ambientModules = checker.getAmbientModules().map(sym => stripQuotes(sym.name)); | ||
const nonRelativeModuleNames = ambientModules.filter(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); | ||
nonRelativeModuleNames = map(nonRelativeModuleNames, nonRelativeModuleName => { | ||
return removePrefix(nonRelativeModuleName, moduleNameWithSeperator); | ||
}); | ||
if (fragmentDirectory !== undefined) { | ||
const moduleNameWithSeperator = ensureTrailingDirectorySeparator(fragmentDirectory); | ||
return nonRelativeModuleNames.map(nonRelativeModuleName => removePrefix(nonRelativeModuleName, moduleNameWithSeperator)); | ||
} | ||
return nonRelativeModuleNames; | ||
} | ||
|
||
|
||
if (!options.moduleResolution || options.moduleResolution === ModuleResolutionKind.NodeJs) { | ||
for (const visibleModule of enumerateNodeModulesVisibleToScript(host, scriptPath)) { | ||
if (!isNestedModule) { | ||
nonRelativeModuleNames.push(visibleModule.moduleName); | ||
} | ||
else if (startsWith(visibleModule.moduleName, moduleNameFragment)) { | ||
const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/ undefined, /*include*/ ["./*"]); | ||
if (nestedFiles) { | ||
for (let f of nestedFiles) { | ||
f = normalizePath(f); | ||
const nestedModule = removeFileExtension(getBaseFileName(f)); | ||
nonRelativeModuleNames.push(nestedModule); | ||
} | ||
} | ||
} | ||
function getNamesFromVisibleNodeModules(fragmentDirectory: string | undefined, scriptPath: string, host: LanguageServiceHost): string[] { | ||
return flatMap(enumerateNodeModulesVisibleToScript(host, scriptPath), visibleModule => { | ||
if (fragmentDirectory === undefined) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isnt this always undefined since its called only when fragmentDirectory ===undefined There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, if we're always in the first case we can clean out a lot of dead code. |
||
return visibleModule.moduleName; | ||
} | ||
} | ||
|
||
return deduplicate(nonRelativeModuleNames, equateStringsCaseSensitive, compareStringsCaseSensitive); | ||
else if (startsWith(visibleModule.moduleName, fragmentDirectory)) { | ||
const nestedFiles = tryReadDirectory(host, visibleModule.moduleDir, supportedTypeScriptExtensions, /*exclude*/ undefined, /*include*/ ["./*"]); | ||
return map(nestedFiles, f => removeFileExtension(getBaseFileName(normalizePath(f)))); | ||
} | ||
}); | ||
} | ||
|
||
export function getTripleSlashReferenceCompletion(sourceFile: SourceFile, position: number, compilerOptions: CompilerOptions, host: LanguageServiceHost): ReadonlyArray<PathCompletion> | undefined { | ||
|
@@ -522,4 +521,8 @@ namespace ts.Completions.PathCompletions { | |
catch { /*ignore*/ } | ||
return undefined; | ||
} | ||
|
||
function containsSlash(fragment: string) { | ||
return stringContains(fragment, directorySeparator); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,4 @@ | |
// NOTE: The node_modules folder is in "/", rather than ".", because it requires | ||
// less scaffolding to mock. In particular, "/" is where we look for type roots. | ||
|
||
verify.completionsAt("1", ["@a/b", "@c/d", "@e/f"], { isNewIdentifierLocation: true }); | ||
verify.completionsAt("1", ["@e/f", "@a/b", "@c/d"], { isNewIdentifierLocation: true }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The order changed because I moved up the check for ambient module declarations. Don't think it matters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the results be sorted? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They weren't sorted by name before, except by coincidence. We could make a PR sorting them but it would change a bunch of baselines. I think editors do their own sorting on the result anyway. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
earlier this was done irrespective of fragment/module emit .. Why the change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 good catch