mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Refactor default export info name gathering (#58460)
This commit is contained in:
@@ -1900,6 +1900,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
getMemberOverrideModifierStatus,
|
||||
isTypeParameterPossiblyReferenced,
|
||||
typeHasCallOrConstructSignatures,
|
||||
getSymbolFlags,
|
||||
};
|
||||
|
||||
function getCandidateSignaturesForStringLiteralCompletions(call: CallLikeExpression, editingArgument: Node) {
|
||||
|
||||
@@ -5361,6 +5361,7 @@ export interface TypeChecker {
|
||||
/** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus;
|
||||
/** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean;
|
||||
/** @internal */ typeHasCallOrConstructSignatures(type: Type): boolean;
|
||||
/** @internal */ getSymbolFlags(symbol: Symbol): SymbolFlags;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
createCodeFixActionWithoutFixAll,
|
||||
moduleSpecifierToValidIdentifier,
|
||||
registerCodeFix,
|
||||
} from "../_namespaces/ts.codefix.js";
|
||||
import {
|
||||
@@ -59,6 +58,7 @@ import {
|
||||
mapIterator,
|
||||
MethodDeclaration,
|
||||
Modifier,
|
||||
moduleSpecifierToValidIdentifier,
|
||||
Node,
|
||||
NodeArray,
|
||||
NodeFlags,
|
||||
|
||||
@@ -41,12 +41,12 @@ import {
|
||||
flatMap,
|
||||
flatMapIterator,
|
||||
forEachExternalModuleToImportFrom,
|
||||
forEachNameOfDefaultExport,
|
||||
formatting,
|
||||
FutureSourceFile,
|
||||
FutureSymbolExportInfo,
|
||||
getAllowSyntheticDefaultImports,
|
||||
getBaseFileName,
|
||||
getDefaultExportInfoWorker,
|
||||
getDefaultLikeExportInfo,
|
||||
getDirectoryPath,
|
||||
getEmitModuleFormatOfFileWorker,
|
||||
@@ -55,7 +55,6 @@ import {
|
||||
getEmitScriptTarget,
|
||||
getExportInfoMap,
|
||||
getImpliedNodeFormatForEmitWorker,
|
||||
getMeaningFromDeclaration,
|
||||
getMeaningFromLocation,
|
||||
getNameForExportedSymbol,
|
||||
getOutputExtension,
|
||||
@@ -71,6 +70,7 @@ import {
|
||||
hasJSFileExtension,
|
||||
hostGetCanonicalFileName,
|
||||
Identifier,
|
||||
identity,
|
||||
ImportClause,
|
||||
ImportEqualsDeclaration,
|
||||
importFromModuleSpecifier,
|
||||
@@ -81,8 +81,6 @@ import {
|
||||
isExternalModuleReference,
|
||||
isFullSourceFile,
|
||||
isIdentifier,
|
||||
isIdentifierPart,
|
||||
isIdentifierStart,
|
||||
isImportableFile,
|
||||
isImportDeclaration,
|
||||
isImportEqualsDeclaration,
|
||||
@@ -96,7 +94,6 @@ import {
|
||||
isNamespaceImport,
|
||||
isRequireVariableStatement,
|
||||
isSourceFileJS,
|
||||
isStringANonContextualKeyword,
|
||||
isStringLiteral,
|
||||
isStringLiteralLike,
|
||||
isTypeOnlyImportDeclaration,
|
||||
@@ -114,6 +111,7 @@ import {
|
||||
ModuleKind,
|
||||
moduleResolutionUsesNodeModules,
|
||||
moduleSpecifiers,
|
||||
moduleSymbolToValidIdentifier,
|
||||
MultiMap,
|
||||
Mutable,
|
||||
NamedImports,
|
||||
@@ -129,12 +127,9 @@ import {
|
||||
pathIsBareSpecifier,
|
||||
Program,
|
||||
QuotePreference,
|
||||
removeFileExtension,
|
||||
removeSuffix,
|
||||
RequireOrImportCall,
|
||||
RequireVariableStatement,
|
||||
sameMap,
|
||||
ScriptTarget,
|
||||
SemanticMeaning,
|
||||
shouldUseUriStyleNodeCoreModules,
|
||||
single,
|
||||
@@ -873,7 +868,6 @@ function getAllExportInfoForSymbol(importingFile: SourceFile | FutureSourceFile,
|
||||
}
|
||||
|
||||
function getSingleExportInfoForSymbol(symbol: Symbol, symbolName: string, moduleSymbol: Symbol, program: Program, host: LanguageServiceHost): SymbolExportInfo {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const mainProgramInfo = getInfoWithChecker(program.getTypeChecker(), /*isFromPackageJson*/ false);
|
||||
if (mainProgramInfo) {
|
||||
return mainProgramInfo;
|
||||
@@ -882,7 +876,7 @@ function getSingleExportInfoForSymbol(symbol: Symbol, symbolName: string, module
|
||||
return Debug.checkDefined(autoImportProvider && getInfoWithChecker(autoImportProvider, /*isFromPackageJson*/ true), `Could not find symbol in specified module for code actions`);
|
||||
|
||||
function getInfoWithChecker(checker: TypeChecker, isFromPackageJson: boolean): SymbolExportInfo | undefined {
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker);
|
||||
if (defaultInfo && skipAlias(defaultInfo.symbol, checker) === symbol) {
|
||||
return { symbol: defaultInfo.symbol, moduleSymbol, moduleFileName: undefined, exportKind: defaultInfo.exportKind, targetFlags: skipAlias(symbol, checker).flags, isFromPackageJson };
|
||||
}
|
||||
@@ -1194,7 +1188,7 @@ function getNewImportFixes(
|
||||
const exportEquals = checker.resolveExternalModuleSymbol(exportInfo.moduleSymbol);
|
||||
let namespacePrefix;
|
||||
if (exportEquals !== exportInfo.moduleSymbol) {
|
||||
namespacePrefix = getDefaultExportInfoWorker(exportEquals, checker, compilerOptions)?.name;
|
||||
namespacePrefix = forEachNameOfDefaultExport(exportEquals, checker, compilerOptions, /*preferCapitalizedNames*/ false, identity)!;
|
||||
}
|
||||
namespacePrefix ||= moduleSymbolToValidIdentifier(
|
||||
exportInfo.moduleSymbol,
|
||||
@@ -1533,14 +1527,18 @@ function getExportInfos(
|
||||
cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === symbolName) && symbolHasMeaning(defaultInfo.resolvedSymbol, currentTokenMeaning)) {
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker);
|
||||
if (
|
||||
defaultInfo
|
||||
&& symbolFlagsHaveMeaning(checker.getSymbolFlags(defaultInfo.symbol), currentTokenMeaning)
|
||||
&& forEachNameOfDefaultExport(defaultInfo.symbol, checker, compilerOptions, isJsxTagName, name => name === symbolName)
|
||||
) {
|
||||
addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson);
|
||||
}
|
||||
|
||||
// check exports with the same name
|
||||
const exportSymbolWithIdenticalName = checker.tryGetMemberInModuleExportsAndProperties(symbolName, moduleSymbol);
|
||||
if (exportSymbolWithIdenticalName && symbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) {
|
||||
if (exportSymbolWithIdenticalName && symbolFlagsHaveMeaning(checker.getSymbolFlags(exportSymbolWithIdenticalName), currentTokenMeaning)) {
|
||||
addSymbol(moduleSymbol, sourceFile, exportSymbolWithIdenticalName, ExportKind.Named, program, isFromPackageJson);
|
||||
}
|
||||
});
|
||||
@@ -2009,44 +2007,12 @@ function createConstEqualsRequireDeclaration(name: string | ObjectBindingPattern
|
||||
) as RequireVariableStatement;
|
||||
}
|
||||
|
||||
function symbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
|
||||
return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined, forceCapitalize: boolean): string {
|
||||
return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target, forceCapitalize);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget | undefined, forceCapitalize?: boolean): string {
|
||||
const baseName = getBaseFileName(removeSuffix(moduleSpecifier, "/index"));
|
||||
let res = "";
|
||||
let lastCharWasValid = true;
|
||||
const firstCharCode = baseName.charCodeAt(0);
|
||||
if (isIdentifierStart(firstCharCode, target)) {
|
||||
res += String.fromCharCode(firstCharCode);
|
||||
if (forceCapitalize) {
|
||||
res = res.toUpperCase();
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastCharWasValid = false;
|
||||
}
|
||||
for (let i = 1; i < baseName.length; i++) {
|
||||
const ch = baseName.charCodeAt(i);
|
||||
const isValid = isIdentifierPart(ch, target);
|
||||
if (isValid) {
|
||||
let char = String.fromCharCode(ch);
|
||||
if (!lastCharWasValid) {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
res += char;
|
||||
}
|
||||
lastCharWasValid = isValid;
|
||||
}
|
||||
// Need `|| "_"` to ensure result isn't empty.
|
||||
return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`;
|
||||
function symbolFlagsHaveMeaning(flags: SymbolFlags, meaning: SemanticMeaning): boolean {
|
||||
return meaning === SemanticMeaning.All ? true :
|
||||
meaning & SemanticMeaning.Value ? !!(flags & SymbolFlags.Value) :
|
||||
meaning & SemanticMeaning.Type ? !!(flags & SymbolFlags.Type) :
|
||||
meaning & SemanticMeaning.Namespace ? !!(flags & SymbolFlags.Namespace) :
|
||||
false;
|
||||
}
|
||||
|
||||
function getImpliedNodeFormatForEmit(file: SourceFile | FutureSourceFile, program: Program) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
__String,
|
||||
addToSeen,
|
||||
append,
|
||||
arrayIsEqualTo,
|
||||
CancellationToken,
|
||||
CompilerOptions,
|
||||
@@ -10,15 +11,15 @@ import {
|
||||
emptyArray,
|
||||
ensureTrailingDirectorySeparator,
|
||||
findIndex,
|
||||
firstDefined,
|
||||
forEachAncestorDirectory,
|
||||
forEachEntry,
|
||||
FutureSourceFile,
|
||||
getBaseFileName,
|
||||
GetCanonicalFileName,
|
||||
getDefaultLikeExportNameFromDeclaration,
|
||||
getDirectoryPath,
|
||||
getEmitScriptTarget,
|
||||
getLocalSymbolForExportDefault,
|
||||
getNameForExportedSymbol,
|
||||
getNamesForExportedSymbol,
|
||||
getNodeModulePathParts,
|
||||
getPackageNameFromTypesPackageName,
|
||||
@@ -28,12 +29,9 @@ import {
|
||||
hostGetCanonicalFileName,
|
||||
hostUsesCaseSensitiveFileNames,
|
||||
InternalSymbolName,
|
||||
isExportAssignment,
|
||||
isExportSpecifier,
|
||||
isExternalModuleNameRelative,
|
||||
isExternalModuleSymbol,
|
||||
isExternalOrCommonJsModule,
|
||||
isIdentifier,
|
||||
isKnownSymbol,
|
||||
isNonGlobalAmbientModule,
|
||||
isPrivateIdentifierSymbol,
|
||||
@@ -42,13 +40,13 @@ import {
|
||||
ModuleSpecifierCache,
|
||||
ModuleSpecifierResolutionHost,
|
||||
moduleSpecifiers,
|
||||
moduleSymbolToValidIdentifier,
|
||||
nodeModulesPathPart,
|
||||
PackageJsonImportFilter,
|
||||
Path,
|
||||
pathContainsNodeModules,
|
||||
Program,
|
||||
skipAlias,
|
||||
skipOuterExpressions,
|
||||
SourceFile,
|
||||
startsWith,
|
||||
Statement,
|
||||
@@ -56,7 +54,6 @@ import {
|
||||
Symbol,
|
||||
SymbolFlags,
|
||||
timestamp,
|
||||
tryCast,
|
||||
TypeChecker,
|
||||
unescapeLeadingUnderscores,
|
||||
unmangleScopedPackageName,
|
||||
@@ -502,14 +499,13 @@ export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, h
|
||||
}
|
||||
|
||||
host.log?.("getExportInfoMap: cache miss or empty; calculating new results");
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
let moduleCount = 0;
|
||||
try {
|
||||
forEachExternalModuleToImportFrom(program, host, preferences, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
|
||||
if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested();
|
||||
const seenExports = new Map<__String, true>();
|
||||
const checker = program.getTypeChecker();
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker);
|
||||
// Note: I think we shouldn't actually see resolved module symbols here, but weird merges
|
||||
// can cause it to happen: see 'completionsImport_mergedReExport.ts'
|
||||
if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) {
|
||||
@@ -551,61 +547,49 @@ export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, h
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions) {
|
||||
const exported = getDefaultLikeExportWorker(moduleSymbol, checker);
|
||||
if (!exported) return undefined;
|
||||
const { symbol, exportKind } = exported;
|
||||
const info = getDefaultExportInfoWorker(symbol, checker, compilerOptions);
|
||||
return info && { symbol, exportKind, ...info };
|
||||
}
|
||||
|
||||
function isImportableSymbol(symbol: Symbol, checker: TypeChecker) {
|
||||
return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol);
|
||||
}
|
||||
|
||||
function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol; readonly exportKind: ExportKind; } | undefined {
|
||||
export function getDefaultLikeExportInfo(moduleSymbol: Symbol, checker: TypeChecker) {
|
||||
const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol);
|
||||
if (exportEquals !== moduleSymbol) return { symbol: exportEquals, exportKind: ExportKind.ExportEquals };
|
||||
const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol);
|
||||
if (defaultExport) return { symbol: defaultExport, exportKind: ExportKind.Default };
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getDefaultExportInfoWorker(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly resolvedSymbol: Symbol; readonly name: string; } | undefined {
|
||||
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
|
||||
if (localSymbol) return { resolvedSymbol: localSymbol, name: localSymbol.name };
|
||||
|
||||
const name = getNameForExportDefault(defaultExport);
|
||||
if (name !== undefined) return { resolvedSymbol: defaultExport, name };
|
||||
|
||||
if (defaultExport.flags & SymbolFlags.Alias) {
|
||||
const aliased = checker.getImmediateAliasedSymbol(defaultExport);
|
||||
if (aliased && aliased.parent) {
|
||||
// - `aliased` will be undefined if the module is exporting an unresolvable name,
|
||||
// but we can still offer completions for it.
|
||||
// - `aliased.parent` will be undefined if the module is exporting `globalThis.something`,
|
||||
// or another expression that resolves to a global.
|
||||
return getDefaultExportInfoWorker(aliased, checker, compilerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
defaultExport.escapedName !== InternalSymbolName.Default &&
|
||||
defaultExport.escapedName !== InternalSymbolName.ExportEquals
|
||||
) {
|
||||
return { resolvedSymbol: defaultExport, name: defaultExport.getName() };
|
||||
}
|
||||
return { resolvedSymbol: defaultExport, name: getNameForExportedSymbol(defaultExport, compilerOptions.target) };
|
||||
function isImportableSymbol(symbol: Symbol, checker: TypeChecker) {
|
||||
return !checker.isUndefinedSymbol(symbol) && !checker.isUnknownSymbol(symbol) && !isKnownSymbol(symbol) && !isPrivateIdentifierSymbol(symbol);
|
||||
}
|
||||
|
||||
function getNameForExportDefault(symbol: Symbol): string | undefined {
|
||||
return symbol.declarations && firstDefined(symbol.declarations, declaration => {
|
||||
if (isExportAssignment(declaration)) {
|
||||
return tryCast(skipOuterExpressions(declaration.expression), isIdentifier)?.text;
|
||||
/**
|
||||
* @internal
|
||||
* May call `cb` multiple times with the same name.
|
||||
* Terminates when `cb` returns a truthy value.
|
||||
*/
|
||||
export function forEachNameOfDefaultExport<T>(defaultExport: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions, preferCapitalizedNames: boolean, cb: (name: string) => T | undefined): T | undefined {
|
||||
let chain: Symbol[] | undefined;
|
||||
let current: Symbol | undefined = defaultExport;
|
||||
|
||||
while (current) {
|
||||
// The predecessor to this function also looked for a name on the `localSymbol`
|
||||
// of default exports, but I think `getDefaultLikeExportNameFromDeclaration`
|
||||
// accomplishes the same thing via syntax - no tests failed when I removed it.
|
||||
const fromDeclaration = getDefaultLikeExportNameFromDeclaration(current);
|
||||
if (fromDeclaration) {
|
||||
const final = cb(fromDeclaration);
|
||||
if (final) return final;
|
||||
}
|
||||
else if (isExportSpecifier(declaration)) {
|
||||
Debug.assert(declaration.name.text === InternalSymbolName.Default, "Expected the specifier to be a default export");
|
||||
return declaration.propertyName && declaration.propertyName.text;
|
||||
|
||||
if (current.escapedName !== InternalSymbolName.Default && current.escapedName !== InternalSymbolName.ExportEquals) {
|
||||
const final = cb(current.name);
|
||||
if (final) return final;
|
||||
}
|
||||
});
|
||||
|
||||
chain = append(chain, current);
|
||||
current = current.flags & SymbolFlags.Alias ? checker.getImmediateAliasedSymbol(current) : undefined;
|
||||
}
|
||||
|
||||
for (const symbol of chain ?? emptyArray) {
|
||||
if (symbol.parent && isExternalModuleSymbol(symbol.parent)) {
|
||||
const final = cb(moduleSymbolToValidIdentifier(symbol.parent, getEmitScriptTarget(compilerOptions), preferCapitalizedNames));
|
||||
if (final) return final;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
ApplicableRefactorInfo,
|
||||
arrayFrom,
|
||||
codefix,
|
||||
Debug,
|
||||
Diagnostics,
|
||||
emptyArray,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
isPropertyAccessOrQualifiedName,
|
||||
isShorthandPropertyAssignment,
|
||||
isStringLiteral,
|
||||
moduleSpecifierToValidIdentifier,
|
||||
NamedImports,
|
||||
NamespaceImport,
|
||||
or,
|
||||
@@ -222,7 +222,7 @@ export function doChangeNamedToNamespaceOrDefault(sourceFile: SourceFile, progra
|
||||
toConvertSymbols.add(symbol);
|
||||
}
|
||||
});
|
||||
const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? codefix.moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module";
|
||||
const preferredName = moduleSpecifier && isStringLiteral(moduleSpecifier) ? moduleSpecifierToValidIdentifier(moduleSpecifier.text, ScriptTarget.ESNext) : "module";
|
||||
function hasNamespaceNameConflict(namedImport: ImportSpecifier): boolean {
|
||||
// We need to check if the preferred namespace name (`preferredName`) we'd like to use in the refactored code will present a name conflict.
|
||||
// A name conflict means that, in a scope where we would like to use the preferred namespace name, there already exists a symbol with that name in that scope.
|
||||
|
||||
@@ -118,6 +118,7 @@ import {
|
||||
ModifierLike,
|
||||
ModuleDeclaration,
|
||||
ModuleKind,
|
||||
moduleSpecifierToValidIdentifier,
|
||||
NamedImportBindings,
|
||||
Node,
|
||||
NodeFlags,
|
||||
@@ -392,7 +393,7 @@ function updateNamespaceLikeImport(
|
||||
oldImportNode: SupportedImport,
|
||||
quotePreference: QuotePreference,
|
||||
): void {
|
||||
const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleSpecifier, ScriptTarget.ESNext);
|
||||
const preferredNewNamespaceName = moduleSpecifierToValidIdentifier(newModuleSpecifier, ScriptTarget.ESNext);
|
||||
let needUniqueName = false;
|
||||
const toChange: Identifier[] = [];
|
||||
FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => {
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
ClassDeclaration,
|
||||
ClassExpression,
|
||||
clone,
|
||||
codefix,
|
||||
combinePaths,
|
||||
CommentKind,
|
||||
CommentRange,
|
||||
@@ -92,6 +91,7 @@ import {
|
||||
FunctionLikeDeclaration,
|
||||
FutureSourceFile,
|
||||
getAssignmentDeclarationKind,
|
||||
getBaseFileName,
|
||||
getCombinedNodeFlagsAlwaysIncludeJSDoc,
|
||||
getDirectoryPath,
|
||||
getEmitModuleKind,
|
||||
@@ -181,6 +181,8 @@ import {
|
||||
isGlobalScopeAugmentation,
|
||||
isHeritageClause,
|
||||
isIdentifier,
|
||||
isIdentifierPart,
|
||||
isIdentifierStart,
|
||||
isImportCall,
|
||||
isImportClause,
|
||||
isImportDeclaration,
|
||||
@@ -240,6 +242,7 @@ import {
|
||||
isSetAccessorDeclaration,
|
||||
isSourceFile,
|
||||
isSourceFileJS,
|
||||
isStringANonContextualKeyword,
|
||||
isStringDoubleQuoted,
|
||||
isStringLiteral,
|
||||
isStringLiteralLike,
|
||||
@@ -319,6 +322,8 @@ import {
|
||||
pseudoBigIntToString,
|
||||
QualifiedName,
|
||||
RefactorContext,
|
||||
removeFileExtension,
|
||||
removeSuffix,
|
||||
Scanner,
|
||||
ScriptElementKind,
|
||||
ScriptElementKindModifier,
|
||||
@@ -4023,8 +4028,8 @@ export function getNamesForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTa
|
||||
if (needsNameFromDeclaration(symbol)) {
|
||||
const fromDeclaration = getDefaultLikeExportNameFromDeclaration(symbol);
|
||||
if (fromDeclaration) return fromDeclaration;
|
||||
const fileNameCase = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ false);
|
||||
const capitalized = codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ true);
|
||||
const fileNameCase = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ false);
|
||||
const capitalized = moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, /*forceCapitalize*/ true);
|
||||
if (fileNameCase === capitalized) return fileNameCase;
|
||||
return [fileNameCase, capitalized];
|
||||
}
|
||||
@@ -4039,7 +4044,7 @@ export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTar
|
||||
// - export { foo as default } => foo
|
||||
// - export default 0 => filename converted to camelCase
|
||||
return getDefaultLikeExportNameFromDeclaration(symbol)
|
||||
|| codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, !!preferCapitalized);
|
||||
|| moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, !!preferCapitalized);
|
||||
}
|
||||
return symbol.name;
|
||||
}
|
||||
@@ -4048,7 +4053,8 @@ function needsNameFromDeclaration(symbol: Symbol) {
|
||||
return !(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default);
|
||||
}
|
||||
|
||||
function getDefaultLikeExportNameFromDeclaration(symbol: Symbol): string | undefined {
|
||||
/** @internal */
|
||||
export function getDefaultLikeExportNameFromDeclaration(symbol: Symbol): string | undefined {
|
||||
return firstDefined(symbol.declarations, d => {
|
||||
// "export default" in this case. See `ExportAssignment`for more details.
|
||||
if (isExportAssignment(d)) {
|
||||
@@ -4063,7 +4069,8 @@ function getDefaultLikeExportNameFromDeclaration(symbol: Symbol): string | undef
|
||||
});
|
||||
}
|
||||
|
||||
function getSymbolParentOrFail(symbol: Symbol) {
|
||||
/** @internal */
|
||||
export function getSymbolParentOrFail(symbol: Symbol) {
|
||||
return Debug.checkDefined(
|
||||
symbol.parent,
|
||||
`Symbol parent was undefined. Flags: ${Debug.formatSymbolFlags(symbol.flags)}. ` +
|
||||
@@ -4078,6 +4085,42 @@ function getSymbolParentOrFail(symbol: Symbol) {
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined, forceCapitalize: boolean): string {
|
||||
return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target, forceCapitalize);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget | undefined, forceCapitalize?: boolean): string {
|
||||
const baseName = getBaseFileName(removeSuffix(moduleSpecifier, "/index"));
|
||||
let res = "";
|
||||
let lastCharWasValid = true;
|
||||
const firstCharCode = baseName.charCodeAt(0);
|
||||
if (isIdentifierStart(firstCharCode, target)) {
|
||||
res += String.fromCharCode(firstCharCode);
|
||||
if (forceCapitalize) {
|
||||
res = res.toUpperCase();
|
||||
}
|
||||
}
|
||||
else {
|
||||
lastCharWasValid = false;
|
||||
}
|
||||
for (let i = 1; i < baseName.length; i++) {
|
||||
const ch = baseName.charCodeAt(i);
|
||||
const isValid = isIdentifierPart(ch, target);
|
||||
if (isValid) {
|
||||
let char = String.fromCharCode(ch);
|
||||
if (!lastCharWasValid) {
|
||||
char = char.toUpperCase();
|
||||
}
|
||||
res += char;
|
||||
}
|
||||
lastCharWasValid = isValid;
|
||||
}
|
||||
// Need `|| "_"` to ensure result isn't empty.
|
||||
return !isStringANonContextualKeyword(res) ? res || "_" : `_${res}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful to check whether a string contains another string at a specific index
|
||||
* without allocating another string or traversing the entire contents of the outer string.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: nodenext
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /node_modules/dom7/index.d.ts
|
||||
//// export interface Dom7Array {
|
||||
//// length: number;
|
||||
//// prop(propName: string): any;
|
||||
//// }
|
||||
////
|
||||
//// export interface Dom7 {
|
||||
//// (): Dom7Array;
|
||||
//// fn: any;
|
||||
//// }
|
||||
////
|
||||
//// declare const Dom7: Dom7;
|
||||
////
|
||||
//// export {
|
||||
//// Dom7 as $,
|
||||
//// };
|
||||
|
||||
// @Filename: /dom7.js
|
||||
//// import * as methods from 'dom7';
|
||||
//// Object.keys(methods).forEach((methodName) => {
|
||||
//// if (methodName === '$') return;
|
||||
//// methods.$.fn[methodName] = methods[methodName];
|
||||
//// });
|
||||
////
|
||||
//// export default methods.$;
|
||||
|
||||
// @Filename: /swipe-back.js
|
||||
//// import $ from './dom7.js';
|
||||
//// /*1*/
|
||||
|
||||
verify.completions({
|
||||
marker: "1",
|
||||
// some kind of a check should be added here
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
// @module: commonjs
|
||||
// @allowJs: true
|
||||
|
||||
// @Filename: /file1.js
|
||||
//// const a = 1;
|
||||
//// export {
|
||||
//// a as b
|
||||
//// };
|
||||
//// export default a;
|
||||
|
||||
// @Filename: /file2.js
|
||||
//// import * as foo from './file1';
|
||||
//// /**/
|
||||
//// export default foo.b;
|
||||
|
||||
goTo.marker("");
|
||||
verify.completions({
|
||||
marker: "",
|
||||
exact: completion.globalsInJsPlus([
|
||||
"foo",
|
||||
{
|
||||
name: "a",
|
||||
source: "./file1",
|
||||
sourceDisplay: "./file1",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
},
|
||||
{
|
||||
name: "b",
|
||||
source: "./file1",
|
||||
sourceDisplay: "./file1",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}
|
||||
]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
allowIncompleteCompletions: true,
|
||||
},
|
||||
});
|
||||
@@ -6,6 +6,9 @@
|
||||
// @Filename: /user2.ts
|
||||
////unnamed;
|
||||
|
||||
// @Filename: /user3.ts
|
||||
////reExportUnnamed;
|
||||
|
||||
// @Filename: /reExportNamed.ts
|
||||
////export { default } from "./named";
|
||||
|
||||
@@ -38,3 +41,9 @@ unnamed;`,
|
||||
|
||||
unnamed;`,
|
||||
]);
|
||||
|
||||
goTo.file("/user3.ts");
|
||||
verify.importFixAtPosition([
|
||||
`import reExportUnnamed from "./reExportUnnamed";
|
||||
|
||||
reExportUnnamed;`]);
|
||||
|
||||
Reference in New Issue
Block a user