Repository: prettier/plugin-pug (ts)

Issue:
Option to indent the closing bracket of a multiline element by +1
## Request / Idea
Thanks for the plugin. We are migrating a sizeable codebase to use it. One currently impossible style opinion would be really helpful for us: indenting the trailing `)` of a multiline element to the same indent level as the props.

## Input

```pug
ExampleComponent(
  variant="danger"
  title="Oh no, a connection error!"
)
```

## Expected Output

```pug
ExampleComponent(
  variant="danger"
  title="Oh no, a connection error!"
  )
```

## Crude Implementation

```diff
// Line 1171 in printer.ts
- this.result += this.indentString.repeat(this.indentLevel);
+ this.result += this.indentString.repeat(this.indentLevel + 1);
```

## Additional Context

We feel this is preferable over the current default indent position as it allows you to both comment out the whole component one one go...

```pug
// Errors
// ExampleComponent(
  variant="danger"
  title="Oh no, a connection error!"
)
```

```pug
// Works
// ExampleComponent(
  variant="danger"
  title="Oh no, a connection error!"
  )
```

...as well as retains the ability to re-order the props easily without having to manually move the closing bracket to the end of the last prop.

Could this style be supported as an option? The code is trivial, so it's more of a bike-shedding question.

Pull request:
Add option to set indent depth of closing brackets
Closes #501 

Adds a new option to modify how deep the closing brackets are indented when `bracketSameLine` is not used. Defaults (= null) to the current behavior and allows the choices of `0` (current default) or `1` (indent by +1).

I chose to add the feature as a numeric depth parameter instead of a true/false flag because it's a good way to communicate the feature, and there seem to be issues with passing booleans into Prettier plugins. Added tests to verify the new option.

Feedback is very much welcome if this approach makes sense!

Patch:
diff --git a/docs/guide/pug-specific-options.md b/docs/guide/pug-specific-options.md
index 59de9ce..8c405a3 100644
--- a/docs/guide/pug-specific-options.md
+++ b/docs/guide/pug-specific-options.md
@@ -540,3 +540,74 @@ Output:
 ```pug
 .text-red-400()&attributes(attributes)
 ```
+
+---
+
+## Indent Closing Bracket in Multiline Elements
+
+`pugClosingBracketIndentDepth`
+
+### Description
+
+Define if the closing bracket of a multiline element should be indented to align with the element or its attributes. This only takes effect if the `bracketSameLine` or `pugBracketSameLine` options are set to false.
+
+The benefit of aligning closing brackets with the element's attributes is that elements are easier to comment out.
+
+### Options
+
+**Type:** `choice`  
+**Default:** `0`
+
+#### `0`
+
+Input:
+
+```pug
+foo(
+  bar="baz"
+)
+```
+
+Output:
+
+```pug
+foo(
+  bar="baz"
+)
+```
+
+Comment example:
+
+```pug
+// This will error.
+//foo(
+  bar="baz"
+)
+```
+
+#### `1`
+
+Input:
+
+```pug
+foo(
+  bar="baz"
+)
+```
+
+Output:
+
+```pug
+foo(
+  bar="baz"
+  )
+```
+
+Comment example:
+
+```pug
+// This will work as expected.
+//foo(
+  bar="baz"
+  )
+```
diff --git a/src/options/converge.ts b/src/options/converge.ts
index 4a21cb2..17bd34c 100644
--- a/src/options/converge.ts
+++ b/src/options/converge.ts
@@ -51,5 +51,6 @@ export function convergeOptions(
     pugFramework: options.pugFramework,
     pugExplicitDiv: options.pugExplicitDiv,
     pugPreserveAttributeBrackets: options.pugPreserveAttributeBrackets,
+    pugClosingBracketIndentDepth: options.pugClosingBracketIndentDepth ?? 0,
   };
 }
diff --git a/src/options/index.ts b/src/options/index.ts
index 28b1d68..cff291d 100644
--- a/src/options/index.ts
+++ b/src/options/index.ts
@@ -21,6 +21,7 @@ import {
 import { PUG_ATTRIBUTE_SEPARATOR_OPTION } from './pug-attribute-separator';
 import { PUG_CLASS_LOCATION } from './pug-class-location';
 import { PUG_CLASS_NOTATION } from './pug-class-notation';
+import { PUG_CLOSING_BRACKET_INDENT_DEPTH_OPTION } from './pug-closing-bracket-indent-depth';
 import { PUG_COMMENT_PRESERVE_SPACES_OPTION } from './pug-comment-preserve-spaces';
 import { PUG_EXPLICIT_DIV } from './pug-explicit-div';
 import { PUG_FRAMEWORK } from './pug-framework';
@@ -44,6 +45,7 @@ export const options: SupportOptions = {
   pugArrowParens: PUG_ARROW_PARENS_OPTION,
   pugSemi: PUG_SEMI_OPTION,
   pugBracketSameLine: PUG_BRACKET_SAME_LINE_OPTION,
+  pugClosingBracketIndentDepth: PUG_CLOSING_BRACKET_INDENT_DEPTH_OPTION,
   pugAttributeSeparator: PUG_ATTRIBUTE_SEPARATOR_OPTION,
   pugCommentPreserveSpaces: PUG_COMMENT_PRESERVE_SPACES_OPTION,
   pugSortAttributes: PUG_SORT_ATTRIBUTES_OPTION,
diff --git a/src/options/pug-closing-bracket-indent-depth.ts b/src/options/pug-closing-bracket-indent-depth.ts
new file mode 100644
index 0000000..27bd5e9
--- /dev/null
+++ b/src/options/pug-closing-bracket-indent-depth.ts
@@ -0,0 +1,46 @@
+import type { ChoiceSupportOption } from 'prettier';
+import { CATEGORY_PUG } from './constants';
+
+/** Pug bracket same line option. */
+export const PUG_CLOSING_BRACKET_INDENT_DEPTH_OPTION: ChoiceSupportOption<PugClosingBracketIndentDepth> =
+  {
+    // since: '3.1.0',
+    category: CATEGORY_PUG,
+    type: 'choice',
+    default: 0,
+    description:
+      'Determines the indent level of closing brackets when wrapping attributes.',
+    choices: [
+      {
+        value: 0,
+        description: `
+			Closing bracket aligns with the element.
+			Example:
+			input(
+				type='text',
+				value='my_value',
+				name='my_name',
+				alt='my_alt',
+				autocomplete='on'
+			)
+			`,
+      },
+      {
+        value: 1,
+        description: `
+			Closing bracket aligns with the element's attributes.
+			Example:
+			input(
+				type='text',
+				value='my_value',
+				name='my_name',
+				alt='my_alt',
+				autocomplete='on'
+				)
+			`,
+      },
+    ],
+  };
+
+/** Pug closing bracket indent depth. */
+export type PugClosingBracketIndentDepth = 0 | 1;
diff --git a/src/options/types.ts b/src/options/types.ts
index 4b90d83..3e4ac4b 100644
--- a/src/options/types.ts
+++ b/src/options/types.ts
@@ -8,6 +8,7 @@ import type {
 import type { PugAttributeSeparator } from './pug-attribute-separator';
 import type { PugClassLocation } from './pug-class-location';
 import type { PugClassNotation } from './pug-class-notation';
+import type { PugClosingBracketIndentDepth } from './pug-closing-bracket-indent-depth';
 import type { PugCommentPreserveSpaces } from './pug-comment-preserve-spaces';
 import type { PugFramework } from './pug-framework';
 import type { PugIdNotation } from './pug-id-notation';
@@ -35,6 +36,7 @@ export interface PugParserOptions
   pugArrowParens: ArrowParens | null;
   pugSemi: boolean | null;
   pugBracketSameLine: boolean | null;
+  pugClosingBracketIndentDepth: PugClosingBracketIndentDepth;
 
   pugAttributeSeparator: PugAttributeSeparator;
 
diff --git a/src/printer.ts b/src/printer.ts
index 238ddbb..9de56f2 100644
--- a/src/printer.ts
+++ b/src/printer.ts
@@ -71,6 +71,7 @@ import type { PugAttributeSeparator } from './options/pug-attribute-separator';
 import { resolvePugAttributeSeparatorOption } from './options/pug-attribute-separator';
 import type { PugClassLocation } from './options/pug-class-location';
 import type { PugClassNotation } from './options/pug-class-notation';
+import type { PugClosingBracketIndentDepth } from './options/pug-closing-bracket-indent-depth';
 import type { PugCommentPreserveSpaces } from './options/pug-comment-preserve-spaces';
 import { formatPugCommentPreserveSpaces } from './options/pug-comment-preserve-spaces';
 import type { PugFramework } from './options/pug-framework';
@@ -143,6 +144,7 @@ export interface PugPrinterOptions {
   readonly pugFramework: PugFramework;
   readonly pugExplicitDiv: boolean;
   readonly pugPreserveAttributeBrackets: boolean;
+  readonly pugClosingBracketIndentDepth: PugClosingBracketIndentDepth;
 }
 
 /**
@@ -1168,7 +1170,9 @@ export class PugPrinter {
       if (!this.options.pugBracketSameLine) {
         this.result += '\n';
       }
-      this.result += this.indentString.repeat(this.indentLevel);
+      this.result += this.indentString.repeat(
+        this.indentLevel + this.options.pugClosingBracketIndentDepth,
+      );
     }
     this.wrapAttributes = false;
 


Test patch:
diff --git a/tests/options/pugClosingBracketIndentDepth/depth-0.pug b/tests/options/pugClosingBracketIndentDepth/depth-0.pug
new file mode 100644
index 0000000..d8a1b49
--- /dev/null
+++ b/tests/options/pugClosingBracketIndentDepth/depth-0.pug
@@ -0,0 +1,34 @@
+button(type="submit", (click)="play()", disabled)
+
+hello-framework(
+  [name]="name",
+  [version]="version",
+  (release)="onVersionRelease()"
+)
+
+nav-component(locale-relative-redirect="true", highlight="home", pin="false")
+
+.wrapper(
+  data-nav,
+  data-next="Next",
+  data-prev="Prev",
+  data-slides=4,
+  data-arr=[1, 2, 3, 4, 5, 6],
+  data-obj={ att: 1, attr2: 2 }
+)
+
+div
+  .nested-div-1(
+    type="text",
+    value="my_value1",
+    name="my_name1",
+    alt="my_alt1",
+    autocomplete="on"
+  )
+    .nested-div-2(
+      type="text",
+      value="my_value2",
+      name="my_name2",
+      alt="my_alt2",
+      autocomplete="on"
+    )
diff --git a/tests/options/pugClosingBracketIndentDepth/depth-1.pug b/tests/options/pugClosingBracketIndentDepth/depth-1.pug
new file mode 100644
index 0000000..b69cf2f
--- /dev/null
+++ b/tests/options/pugClosingBracketIndentDepth/depth-1.pug
@@ -0,0 +1,34 @@
+button(type="submit", (click)="play()", disabled)
+
+hello-framework(
+  [name]="name",
+  [version]="version",
+  (release)="onVersionRelease()"
+  )
+
+nav-component(locale-relative-redirect="true", highlight="home", pin="false")
+
+.wrapper(
+  data-nav,
+  data-next="Next",
+  data-prev="Prev",
+  data-slides=4,
+  data-arr=[1, 2, 3, 4, 5, 6],
+  data-obj={ att: 1, attr2: 2 }
+  )
+
+div
+  .nested-div-1(
+    type="text",
+    value="my_value1",
+    name="my_name1",
+    alt="my_alt1",
+    autocomplete="on"
+    )
+    .nested-div-2(
+      type="text",
+      value="my_value2",
+      name="my_name2",
+      alt="my_alt2",
+      autocomplete="on"
+      )
diff --git a/tests/options/pugClosingBracketIndentDepth/pug-closing-bracket-indent-depth.test.ts b/tests/options/pugClosingBracketIndentDepth/pug-closing-bracket-indent-depth.test.ts
new file mode 100644
index 0000000..8bc7fad
--- /dev/null
+++ b/tests/options/pugClosingBracketIndentDepth/pug-closing-bracket-indent-depth.test.ts
@@ -0,0 +1,38 @@
+import { compareFiles } from 'tests/common';
+import { describe, expect, it } from 'vitest';
+
+describe('Options', () => {
+  describe('pugClosingBracketIndentDepth', () => {
+    it('should handle pugClosingBracketIndentDepth:0', async () => {
+      const { actual, expected } = await compareFiles(import.meta.url, {
+        target: 'depth-0.pug',
+        formatOptions: {
+          pugBracketSameLine: false,
+          pugClosingBracketIndentDepth: 0,
+        },
+      });
+      expect(actual).toBe(expected);
+    });
+
+    it('should handle pugClosingBracketIndentDepth:1', async () => {
+      const { actual, expected } = await compareFiles(import.meta.url, {
+        target: 'depth-1.pug',
+        formatOptions: {
+          pugBracketSameLine: false,
+          pugClosingBracketIndentDepth: 1,
+        },
+      });
+      expect(actual).toBe(expected);
+    });
+
+    it('should default to 0', async () => {
+      const { actual, expected } = await compareFiles(import.meta.url, {
+        target: 'depth-0.pug',
+        formatOptions: {
+          pugBracketSameLine: false,
+        },
+      });
+      expect(actual).toBe(expected);
+    });
+  });
+});
diff --git a/tests/options/pugClosingBracketIndentDepth/unformatted.pug b/tests/options/pugClosingBracketIndentDepth/unformatted.pug
new file mode 100644
index 0000000..7e25486
--- /dev/null
+++ b/tests/options/pugClosingBracketIndentDepth/unformatted.pug
@@ -0,0 +1,25 @@
+button(type="submit", (click)="play()", disabled)
+
+hello-framework([name]="name", [version]="version", (release)="onVersionRelease()")
+
+nav-component(locale-relative-redirect="true", highlight="home", pin="false")
+
+.wrapper(data-nav=true data-next="Next" data-prev="Prev" data-slides=4 data-arr=[1,2,3,4,5,6] data-obj={att: 1, attr2: 2})
+
+div
+  div(
+    class="nested-div-1",
+    type="text",
+    value="my_value1",
+    name="my_name1",
+    alt="my_alt1",
+    autocomplete="on"
+  )
+    div(
+      class="nested-div-2",
+      type="text",
+      value="my_value2",
+      name="my_name2",
+      alt="my_alt2",
+      autocomplete="on"
+    )