npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

prettier-plugin-hug-call-arguments

v0.1.12

Published

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

Readme

prettier-plugin-hug-call-arguments

license npm version npm downloads CI

Fixes prettier/prettier#11080.

Usage

Requires Prettier 3.x as a peer dependency.

npm install --save-dev prettier-plugin-hug-call-arguments
yarn add --dev prettier-plugin-hug-call-arguments
pnpm add --save-dev prettier-plugin-hug-call-arguments

Add it to your Prettier config (.prettierrc, .prettierrc.json, prettier.config.js, or the "prettier" key in package.json):

{
  "plugins": ["prettier-plugin-hug-call-arguments"]
}

No other options — it works automatically wherever Prettier already formats JS/JSX/TS/TSX with the estree printer (the babel, babel-ts, and typescript parsers). Nothing to configure, and every case it doesn't recognize is left to Prettier's own formatting untouched.

Examples

Prettier hugs the last argument of a call when it's a function/object/array literal:

app.get("/x", (req, res) => {
  res.send("ok");
});

...but not when that literal is wrapped in another call, which is a very common pattern for middleware wrappers (Express catchAsync, error boundaries, decorators-as-functions, etc.):

// input
app.delete('/campgrounds/:id', catchAsync(async (req, res) => {
  const { id } = req.params;
  await Campground.findByIdAndDelete(id);
  res.redirect('/campgrounds');
}));

// vanilla Prettier output
app.delete(
  "/campgrounds/:id",
  catchAsync(async (req, res) => {
    const { id } = req.params;
    await Campground.findByIdAndDelete(id);
    res.redirect("/campgrounds");
  }),
);

// with this plugin
app.delete("/campgrounds/:id", catchAsync(async (req, res) => {
  const { id } = req.params;
  await Campground.findByIdAndDelete(id);
  res.redirect("/campgrounds");
}));

It also flattens method chains instead of breaking every .method() onto its own line — a common look with query builders:

// input
db.updateTable('User').set({
  isDeleted: true,
  profilePicture: null,
  username: null,
}).where('id', '=', user.id).execute();

// vanilla Prettier output
db
  .updateTable("User")
  .set({
    isDeleted: true,
    profilePicture: null,
    username: null,
  })
  .where("id", "=", user.id)
  .execute();

// with this plugin
db.updateTable("User").set({
  isDeleted: true,
  profilePicture: null,
  username: null,
}).where("id", "=", user.id).execute();

How it works

This plugin wraps Prettier's built-in estree printer and only overrides the print step for CallExpression, ArrayExpression, and ArrowFunctionExpression nodes that match one of a few narrow, specific shapes.

Preserving a layout you already chose:

// input
foo(
  1,
  2,
);

// with this plugin — kept exactly as written
foo(
  1,
  2,
);

// bar(1, 2, 3) stays on one line — nothing to preserve, so it's printed
// the normal, printWidth-aware way

Prettier already does this for object literals by default (objectWrap: "preserve"): write { with a newline before the first property and it stays multi-line, even if it would fit on one line. This plugin extends that same "respect how the author wrote it" behavior to call arguments, array elements, and arrow function parameter lists, which Prettier doesn't normally preserve — those always auto-collapse to fit printWidth regardless of the original formatting.

  • applies once there are at least two items with a newline right after the opening (/[ and before the first one — a single item has no "one per line" layout to preserve, so that's left to this plugin's other hugging behavior, or Prettier's own
  • takes priority over every other feature below: a layout you explicitly chose is never fought by this plugin's own hugging heuristics
  • bails on comments anywhere in the list, a sparse array, TS type arguments/parameters, or (for parameters) anything but a plain arrow function — a function expression/declaration has a name and block body to reproduce too, which is more than this narrow check takes on

Hugging a call-wrapped callback:

  • the callee is a plain identifier or non-computed member chain (foo, a.b.c) — chained calls (a().b()) are left untouched
  • either the last argument is itself a call whose own last argument is a function/object/array literal (looked through recursively, so a chain of wrappers like a(b(cb)) is still found — the common catchAsync(cb) case), or, for a 2-argument call whose second argument is short and simple (a literal or identifier), the first argument is such a wrapping call (the lodash-style uniqueBy(collection, "key") shape)
  • the resulting line actually fits printWidth; if it doesn't (most often because a wrapping call itself sits inside another wrapping call), this falls back to Prettier's default printing for the outer call — inner calls still get their own independent chance to hug once Prettier places them at their own, shallower indentation
  • there's no blank line between arguments, no comments on the relevant nodes, and no TS type arguments

Hugging a call whose sole argument needs it:

// input
firstValueFrom(client.getX({ fileKey: data.fileKey, frameCount: FRAME_COUNT }));

// with this plugin
firstValueFrom(client.getX({
  fileKey: data.fileKey,
  frameCount: FRAME_COUNT,
}));
  • the call has exactly one argument, and it's directly an object or array literal that needs multi-line printing
  • Prettier already hugs this shape reliably in isolation, but its own "does the opening line fit" check can still give up and break the call's own parens too once this plugin has fused several outer layers together (pushing the real column deeper than Prettier expected) — there's essentially never a readability win to that fallback over just hugging directly, so this always does instead

Hugging an arrow function's concise body:

// input
const f = (data) =>
  pgBoss.then((boss) => {
    return boss.send(jobName, data);
  });

// with this plugin
const f = (data) => pgBoss.then((boss) => {
  return boss.send(jobName, data);
});
  • the arrow has a concise (non-block) body, no type parameters, return type, or predicate, and default arrowParens
  • the body is a CallExpression that needs multi-line printing (Prettier already hugs directly after => for other body types — objects, arrays, JSX, template literals — so there's no gap to fix there)
  • the resulting line fits printWidth; otherwise this falls back to Prettier's default (a hardline after =>, body indented on its own line). The arrow's own parameter list can still break onto its own line first the normal way — only the => body boundary is affected

Flattening a method chain:

  • every link is a plain, non-optional .name(...) or [expr](...) call down to a simple base (an identifier, this, or a non-computed member chain)
  • each link's own arguments are printed exactly as Prettier would print them standalone — a single object/array/function argument hugs the parens, a multi-argument list breaks onto its own lines only if it doesn't fit — so any number of links can break independently, correctly, with no special casing needed
  • the whole flattened candidate is still rendered and measured against printWidth as a final check (catching the one thing per-link breaking can't: many short, individually-fitting links whose combined length still overflows); if any line would overflow, it falls back to Prettier's normal chain layout

Every other node, and every CallExpression that doesn't match one of these shapes, is delegated unchanged to Prettier's original printer.

Development

npm install
npm run build
npm test