Skip to content

Commit 8d7ad8c

Browse files
authoredAug 24, 2022
fix(50375): Errors for missing enum-named properties should attempt to preserve names (#50382)
* fix(50375): preserve enum-named properties * add AllowComputedPropertyEnums option * use bit shifting * rename AllowComputedPropertyEnum -> WriteComputedProps * mark WriteComputedProps as internal * mark symbolToNode as internal
1 parent fb717df commit 8d7ad8c

File tree

9 files changed

+112
-13
lines changed

9 files changed

+112
-13
lines changed
 

‎src/compiler/checker.ts

+23-2
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ namespace ts {
464464
signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration,
465465
symbolToEntityName: nodeBuilder.symbolToEntityName,
466466
symbolToExpression: nodeBuilder.symbolToExpression,
467+
symbolToNode: nodeBuilder.symbolToNode,
467468
symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations,
468469
symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration,
469470
typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration,
@@ -4822,7 +4823,10 @@ namespace ts {
48224823
if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) {
48234824
nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain;
48244825
}
4825-
const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName;
4826+
if (flags & SymbolFormatFlags.WriteComputedProps) {
4827+
nodeFlags |= NodeBuilderFlags.WriteComputedProps;
4828+
}
4829+
const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName;
48264830
return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker);
48274831

48284832
function symbolToStringWorker(writer: EmitTextWriter) {
@@ -4919,8 +4923,25 @@ namespace ts {
49194923
withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)),
49204924
symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) =>
49214925
withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)),
4926+
symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
4927+
withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)),
49224928
};
49234929

4930+
function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) {
4931+
if (context.flags & NodeBuilderFlags.WriteComputedProps) {
4932+
if (symbol.valueDeclaration) {
4933+
const name = getNameOfDeclaration(symbol.valueDeclaration);
4934+
if (name && isComputedPropertyName(name)) return name;
4935+
}
4936+
const nameType = getSymbolLinks(symbol).nameType;
4937+
if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) {
4938+
context.enclosingDeclaration = nameType.symbol.valueDeclaration;
4939+
return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning));
4940+
}
4941+
}
4942+
return symbolToExpression(symbol, context, meaning);
4943+
}
4944+
49244945
function withContext<T>(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined {
49254946
Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0);
49264947
const context: NodeBuilderContext = {
@@ -20310,7 +20331,7 @@ namespace ts {
2031020331
shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it
2031120332
}
2031220333
if (props.length === 1) {
20313-
const propName = symbolToString(unmatchedProperty);
20334+
const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps);
2031420335
reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target));
2031520336
if (length(unmatchedProperty.declarations)) {
2031620337
associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName));

‎src/compiler/types.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -4595,6 +4595,8 @@ namespace ts {
45954595
/** Note that the resulting nodes cannot be checked. */
45964596
symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Expression | undefined;
45974597
/** Note that the resulting nodes cannot be checked. */
4598+
/* @internal */ symbolToNode(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Node | undefined;
4599+
/** Note that the resulting nodes cannot be checked. */
45984600
symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeArray<TypeParameterDeclaration> | undefined;
45994601
/** Note that the resulting nodes cannot be checked. */
46004602
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
@@ -4872,6 +4874,7 @@ namespace ts {
48724874
AllowEmptyTuple = 1 << 19,
48734875
AllowUniqueESSymbolType = 1 << 20,
48744876
AllowEmptyIndexInfoType = 1 << 21,
4877+
/* @internal */ WriteComputedProps = 1 << 30, // { [E.A]: 1 }
48754878

48764879
// Errors (cont.)
48774880
AllowNodeModulesRelativePaths = 1 << 26,
@@ -4930,27 +4933,30 @@ namespace ts {
49304933
}
49314934

49324935
export const enum SymbolFormatFlags {
4933-
None = 0x00000000,
4936+
None = 0,
49344937

49354938
// Write symbols's type argument if it is instantiated symbol
49364939
// eg. class C<T> { p: T } <-- Show p as C<T>.p here
49374940
// var a: C<number>;
49384941
// var p = a.p; <--- Here p is property of C<number> so show it as C<number>.p instead of just C.p
4939-
WriteTypeParametersOrArguments = 0x00000001,
4942+
WriteTypeParametersOrArguments = 1 << 0,
49404943

49414944
// Use only external alias information to get the symbol name in the given context
49424945
// eg. module m { export class c { } } import x = m.c;
49434946
// When this flag is specified m.c will be used to refer to the class instead of alias symbol x
4944-
UseOnlyExternalAliasing = 0x00000002,
4947+
UseOnlyExternalAliasing = 1 << 1,
49454948

49464949
// Build symbol name using any nodes needed, instead of just components of an entity name
4947-
AllowAnyNodeKind = 0x00000004,
4950+
AllowAnyNodeKind = 1 << 2,
49484951

49494952
// Prefer aliases which are not directly visible
4950-
UseAliasDefinedOutsideCurrentScope = 0x00000008,
4953+
UseAliasDefinedOutsideCurrentScope = 1 << 3,
4954+
4955+
// { [E.A]: 1 }
4956+
/* @internal */ WriteComputedProps = 1 << 4,
49514957

49524958
// Skip building an accessible symbol chain
4953-
/* @internal */ DoNotIncludeSymbolChain = 0x00000010,
4959+
/* @internal */ DoNotIncludeSymbolChain = 1 << 5,
49544960
}
49554961

49564962
/* @internal */

‎src/services/codefixes/fixAddMissingMember.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,9 @@ namespace ts.codefix {
642642
}
643643

644644
function createPropertyNameFromSymbol(symbol: Symbol, target: ScriptTarget, quotePreference: QuotePreference, checker: TypeChecker) {
645-
if (isTransientSymbol(symbol) && symbol.nameType && symbol.nameType.flags & TypeFlags.UniqueESSymbol) {
646-
const expression = checker.symbolToExpression((symbol.nameType as UniqueESSymbolType).symbol, SymbolFlags.Value, symbol.valueDeclaration, NodeBuilderFlags.AllowUniqueESSymbolType);
647-
if (expression) {
648-
return factory.createComputedPropertyName(expression);
649-
}
645+
if (isTransientSymbol(symbol)) {
646+
const prop = checker.symbolToNode(symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.WriteComputedProps);
647+
if (prop && isComputedPropertyName(prop)) return prop;
650648
}
651649
return createPropertyNameNodeForIdentifierOrLiteral(symbol.name, target, quotePreference === QuotePreference.Single);
652650
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts(5,5): error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record<E, any>'.
2+
3+
4+
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts (1 errors) ====
5+
enum E {
6+
A
7+
}
8+
9+
let foo: Record<E, any> = {}
10+
~~~
11+
!!! error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record<E, any>'.
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//// [assignmentCompatWithEnumIndexer.ts]
2+
enum E {
3+
A
4+
}
5+
6+
let foo: Record<E, any> = {}
7+
8+
9+
//// [assignmentCompatWithEnumIndexer.js]
10+
var E;
11+
(function (E) {
12+
E[E["A"] = 0] = "A";
13+
})(E || (E = {}));
14+
var foo = {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts ===
2+
enum E {
3+
>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0))
4+
5+
A
6+
>A : Symbol(E.A, Decl(assignmentCompatWithEnumIndexer.ts, 0, 8))
7+
}
8+
9+
let foo: Record<E, any> = {}
10+
>foo : Symbol(foo, Decl(assignmentCompatWithEnumIndexer.ts, 4, 3))
11+
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
12+
>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0))
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts ===
2+
enum E {
3+
>E : E
4+
5+
A
6+
>A : E.A
7+
}
8+
9+
let foo: Record<E, any> = {}
10+
>foo : Record<E, any>
11+
>{} : {}
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum E {
2+
A
3+
}
4+
5+
let foo: Record<E, any> = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
////enum E {
4+
//// A
5+
////}
6+
////let obj: Record<E, any> = {}
7+
8+
verify.codeFix({
9+
index: 0,
10+
description: ts.Diagnostics.Add_missing_properties.message,
11+
newFileContent:
12+
`enum E {
13+
A
14+
}
15+
let obj: Record<E, any> = {
16+
[E.A]: undefined
17+
}`,
18+
});

0 commit comments

Comments
 (0)
Please sign in to comment.