@aforemendude/prettier-plugin-wrap-comments 1.0.6 → 1.1.1
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 +85 -8
- 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 +52 -0
- package/dist/comments/comment-eligibility.d.ts +5 -0
- package/dist/comments/comment-eligibility.js +23 -0
- package/dist/comments/comment-location.d.ts +9 -0
- package/dist/comments/comment-location.js +39 -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 +12 -0
- package/dist/comments/embedded-expression-ranges.js +117 -0
- package/dist/comments/jsx-expression-layout.d.ts +10 -0
- package/dist/comments/jsx-expression-layout.js +114 -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 +9 -0
- package/dist/comments/prettier-ignore.js +239 -0
- package/dist/comments/printer-layout.d.ts +18 -0
- package/dist/comments/printer-layout.js +65 -0
- package/dist/comments/{block.d.ts → wrap-block-comment.d.ts} +5 -1
- package/dist/comments/{block.js → wrap-block-comment.js} +18 -15
- package/dist/comments/wrap-comments.d.ts +9 -0
- package/dist/comments/wrap-comments.js +169 -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 +60 -0
- package/dist/plugin/create-printers.d.ts +2 -0
- package/dist/plugin/create-printers.js +59 -0
- package/dist/plugin/get-printer-layout-source.d.ts +4 -0
- package/dist/plugin/get-printer-layout-source.js +35 -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 +12 -0
- package/dist/utils/ast.js +95 -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/utils/format-markdown.js +69 -0
- 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 +7 -0
- package/dist/utils/source-lines.js +43 -0
- package/dist/utils/type-guards.d.ts +2 -0
- package/dist/utils/type-guards.js +6 -0
- package/dist/utils/whitespace.d.ts +3 -0
- package/dist/utils/whitespace.js +26 -0
- package/dist/{shared/options.d.ts → utils/wrap-options.d.ts} +2 -1
- package/package.json +27 -11
- 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 -439
- 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/plugin/printers.js +0 -39
- package/dist/shared/markdown.js +0 -20
- 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,9 @@ real column to calculate the available content width, so nested comments wrap mo
|
|
|
5
5
|
|
|
6
6
|
## Requirements
|
|
7
7
|
|
|
8
|
-
- Node.js
|
|
9
|
-
-
|
|
8
|
+
- Node.js 20 or newer to use the plugin
|
|
9
|
+
- Node.js 22.12 or newer to develop the plugin
|
|
10
|
+
- Prettier 3 (`>=3.0.0 <4.0.0`)
|
|
10
11
|
|
|
11
12
|
## Install
|
|
12
13
|
|
|
@@ -30,6 +31,31 @@ Then run Prettier normally:
|
|
|
30
31
|
npx prettier --write .
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
### Cache Repeated CLI Runs
|
|
35
|
+
|
|
36
|
+
Prettier's CLI cache skips files that have not changed since a successful formatting pass. Enable it in package scripts
|
|
37
|
+
so contributors use it consistently. This repository uses the faster `metadata` strategy for local formatting:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"scripts": {
|
|
42
|
+
"format": "prettier --write --cache --cache-strategy metadata .",
|
|
43
|
+
"format:check": "prettier --check --cache --cache-strategy metadata .",
|
|
44
|
+
"format:nocache": "prettier --write ."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The first cached run still processes every file; later runs skip files whose relevant metadata and other cache keys have
|
|
50
|
+
not changed. Omit `--cache-strategy metadata` to use the default `content` strategy when workflows such as Git
|
|
51
|
+
operations frequently change timestamps without changing file contents.
|
|
52
|
+
|
|
53
|
+
By default, Prettier stores the cache under `node_modules/.cache/prettier/`, which is normally excluded from version
|
|
54
|
+
control with `node_modules`. Prettier does not include plugin versions or implementations in its cache keys, so run the
|
|
55
|
+
uncached command once after updating this plugin or another Prettier plugin. Running Prettier without `--cache`, as the
|
|
56
|
+
`format:nocache` script does, also removes the default cache. See [Prettier's CLI cache documentation][prettier-cache]
|
|
57
|
+
for cache keys, strategies, and custom cache locations.
|
|
58
|
+
|
|
33
59
|
## Behavior
|
|
34
60
|
|
|
35
61
|
The plugin wraps comments for Prettier's `babel`, `babel-ts`, and `typescript` parsers. It runs during parser
|
|
@@ -37,9 +63,15 @@ preprocessing: the underlying Prettier parser preprocesses and parses the source
|
|
|
37
63
|
comments from that parsed comment list, and Prettier then formats the rewritten source with its built-in JavaScript and
|
|
38
64
|
TypeScript printers. If the parser cannot parse the preprocessed source, the plugin leaves the source unchanged.
|
|
39
65
|
|
|
66
|
+
Offset-sensitive formatting is conservative. Full-file `formatWithCursor` calls skip comment rewriting so Prettier can
|
|
67
|
+
map the cursor from the original source. During range formatting, preprocessing does not rewrite text outside Prettier's
|
|
68
|
+
selected range; eligible comments inside that range can still be wrapped.
|
|
69
|
+
|
|
40
70
|
Comment text is normalized and reflowed with Prettier's Markdown parser. The available content width is based on
|
|
41
71
|
Prettier's `printWidth` minus the column where the comment text starts. `tabWidth`, `useTabs`, and `endOfLine` are used
|
|
42
|
-
when measuring and rebuilding comments.
|
|
72
|
+
when measuring and rebuilding comments. When `tabWidth` is `0`, tabs contribute zero columns during measurement, as they
|
|
73
|
+
do in Prettier. If the plugin must synthesize indentation at a positive column, it uses spaces because zero-width tabs
|
|
74
|
+
cannot advance to that column.
|
|
43
75
|
|
|
44
76
|
Standalone `//` comments are wrapped in place. Adjacent standalone line comments are combined and reflowed as one
|
|
45
77
|
Markdown block when they are directly next to each other and their `//` markers start in the same column.
|
|
@@ -53,7 +85,11 @@ function example() {
|
|
|
53
85
|
```
|
|
54
86
|
|
|
55
87
|
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.
|
|
88
|
+
the comment is moved above the code and normally wrapped using the code line's indentation. On a line made up of closing
|
|
89
|
+
delimiters such as `}`, `]`, or `)`, the moved comment is indented one additional level so it remains visually inside
|
|
90
|
+
the construct being closed. When a comment directly trails the root value of a JSX expression or template interpolation,
|
|
91
|
+
it can move above that value and wrap inside the expression delimiters. Nested or otherwise ambiguous expression
|
|
92
|
+
comments and comments in JSX spreads stay in place.
|
|
57
93
|
|
|
58
94
|
```ts
|
|
59
95
|
function example() {
|
|
@@ -138,10 +174,13 @@ The plugin leaves these comments unchanged:
|
|
|
138
174
|
- JSDoc comments that start with `/**`
|
|
139
175
|
- bang-preserved comments that start with `/*!` or `//!`
|
|
140
176
|
- TypeScript-style triple-slash line comments that start with `///`
|
|
177
|
+
- Flow type annotations and includes that start with `/*:`, `/*::`, or `/*flow-include`, including Flow's supported
|
|
178
|
+
spaces or tabs before the marker
|
|
141
179
|
- empty comment bodies
|
|
142
180
|
- `prettier-ignore` markers themselves
|
|
143
|
-
- other directive comments such as `@license`, `@preserve`, JSX and
|
|
144
|
-
`#__PURE__`, `@__PURE__`,
|
|
181
|
+
- other directive comments such as `@license`, `@preserve`, JSX, TypeScript, and Flow pragmas, Flow error suppressions,
|
|
182
|
+
`flowlint` comments, source map directives, `#__PURE__`, `@__PURE__`, exact Node test coverage controls, other
|
|
183
|
+
lint/coverage/formatter directives, `vite-ignore`, and webpack magic comments
|
|
145
184
|
|
|
146
185
|
## Supported Parsers
|
|
147
186
|
|
|
@@ -149,13 +188,51 @@ The plugin leaves these comments unchanged:
|
|
|
149
188
|
- `babel-ts`
|
|
150
189
|
- `typescript`
|
|
151
190
|
|
|
191
|
+
## Performance
|
|
192
|
+
|
|
193
|
+
`npm run benchmark` compares uncached, in-memory `prettier.format()` calls with and without the plugin across five
|
|
194
|
+
generated files. Lower times are better. The plugin/plain column divides the plugin mean by the plain Prettier mean.
|
|
195
|
+
|
|
196
|
+
These results are from a representative run on August 8, 2026, using Linux 6.8, an Intel Core i7-10750H, Node.js
|
|
197
|
+
24.18.0, Prettier 3.9.6, and Vitest 4.1.10. The suite was configured with `time: 500`, `iterations: 10`,
|
|
198
|
+
`warmupTime: 100`, and `warmupIterations: 2` for each case.
|
|
199
|
+
|
|
200
|
+
| Generated workload | Characters | Plain Prettier mean | Plugin mean | Plugin/plain |
|
|
201
|
+
| ------------------------------------------ | ---------: | ------------------: | -----------------: | -----------: |
|
|
202
|
+
| Comment-free JavaScript | 29,995 | 54.78 ms (±10.09%) | 45.59 ms (±5.82%) | 0.83× |
|
|
203
|
+
| Code-heavy JavaScript with sparse comments | 30,494 | 44.05 ms (±3.22%) | 135.66 ms (±3.81%) | 3.08× |
|
|
204
|
+
| Comment-heavy TypeScript | 64,432 | 56.75 ms (±11.82%) | 327.61 ms (±2.67%) | 5.77× |
|
|
205
|
+
| Preformatted comment-heavy TypeScript | 54,467 | 54.54 ms (±2.47%) | 279.65 ms (±2.50%) | 5.13× |
|
|
206
|
+
| JSX-comment-heavy TSX | 40,575 | 57.52 ms (±6.87%) | 263.67 ms (±3.83%) | 4.58× |
|
|
207
|
+
|
|
208
|
+
The comment-free case takes the plugin's early exit after scanning for comment delimiters. Its apparent speedup should
|
|
209
|
+
be treated as benchmark variation, not as an expected optimization over plain Prettier. In this run, sparse comments
|
|
210
|
+
took about 3.08× the plain formatting time, while comment-heavy inputs took 4.58–5.77×. These measurements isolate
|
|
211
|
+
formatter work on changed files; enabling the CLI cache above skips that work for unchanged files.
|
|
212
|
+
|
|
152
213
|
## Development
|
|
153
214
|
|
|
215
|
+
Source files are organized by responsibility. `src/plugin/` contains parser and printer integration,
|
|
216
|
+
`src/comments/wrap-comments.ts` coordinates the rewriting pipeline, the other `src/comments/` modules isolate comment
|
|
217
|
+
analysis and wrapping concerns, and `src/utils/` contains reusable utilities. Unit tests under `test/unit/` mirror those
|
|
218
|
+
concerns and source file names wherever practical.
|
|
219
|
+
|
|
154
220
|
```sh
|
|
155
221
|
npm install
|
|
222
|
+
npm run benchmark
|
|
156
223
|
npm run format:check
|
|
224
|
+
npm run typecheck
|
|
157
225
|
npm run test
|
|
226
|
+
npm run build
|
|
158
227
|
```
|
|
159
228
|
|
|
160
|
-
`npm run test`
|
|
161
|
-
`npm run
|
|
229
|
+
`npm run test` runs the TypeScript unit and fixture-based integration suites with Vitest. Use `npm run test:unit` or
|
|
230
|
+
`npm run test:integration` to run one suite. `npm run build` removes and recreates `dist` using a cross-platform Node
|
|
231
|
+
cleanup script, and `npm run verify` runs formatting, type checking, the build, and both test suites.
|
|
232
|
+
|
|
233
|
+
`npm run benchmark` compares plain Prettier with Prettier using the plugin. Its JavaScript, TypeScript, and TSX inputs
|
|
234
|
+
are generated in memory by the files under `test/benchmark`, so large benchmark fixtures are not stored in the
|
|
235
|
+
repository. Benchmarks use Vitest's separate benchmark mode and do not run as part of `npm run test` or
|
|
236
|
+
`npm run verify`.
|
|
237
|
+
|
|
238
|
+
[prettier-cache]: https://prettier.io/docs/cli#--cache
|
|
@@ -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, normalizeLineTerminators } 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 = normalizeLineTerminators(rawComment.slice(2, -2));
|
|
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,52 @@
|
|
|
1
|
+
const PRAGMA_DIRECTIVE_COMMENT_PATTERNS = [
|
|
2
|
+
/^@(?:flow|noflow)\b/u,
|
|
3
|
+
/^@(?:license|preserve)\b/u,
|
|
4
|
+
/^@(?:jsxFrag|jsxImportSource|jsxRuntime|jsx)\b/u,
|
|
5
|
+
/^@(?:ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b/u,
|
|
6
|
+
/^[@#]__(?:NO_SIDE_EFFECTS|PURE)__\b/u,
|
|
7
|
+
];
|
|
8
|
+
const SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS = [
|
|
9
|
+
/^[#@][ \t]*sourceMappingURL=/u,
|
|
10
|
+
/^[#@][ \t]*sourceURL=/u,
|
|
11
|
+
/^sourceMappingURL=/u,
|
|
12
|
+
/^sourceURL=/u,
|
|
13
|
+
];
|
|
14
|
+
const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
|
|
15
|
+
/^biome-ignore\b/u,
|
|
16
|
+
/^c8\b/u,
|
|
17
|
+
/^deno-lint-ignore\b/u,
|
|
18
|
+
/^eslint\b/u,
|
|
19
|
+
/^eslint-/u,
|
|
20
|
+
/^exported\b/u,
|
|
21
|
+
/^flowlint(?:-next-line|-line)?\b/u,
|
|
22
|
+
/^globals?\b/u,
|
|
23
|
+
/^istanbul\b/u,
|
|
24
|
+
/^jshint\b/u,
|
|
25
|
+
/^node:coverage (?:disable|enable|ignore next(?: [1-9]\d*)?)$/u,
|
|
26
|
+
/^nyc\b/u,
|
|
27
|
+
/^oxlint\b/u,
|
|
28
|
+
/^prettier-ignore\b/u,
|
|
29
|
+
/^prettier-ignore-start\b/u,
|
|
30
|
+
/^prettier-ignore-end\b/u,
|
|
31
|
+
/^stylelint\b/u,
|
|
32
|
+
/^tslint\b/u,
|
|
33
|
+
/^v8\b/u,
|
|
34
|
+
/^vite-ignore\b/u,
|
|
35
|
+
/^\$(?:FlowExpectedError|FlowFixMe)\b/u,
|
|
36
|
+
];
|
|
37
|
+
const BUNDLER_DIRECTIVE_COMMENT_PATTERNS = [
|
|
38
|
+
/^webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b/u,
|
|
39
|
+
];
|
|
40
|
+
const DIRECTIVE_COMMENT_PATTERNS = [
|
|
41
|
+
...PRAGMA_DIRECTIVE_COMMENT_PATTERNS,
|
|
42
|
+
...SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS,
|
|
43
|
+
...TOOL_DIRECTIVE_COMMENT_PATTERNS,
|
|
44
|
+
...BUNDLER_DIRECTIVE_COMMENT_PATTERNS,
|
|
45
|
+
];
|
|
46
|
+
export function isDirectiveComment(body) {
|
|
47
|
+
const normalizedBody = body.trimStart();
|
|
48
|
+
return DIRECTIVE_COMMENT_PATTERNS.some((pattern) => pattern.test(normalizedBody));
|
|
49
|
+
}
|
|
50
|
+
export function isPrettierIgnoreComment(body) {
|
|
51
|
+
return body.trim() === 'prettier-ignore';
|
|
52
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
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;
|
|
5
|
+
export declare function hasFlowCommentTypeMarker(rawComment: string): boolean;
|
|
@@ -0,0 +1,23 @@
|
|
|
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) || hasFlowCommentTypeMarker(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
|
+
}
|
|
21
|
+
export function hasFlowCommentTypeMarker(rawComment) {
|
|
22
|
+
return /^\/\*[ \t]*(?::|flow-include)/u.test(rawComment);
|
|
23
|
+
}
|
|
@@ -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,39 @@
|
|
|
1
|
+
import { getLineEnd, getLinePrefix, getLineStart } from '../utils/source-lines.js';
|
|
2
|
+
import { isEcmaScriptHorizontalWhitespace } from '../utils/whitespace.js';
|
|
3
|
+
export function isStandaloneComment(text, comment) {
|
|
4
|
+
return comment.kind === 'line' ? isStandaloneLineComment(text, comment) : isStandaloneBlockComment(text, comment);
|
|
5
|
+
}
|
|
6
|
+
export function isStandaloneLineComment(text, comment) {
|
|
7
|
+
return isOnlyHorizontalWhitespace(getLinePrefix(text, comment.start));
|
|
8
|
+
}
|
|
9
|
+
export function isStandaloneBlockComment(text, comment) {
|
|
10
|
+
const before = text.slice(getLineStart(text, comment.start), comment.start);
|
|
11
|
+
const after = text.slice(comment.end, getLineEnd(text, comment.end));
|
|
12
|
+
return isOnlyHorizontalWhitespace(before) && isOnlyHorizontalWhitespace(after);
|
|
13
|
+
}
|
|
14
|
+
export function areCommentsOnAdjacentLines(text, previousComment, comment) {
|
|
15
|
+
return isOnlyHorizontalWhitespaceAroundNewline(text.slice(previousComment.end, comment.start));
|
|
16
|
+
}
|
|
17
|
+
export function isCommentAdjacentBeforeIndex(text, comment, index) {
|
|
18
|
+
return isOnlyHorizontalWhitespaceAroundNewline(text.slice(comment.end, index));
|
|
19
|
+
}
|
|
20
|
+
function isOnlyHorizontalWhitespaceAroundNewline(text) {
|
|
21
|
+
let newlineStart = 0;
|
|
22
|
+
while (newlineStart < text.length) {
|
|
23
|
+
const character = text[newlineStart];
|
|
24
|
+
if (character === undefined || !isEcmaScriptHorizontalWhitespace(character)) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
newlineStart += 1;
|
|
28
|
+
}
|
|
29
|
+
const newline = /^(?:\r\n|[\n\r\u2028\u2029])/u.exec(text.slice(newlineStart))?.[0];
|
|
30
|
+
return newline !== undefined && isOnlyHorizontalWhitespace(text.slice(newlineStart + newline.length));
|
|
31
|
+
}
|
|
32
|
+
function isOnlyHorizontalWhitespace(text) {
|
|
33
|
+
for (const character of text) {
|
|
34
|
+
if (!isEcmaScriptHorizontalWhitespace(character)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
@@ -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,12 @@
|
|
|
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 getEmbeddedTrailingLineCommentMove(text: string, comment: CommentRange, range: EmbeddedExpressionRange | undefined): EmbeddedTrailingLineCommentMove | undefined;
|
|
12
|
+
export declare function doesBlockCommentSeparateEmbeddedTrailingLineComment(text: string, blockComment: CommentRange, nextComment: CommentRange | undefined, range: EmbeddedExpressionRange | undefined, nextRange: EmbeddedExpressionRange | undefined): boolean;
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
import { skipWhitespace, trimWhitespaceEnd } from '../utils/whitespace.js';
|
|
5
|
+
const JSX_EMBEDDED_EXPRESSION_TYPES = new Set(['JSXExpressionContainer', 'JSXSpreadAttribute', 'JSXSpreadChild']);
|
|
6
|
+
export function collectEmbeddedExpressionRanges(ast) {
|
|
7
|
+
const ranges = [];
|
|
8
|
+
visitAstNodes(ast, (node) => {
|
|
9
|
+
const type = node['type'];
|
|
10
|
+
if (typeof type !== 'string') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (JSX_EMBEDDED_EXPRESSION_TYPES.has(type)) {
|
|
14
|
+
const range = getAstNodeRange(node);
|
|
15
|
+
if (range !== undefined) {
|
|
16
|
+
const expressionNode = type === 'JSXExpressionContainer' ? node['expression'] : undefined;
|
|
17
|
+
const expression = isRecord(expressionNode) ? getAstNodeRange(expressionNode) : undefined;
|
|
18
|
+
ranges.push(expression === undefined ? range : { ...range, expression });
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (type === 'TemplateLiteral') {
|
|
23
|
+
collectTemplateInterpolationRanges(node, 'expressions', ranges);
|
|
24
|
+
}
|
|
25
|
+
else if (type === 'TSTemplateLiteralType') {
|
|
26
|
+
collectTemplateInterpolationRanges(node, 'types', ranges);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
30
|
+
}
|
|
31
|
+
export function getEmbeddedTrailingLineCommentMove(text, comment, range) {
|
|
32
|
+
const expression = range?.expression;
|
|
33
|
+
if (range === undefined || expression === undefined) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const rootExpression = getRootExpressionRangeBefore(text, expression, comment.start, range.start);
|
|
37
|
+
return rootExpression === undefined ? undefined : { insertAt: rootExpression.start, removeStart: rootExpression.end };
|
|
38
|
+
}
|
|
39
|
+
export function doesBlockCommentSeparateEmbeddedTrailingLineComment(text, blockComment, nextComment, range, nextRange) {
|
|
40
|
+
if (blockComment.kind !== 'block' ||
|
|
41
|
+
nextComment?.kind !== 'line' ||
|
|
42
|
+
blockComment.end >= nextComment.start ||
|
|
43
|
+
nextComment.end > getLineEnd(text, blockComment.start)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const expression = range?.expression;
|
|
47
|
+
const rootExpression = range === undefined || expression === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: getRootExpressionRangeBefore(text, expression, blockComment.start, range.start);
|
|
50
|
+
return (range !== undefined &&
|
|
51
|
+
range === nextRange &&
|
|
52
|
+
rootExpression !== undefined &&
|
|
53
|
+
/^[\t ]*$/u.test(text.slice(blockComment.end, nextComment.start)));
|
|
54
|
+
}
|
|
55
|
+
function getRootExpressionRangeBefore(text, expression, before, containerStart) {
|
|
56
|
+
if (expression.end > before) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
let cursor = expression.end;
|
|
60
|
+
let expandedEnd = expression.end;
|
|
61
|
+
let parenthesisCount = 0;
|
|
62
|
+
while (cursor < before) {
|
|
63
|
+
cursor = skipWhitespace(text, cursor);
|
|
64
|
+
if (cursor >= before) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (text[cursor] !== ')') {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
cursor += 1;
|
|
71
|
+
expandedEnd = cursor;
|
|
72
|
+
parenthesisCount += 1;
|
|
73
|
+
}
|
|
74
|
+
let expandedStart = expression.start;
|
|
75
|
+
for (let index = 0; index < parenthesisCount; index += 1) {
|
|
76
|
+
expandedStart = trimWhitespaceEnd(text, containerStart, expandedStart);
|
|
77
|
+
if (expandedStart <= containerStart || text[expandedStart - 1] !== '(') {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
expandedStart -= 1;
|
|
81
|
+
}
|
|
82
|
+
return { end: expandedEnd, start: expandedStart };
|
|
83
|
+
}
|
|
84
|
+
function collectTemplateInterpolationRanges(node, expressionsKey, ranges) {
|
|
85
|
+
const quasis = node['quasis'];
|
|
86
|
+
const expressions = node[expressionsKey];
|
|
87
|
+
if (!Array.isArray(quasis) || !Array.isArray(expressions) || quasis.length !== expressions.length + 1) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (let index = 0; index < expressions.length; index += 1) {
|
|
91
|
+
const precedingQuasi = getNodeBoundary(quasis[index]);
|
|
92
|
+
const followingQuasi = getNodeBoundary(quasis[index + 1]);
|
|
93
|
+
const expression = getExpressionRange(expressions[index]);
|
|
94
|
+
if (precedingQuasi === undefined ||
|
|
95
|
+
followingQuasi === undefined ||
|
|
96
|
+
expression === undefined ||
|
|
97
|
+
precedingQuasi.end >= followingQuasi.start) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
ranges.push({ end: followingQuasi.start, expression, start: precedingQuasi.end });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function getExpressionRange(value) {
|
|
104
|
+
return isRecord(value) ? getAstNodeRange(value) : undefined;
|
|
105
|
+
}
|
|
106
|
+
function getNodeBoundary(value) {
|
|
107
|
+
if (!isRecord(value)) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const range = Array.isArray(value['range']) ? value['range'] : undefined;
|
|
111
|
+
const start = numberOrUndefined(value['start']) ?? numberOrUndefined(range?.[0]);
|
|
112
|
+
const end = numberOrUndefined(value['end']) ?? numberOrUndefined(range?.[1]);
|
|
113
|
+
if (start === undefined || end === undefined || start > end) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return { end, start };
|
|
117
|
+
}
|
|
@@ -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 { ContainingRangeMatch, 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, containerMatch: ContainingRangeMatch<JsxExpressionContainerRange> | undefined, tabWidth: number, outputCommentLayout: PrinterCommentLayout | undefined, outputCommentMarkerColumns: Array<number | undefined>): BlockCommentLayout | undefined;
|