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
This commit is contained in:
Oleksandr T 2022-08-24 21:54:44 +03:00 committed by GitHub
parent fb717df6bf
commit 8d7ad8c3ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 13 deletions

View File

@ -464,6 +464,7 @@ namespace ts {
signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration,
symbolToEntityName: nodeBuilder.symbolToEntityName,
symbolToExpression: nodeBuilder.symbolToExpression,
symbolToNode: nodeBuilder.symbolToNode,
symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations,
symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration,
typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration,
@ -4822,7 +4823,10 @@ namespace ts {
if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) {
nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain;
}
const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName;
if (flags & SymbolFormatFlags.WriteComputedProps) {
nodeFlags |= NodeBuilderFlags.WriteComputedProps;
}
const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName;
return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker);
function symbolToStringWorker(writer: EmitTextWriter) {
@ -4919,8 +4923,25 @@ namespace ts {
withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)),
symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) =>
withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)),
symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)),
};
function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) {
if (context.flags & NodeBuilderFlags.WriteComputedProps) {
if (symbol.valueDeclaration) {
const name = getNameOfDeclaration(symbol.valueDeclaration);
if (name && isComputedPropertyName(name)) return name;
}
const nameType = getSymbolLinks(symbol).nameType;
if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) {
context.enclosingDeclaration = nameType.symbol.valueDeclaration;
return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning));
}
}
return symbolToExpression(symbol, context, meaning);
}
function withContext<T>(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined {
Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0);
const context: NodeBuilderContext = {
@ -20310,7 +20331,7 @@ namespace ts {
shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it
}
if (props.length === 1) {
const propName = symbolToString(unmatchedProperty);
const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps);
reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target));
if (length(unmatchedProperty.declarations)) {
associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName));

View File

@ -4595,6 +4595,8 @@ namespace ts {
/** Note that the resulting nodes cannot be checked. */
symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Expression | undefined;
/** Note that the resulting nodes cannot be checked. */
/* @internal */ symbolToNode(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Node | undefined;
/** Note that the resulting nodes cannot be checked. */
symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeArray<TypeParameterDeclaration> | undefined;
/** Note that the resulting nodes cannot be checked. */
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
@ -4872,6 +4874,7 @@ namespace ts {
AllowEmptyTuple = 1 << 19,
AllowUniqueESSymbolType = 1 << 20,
AllowEmptyIndexInfoType = 1 << 21,
/* @internal */ WriteComputedProps = 1 << 30, // { [E.A]: 1 }
// Errors (cont.)
AllowNodeModulesRelativePaths = 1 << 26,
@ -4930,27 +4933,30 @@ namespace ts {
}
export const enum SymbolFormatFlags {
None = 0x00000000,
None = 0,
// Write symbols's type argument if it is instantiated symbol
// eg. class C<T> { p: T } <-- Show p as C<T>.p here
// var a: C<number>;
// var p = a.p; <--- Here p is property of C<number> so show it as C<number>.p instead of just C.p
WriteTypeParametersOrArguments = 0x00000001,
WriteTypeParametersOrArguments = 1 << 0,
// Use only external alias information to get the symbol name in the given context
// eg. module m { export class c { } } import x = m.c;
// When this flag is specified m.c will be used to refer to the class instead of alias symbol x
UseOnlyExternalAliasing = 0x00000002,
UseOnlyExternalAliasing = 1 << 1,
// Build symbol name using any nodes needed, instead of just components of an entity name
AllowAnyNodeKind = 0x00000004,
AllowAnyNodeKind = 1 << 2,
// Prefer aliases which are not directly visible
UseAliasDefinedOutsideCurrentScope = 0x00000008,
UseAliasDefinedOutsideCurrentScope = 1 << 3,
// { [E.A]: 1 }
/* @internal */ WriteComputedProps = 1 << 4,
// Skip building an accessible symbol chain
/* @internal */ DoNotIncludeSymbolChain = 0x00000010,
/* @internal */ DoNotIncludeSymbolChain = 1 << 5,
}
/* @internal */

View File

@ -642,11 +642,9 @@ namespace ts.codefix {
}
function createPropertyNameFromSymbol(symbol: Symbol, target: ScriptTarget, quotePreference: QuotePreference, checker: TypeChecker) {
if (isTransientSymbol(symbol) && symbol.nameType && symbol.nameType.flags & TypeFlags.UniqueESSymbol) {
const expression = checker.symbolToExpression((symbol.nameType as UniqueESSymbolType).symbol, SymbolFlags.Value, symbol.valueDeclaration, NodeBuilderFlags.AllowUniqueESSymbolType);
if (expression) {
return factory.createComputedPropertyName(expression);
}
if (isTransientSymbol(symbol)) {
const prop = checker.symbolToNode(symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.WriteComputedProps);
if (prop && isComputedPropertyName(prop)) return prop;
}
return createPropertyNameNodeForIdentifierOrLiteral(symbol.name, target, quotePreference === QuotePreference.Single);
}

View File

@ -0,0 +1,12 @@
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>'.
==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts (1 errors) ====
enum E {
A
}
let foo: Record<E, any> = {}
~~~
!!! error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record<E, any>'.

View File

@ -0,0 +1,14 @@
//// [assignmentCompatWithEnumIndexer.ts]
enum E {
A
}
let foo: Record<E, any> = {}
//// [assignmentCompatWithEnumIndexer.js]
var E;
(function (E) {
E[E["A"] = 0] = "A";
})(E || (E = {}));
var foo = {};

View File

@ -0,0 +1,13 @@
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts ===
enum E {
>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0))
A
>A : Symbol(E.A, Decl(assignmentCompatWithEnumIndexer.ts, 0, 8))
}
let foo: Record<E, any> = {}
>foo : Symbol(foo, Decl(assignmentCompatWithEnumIndexer.ts, 4, 3))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0))

View File

@ -0,0 +1,12 @@
=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts ===
enum E {
>E : E
A
>A : E.A
}
let foo: Record<E, any> = {}
>foo : Record<E, any>
>{} : {}

View File

@ -0,0 +1,5 @@
enum E {
A
}
let foo: Record<E, any> = {}

View File

@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />
////enum E {
//// A
////}
////let obj: Record<E, any> = {}
verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newFileContent:
`enum E {
A
}
let obj: Record<E, any> = {
[E.A]: undefined
}`,
});