mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 07:45:18 -06:00
fix(54694): Class incorrectly implements interface generated with template string literal mapped type (#54715)
This commit is contained in:
parent
5128e06a9d
commit
2136bef652
@ -335,6 +335,7 @@ import {
|
||||
getParseTreeNode,
|
||||
getPropertyAssignmentAliasLikeExpression,
|
||||
getPropertyNameForPropertyNameNode,
|
||||
getPropertyNameFromType,
|
||||
getResolutionDiagnostic,
|
||||
getResolutionModeOverrideForClause,
|
||||
getResolvedExternalModuleName,
|
||||
@ -729,6 +730,7 @@ import {
|
||||
isTypeQueryNode,
|
||||
isTypeReferenceNode,
|
||||
isTypeReferenceType,
|
||||
isTypeUsableAsPropertyName,
|
||||
isUMDExportSymbol,
|
||||
isValidBigIntString,
|
||||
isValidESSymbolDeclaration,
|
||||
@ -12286,13 +12288,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return type as InterfaceTypeWithDeclaredMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a type can be used as a property name.
|
||||
*/
|
||||
function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType {
|
||||
return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a declaration name is definitely late-bindable.
|
||||
* A declaration name is only late-bindable if:
|
||||
@ -12338,19 +12333,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return isDynamicName(node) && !isLateBindableName(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the symbolic name for a member from its type.
|
||||
*/
|
||||
function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
|
||||
if (type.flags & TypeFlags.UniqueESSymbol) {
|
||||
return (type as UniqueESSymbolType).escapedName;
|
||||
}
|
||||
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
|
||||
return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value);
|
||||
}
|
||||
return Debug.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a declaration to a late-bound dynamic member. This performs the same function for
|
||||
* late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound
|
||||
|
||||
@ -416,6 +416,7 @@ import {
|
||||
noop,
|
||||
normalizePath,
|
||||
NoSubstitutionTemplateLiteral,
|
||||
NumberLiteralType,
|
||||
NumericLiteral,
|
||||
ObjectFlags,
|
||||
ObjectFlagsType,
|
||||
@ -494,6 +495,7 @@ import {
|
||||
stringContains,
|
||||
StringLiteral,
|
||||
StringLiteralLike,
|
||||
StringLiteralType,
|
||||
stringToToken,
|
||||
SuperCall,
|
||||
SuperExpression,
|
||||
@ -544,6 +546,7 @@ import {
|
||||
TypeReferenceNode,
|
||||
unescapeLeadingUnderscores,
|
||||
UnionOrIntersectionTypeNode,
|
||||
UniqueESSymbolType,
|
||||
UserPreferences,
|
||||
ValidImportTypeNode,
|
||||
VariableDeclaration,
|
||||
@ -10313,3 +10316,25 @@ export function getTextOfJsxNamespacedName(node: JsxNamespacedName) {
|
||||
export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName) {
|
||||
return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a type can be used as a property name.
|
||||
* @internal
|
||||
*/
|
||||
export function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType {
|
||||
return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the symbolic name for a member from its type.
|
||||
* @internal
|
||||
*/
|
||||
export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String {
|
||||
if (type.flags & TypeFlags.UniqueESSymbol) {
|
||||
return (type as UniqueESSymbolType).escapedName;
|
||||
}
|
||||
if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) {
|
||||
return escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value);
|
||||
}
|
||||
return Debug.fail();
|
||||
}
|
||||
|
||||
@ -6,27 +6,32 @@ import {
|
||||
Block,
|
||||
CallExpression,
|
||||
CharacterCodes,
|
||||
CheckFlags,
|
||||
ClassLikeDeclaration,
|
||||
CodeFixContextBase,
|
||||
combine,
|
||||
Debug,
|
||||
Declaration,
|
||||
Diagnostics,
|
||||
emptyArray,
|
||||
EntityName,
|
||||
Expression,
|
||||
factory,
|
||||
find,
|
||||
firstOrUndefined,
|
||||
flatMap,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
GetAccessorDeclaration,
|
||||
getAllAccessorDeclarations,
|
||||
getCheckFlags,
|
||||
getEffectiveModifierFlags,
|
||||
getEmitScriptTarget,
|
||||
getFirstIdentifier,
|
||||
getModuleSpecifierResolverHost,
|
||||
getNameForExportedSymbol,
|
||||
getNameOfDeclaration,
|
||||
getPropertyNameFromType,
|
||||
getQuotePreference,
|
||||
getSetAccessorValueParameter,
|
||||
getSynthesizedDeepClone,
|
||||
@ -52,6 +57,7 @@ import {
|
||||
isSetAccessorDeclaration,
|
||||
isStringLiteral,
|
||||
isTypeNode,
|
||||
isTypeUsableAsPropertyName,
|
||||
isYieldExpression,
|
||||
LanguageServiceHost,
|
||||
length,
|
||||
@ -91,6 +97,7 @@ import {
|
||||
textChanges,
|
||||
TextSpan,
|
||||
textSpanEnd,
|
||||
TransientSymbol,
|
||||
tryCast,
|
||||
TsConfigSourceFile,
|
||||
Type,
|
||||
@ -98,6 +105,7 @@ import {
|
||||
TypeFlags,
|
||||
TypeNode,
|
||||
TypeParameterDeclaration,
|
||||
unescapeLeadingUnderscores,
|
||||
UnionType,
|
||||
UserPreferences,
|
||||
visitEachChild,
|
||||
@ -174,7 +182,7 @@ export function addNewNodeForMemberSymbol(
|
||||
isAmbient = false,
|
||||
): void {
|
||||
const declarations = symbol.getDeclarations();
|
||||
const declaration = declarations?.[0];
|
||||
const declaration = firstOrUndefined(declarations);
|
||||
const checker = context.program.getTypeChecker();
|
||||
const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
|
||||
|
||||
@ -193,7 +201,7 @@ export function addNewNodeForMemberSymbol(
|
||||
* In such cases, we assume the declaration to be a `PropertySignature`.
|
||||
*/
|
||||
const kind = declaration?.kind ?? SyntaxKind.PropertySignature;
|
||||
const declarationName = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
|
||||
const declarationName = createDeclarationName(symbol, declaration);
|
||||
const effectiveModifierFlags = declaration ? getEffectiveModifierFlags(declaration) : ModifierFlags.None;
|
||||
let modifierFlags = effectiveModifierFlags & ModifierFlags.Static;
|
||||
modifierFlags |=
|
||||
@ -310,7 +318,6 @@ export function addNewNodeForMemberSymbol(
|
||||
if (method) addClassElement(method);
|
||||
}
|
||||
|
||||
|
||||
function createModifiers(): NodeArray<Modifier> | undefined {
|
||||
let modifiers: Modifier[] | undefined;
|
||||
|
||||
@ -344,6 +351,16 @@ export function addNewNodeForMemberSymbol(
|
||||
function createTypeNode(typeNode: TypeNode | undefined) {
|
||||
return getSynthesizedDeepClone(typeNode, /*includeTrivia*/ false);
|
||||
}
|
||||
|
||||
function createDeclarationName(symbol: Symbol, declaration: Declaration | undefined): PropertyName {
|
||||
if (getCheckFlags(symbol) & CheckFlags.Mapped) {
|
||||
const nameType = (symbol as TransientSymbol).links.nameType;
|
||||
if (nameType && isTypeUsableAsPropertyName(nameType)) {
|
||||
return factory.createIdentifier(unescapeLeadingUnderscores(getPropertyNameFromType(nameType)));
|
||||
}
|
||||
}
|
||||
return getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
////type ListenerTemplate<T, S extends string, I extends string = "${1}"> = {
|
||||
//// [K in keyof T as K extends string
|
||||
//// ? S extends `${infer F}${I}${infer R}` ? `${F}${K}${R}` : K : K]
|
||||
//// : (listener: (payload: T[K]) => void) => void;
|
||||
////};
|
||||
////type ListenActionable<E> = ListenerTemplate<E, "add*Listener" | "remove*Listener", "*">;
|
||||
////type ClickEventSupport = ListenActionable<{ Click: 'some-click-event-payload' }>;
|
||||
////
|
||||
////[|class C implements ClickEventSupport { }|]
|
||||
|
||||
verify.codeFix({
|
||||
description: "Implement interface 'ClickEventSupport'",
|
||||
newRangeContent:
|
||||
`class C implements ClickEventSupport {
|
||||
addClickListener: (listener: (payload: "some-click-event-payload") => void) => void;
|
||||
removeClickListener: (listener: (payload: "some-click-event-payload") => void) => void;
|
||||
}`,
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user