Before You File a Bug Report Please Confirm You Have Done The Following...
Playground Link
https://typescript-eslint.io/play/#ts=5.7.2&fileType=.ts&code=DwbwBAhgXGB2CuBbARgUwE5gL4D5zTAEZsA6ADwG4AoUSGBFDbPOo7MAaiOtoIbUy58MYlgA0YAGYB7aQAoAlNSA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQAIBcBPABxQGNoBLY-AWhXkoDt8B6Jge1tiacTJTIAhtEK0ipWkOTJE0fJQ5N0UOdA7RI4MAF8QOoA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false
Repro Code
<{ a: number }>{ a: 1 }.x;
<{ a: number }>{ a: 1 } + 1;
<{ a: number }>{ a: 1 }, foo();
ESLint Config
module.exports = {
parser: "@typescript-eslint/parser",
rules: {
"@typescript-eslint/no-unnecessary-type-assertion": "error",
},
};
tsconfig
Expected Result
Autofix produces parseable code that preserves the original semantics, e.g. wraps the object literal in parentheses:
({ a: 1 }).x;
({ a: 1 }) + 1;
({ a: 1 }), foo();
Actual Result
The fixer removes the <...> tokens unconditionally, leaving the object literal at the start of the statement, where { is parsed as a block:
{ a: 1 }.x; // SyntaxError on `.x`
{ a: 1 } + 1; // parses as Block + leading-`+` expression statement
{ a: 1 }, foo(); // parses as Block + sequence-expression statement
Additional Info
Follow-up to #12393 / #12394.
#12394 covers two shapes: arrow concise body (() => <T>{ ... }) and a bare ExpressionStatement whose entire expression is the assertion (<T>{ ... };). It does not cover the broader class where the assertion sits at the left edge of a larger expression that is itself an ExpressionStatement. The leading { after the fix still leads the statement and gets parsed as a block.
Affected positions (left-edge descent from the ExpressionStatement's expression):
MemberExpression.object — <T>{a:1}.x;
CallExpression.callee — <T>{a:1}();
NewExpression.callee — new <T>{a:1}(); (unusual, but parses)
BinaryExpression.left — <T>{a:1} + 1;
LogicalExpression.left — <T>{a:1} && foo;
ConditionalExpression.test — <T>{a:1} ? a : b;
SequenceExpression.expressions[0] — <T>{a:1}, foo();
TaggedTemplateExpression.tag — <T>{a:1}`x`; (also unusual)
AssignmentExpression.left is invalid here, so n/a.
Catching this properly means walking up through left-edge parents until the parent is the ExpressionStatement, then wrapping in parens — analogous to how a pretty-printer decides "starts-with-{" needs grouping.
Discussed on #12394 (review thread r3347832383); the maintainer reviewing #12394 was fine with deferring the broader class to a separate issue. Filing so it isn't lost. Severity is low — these shapes are uncommon in real code, but the autofix is silently incorrect when they occur (some variants change semantics rather than throw, which is arguably worse).
AI disclosure: Used an AI assistant to help enumerate the left-edge positions and format the issue. The bug class itself was identified during review of #12394.
💖
Before You File a Bug Report Please Confirm You Have Done The Following...
Playground Link
https://typescript-eslint.io/play/#ts=5.7.2&fileType=.ts&code=DwbwBAhgXGB2CuBbARgUwE5gL4D5zTAEZsA6ADwG4AoUSGBFDbPOo7MAaiOtoIbUy58MYlgA0YAGYB7aQAoAlNSA&eslintrc=N4KABGBEBOCuA2BTAzpAXGUEKQAIBcBPABxQGNoBLY-AWhXkoDt8B6Jge1tiacTJTIAhtEK0ipWkOTJE0fJQ5N0UOdA7RI4MAF8QOoA&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false
Repro Code
ESLint Config
tsconfig
{ "compilerOptions": { "strictNullChecks": true } }Expected Result
Autofix produces parseable code that preserves the original semantics, e.g. wraps the object literal in parentheses:
Actual Result
The fixer removes the
<...>tokens unconditionally, leaving the object literal at the start of the statement, where{is parsed as a block:Additional Info
Follow-up to #12393 / #12394.
#12394 covers two shapes: arrow concise body (
() => <T>{ ... }) and a bareExpressionStatementwhose entire expression is the assertion (<T>{ ... };). It does not cover the broader class where the assertion sits at the left edge of a larger expression that is itself anExpressionStatement. The leading{after the fix still leads the statement and gets parsed as a block.Affected positions (left-edge descent from the
ExpressionStatement's expression):MemberExpression.object—<T>{a:1}.x;CallExpression.callee—<T>{a:1}();NewExpression.callee—new <T>{a:1}();(unusual, but parses)BinaryExpression.left—<T>{a:1} + 1;LogicalExpression.left—<T>{a:1} && foo;ConditionalExpression.test—<T>{a:1} ? a : b;SequenceExpression.expressions[0]—<T>{a:1}, foo();TaggedTemplateExpression.tag—<T>{a:1}`x`;(also unusual)AssignmentExpression.leftis invalid here, so n/a.Catching this properly means walking up through left-edge parents until the parent is the
ExpressionStatement, then wrapping in parens — analogous to how a pretty-printer decides "starts-with-{" needs grouping.Discussed on #12394 (review thread r3347832383); the maintainer reviewing #12394 was fine with deferring the broader class to a separate issue. Filing so it isn't lost. Severity is low — these shapes are uncommon in real code, but the autofix is silently incorrect when they occur (some variants change semantics rather than throw, which is arguably worse).
AI disclosure: Used an AI assistant to help enumerate the left-edge positions and format the issue. The bug class itself was identified during review of #12394.
💖