Repository: prettier/prettier (JavaScript)
Date: 2024-06-04 08:54:03+00:00
Task type: Bug Fix

Issue:
Unexpected line break in `assert { type: "json" }`
**Prettier 3.3.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEB6VACL2e7-gwo4rADgAYMwALAQwCcBnDQZMIMAdKASwFsAHCPRgZaARkoAzehB4cQAOlS1lK1fIBWjaOxAjGjOEIzAMMAJ584SOZu26AvgG5OvAUdoAmSdNk7FqgMCAjS0oHT0DIxNzS2sdWzCHZ25+QWFaAGZvGTl-IPyC4ITw2n1DYWiLKxtQ8KcXVPcAFmzfBSVCzq7AkLsI8uNTKriQYqSGt3SAVlbcju6FxZ6x-qih2Jq++pTJkQA2Wb95pZPT5drdUsiK9er4i4xt1zSRAHZD9rOv78LexNWbjE7qMHk9GukKBgpDkjj84fDOn8SmU1kCRisnCAADQgCB8GBcaCMZCgBjSADuAAUGAhiShaAAbcm0MzEnEAI3otDAAGs4DAAMq0HhwAAyXCgcGQEkZBg5XN5-IFfG5EoA5sgYPQAK5wHEGHhcTU6vUgOAAD0s9F4CBgjIAKoYoAwuHA6TKGXKQIx1Qy4ABFbUQeDS2WmzTmgW+gNBkNID1egCOsbglOkfDpIFKAFpJXAACYF7EgLW0LgM9UAYRkPFoyCzDIZxZ9UDVfoAgjAtVx2dr4JTDOLJaHPabqDAeAyAOrULjwRgqsBwAW0udcABuc7M9bA+mL691AEkoIXYAKwNb8e2TwLzH6R16+NIDFOuXx60+3YZ11KcRLrmmtBqrWD6miqTCGPW7K0OycBNjiT4SjAU5cPmMDUMgFA4vQcDJlwOGAcBdbxmGOJ2uyKFoRhSAeDi2oGPaMHuqRZo8LB+aFvmoq0K22pAXAABigi1l26r1rQfYQCA9j2EAA)
<!-- prettier-ignore -->
```sh
--parser babel
```

**Input:**
<!-- prettier-ignore -->
```ts
//                                                                    80 chars ↓ 
import a10 from "./aaaaaaaaaa.json" assert { type: "json" };
import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a40 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a50 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a60 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a70 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a80 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
```

**Output:**
<!-- prettier-ignore -->
```ts
//                                                                    80 chars ↓
import a10 from "./aaaaaaaaaa.json" assert { type: "json" };
import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type: "json" };
import a40 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type:
  "json" };
import a50 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type:
  "json" };
import a60 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type:
  "json" };
import a70 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type:
  "json" };
import a80 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" assert { type:
  "json" };

```

**Expected output:**

same as input

**Why?**

Newline after `type:` breaks the rhythm of the import statements. There was no line break in 3.2 and below. The behavior changed in 3.3 via https://github.com/prettier/prettier/pull/15888. Newline is consistently added after `type:` after reaching `--print-width`, even if `assert` does not fit too.

@sosukesuzuki I suggest to enforce a single line here. WDYT?


Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/javascript/16349.md b/changelog_unreleased/javascript/16349.md
new file mode 100644
index 000000000000..5449ced4bc3a
--- /dev/null
+++ b/changelog_unreleased/javascript/16349.md
@@ -0,0 +1,14 @@
+#### Avoid line breaks in import attributes (#16349 by @fisker)
+
+<!-- prettier-ignore -->
+```jsx
+// Input
+import something from "./some-very-very-very-very-very-very-very-very-long-path.json" with { type: "json" };
+
+// Prettier stable
+import something from "./some-very-very-very-very-very-very-very-very-long-path.json" with { type:
+  "json" };
+
+// Prettier main
+import something from "./some-very-very-very-very-very-very-very-very-long-path.json" with { type: "json" };
+```
diff --git a/src/language-js/print/assignment.js b/src/language-js/print/assignment.js
index 9ea30b5b2275..5d6afaf8758f 100644
--- a/src/language-js/print/assignment.js
+++ b/src/language-js/print/assignment.js
@@ -138,6 +138,7 @@ function chooseLayout(path, options, print, leftDoc, rightPropertyName) {
   }
 
   if (
+    node.type === "ImportAttribute" ||
     (rightNode.type === "CallExpression" &&
       rightNode.callee.name === "require") ||
     // do not put values on a separate line from the key in json


Test patch:
diff --git a/tests/format/js/import-attributes/__snapshots__/format.test.js.snap b/tests/format/js/import-attributes/__snapshots__/format.test.js.snap
index db49f25eca14..7bdeced1286c 100644
--- a/tests/format/js/import-attributes/__snapshots__/format.test.js.snap
+++ b/tests/format/js/import-attributes/__snapshots__/format.test.js.snap
@@ -159,6 +159,97 @@ export * from "./test.json" /* with */ /* with */ assert { type: "json" };
 ================================================================================
 `;
 
+exports[`long-sources.js [acorn] format 1`] = `
+"Unexpected token (1:37)
+> 1 | import a10 from "./aaaaaaaaaa.json" with {
+    |                                     ^
+  2 | type: "json" };
+  3 | import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+  4 | import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+Cause: Unexpected token (1:36)"
+`;
+
+exports[`long-sources.js [espree] format 1`] = `
+"Unexpected token with (1:37)
+> 1 | import a10 from "./aaaaaaaaaa.json" with {
+    |                                     ^
+  2 | type: "json" };
+  3 | import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+  4 | import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+Cause: Unexpected token with"
+`;
+
+exports[`long-sources.js [meriyah] format 1`] = `
+"Unexpected token: 'with' (1:40)
+> 1 | import a10 from "./aaaaaaaaaa.json" with {
+    |                                        ^
+  2 | type: "json" };
+  3 | import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+  4 | import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+Cause: [1:40]: Unexpected token: 'with'"
+`;
+
+exports[`long-sources.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+import a10 from "./aaaaaaaaaa.json" with {
+type: "json" };
+import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a40 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a50 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a60 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a70 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with {
+type: "json" };
+import a80
+from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+
+import("./aaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+
+=====================================output=====================================
+import a10 from "./aaaaaaaaaa.json" with { type: "json" };
+import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a40 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a50 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a60 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a70 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a80 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+
+import("./aaaaaaaaaa.json", { with: { type: "json" } });
+import("./aaaaaaaaaaaaaaaaaaaa.json", { with: { type: "json" } });
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", { with: { type: "json" } });
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {
+  with: { type: "json" },
+});
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {
+  with: { type: "json" },
+});
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {
+  with: { type: "json" },
+});
+import(
+  "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json",
+  { with: { type: "json" } }
+);
+import(
+  "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json",
+  { with: { type: "json" } }
+);
+
+================================================================================
+`;
+
 exports[`multi-types.js [acorn] format 1`] = `
 "Unexpected token (1:31)
 > 1 | import json from "./foo.json" with { type: "json", type: "bar" };
diff --git a/tests/format/js/import-attributes/format.test.js b/tests/format/js/import-attributes/format.test.js
index e21961a548d1..0820459aac5d 100644
--- a/tests/format/js/import-attributes/format.test.js
+++ b/tests/format/js/import-attributes/format.test.js
@@ -8,6 +8,7 @@ const errors = {
     "without-from.js",
     "non-type.js",
     "keyword-detect.js",
+    "long-sources.js",
   ],
   espree: [
     "dynamic-import.js",
@@ -18,6 +19,7 @@ const errors = {
     "without-from.js",
     "non-type.js",
     "keyword-detect.js",
+    "long-sources.js",
   ],
   meriyah: [
     "dynamic-import.js",
@@ -28,6 +30,7 @@ const errors = {
     "without-from.js",
     "non-type.js",
     "keyword-detect.js",
+    "long-sources.js",
   ],
 };
 runFormatTest(import.meta, ["babel", "typescript"], { errors });
diff --git a/tests/format/js/import-attributes/long-sources.js b/tests/format/js/import-attributes/long-sources.js
new file mode 100644
index 000000000000..042d421cc956
--- /dev/null
+++ b/tests/format/js/import-attributes/long-sources.js
@@ -0,0 +1,20 @@
+import a10 from "./aaaaaaaaaa.json" with {
+type: "json" };
+import a20 from "./aaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a30 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a40 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a50 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a60 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+import a70 from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with {
+type: "json" };
+import a80
+from "./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json" with { type: "json" };
+
+import("./aaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})
+import("./aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.json", {with: { type: "json" }})