@aforemendude/prettier-plugin-wrap-comments 1.0.5 → 1.1.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -14
  3. package/dist/comments/comment-body.d.ts +4 -0
  4. package/dist/comments/comment-body.js +36 -0
  5. package/dist/comments/comment-directives.d.ts +2 -0
  6. package/dist/comments/comment-directives.js +48 -0
  7. package/dist/comments/comment-eligibility.d.ts +4 -0
  8. package/dist/comments/comment-eligibility.js +20 -0
  9. package/dist/comments/comment-location.d.ts +9 -0
  10. package/dist/comments/comment-location.js +21 -0
  11. package/dist/comments/comment-ranges.d.ts +24 -0
  12. package/dist/comments/comment-ranges.js +36 -0
  13. package/dist/comments/embedded-expression-ranges.d.ts +13 -0
  14. package/dist/comments/embedded-expression-ranges.js +105 -0
  15. package/dist/comments/jsx-expression-layout.d.ts +10 -0
  16. package/dist/comments/jsx-expression-layout.js +117 -0
  17. package/dist/comments/line-comment-groups.d.ts +6 -0
  18. package/dist/comments/line-comment-groups.js +28 -0
  19. package/dist/comments/prettier-ignore.d.ts +8 -0
  20. package/dist/comments/prettier-ignore.js +145 -0
  21. package/dist/comments/printer-layout.d.ts +17 -0
  22. package/dist/comments/printer-layout.js +64 -0
  23. package/dist/comments/{block.d.ts → wrap-block-comment.d.ts} +9 -1
  24. package/dist/comments/{block.js → wrap-block-comment.js} +30 -14
  25. package/dist/comments/wrap-comments.d.ts +9 -0
  26. package/dist/comments/wrap-comments.js +101 -0
  27. package/dist/comments/wrap-line-comment-group.d.ts +4 -0
  28. package/dist/comments/wrap-line-comment-group.js +39 -0
  29. package/dist/comments/wrap-trailing-line-comment.d.ts +9 -0
  30. package/dist/comments/wrap-trailing-line-comment.js +78 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +4 -4
  33. package/dist/plugin/create-parsers.d.ts +2 -0
  34. package/dist/plugin/create-parsers.js +57 -0
  35. package/dist/plugin/create-printers.d.ts +2 -0
  36. package/dist/plugin/{printers.js → create-printers.js} +9 -10
  37. package/dist/plugin/get-printer-layout-source.d.ts +4 -0
  38. package/dist/plugin/get-printer-layout-source.js +27 -0
  39. package/dist/plugin/jsx-comment-rewrite-metadata.d.ts +8 -0
  40. package/dist/plugin/jsx-comment-rewrite-metadata.js +40 -0
  41. package/dist/plugin/parser-names.d.ts +2 -0
  42. package/dist/plugin/parser-names.js +1 -0
  43. package/dist/utils/ast.d.ts +6 -0
  44. package/dist/utils/ast.js +52 -0
  45. package/dist/utils/display-width.d.ts +2 -0
  46. package/dist/utils/display-width.js +19 -0
  47. package/dist/{shared/markdown.d.ts → utils/format-markdown.d.ts} +1 -1
  48. package/dist/{shared/markdown.js → utils/format-markdown.js} +12 -2
  49. package/dist/utils/indentation.d.ts +4 -0
  50. package/dist/utils/indentation.js +21 -0
  51. package/dist/utils/replacements.d.ts +6 -0
  52. package/dist/utils/replacements.js +21 -0
  53. package/dist/utils/source-lines.d.ts +6 -0
  54. package/dist/utils/source-lines.js +40 -0
  55. package/dist/utils/type-guards.d.ts +2 -0
  56. package/dist/utils/type-guards.js +6 -0
  57. package/dist/utils/whitespace.d.ts +2 -0
  58. package/dist/utils/whitespace.js +22 -0
  59. package/dist/{shared/options.d.ts → utils/wrap-options.d.ts} +2 -1
  60. package/package.json +17 -9
  61. package/dist/comments/core.d.ts +0 -8
  62. package/dist/comments/core.js +0 -114
  63. package/dist/comments/line.d.ts +0 -6
  64. package/dist/comments/line.js +0 -108
  65. package/dist/comments/wrap.d.ts +0 -3
  66. package/dist/comments/wrap.js +0 -375
  67. package/dist/plugin/parsers.d.ts +0 -2
  68. package/dist/plugin/parsers.js +0 -35
  69. package/dist/plugin/printers.d.ts +0 -2
  70. package/dist/shared/text.d.ts +0 -15
  71. package/dist/shared/text.js +0 -99
  72. package/dist/shared/types.d.ts +0 -30
  73. package/dist/shared/types.js +0 -1
  74. /package/dist/{shared/options.js → utils/wrap-options.js} +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aforemendude
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -5,8 +5,8 @@ real column to calculate the available content width, so nested comments wrap mo
5
5
 
6
6
  ## Requirements
7
7
 
8
- - Node.js 18 or newer
9
- - Prettier 3 or newer
8
+ - Node.js 20 or newer
9
+ - Prettier 3 (`>=3.0.0 <4.0.0`)
10
10
 
11
11
  ## Install
12
12
 
@@ -37,9 +37,15 @@ preprocessing: the underlying Prettier parser preprocesses and parses the source
37
37
  comments from that parsed comment list, and Prettier then formats the rewritten source with its built-in JavaScript and
38
38
  TypeScript printers. If the parser cannot parse the preprocessed source, the plugin leaves the source unchanged.
39
39
 
40
+ Offset-sensitive formatting is conservative. Full-file `formatWithCursor` calls skip comment rewriting so Prettier can
41
+ map the cursor from the original source. During range formatting, preprocessing does not rewrite text outside Prettier's
42
+ selected range; eligible comments inside that range can still be wrapped.
43
+
40
44
  Comment text is normalized and reflowed with Prettier's Markdown parser. The available content width is based on
41
45
  Prettier's `printWidth` minus the column where the comment text starts. `tabWidth`, `useTabs`, and `endOfLine` are used
42
- when measuring and rebuilding comments.
46
+ when measuring and rebuilding comments. When `tabWidth` is `0`, tabs contribute zero columns during measurement, as they
47
+ do in Prettier. If the plugin must synthesize indentation at a positive column, it uses spaces because zero-width tabs
48
+ cannot advance to that column.
43
49
 
44
50
  Standalone `//` comments are wrapped in place. Adjacent standalone line comments are combined and reflowed as one
45
51
  Markdown block when they are directly next to each other and their `//` markers start in the same column.
@@ -53,7 +59,11 @@ function example() {
53
59
  ```
54
60
 
55
61
  Trailing `//` comments stay in place when the full source line fits within `printWidth`. If the source line is too long,
56
- the comment is moved above the code and wrapped using the code line's indentation.
62
+ the comment is moved above the code and normally wrapped using the code line's indentation. On a line made up of closing
63
+ delimiters such as `}`, `]`, or `)`, the moved comment is indented one additional level so it remains visually inside
64
+ the construct being closed. When a comment directly trails the root value of a JSX expression or template interpolation,
65
+ it can move above that value and wrap inside the expression delimiters. Nested or otherwise ambiguous expression
66
+ comments and comments in JSX spreads stay in place.
57
67
 
58
68
  ```ts
59
69
  function example() {
@@ -67,6 +77,11 @@ Non-JSDoc `/* ... */` comments are also normalized as Markdown. A block comment
67
77
  comment fits within `printWidth`; otherwise, only standalone block comments are expanded into star-prefixed blocks. Long
68
78
  inline block comments are left unchanged when they cannot fit on one line.
69
79
 
80
+ Multiline block comments are normalized as conventional block-comment text before Markdown parsing: leading indentation
81
+ is removed, and a `*` that is the first non-whitespace character on a body line is treated as comment formatting, not
82
+ Markdown content. This also applies to unstarred `/* ... */` blocks, so use line comments when Markdown-significant
83
+ indentation or leading `*` characters must be preserved exactly.
84
+
70
85
  ```ts
71
86
  if (ready) {
72
87
  /*
@@ -78,9 +93,10 @@ if (ready) {
78
93
  ```
79
94
 
80
95
  JSX and TSX expression comments use the expression contents, not the outer React expression braces, to decide whether a
81
- block comment is standalone, trailing, or inline. A comment-only expression like `{/* ... */}` can wrap as a standalone
82
- JSX comment. A trailing expression comment like `{label /* ... */}` can move before `label` and wrap. A true inline
83
- expression comment like `{"abc" + /* ... */ "123"}` is left unchanged when it cannot fit on one line.
96
+ block comment is standalone, leading, trailing, or inline. A comment-only expression like `{/* ... */}` can wrap as a
97
+ standalone JSX comment. A leading expression comment like `{/* ... */ label}` can move above `label` and wrap. A
98
+ trailing expression comment like `{label /* ... */}` can move before `label` and wrap. A true inline expression comment
99
+ like `{"abc" + /* ... */ "123"}` is left unchanged when it cannot fit on one line.
84
100
 
85
101
  <!-- prettier-ignore-start -->
86
102
  ```tsx
@@ -91,6 +107,13 @@ expression comment like `{"abc" + /* ... */ "123"}` is left unchanged when it ca
91
107
  * make it inline.
92
108
  */
93
109
  }
110
+ {
111
+ /*
112
+ * This expression comment moved above the expression value because it was
113
+ * leading.
114
+ */
115
+ label
116
+ }
94
117
  {
95
118
  /*
96
119
  * This expression comment moved above the expression value because it was
@@ -109,11 +132,16 @@ the block comment is one the plugin would otherwise wrap, the following code sti
109
132
  comment is already skipped by the plugin, such as a JSDoc or directive block, Prettier keeps its normal ignore behavior
110
133
  for the following code.
111
134
 
112
- For trailing line comments, a `// prettier-ignore` line can apply to the code line and its inline comment. The plugin
113
- walks past adjacent standalone comments that it normally leaves alone, such as an `eslint-disable-next-line` directive,
114
- so an ignored code line's trailing comment remains inline and unchanged. A directive comment by itself does not ignore
115
- the following code line; without `// prettier-ignore`, an overlong trailing comment below a directive is still moved
116
- above the statement and wrapped.
135
+ An exact `// prettier-ignore` marker can also apply to the following adjacent standalone `//` comment group. The plugin
136
+ leaves that group unchanged and neutralizes the marker for Prettier, so code after the ignored comment group still
137
+ formats normally.
138
+
139
+ For trailing line comments, a standalone exact-body `prettier-ignore` marker, written as `// prettier-ignore` or
140
+ `/* prettier-ignore */`, can apply to the code line and its inline comment. The plugin walks past adjacent standalone
141
+ comments that it normally leaves alone, such as an `eslint-disable-next-line` directive, so an ignored code line's
142
+ trailing comment remains inline and unchanged. A directive comment by itself does not ignore the following code line;
143
+ without `prettier-ignore`, an overlong trailing comment below a directive is still moved above the statement and
144
+ wrapped.
117
145
 
118
146
  The plugin leaves these comments unchanged:
119
147
 
@@ -133,11 +161,19 @@ The plugin leaves these comments unchanged:
133
161
 
134
162
  ## Development
135
163
 
164
+ Source files are organized by responsibility. `src/plugin/` contains parser and printer integration,
165
+ `src/comments/wrap-comments.ts` coordinates the rewriting pipeline, the other `src/comments/` modules isolate comment
166
+ analysis and wrapping concerns, and `src/utils/` contains reusable utilities. Unit tests under `test/unit/` mirror those
167
+ concerns and source file names wherever practical.
168
+
136
169
  ```sh
137
170
  npm install
138
171
  npm run format:check
172
+ npm run typecheck
139
173
  npm run test
174
+ npm run build
140
175
  ```
141
176
 
142
- `npm run test` builds `dist` before running the Node test suite. `npm run verify` runs `npm install`,
143
- `npm run format:check`, and `npm run test`.
177
+ `npm run test` runs the TypeScript unit and fixture-based integration suites with Vitest. Use `npm run test:unit` or
178
+ `npm run test:integration` to run one suite. `npm run build` removes and recreates `dist` using a cross-platform Node
179
+ cleanup script, and `npm run verify` runs formatting, type checking, the build, and both test suites.
@@ -0,0 +1,4 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ export declare function getCommentBody(text: string, comment: CommentRange): string;
3
+ export declare function normalizeLineCommentBody(rawBody: string): string;
4
+ export declare function normalizeBlockCommentBody(rawComment: string): string;
@@ -0,0 +1,36 @@
1
+ import { isBlankLine } from '../utils/source-lines.js';
2
+ export function getCommentBody(text, comment) {
3
+ const raw = text.slice(comment.start, comment.end);
4
+ return comment.kind === 'line' ? normalizeLineCommentBody(raw.slice(2)) : normalizeBlockCommentBody(raw);
5
+ }
6
+ export function normalizeLineCommentBody(rawBody) {
7
+ if (rawBody.trim() === '') {
8
+ return '';
9
+ }
10
+ return rawBody.replace(/^[ \t]?/, '').replace(/[ \t]+$/u, '');
11
+ }
12
+ export function normalizeBlockCommentBody(rawComment) {
13
+ const body = rawComment.slice(2, -2).replace(/\r\n?/g, '\n');
14
+ const lines = body.split('\n');
15
+ if (lines.length === 1) {
16
+ return lines[0]?.trim() ?? '';
17
+ }
18
+ while (isBlankLine(lines[0])) {
19
+ lines.shift();
20
+ }
21
+ while (isBlankLine(lines.at(-1))) {
22
+ lines.pop();
23
+ }
24
+ return lines
25
+ .map((line) => {
26
+ const withoutIndent = line.replace(/^[ \t]*/u, '');
27
+ if (!withoutIndent.startsWith('*')) {
28
+ return withoutIndent.replace(/[ \t]+$/u, '');
29
+ }
30
+ return withoutIndent
31
+ .slice(1)
32
+ .replace(/^[ \t]?/u, '')
33
+ .replace(/[ \t]+$/u, '');
34
+ })
35
+ .join('\n');
36
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isDirectiveComment(body: string): boolean;
2
+ export declare function isPrettierIgnoreComment(body: string): boolean;
@@ -0,0 +1,48 @@
1
+ const PRAGMA_DIRECTIVE_COMMENT_PATTERNS = [
2
+ /^@(?:license|preserve)\b/u,
3
+ /^@(?:jsxFrag|jsxImportSource|jsxRuntime|jsx)\b/u,
4
+ /^@(?:ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b/u,
5
+ /^[@#]__(?:NO_SIDE_EFFECTS|PURE)__\b/u,
6
+ ];
7
+ const SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS = [
8
+ /^[#@][ \t]*sourceMappingURL=/u,
9
+ /^[#@][ \t]*sourceURL=/u,
10
+ /^sourceMappingURL=/u,
11
+ /^sourceURL=/u,
12
+ ];
13
+ const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
14
+ /^biome-ignore\b/u,
15
+ /^c8\b/u,
16
+ /^deno-lint-ignore\b/u,
17
+ /^eslint\b/u,
18
+ /^eslint-/u,
19
+ /^exported\b/u,
20
+ /^globals?\b/u,
21
+ /^istanbul\b/u,
22
+ /^jshint\b/u,
23
+ /^nyc\b/u,
24
+ /^oxlint\b/u,
25
+ /^prettier-ignore\b/u,
26
+ /^prettier-ignore-start\b/u,
27
+ /^prettier-ignore-end\b/u,
28
+ /^stylelint\b/u,
29
+ /^tslint\b/u,
30
+ /^v8\b/u,
31
+ /^vite-ignore\b/u,
32
+ ];
33
+ const BUNDLER_DIRECTIVE_COMMENT_PATTERNS = [
34
+ /^webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b/u,
35
+ ];
36
+ const DIRECTIVE_COMMENT_PATTERNS = [
37
+ ...PRAGMA_DIRECTIVE_COMMENT_PATTERNS,
38
+ ...SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS,
39
+ ...TOOL_DIRECTIVE_COMMENT_PATTERNS,
40
+ ...BUNDLER_DIRECTIVE_COMMENT_PATTERNS,
41
+ ];
42
+ export function isDirectiveComment(body) {
43
+ const normalizedBody = body.trimStart();
44
+ return DIRECTIVE_COMMENT_PATTERNS.some((pattern) => pattern.test(normalizedBody));
45
+ }
46
+ export function isPrettierIgnoreComment(body) {
47
+ return body.trim() === 'prettier-ignore';
48
+ }
@@ -0,0 +1,4 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ export declare function shouldSkipLineComment(text: string, comment: CommentRange): boolean;
3
+ export declare function shouldSkipBlockComment(text: string, comment: CommentRange): boolean;
4
+ export declare function hasPreserveCommentMarker(rawComment: string): boolean;
@@ -0,0 +1,20 @@
1
+ import { normalizeBlockCommentBody, normalizeLineCommentBody } from './comment-body.js';
2
+ import { isDirectiveComment } from './comment-directives.js';
3
+ export function shouldSkipLineComment(text, comment) {
4
+ const raw = text.slice(comment.start, comment.end);
5
+ if (raw.startsWith('///') || hasPreserveCommentMarker(raw)) {
6
+ return true;
7
+ }
8
+ return isDirectiveComment(normalizeLineCommentBody(raw.slice(2)));
9
+ }
10
+ export function shouldSkipBlockComment(text, comment) {
11
+ const raw = text.slice(comment.start, comment.end);
12
+ if (raw.startsWith('/**') || hasPreserveCommentMarker(raw)) {
13
+ return true;
14
+ }
15
+ const body = normalizeBlockCommentBody(raw);
16
+ return body.trim() === '' || isDirectiveComment(body);
17
+ }
18
+ export function hasPreserveCommentMarker(rawComment) {
19
+ return rawComment.startsWith('/*!') || rawComment.startsWith('//!');
20
+ }
@@ -0,0 +1,9 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ export declare function isStandaloneComment(text: string, comment: CommentRange): boolean;
3
+ export declare function isStandaloneLineComment(text: string, comment: CommentRange): boolean;
4
+ export declare function isStandaloneBlockComment(text: string, comment: {
5
+ end: number;
6
+ start: number;
7
+ }): boolean;
8
+ export declare function areCommentsOnAdjacentLines(text: string, previousComment: CommentRange, comment: CommentRange): boolean;
9
+ export declare function isCommentAdjacentBeforeIndex(text: string, comment: CommentRange, index: number): boolean;
@@ -0,0 +1,21 @@
1
+ import { getLineEnd, getLinePrefix, getLineStart } from '../utils/source-lines.js';
2
+ export function isStandaloneComment(text, comment) {
3
+ return comment.kind === 'line' ? isStandaloneLineComment(text, comment) : isStandaloneBlockComment(text, comment);
4
+ }
5
+ export function isStandaloneLineComment(text, comment) {
6
+ return /^[ \t]*$/u.test(getLinePrefix(text, comment.start));
7
+ }
8
+ export function isStandaloneBlockComment(text, comment) {
9
+ const before = text.slice(getLineStart(text, comment.start), comment.start);
10
+ const after = text.slice(comment.end, getLineEnd(text, comment.end));
11
+ return /^[ \t]*$/u.test(before) && /^[ \t]*$/u.test(after);
12
+ }
13
+ export function areCommentsOnAdjacentLines(text, previousComment, comment) {
14
+ return isOnlyNewlineAndIndent(text.slice(previousComment.end, comment.start));
15
+ }
16
+ export function isCommentAdjacentBeforeIndex(text, comment, index) {
17
+ return isOnlyNewlineAndIndent(text.slice(comment.end, index));
18
+ }
19
+ function isOnlyNewlineAndIndent(text) {
20
+ return /^(?:\r\n|[\n\r\u2028\u2029])[ \t]*$/u.test(text);
21
+ }
@@ -0,0 +1,24 @@
1
+ export type RawComment = {
2
+ end?: unknown;
3
+ loc?: {
4
+ start?: {
5
+ column?: unknown;
6
+ };
7
+ };
8
+ range?: unknown;
9
+ start?: unknown;
10
+ type?: unknown;
11
+ value?: unknown;
12
+ };
13
+ export type CommentRange = {
14
+ end: number;
15
+ kind: 'block' | 'line';
16
+ start: number;
17
+ };
18
+ export type CommentEntry = {
19
+ range: CommentRange;
20
+ raw: RawComment;
21
+ };
22
+ export declare function collectAstComments(ast: unknown): RawComment[];
23
+ export declare function collectCommentEntries(ast: unknown, text: string): CommentEntry[];
24
+ export declare function toCommentRange(comment: RawComment, text: string): CommentRange | undefined;
@@ -0,0 +1,36 @@
1
+ import { numberOrUndefined } from '../utils/type-guards.js';
2
+ export function collectAstComments(ast) {
3
+ if (ast === null || ast === undefined) {
4
+ return [];
5
+ }
6
+ const candidate = ast;
7
+ if (Array.isArray(candidate.comments)) {
8
+ return candidate.comments;
9
+ }
10
+ if (Array.isArray(candidate.program?.comments)) {
11
+ return candidate.program.comments;
12
+ }
13
+ return [];
14
+ }
15
+ export function collectCommentEntries(ast, text) {
16
+ return collectAstComments(ast)
17
+ .map((raw) => ({ range: toCommentRange(raw, text), raw }))
18
+ .filter((entry) => entry.range !== undefined)
19
+ .sort((left, right) => left.range.start - right.range.start);
20
+ }
21
+ export function toCommentRange(comment, text) {
22
+ const range = Array.isArray(comment.range) ? comment.range : undefined;
23
+ const start = numberOrUndefined(comment.start) ?? numberOrUndefined(range?.[0]);
24
+ const end = numberOrUndefined(comment.end) ?? numberOrUndefined(range?.[1]);
25
+ if (start === undefined || end === undefined || start >= end) {
26
+ return undefined;
27
+ }
28
+ const rawStart = text.slice(start, start + 3);
29
+ if (rawStart.startsWith('//')) {
30
+ return { end, kind: 'line', start };
31
+ }
32
+ if (rawStart.startsWith('/*')) {
33
+ return { end, kind: 'block', start };
34
+ }
35
+ return undefined;
36
+ }
@@ -0,0 +1,13 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ import type { SourceRange } from '../utils/ast.js';
3
+ export type EmbeddedExpressionRange = SourceRange & {
4
+ expression?: SourceRange;
5
+ };
6
+ export type EmbeddedTrailingLineCommentMove = {
7
+ insertAt: number;
8
+ removeStart: number;
9
+ };
10
+ export declare function collectEmbeddedExpressionRanges(ast: unknown): EmbeddedExpressionRange[];
11
+ export declare function isCommentInEmbeddedExpression(comment: CommentRange, ranges: SourceRange[]): boolean;
12
+ export declare function getEmbeddedTrailingLineCommentMove(text: string, comment: CommentRange, ranges: EmbeddedExpressionRange[]): EmbeddedTrailingLineCommentMove | undefined;
13
+ export declare function doesBlockCommentSeparateEmbeddedTrailingLineComment(text: string, blockComment: CommentRange, nextComment: CommentRange | undefined, ranges: EmbeddedExpressionRange[]): boolean;
@@ -0,0 +1,105 @@
1
+ import { getAstNodeRange, visitAstNodes } from '../utils/ast.js';
2
+ import { getLineEnd } from '../utils/source-lines.js';
3
+ import { isRecord, numberOrUndefined } from '../utils/type-guards.js';
4
+ const JSX_EMBEDDED_EXPRESSION_TYPES = new Set(['JSXExpressionContainer', 'JSXSpreadAttribute', 'JSXSpreadChild']);
5
+ export function collectEmbeddedExpressionRanges(ast) {
6
+ const ranges = [];
7
+ visitAstNodes(ast, (node) => {
8
+ const type = node['type'];
9
+ if (typeof type !== 'string') {
10
+ return;
11
+ }
12
+ if (JSX_EMBEDDED_EXPRESSION_TYPES.has(type)) {
13
+ const range = getAstNodeRange(node);
14
+ if (range !== undefined) {
15
+ const expressionNode = type === 'JSXExpressionContainer' ? node['expression'] : undefined;
16
+ const expression = isRecord(expressionNode) ? getAstNodeRange(expressionNode) : undefined;
17
+ ranges.push(expression === undefined ? range : { ...range, expression });
18
+ }
19
+ return;
20
+ }
21
+ if (type === 'TemplateLiteral') {
22
+ collectTemplateInterpolationRanges(node, 'expressions', ranges);
23
+ }
24
+ else if (type === 'TSTemplateLiteralType') {
25
+ collectTemplateInterpolationRanges(node, 'types', ranges);
26
+ }
27
+ });
28
+ return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
29
+ }
30
+ export function isCommentInEmbeddedExpression(comment, ranges) {
31
+ return getSmallestContainingRange(comment, ranges) !== undefined;
32
+ }
33
+ export function getEmbeddedTrailingLineCommentMove(text, comment, ranges) {
34
+ const range = getSmallestContainingRange(comment, ranges);
35
+ const expression = range?.expression;
36
+ if (expression === undefined ||
37
+ expression.end > comment.start ||
38
+ !/^[\t ]*$/u.test(text.slice(expression.end, comment.start))) {
39
+ return undefined;
40
+ }
41
+ return { insertAt: expression.start, removeStart: expression.end };
42
+ }
43
+ export function doesBlockCommentSeparateEmbeddedTrailingLineComment(text, blockComment, nextComment, ranges) {
44
+ if (blockComment.kind !== 'block' ||
45
+ nextComment?.kind !== 'line' ||
46
+ blockComment.end >= nextComment.start ||
47
+ nextComment.end > getLineEnd(text, blockComment.start)) {
48
+ return false;
49
+ }
50
+ const range = getSmallestContainingRange(blockComment, ranges);
51
+ const nextRange = getSmallestContainingRange(nextComment, ranges);
52
+ const expression = range?.expression;
53
+ return (range !== undefined &&
54
+ range === nextRange &&
55
+ expression !== undefined &&
56
+ expression.end <= blockComment.start &&
57
+ /^[\t ]*$/u.test(text.slice(expression.end, blockComment.start)) &&
58
+ /^[\t ]*$/u.test(text.slice(blockComment.end, nextComment.start)));
59
+ }
60
+ function collectTemplateInterpolationRanges(node, expressionsKey, ranges) {
61
+ const quasis = node['quasis'];
62
+ const expressions = node[expressionsKey];
63
+ if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length !== expressions.length + 1) {
64
+ return;
65
+ }
66
+ for (let index = 0; index < expressions.length; index += 1) {
67
+ const precedingQuasi = getNodeBoundary(quasis[index]);
68
+ const followingQuasi = getNodeBoundary(quasis[index + 1]);
69
+ const expression = getExpressionRange(expressions[index]);
70
+ if (precedingQuasi === undefined ||
71
+ followingQuasi === undefined ||
72
+ expression === undefined ||
73
+ precedingQuasi.end >= followingQuasi.start) {
74
+ continue;
75
+ }
76
+ ranges.push({ end: followingQuasi.start, expression, start: precedingQuasi.end });
77
+ }
78
+ }
79
+ function getExpressionRange(value) {
80
+ return isRecord(value) ? getAstNodeRange(value) : undefined;
81
+ }
82
+ function getNodeBoundary(value) {
83
+ if (!isRecord(value)) {
84
+ return undefined;
85
+ }
86
+ const range = Array.isArray(value['range']) ? value['range'] : undefined;
87
+ const start = numberOrUndefined(value['start']) ?? numberOrUndefined(range?.[0]);
88
+ const end = numberOrUndefined(value['end']) ?? numberOrUndefined(range?.[1]);
89
+ if (start === undefined || end === undefined || start > end) {
90
+ return undefined;
91
+ }
92
+ return { end, start };
93
+ }
94
+ function getSmallestContainingRange(comment, ranges) {
95
+ let containingRange;
96
+ for (const range of ranges) {
97
+ if (comment.start <= range.start || comment.end >= range.end) {
98
+ continue;
99
+ }
100
+ if (containingRange === undefined || range.end - range.start < containingRange.end - containingRange.start) {
101
+ containingRange = range;
102
+ }
103
+ }
104
+ return containingRange;
105
+ }
@@ -0,0 +1,10 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ import type { PrinterCommentLayout } from './printer-layout.js';
3
+ import type { BlockCommentLayout } from './wrap-block-comment.js';
4
+ import type { SourceRange } from '../utils/ast.js';
5
+ export type JsxExpressionContainerRange = SourceRange & {
6
+ expression: SourceRange | undefined;
7
+ };
8
+ export declare function collectJsxExpressionContainerRanges(ast: unknown): JsxExpressionContainerRange[];
9
+ export declare function getPrintedJsxCommentMarkerColumn(text: string, container: SourceRange, tabWidth: number): number;
10
+ export declare function getJsxExpressionBlockCommentLayout(text: string, comment: CommentRange, previousComment: CommentRange | undefined, containers: JsxExpressionContainerRange[], tabWidth: number, outputCommentLayout: PrinterCommentLayout | undefined, outputCommentMarkerColumns: Array<number | undefined>): BlockCommentLayout | undefined;
@@ -0,0 +1,117 @@
1
+ import { isStandaloneBlockComment } from './comment-location.js';
2
+ import { getAstNodeRange, visitAstNodes } from '../utils/ast.js';
3
+ import { getColumns } from '../utils/display-width.js';
4
+ import { getLeadingIndent } from '../utils/indentation.js';
5
+ import { getLinePrefix } from '../utils/source-lines.js';
6
+ import { isRecord } from '../utils/type-guards.js';
7
+ import { skipWhitespace, trimWhitespaceEnd } from '../utils/whitespace.js';
8
+ export function collectJsxExpressionContainerRanges(ast) {
9
+ const ranges = [];
10
+ visitAstNodes(ast, (node) => {
11
+ if (node['type'] !== 'JSXExpressionContainer') {
12
+ return;
13
+ }
14
+ const range = getAstNodeRange(node);
15
+ if (range === undefined) {
16
+ return;
17
+ }
18
+ const expressionNode = node['expression'];
19
+ const expression = isRecord(expressionNode) && expressionNode['type'] !== 'JSXEmptyExpression'
20
+ ? getAstNodeRange(expressionNode)
21
+ : undefined;
22
+ ranges.push({ ...range, expression });
23
+ });
24
+ return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
25
+ }
26
+ export function getPrintedJsxCommentMarkerColumn(text, container, tabWidth) {
27
+ const linePrefix = getLinePrefix(text, container.start);
28
+ const lineIndent = getLeadingIndent(linePrefix);
29
+ return getColumns(lineIndent, tabWidth) + tabWidth;
30
+ }
31
+ export function getJsxExpressionBlockCommentLayout(text, comment, previousComment, containers, tabWidth, outputCommentLayout, outputCommentMarkerColumns) {
32
+ const container = getSmallestContainingRange(comment, containers);
33
+ if (container === undefined || text[container.start] !== '{' || text[container.end - 1] !== '}') {
34
+ return undefined;
35
+ }
36
+ const hasExpressionBeforeComment = container.expression !== undefined && container.expression.start < comment.start;
37
+ const hasExpressionAfterComment = container.expression !== undefined && container.expression.end > comment.end;
38
+ const containerIndex = containers.indexOf(container);
39
+ const multilineMarkerColumn = outputCommentMarkerColumns[containerIndex] ??
40
+ getJsxExpressionContainerOutputColumn(text, container, tabWidth) + tabWidth;
41
+ const markerColumn = outputCommentLayout?.markerColumn ?? multilineMarkerColumn;
42
+ const contentColumn = multilineMarkerColumn + 3;
43
+ if (!hasExpressionBeforeComment && !hasExpressionAfterComment) {
44
+ return {
45
+ contentColumn,
46
+ markerColumn,
47
+ multilineIndent: '',
48
+ placement: 'standalone',
49
+ singleLineSuffixWidth: outputCommentLayout?.suffixWidth ?? 1,
50
+ };
51
+ }
52
+ if (hasExpressionBeforeComment && !hasExpressionAfterComment) {
53
+ const hasEarlierCommentInContainer = previousComment !== undefined && previousComment.start > container.start && previousComment.end < container.end;
54
+ if (hasEarlierCommentInContainer) {
55
+ return {
56
+ contentColumn,
57
+ markerColumn,
58
+ multilineIndent: '',
59
+ placement: 'standalone',
60
+ singleLineSuffixWidth: outputCommentLayout?.suffixWidth ?? 1,
61
+ };
62
+ }
63
+ const expressionStart = skipWhitespace(text, container.start + 1);
64
+ const expressionEnd = trimWhitespaceEnd(text, container.start + 1, comment.start);
65
+ const removalEnd = Math.min(skipWhitespace(text, comment.end), container.end - 1);
66
+ return {
67
+ contentColumn,
68
+ markerColumn,
69
+ multilineIndent: '',
70
+ placement: 'trailing',
71
+ singleLineSuffixWidth: outputCommentLayout?.suffixWidth ?? 1,
72
+ trailingMove: {
73
+ insertAt: expressionStart,
74
+ removeEnd: removalEnd,
75
+ removeStart: expressionEnd,
76
+ },
77
+ };
78
+ }
79
+ if (!hasExpressionBeforeComment) {
80
+ if (isStandaloneBlockComment(text, comment)) {
81
+ return { placement: 'inline' };
82
+ }
83
+ const expressionStart = skipWhitespace(text, comment.end);
84
+ return {
85
+ contentColumn,
86
+ leadingMove: {
87
+ removeEnd: expressionStart,
88
+ removeStart: comment.end,
89
+ },
90
+ markerColumn,
91
+ multilineIndent: '',
92
+ placement: 'standalone',
93
+ singleLineSuffixWidth: outputCommentLayout?.suffixWidth ?? getColumns(text.slice(comment.end, container.end), tabWidth),
94
+ };
95
+ }
96
+ return { placement: 'inline' };
97
+ }
98
+ function getJsxExpressionContainerOutputColumn(text, container, tabWidth) {
99
+ const linePrefix = getLinePrefix(text, container.start);
100
+ if (/^[ \t]*$/u.test(linePrefix)) {
101
+ return getColumns(linePrefix, tabWidth);
102
+ }
103
+ const lineIndent = getLeadingIndent(linePrefix);
104
+ return getColumns(lineIndent, tabWidth) + tabWidth;
105
+ }
106
+ function getSmallestContainingRange(comment, ranges) {
107
+ let containingRange;
108
+ for (const range of ranges) {
109
+ if (comment.start <= range.start || comment.end >= range.end) {
110
+ continue;
111
+ }
112
+ if (containingRange === undefined || range.end - range.start < containingRange.end - containingRange.start) {
113
+ containingRange = range;
114
+ }
115
+ }
116
+ return containingRange;
117
+ }
@@ -0,0 +1,6 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ export type LineCommentGroup = {
3
+ comments: CommentRange[];
4
+ endIndex: number;
5
+ };
6
+ export declare function collectLineCommentGroup(text: string, comments: CommentRange[], startIndex: number, tabWidth: number): LineCommentGroup;
@@ -0,0 +1,28 @@
1
+ import { shouldSkipLineComment } from './comment-eligibility.js';
2
+ import { areCommentsOnAdjacentLines, isStandaloneLineComment } from './comment-location.js';
3
+ import { getColumnAt } from '../utils/display-width.js';
4
+ export function collectLineCommentGroup(text, comments, startIndex, tabWidth) {
5
+ const firstComment = comments[startIndex];
6
+ if (firstComment === undefined) {
7
+ return { comments: [], endIndex: startIndex };
8
+ }
9
+ const group = [firstComment];
10
+ const markerColumn = getColumnAt(text, firstComment.start, tabWidth);
11
+ let endIndex = startIndex;
12
+ let previousComment = firstComment;
13
+ while (endIndex + 1 < comments.length) {
14
+ const nextComment = comments[endIndex + 1];
15
+ if (nextComment === undefined ||
16
+ nextComment.kind !== 'line' ||
17
+ !isStandaloneLineComment(text, nextComment) ||
18
+ shouldSkipLineComment(text, nextComment) ||
19
+ !areCommentsOnAdjacentLines(text, previousComment, nextComment) ||
20
+ markerColumn !== getColumnAt(text, nextComment.start, tabWidth)) {
21
+ break;
22
+ }
23
+ group.push(nextComment);
24
+ previousComment = nextComment;
25
+ endIndex += 1;
26
+ }
27
+ return { comments: group, endIndex };
28
+ }