prettier-java-next-line 2.10.3 → 2.11.0

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
@@ -1,99 +1,93 @@
1
1
  # Prettier Java Next Line
2
2
 
3
- Prettier Java Next Line is a fork of
4
- [prettier-plugin-java](https://github.com/jhipster/prettier-java) with
5
- configurable opening-brace placement. It defaults to Allman-style, next-line
6
- braces.
3
+ Prettier Java Next Line forks [prettier-plugin-java](https://github.com/jhipster/prettier-java) to make brace placement configurable, with next-line braces as the default.
7
4
 
8
- The option was proposed upstream in
9
- [jhipster/prettier-java#840](https://github.com/jhipster/prettier-java/pull/840)
10
- and declined because upstream intentionally preserves one brace style.
5
+ [Upstream declined the option](https://github.com/jhipster/prettier-java/pull/840) because Prettier Java intentionally supports one brace style.
11
6
 
12
- ## Installation
7
+ ## Why next line is the default
13
8
 
14
- Use it with Prettier:
9
+ Same-line formatting made sense on small, low-resolution CRT displays where every row counted.
10
+
11
+ On a modern display, we would rather spend one extra line and see the shape of the block.
12
+
13
+ ## Install
14
+
15
+ Follow the [upstream instructions](https://github.com/jhipster/prettier-java#readme), replacing `prettier-plugin-java` with `prettier-java-next-line`:
15
16
 
16
17
  ```bash
17
18
  npm install --save-dev prettier prettier-java-next-line
18
19
  ```
19
20
 
20
- Or use the matching Prettier Next Line fork through an npm alias:
21
+ Load the plugin explicitly:
21
22
 
22
- ```bash
23
- npm install --save-dev \
24
- prettier@npm:prettier-next-line \
25
- prettier-java-next-line
23
+ ```json
24
+ {
25
+ "plugins": ["prettier-java-next-line"]
26
+ }
26
27
  ```
27
28
 
28
- ## Configuration
29
-
30
- Load the plugin explicitly:
29
+ Set `braceStyle` only when a project needs the inherited same-line style:
31
30
 
32
31
  ```json
33
32
  {
34
- "plugins": ["prettier-java-next-line"]
33
+ "plugins": ["prettier-java-next-line"],
34
+ "braceStyle": "same-line"
35
35
  }
36
36
  ```
37
37
 
38
- Next-line braces are the default. Use the inherited K&R placement when needed:
38
+ Set `importOrder` to group imports the way Spotless's `importOrder` step does. Groups separated by `,` get blank lines between them, prefixes joined by `|` share a group, `#` marks a static prefix, and an empty prefix catches everything else. Each import belongs to its longest matching prefix. Module imports (`import module`) always come first in their own group. When `importOrder` is empty, static imports follow them, then all others.
39
39
 
40
40
  ```json
41
41
  {
42
- "plugins": ["prettier-java-next-line"],
43
- "braceStyle": "same-line"
42
+ "plugins": ["prettier-java-next-line"],
43
+ "importOrder": "java,javax,,#java|#javax,#"
44
44
  }
45
45
  ```
46
46
 
47
+ To use the matching Prettier runtime fork too, install it under the name `prettier` with an npm alias:
48
+
49
+ ```bash
50
+ npm install --save-dev \
51
+ prettier@npm:prettier-next-line \
52
+ prettier-java-next-line
53
+ ```
54
+
47
55
  ## Spotless Maven
48
56
 
49
- Spotless loads a package named `prettier`, so install Prettier Next Line under
50
- that name with an npm alias:
57
+ Spotless loads a package named `prettier`, so install Prettier Next Line under that name with an npm alias:
51
58
 
52
59
  ```xml
53
60
  <prettier>
54
- <devDependencies>
55
- <prettier>npm:prettier-next-line@3.10.0-dev</prettier>
56
- <prettier-java-next-line>2.10.3-beta.1</prettier-java-next-line>
57
- </devDependencies>
58
- <configFile>.prettierrc.json5</configFile>
59
- <config>
60
- <parser>java</parser>
61
- <plugins>prettier-java-next-line</plugins>
62
- </config>
61
+ <devDependencies>
62
+ <prettier>npm:prettier-next-line@3.10.0</prettier>
63
+ <prettier-java-next-line>2.10.3</prettier-java-next-line>
64
+ </devDependencies>
65
+ <configFile>.prettierrc.json5</configFile>
66
+ <config>
67
+ <parser>java</parser>
68
+ <plugins>prettier-java-next-line</plugins>
69
+ </config>
63
70
  </prettier>
64
71
  ```
65
72
 
66
- ## CLI
67
-
68
- With upstream Prettier:
69
-
70
- ```bash
71
- npx prettier --check --plugin=prettier-java-next-line "**/*.java"
72
- npx prettier --write --plugin=prettier-java-next-line "**/*.java"
73
- ```
74
-
75
- With the aliased Prettier Next Line fork, use `npx prettier-next-line` instead.
76
-
77
73
  ## Example
78
74
 
79
75
  ```java
80
76
  public class Example
81
77
  {
82
- public void method()
83
- {
84
- if (condition)
85
- {
86
- doSomething();
87
- }
88
- }
78
+ public void method()
79
+ {
80
+ if (condition)
81
+ {
82
+ doSomething();
83
+ }
84
+ }
89
85
  }
90
86
  ```
91
87
 
92
88
  ## Upstream
93
89
 
94
- Prettier Java Next Line tracks
95
- [jhipster/prettier-java](https://github.com/jhipster/prettier-java) and keeps
96
- fork-specific behavior focused on brace placement.
90
+ Except for brace placement, behavior comes from [Prettier Java](https://github.com/jhipster/prettier-java#readme).
97
91
 
98
92
  ## License
99
93
 
package/dist/index.cjs CHANGED
@@ -61,6 +61,12 @@ var options_default = {
61
61
  description: "Put opening braces on new lines (Allman style)"
62
62
  }],
63
63
  description: "Placement of opening braces."
64
+ },
65
+ importOrder: {
66
+ type: "string",
67
+ category: "Java",
68
+ default: "",
69
+ description: "Import groups in Spotless's format, such as `java,javax,,#java|#javax,#`. Module imports always come first. Empty puts static imports next, then all others."
64
70
  }
65
71
  };
66
72
  //#endregion
@@ -125,6 +131,9 @@ function printComment(comment) {
125
131
  function hasLeadingComments(node) {
126
132
  return node.comments?.some(({ leading }) => leading) ?? false;
127
133
  }
134
+ function hasDanglingComments(node) {
135
+ return node.comments?.some(({ leading, trailing }) => !leading && !trailing) ?? false;
136
+ }
128
137
  function indentInParentheses(contents) {
129
138
  return contents && !Array.isArray(contents) || contents.length ? [
130
139
  "(",
@@ -292,7 +301,7 @@ function printVariableDeclaration(path, print) {
292
301
  }
293
302
  function printAssignment(leftDoc, operator, rightDoc, rightNode) {
294
303
  if (!rightDoc || !rightNode) return leftDoc;
295
- if (rightNode.type === "binary_expression" || rightNode.type === "instanceof_expression" || rightNode.type === "ternary_expression" && (rightNode.conditionNode.type === "binary_expression" || rightNode.conditionNode.type === "instanceof_expression") || hasLeadingComments(rightNode)) return group$7([
304
+ if (rightNode.type === "binary_expression" || rightNode.type === "instanceof_expression" || rightNode.type === "ternary_expression" && (rightNode.conditionNode.type === "binary_expression" || rightNode.conditionNode.type === "instanceof_expression") || isMember(rightNode) && hasDanglingComments(rightNode) || !isMember(rightNode) && hasLeadingComments(rightNode)) return group$7([
296
305
  leftDoc,
297
306
  operator,
298
307
  group$7(indent$7([line$7, rightDoc]))
@@ -306,6 +315,12 @@ function printAssignment(leftDoc, operator, rightDoc, rightNode) {
306
315
  indentIfBreak$2(rightDoc, { groupId })
307
316
  ]);
308
317
  }
318
+ function getLanguageByLanguageName(languages, languageName) {
319
+ return languages.find(({ name }) => name.toLowerCase() === languageName) ?? languages.find(({ aliases }) => aliases?.includes(languageName)) ?? languages.find(({ extensions }) => extensions?.includes(`.${languageName}`));
320
+ }
321
+ function inferParser(options, language) {
322
+ return getLanguageByLanguageName(options.plugins?.filter((plugin) => typeof plugin !== "string" && !(plugin instanceof URL)).reverse().flatMap((plugin) => plugin.languages ?? []) ?? [], language)?.parsers[0];
323
+ }
309
324
  function printTextBlock(contents) {
310
325
  return [
311
326
  "\"\"\"",
@@ -314,13 +329,15 @@ function printTextBlock(contents) {
314
329
  "\"\"\""
315
330
  ];
316
331
  }
317
- function embedTextBlock(path) {
332
+ function embedTextBlock(path, options) {
318
333
  if (path.node.namedChildren.some(({ type }) => type === "string_interpolation") || path.node.children[0].value === "\"") return null;
319
334
  const language = findEmbeddedLanguage(path);
320
335
  if (!language) return null;
336
+ const parser = inferParser(options, language);
337
+ if (!parser) return null;
321
338
  const text = unescapeTextBlockContents(textBlockContents(path.node));
322
339
  return async (textToDoc) => {
323
- return printTextBlock([escapeDocForTextBlock(await textToDoc(text, { parser: language })), hardline$7]);
340
+ return printTextBlock([escapeDocForTextBlock(await textToDoc(text, { parser })), hardline$7]);
324
341
  };
325
342
  }
326
343
  function textBlockContents(node) {
@@ -463,9 +480,7 @@ function parentNeedsParentheses(path) {
463
480
  const { parent } = path;
464
481
  switch (parent?.type) {
465
482
  case "return_statement":
466
- case "throw_statement":
467
- if (willReturnOrThrowStatementBreak(path)) return false;
468
- break;
483
+ case "throw_statement": if (willReturnOrThrowStatementBreak(path)) return false;
469
484
  }
470
485
  }
471
486
  function willReturnOrThrowStatementBreak(path) {
@@ -491,7 +506,7 @@ function findBaseIndent(lines) {
491
506
  return Math.min(...lines.map((line) => line.search(/\S/)).filter((indent) => indent >= 0));
492
507
  }
493
508
  function findEmbeddedLanguage(path) {
494
- return path.ancestors.find(({ type, comments }) => type === "block" || comments?.some(({ leading }) => leading))?.comments?.filter(({ leading }) => leading).map(({ value }) => value.match(/^(?:\/\/|\/\*)\s*language\s*=\s*(\S+)/)?.[1]).findLast((language) => language)?.toLowerCase();
509
+ return [path.node, ...path.ancestors].find(({ type, comments }) => type === "block" || comments?.some(({ leading }) => leading))?.comments?.filter(({ leading }) => leading).map(({ value }) => value.match(/^(?:\/\/|\/\*)\s*language\s*=\s*(\S+)/)?.[1]).findLast((language) => language)?.toLowerCase();
495
510
  }
496
511
  function escapeDocForTextBlock(doc) {
497
512
  return mapDoc(doc, (currentDoc) => typeof currentDoc === "string" ? currentDoc.replace(/\\|"""/g, (match) => match === "\\" ? "\\\\" : "\"\"\\\"") : currentDoc);
@@ -1175,7 +1190,11 @@ var expressions_default = {
1175
1190
  }, "children")));
1176
1191
  },
1177
1192
  unary_expression(path, print) {
1178
- return path.map(print, "children");
1193
+ const parts = [path.call(print, "operatorNode")];
1194
+ const operandDoc = path.call(print, "operandNode");
1195
+ if (path.node.operandNode.comments) parts.push(group$4(indentInParentheses(operandDoc)));
1196
+ else parts.push(operandDoc);
1197
+ return parts;
1179
1198
  },
1180
1199
  field_access: printMemberChain,
1181
1200
  generic_type(path, print) {
@@ -1453,7 +1472,7 @@ function shouldPrintParamsWithoutParens(path, options) {
1453
1472
  return false;
1454
1473
  }
1455
1474
  function canPrintParamsWithoutParens(node) {
1456
- return node.parametersNode.type === "identifier" && !node.comments?.some(({ leading, trailing }) => !leading && !trailing) && !node.parametersNode.comments;
1475
+ return node.parametersNode.type === "identifier" && !hasDanglingComments(node) && !node.parametersNode.comments;
1457
1476
  }
1458
1477
  function printMemberChain(path, print, options) {
1459
1478
  const isExpressionStatement = path.parent?.type === "expression_statement";
@@ -1917,11 +1936,11 @@ var names_default = {
1917
1936
  //#region src/printers/packages-and-modules.ts
1918
1937
  const { group: group$1, hardline: hardline$1, indent: indent$1, join: join$1, line: line$2 } = prettier_doc.builders;
1919
1938
  var packages_and_modules_default = {
1920
- program(path, print) {
1939
+ program(path, print, options) {
1921
1940
  if (!path.node.namedChildren.length) return [printDanglingComments(path), hardline$1];
1922
1941
  const parts = [];
1923
1942
  if (path.node.namedChildren[0].type !== "import_declaration") parts.push(path.call(print, "namedChildren", 0));
1924
- const staticImports = [];
1943
+ const moduleImports = [];
1925
1944
  const imports = [];
1926
1945
  const otherDeclarations = [];
1927
1946
  path.each((child) => {
@@ -1929,13 +1948,15 @@ var packages_and_modules_default = {
1929
1948
  if (child.node.type === "import_declaration") {
1930
1949
  const names = extractNames(child.node.namedChildren[0]);
1931
1950
  if (child.node.namedChildren.some(({ type }) => type === "asterisk")) names.push("*");
1932
- (child.node.children[1].type === "static" ? staticImports : imports).push({
1951
+ const modifier = child.node.children[1].type;
1952
+ (modifier === "module" ? moduleImports : imports).push({
1933
1953
  doc,
1934
- names
1954
+ names,
1955
+ isStatic: modifier === "static"
1935
1956
  });
1936
1957
  } else if (!child.isFirst) otherDeclarations.push(doc);
1937
1958
  }, "namedChildren");
1938
- parts.push(...[staticImports, imports].filter((imports) => imports.length).map((imports) => join$1(hardline$1, imports.sort(compareFqn).map(({ doc }) => doc))), ...otherDeclarations);
1959
+ parts.push(...[moduleImports.sort(compareFqn), ...groupImports(imports, options.importOrder)].filter((group) => group.length).map((group) => join$1(hardline$1, group.map(({ doc }) => doc))), ...otherDeclarations);
1939
1960
  return [...join$1([hardline$1, hardline$1], parts), hardline$1];
1940
1961
  },
1941
1962
  package_declaration(path, print) {
@@ -1948,9 +1969,7 @@ var packages_and_modules_default = {
1948
1969
  annotations.push(print(child));
1949
1970
  break;
1950
1971
  case "identifier":
1951
- case "scoped_identifier":
1952
- identifier.push(print(child));
1953
- break;
1972
+ case "scoped_identifier": identifier.push(print(child));
1954
1973
  }
1955
1974
  }, "namedChildren");
1956
1975
  return join$1(hardline$1, [...annotations, [
@@ -1961,7 +1980,8 @@ var packages_and_modules_default = {
1961
1980
  },
1962
1981
  import_declaration(path, print) {
1963
1982
  const declaration = ["import "];
1964
- if (path.node.children.some(({ type }) => type === "static")) declaration.push("static ");
1983
+ const modifier = path.node.children[1].type;
1984
+ if (modifier === "module" || modifier === "static") declaration.push(modifier, " ");
1965
1985
  const identifierIndex = path.node.namedChildren.findIndex(({ type }) => type === "identifier" || type === "scoped_identifier");
1966
1986
  declaration.push(path.call(print, "namedChildren", identifierIndex));
1967
1987
  if (path.node.namedChildren.some(({ type }) => type === "asterisk")) declaration.push(".*");
@@ -2015,6 +2035,46 @@ var packages_and_modules_default = {
2015
2035
  function extractNames(node) {
2016
2036
  return node.type === "identifier" ? [node.value] : [...extractNames(node.scopeNode), node.nameNode.value];
2017
2037
  }
2038
+ const STATIC_PREFIX = "static ";
2039
+ const CATCH_ALL_PREFIX = "";
2040
+ function groupImports(imports, importOrder) {
2041
+ if (!importOrder) return [imports.filter(({ isStatic }) => isStatic), imports.filter(({ isStatic }) => !isStatic)].filter((group) => group.length).map((group) => group.sort(compareFqn));
2042
+ const groups = parseImportOrder(importOrder);
2043
+ const prefixes = groups.flat();
2044
+ const importsByPrefix = /* @__PURE__ */ new Map();
2045
+ for (const entry of imports) {
2046
+ const key = importKey(entry);
2047
+ const prefix = prefixes.filter((candidate) => key.startsWith(candidate)).reduce((best, candidate) => candidate.length > best.length ? candidate : best);
2048
+ importsByPrefix.set(prefix, [...importsByPrefix.get(prefix) ?? [], {
2049
+ key,
2050
+ entry
2051
+ }]);
2052
+ }
2053
+ return groups.map((group) => group.flatMap((prefix) => (importsByPrefix.get(prefix) ?? []).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ entry }) => entry))).filter((group) => group.length);
2054
+ }
2055
+ /**
2056
+ * Parses an import order in Spotless's format: groups separated by `,` are
2057
+ * printed with blank lines between them, prefixes joined by `|` share a group,
2058
+ * `#` marks a static prefix, and an empty prefix catches everything else.
2059
+ * Each import belongs to its longest matching prefix.
2060
+ */
2061
+ function parseImportOrder(importOrder) {
2062
+ const knownPrefixes = /* @__PURE__ */ new Set();
2063
+ const groups = importOrder.split(",").map((group) => group.split("|").map((prefix) => prefix.startsWith("#") ? STATIC_PREFIX + prefix.slice(1) : prefix).filter((prefix) => {
2064
+ if (knownPrefixes.has(prefix)) return false;
2065
+ knownPrefixes.add(prefix);
2066
+ return true;
2067
+ }));
2068
+ if (!knownPrefixes.has(STATIC_PREFIX)) {
2069
+ const lastStaticGroupIndex = groups.findLastIndex((group) => group.some((prefix) => prefix.startsWith(STATIC_PREFIX)));
2070
+ groups.splice(Math.max(lastStaticGroupIndex, 0), 0, [STATIC_PREFIX]);
2071
+ }
2072
+ if (!knownPrefixes.has(CATCH_ALL_PREFIX)) groups.push([CATCH_ALL_PREFIX]);
2073
+ return groups;
2074
+ }
2075
+ function importKey({ names, isStatic }) {
2076
+ return (isStatic ? STATIC_PREFIX : "") + names.join(".");
2077
+ }
2018
2078
  function compareFqn({ names: a }, { names: b }) {
2019
2079
  const minParts = Math.min(a.length, b.length);
2020
2080
  for (let i = 0; i < minParts; i++) {
@@ -2115,8 +2175,8 @@ var printer_default = {
2115
2175
  ")"
2116
2176
  ] : doc;
2117
2177
  },
2118
- embed(path) {
2119
- return hasType(path, "string_literal") ? embedTextBlock(path) : null;
2178
+ embed(path, options) {
2179
+ return hasType(path, "string_literal") ? embedTextBlock(path, options) : null;
2120
2180
  },
2121
2181
  hasPrettierIgnore(path) {
2122
2182
  return path.node.comments?.some(isPrettierIgnore) === true || canAttachComment(path.node, path.parent ? [path.parent] : []) && isFullyBetweenPrettierIgnore(path);
@@ -2281,9 +2341,10 @@ function handleLabeledStatementComments(commentNode) {
2281
2341
  function handleLambdaExpressionComments(commentNode) {
2282
2342
  const { enclosingNode, precedingNode, followingNode } = commentNode;
2283
2343
  if (enclosingNode?.type === "lambda_expression" && precedingNode && followingNode && enclosingNode.children.find(({ type }) => type === "->").end.index < commentNode.start.index) {
2284
- if (followingNode.type === "block") if (followingNode.namedChildren.length) prettier.util.addLeadingComment(followingNode.namedChildren[0], commentNode);
2285
- else prettier.util.addDanglingComment(followingNode, commentNode, void 0);
2286
- else prettier.util.addLeadingComment(followingNode, commentNode);
2344
+ if (followingNode.type === "block") {
2345
+ if (followingNode.namedChildren.length) prettier.util.addLeadingComment(followingNode.namedChildren[0], commentNode);
2346
+ else prettier.util.addDanglingComment(followingNode, commentNode, void 0);
2347
+ } else prettier.util.addLeadingComment(followingNode, commentNode);
2287
2348
  return true;
2288
2349
  }
2289
2350
  }
@@ -2366,8 +2427,10 @@ function printLeadingComment(path) {
2366
2427
  const originalText = path.root.value;
2367
2428
  if (comment.type === "block_comment") {
2368
2429
  let lineBreak = " ";
2369
- if (hasNewline(originalText, parser_default.locEnd(comment))) if (hasNewline(originalText, parser_default.locStart(comment), { backwards: true })) lineBreak = hardline;
2370
- else lineBreak = line;
2430
+ if (hasNewline(originalText, parser_default.locEnd(comment))) {
2431
+ if (hasNewline(originalText, parser_default.locStart(comment), { backwards: true })) lineBreak = hardline;
2432
+ else lineBreak = line;
2433
+ }
2371
2434
  parts.push(lineBreak);
2372
2435
  } else parts.push(hardline);
2373
2436
  const index = skipNewline(originalText, skipSpaces(originalText, parser_default.locEnd(comment)));
@@ -2527,8 +2590,10 @@ function processTree(node, fieldName = null, comments) {
2527
2590
  const javaChild = processTree(child, fieldName, comments);
2528
2591
  javaNode.children.push(javaChild);
2529
2592
  if (javaChild.isNamed) javaNode.namedChildren.push(javaChild);
2530
- if (fieldName) if (multiFields?.[fieldName]) javaNode[`${fieldName}Nodes`].push(javaChild);
2531
- else javaNode[`${fieldName}Node`] = javaChild;
2593
+ if (fieldName) {
2594
+ if (multiFields?.[fieldName]) javaNode[`${fieldName}Nodes`].push(javaChild);
2595
+ else javaNode[`${fieldName}Node`] = javaChild;
2596
+ }
2532
2597
  }
2533
2598
  });
2534
2599
  });
package/dist/index.d.cts CHANGED
@@ -1575,7 +1575,7 @@ declare const _default: {
1575
1575
  printers: {
1576
1576
  java: {
1577
1577
  print(path: import("prettier").AstPath<SyntaxNode>, options: import("prettier").ParserOptions<SyntaxNode>, print: (selector?: string | number | import("prettier").AstPath<SyntaxNode> | (string | number)[] | undefined, args?: unknown) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
1578
- embed(path: import("prettier").AstPath<any>): ((textToDoc: (text: string, options: import("prettier").Options) => Promise<import("prettier").Doc>) => Promise<import("prettier/doc.js").builders.Doc[]>) | null;
1578
+ embed(path: import("prettier").AstPath<any>, options: import("prettier").Options): ((textToDoc: (text: string, options: import("prettier").Options) => Promise<import("prettier").Doc>) => Promise<import("prettier/doc.js").builders.Doc[]>) | null;
1579
1579
  hasPrettierIgnore(path: import("prettier").AstPath<SyntaxNode>): boolean;
1580
1580
  canAttachComment: typeof canAttachComment;
1581
1581
  isBlockComment(node: SyntaxNode): boolean;
@@ -1631,6 +1631,12 @@ declare const _default: {
1631
1631
  }[];
1632
1632
  description: string;
1633
1633
  };
1634
+ importOrder: {
1635
+ type: "string";
1636
+ category: string;
1637
+ default: string;
1638
+ description: string;
1639
+ };
1634
1640
  };
1635
1641
  defaultOptions: {
1636
1642
  arrowParens: "avoid";
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AstPath, ParserOptions } from "prettier";
2
- import { builders } from "prettier/doc";
2
+ import { builders as builders$1 } from "prettier/doc";
3
3
  //#region src/node-types.d.ts
4
4
  interface Point {
5
5
  index: number;
@@ -1575,7 +1575,7 @@ declare const _default: {
1575
1575
  printers: {
1576
1576
  java: {
1577
1577
  print(path: import("prettier").AstPath<SyntaxNode>, options: import("prettier").ParserOptions<SyntaxNode>, print: (selector?: string | number | import("prettier").AstPath<SyntaxNode> | (string | number)[] | undefined, args?: unknown) => import("prettier").Doc, args: unknown): import("prettier/doc.js").builders.Doc;
1578
- embed(path: import("prettier").AstPath<any>): ((textToDoc: (text: string, options: import("prettier").Options) => Promise<import("prettier").Doc>) => Promise<import("prettier/doc.js").builders.Doc[]>) | null;
1578
+ embed(path: import("prettier").AstPath<any>, options: import("prettier").Options): ((textToDoc: (text: string, options: import("prettier").Options) => Promise<import("prettier").Doc>) => Promise<import("prettier/doc.js").builders.Doc[]>) | null;
1579
1579
  hasPrettierIgnore(path: import("prettier").AstPath<SyntaxNode>): boolean;
1580
1580
  canAttachComment: typeof canAttachComment;
1581
1581
  isBlockComment(node: SyntaxNode): boolean;
@@ -1631,6 +1631,12 @@ declare const _default: {
1631
1631
  }[];
1632
1632
  description: string;
1633
1633
  };
1634
+ importOrder: {
1635
+ type: "string";
1636
+ category: string;
1637
+ default: string;
1638
+ description: string;
1639
+ };
1634
1640
  };
1635
1641
  defaultOptions: {
1636
1642
  arrowParens: "avoid";
package/dist/index.mjs CHANGED
@@ -61,6 +61,12 @@ var options_default = {
61
61
  description: "Put opening braces on new lines (Allman style)"
62
62
  }],
63
63
  description: "Placement of opening braces."
64
+ },
65
+ importOrder: {
66
+ type: "string",
67
+ category: "Java",
68
+ default: "",
69
+ description: "Import groups in Spotless's format, such as `java,javax,,#java|#javax,#`. Module imports always come first. Empty puts static imports next, then all others."
64
70
  }
65
71
  };
66
72
  //#endregion
@@ -125,6 +131,9 @@ function printComment(comment) {
125
131
  function hasLeadingComments(node) {
126
132
  return node.comments?.some(({ leading }) => leading) ?? false;
127
133
  }
134
+ function hasDanglingComments(node) {
135
+ return node.comments?.some(({ leading, trailing }) => !leading && !trailing) ?? false;
136
+ }
128
137
  function indentInParentheses(contents) {
129
138
  return contents && !Array.isArray(contents) || contents.length ? [
130
139
  "(",
@@ -292,7 +301,7 @@ function printVariableDeclaration(path, print) {
292
301
  }
293
302
  function printAssignment(leftDoc, operator, rightDoc, rightNode) {
294
303
  if (!rightDoc || !rightNode) return leftDoc;
295
- if (rightNode.type === "binary_expression" || rightNode.type === "instanceof_expression" || rightNode.type === "ternary_expression" && (rightNode.conditionNode.type === "binary_expression" || rightNode.conditionNode.type === "instanceof_expression") || hasLeadingComments(rightNode)) return group$7([
304
+ if (rightNode.type === "binary_expression" || rightNode.type === "instanceof_expression" || rightNode.type === "ternary_expression" && (rightNode.conditionNode.type === "binary_expression" || rightNode.conditionNode.type === "instanceof_expression") || isMember(rightNode) && hasDanglingComments(rightNode) || !isMember(rightNode) && hasLeadingComments(rightNode)) return group$7([
296
305
  leftDoc,
297
306
  operator,
298
307
  group$7(indent$7([line$7, rightDoc]))
@@ -306,6 +315,12 @@ function printAssignment(leftDoc, operator, rightDoc, rightNode) {
306
315
  indentIfBreak$2(rightDoc, { groupId })
307
316
  ]);
308
317
  }
318
+ function getLanguageByLanguageName(languages, languageName) {
319
+ return languages.find(({ name }) => name.toLowerCase() === languageName) ?? languages.find(({ aliases }) => aliases?.includes(languageName)) ?? languages.find(({ extensions }) => extensions?.includes(`.${languageName}`));
320
+ }
321
+ function inferParser(options, language) {
322
+ return getLanguageByLanguageName(options.plugins?.filter((plugin) => typeof plugin !== "string" && !(plugin instanceof URL)).reverse().flatMap((plugin) => plugin.languages ?? []) ?? [], language)?.parsers[0];
323
+ }
309
324
  function printTextBlock(contents) {
310
325
  return [
311
326
  "\"\"\"",
@@ -314,13 +329,15 @@ function printTextBlock(contents) {
314
329
  "\"\"\""
315
330
  ];
316
331
  }
317
- function embedTextBlock(path) {
332
+ function embedTextBlock(path, options) {
318
333
  if (path.node.namedChildren.some(({ type }) => type === "string_interpolation") || path.node.children[0].value === "\"") return null;
319
334
  const language = findEmbeddedLanguage(path);
320
335
  if (!language) return null;
336
+ const parser = inferParser(options, language);
337
+ if (!parser) return null;
321
338
  const text = unescapeTextBlockContents(textBlockContents(path.node));
322
339
  return async (textToDoc) => {
323
- return printTextBlock([escapeDocForTextBlock(await textToDoc(text, { parser: language })), hardline$7]);
340
+ return printTextBlock([escapeDocForTextBlock(await textToDoc(text, { parser })), hardline$7]);
324
341
  };
325
342
  }
326
343
  function textBlockContents(node) {
@@ -463,9 +480,7 @@ function parentNeedsParentheses(path) {
463
480
  const { parent } = path;
464
481
  switch (parent?.type) {
465
482
  case "return_statement":
466
- case "throw_statement":
467
- if (willReturnOrThrowStatementBreak(path)) return false;
468
- break;
483
+ case "throw_statement": if (willReturnOrThrowStatementBreak(path)) return false;
469
484
  }
470
485
  }
471
486
  function willReturnOrThrowStatementBreak(path) {
@@ -491,7 +506,7 @@ function findBaseIndent(lines) {
491
506
  return Math.min(...lines.map((line) => line.search(/\S/)).filter((indent) => indent >= 0));
492
507
  }
493
508
  function findEmbeddedLanguage(path) {
494
- return path.ancestors.find(({ type, comments }) => type === "block" || comments?.some(({ leading }) => leading))?.comments?.filter(({ leading }) => leading).map(({ value }) => value.match(/^(?:\/\/|\/\*)\s*language\s*=\s*(\S+)/)?.[1]).findLast((language) => language)?.toLowerCase();
509
+ return [path.node, ...path.ancestors].find(({ type, comments }) => type === "block" || comments?.some(({ leading }) => leading))?.comments?.filter(({ leading }) => leading).map(({ value }) => value.match(/^(?:\/\/|\/\*)\s*language\s*=\s*(\S+)/)?.[1]).findLast((language) => language)?.toLowerCase();
495
510
  }
496
511
  function escapeDocForTextBlock(doc) {
497
512
  return mapDoc(doc, (currentDoc) => typeof currentDoc === "string" ? currentDoc.replace(/\\|"""/g, (match) => match === "\\" ? "\\\\" : "\"\"\\\"") : currentDoc);
@@ -1175,7 +1190,11 @@ var expressions_default = {
1175
1190
  }, "children")));
1176
1191
  },
1177
1192
  unary_expression(path, print) {
1178
- return path.map(print, "children");
1193
+ const parts = [path.call(print, "operatorNode")];
1194
+ const operandDoc = path.call(print, "operandNode");
1195
+ if (path.node.operandNode.comments) parts.push(group$4(indentInParentheses(operandDoc)));
1196
+ else parts.push(operandDoc);
1197
+ return parts;
1179
1198
  },
1180
1199
  field_access: printMemberChain,
1181
1200
  generic_type(path, print) {
@@ -1453,7 +1472,7 @@ function shouldPrintParamsWithoutParens(path, options) {
1453
1472
  return false;
1454
1473
  }
1455
1474
  function canPrintParamsWithoutParens(node) {
1456
- return node.parametersNode.type === "identifier" && !node.comments?.some(({ leading, trailing }) => !leading && !trailing) && !node.parametersNode.comments;
1475
+ return node.parametersNode.type === "identifier" && !hasDanglingComments(node) && !node.parametersNode.comments;
1457
1476
  }
1458
1477
  function printMemberChain(path, print, options) {
1459
1478
  const isExpressionStatement = path.parent?.type === "expression_statement";
@@ -1917,11 +1936,11 @@ var names_default = {
1917
1936
  //#region src/printers/packages-and-modules.ts
1918
1937
  const { group: group$1, hardline: hardline$1, indent: indent$1, join: join$1, line: line$2 } = builders;
1919
1938
  var packages_and_modules_default = {
1920
- program(path, print) {
1939
+ program(path, print, options) {
1921
1940
  if (!path.node.namedChildren.length) return [printDanglingComments(path), hardline$1];
1922
1941
  const parts = [];
1923
1942
  if (path.node.namedChildren[0].type !== "import_declaration") parts.push(path.call(print, "namedChildren", 0));
1924
- const staticImports = [];
1943
+ const moduleImports = [];
1925
1944
  const imports = [];
1926
1945
  const otherDeclarations = [];
1927
1946
  path.each((child) => {
@@ -1929,13 +1948,15 @@ var packages_and_modules_default = {
1929
1948
  if (child.node.type === "import_declaration") {
1930
1949
  const names = extractNames(child.node.namedChildren[0]);
1931
1950
  if (child.node.namedChildren.some(({ type }) => type === "asterisk")) names.push("*");
1932
- (child.node.children[1].type === "static" ? staticImports : imports).push({
1951
+ const modifier = child.node.children[1].type;
1952
+ (modifier === "module" ? moduleImports : imports).push({
1933
1953
  doc,
1934
- names
1954
+ names,
1955
+ isStatic: modifier === "static"
1935
1956
  });
1936
1957
  } else if (!child.isFirst) otherDeclarations.push(doc);
1937
1958
  }, "namedChildren");
1938
- parts.push(...[staticImports, imports].filter((imports) => imports.length).map((imports) => join$1(hardline$1, imports.sort(compareFqn).map(({ doc }) => doc))), ...otherDeclarations);
1959
+ parts.push(...[moduleImports.sort(compareFqn), ...groupImports(imports, options.importOrder)].filter((group) => group.length).map((group) => join$1(hardline$1, group.map(({ doc }) => doc))), ...otherDeclarations);
1939
1960
  return [...join$1([hardline$1, hardline$1], parts), hardline$1];
1940
1961
  },
1941
1962
  package_declaration(path, print) {
@@ -1948,9 +1969,7 @@ var packages_and_modules_default = {
1948
1969
  annotations.push(print(child));
1949
1970
  break;
1950
1971
  case "identifier":
1951
- case "scoped_identifier":
1952
- identifier.push(print(child));
1953
- break;
1972
+ case "scoped_identifier": identifier.push(print(child));
1954
1973
  }
1955
1974
  }, "namedChildren");
1956
1975
  return join$1(hardline$1, [...annotations, [
@@ -1961,7 +1980,8 @@ var packages_and_modules_default = {
1961
1980
  },
1962
1981
  import_declaration(path, print) {
1963
1982
  const declaration = ["import "];
1964
- if (path.node.children.some(({ type }) => type === "static")) declaration.push("static ");
1983
+ const modifier = path.node.children[1].type;
1984
+ if (modifier === "module" || modifier === "static") declaration.push(modifier, " ");
1965
1985
  const identifierIndex = path.node.namedChildren.findIndex(({ type }) => type === "identifier" || type === "scoped_identifier");
1966
1986
  declaration.push(path.call(print, "namedChildren", identifierIndex));
1967
1987
  if (path.node.namedChildren.some(({ type }) => type === "asterisk")) declaration.push(".*");
@@ -2015,6 +2035,46 @@ var packages_and_modules_default = {
2015
2035
  function extractNames(node) {
2016
2036
  return node.type === "identifier" ? [node.value] : [...extractNames(node.scopeNode), node.nameNode.value];
2017
2037
  }
2038
+ const STATIC_PREFIX = "static ";
2039
+ const CATCH_ALL_PREFIX = "";
2040
+ function groupImports(imports, importOrder) {
2041
+ if (!importOrder) return [imports.filter(({ isStatic }) => isStatic), imports.filter(({ isStatic }) => !isStatic)].filter((group) => group.length).map((group) => group.sort(compareFqn));
2042
+ const groups = parseImportOrder(importOrder);
2043
+ const prefixes = groups.flat();
2044
+ const importsByPrefix = /* @__PURE__ */ new Map();
2045
+ for (const entry of imports) {
2046
+ const key = importKey(entry);
2047
+ const prefix = prefixes.filter((candidate) => key.startsWith(candidate)).reduce((best, candidate) => candidate.length > best.length ? candidate : best);
2048
+ importsByPrefix.set(prefix, [...importsByPrefix.get(prefix) ?? [], {
2049
+ key,
2050
+ entry
2051
+ }]);
2052
+ }
2053
+ return groups.map((group) => group.flatMap((prefix) => (importsByPrefix.get(prefix) ?? []).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ entry }) => entry))).filter((group) => group.length);
2054
+ }
2055
+ /**
2056
+ * Parses an import order in Spotless's format: groups separated by `,` are
2057
+ * printed with blank lines between them, prefixes joined by `|` share a group,
2058
+ * `#` marks a static prefix, and an empty prefix catches everything else.
2059
+ * Each import belongs to its longest matching prefix.
2060
+ */
2061
+ function parseImportOrder(importOrder) {
2062
+ const knownPrefixes = /* @__PURE__ */ new Set();
2063
+ const groups = importOrder.split(",").map((group) => group.split("|").map((prefix) => prefix.startsWith("#") ? STATIC_PREFIX + prefix.slice(1) : prefix).filter((prefix) => {
2064
+ if (knownPrefixes.has(prefix)) return false;
2065
+ knownPrefixes.add(prefix);
2066
+ return true;
2067
+ }));
2068
+ if (!knownPrefixes.has(STATIC_PREFIX)) {
2069
+ const lastStaticGroupIndex = groups.findLastIndex((group) => group.some((prefix) => prefix.startsWith(STATIC_PREFIX)));
2070
+ groups.splice(Math.max(lastStaticGroupIndex, 0), 0, [STATIC_PREFIX]);
2071
+ }
2072
+ if (!knownPrefixes.has(CATCH_ALL_PREFIX)) groups.push([CATCH_ALL_PREFIX]);
2073
+ return groups;
2074
+ }
2075
+ function importKey({ names, isStatic }) {
2076
+ return (isStatic ? STATIC_PREFIX : "") + names.join(".");
2077
+ }
2018
2078
  function compareFqn({ names: a }, { names: b }) {
2019
2079
  const minParts = Math.min(a.length, b.length);
2020
2080
  for (let i = 0; i < minParts; i++) {
@@ -2115,8 +2175,8 @@ var printer_default = {
2115
2175
  ")"
2116
2176
  ] : doc;
2117
2177
  },
2118
- embed(path) {
2119
- return hasType(path, "string_literal") ? embedTextBlock(path) : null;
2178
+ embed(path, options) {
2179
+ return hasType(path, "string_literal") ? embedTextBlock(path, options) : null;
2120
2180
  },
2121
2181
  hasPrettierIgnore(path) {
2122
2182
  return path.node.comments?.some(isPrettierIgnore) === true || canAttachComment(path.node, path.parent ? [path.parent] : []) && isFullyBetweenPrettierIgnore(path);
@@ -2281,9 +2341,10 @@ function handleLabeledStatementComments(commentNode) {
2281
2341
  function handleLambdaExpressionComments(commentNode) {
2282
2342
  const { enclosingNode, precedingNode, followingNode } = commentNode;
2283
2343
  if (enclosingNode?.type === "lambda_expression" && precedingNode && followingNode && enclosingNode.children.find(({ type }) => type === "->").end.index < commentNode.start.index) {
2284
- if (followingNode.type === "block") if (followingNode.namedChildren.length) util.addLeadingComment(followingNode.namedChildren[0], commentNode);
2285
- else util.addDanglingComment(followingNode, commentNode, void 0);
2286
- else util.addLeadingComment(followingNode, commentNode);
2344
+ if (followingNode.type === "block") {
2345
+ if (followingNode.namedChildren.length) util.addLeadingComment(followingNode.namedChildren[0], commentNode);
2346
+ else util.addDanglingComment(followingNode, commentNode, void 0);
2347
+ } else util.addLeadingComment(followingNode, commentNode);
2287
2348
  return true;
2288
2349
  }
2289
2350
  }
@@ -2366,8 +2427,10 @@ function printLeadingComment(path) {
2366
2427
  const originalText = path.root.value;
2367
2428
  if (comment.type === "block_comment") {
2368
2429
  let lineBreak = " ";
2369
- if (hasNewline(originalText, parser_default.locEnd(comment))) if (hasNewline(originalText, parser_default.locStart(comment), { backwards: true })) lineBreak = hardline;
2370
- else lineBreak = line;
2430
+ if (hasNewline(originalText, parser_default.locEnd(comment))) {
2431
+ if (hasNewline(originalText, parser_default.locStart(comment), { backwards: true })) lineBreak = hardline;
2432
+ else lineBreak = line;
2433
+ }
2371
2434
  parts.push(lineBreak);
2372
2435
  } else parts.push(hardline);
2373
2436
  const index = skipNewline(originalText, skipSpaces(originalText, parser_default.locEnd(comment)));
@@ -2527,8 +2590,10 @@ function processTree(node, fieldName = null, comments) {
2527
2590
  const javaChild = processTree(child, fieldName, comments);
2528
2591
  javaNode.children.push(javaChild);
2529
2592
  if (javaChild.isNamed) javaNode.namedChildren.push(javaChild);
2530
- if (fieldName) if (multiFields?.[fieldName]) javaNode[`${fieldName}Nodes`].push(javaChild);
2531
- else javaNode[`${fieldName}Node`] = javaChild;
2593
+ if (fieldName) {
2594
+ if (multiFields?.[fieldName]) javaNode[`${fieldName}Nodes`].push(javaChild);
2595
+ else javaNode[`${fieldName}Node`] = javaChild;
2596
+ }
2532
2597
  }
2533
2598
  });
2534
2599
  });
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prettier-java-next-line",
3
- "version": "2.10.3",
3
+ "version": "2.11.0",
4
4
  "description": "Prettier Java plugin with configurable same-line and next-line braces",
5
5
  "homepage": "https://github.com/motlin/prettier-java-next-line",
6
6
  "repository": {
@@ -39,6 +39,7 @@
39
39
  "lerna:version": "lerna version --exact --no-private",
40
40
  "lint": "eslint src test",
41
41
  "prepare": "husky",
42
+ "prepublishOnly": "npm run build",
42
43
  "test": "yarn run test:unit && yarn run test:e2e-core",
43
44
  "test:all": "yarn run test && yarn run test:e2e-jhipster1 && yarn run test:e2e-jhipster2",
44
45
  "test:e2e-core": "node scripts/clone-samples e2e-core && mocha \"test/repository-test/core-test.ts\"",
@@ -58,7 +59,7 @@
58
59
  ]
59
60
  },
60
61
  "dependencies": {
61
- "web-tree-sitter": "0.26.11"
62
+ "web-tree-sitter": "0.27.0"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@eslint/js": "^10.0.1",
@@ -73,12 +74,12 @@
73
74
  "lerna": "^10.0.0",
74
75
  "lint-staged": "^17.0.2",
75
76
  "mitata": "^1.0.34",
76
- "mocha": "^11.7.5",
77
+ "mocha": "^12.0.0",
77
78
  "postcss": "^8.5.15",
78
79
  "prettier": "^3.8.1",
79
80
  "prettier-plugin-css-order": "^2.2.0",
80
- "tree-sitter-java-orchard": "0.5.10",
81
- "tsdown": "^0.22.0",
81
+ "tree-sitter-java-orchard": "0.5.22",
82
+ "tsdown": "^0.23.0",
82
83
  "typescript": "^6.0.3",
83
84
  "typescript-eslint": "^8.57.1"
84
85
  },