From 9febfe8ca3939e79923a8e3a35d47cbcb89e46ce Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 02:07:18 -0500 Subject: [PATCH] Place a quoted name field among an error's fields The position of an Error subclass's own `name` among its fields was found from identifier keys only, so a quoted `"name"` field, other quoted fields, and `this["name"] = ...` in a constructor were not seen, and `name` was listed last. Field keys and constructor assignments now read any statically spelled key: an identifier, a quoted string, a number in its canonical spelling, or a computed string literal. --- .../src/frontend/lowering/error-name-order.ts | 32 +++++++++++++------ tests/corpus/6403-error-own-name-order.ts | 6 ++++ tests/corpus/6404-error-own-name-order-js.js | 10 ++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/error-name-order.ts b/packages/compiler/src/frontend/lowering/error-name-order.ts index 8523b47cb..7453d123e 100644 --- a/packages/compiler/src/frontend/lowering/error-name-order.ts +++ b/packages/compiler/src/frontend/lowering/error-name-order.ts @@ -6,17 +6,27 @@ type ErrorClassLink = Pick & { base: ErrorClassLink | null; }; -/** Whether `node` is an assignment to `this.`, and the name. */ +/** The property key a member or element-access name spells statically: + * an identifier, a quoted string, a number in its canonical spelling, or a + * computed string literal. Null for other keys. */ +function staticKeyText(name: ts.Node): string | null { + if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text; + if (ts.isNumericLiteral(name)) return String(Number(name.text)); + if (ts.isComputedPropertyName(name)) return staticKeyText(name.expression); + return null; +} + +/** Whether `node` is an assignment to `this.` or `this[""]`, + * and the name. */ function thisAssignmentName(node: ts.Node): string | null { if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return null; const target = node.left; - if ( - !ts.isPropertyAccessExpression(target) || - target.expression.kind !== ts.SyntaxKind.ThisKeyword - ) - return null; - return ts.isIdentifier(target.name) ? target.name.text : null; + if (ts.isPropertyAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return ts.isIdentifier(target.name) ? target.name.text : null; + if (ts.isElementAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return staticKeyText(target.argumentExpression); + return null; } /** Where an Error subclass instance's own `name` sits among its fields when @@ -45,15 +55,17 @@ export function errorNameAfterField( for (const link of chain) { for (const property of link.paramProps ?? []) create(property.name); for (const member of link.decl!.members.nodes) { - if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name)) continue; + if (!ts.isPropertyDeclaration(member)) continue; + const key = staticKeyText(member.name); + if (key === null) continue; if ( member.modifiers?.nodes.some( (m) => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.DeclareKeyword, ) ) continue; - if (member.name.text === "name") return last; - create(member.name.text); + if (key === "name") return last; + create(key); } const body = link.ctor?.body; if (!body) continue; diff --git a/tests/corpus/6403-error-own-name-order.ts b/tests/corpus/6403-error-own-name-order.ts index 25e59bb7c..d1a5200c4 100644 --- a/tests/corpus/6403-error-own-name-order.ts +++ b/tests/corpus/6403-error-own-name-order.ts @@ -57,6 +57,11 @@ class NamedChild extends FieldBase { } } +class QuotedName extends Error { + override "name" = "QuotedName"; + "quoted-extra" = 1; +} + class Unnamed extends Error { extra = 1; } @@ -83,6 +88,7 @@ show("later field", new LaterNameField("c")); show("code then name", new CodeThenName("d")); show("child of named", new ChildOfNamed("e")); show("named child", new NamedChild("f")); +show("quoted name field", new QuotedName("q")); const late = new Unnamed("g"); late.name = "Late"; diff --git a/tests/corpus/6404-error-own-name-order-js.js b/tests/corpus/6404-error-own-name-order-js.js index 0b5ad4e82..b5f9b4d32 100644 --- a/tests/corpus/6404-error-own-name-order-js.js +++ b/tests/corpus/6404-error-own-name-order-js.js @@ -49,6 +49,15 @@ class Derived extends Base { } } +class ElementName extends Error { + constructor(message) { + super(message); + this.first = 1; + this["name"] = "ElementName"; + this.second = 2; + } +} + class Unnamed extends Error { constructor(message) { super(message); @@ -73,6 +82,7 @@ show("first", new AssignedFirst("b")); show("declared field", new DeclaredField("c")); show("name field", new NameField("d")); show("derived", new Derived("e")); +show("element name", new ElementName("g")); const late = new Unnamed("f"); late.name = "Late";