Skip to content

fix(parser): avoid TS1477 for parenthesized instantiation expressions#23135

Closed
shawnrice wants to merge 1 commit into
oxc-project:mainfrom
shawnrice:fix/ts1477-parenthesized-instantiation-expression
Closed

fix(parser): avoid TS1477 for parenthesized instantiation expressions#23135
shawnrice wants to merge 1 commit into
oxc-project:mainfrom
shawnrice:fix/ts1477-parenthesized-instantiation-expression

Conversation

@shawnrice

Copy link
Copy Markdown

(Foo<Bar>).baz is valid TypeScript but got rejected as TS1477, and oxc-parser's behavior diverges from tsc. The check for TS1477 keyed off AST node shape, which only tells it apart from the invalid Foo<Bar>.baz when the parens are preserved as a wrapper node. oxfmt parses with preserve_parens: false, so it hits the false positive.

Fix: Track whether an instantiation expression was parenthesized in parse state instead, mirroring the existing not_parenthesized_arrow.

Fixes #23133

AI usage disclosure: developed with Claude Code, reviewed and tested by me.

`(Foo<Bar>).baz` is valid TypeScript but got rejected as TS1477. The check
keyed off AST node shape, which only tells it apart from the invalid
`Foo<Bar>.baz` when the parens are preserved as a wrapper node. oxfmt and
oxlint parse with `preserve_parens: false`, so they hit the false positive.

Track whether an instantiation expression was parenthesized in parse state
instead, mirroring the existing `not_parenthesized_arrow`.

AI usage disclosure: developed with Claude Code, reviewed and tested by me.
graphite-app Bot pushed a commit that referenced this pull request Jun 9, 2026
…sion (#23147)

`oxc_parser` emitted TS1477 ("An instantiation expression cannot be followed by a property access") for parenthesized instantiation expressions such as `(a<b>).c` when `preserve_parens: false` (the mode oxfmt and oxlint use). tsc and Babel accept it — only the *unparenthesized* form is an error.

The old check keyed off `lhs` being a `TSInstantiationExpression`, but the `ParenthesizedExpression` wrapper that distinguishes `(a<b>).c` from `a<b>.c` is dropped when `preserve_parens` is off, so the bare instantiation leaked through.

## Fix

Emit TS1477 only when the instantiation expression is *not* parenthesized. Instead of relying on a `ParenthesizedExpression` node surviving the AST, detect it by span: an instantiation built in the member-expression loop starts at `lhs_span`, while one unwrapped from a stripped paren starts past the `(`. A shared helper covers both the property-access (`.`) and element-access (`[`) branches.

| source | result | notes |
| --- | --- | --- |
| `a<b>.c` | TS1477 | |
| `a<b>?.[c]` | TS1477 | element access on a bare instantiation |
| `a?.b<c>.d` | TS1477 | |
| `(a<b>).c` | ok | the fix |
| `(a<b>)[c]` / `(a<b>)?.[c]` | ok | the fix |
| `(a).b<c>.d` | TS1477 | parens wrap `a`, not the instantiation |

Correct regardless of `preserve_parens`; under the default (`true`) behavior is unchanged, so no conformance snapshots move.

Note: tsc only checks property access (`parsePropertyAccessExpressionRest`), so it does not flag `a<b>?.[c]` — an apparent oversight, since accessing a member of an instantiation expression is the same operation either way. Babel flags both, and this PR keeps oxc's existing behavior for the unparenthesized element-access case.

Closes #23133.

Alternative to #23135 (same issue, same resulting behavior). That PR records parenthesized instantiation expressions in a new `ParserState` set; this one derives the same fact from spans, needing no extra parser state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@shawnrice

Copy link
Copy Markdown
Author

Closing as fixed. #23147 merged a more efficient version of this. Thanks for fixing, @Boshen.

@shawnrice shawnrice closed this Jun 9, 2026
camc314 pushed a commit that referenced this pull request Jul 3, 2026
…sion (#23147)

`oxc_parser` emitted TS1477 ("An instantiation expression cannot be followed by a property access") for parenthesized instantiation expressions such as `(a<b>).c` when `preserve_parens: false` (the mode oxfmt and oxlint use). tsc and Babel accept it — only the *unparenthesized* form is an error.

The old check keyed off `lhs` being a `TSInstantiationExpression`, but the `ParenthesizedExpression` wrapper that distinguishes `(a<b>).c` from `a<b>.c` is dropped when `preserve_parens` is off, so the bare instantiation leaked through.

## Fix

Emit TS1477 only when the instantiation expression is *not* parenthesized. Instead of relying on a `ParenthesizedExpression` node surviving the AST, detect it by span: an instantiation built in the member-expression loop starts at `lhs_span`, while one unwrapped from a stripped paren starts past the `(`. A shared helper covers both the property-access (`.`) and element-access (`[`) branches.

| source | result | notes |
| --- | --- | --- |
| `a<b>.c` | TS1477 | |
| `a<b>?.[c]` | TS1477 | element access on a bare instantiation |
| `a?.b<c>.d` | TS1477 | |
| `(a<b>).c` | ok | the fix |
| `(a<b>)[c]` / `(a<b>)?.[c]` | ok | the fix |
| `(a).b<c>.d` | TS1477 | parens wrap `a`, not the instantiation |

Correct regardless of `preserve_parens`; under the default (`true`) behavior is unchanged, so no conformance snapshots move.

Note: tsc only checks property access (`parsePropertyAccessExpressionRest`), so it does not flag `a<b>?.[c]` — an apparent oversight, since accessing a member of an instantiation expression is the same operation either way. Babel flags both, and this PR keeps oxc's existing behavior for the unparenthesized element-access case.

Closes #23133.

Alternative to #23135 (same issue, same resulting behavior). That PR records parenthesized instantiation expressions in a new `ParserState` set; this one derives the same fact from spans, needing no extra parser state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parser: false-positive TS1477 on parenthesized instantiation expression

1 participant