mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Fix auto import crash on weird JS aliasing (#46490)
* Fix auto import crash on weird JS aliasing * Comment up the weird test * Fix setting members on union/intersection type, happens later
This commit is contained in:
@@ -3656,8 +3656,8 @@ namespace ts {
|
||||
if (exportEquals !== moduleSymbol) {
|
||||
const type = getTypeOfSymbol(exportEquals);
|
||||
if (shouldTreatPropertiesOfExternalModuleAsExports(type)) {
|
||||
getPropertiesOfType(type).forEach(symbol => {
|
||||
cb(symbol, symbol.escapedName);
|
||||
forEachPropertyOfType(type, (symbol, escapedName) => {
|
||||
cb(symbol, escapedName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4033,13 +4033,17 @@ namespace ts {
|
||||
function getNamedMembers(members: SymbolTable): Symbol[] {
|
||||
let result: Symbol[] | undefined;
|
||||
members.forEach((symbol, id) => {
|
||||
if (!isReservedMemberName(id) && symbolIsValue(symbol)) {
|
||||
if (isNamedMember(symbol, id)) {
|
||||
(result || (result = [])).push(symbol);
|
||||
}
|
||||
});
|
||||
return result || emptyArray;
|
||||
}
|
||||
|
||||
function isNamedMember(member: Symbol, escapedName: __String) {
|
||||
return !isReservedMemberName(escapedName) && symbolIsValue(member);
|
||||
}
|
||||
|
||||
function getNamedOrIndexSignatureMembers(members: SymbolTable): Symbol[] {
|
||||
const result = getNamedMembers(members);
|
||||
const index = getIndexSymbolFromSymbolTable(members);
|
||||
@@ -11571,6 +11575,17 @@ namespace ts {
|
||||
getPropertiesOfObjectType(type);
|
||||
}
|
||||
|
||||
function forEachPropertyOfType(type: Type, action: (symbol: Symbol, escapedName: __String) => void): void {
|
||||
type = getReducedApparentType(type);
|
||||
if (type.flags & TypeFlags.StructuredType) {
|
||||
resolveStructuredTypeMembers(type as StructuredType).members.forEach((symbol, escapedName) => {
|
||||
if (isNamedMember(symbol, escapedName)) {
|
||||
action(symbol, escapedName);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
|
||||
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
|
||||
return list.some(property => {
|
||||
|
||||
@@ -268,7 +268,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
export function getImportCompletionAction(
|
||||
exportedSymbol: Symbol,
|
||||
targetSymbol: Symbol,
|
||||
moduleSymbol: Symbol,
|
||||
sourceFile: SourceFile,
|
||||
symbolName: string,
|
||||
@@ -280,8 +280,8 @@ namespace ts.codefix {
|
||||
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name))
|
||||
? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, program, host)]
|
||||
: getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true);
|
||||
? [getSymbolExportInfoForSymbol(targetSymbol, moduleSymbol, program, host)]
|
||||
: getAllReExportingModules(sourceFile, targetSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, isValidTypeOnlyUseSite, useRequire, host, preferences));
|
||||
@@ -326,7 +326,7 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getAllReExportingModules(importingFile: SourceFile, exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, preferences: UserPreferences, useAutoImportProvider: boolean): readonly SymbolExportInfo[] {
|
||||
function getAllReExportingModules(importingFile: SourceFile, targetSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, host: LanguageServiceHost, program: Program, preferences: UserPreferences, useAutoImportProvider: boolean): readonly SymbolExportInfo[] {
|
||||
const result: SymbolExportInfo[] = [];
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => {
|
||||
@@ -341,12 +341,12 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions)) === symbolName) && skipAlias(defaultInfo.symbol, checker) === exportedSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions)) === symbolName) && skipAlias(defaultInfo.symbol, checker) === targetSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
|
||||
result.push({ symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: defaultInfo.exportKind, targetFlags: skipAlias(defaultInfo.symbol, checker).flags, isFromPackageJson });
|
||||
}
|
||||
|
||||
for (const exported of checker.getExportsAndPropertiesOfModule(moduleSymbol)) {
|
||||
if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
|
||||
if (exported.name === symbolName && checker.getMergedSymbol(skipAlias(exported, checker)) === targetSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
|
||||
result.push({ symbol: exported, moduleSymbol, moduleFileName: moduleFile?.fileName, exportKind: ExportKind.Named, targetFlags: skipAlias(exported, checker).flags, isFromPackageJson });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1183,9 +1183,9 @@ namespace ts.Completions {
|
||||
|
||||
const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker();
|
||||
const { moduleSymbol } = origin;
|
||||
const exportedSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker));
|
||||
const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker));
|
||||
const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction(
|
||||
exportedSymbol,
|
||||
targetSymbol,
|
||||
moduleSymbol,
|
||||
sourceFile,
|
||||
getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions)),
|
||||
|
||||
@@ -79,10 +79,14 @@ namespace ts {
|
||||
}
|
||||
const isDefault = exportKind === ExportKind.Default;
|
||||
const namedSymbol = isDefault && getLocalSymbolForExportDefault(symbol) || symbol;
|
||||
// A re-export merged with an export from a module augmentation can result in `symbol`
|
||||
// being an external module symbol; the name it is re-exported by will be `symbolTableKey`
|
||||
// (which comes from the keys of `moduleSymbol.exports`.)
|
||||
const importedName = isExternalModuleSymbol(namedSymbol)
|
||||
// 1. A named export must be imported by its key in `moduleSymbol.exports` or `moduleSymbol.members`.
|
||||
// 2. A re-export merged with an export from a module augmentation can result in `symbol`
|
||||
// being an external module symbol; the name it is re-exported by will be `symbolTableKey`
|
||||
// (which comes from the keys of `moduleSymbol.exports`.)
|
||||
// 3. Otherwise, we have a default/namespace import that can be imported by any name, and
|
||||
// `symbolTableKey` will be something undesirable like `export=` or `default`, so we try to
|
||||
// get a better name.
|
||||
const importedName = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol)
|
||||
? unescapeLeadingUnderscores(symbolTableKey)
|
||||
: getNameForExportedSymbol(namedSymbol, scriptTarget);
|
||||
const moduleName = stripQuotes(moduleSymbol.name);
|
||||
@@ -321,7 +325,7 @@ namespace ts {
|
||||
let moduleCount = 0;
|
||||
forEachExternalModuleToImportFrom(program, host, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
|
||||
if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested();
|
||||
const seenExports = new Map<Symbol, true>();
|
||||
const seenExports = new Map<__String, true>();
|
||||
const checker = program.getTypeChecker();
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
// Note: I think we shouldn't actually see resolved module symbols here, but weird merges
|
||||
@@ -339,7 +343,7 @@ namespace ts {
|
||||
checker);
|
||||
}
|
||||
checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => {
|
||||
if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, exported)) {
|
||||
if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) {
|
||||
cache.add(
|
||||
importingFile.path,
|
||||
exported,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/// <reference path="../fourslash.ts" />
|
||||
|
||||
// @Filename: /tsconfig.json
|
||||
//// { "compilerOptions": { "module": "commonjs", "allowJs": true } }
|
||||
|
||||
// @Filename: /third_party/marked/src/defaults.js
|
||||
//// function getDefaults() {
|
||||
//// return {
|
||||
//// baseUrl: null,
|
||||
//// };
|
||||
//// }
|
||||
////
|
||||
//// function changeDefaults(newDefaults) {
|
||||
//// module.exports.defaults = newDefaults;
|
||||
//// }
|
||||
////
|
||||
//// module.exports = {
|
||||
//// defaults: getDefaults(),
|
||||
//// getDefaults,
|
||||
//// changeDefaults
|
||||
//// };
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// /**/
|
||||
|
||||
format.setOption("newLineCharacter", "\n")
|
||||
goTo.marker("");
|
||||
|
||||
// Create the exportInfoMap
|
||||
verify.completions({ marker: "", preferences: { includeCompletionsForModuleExports: true } });
|
||||
|
||||
// Create a new program and reuse the exportInfoMap from the last request
|
||||
edit.insert("d");
|
||||
verify.completions({
|
||||
marker: "",
|
||||
excludes: ["newDefaults"],
|
||||
includes: [{
|
||||
name: "defaults",
|
||||
source: "/third_party/marked/src/defaults",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions,
|
||||
}],
|
||||
preferences: { includeCompletionsForModuleExports: true }
|
||||
});
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "defaults",
|
||||
source: "/third_party/marked/src/defaults",
|
||||
description: `Import 'defaults' from module "./third_party/marked/src/defaults"`,
|
||||
data: {
|
||||
exportName: "defaults",
|
||||
fileName: "/third_party/marked/src/defaults.js",
|
||||
},
|
||||
newFileContent: `import { defaults } from "./third_party/marked/src/defaults";
|
||||
|
||||
d`
|
||||
});
|
||||
Reference in New Issue
Block a user