From e9c5b1ec319c3c7ef4f77e6edc012a0d2a1bcb63 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:10:04 +0000 Subject: [PATCH] fix(formatter): treat `PrivateFieldExpression` as simple call argument (#19348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Treat `PrivateFieldExpression` (`this.#v`) as a simple call argument, matching Prettier's behavior where it's a `MemberExpression` with a `PrivateIdentifier` - Without this fix, `this.#v` as a call argument causes the formatter to break arguments into multiple lines unnecessarily **Before:** ```ts this.#t = setInterval( () => { console.log(); }, this.#v, ); ``` **After (matches Prettier):** ```ts this.#t = setInterval(() => { console.log(); }, this.#v); ``` Closes #19330 ## Test plan - [x] Added test fixture `private-field-call-args.ts` reproducing the issue - [x] All 171 formatter tests pass - [x] Prettier conformance unchanged: JS 746/753 (99.07%), TS 591/601 (98.34%) - [x] Clippy clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../src/utils/member_chain/simple_argument.rs | 8 +++ .../fixtures/ts/private-field-call-args.ts | 15 +++++ .../ts/private-field-call-args.ts.snap | 60 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts create mode 100644 crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts.snap diff --git a/crates/oxc_formatter/src/utils/member_chain/simple_argument.rs b/crates/oxc_formatter/src/utils/member_chain/simple_argument.rs index a4b72405466d0..045f47a08c8d4 100644 --- a/crates/oxc_formatter/src/utils/member_chain/simple_argument.rs +++ b/crates/oxc_formatter/src/utils/member_chain/simple_argument.rs @@ -96,6 +96,14 @@ impl<'a, 'b> SimpleArgument<'a, 'b> { Self::from(&computed_expression.expression).is_simple_impl(depth) && Self::from(&computed_expression.object).is_simple_impl(depth) } + // In Prettier's default `typescript` parser (typescript-estree) AST, + // `this.#v` is a `MemberExpression` with a `PrivateIdentifier` property. + // In oxc's AST, it's a separate type. + // The private field name is always simple, so only check the object. + // https://github.com/prettier/prettier/blob/093745f0ec429d3db47c1edd823357e0ef24e226/src/language-js/utilities/index.js#L643-L648 + Expression::PrivateFieldExpression(private_field) => { + Self::from(&private_field.object).is_simple_impl(depth) + } Expression::NewExpression(expr) => { Self::is_simple_call_like(&expr.callee, &expr.arguments, depth) } diff --git a/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts b/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts new file mode 100644 index 0000000000000..3a194f651adad --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts @@ -0,0 +1,15 @@ +// Private field access should not force multi-line call arguments +class Foo { + #t: NodeJS.Timeout | undefined = undefined; + #v: number; + + constructor(v: number) { + this.#v = v; + } + + start() { + this.#t = setInterval(() => { + console.log(); + }, this.#v); + } +} diff --git a/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts.snap b/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts.snap new file mode 100644 index 0000000000000..9022aeb185f9c --- /dev/null +++ b/crates/oxc_formatter/tests/fixtures/ts/private-field-call-args.ts.snap @@ -0,0 +1,60 @@ +--- +source: crates/oxc_formatter/tests/fixtures/mod.rs +--- +==================== Input ==================== +// Private field access should not force multi-line call arguments +class Foo { + #t: NodeJS.Timeout | undefined = undefined; + #v: number; + + constructor(v: number) { + this.#v = v; + } + + start() { + this.#t = setInterval(() => { + console.log(); + }, this.#v); + } +} + +==================== Output ==================== +------------------ +{ printWidth: 80 } +------------------ +// Private field access should not force multi-line call arguments +class Foo { + #t: NodeJS.Timeout | undefined = undefined; + #v: number; + + constructor(v: number) { + this.#v = v; + } + + start() { + this.#t = setInterval(() => { + console.log(); + }, this.#v); + } +} + +------------------- +{ printWidth: 100 } +------------------- +// Private field access should not force multi-line call arguments +class Foo { + #t: NodeJS.Timeout | undefined = undefined; + #v: number; + + constructor(v: number) { + this.#v = v; + } + + start() { + this.#t = setInterval(() => { + console.log(); + }, this.#v); + } +} + +===================== End =====================