prettier-plugin-hug-call-arguments 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -120,8 +120,47 @@ db.updateTable("User").set({
120
120
  ## How it works
121
121
 
122
122
  This plugin wraps Prettier's built-in `estree` printer and only overrides the
123
- `print` step for `CallExpression` and `ArrowFunctionExpression` nodes that
124
- match one of a few narrow, specific shapes.
123
+ `print` step for `CallExpression`, `ArrayExpression`, and
124
+ `ArrowFunctionExpression` nodes that match one of a few narrow, specific
125
+ shapes.
126
+
127
+ **Preserving a layout you already chose:**
128
+
129
+ ```js
130
+ // input
131
+ foo(
132
+ 1,
133
+ 2,
134
+ );
135
+
136
+ // with this plugin — kept exactly as written
137
+ foo(
138
+ 1,
139
+ 2,
140
+ );
141
+
142
+ // bar(1, 2, 3) stays on one line — nothing to preserve, so it's printed
143
+ // the normal, printWidth-aware way
144
+ ```
145
+
146
+ Prettier already does this for object literals by default (`objectWrap:
147
+ "preserve"`): write `{` with a newline before the first property and it
148
+ stays multi-line, even if it would fit on one line. This plugin extends that
149
+ same "respect how the author wrote it" behavior to call arguments, array
150
+ elements, and arrow function parameter lists, which Prettier doesn't
151
+ normally preserve — those always auto-collapse to fit `printWidth`
152
+ regardless of the original formatting.
153
+
154
+ - applies once there are at least two items with a newline right after the
155
+ opening `(`/`[` and before the first one — a single item has no "one per
156
+ line" layout to preserve, so that's left to this plugin's other hugging
157
+ behavior, or Prettier's own
158
+ - takes priority over every other feature below: a layout you explicitly
159
+ chose is never fought by this plugin's own hugging heuristics
160
+ - bails on comments anywhere in the list, a sparse array, TS type
161
+ arguments/parameters, or (for parameters) anything but a plain arrow
162
+ function — a `function` expression/declaration has a name and block body
163
+ to reproduce too, which is more than this narrow check takes on
125
164
 
126
165
  **Hugging a call-wrapped callback:**
127
166
 
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ interface ESNode {
28
28
  async?: boolean;
29
29
  returnType?: unknown;
30
30
  predicate?: unknown;
31
+ elements?: ESNode[];
31
32
  }
32
33
  declare const plugin: Plugin<ESNode>;
33
34
  export = plugin;
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ const doc_1 = require("prettier/doc");
59
59
  // what `require`/interop resolves to, and its printer object is what we
60
60
  // spread and delegate to for every node we don't special-case.
61
61
  const estree = __importStar(require("prettier/plugins/estree"));
62
- const { group, indent, line, softline, join, ifBreak, breakParent } = doc_1.builders;
62
+ const { group, indent, line, softline, hardline, join, ifBreak, breakParent, } = doc_1.builders;
63
63
  const { willBreak } = doc_1.utils;
64
64
  // `estree.printers.estree` is typed as `Printer<any>` by Prettier, which is
65
65
  // assignable to `Printer<ESNode>` on its own — no cast needed.
@@ -209,6 +209,122 @@ function fitsWhenFlattened(doc, options) {
209
209
  .split("\n")
210
210
  .every((line) => line.length <= options.printWidth);
211
211
  }
212
+ function getTrailingComma(options, kind) {
213
+ if (options.trailingComma === "all") {
214
+ return ",";
215
+ }
216
+ if (options.trailingComma === "es5" && kind === "array") {
217
+ return ",";
218
+ }
219
+ return "";
220
+ }
221
+ /**
222
+ * Prettier already preserves this for object literals by default
223
+ * (`objectWrap: "preserve"`): write `{` with a newline before the first
224
+ * property and it stays multi-line, even if it would otherwise fit on one
225
+ * line. Prettier doesn't extend that courtesy to call arguments, array
226
+ * elements, or parameter lists — those always auto-collapse to fit
227
+ * `printWidth` regardless of how they were originally written. This
228
+ * reproduces the same "respect how the author wrote it" behavior for those
229
+ * three, so the plugin's own hugging heuristics never fight a layout the
230
+ * developer explicitly chose by hand.
231
+ *
232
+ * Only applies once there are at least two items — with a single item,
233
+ * there's no "one per line" layout to preserve, and that case is already
234
+ * handled by this plugin's other hugging behavior (or Prettier's own).
235
+ * Bails on anything that would make correctly locating the opening bracket
236
+ * or reproducing the layout unsafe: comments anywhere in the list, a sparse
237
+ * array, or (for calls) TS type arguments.
238
+ */
239
+ function tryPreserveMultilineList(path, options, print, kind) {
240
+ const { node } = path;
241
+ let items;
242
+ let openChar;
243
+ let closeChar;
244
+ let searchFrom;
245
+ let prefixDoc = "";
246
+ let suffixDoc = "";
247
+ let printItems;
248
+ if (kind === "call") {
249
+ if (node.type !== "CallExpression" ||
250
+ node.optional ||
251
+ node.typeArguments ||
252
+ node.typeParameters ||
253
+ !node.callee ||
254
+ !isSimpleCallee(node.callee) ||
255
+ // Let chain-flattening handle the outermost call of a chain instead —
256
+ // this only steps in for a plain, non-chained call.
257
+ (node.callee.type === "MemberExpression" &&
258
+ node.callee.object?.type === "CallExpression")) {
259
+ return undefined;
260
+ }
261
+ items = node.arguments ?? [];
262
+ openChar = "(";
263
+ closeChar = ")";
264
+ searchFrom = node.callee.end;
265
+ prefixDoc = print("callee");
266
+ printItems = () => path.map(print, "arguments");
267
+ }
268
+ else if (kind === "array") {
269
+ if (node.type !== "ArrayExpression") {
270
+ return undefined;
271
+ }
272
+ items = node.elements ?? [];
273
+ openChar = "[";
274
+ closeChar = "]";
275
+ searchFrom = node.start;
276
+ printItems = () => path.map(print, "elements");
277
+ }
278
+ else {
279
+ // Arrow functions only — a plain `function` expression/declaration also
280
+ // has a name and a block body to reproduce, which is more than this
281
+ // narrow check is worth taking on.
282
+ if (node.type !== "ArrowFunctionExpression" ||
283
+ node.typeParameters ||
284
+ node.returnType ||
285
+ node.predicate ||
286
+ !node.body ||
287
+ hasComment(node.body)) {
288
+ return undefined;
289
+ }
290
+ items = node.params ?? [];
291
+ openChar = "(";
292
+ closeChar = ")";
293
+ searchFrom = node.start;
294
+ prefixDoc = node.async ? "async " : "";
295
+ suffixDoc = [" => ", print("body")];
296
+ printItems = () => path.map(print, "params");
297
+ }
298
+ if (items.length < 2 ||
299
+ hasComment(node) ||
300
+ items.some((item) => !item || hasComment(item)) ||
301
+ searchFrom === undefined) {
302
+ return undefined;
303
+ }
304
+ const openIndex = options.originalText.indexOf(openChar, searchFrom);
305
+ const firstStart = items[0]?.start;
306
+ if (openIndex === -1 || firstStart === undefined) {
307
+ return undefined;
308
+ }
309
+ const between = options.originalText.slice(openIndex + 1, firstStart);
310
+ if (!/\n/.test(between)) {
311
+ // Not originally multi-line — nothing to preserve; let this plugin's
312
+ // other hugging attempts, or Prettier's own default, decide.
313
+ return undefined;
314
+ }
315
+ const printedItems = printItems();
316
+ const listDoc = [
317
+ openChar,
318
+ indent([
319
+ hardline,
320
+ join([",", hardline], printedItems),
321
+ getTrailingComma(options, kind),
322
+ ]),
323
+ hardline,
324
+ closeChar,
325
+ ];
326
+ return [breakParent, prefixDoc, listDoc, suffixDoc];
327
+ }
212
328
  function tryHugWrappedCallback(path, options, print) {
213
329
  const { node } = path;
214
330
  if (node.type !== "CallExpression" || node.optional) {
@@ -458,7 +574,12 @@ const plugin = {
458
574
  estree: {
459
575
  ...estreePrinter,
460
576
  print(path, options, print, args) {
461
- const hugged = tryHugWrappedCallback(path, options, print) ??
577
+ // Preserving a layout the developer explicitly chose by hand takes
578
+ // priority over this plugin's own hugging heuristics.
579
+ const hugged = tryPreserveMultilineList(path, options, print, "call") ??
580
+ tryPreserveMultilineList(path, options, print, "array") ??
581
+ tryPreserveMultilineList(path, options, print, "params") ??
582
+ tryHugWrappedCallback(path, options, print) ??
462
583
  tryHugArrowBody(path, options, print) ??
463
584
  tryHugChainArgument(path, options, print);
464
585
  return hugged === undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettier-plugin-hug-call-arguments",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Prettier plugin: hug the last call argument even when it's wrapped in another call, e.g. app.delete(\"/x\", catchAsync(async (req, res) => { ... })). Fixes https://github.com/prettier/prettier/issues/11080",
5
5
  "author": "soso tsertsvadze",
6
6
  "license": "MIT",