@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.
- package/LICENSE +21 -0
- package/README.md +50 -14
- package/dist/comments/comment-body.d.ts +4 -0
- package/dist/comments/comment-body.js +36 -0
- package/dist/comments/comment-directives.d.ts +2 -0
- package/dist/comments/comment-directives.js +48 -0
- package/dist/comments/comment-eligibility.d.ts +4 -0
- package/dist/comments/comment-eligibility.js +20 -0
- package/dist/comments/comment-location.d.ts +9 -0
- package/dist/comments/comment-location.js +21 -0
- package/dist/comments/comment-ranges.d.ts +24 -0
- package/dist/comments/comment-ranges.js +36 -0
- package/dist/comments/embedded-expression-ranges.d.ts +13 -0
- package/dist/comments/embedded-expression-ranges.js +105 -0
- package/dist/comments/jsx-expression-layout.d.ts +10 -0
- package/dist/comments/jsx-expression-layout.js +117 -0
- package/dist/comments/line-comment-groups.d.ts +6 -0
- package/dist/comments/line-comment-groups.js +28 -0
- package/dist/comments/prettier-ignore.d.ts +8 -0
- package/dist/comments/prettier-ignore.js +145 -0
- package/dist/comments/printer-layout.d.ts +17 -0
- package/dist/comments/printer-layout.js +64 -0
- package/dist/comments/{block.d.ts → wrap-block-comment.d.ts} +9 -1
- package/dist/comments/{block.js → wrap-block-comment.js} +30 -14
- package/dist/comments/wrap-comments.d.ts +9 -0
- package/dist/comments/wrap-comments.js +101 -0
- package/dist/comments/wrap-line-comment-group.d.ts +4 -0
- package/dist/comments/wrap-line-comment-group.js +39 -0
- package/dist/comments/wrap-trailing-line-comment.d.ts +9 -0
- package/dist/comments/wrap-trailing-line-comment.js +78 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/plugin/create-parsers.d.ts +2 -0
- package/dist/plugin/create-parsers.js +57 -0
- package/dist/plugin/create-printers.d.ts +2 -0
- package/dist/plugin/{printers.js → create-printers.js} +9 -10
- package/dist/plugin/get-printer-layout-source.d.ts +4 -0
- package/dist/plugin/get-printer-layout-source.js +27 -0
- package/dist/plugin/jsx-comment-rewrite-metadata.d.ts +8 -0
- package/dist/plugin/jsx-comment-rewrite-metadata.js +40 -0
- package/dist/plugin/parser-names.d.ts +2 -0
- package/dist/plugin/parser-names.js +1 -0
- package/dist/utils/ast.d.ts +6 -0
- package/dist/utils/ast.js +52 -0
- package/dist/utils/display-width.d.ts +2 -0
- package/dist/utils/display-width.js +19 -0
- package/dist/{shared/markdown.d.ts → utils/format-markdown.d.ts} +1 -1
- package/dist/{shared/markdown.js → utils/format-markdown.js} +12 -2
- package/dist/utils/indentation.d.ts +4 -0
- package/dist/utils/indentation.js +21 -0
- package/dist/utils/replacements.d.ts +6 -0
- package/dist/utils/replacements.js +21 -0
- package/dist/utils/source-lines.d.ts +6 -0
- package/dist/utils/source-lines.js +40 -0
- package/dist/utils/type-guards.d.ts +2 -0
- package/dist/utils/type-guards.js +6 -0
- package/dist/utils/whitespace.d.ts +2 -0
- package/dist/utils/whitespace.js +22 -0
- package/dist/{shared/options.d.ts → utils/wrap-options.d.ts} +2 -1
- package/package.json +17 -9
- package/dist/comments/core.d.ts +0 -8
- package/dist/comments/core.js +0 -114
- package/dist/comments/line.d.ts +0 -6
- package/dist/comments/line.js +0 -108
- package/dist/comments/wrap.d.ts +0 -3
- package/dist/comments/wrap.js +0 -375
- package/dist/plugin/parsers.d.ts +0 -2
- package/dist/plugin/parsers.js +0 -35
- package/dist/plugin/printers.d.ts +0 -2
- package/dist/shared/text.d.ts +0 -15
- package/dist/shared/text.js +0 -99
- package/dist/shared/types.d.ts +0 -30
- package/dist/shared/types.js +0 -1
- /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
|
|
9
|
-
- Prettier 3
|
|
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
|
|
82
|
-
JSX comment. A
|
|
83
|
-
expression comment like `{
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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`
|
|
143
|
-
`npm run
|
|
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,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
|
+
}
|