mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Full support for CommonJS auto-imports in JS (#37027)
* Support add new requires * Always use destructuring for requiring default exports * Add more tests * Update existing fourslash tests * Use `getExportsAndPropertiesOfModule` * Add UMD test * Apply suggestions from code review Fix typos Co-Authored-By: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
co-authored by
Nathan Shively-Sanders
parent
6a1e474cac
commit
f9945f5acf
@@ -609,6 +609,9 @@ namespace ts {
|
||||
getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)),
|
||||
getAccessibleSymbolChain,
|
||||
getTypePredicateOfSignature,
|
||||
resolveExternalModuleName: moduleSpecifier => {
|
||||
return resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true);
|
||||
},
|
||||
resolveExternalModuleSymbol,
|
||||
tryGetThisTypeAt: (node, includeGlobalThis) => {
|
||||
node = getParseTreeNode(node);
|
||||
@@ -2576,7 +2579,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getTargetOfExportAssignment(node: ExportAssignment | BinaryExpression, dontResolveAlias: boolean): Symbol | undefined {
|
||||
const expression = (isExportAssignment(node) ? node.expression : node.right) as EntityNameExpression | ClassExpression;
|
||||
const expression = isExportAssignment(node) ? node.expression : node.right;
|
||||
const resolved = getTargetOfAliasLikeExpression(expression, dontResolveAlias);
|
||||
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
|
||||
return resolved;
|
||||
|
||||
+12
-1
@@ -3596,6 +3596,7 @@ namespace ts {
|
||||
*/
|
||||
/* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined;
|
||||
/* @internal */ getTypePredicateOfSignature(signature: Signature): TypePredicate | undefined;
|
||||
/* @internal */ resolveExternalModuleName(moduleSpecifier: Expression): Symbol | undefined;
|
||||
/**
|
||||
* An external module with an 'export =' declaration resolves to the target of the 'export =' declaration,
|
||||
* and an external module with no 'export =' declaration resolves to the module itself.
|
||||
@@ -3830,6 +3831,10 @@ namespace ts {
|
||||
/* @internal */
|
||||
export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration;
|
||||
|
||||
/* @internal */
|
||||
export type AnyImportOrRequire = AnyImportSyntax | RequireVariableDeclaration;
|
||||
|
||||
|
||||
/* @internal */
|
||||
export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration;
|
||||
|
||||
@@ -3846,7 +3851,13 @@ namespace ts {
|
||||
| ValidImportTypeNode;
|
||||
|
||||
/* @internal */
|
||||
export type RequireOrImportCall = CallExpression & { arguments: [StringLiteralLike] };
|
||||
export type RequireOrImportCall = CallExpression & { expression: Identifier, arguments: [StringLiteralLike] };
|
||||
|
||||
/* @internal */
|
||||
export interface RequireVariableDeclaration extends VariableDeclaration {
|
||||
|
||||
initializer: RequireOrImportCall;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export type LateVisibilityPaintedStatement =
|
||||
|
||||
@@ -1856,6 +1856,20 @@ namespace ts {
|
||||
return !requireStringLiteralLikeArgument || isStringLiteralLike(arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the node is a VariableDeclaration initialized to a require call (see `isRequireCall`).
|
||||
* This function does not test if the node is in a JavaScript file or not.
|
||||
*/
|
||||
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: true): node is RequireVariableDeclaration;
|
||||
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration;
|
||||
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration {
|
||||
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(node.initializer, requireStringLiteralLikeArgument);
|
||||
}
|
||||
|
||||
export function isRequireVariableDeclarationStatement(node: Node, requireStringLiteralLikeArgument = true): node is VariableStatement {
|
||||
return isVariableStatement(node) && every(node.declarationList.declarations, decl => isRequireVariableDeclaration(decl, requireStringLiteralLikeArgument));
|
||||
}
|
||||
|
||||
export function isSingleOrDoubleQuote(charCode: number) {
|
||||
return charCode === CharacterCodes.singleQuote || charCode === CharacterCodes.doubleQuote;
|
||||
}
|
||||
|
||||
@@ -2717,7 +2717,7 @@ namespace FourSlash {
|
||||
const oldText = this.tryGetFileContent(change.fileName);
|
||||
ts.Debug.assert(!!change.isNewFile === (oldText === undefined));
|
||||
const newContent = change.isNewFile ? ts.first(change.textChanges).newText : ts.textChanges.applyChanges(oldText!, change.textChanges);
|
||||
assert.equal(newContent, expectedNewContent, `String mis-matched in file ${change.fileName}`);
|
||||
this.verifyTextMatches(newContent, /*includeWhitespace*/ true, expectedNewContent);
|
||||
}
|
||||
for (const newFileName in newFileContent) {
|
||||
ts.Debug.assert(changes.some(c => c.fileName === newFileName), "No change in file", () => newFileName);
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace ts.codefix {
|
||||
const addToNamespace: FixUseNamespaceImport[] = [];
|
||||
const importType: FixUseImportType[] = [];
|
||||
// Keys are import clause node IDs.
|
||||
const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
|
||||
const newImports = createMap<Mutable<ImportsCollection>>();
|
||||
const addToExisting = createMap<{ readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
|
||||
const newImports = createMap<Mutable<ImportsCollection & { useRequire: boolean }>>();
|
||||
let lastModuleSpecifier: string | undefined;
|
||||
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes };
|
||||
|
||||
@@ -61,7 +61,8 @@ namespace ts.codefix {
|
||||
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
|
||||
const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, sourceFile, compilerOptions, checker, program.getSourceFiles());
|
||||
const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, host, preferences);
|
||||
const useRequire = shouldUseRequire(sourceFile, compilerOptions);
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences);
|
||||
addImport({ fixes: [fix], symbolName });
|
||||
}
|
||||
|
||||
@@ -76,11 +77,11 @@ namespace ts.codefix {
|
||||
importType.push(fix);
|
||||
break;
|
||||
case ImportFixKind.AddToExisting: {
|
||||
const { importClause, importKind, canUseTypeOnlyImport } = fix;
|
||||
const key = String(getNodeId(importClause));
|
||||
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport } = fix;
|
||||
const key = String(getNodeId(importClauseOrBindingPattern));
|
||||
let entry = addToExisting.get(key);
|
||||
if (!entry) {
|
||||
addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
|
||||
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
|
||||
}
|
||||
if (importKind === ImportKind.Named) {
|
||||
pushIfUnique(entry.namedImports, symbolName);
|
||||
@@ -92,10 +93,10 @@ namespace ts.codefix {
|
||||
break;
|
||||
}
|
||||
case ImportFixKind.AddNew: {
|
||||
const { moduleSpecifier, importKind, typeOnly } = fix;
|
||||
const { moduleSpecifier, importKind, useRequire, typeOnly } = fix;
|
||||
let entry = newImports.get(moduleSpecifier);
|
||||
if (!entry) {
|
||||
newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined, typeOnly });
|
||||
newImports.set(moduleSpecifier, entry = { namedImports: [], namespaceLikeImport: undefined, typeOnly, useRequire });
|
||||
lastModuleSpecifier = moduleSpecifier;
|
||||
}
|
||||
else {
|
||||
@@ -108,9 +109,9 @@ namespace ts.codefix {
|
||||
entry.defaultImport = symbolName;
|
||||
break;
|
||||
case ImportKind.Named:
|
||||
pushIfUnique(entry.namedImports, symbolName);
|
||||
pushIfUnique(entry.namedImports || (entry.namedImports = []), symbolName);
|
||||
break;
|
||||
case ImportKind.Equals:
|
||||
case ImportKind.CommonJS:
|
||||
case ImportKind.Namespace:
|
||||
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
|
||||
entry.namespaceLikeImport = { importKind, name: symbolName };
|
||||
@@ -131,11 +132,12 @@ namespace ts.codefix {
|
||||
for (const fix of importType) {
|
||||
addImportType(changeTracker, sourceFile, fix, quotePreference);
|
||||
}
|
||||
addToExisting.forEach(({ importClause, defaultImport, namedImports, canUseTypeOnlyImport }) => {
|
||||
doAddExistingFix(changeTracker, sourceFile, importClause, defaultImport, namedImports, canUseTypeOnlyImport);
|
||||
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport }) => {
|
||||
doAddExistingFix(changeTracker, sourceFile, importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport);
|
||||
});
|
||||
newImports.forEach((imports, moduleSpecifier) => {
|
||||
addNewImports(changeTracker, sourceFile, moduleSpecifier, quotePreference, imports, /*blankLineBetween*/ lastModuleSpecifier === moduleSpecifier);
|
||||
newImports.forEach(({ useRequire, ...imports }, moduleSpecifier) => {
|
||||
const addDeclarations = useRequire ? addNewRequires : addNewImports;
|
||||
addDeclarations(changeTracker, sourceFile, moduleSpecifier, quotePreference, imports, /*blankLineBetween*/ lastModuleSpecifier === moduleSpecifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -155,7 +157,8 @@ namespace ts.codefix {
|
||||
}
|
||||
interface FixAddToExistingImport {
|
||||
readonly kind: ImportFixKind.AddToExisting;
|
||||
readonly importClause: ImportClause;
|
||||
readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
|
||||
readonly moduleSpecifier: string;
|
||||
readonly importKind: ImportKind.Default | ImportKind.Named;
|
||||
readonly canUseTypeOnlyImport: boolean;
|
||||
}
|
||||
@@ -164,14 +167,14 @@ namespace ts.codefix {
|
||||
readonly moduleSpecifier: string;
|
||||
readonly importKind: ImportKind;
|
||||
readonly typeOnly: boolean;
|
||||
readonly useRequire: boolean;
|
||||
}
|
||||
|
||||
const enum ImportKind {
|
||||
Named,
|
||||
Default,
|
||||
Namespace,
|
||||
Equals,
|
||||
ConstEquals
|
||||
CommonJS,
|
||||
}
|
||||
|
||||
/** Information about how a symbol is exported from a module. (We don't need to store the exported symbol, just its module.) */
|
||||
@@ -184,7 +187,7 @@ namespace ts.codefix {
|
||||
|
||||
/** Information needed to augment an existing import declaration. */
|
||||
interface FixAddToExistingImportInfo {
|
||||
readonly declaration: AnyImportSyntax;
|
||||
readonly declaration: AnyImportOrRequire;
|
||||
readonly importKind: ImportKind;
|
||||
}
|
||||
|
||||
@@ -201,16 +204,17 @@ namespace ts.codefix {
|
||||
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const exportInfos = getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, sourceFile, compilerOptions, program.getTypeChecker(), program.getSourceFiles());
|
||||
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, exportInfos, host, preferences)).moduleSpecifier;
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, host, preferences);
|
||||
const useRequire = shouldUseRequire(sourceFile, compilerOptions);
|
||||
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences)).moduleSpecifier;
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences);
|
||||
return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
|
||||
}
|
||||
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
|
||||
// We sort the best codefixes first, so taking `first` is best.
|
||||
return first(getFixForImport(exportInfos, symbolName, position, preferTypeOnlyImport, program, sourceFile, host, preferences));
|
||||
return first(getFixForImport(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences));
|
||||
}
|
||||
|
||||
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
|
||||
@@ -230,7 +234,7 @@ namespace ts.codefix {
|
||||
result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol, checker) });
|
||||
}
|
||||
|
||||
for (const exported of checker.getExportsOfModule(moduleSymbol)) {
|
||||
for (const exported of checker.getExportsAndPropertiesOfModule(moduleSymbol)) {
|
||||
if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) {
|
||||
result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported, checker) });
|
||||
}
|
||||
@@ -253,6 +257,7 @@ namespace ts.codefix {
|
||||
/** undefined only for missing JSX namespace */
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
useRequire: boolean,
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
host: LanguageServiceHost,
|
||||
@@ -263,7 +268,7 @@ namespace ts.codefix {
|
||||
const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker);
|
||||
const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position));
|
||||
// Don't bother providing an action to add a new import if we can add to an existing one.
|
||||
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, host, preferences);
|
||||
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, useRequire, host, preferences);
|
||||
return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
|
||||
}
|
||||
|
||||
@@ -281,66 +286,100 @@ namespace ts.codefix {
|
||||
// 2. add "member3" to the second import statement's import list
|
||||
// and it is up to the user to decide which one fits best.
|
||||
return firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => {
|
||||
const namespace = getNamespaceImportName(declaration);
|
||||
if (namespace) {
|
||||
const moduleSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(namespace)!);
|
||||
const namespacePrefix = getNamespaceLikeImportText(declaration);
|
||||
if (namespacePrefix) {
|
||||
const moduleSymbol = getTargetModuleFromNamespaceLikeImport(declaration, checker);
|
||||
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) {
|
||||
return { kind: ImportFixKind.UseNamespace, namespacePrefix: namespace.text, position };
|
||||
return { kind: ImportFixKind.UseNamespace, namespacePrefix, position };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getTargetModuleFromNamespaceLikeImport(declaration: AnyImportOrRequire, checker: TypeChecker) {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return checker.resolveExternalModuleName(declaration.initializer.arguments[0]);
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
return checker.getAliasedSymbol(declaration.symbol);
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
const namespaceImport = tryCast(declaration.importClause?.namedBindings, isNamespaceImport);
|
||||
return namespaceImport && checker.getAliasedSymbol(namespaceImport.symbol);
|
||||
default:
|
||||
return Debug.assertNever(declaration);
|
||||
}
|
||||
}
|
||||
|
||||
function getNamespaceLikeImportText(declaration: AnyImportOrRequire) {
|
||||
switch (declaration.kind) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
return tryCast(declaration.name, isIdentifier)?.text;
|
||||
case SyntaxKind.ImportEqualsDeclaration:
|
||||
return declaration.name.text;
|
||||
case SyntaxKind.ImportDeclaration:
|
||||
return tryCast(declaration.importClause?.namedBindings, isNamespaceImport)?.name.text;
|
||||
default:
|
||||
return Debug.assertNever(declaration);
|
||||
}
|
||||
}
|
||||
|
||||
function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], canUseTypeOnlyImport: boolean): FixAddToExistingImport | undefined {
|
||||
return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => {
|
||||
if (declaration.kind !== SyntaxKind.ImportDeclaration) return undefined;
|
||||
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) return undefined;
|
||||
if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
||||
return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern
|
||||
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, canUseTypeOnlyImport: false }
|
||||
: undefined;
|
||||
}
|
||||
const { importClause } = declaration;
|
||||
if (!importClause) return undefined;
|
||||
const { name, namedBindings } = importClause;
|
||||
return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports)
|
||||
? { kind: ImportFixKind.AddToExisting, importClause, importKind, canUseTypeOnlyImport }
|
||||
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: importClause, importKind, moduleSpecifier: declaration.moduleSpecifier.getText(), canUseTypeOnlyImport }
|
||||
: undefined;
|
||||
});
|
||||
}
|
||||
|
||||
function getNamespaceImportName(declaration: AnyImportSyntax): Identifier | undefined {
|
||||
if (declaration.kind === SyntaxKind.ImportDeclaration) {
|
||||
const namedBindings = declaration.importClause && isImportClause(declaration.importClause) && declaration.importClause.namedBindings;
|
||||
return namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport ? namedBindings.name : undefined;
|
||||
}
|
||||
else {
|
||||
return declaration.name;
|
||||
}
|
||||
}
|
||||
|
||||
function getExistingImportDeclarations({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }: SymbolExportInfo, checker: TypeChecker, sourceFile: SourceFile): readonly FixAddToExistingImportInfo[] {
|
||||
// Can't use an es6 import for a type in JS.
|
||||
return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined<StringLiteralLike, FixAddToExistingImportInfo>(sourceFile.imports, moduleSpecifier => {
|
||||
return exportedSymbolIsTypeOnly && isSourceFileJS(sourceFile) ? emptyArray : mapDefined(sourceFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
|
||||
const i = importFromModuleSpecifier(moduleSpecifier);
|
||||
return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration)
|
||||
&& checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, exportedSymbolIsTypeOnly } : undefined;
|
||||
if (isRequireVariableDeclaration(i.parent, /*requireStringLiteralLikeArgument*/ true)) {
|
||||
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind } : undefined;
|
||||
}
|
||||
if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shouldUseRequire(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {
|
||||
return isSourceFileJS(sourceFile)
|
||||
&& !sourceFile.externalModuleIndicator
|
||||
&& (!!sourceFile.commonJsModuleIndicator || getEmitModuleKind(compilerOptions) < ModuleKind.ES2015);
|
||||
}
|
||||
|
||||
function getNewImportInfos(
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
useRequire: boolean,
|
||||
moduleSymbols: readonly SymbolExportInfo[],
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
): readonly (FixAddNewImport | FixUseImportType)[] {
|
||||
const isJs = isSourceFileJS(sourceFile);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const { allowsImportingSpecifier } = createAutoImportFilter(sourceFile, program, host);
|
||||
|
||||
const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) =>
|
||||
moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap)
|
||||
moduleSpecifiers.getModuleSpecifiers(moduleSymbol, compilerOptions, 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.checkDefined(position, "position should be defined") }
|
||||
: { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: preferTypeOnlyImport }));
|
||||
: { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, useRequire, typeOnly: preferTypeOnlyImport }));
|
||||
|
||||
// Sort by presence in package.json, then shortest paths first
|
||||
return sort(choicesForEachExportingModule, (a, b) => {
|
||||
@@ -363,21 +402,21 @@ namespace ts.codefix {
|
||||
sourceFile: SourceFile,
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
useRequire: boolean,
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
): readonly (FixAddNewImport | FixUseImportType)[] {
|
||||
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport));
|
||||
return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, exportInfos, host, preferences);
|
||||
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport, useRequire));
|
||||
return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences);
|
||||
}
|
||||
|
||||
function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean): FixAddNewImport | undefined {
|
||||
const expression = declaration.kind === SyntaxKind.ImportDeclaration
|
||||
? declaration.moduleSpecifier
|
||||
: declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference
|
||||
? declaration.moduleReference.expression
|
||||
: undefined;
|
||||
return expression && isStringLiteral(expression)
|
||||
? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind, typeOnly: preferTypeOnlyImport }
|
||||
function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean, useRequire: boolean): FixAddNewImport | undefined {
|
||||
const moduleSpecifier = declaration.kind === SyntaxKind.ImportDeclaration ? declaration.moduleSpecifier :
|
||||
declaration.kind === SyntaxKind.VariableDeclaration ? declaration.initializer.arguments[0] :
|
||||
declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference ? declaration.moduleReference.expression :
|
||||
undefined;
|
||||
return moduleSpecifier && isStringLiteral(moduleSpecifier)
|
||||
? { kind: ImportFixKind.AddNew, moduleSpecifier: moduleSpecifier.text, importKind, typeOnly: preferTypeOnlyImport, useRequire }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -397,7 +436,8 @@ namespace ts.codefix {
|
||||
const symbol = checker.getAliasedSymbol(umdSymbol);
|
||||
const symbolName = umdSymbol.name;
|
||||
const exportInfos: readonly SymbolExportInfo[] = [{ moduleSymbol: symbol, importKind: getUmdImportKind(sourceFile, program.getCompilerOptions()), exportedSymbolIsTypeOnly: false }];
|
||||
const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, program, sourceFile, host, preferences);
|
||||
const useRequire = shouldUseRequire(sourceFile, program.getCompilerOptions());
|
||||
const fixes = getFixForImport(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, useRequire, program, sourceFile, host, preferences);
|
||||
return { fixes, symbolName };
|
||||
}
|
||||
function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
|
||||
@@ -425,9 +465,9 @@ namespace ts.codefix {
|
||||
case ModuleKind.CommonJS:
|
||||
case ModuleKind.UMD:
|
||||
if (isInJSFile(importingFile)) {
|
||||
return isExternalModule(importingFile) ? ImportKind.Namespace : ImportKind.ConstEquals;
|
||||
return isExternalModule(importingFile) ? ImportKind.Namespace : ImportKind.CommonJS;
|
||||
}
|
||||
return ImportKind.Equals;
|
||||
return ImportKind.CommonJS;
|
||||
case ModuleKind.System:
|
||||
case ModuleKind.ES2015:
|
||||
case ModuleKind.ES2020:
|
||||
@@ -451,10 +491,12 @@ 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, "'default' isn't a legal identifier and couldn't occur here");
|
||||
|
||||
const preferTypeOnlyImport = program.getCompilerOptions().importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
|
||||
const useRequire = shouldUseRequire(sourceFile, compilerOptions);
|
||||
const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program, host);
|
||||
const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
|
||||
getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, program, sourceFile, host, preferences)));
|
||||
getFixForImport(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences)));
|
||||
return { fixes, symbolName };
|
||||
}
|
||||
|
||||
@@ -518,19 +560,19 @@ namespace ts.codefix {
|
||||
// 2. 'import =' will not work in JavaScript, so the decision is between a default
|
||||
// and const/require.
|
||||
if (isInJSFile(importingFile)) {
|
||||
return isExternalModule(importingFile) ? ImportKind.Default : ImportKind.ConstEquals;
|
||||
return isExternalModule(importingFile) ? ImportKind.Default : ImportKind.CommonJS;
|
||||
}
|
||||
// 3. At this point the most correct choice is probably 'import =', but people
|
||||
// really hate that, so look to see if the importing file has any precedent
|
||||
// on how to handle it.
|
||||
for (const statement of importingFile.statements) {
|
||||
if (isImportEqualsDeclaration(statement)) {
|
||||
return ImportKind.Equals;
|
||||
return ImportKind.CommonJS;
|
||||
}
|
||||
}
|
||||
// 4. We have no precedent to go on, so just use a default import if
|
||||
// allowSyntheticDefaultImports/esModuleInterop is enabled.
|
||||
return allowSyntheticDefaults ? ImportKind.Default : ImportKind.Equals;
|
||||
return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS;
|
||||
}
|
||||
|
||||
function getDefaultExportInfoWorker(defaultExport: Symbol, moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
|
||||
@@ -582,16 +624,18 @@ namespace ts.codefix {
|
||||
addImportType(changes, sourceFile, fix, quotePreference);
|
||||
return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName];
|
||||
case ImportFixKind.AddToExisting: {
|
||||
const { importClause, importKind, canUseTypeOnlyImport } = fix;
|
||||
doAddExistingFix(changes, sourceFile, importClause, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(importClause.parent.moduleSpecifier.getText());
|
||||
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport, moduleSpecifier } = fix;
|
||||
doAddExistingFix(changes, sourceFile, importClauseOrBindingPattern, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
|
||||
return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
|
||||
}
|
||||
case ImportFixKind.AddNew: {
|
||||
const { importKind, moduleSpecifier, typeOnly } = fix;
|
||||
addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined, typeOnly }
|
||||
: importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined, typeOnly }
|
||||
: { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName }, typeOnly }, /*blankLineBetween*/ true);
|
||||
const { importKind, moduleSpecifier, typeOnly, useRequire } = fix;
|
||||
const addDeclarations = useRequire ? addNewRequires : addNewImports;
|
||||
const importsCollection = importKind === ImportKind.Default ? { defaultImport: symbolName, typeOnly } :
|
||||
importKind === ImportKind.Named ? { namedImports: [symbolName], typeOnly } :
|
||||
{ namespaceLikeImport: { importKind, name: symbolName }, typeOnly };
|
||||
addDeclarations(changes, sourceFile, moduleSpecifier, quotePreference, importsCollection, /*blankLineBetween*/ true);
|
||||
return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
|
||||
}
|
||||
default:
|
||||
@@ -599,7 +643,17 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void {
|
||||
function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause | ObjectBindingPattern, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void {
|
||||
if (clause.kind === SyntaxKind.ObjectBindingPattern) {
|
||||
if (defaultImport) {
|
||||
addElementToBindingPattern(clause, defaultImport, "default");
|
||||
}
|
||||
for (const specifier of namedImports) {
|
||||
addElementToBindingPattern(clause, specifier, /*propertyName*/ undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly;
|
||||
if (defaultImport) {
|
||||
Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one");
|
||||
@@ -629,6 +683,16 @@ namespace ts.codefix {
|
||||
if (convertTypeOnlyToRegular) {
|
||||
changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
|
||||
}
|
||||
|
||||
function addElementToBindingPattern(bindingPattern: ObjectBindingPattern, name: string, propertyName: string | undefined) {
|
||||
const element = createBindingElement(/*dotDotDotToken*/ undefined, propertyName, name);
|
||||
if (bindingPattern.elements.length) {
|
||||
changes.insertNodeInListAfter(sourceFile, last(bindingPattern.elements), element);
|
||||
}
|
||||
else {
|
||||
changes.replaceNode(sourceFile, bindingPattern, createObjectBindingPattern([element]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, position }: FixUseNamespaceImport): void {
|
||||
@@ -646,39 +710,68 @@ namespace ts.codefix {
|
||||
|
||||
interface ImportsCollection {
|
||||
readonly typeOnly: boolean;
|
||||
readonly defaultImport: string | undefined;
|
||||
readonly namedImports: string[];
|
||||
readonly namespaceLikeImport: {
|
||||
readonly importKind: ImportKind.Equals | ImportKind.Namespace | ImportKind.ConstEquals;
|
||||
readonly defaultImport?: string;
|
||||
readonly namedImports?: string[];
|
||||
readonly namespaceLikeImport?: {
|
||||
readonly importKind: ImportKind.CommonJS | ImportKind.Namespace;
|
||||
readonly name: string;
|
||||
} | undefined;
|
||||
};
|
||||
}
|
||||
function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport, typeOnly }: ImportsCollection, blankLineBetween: boolean): void {
|
||||
function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection, blankLineBetween: boolean): void {
|
||||
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
|
||||
if (defaultImport !== undefined || namedImports.length) {
|
||||
if (imports.defaultImport !== undefined || imports.namedImports?.length) {
|
||||
insertImport(changes, sourceFile,
|
||||
makeImport(
|
||||
defaultImport === undefined ? undefined : createIdentifier(defaultImport),
|
||||
namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference, typeOnly), /*blankLineBetween*/ blankLineBetween);
|
||||
imports.defaultImport === undefined ? undefined : createIdentifier(imports.defaultImport),
|
||||
imports.namedImports?.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference, imports.typeOnly), /*blankLineBetween*/ blankLineBetween);
|
||||
}
|
||||
const { namespaceLikeImport, typeOnly } = imports;
|
||||
if (namespaceLikeImport) {
|
||||
insertImport(
|
||||
changes,
|
||||
sourceFile,
|
||||
namespaceLikeImport.importKind === ImportKind.Equals ? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(namespaceLikeImport.name), createExternalModuleReference(quotedModuleSpecifier)) :
|
||||
namespaceLikeImport.importKind === ImportKind.ConstEquals ? createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier) :
|
||||
createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name)), typeOnly), quotedModuleSpecifier), /*blankLineBetween*/ blankLineBetween);
|
||||
const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
|
||||
? createImportEqualsDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
createIdentifier(namespaceLikeImport.name),
|
||||
createExternalModuleReference(quotedModuleSpecifier))
|
||||
: createImportDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
createImportClause(
|
||||
/*name*/ undefined,
|
||||
createNamespaceImport(createIdentifier(namespaceLikeImport.name)),
|
||||
typeOnly),
|
||||
quotedModuleSpecifier);
|
||||
insertImport(changes, sourceFile, declaration, /*blankLineBetween*/ blankLineBetween);
|
||||
}
|
||||
}
|
||||
|
||||
function createConstEqualsRequireDeclaration(name: string, quotedModuleSpecifier: StringLiteral): VariableStatement {
|
||||
return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([
|
||||
createVariableDeclaration(
|
||||
createIdentifier(name),
|
||||
/*type*/ undefined,
|
||||
createCall(createIdentifier("require"), /*typeArguments*/ undefined, [quotedModuleSpecifier])
|
||||
)
|
||||
], NodeFlags.Const));
|
||||
function addNewRequires(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection, blankLineBetween: boolean) {
|
||||
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
|
||||
// const { default: foo, bar, etc } = require('./mod');
|
||||
if (imports.defaultImport || imports.namedImports?.length) {
|
||||
const bindingElements = imports.namedImports?.map(name => createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
|
||||
if (imports.defaultImport) {
|
||||
bindingElements.unshift(createBindingElement(/*dotDotDotToken*/ undefined, "default", imports.defaultImport));
|
||||
}
|
||||
const declaration = createConstEqualsRequireDeclaration(createObjectBindingPattern(bindingElements), quotedModuleSpecifier);
|
||||
insertImport(changes, sourceFile, declaration, blankLineBetween);
|
||||
}
|
||||
// const foo = require('./mod');
|
||||
if (imports.namespaceLikeImport) {
|
||||
const declaration = createConstEqualsRequireDeclaration(imports.namespaceLikeImport.name, quotedModuleSpecifier);
|
||||
insertImport(changes, sourceFile, declaration, blankLineBetween);
|
||||
}
|
||||
}
|
||||
|
||||
function createConstEqualsRequireDeclaration(name: string | ObjectBindingPattern, quotedModuleSpecifier: StringLiteral): VariableStatement {
|
||||
return createVariableStatement(
|
||||
/*modifiers*/ undefined,
|
||||
createVariableDeclarationList([
|
||||
createVariableDeclaration(
|
||||
typeof name === "string" ? createIdentifier(name) : name,
|
||||
/*type*/ undefined,
|
||||
createCall(createIdentifier("require"), /*typeArguments*/ undefined, [quotedModuleSpecifier]))],
|
||||
NodeFlags.Const));
|
||||
}
|
||||
|
||||
function symbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
|
||||
|
||||
@@ -1384,16 +1384,14 @@ namespace ts.Completions {
|
||||
}
|
||||
|
||||
function shouldOfferImportCompletions(): boolean {
|
||||
// If not already a module, must have modules enabled and not currently be in a commonjs module. (TODO: import completions for commonjs)
|
||||
// If not already a module, must have modules enabled.
|
||||
if (!preferences.includeCompletionsForModuleExports) return false;
|
||||
// If already using ES6 modules, OK to continue using them.
|
||||
if (sourceFile.externalModuleIndicator) return true;
|
||||
// If already using commonjs, don't introduce ES6.
|
||||
if (sourceFile.commonJsModuleIndicator) return false;
|
||||
if (sourceFile.externalModuleIndicator || sourceFile.commonJsModuleIndicator) return true;
|
||||
// If module transpilation is enabled or we're targeting es6 or above, or not emitting, OK.
|
||||
if (compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) return true;
|
||||
// If some file is using ES6 modules, assume that it's OK to add more.
|
||||
return programContainsEs6Modules(program);
|
||||
return programContainsModules(program);
|
||||
}
|
||||
|
||||
function isSnippetScope(scopeNode: Node): boolean {
|
||||
@@ -1557,6 +1555,7 @@ namespace ts.Completions {
|
||||
const startTime = timestamp();
|
||||
log(`getSymbolsFromOtherSourceFileExports: Recomputing list${detailsEntryId ? " for details entry" : ""}`);
|
||||
const seenResolvedModules = createMap<true>();
|
||||
const seenExports = createMap<true>();
|
||||
/** Bucket B */
|
||||
const aliasesToAlreadyIncludedSymbols = createMap<true>();
|
||||
/** Bucket C */
|
||||
@@ -1580,18 +1579,21 @@ namespace ts.Completions {
|
||||
|
||||
// Don't add another completion for `export =` of a symbol that's already global.
|
||||
// So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`.
|
||||
if (resolvedModuleSymbol !== moduleSymbol &&
|
||||
every(resolvedModuleSymbol.declarations, d => !!d.getSourceFile().externalModuleIndicator && !findAncestor(d, isGlobalScopeAugmentation))) {
|
||||
if (resolvedModuleSymbol !== moduleSymbol && every(resolvedModuleSymbol.declarations, isNonGlobalDeclaration)) {
|
||||
pushSymbol(resolvedModuleSymbol, moduleSymbol, /*skipFilter*/ true);
|
||||
}
|
||||
|
||||
for (const symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
|
||||
for (const symbol of typeChecker.getExportsAndPropertiesOfModule(moduleSymbol)) {
|
||||
const symbolId = getSymbolId(symbol).toString();
|
||||
// `getExportsAndPropertiesOfModule` can include duplicates
|
||||
if (!addToSeen(seenExports, symbolId)) {
|
||||
continue;
|
||||
}
|
||||
// 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))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const symbolId = getSymbolId(symbol).toString();
|
||||
// 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).
|
||||
@@ -2683,4 +2685,14 @@ namespace ts.Completions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNonGlobalDeclaration(declaration: Declaration) {
|
||||
const sourceFile = declaration.getSourceFile();
|
||||
// If the file is not a module, the declaration is global
|
||||
if (!sourceFile.externalModuleIndicator && !sourceFile.commonJsModuleIndicator) {
|
||||
return false;
|
||||
}
|
||||
// If the file is a module written in TypeScript, it still might be in a `declare global` augmentation
|
||||
return isInJSFile(declaration) || !findAncestor(declaration, isGlobalScopeAugmentation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1691,6 +1691,9 @@ namespace ts {
|
||||
: isPrivateIdentifier(name) ? idText(name) : getTextOfIdentifierOrLiteral(name);
|
||||
}
|
||||
|
||||
export function programContainsModules(program: Program): boolean {
|
||||
return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!(s.externalModuleIndicator || s.commonJsModuleIndicator));
|
||||
}
|
||||
export function programContainsEs6Modules(program: Program): boolean {
|
||||
return program.getSourceFiles().some(s => !s.isDeclarationFile && !program.isSourceFileFromExternalLibrary(s) && !!s.externalModuleIndicator);
|
||||
}
|
||||
@@ -1832,7 +1835,8 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement, blankLineBetween: boolean): void {
|
||||
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
|
||||
const importKindPredicate = importDecl.kind === SyntaxKind.VariableStatement ? isRequireVariableDeclarationStatement : isAnyImportSyntax;
|
||||
const lastImportDeclaration = findLast(sourceFile.statements, statement => importKindPredicate(statement));
|
||||
if (lastImportDeclaration) {
|
||||
changes.insertNodeAfter(sourceFile, lastImportDeclaration, importDecl);
|
||||
}
|
||||
|
||||
@@ -34,12 +34,7 @@
|
||||
////fo/*dts*/
|
||||
|
||||
verify.completions({
|
||||
marker: ["b"],
|
||||
excludes: "foo",
|
||||
preferences: { includeCompletionsForModuleExports: true }
|
||||
});
|
||||
verify.completions({
|
||||
marker: ["c", "ccheck", "cts", "d", "dcheck", "dts"],
|
||||
marker: ["b", "c", "ccheck", "cts", "d", "dcheck", "dts"],
|
||||
includes: [{
|
||||
name: "foo",
|
||||
source: "/node_modules/a/index",
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /a.js
|
||||
////const x = 0;
|
||||
////module.exports = { x };
|
||||
|
||||
// @Filename: /b.js
|
||||
////x/**/
|
||||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
includes: {
|
||||
name: "x",
|
||||
source: "/a",
|
||||
sourceDisplay: "./a",
|
||||
text: "(property) x: number",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
},
|
||||
preferences: { includeCompletionsForModuleExports: true },
|
||||
});
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "x",
|
||||
source: "/a",
|
||||
description: `Import 'x' from module "./a"`,
|
||||
newFileContent: `const { x } = require("./a");
|
||||
|
||||
x`,
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /a.js
|
||||
////const x = 0;
|
||||
////function f() {}
|
||||
////module.exports = { x, f };
|
||||
|
||||
// @Filename: /b.js
|
||||
////const { f } = require("./a");
|
||||
////
|
||||
////x/**/
|
||||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
includes: {
|
||||
name: "x",
|
||||
source: "/a",
|
||||
sourceDisplay: "./a",
|
||||
text: "(property) x: number",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
},
|
||||
preferences: { includeCompletionsForModuleExports: true },
|
||||
});
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "x",
|
||||
source: "/a",
|
||||
description: `Add 'x' to existing import declaration from "./a"`,
|
||||
newFileContent: `const { f, x } = require("./a");
|
||||
|
||||
x`,
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: esnext
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
////BrowserRouter/**/
|
||||
|
||||
goTo.file("/project/index.js");
|
||||
verify.importFixAtPosition([`import { BrowserRouter } from "react-router-dom";
|
||||
verify.importFixAtPosition([`const { BrowserRouter } = require("react-router-dom");
|
||||
|
||||
BrowserRouter`]);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @module: esnext
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: esnext
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: foo.js
|
||||
////module.exports = function foo() {}
|
||||
|
||||
// @Filename: utils.js
|
||||
////function util1() {}
|
||||
////function util2() {}
|
||||
////module.exports = { util1, util2 };
|
||||
|
||||
// @Filename: blah.js
|
||||
////export default class Blah {}
|
||||
|
||||
// @Filename: index.js
|
||||
////foo();
|
||||
////util1();
|
||||
////util2();
|
||||
////new Blah;
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFixAll({
|
||||
fixId: "fixMissingImport",
|
||||
fixAllDescription: "Add all missing imports",
|
||||
newFileContent:
|
||||
`const foo = require("./foo");
|
||||
const { util1, util2 } = require("./utils");
|
||||
const { default: Blah } = require("./blah");
|
||||
|
||||
foo();
|
||||
util1();
|
||||
util2();
|
||||
new Blah;`
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: umd.d.ts
|
||||
////namespace Foo { function f() {} }
|
||||
////export = Foo;
|
||||
////export as namespace Foo;
|
||||
|
||||
// @Filename: index.js
|
||||
////Foo;
|
||||
////module.exports = {};
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
description: `Import 'Foo' from module "./umd"`,
|
||||
newFileContent:
|
||||
`const Foo = require("./umd");
|
||||
|
||||
Foo;
|
||||
module.exports = {};`
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: blah.js
|
||||
////export default class Blah {}
|
||||
////export const Named1 = 0;
|
||||
////export const Named2 = 1;
|
||||
|
||||
// @Filename: index.js
|
||||
////var path = require('path')
|
||||
//// , { promisify } = require('util')
|
||||
//// , { Named1 } = require('./blah')
|
||||
////
|
||||
////new Blah
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Add default import 'Blah' to existing import declaration from "./blah"`,
|
||||
newFileContent:
|
||||
`var path = require('path')
|
||||
, { promisify } = require('util')
|
||||
, { Named1, default: Blah } = require('./blah')
|
||||
|
||||
new Blah`
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// If a file has both `require` and `import` declarations,
|
||||
// prefer whichever can be used for an "add to existing" action.
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: blah.js
|
||||
////export default class Blah {}
|
||||
////export const Named1 = 0;
|
||||
////export const Named2 = 1;
|
||||
|
||||
// @Filename: index.js
|
||||
////var path = require('path')
|
||||
//// , { promisify } = require('util')
|
||||
//// , { Named1 } = require('./blah')
|
||||
////
|
||||
////import fs from 'fs'
|
||||
////
|
||||
////new Blah
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Add default import 'Blah' to existing import declaration from "./blah"`,
|
||||
newFileContent:
|
||||
`var path = require('path')
|
||||
, { promisify } = require('util')
|
||||
, { Named1, default: Blah } = require('./blah')
|
||||
|
||||
import fs from 'fs'
|
||||
|
||||
new Blah`
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: blah.js
|
||||
////export default class Blah {}
|
||||
////export const Named1 = 0;
|
||||
////export const Named2 = 1;
|
||||
|
||||
// @Filename: addToExisting.js
|
||||
////const { Named2 } = require('./blah')
|
||||
////import { Named1 } from './blah'
|
||||
////
|
||||
////new Blah
|
||||
|
||||
// @Filename: newImport.js
|
||||
////import fs from 'fs';
|
||||
////const path = require('path');
|
||||
////
|
||||
////new Blah
|
||||
|
||||
// If an "add to existing" fix could be applied both to an `import`
|
||||
// and to a `require` declaration, prefer the `import`.
|
||||
goTo.file("addToExisting.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Add default import 'Blah' to existing import declaration from "./blah"`,
|
||||
newFileContent:
|
||||
`const { Named2 } = require('./blah')
|
||||
import Blah, { Named1 } from './blah'
|
||||
|
||||
new Blah`
|
||||
});
|
||||
|
||||
// If a file contains `import` and `require` declarations but none
|
||||
// can be used for an "add to existing" fix, prefer `import` for the
|
||||
// new declaration.
|
||||
goTo.file("newImport.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Import default 'Blah' from module "./blah"`,
|
||||
newFileContent:
|
||||
`import fs from 'fs';
|
||||
import Blah from './blah';
|
||||
const path = require('path');
|
||||
|
||||
new Blah`
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
// If the module target is es2015+ and the file has no existing CommonJS
|
||||
// indicators, use `import` declarations.
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @module: es2015
|
||||
|
||||
// @Filename: a.js
|
||||
////export const x = 0;
|
||||
|
||||
// @Filename: index.js
|
||||
////x
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Import 'x' from module "./a"`,
|
||||
applyChanges: false,
|
||||
newFileContent:
|
||||
`import { x } from "./a";
|
||||
|
||||
x`
|
||||
});
|
||||
|
||||
// If the module target is es2015+ but the file already uses `require`
|
||||
// (and not `import`), use `require`.
|
||||
goTo.position(0);
|
||||
edit.insertLine("const fs = require('fs');\n");
|
||||
verify.codeFix({
|
||||
index: 0,
|
||||
errorCode: ts.Diagnostics.Cannot_find_name_0.code,
|
||||
description: `Import 'x' from module "./a"`,
|
||||
applyChanges: false,
|
||||
newFileContent:
|
||||
`const fs = require('fs');
|
||||
const { x } = require('./a');
|
||||
|
||||
x`
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
|
||||
// @Filename: blah.js
|
||||
////export default class Blah {}
|
||||
////export const Named1 = 0;
|
||||
////export const Named2 = 1;
|
||||
|
||||
// @Filename: index.js
|
||||
////Named1 + Named2;
|
||||
////new Blah;
|
||||
|
||||
goTo.file("index.js");
|
||||
verify.codeFixAll({
|
||||
fixId: "fixMissingImport",
|
||||
fixAllDescription: "Add all missing imports",
|
||||
newFileContent:
|
||||
`const { default: Blah, Named1, Named2 } = require("./blah");
|
||||
|
||||
Named1 + Named2;
|
||||
new Blah;`
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @module: esnext
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /a.js
|
||||
@@ -16,10 +17,8 @@
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/a.js":
|
||||
// TODO: GH#22330
|
||||
`const { y, z } = require("./y");
|
||||
|
||||
const { a, } = require("./other");
|
||||
`const { a, } = require("./other");
|
||||
const { y, z } = require("./y");
|
||||
const p = 0;
|
||||
exports.p = p;
|
||||
a; y; z;`,
|
||||
|
||||
Reference in New Issue
Block a user