diff --git a/changelog_unreleased/typescript/14402.md b/changelog_unreleased/typescript/14402.md new file mode 100644 index 000000000..2c1cb46fa --- /dev/null +++ b/changelog_unreleased/typescript/14402.md @@ -0,0 +1,25 @@ +#### Break on TypeScript parameter properties (#14402 by @seiyab) + + +```ts +// Input +class MyClass { + constructor( + protected x: number, + private y: string + ) {} +} + +// Prettier stable +class MyClass { + constructor(protected x: number, private y: string) {} +} + +// Prettier main +class MyClass { + constructor( + protected x: number, + private y: string, + ) {} +} +``` diff --git a/src/language-js/print/function-parameters.js b/src/language-js/print/function-parameters.js index b449f25fa..712ef0faf 100644 --- a/src/language-js/print/function-parameters.js +++ b/src/language-js/print/function-parameters.js @@ -279,8 +279,17 @@ function isDecoratedFunction(path) { ); } +function shouldBreakFunctionParameters(functionNode) { + const parameters = getFunctionParameters(functionNode); + return ( + parameters.length > 1 && + parameters.some((parameter) => parameter.type === "TSParameterProperty") + ); +} + export { printFunctionParameters, shouldHugTheOnlyFunctionParameter, shouldGroupFunctionParameters, + shouldBreakFunctionParameters, }; diff --git a/src/language-js/print/function.js b/src/language-js/print/function.js index 3a40deac3..fd40444c3 100644 --- a/src/language-js/print/function.js +++ b/src/language-js/print/function.js @@ -41,6 +41,7 @@ import { locEnd } from "../loc.js"; import { printFunctionParameters, shouldGroupFunctionParameters, + shouldBreakFunctionParameters, } from "./function-parameters.js"; import { printPropertyKey } from "./property.js"; import { printFunctionTypeParameters, printDeclareToken } from "./misc.js"; @@ -171,6 +172,7 @@ function printMethodValue(path, options, print) { const { node } = path; const parametersDoc = printFunctionParameters(path, print, options); const returnTypeDoc = printReturnType(path, print); + const shouldBreakParameters = shouldBreakFunctionParameters(node); const shouldGroupParameters = shouldGroupFunctionParameters( node, returnTypeDoc @@ -178,7 +180,11 @@ function printMethodValue(path, options, print) { const parts = [ printFunctionTypeParameters(path, options, print), group([ - shouldGroupParameters ? group(parametersDoc) : parametersDoc, + shouldBreakParameters + ? group(parametersDoc, { shouldBreak: true }) + : shouldGroupParameters + ? group(parametersDoc) + : parametersDoc, returnTypeDoc, ]), ]; diff --git a/tests/format/misc/errors/typescript/modifiers/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/typescript/modifiers/__snapshots__/jsfmt.spec.js.snap index 50bbd697b..0357a2afe 100644 --- a/tests/format/misc/errors/typescript/modifiers/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/errors/typescript/modifiers/__snapshots__/jsfmt.spec.js.snap @@ -2213,174 +2213,222 @@ exports[`snippet: #163 [typescript] format 1`] = ` `; exports[`snippet: #164 [babel-ts] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {["constructor"](private parameter) {}} + | ^" +`; + +exports[`snippet: #164 [typescript] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {["constructor"](private parameter) {}} + | ^^^^^^^" +`; + +exports[`snippet: #165 [babel-ts] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {['constructor'](private parameter) {}} + | ^" +`; + +exports[`snippet: #165 [typescript] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {['constructor'](private parameter) {}} + | ^^^^^^^" +`; + +exports[`snippet: #166 [babel-ts] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {[\`constructor\`](private parameter) {}} + | ^" +`; + +exports[`snippet: #166 [typescript] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:28) +> 1 | class Foo {[\`constructor\`](private parameter) {}} + | ^^^^^^^" +`; + +exports[`snippet: #167 [babel-ts] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:33) +> 1 | class Foo {['const' + 'ructor'](private parameter) {}} + | ^" +`; + +exports[`snippet: #167 [typescript] format 1`] = ` +"A parameter property is only allowed in a constructor implementation. (1:33) +> 1 | class Foo {['const' + 'ructor'](private parameter) {}} + | ^^^^^^^" +`; + +exports[`snippet: #168 [babel-ts] format 1`] = ` "'abstract' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {abstract bar}; | ^" `; -exports[`snippet: #164 [typescript] format 1`] = ` +exports[`snippet: #168 [typescript] format 1`] = ` "'abstract' modifier cannot appear on a type member (1:13) > 1 | type Foo = {abstract bar}; | ^^^^^^^^" `; -exports[`snippet: #165 [babel-ts] format 1`] = ` +exports[`snippet: #169 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:22) > 1 | type Foo = {accessor bar}; | ^" `; -exports[`snippet: #165 [typescript] format 1`] = ` +exports[`snippet: #169 [typescript] format 1`] = ` "'accessor' modifier cannot appear on a type member (1:13) > 1 | type Foo = {accessor bar}; | ^^^^^^^^" `; -exports[`snippet: #166 [babel-ts] format 1`] = ` +exports[`snippet: #170 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:19) > 1 | type Foo = {async bar}; | ^" `; -exports[`snippet: #166 [typescript] format 1`] = ` +exports[`snippet: #170 [typescript] format 1`] = ` "'async' modifier cannot appear on a type member (1:13) > 1 | type Foo = {async bar}; | ^^^^^" `; -exports[`snippet: #167 [babel-ts] format 1`] = ` +exports[`snippet: #171 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:19) > 1 | type Foo = {const bar}; | ^" `; -exports[`snippet: #167 [typescript] format 1`] = ` +exports[`snippet: #171 [typescript] format 1`] = ` "';' expected. (1:19) > 1 | type Foo = {const bar}; | ^" `; -exports[`snippet: #168 [babel-ts] format 1`] = ` +exports[`snippet: #172 [babel-ts] format 1`] = ` "'declare' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {declare bar}; | ^" `; -exports[`snippet: #168 [typescript] format 1`] = ` +exports[`snippet: #172 [typescript] format 1`] = ` "'declare' modifier cannot appear on a type member (1:13) > 1 | type Foo = {declare bar}; | ^^^^^^^" `; -exports[`snippet: #169 [babel-ts] format 1`] = ` +exports[`snippet: #173 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:21) > 1 | type Foo = {default bar}; | ^" `; -exports[`snippet: #169 [typescript] format 1`] = ` +exports[`snippet: #173 [typescript] format 1`] = ` "';' expected. (1:21) > 1 | type Foo = {default bar}; | ^" `; -exports[`snippet: #170 [babel-ts] format 1`] = ` +exports[`snippet: #174 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:20) > 1 | type Foo = {export bar}; | ^" `; -exports[`snippet: #170 [typescript] format 1`] = ` +exports[`snippet: #174 [typescript] format 1`] = ` "'export' modifier cannot appear on a type member (1:13) > 1 | type Foo = {export bar}; | ^^^^^^" `; -exports[`snippet: #171 [babel-ts] format 1`] = ` +exports[`snippet: #175 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:16) > 1 | type Foo = {in bar}; | ^" `; -exports[`snippet: #171 [typescript] format 1`] = ` +exports[`snippet: #175 [typescript] format 1`] = ` "'in' modifier cannot appear on a type member (1:13) > 1 | type Foo = {in bar}; | ^^" `; -exports[`snippet: #172 [babel-ts] format 1`] = ` +exports[`snippet: #176 [babel-ts] format 1`] = ` "Unexpected token, expected ";" (1:17) > 1 | type Foo = {out bar}; | ^" `; -exports[`snippet: #172 [typescript] format 1`] = ` +exports[`snippet: #176 [typescript] format 1`] = ` "'out' modifier cannot appear on a type member (1:13) > 1 | type Foo = {out bar}; | ^^^" `; -exports[`snippet: #173 [babel-ts] format 1`] = ` +exports[`snippet: #177 [babel-ts] format 1`] = ` "'override' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {override bar}; | ^" `; -exports[`snippet: #173 [typescript] format 1`] = ` +exports[`snippet: #177 [typescript] format 1`] = ` "'override' modifier cannot appear on a type member (1:13) > 1 | type Foo = {override bar}; | ^^^^^^^^" `; -exports[`snippet: #174 [babel-ts] format 1`] = ` +exports[`snippet: #178 [babel-ts] format 1`] = ` "'private' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {private bar}; | ^" `; -exports[`snippet: #174 [typescript] format 1`] = ` +exports[`snippet: #178 [typescript] format 1`] = ` "'private' modifier cannot appear on a type member (1:13) > 1 | type Foo = {private bar}; | ^^^^^^^" `; -exports[`snippet: #175 [babel-ts] format 1`] = ` +exports[`snippet: #179 [babel-ts] format 1`] = ` "'protected' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {protected bar}; | ^" `; -exports[`snippet: #175 [typescript] format 1`] = ` +exports[`snippet: #179 [typescript] format 1`] = ` "'protected' modifier cannot appear on a type member (1:13) > 1 | type Foo = {protected bar}; | ^^^^^^^^^" `; -exports[`snippet: #176 [babel-ts] format 1`] = ` +exports[`snippet: #180 [babel-ts] format 1`] = ` "'public' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {public bar}; | ^" `; -exports[`snippet: #176 [typescript] format 1`] = ` +exports[`snippet: #180 [typescript] format 1`] = ` "'public' modifier cannot appear on a type member (1:13) > 1 | type Foo = {public bar}; | ^^^^^^" `; -exports[`snippet: #177 [babel-ts] format 1`] = ` +exports[`snippet: #181 [babel-ts] format 1`] = ` "'static' modifier cannot appear on a type member. (1:13) > 1 | type Foo = {static bar}; | ^" `; -exports[`snippet: #177 [typescript] format 1`] = ` +exports[`snippet: #181 [typescript] format 1`] = ` "'static' modifier cannot appear on a type member (1:13) > 1 | type Foo = {static bar}; | ^^^^^^" `; -exports[`snippet: #178 [babel-ts] format 1`] = ` +exports[`snippet: #182 [babel-ts] format 1`] = ` "Index signatures cannot have the 'declare' modifier. (2:3) 1 | class Foo { > 2 | declare [index: string]: number @@ -2388,7 +2436,7 @@ exports[`snippet: #178 [babel-ts] format 1`] = ` 3 | }" `; -exports[`snippet: #178 [typescript] format 1`] = ` +exports[`snippet: #182 [typescript] format 1`] = ` "'declare' modifier cannot appear on an index signature (2:3) 1 | class Foo { > 2 | declare [index: string]: number diff --git a/tests/format/misc/errors/typescript/modifiers/jsfmt.spec.js b/tests/format/misc/errors/typescript/modifiers/jsfmt.spec.js index 083812419..237b30b75 100644 --- a/tests/format/misc/errors/typescript/modifiers/jsfmt.spec.js +++ b/tests/format/misc/errors/typescript/modifiers/jsfmt.spec.js @@ -103,6 +103,11 @@ run_spec( ).map( (modifier) => `class Foo { constructor(${modifier} parameter) {} }` ), + 'class Foo {["constructor"](private parameter) {}}', + "class Foo {['constructor'](private parameter) {}}", + "class Foo {[`constructor`](private parameter) {}}", + // cspell:disable-next-line + "class Foo {['const' + 'ructor'](private parameter) {}}", // `TSPropertySignature` ...POSSIBLE_MODIFIERS.filter((modifier) => modifier !== "readonly").map( diff --git a/tests/format/typescript/class/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/class/__snapshots__/jsfmt.spec.js.snap index 9d4e88ce0..b0dc45c0a 100644 --- a/tests/format/typescript/class/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/class/__snapshots__/jsfmt.spec.js.snap @@ -450,6 +450,65 @@ class A { ================================================================================ `; +exports[`parameter-properties.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +class MyClass { + constructor(protected x: number, private y: string) { + } +} + +[ + class { + "constructor"(protected x: number, private y: string) { + } + }, +] + +class Mixed { + constructor(public a: number, b: unknown) { + } +} + +class OneParameterProperty { + constructor(public foobar: boolean) { + } +} + +=====================================output===================================== +class MyClass { + constructor( + protected x: number, + private y: string, + ) {} +} + +[ + class { + constructor( + protected x: number, + private y: string, + ) {} + }, +]; + +class Mixed { + constructor( + public a: number, + b: unknown, + ) {} +} + +class OneParameterProperty { + constructor(public foobar: boolean) {} +} + +================================================================================ +`; + exports[`quoted-property.ts format 1`] = ` ====================================options===================================== parsers: ["typescript"] diff --git a/tests/format/typescript/class/parameter-properties.ts b/tests/format/typescript/class/parameter-properties.ts new file mode 100644 index 000000000..a488a14e2 --- /dev/null +++ b/tests/format/typescript/class/parameter-properties.ts @@ -0,0 +1,21 @@ +class MyClass { + constructor(protected x: number, private y: string) { + } +} + +[ + class { + "constructor"(protected x: number, private y: string) { + } + }, +] + +class Mixed { + constructor(public a: number, b: unknown) { + } +} + +class OneParameterProperty { + constructor(public foobar: boolean) { + } +} diff --git a/tests/format/typescript/conformance/classes/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/conformance/classes/__snapshots__/jsfmt.spec.js.snap index 87892754d..b3cd4265d 100644 --- a/tests/format/typescript/conformance/classes/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/conformance/classes/__snapshots__/jsfmt.spec.js.snap @@ -348,11 +348,18 @@ class Thing3 extends Thing2 { type Constructor = new (...args: any[]) => T; class Base { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number, + ) {} } class Derived extends Base { - constructor(x: number, y: number, public z: number) { + constructor( + x: number, + y: number, + public z: number, + ) { super(x, y); } } @@ -485,11 +492,18 @@ const Timestamped = >(Base: CT) => { type Constructor = new (...args: any[]) => T; class Base { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number, + ) {} } class Derived extends Base { - constructor(x: number, y: number, public z: number) { + constructor( + x: number, + y: number, + public z: number, + ) { super(x, y); } } diff --git a/tests/format/typescript/conformance/classes/constructorDeclarations/constructorParameters/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/conformance/classes/constructorDeclarations/constructorParameters/__snapshots__/jsfmt.spec.js.snap index 9af9892ee..70ae1ed0d 100644 --- a/tests/format/typescript/conformance/classes/constructorDeclarations/constructorParameters/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/conformance/classes/constructorDeclarations/constructorParameters/__snapshots__/jsfmt.spec.js.snap @@ -120,7 +120,10 @@ class C { class D { constructor(x: T, y: U); - constructor(x: T = 1, public y: U = x) { + constructor( + x: T = 1, + public y: U = x, + ) { var z = x; } } @@ -235,7 +238,10 @@ var r4 = d.z; // error =====================================output===================================== class C { y: string; - constructor(private x: string, protected z: string) {} + constructor( + private x: string, + protected z: string, + ) {} } var c: C; @@ -245,7 +251,11 @@ var r3 = c.z; // error class D { y: T; - constructor(a: T, private x: T, protected z: T) {} + constructor( + a: T, + private x: T, + protected z: T, + ) {} } var d: D; diff --git a/tests/format/typescript/conformance/internalModules/importDeclarations/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/conformance/internalModules/importDeclarations/__snapshots__/jsfmt.spec.js.snap index 9a81e7dab..c2011e6d3 100644 --- a/tests/format/typescript/conformance/internalModules/importDeclarations/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/conformance/internalModules/importDeclarations/__snapshots__/jsfmt.spec.js.snap @@ -128,7 +128,10 @@ var p: M.D.Point; module A { export var x = "hello world"; export class Point { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number, + ) {} } export module B { export interface Id { @@ -153,7 +156,10 @@ module X { export module Y { export class Point { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number, + ) {} } } } @@ -261,7 +267,10 @@ var p: { x: number; y: number; }; =====================================output===================================== module moduleA { export class Point { - constructor(public x: number, public y: number) {} + constructor( + public x: number, + public y: number, + ) {} } }