Revert "Proposal: If there’s a package.json, only auto-import things in it, more or less (#31893)"

This reverts commit 60a1b1dc1a93ca792cf12bb0432cf7bc134c3ad1.
This commit is contained in:
Andrew Branch 2019-07-17 09:04:53 -07:00 committed by GitHub
parent 049618f7da
commit dbefaf2911
18 changed files with 98 additions and 752 deletions

View File

@ -7485,7 +7485,7 @@ namespace ts {
export function getDirectoryPath(path: Path): Path;
/**
* Returns the path except for its basename. Semantics align with NodeJS's `path.dirname`
* except that we support URLs as well.
* except that we support URL's as well.
*
* ```ts
* getDirectoryPath("/path/to/file.ext") === "/path/to"

View File

@ -798,7 +798,7 @@ namespace FourSlash {
const name = typeof include === "string" ? include : include.name;
const found = nameToEntries.get(name);
if (!found) throw this.raiseError(`No completion ${name} found`);
assert(found.length === 1, `Must use 'exact' for multiple completions with same name: '${name}'`);
assert(found.length === 1); // Must use 'exact' for multiple completions with same name
this.verifyCompletionEntry(ts.first(found), include);
}
}

View File

@ -283,25 +283,13 @@ namespace ts.codefix {
preferences: UserPreferences,
): ReadonlyArray<FixAddNewImport | FixUseImportType> {
const isJs = isSourceFileJS(sourceFile);
const { allowsImporting } = createLazyPackageJsonDependencyReader(sourceFile, host);
const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) =>
moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap)
.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position) } : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind }));
// Sort by presence in package.json, then shortest paths first
return sort(choicesForEachExportingModule, (a, b) => {
const allowsImportingA = allowsImporting(a.moduleSpecifier);
const allowsImportingB = allowsImporting(b.moduleSpecifier);
if (allowsImportingA && !allowsImportingB) {
return -1;
}
if (allowsImportingB && !allowsImportingA) {
return 1;
}
return a.moduleSpecifier.length - b.moduleSpecifier.length;
});
// Sort to keep the shortest paths first
return sort(choicesForEachExportingModule, (a, b) => a.moduleSpecifier.length - b.moduleSpecifier.length);
}
function getFixesForAddImport(
@ -392,8 +380,7 @@ namespace ts.codefix {
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
Debug.assert(symbolName !== InternalSymbolName.Default);
const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, preferences, host);
const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
const fixes = arrayFrom(flatMapIterator(getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program).entries(), ([_, exportInfos]) =>
getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), program, sourceFile, host, preferences)));
return { fixes, symbolName };
}
@ -406,8 +393,6 @@ namespace ts.codefix {
sourceFile: SourceFile,
checker: TypeChecker,
program: Program,
preferences: UserPreferences,
host: LanguageServiceHost
): ReadonlyMap<ReadonlyArray<SymbolExportInfo>> {
// For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once.
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
@ -415,7 +400,7 @@ namespace ts.codefix {
function addSymbol(moduleSymbol: Symbol, exportedSymbol: Symbol, importKind: ImportKind): void {
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { moduleSymbol, importKind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exportedSymbol, checker) });
}
forEachExternalModuleToImportFrom(checker, host, preferences, program.redirectTargetsMap, sourceFile, program.getSourceFiles(), moduleSymbol => {
forEachExternalModuleToImportFrom(checker, sourceFile, program.getSourceFiles(), moduleSymbol => {
cancellationToken.throwIfCancellationRequested();
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, program.getCompilerOptions());
@ -576,44 +561,12 @@ namespace ts.codefix {
return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning));
}
export function forEachExternalModuleToImportFrom(checker: TypeChecker, host: LanguageServiceHost, preferences: UserPreferences, redirectTargetsMap: RedirectTargetsMap, from: SourceFile, allSourceFiles: ReadonlyArray<SourceFile>, cb: (module: Symbol) => void) {
const { allowsImporting } = createLazyPackageJsonDependencyReader(from, host);
const compilerOptions = host.getCompilationSettings();
const getCanonicalFileName = hostGetCanonicalFileName(host);
export function forEachExternalModuleToImportFrom(checker: TypeChecker, from: SourceFile, allSourceFiles: ReadonlyArray<SourceFile>, cb: (module: Symbol) => void) {
forEachExternalModule(checker, allSourceFiles, (module, sourceFile) => {
if (sourceFile === undefined && allowsImporting(stripQuotes(module.getName()))) {
if (sourceFile === undefined || sourceFile !== from && isImportablePath(from.fileName, sourceFile.fileName)) {
cb(module);
}
else if (sourceFile && sourceFile !== from && isImportablePath(from.fileName, sourceFile.fileName)) {
const moduleSpecifier = getNodeModulesPackageNameFromFileName(sourceFile.fileName);
if (!moduleSpecifier || allowsImporting(moduleSpecifier)) {
cb(module);
}
}
});
function getNodeModulesPackageNameFromFileName(importedFileName: string): string | undefined {
const specifier = moduleSpecifiers.getModuleSpecifier(
compilerOptions,
from,
toPath(from.fileName, /*basePath*/ undefined, getCanonicalFileName),
importedFileName,
host,
allSourceFiles,
preferences,
redirectTargetsMap);
// Paths here are not node_modules, so we dont care about them;
// returning anything will trigger a lookup in package.json.
if (!pathIsRelative(specifier) && !isRootedDiskPath(specifier)) {
const components = getPathComponents(getPackageNameFromTypesPackageName(specifier)).slice(1);
// Scoped packages
if (startsWith(components[0], "@")) {
return `${components[0]}/${components[1]}`;
}
return components[0];
}
}
}
function forEachExternalModule(checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>, cb: (module: Symbol, sourceFile: SourceFile | undefined) => void) {
@ -667,69 +620,4 @@ namespace ts.codefix {
// Need `|| "_"` to ensure result isn't empty.
return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`;
}
function createLazyPackageJsonDependencyReader(fromFile: SourceFile, host: LanguageServiceHost) {
const packageJsonPaths = findPackageJsons(getDirectoryPath(fromFile.fileName), host);
const dependencyIterator = readPackageJsonDependencies(host, packageJsonPaths);
let seenDeps: Map<true> | undefined;
let usesNodeCoreModules: boolean | undefined;
return { allowsImporting };
function containsDependency(dependency: string) {
if ((seenDeps || (seenDeps = createMap())).has(dependency)) {
return true;
}
let packageName: string | void;
while (packageName = dependencyIterator.next().value) {
seenDeps.set(packageName, true);
if (packageName === dependency) {
return true;
}
}
return false;
}
function allowsImporting(moduleSpecifier: string): boolean {
if (!packageJsonPaths.length) {
return true;
}
// If were in JavaScript, it can be difficult to tell whether the user wants to import
// from Node core modules or not. We can start by seeing if the user is actually using
// any node core modules, as opposed to simply having @types/node accidentally as a
// dependency of a dependency.
if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
if (usesNodeCoreModules === undefined) {
usesNodeCoreModules = consumesNodeCoreModules(fromFile);
}
if (usesNodeCoreModules) {
return true;
}
}
return containsDependency(moduleSpecifier)
|| containsDependency(getTypesPackageName(moduleSpecifier));
}
}
function *readPackageJsonDependencies(host: LanguageServiceHost, packageJsonPaths: string[]) {
type PackageJson = Record<typeof dependencyKeys[number], Record<string, string> | undefined>;
const dependencyKeys = ["dependencies", "devDependencies", "optionalDependencies"] as const;
for (const fileName of packageJsonPaths) {
const content = readJson(fileName, { readFile: host.readFile ? host.readFile.bind(host) : sys.readFile }) as PackageJson;
for (const key of dependencyKeys) {
const dependencies = content[key];
if (!dependencies) {
continue;
}
for (const packageName in dependencies) {
yield packageName;
}
}
}
}
function consumesNodeCoreModules(sourceFile: SourceFile): boolean {
return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text));
}
}

View File

@ -64,7 +64,7 @@ namespace ts.Completions {
return getLabelCompletionAtPosition(contextToken.parent);
}
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined, host);
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, preferences, /*detailsEntryId*/ undefined);
if (!completionData) {
return undefined;
}
@ -407,10 +407,10 @@ namespace ts.Completions {
previousToken: Node | undefined;
readonly isJsxInitializer: IsJsxInitializer;
}
function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost
function getSymbolCompletionFromEntryId(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier,
): SymbolCompletion | { type: "request", request: Request } | { type: "literal", literal: string | number | PseudoBigInt } | { type: "none" } {
const compilerOptions = program.getCompilerOptions();
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId, host);
const completionData = getCompletionData(program, log, sourceFile, isUncheckedFile(sourceFile, compilerOptions), position, { includeCompletionsForModuleExports: true, includeCompletionsWithInsertText: true }, entryId);
if (!completionData) {
return { type: "none" };
}
@ -472,7 +472,7 @@ namespace ts.Completions {
}
// Compute all the completion symbols again.
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
const symbolCompletion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId);
switch (symbolCompletion.type) {
case "request": {
const { request } = symbolCompletion;
@ -557,8 +557,8 @@ namespace ts.Completions {
return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] };
}
export function getCompletionEntrySymbol(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost): Symbol | undefined {
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId, host);
export function getCompletionEntrySymbol(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier): Symbol | undefined {
const completion = getSymbolCompletionFromEntryId(program, log, sourceFile, position, entryId);
return completion.type === "symbol" ? completion.symbol : undefined;
}
@ -657,7 +657,6 @@ namespace ts.Completions {
position: number,
preferences: Pick<UserPreferences, "includeCompletionsForModuleExports" | "includeCompletionsWithInsertText">,
detailsEntryId: CompletionEntryIdentifier | undefined,
host: LanguageServiceHost
): CompletionData | Request | undefined {
const typeChecker = program.getTypeChecker();
@ -1150,7 +1149,7 @@ namespace ts.Completions {
}
if (shouldOfferImportCompletions()) {
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", program.getCompilerOptions().target!, host);
getSymbolsFromOtherSourceFileExports(symbols, previousToken && isIdentifier(previousToken) ? previousToken.text : "", program.getCompilerOptions().target!);
}
filterGlobalCompletion(symbols);
}
@ -1268,64 +1267,12 @@ namespace ts.Completions {
typeChecker.getExportsOfModule(sym).some(e => symbolCanBeReferencedAtTypeLocation(e, seenModules));
}
/**
* Gathers symbols that can be imported from other files, deduplicating along the way. Symbols can be duplicates
* if re-exported from another module, e.g. `export { foo } from "./a"`. That syntax creates a fresh symbol, but
* its just an alias to the first, and both have the same name, so we generally want to filter those aliases out,
* if and only if the the first can be imported (it may be excluded due to package.json filtering in
* `codefix.forEachExternalModuleToImportFrom`).
*
* Example. Imagine a chain of node_modules re-exporting one original symbol:
*
* ```js
* node_modules/x/index.js node_modules/y/index.js node_modules/z/index.js
* +-----------------------+ +--------------------------+ +--------------------------+
* | | | | | |
* | export const foo = 0; | <--- | export { foo } from 'x'; | <--- | export { foo } from 'y'; |
* | | | | | |
* +-----------------------+ +--------------------------+ +--------------------------+
* ```
*
* Also imagine three buckets, which well reference soon:
*
* ```md
* | | | | | |
* | **Bucket A** | | **Bucket B** | | **Bucket C** |
* | Symbols to | | Aliases to symbols | | Symbols to return |
* | definitely | | in Buckets A or C | | if nothing better |
* | return | | (dont return these) | | comes along |
* |__________________| |______________________| |___________________|
* ```
*
* We _probably_ want to show `foo` from 'x', but not from 'y' or 'z'. However, if 'x' is not in a package.json, it
* will not appear in a `forEachExternalModuleToImportFrom` iteration. Furthermore, the order of iterations is not
* guaranteed, as it is host-dependent. Therefore, when presented with the symbol `foo` from module 'y' alone, we
* may not be sure whether or not it should go in the list. So, well take the following steps:
*
* 1. Resolve alias `foo` from 'y' to the export declaration in 'x', get the symbol there, and see if that symbol is
* already in Bucket A (symbols we already know will be returned). If it is, put `foo` from 'y' in Bucket B
* (symbols that are aliases to symbols in Bucket A). If its not, put it in Bucket C.
* 2. Next, imagine we see `foo` from module 'z'. Again, we resolve the alias to the nearest export, which is in 'y'.
* At this point, if that nearest export from 'y' is in _any_ of the three buckets, we know the symbol in 'z'
* should never be returned in the final list, so put it in Bucket B.
* 3. Next, imagine we see `foo` from module 'x', the original. Syntactically, it doesnt look like a re-export, so
* we can just check Bucket C to see if we put any aliases to the original in there. If they exist, throw them out.
* Put this symbol in Bucket A.
* 4. After weve iterated through every symbol of every module, any symbol left in Bucket C means that step 3 didnt
* occur for that symbol---that is, the original symbol is not in Bucket A, so we should include the alias. Move
* everything from Bucket C to Bucket A.
*
* Note: Bucket A is passed in as the parameter `symbols` and mutated.
*/
function getSymbolsFromOtherSourceFileExports(/** Bucket A */ symbols: Symbol[], tokenText: string, target: ScriptTarget, host: LanguageServiceHost): void {
function getSymbolsFromOtherSourceFileExports(symbols: Symbol[], tokenText: string, target: ScriptTarget): void {
const tokenTextLowerCase = tokenText.toLowerCase();
const seenResolvedModules = createMap<true>();
/** Bucket B */
const aliasesToAlreadyIncludedSymbols = createMap<true>();
/** Bucket C */
const aliasesToReturnIfOriginalsAreMissing = createMap<{ alias: Symbol, moduleSymbol: Symbol }>();
codefix.forEachExternalModuleToImportFrom(typeChecker, host, preferences, program.redirectTargetsMap, sourceFile, program.getSourceFiles(), moduleSymbol => {
const seenResolvedModules = createMap<true>();
codefix.forEachExternalModuleToImportFrom(typeChecker, sourceFile, program.getSourceFiles(), moduleSymbol => {
// Perf -- ignore other modules if this is a request for details
if (detailsEntryId && detailsEntryId.source && stripQuotes(moduleSymbol.name) !== detailsEntryId.source) {
return;
@ -1346,59 +1293,33 @@ namespace ts.Completions {
symbolToOriginInfoMap[getSymbolId(resolvedModuleSymbol)] = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport: false };
}
for (const symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
// If this is `export { _break as break };` (a keyword) -- skip this and prefer the keyword completion.
if (some(symbol.declarations, d => isExportSpecifier(d) && !!d.propertyName && isIdentifierANonContextualKeyword(d.name))) {
for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
// Don't add a completion for a re-export, only for the original.
// The actual import fix might end up coming from a re-export -- we don't compute that until getting completion details.
// This is just to avoid adding duplicate completion entries.
//
// If `symbol.parent !== ...`, this is an `export * from "foo"` re-export. Those don't create new symbols.
if (typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol
|| some(symbol.declarations, d =>
// If `!!d.name.originalKeywordKind`, this is `export { _break as break };` -- skip this and prefer the keyword completion.
// If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
isExportSpecifier(d) && (d.propertyName ? isIdentifierANonContextualKeyword(d.name) : !!d.parent.parent.moduleSpecifier))) {
continue;
}
// If `symbol.parent !== moduleSymbol`, this is an `export * from "foo"` re-export. Those don't create new symbols.
const isExportStarFromReExport = typeChecker.getMergedSymbol(symbol.parent!) !== resolvedModuleSymbol;
// If `!!d.parent.parent.moduleSpecifier`, this is `export { foo } from "foo"` re-export, which creates a new symbol (thus isn't caught by the first check).
if (isExportStarFromReExport || some(symbol.declarations, d => isExportSpecifier(d) && !d.propertyName && !!d.parent.parent.moduleSpecifier)) {
// Walk the export chain back one module (step 1 or 2 in diagrammed example).
// Or, in the case of `export * from "foo"`, `symbol` already points to the original export, so just use that.
const nearestExportSymbolId = getSymbolId(isExportStarFromReExport ? symbol : Debug.assertDefined(getNearestExportSymbol(symbol)));
const symbolHasBeenSeen = !!symbolToOriginInfoMap[nearestExportSymbolId] || aliasesToAlreadyIncludedSymbols.has(nearestExportSymbolId.toString());
if (!symbolHasBeenSeen) {
aliasesToReturnIfOriginalsAreMissing.set(nearestExportSymbolId.toString(), { alias: symbol, moduleSymbol });
aliasesToAlreadyIncludedSymbols.set(getSymbolId(symbol).toString(), true);
}
else {
// Perf - we know this symbol is an alias to one thats already covered in `symbols`, so store it here
// in case another symbol re-exports this one; that way we can short-circuit as soon as we see this symbol id.
addToSeen(aliasesToAlreadyIncludedSymbols, getSymbolId(symbol));
}
const isDefaultExport = symbol.escapedName === InternalSymbolName.Default;
if (isDefaultExport) {
symbol = getLocalSymbolForExportDefault(symbol) || symbol;
}
else {
// This is not a re-export, so see if we have any aliases pending and remove them (step 3 in diagrammed example)
aliasesToReturnIfOriginalsAreMissing.delete(getSymbolId(symbol).toString());
pushSymbol(symbol, moduleSymbol);
const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport };
if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) {
symbols.push(symbol);
symbolToSortTextMap[getSymbolId(symbol)] = SortText.AutoImportSuggestions;
symbolToOriginInfoMap[getSymbolId(symbol)] = origin;
}
}
});
// By this point, any potential duplicates that were actually duplicates have been
// removed, so the rest need to be added. (Step 4 in diagrammed example)
aliasesToReturnIfOriginalsAreMissing.forEach(({ alias, moduleSymbol }) => pushSymbol(alias, moduleSymbol));
function pushSymbol(symbol: Symbol, moduleSymbol: Symbol) {
const isDefaultExport = symbol.escapedName === InternalSymbolName.Default;
if (isDefaultExport) {
symbol = getLocalSymbolForExportDefault(symbol) || symbol;
}
const origin: SymbolOriginInfoExport = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport };
if (detailsEntryId || stringContainsCharactersInOrder(getSymbolName(symbol, origin, target).toLowerCase(), tokenTextLowerCase)) {
symbols.push(symbol);
symbolToSortTextMap[getSymbolId(symbol)] = SortText.AutoImportSuggestions;
symbolToOriginInfoMap[getSymbolId(symbol)] = origin;
}
}
}
function getNearestExportSymbol(fromSymbol: Symbol) {
return findAlias(typeChecker, fromSymbol, alias => {
return some(alias.declarations, d => isExportSpecifier(d) || !!d.localSymbol);
});
}
/**
@ -2322,13 +2243,4 @@ namespace ts.Completions {
function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean {
return nodeIsMissing(left);
}
function findAlias(typeChecker: TypeChecker, symbol: Symbol, predicate: (symbol: Symbol) => boolean): Symbol | undefined {
let currentAlias: Symbol | undefined = symbol;
while (currentAlias.flags & SymbolFlags.Alias && (currentAlias = typeChecker.getImmediateAliasedSymbol(currentAlias))) {
if (predicate(currentAlias)) {
return currentAlias;
}
}
}
}

View File

@ -1453,7 +1453,7 @@ namespace ts {
function getCompletionEntrySymbol(fileName: string, position: number, name: string, source?: string): Symbol | undefined {
synchronizeHostData();
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source }, host);
return Completions.getCompletionEntrySymbol(program, log, getValidSourceFile(fileName), position, { name, source });
}
function getQuickInfoAtPosition(fileName: string, position: number): QuickInfo | undefined {

View File

@ -627,6 +627,30 @@ namespace ts.Completions.StringCompletions {
}
}
function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
const paths: string[] = [];
forEachAncestorDirectory(directory, ancestor => {
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (!currentConfigPath) {
return true; // break out
}
paths.push(currentConfigPath);
});
return paths;
}
function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
let packageJson: string | undefined;
forEachAncestorDirectory(directory, ancestor => {
if (ancestor === "node_modules") return true;
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (packageJson) {
return true; // break out
}
});
return packageJson;
}
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string): ReadonlyArray<string> {
if (!host.readFile || !host.fileExists) return emptyArray;
@ -682,6 +706,31 @@ namespace ts.Completions.StringCompletions {
const nodeModulesDependencyKeys: ReadonlyArray<string> = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] {
return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || [];
}
function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>): ReadonlyArray<string> {
return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray;
}
function tryFileExists(host: LanguageServiceHost, path: string): boolean {
return tryIOAndConsumeErrors(host, host.fileExists, path);
}
function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean {
return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false;
}
function tryIOAndConsumeErrors<T>(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) {
return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args));
}
function tryAndIgnoreErrors<T>(cb: () => T): T | undefined {
try { return cb(); }
catch { return undefined; }
}
function containsSlash(fragment: string) {
return stringContains(fragment, directorySeparator);
}

View File

@ -2022,53 +2022,4 @@ namespace ts {
// If even 2/5 places have a semicolon, the user probably wants semicolons
return withSemicolon / withoutSemicolon > 1 / nStatementsToObserve;
}
export function tryGetDirectories(host: LanguageServiceHost, directoryName: string): string[] {
return tryIOAndConsumeErrors(host, host.getDirectories, directoryName) || [];
}
export function tryReadDirectory(host: LanguageServiceHost, path: string, extensions?: ReadonlyArray<string>, exclude?: ReadonlyArray<string>, include?: ReadonlyArray<string>): ReadonlyArray<string> {
return tryIOAndConsumeErrors(host, host.readDirectory, path, extensions, exclude, include) || emptyArray;
}
export function tryFileExists(host: LanguageServiceHost, path: string): boolean {
return tryIOAndConsumeErrors(host, host.fileExists, path);
}
export function tryDirectoryExists(host: LanguageServiceHost, path: string): boolean {
return tryAndIgnoreErrors(() => directoryProbablyExists(path, host)) || false;
}
export function tryAndIgnoreErrors<T>(cb: () => T): T | undefined {
try { return cb(); }
catch { return undefined; }
}
export function tryIOAndConsumeErrors<T>(host: LanguageServiceHost, toApply: ((...a: any[]) => T) | undefined, ...args: any[]) {
return tryAndIgnoreErrors(() => toApply && toApply.apply(host, args));
}
export function findPackageJsons(directory: string, host: LanguageServiceHost): string[] {
const paths: string[] = [];
forEachAncestorDirectory(directory, ancestor => {
const currentConfigPath = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (!currentConfigPath) {
return true; // break out
}
paths.push(currentConfigPath);
});
return paths;
}
export function findPackageJson(directory: string, host: LanguageServiceHost): string | undefined {
let packageJson: string | undefined;
forEachAncestorDirectory(directory, ancestor => {
if (ancestor === "node_modules") return true;
packageJson = findConfigFile(ancestor, (f) => tryFileExists(host, f), "package.json");
if (packageJson) {
return true; // break out
}
});
return packageJson;
}
}

View File

@ -1,44 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "react": "*"
//// }
////}
//@Filename: /node_modules/@types/react/index.d.ts
////export declare var React: any;
//@Filename: /node_modules/@types/react/package.json
////{
//// "name": "@types/react"
////}
//@Filename: /node_modules/@types/fake-react/index.d.ts
////export declare var ReactFake: any;
//@Filename: /node_modules/@types/fake-react/package.json
////{
//// "name": "@types/fake-react"
////}
//@Filename: /src/index.ts
////const x = Re/**/
verify.completions({
marker: test.marker(""),
isNewIdentifierLocation: true,
includes: {
name: "React",
hasAction: true,
source: "/node_modules/@types/react/index",
sortText: completion.SortText.AutoImportSuggestions
},
excludes: "ReactFake",
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,44 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "devDependencies": {
//// "@types/react": "*"
//// }
////}
//@Filename: /node_modules/@types/react/index.d.ts
////export declare var React: any;
//@Filename: /node_modules/@types/react/package.json
////{
//// "name": "@types/react"
////}
//@Filename: /node_modules/@types/fake-react/index.d.ts
////export declare var ReactFake: any;
//@Filename: /node_modules/@types/fake-react/package.json
////{
//// "name": "@types/fake-react"
////}
//@Filename: /src/index.ts
////const x = Re/**/
verify.completions({
marker: test.marker(""),
isNewIdentifierLocation: true,
includes: {
name: "React",
hasAction: true,
source: "/node_modules/@types/react/index",
sortText: completion.SortText.AutoImportSuggestions
},
excludes: "ReactFake",
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,30 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// }
////}
//@Filename: /node_modules/@types/node/timers.d.ts
////declare module "timers" {
//// function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timeout;
////}
//@Filename: /node_modules/@types/node/package.json
////{
//// "name": "@types/node",
////}
//@Filename: /src/index.ts
////setTimeo/**/
verify.completions({
marker: test.marker(""),
exact: completion.globals,
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,46 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "react": "*"
//// }
////}
//@Filename: /node_modules/react/index.d.ts
////export declare var React: any;
//@Filename: /node_modules/react/package.json
////{
//// "name": "react",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/fake-react/index.d.ts
////export declare var ReactFake: any;
//@Filename: /node_modules/fake-react/package.json
////{
//// "name": "fake-react",
//// "types": "./index.d.ts"
////}
//@Filename: /src/index.ts
////const x = Re/**/
verify.completions({
marker: test.marker(""),
isNewIdentifierLocation: true,
includes: {
name: "React",
hasAction: true,
source: "/node_modules/react/index",
sortText: completion.SortText.AutoImportSuggestions
},
excludes: "ReactFake",
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,66 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "react": "*"
//// }
////}
//@Filename: /node_modules/react/index.d.ts
////export declare var React: any;
//@Filename: /node_modules/react/package.json
////{
//// "name": "react",
//// "types": "./index.d.ts"
////}
//@Filename: /dir/package.json
////{
//// "dependencies": {
//// "redux": "*"
//// }
////}
//@Filename: /dir/node_modules/redux/package.json
////{
//// "name": "redux",
//// "types": "./index.d.ts"
////}
//@Filename: /dir/node_modules/redux/index.d.ts
////export declare var Redux: any;
//@Filename: /dir/index.ts
////const x = Re/**/
verify.completions({
marker: test.marker(""),
isNewIdentifierLocation: true,
includes: {
name: "React",
hasAction: true,
source: "/node_modules/react/index",
sortText: completion.SortText.AutoImportSuggestions
},
preferences: {
includeCompletionsForModuleExports: true
}
});
verify.completions({
marker: test.marker(""),
isNewIdentifierLocation: true,
includes: {
name: "Redux",
hasAction: true,
source: "/dir/node_modules/redux/index",
sortText: completion.SortText.AutoImportSuggestions
},
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,58 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "@emotion/core": "*"
//// }
////}
//@Filename: /node_modules/@emotion/css/index.d.ts
////export declare const css: any;
////const css2: any;
////export { css2 };
//@Filename: /node_modules/@emotion/css/package.json
////{
//// "name": "@emotion/css",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/@emotion/core/index.d.ts
////import { css2 } from "@emotion/css";
////export { css } from "@emotion/css";
////export { css2 };
//@Filename: /node_modules/@emotion/core/package.json
////{
//// "name": "@emotion/core",
//// "types": "./index.d.ts"
////}
//@Filename: /src/index.ts
////cs/**/
verify.completions({
marker: test.marker(""),
includes: [
completion.undefinedVarEntry,
{
name: "css",
source: "/node_modules/@emotion/core/index",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
{
name: "css2",
source: "/node_modules/@emotion/core/index",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes
],
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,58 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "b_": "*",
//// "_c": "*"
//// }
////}
//@Filename: /node_modules/a/index.d.ts
////export const foo = 0;
//@Filename: /node_modules/a/package.json
////{
//// "name": "a",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/b_/index.d.ts
////export { foo } from "a";
//@Filename: /node_modules/b_/package.json
////{
//// "name": "b_",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/_c/index.d.ts
////export { foo } from "b_";
//@Filename: /node_modules/_c/package.json
////{
//// "name": "_c",
//// "types": "./index.d.ts"
////}
//@Filename: /src/index.ts
////fo/**/
verify.completions({
marker: test.marker(""),
includes: [
completion.undefinedVarEntry,
{
name: "foo",
source: "/node_modules/b_/index",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes
],
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,48 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "b": "*",
//// }
////}
//@Filename: /node_modules/a/index.d.ts
////export const foo = 0;
//@Filename: /node_modules/a/package.json
////{
//// "name": "a",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/b/index.d.ts
////export * from "a";
//@Filename: /node_modules/b/package.json
////{
//// "name": "b",
//// "types": "./index.d.ts"
////}
//@Filename: /src/index.ts
////fo/**/
verify.completions({
marker: test.marker(""),
includes: [
completion.undefinedVarEntry,
{
name: "foo",
source: "/node_modules/b/index",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes
],
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -1,57 +0,0 @@
/// <reference path="fourslash.ts" />
//@noEmit: true
//@Filename: /package.json
////{
//// "dependencies": {
//// "c": "*",
//// }
////}
//@Filename: /node_modules/a/index.d.ts
////export const foo = 0;
//@Filename: /node_modules/a/package.json
////{
//// "name": "a",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/b/index.d.ts
////export * from "a";
//@Filename: /node_modules/b/package.json
////{
//// "name": "b",
//// "types": "./index.d.ts"
////}
//@Filename: /node_modules/c/index.d.ts
////export * from "a";
//@Filename: /node_modules/c/package.json
////{
//// "name": "c",
//// "types": "./index.d.ts"
////}
//@Filename: /src/index.ts
////fo/**/
verify.completions({
marker: test.marker(""),
includes: [
completion.undefinedVarEntry,
{
name: "foo",
source: "/node_modules/c/index",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes
],
preferences: {
includeCompletionsForModuleExports: true
}
});

View File

@ -16,9 +16,6 @@
// @Filename: /a_reexport_2.ts
////export * from "./a";
// @Filename: /a_reexport_3.ts
////export { foo } from "./a_reexport";
// @Filename: /b.ts
////fo/**/
@ -27,13 +24,13 @@ verify.completions({
includes: [
completion.undefinedVarEntry,
{
name: "foo",
source: "/a",
sourceDisplay: "./a",
text: "(alias) const foo: 0\nexport foo",
kind: "alias",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
name: "foo",
source: "/a",
sourceDisplay: "./a",
text: "(alias) const foo: 0\nexport foo",
kind: "alias",
hasAction: true,
sortText: completion.SortText.AutoImportSuggestions
},
...completion.statementKeywordsWithTypes,
],

View File

@ -3,7 +3,7 @@
//// [|f1/*0*/('');|]
// @Filename: package.json
//// { "dependencies": { "@scope/package-name": "latest" } }
//// { "dependencies": { "package-name": "latest" } }
// @Filename: node_modules/@scope/package-name/bin/lib/index.d.ts
//// export function f1(text: string): string;