mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Allow moduleSymbolToValidIdentifier to be uppercase for JSX tags (#47625)
* Allow moduleSymbolToValidIdentifier to be uppercase for JSX tags * Cleaner way of getting the uppercase name when needed * Fix build errors, get rid of basically unnecessary ScriptTarget * More accurate name for parameter * Rename other parameter too * Fix failing test
This commit is contained in:
parent
0d3ff0cce8
commit
5813a3541c
@ -74,7 +74,7 @@ namespace ts.codefix {
|
||||
const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
|
||||
const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, preferences, useAutoImportProvider);
|
||||
const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, /*isJsxTagName*/ false, host, program, preferences, useAutoImportProvider);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
|
||||
if (fix) {
|
||||
@ -287,6 +287,7 @@ namespace ts.codefix {
|
||||
moduleSymbol: Symbol,
|
||||
sourceFile: SourceFile,
|
||||
symbolName: string,
|
||||
isJsxTagName: boolean,
|
||||
host: LanguageServiceHost,
|
||||
program: Program,
|
||||
formatContext: formatting.FormatContext,
|
||||
@ -296,7 +297,7 @@ namespace ts.codefix {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name))
|
||||
? [getSymbolExportInfoForSymbol(targetSymbol, moduleSymbol, program, host)]
|
||||
: getAllReExportingModules(sourceFile, targetSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true);
|
||||
: getAllReExportingModules(sourceFile, targetSymbol, moduleSymbol, symbolName, isJsxTagName, 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));
|
||||
@ -349,7 +350,7 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function getAllReExportingModules(importingFile: SourceFile, targetSymbol: 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, isJsxTagName: boolean, host: LanguageServiceHost, program: Program, preferences: UserPreferences, useAutoImportProvider: boolean): readonly SymbolExportInfo[] {
|
||||
const result: SymbolExportInfo[] = [];
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const getModuleSpecifierResolutionHost = memoizeOne((isFromPackageJson: boolean) => {
|
||||
@ -364,7 +365,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions)) === symbolName) && skipAlias(defaultInfo.symbol, checker) === targetSymbol && isImportable(program, moduleFile, isFromPackageJson)) {
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === 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 });
|
||||
}
|
||||
|
||||
@ -804,7 +805,7 @@ namespace ts.codefix {
|
||||
|
||||
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(symbolToken);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences);
|
||||
const exportInfos = getExportInfos(symbolName, isJSXTagName(symbolToken), getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences);
|
||||
const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
|
||||
getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences)));
|
||||
return { fixes, symbolName };
|
||||
@ -845,6 +846,7 @@ namespace ts.codefix {
|
||||
// Returns a map from an exported symbol's ID to a list of every way it's (re-)exported.
|
||||
function getExportInfos(
|
||||
symbolName: string,
|
||||
isJsxTagName: boolean,
|
||||
currentTokenMeaning: SemanticMeaning,
|
||||
cancellationToken: CancellationToken,
|
||||
fromFile: SourceFile,
|
||||
@ -876,7 +878,7 @@ namespace ts.codefix {
|
||||
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions)) === symbolName) && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) {
|
||||
if (defaultInfo && (defaultInfo.name === symbolName || moduleSymbolToValidIdentifier(moduleSymbol, getEmitScriptTarget(compilerOptions), isJsxTagName) === symbolName) && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) {
|
||||
addSymbol(moduleSymbol, sourceFile, defaultInfo.symbol, defaultInfo.exportKind, program, isFromPackageJson);
|
||||
}
|
||||
|
||||
@ -1237,17 +1239,20 @@ namespace ts.codefix {
|
||||
return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning));
|
||||
}
|
||||
|
||||
export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined): string {
|
||||
return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target);
|
||||
export function moduleSymbolToValidIdentifier(moduleSymbol: Symbol, target: ScriptTarget | undefined, forceCapitalize: boolean): string {
|
||||
return moduleSpecifierToValidIdentifier(removeFileExtension(stripQuotes(moduleSymbol.name)), target, forceCapitalize);
|
||||
}
|
||||
|
||||
export function moduleSpecifierToValidIdentifier(moduleSpecifier: string, target: ScriptTarget | undefined): string {
|
||||
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;
|
||||
|
||||
@ -1520,7 +1520,6 @@ namespace ts.Completions {
|
||||
source: string | undefined,
|
||||
): CodeActionsAndSourceDisplay {
|
||||
if (data?.moduleSpecifier) {
|
||||
const { contextToken, previousToken } = getRelevantTokens(position, sourceFile);
|
||||
if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementNode) {
|
||||
// Import statement completion: 'import c|'
|
||||
return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] };
|
||||
@ -1572,11 +1571,13 @@ namespace ts.Completions {
|
||||
const checker = origin.isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker();
|
||||
const { moduleSymbol } = origin;
|
||||
const targetSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker));
|
||||
const isJsxOpeningTagName = contextToken?.kind === SyntaxKind.LessThanToken && isJsxOpeningLikeElement(contextToken.parent);
|
||||
const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction(
|
||||
targetSymbol,
|
||||
moduleSymbol,
|
||||
sourceFile,
|
||||
getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions)),
|
||||
getNameForExportedSymbol(symbol, getEmitScriptTarget(compilerOptions), isJsxOpeningTagName),
|
||||
isJsxOpeningTagName,
|
||||
host,
|
||||
program,
|
||||
formatContext,
|
||||
@ -2486,12 +2487,17 @@ namespace ts.Completions {
|
||||
preferences,
|
||||
!!importCompletionNode,
|
||||
context => {
|
||||
exportInfo.forEach(sourceFile.path, (info, symbolName, isFromAmbientModule, exportMapKey) => {
|
||||
exportInfo.forEach(sourceFile.path, (info, getSymbolName, isFromAmbientModule, exportMapKey) => {
|
||||
const symbolName = getSymbolName(/*preferCapitalized*/ isRightOfOpenTag);
|
||||
if (!isIdentifierText(symbolName, getEmitScriptTarget(host.getCompilationSettings()))) return;
|
||||
if (!detailsEntryId && isStringANonContextualKeyword(symbolName)) return;
|
||||
// `targetFlags` should be the same for each `info`
|
||||
if (!isTypeOnlyLocation && !importCompletionNode && !(info[0].targetFlags & SymbolFlags.Value)) return;
|
||||
if (isTypeOnlyLocation && !(info[0].targetFlags & (SymbolFlags.Module | SymbolFlags.Type))) return;
|
||||
// Do not try to auto-import something with a lowercase first letter for a JSX tag
|
||||
const firstChar = symbolName.charCodeAt(0);
|
||||
if (isRightOfOpenTag && (firstChar < CharacterCodes.A || firstChar > CharacterCodes.Z)) return;
|
||||
|
||||
const isCompletionDetailsMatch = detailsEntryId && some(info, i => detailsEntryId.source === stripQuotes(i.moduleSymbol.name));
|
||||
if (isCompletionDetailsMatch || !detailsEntryId && charactersFuzzyMatchInString(symbolName, lowerCaseTokenText)) {
|
||||
const defaultExportInfo = find(info, isImportableExportInfo);
|
||||
@ -2504,6 +2510,7 @@ namespace ts.Completions {
|
||||
const { exportInfo = defaultExportInfo, moduleSpecifier } = context.tryResolve(info, isFromAmbientModule) || {};
|
||||
const isDefaultExport = exportInfo.exportKind === ExportKind.Default;
|
||||
const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol;
|
||||
|
||||
pushAutoImportSymbol(symbol, {
|
||||
kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export,
|
||||
moduleSpecifier,
|
||||
|
||||
@ -45,9 +45,9 @@ namespace ts {
|
||||
export interface ExportInfoMap {
|
||||
isUsableByFile(importingFile: Path): boolean;
|
||||
clear(): void;
|
||||
add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void;
|
||||
add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, checker: TypeChecker): void;
|
||||
get(importingFile: Path, key: string): readonly SymbolExportInfo[] | undefined;
|
||||
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean, key: string) => void): void;
|
||||
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], getSymbolName: (preferCapitalized?: boolean) => string, isFromAmbientModule: boolean, key: string) => void): void;
|
||||
releaseSymbols(): void;
|
||||
isEmpty(): boolean;
|
||||
/** @returns Whether the change resulted in the cache being cleared */
|
||||
@ -72,7 +72,7 @@ namespace ts {
|
||||
symbols.clear();
|
||||
usableByFileName = undefined;
|
||||
},
|
||||
add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, scriptTarget, checker) => {
|
||||
add: (importingFile, symbol, symbolTableKey, moduleSymbol, moduleFile, exportKind, isFromPackageJson, checker) => {
|
||||
if (importingFile !== usableByFileName) {
|
||||
cache.clear();
|
||||
usableByFileName = importingFile;
|
||||
@ -88,7 +88,8 @@ namespace ts {
|
||||
// get a better name.
|
||||
const importedName = exportKind === ExportKind.Named || isExternalModuleSymbol(namedSymbol)
|
||||
? unescapeLeadingUnderscores(symbolTableKey)
|
||||
: getNameForExportedSymbol(namedSymbol, scriptTarget);
|
||||
: getNameForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined);
|
||||
|
||||
const moduleName = stripQuotes(moduleSymbol.name);
|
||||
const id = exportInfoId++;
|
||||
const target = skipAlias(symbol, checker);
|
||||
@ -119,7 +120,18 @@ namespace ts {
|
||||
if (importingFile !== usableByFileName) return;
|
||||
exportInfo.forEach((info, key) => {
|
||||
const { symbolName, ambientModuleName } = parseKey(key);
|
||||
action(info.map(rehydrateCachedInfo), symbolName, !!ambientModuleName, key);
|
||||
const rehydrated = info.map(rehydrateCachedInfo);
|
||||
action(
|
||||
rehydrated,
|
||||
preferCapitalized => {
|
||||
const { symbol, exportKind } = rehydrated[0];
|
||||
const namedSymbol = exportKind === ExportKind.Default && getLocalSymbolForExportDefault(symbol) || symbol;
|
||||
return preferCapitalized
|
||||
? getNameForExportedSymbol(namedSymbol, /*scriptTarget*/ undefined, /*preferCapitalized*/ true)
|
||||
: symbolName;
|
||||
},
|
||||
!!ambientModuleName,
|
||||
key);
|
||||
});
|
||||
},
|
||||
releaseSymbols: () => {
|
||||
@ -321,7 +333,6 @@ namespace ts {
|
||||
|
||||
host.log?.("getExportInfoMap: cache miss or empty; calculating new results");
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const scriptTarget = getEmitScriptTarget(compilerOptions);
|
||||
let moduleCount = 0;
|
||||
forEachExternalModuleToImportFrom(program, host, /*useAutoImportProvider*/ true, (moduleSymbol, moduleFile, program, isFromPackageJson) => {
|
||||
if (++moduleCount % 100 === 0) cancellationToken?.throwIfCancellationRequested();
|
||||
@ -339,7 +350,6 @@ namespace ts {
|
||||
moduleFile,
|
||||
defaultInfo.exportKind,
|
||||
isFromPackageJson,
|
||||
scriptTarget,
|
||||
checker);
|
||||
}
|
||||
checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => {
|
||||
@ -352,7 +362,6 @@ namespace ts {
|
||||
moduleFile,
|
||||
ExportKind.Named,
|
||||
isFromPackageJson,
|
||||
scriptTarget,
|
||||
checker);
|
||||
}
|
||||
});
|
||||
|
||||
@ -3210,11 +3210,11 @@ namespace ts {
|
||||
return isArray(valueOrArray) ? first(valueOrArray) : valueOrArray;
|
||||
}
|
||||
|
||||
export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined) {
|
||||
export function getNameForExportedSymbol(symbol: Symbol, scriptTarget: ScriptTarget | undefined, preferCapitalized?: boolean) {
|
||||
if (!(symbol.flags & SymbolFlags.Transient) && (symbol.escapedName === InternalSymbolName.ExportEquals || symbol.escapedName === InternalSymbolName.Default)) {
|
||||
// Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase.
|
||||
return firstDefined(symbol.declarations, d => isExportAssignment(d) ? tryCast(skipOuterExpressions(d.expression), isIdentifier)?.text : undefined)
|
||||
|| codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget);
|
||||
|| codefix.moduleSymbolToValidIdentifier(getSymbolParentOrFail(symbol), scriptTarget, !!preferCapitalized);
|
||||
}
|
||||
return symbol.name;
|
||||
}
|
||||
|
||||
@ -87,8 +87,8 @@ namespace ts.projectSystem {
|
||||
// transient symbols are recreated with every new checker.
|
||||
const programBefore = project.getCurrentProgram()!;
|
||||
let sigintPropBefore: readonly SymbolExportInfo[] | undefined;
|
||||
exportMapCache.forEach(bTs.path as Path, (info, name) => {
|
||||
if (name === "SIGINT") sigintPropBefore = info;
|
||||
exportMapCache.forEach(bTs.path as Path, (info, getSymbolName) => {
|
||||
if (getSymbolName() === "SIGINT") sigintPropBefore = info;
|
||||
});
|
||||
assert.ok(sigintPropBefore);
|
||||
assert.ok(sigintPropBefore![0].symbol.flags & SymbolFlags.Transient);
|
||||
@ -113,8 +113,8 @@ namespace ts.projectSystem {
|
||||
|
||||
// Get same info from cache again
|
||||
let sigintPropAfter: readonly SymbolExportInfo[] | undefined;
|
||||
exportMapCache.forEach(bTs.path as Path, (info, name) => {
|
||||
if (name === "SIGINT") sigintPropAfter = info;
|
||||
exportMapCache.forEach(bTs.path as Path, (info, getSymbolName) => {
|
||||
if (getSymbolName() === "SIGINT") sigintPropAfter = info;
|
||||
});
|
||||
assert.ok(sigintPropAfter);
|
||||
assert.notEqual(symbolIdBefore, getSymbolId(sigintPropAfter![0].symbol));
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
// @jsx: react
|
||||
|
||||
// @Filename: /component.tsx
|
||||
//// export default function (props: any) {}
|
||||
|
||||
// @Filename: /index.tsx
|
||||
//// export function Index() {
|
||||
//// return <Component/**/
|
||||
//// }
|
||||
|
||||
goTo.marker("");
|
||||
verify.completions({
|
||||
marker: "",
|
||||
includes: {
|
||||
name: "Component",
|
||||
source: "/component",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions,
|
||||
},
|
||||
excludes: "component",
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
|
||||
verify.applyCodeActionFromCompletion("", {
|
||||
name: "Component",
|
||||
source: "/component",
|
||||
description: `Import default 'Component' from module "./component"`,
|
||||
newFileContent:
|
||||
`import Component from "./component";
|
||||
|
||||
export function Index() {
|
||||
return <Component
|
||||
}`,
|
||||
});
|
||||
@ -0,0 +1,19 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
// @jsx: react-jsx
|
||||
|
||||
// @Filename: /component.tsx
|
||||
//// export default function (props: any) {}
|
||||
|
||||
// @Filename: /index.tsx
|
||||
//// export function Index() {
|
||||
//// return <Component/**/ />;
|
||||
//// }
|
||||
|
||||
goTo.marker("");
|
||||
verify.importFixAtPosition([`import Component from "./component";
|
||||
|
||||
export function Index() {
|
||||
return <Component />;
|
||||
}`]);
|
||||
Loading…
x
Reference in New Issue
Block a user