@aforemendude/prettier-plugin-wrap-comments 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -6
- package/dist/comments/comment-body.js +2 -2
- package/dist/comments/comment-directives.js +4 -0
- package/dist/comments/comment-eligibility.d.ts +1 -0
- package/dist/comments/comment-eligibility.js +4 -1
- package/dist/comments/comment-location.js +24 -6
- package/dist/comments/embedded-expression-ranges.d.ts +2 -3
- package/dist/comments/embedded-expression-ranges.js +39 -27
- package/dist/comments/jsx-expression-layout.d.ts +2 -2
- package/dist/comments/jsx-expression-layout.js +16 -19
- package/dist/comments/prettier-ignore.d.ts +2 -1
- package/dist/comments/prettier-ignore.js +110 -16
- package/dist/comments/printer-layout.d.ts +1 -0
- package/dist/comments/printer-layout.js +1 -0
- package/dist/comments/wrap-block-comment.d.ts +1 -0
- package/dist/comments/wrap-block-comment.js +3 -1
- package/dist/comments/wrap-comments.js +75 -7
- package/dist/comments/wrap-trailing-line-comment.d.ts +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -1
- package/dist/plugin/create-parsers.js +92 -5
- package/dist/plugin/create-printers.js +23 -2
- package/dist/plugin/get-printer-layout-source.js +10 -2
- package/dist/plugin/plugin-name.d.ts +1 -0
- package/dist/plugin/plugin-name.js +1 -0
- package/dist/utils/ast.d.ts +6 -0
- package/dist/utils/ast.js +43 -0
- package/dist/utils/display-width.d.ts +1 -1
- package/dist/utils/display-width.js +3 -3
- package/dist/utils/format-markdown.js +50 -11
- package/dist/utils/source-lines.d.ts +1 -0
- package/dist/utils/source-lines.js +3 -0
- package/dist/utils/whitespace.d.ts +1 -0
- package/dist/utils/whitespace.js +4 -0
- package/package.json +15 -6
package/README.md
CHANGED
|
@@ -5,7 +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 20 or newer
|
|
8
|
+
- Node.js 20 or newer to use the plugin
|
|
9
|
+
- Node.js 22.12 or newer to develop the plugin
|
|
9
10
|
- Prettier 3 (`>=3.0.0 <4.0.0`)
|
|
10
11
|
|
|
11
12
|
## Install
|
|
@@ -30,12 +31,52 @@ Then run Prettier normally:
|
|
|
30
31
|
npx prettier --write .
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
### Use With `prettier-plugin-jsdoc`
|
|
35
|
+
|
|
36
|
+
When using both plugins, `@aforemendude/prettier-plugin-wrap-comments` must be loaded after `prettier-plugin-jsdoc`:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"plugins": ["prettier-plugin-jsdoc", "@aforemendude/prettier-plugin-wrap-comments"]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This order is required regardless of `prettier-plugin-jsdoc`'s documentation saying that it should be the last plugin.
|
|
45
|
+
With wrap-comments last, ordinary comments are handled here and parsing is delegated to the preceding JSDoc plugin, so
|
|
46
|
+
JSDoc comments are formatted as well.
|
|
47
|
+
|
|
48
|
+
### Cache Repeated CLI Runs
|
|
49
|
+
|
|
50
|
+
Prettier's CLI cache skips files that have not changed since a successful formatting pass. Enable it in package scripts
|
|
51
|
+
so contributors use it consistently. This repository uses the faster `metadata` strategy for local formatting:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"scripts": {
|
|
56
|
+
"format": "prettier --write --cache --cache-strategy metadata .",
|
|
57
|
+
"format:check": "prettier --check --cache --cache-strategy metadata .",
|
|
58
|
+
"format:nocache": "prettier --write ."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The first cached run still processes every file; later runs skip files whose relevant metadata and other cache keys have
|
|
64
|
+
not changed. Omit `--cache-strategy metadata` to use the default `content` strategy when workflows such as Git
|
|
65
|
+
operations frequently change timestamps without changing file contents.
|
|
66
|
+
|
|
67
|
+
By default, Prettier stores the cache under `node_modules/.cache/prettier/`, which is normally excluded from version
|
|
68
|
+
control with `node_modules`. Prettier does not include plugin versions or implementations in its cache keys, so run the
|
|
69
|
+
uncached command once after updating this plugin or another Prettier plugin. Running Prettier without `--cache`, as the
|
|
70
|
+
`format:nocache` script does, also removes the default cache. See [Prettier's CLI cache documentation][prettier-cache]
|
|
71
|
+
for cache keys, strategies, and custom cache locations.
|
|
72
|
+
|
|
33
73
|
## Behavior
|
|
34
74
|
|
|
35
75
|
The plugin wraps comments for Prettier's `babel`, `babel-ts`, and `typescript` parsers. It runs during parser
|
|
36
|
-
preprocessing: the
|
|
37
|
-
|
|
38
|
-
|
|
76
|
+
preprocessing: the nearest preceding plugin for the selected parser, or Prettier's built-in parser when there is none,
|
|
77
|
+
preprocesses and parses the source first. This plugin rewrites eligible comments from that parsed comment list, and
|
|
78
|
+
Prettier then formats the rewritten source with its built-in JavaScript and TypeScript printers. If the parser cannot
|
|
79
|
+
parse the preprocessed source, the plugin leaves the source unchanged.
|
|
39
80
|
|
|
40
81
|
Offset-sensitive formatting is conservative. Full-file `formatWithCursor` calls skip comment rewriting so Prettier can
|
|
41
82
|
map the cursor from the original source. During range formatting, preprocessing does not rewrite text outside Prettier's
|
|
@@ -148,10 +189,13 @@ The plugin leaves these comments unchanged:
|
|
|
148
189
|
- JSDoc comments that start with `/**`
|
|
149
190
|
- bang-preserved comments that start with `/*!` or `//!`
|
|
150
191
|
- TypeScript-style triple-slash line comments that start with `///`
|
|
192
|
+
- Flow type annotations and includes that start with `/*:`, `/*::`, or `/*flow-include`, including Flow's supported
|
|
193
|
+
spaces or tabs before the marker
|
|
151
194
|
- empty comment bodies
|
|
152
195
|
- `prettier-ignore` markers themselves
|
|
153
|
-
- other directive comments such as `@license`, `@preserve`, JSX and
|
|
154
|
-
`#__PURE__`, `@__PURE__`,
|
|
196
|
+
- other directive comments such as `@license`, `@preserve`, JSX, TypeScript, and Flow pragmas, Flow error suppressions,
|
|
197
|
+
`flowlint` comments, source map directives, `#__PURE__`, `@__PURE__`, exact Node test coverage controls, other
|
|
198
|
+
lint/coverage/formatter directives, `vite-ignore`, and webpack magic comments
|
|
155
199
|
|
|
156
200
|
## Supported Parsers
|
|
157
201
|
|
|
@@ -159,6 +203,28 @@ The plugin leaves these comments unchanged:
|
|
|
159
203
|
- `babel-ts`
|
|
160
204
|
- `typescript`
|
|
161
205
|
|
|
206
|
+
## Performance
|
|
207
|
+
|
|
208
|
+
`npm run benchmark` compares uncached, in-memory `prettier.format()` calls with and without the plugin across five
|
|
209
|
+
generated files. Lower times are better. The plugin/plain column divides the plugin mean by the plain Prettier mean.
|
|
210
|
+
|
|
211
|
+
These results are from a representative run on August 8, 2026, using Linux 6.8, an Intel Core i7-10750H, Node.js
|
|
212
|
+
24.18.0, Prettier 3.9.6, and Vitest 4.1.10. The suite was configured with `time: 500`, `iterations: 10`,
|
|
213
|
+
`warmupTime: 100`, and `warmupIterations: 2` for each case.
|
|
214
|
+
|
|
215
|
+
| Generated workload | Characters | Plain Prettier mean | Plugin mean | Plugin/plain |
|
|
216
|
+
| ------------------------------------------ | ---------: | ------------------: | -----------------: | -----------: |
|
|
217
|
+
| Comment-free JavaScript | 29,995 | 54.78 ms (±10.09%) | 45.59 ms (±5.82%) | 0.83× |
|
|
218
|
+
| Code-heavy JavaScript with sparse comments | 30,494 | 44.05 ms (±3.22%) | 135.66 ms (±3.81%) | 3.08× |
|
|
219
|
+
| Comment-heavy TypeScript | 64,432 | 56.75 ms (±11.82%) | 327.61 ms (±2.67%) | 5.77× |
|
|
220
|
+
| Preformatted comment-heavy TypeScript | 54,467 | 54.54 ms (±2.47%) | 279.65 ms (±2.50%) | 5.13× |
|
|
221
|
+
| JSX-comment-heavy TSX | 40,575 | 57.52 ms (±6.87%) | 263.67 ms (±3.83%) | 4.58× |
|
|
222
|
+
|
|
223
|
+
The comment-free case takes the plugin's early exit after scanning for comment delimiters. Its apparent speedup should
|
|
224
|
+
be treated as benchmark variation, not as an expected optimization over plain Prettier. In this run, sparse comments
|
|
225
|
+
took about 3.08× the plain formatting time, while comment-heavy inputs took 4.58–5.77×. These measurements isolate
|
|
226
|
+
formatter work on changed files; enabling the CLI cache above skips that work for unchanged files.
|
|
227
|
+
|
|
162
228
|
## Development
|
|
163
229
|
|
|
164
230
|
Source files are organized by responsibility. `src/plugin/` contains parser and printer integration,
|
|
@@ -168,6 +234,7 @@ concerns and source file names wherever practical.
|
|
|
168
234
|
|
|
169
235
|
```sh
|
|
170
236
|
npm install
|
|
237
|
+
npm run benchmark
|
|
171
238
|
npm run format:check
|
|
172
239
|
npm run typecheck
|
|
173
240
|
npm run test
|
|
@@ -177,3 +244,10 @@ npm run build
|
|
|
177
244
|
`npm run test` runs the TypeScript unit and fixture-based integration suites with Vitest. Use `npm run test:unit` or
|
|
178
245
|
`npm run test:integration` to run one suite. `npm run build` removes and recreates `dist` using a cross-platform Node
|
|
179
246
|
cleanup script, and `npm run verify` runs formatting, type checking, the build, and both test suites.
|
|
247
|
+
|
|
248
|
+
`npm run benchmark` compares plain Prettier with Prettier using the plugin. Its JavaScript, TypeScript, and TSX inputs
|
|
249
|
+
are generated in memory by the files under `test/benchmark`, so large benchmark fixtures are not stored in the
|
|
250
|
+
repository. Benchmarks use Vitest's separate benchmark mode and do not run as part of `npm run test` or
|
|
251
|
+
`npm run verify`.
|
|
252
|
+
|
|
253
|
+
[prettier-cache]: https://prettier.io/docs/cli#--cache
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isBlankLine } from '../utils/source-lines.js';
|
|
1
|
+
import { isBlankLine, normalizeLineTerminators } from '../utils/source-lines.js';
|
|
2
2
|
export function getCommentBody(text, comment) {
|
|
3
3
|
const raw = text.slice(comment.start, comment.end);
|
|
4
4
|
return comment.kind === 'line' ? normalizeLineCommentBody(raw.slice(2)) : normalizeBlockCommentBody(raw);
|
|
@@ -10,7 +10,7 @@ export function normalizeLineCommentBody(rawBody) {
|
|
|
10
10
|
return rawBody.replace(/^[ \t]?/, '').replace(/[ \t]+$/u, '');
|
|
11
11
|
}
|
|
12
12
|
export function normalizeBlockCommentBody(rawComment) {
|
|
13
|
-
const body = rawComment.slice(2, -2)
|
|
13
|
+
const body = normalizeLineTerminators(rawComment.slice(2, -2));
|
|
14
14
|
const lines = body.split('\n');
|
|
15
15
|
if (lines.length === 1) {
|
|
16
16
|
return lines[0]?.trim() ?? '';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const PRAGMA_DIRECTIVE_COMMENT_PATTERNS = [
|
|
2
|
+
/^@(?:flow|noflow)\b/u,
|
|
2
3
|
/^@(?:license|preserve)\b/u,
|
|
3
4
|
/^@(?:jsxFrag|jsxImportSource|jsxRuntime|jsx)\b/u,
|
|
4
5
|
/^@(?:ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b/u,
|
|
@@ -17,9 +18,11 @@ const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
|
|
|
17
18
|
/^eslint\b/u,
|
|
18
19
|
/^eslint-/u,
|
|
19
20
|
/^exported\b/u,
|
|
21
|
+
/^flowlint(?:-next-line|-line)?\b/u,
|
|
20
22
|
/^globals?\b/u,
|
|
21
23
|
/^istanbul\b/u,
|
|
22
24
|
/^jshint\b/u,
|
|
25
|
+
/^node:coverage (?:disable|enable|ignore next(?: [1-9]\d*)?)$/u,
|
|
23
26
|
/^nyc\b/u,
|
|
24
27
|
/^oxlint\b/u,
|
|
25
28
|
/^prettier-ignore\b/u,
|
|
@@ -29,6 +32,7 @@ const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
|
|
|
29
32
|
/^tslint\b/u,
|
|
30
33
|
/^v8\b/u,
|
|
31
34
|
/^vite-ignore\b/u,
|
|
35
|
+
/^\$(?:FlowExpectedError|FlowFixMe)\b/u,
|
|
32
36
|
];
|
|
33
37
|
const BUNDLER_DIRECTIVE_COMMENT_PATTERNS = [
|
|
34
38
|
/^webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b/u,
|
|
@@ -2,3 +2,4 @@ import type { CommentRange } from './comment-ranges.js';
|
|
|
2
2
|
export declare function shouldSkipLineComment(text: string, comment: CommentRange): boolean;
|
|
3
3
|
export declare function shouldSkipBlockComment(text: string, comment: CommentRange): boolean;
|
|
4
4
|
export declare function hasPreserveCommentMarker(rawComment: string): boolean;
|
|
5
|
+
export declare function hasFlowCommentTypeMarker(rawComment: string): boolean;
|
|
@@ -9,7 +9,7 @@ export function shouldSkipLineComment(text, comment) {
|
|
|
9
9
|
}
|
|
10
10
|
export function shouldSkipBlockComment(text, comment) {
|
|
11
11
|
const raw = text.slice(comment.start, comment.end);
|
|
12
|
-
if (raw.startsWith('/**') || hasPreserveCommentMarker(raw)) {
|
|
12
|
+
if (raw.startsWith('/**') || hasPreserveCommentMarker(raw) || hasFlowCommentTypeMarker(raw)) {
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
15
|
const body = normalizeBlockCommentBody(raw);
|
|
@@ -18,3 +18,6 @@ export function shouldSkipBlockComment(text, comment) {
|
|
|
18
18
|
export function hasPreserveCommentMarker(rawComment) {
|
|
19
19
|
return rawComment.startsWith('/*!') || rawComment.startsWith('//!');
|
|
20
20
|
}
|
|
21
|
+
export function hasFlowCommentTypeMarker(rawComment) {
|
|
22
|
+
return /^\/\*[ \t]*(?::|flow-include)/u.test(rawComment);
|
|
23
|
+
}
|
|
@@ -1,21 +1,39 @@
|
|
|
1
1
|
import { getLineEnd, getLinePrefix, getLineStart } from '../utils/source-lines.js';
|
|
2
|
+
import { isEcmaScriptHorizontalWhitespace } from '../utils/whitespace.js';
|
|
2
3
|
export function isStandaloneComment(text, comment) {
|
|
3
4
|
return comment.kind === 'line' ? isStandaloneLineComment(text, comment) : isStandaloneBlockComment(text, comment);
|
|
4
5
|
}
|
|
5
6
|
export function isStandaloneLineComment(text, comment) {
|
|
6
|
-
return
|
|
7
|
+
return isOnlyHorizontalWhitespace(getLinePrefix(text, comment.start));
|
|
7
8
|
}
|
|
8
9
|
export function isStandaloneBlockComment(text, comment) {
|
|
9
10
|
const before = text.slice(getLineStart(text, comment.start), comment.start);
|
|
10
11
|
const after = text.slice(comment.end, getLineEnd(text, comment.end));
|
|
11
|
-
return
|
|
12
|
+
return isOnlyHorizontalWhitespace(before) && isOnlyHorizontalWhitespace(after);
|
|
12
13
|
}
|
|
13
14
|
export function areCommentsOnAdjacentLines(text, previousComment, comment) {
|
|
14
|
-
return
|
|
15
|
+
return isOnlyHorizontalWhitespaceAroundNewline(text.slice(previousComment.end, comment.start));
|
|
15
16
|
}
|
|
16
17
|
export function isCommentAdjacentBeforeIndex(text, comment, index) {
|
|
17
|
-
return
|
|
18
|
+
return isOnlyHorizontalWhitespaceAroundNewline(text.slice(comment.end, index));
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
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;
|
|
21
39
|
}
|
|
@@ -8,6 +8,5 @@ export type EmbeddedTrailingLineCommentMove = {
|
|
|
8
8
|
removeStart: number;
|
|
9
9
|
};
|
|
10
10
|
export declare function collectEmbeddedExpressionRanges(ast: unknown): EmbeddedExpressionRange[];
|
|
11
|
-
export declare function
|
|
12
|
-
export declare function
|
|
13
|
-
export declare function doesBlockCommentSeparateEmbeddedTrailingLineComment(text: string, blockComment: CommentRange, nextComment: CommentRange | undefined, ranges: EmbeddedExpressionRange[]): boolean;
|
|
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;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getAstNodeRange, visitAstNodes } from '../utils/ast.js';
|
|
2
2
|
import { getLineEnd } from '../utils/source-lines.js';
|
|
3
3
|
import { isRecord, numberOrUndefined } from '../utils/type-guards.js';
|
|
4
|
+
import { skipWhitespace, trimWhitespaceEnd } from '../utils/whitespace.js';
|
|
4
5
|
const JSX_EMBEDDED_EXPRESSION_TYPES = new Set(['JSXExpressionContainer', 'JSXSpreadAttribute', 'JSXSpreadChild']);
|
|
5
6
|
export function collectEmbeddedExpressionRanges(ast) {
|
|
6
7
|
const ranges = [];
|
|
@@ -27,36 +28,59 @@ export function collectEmbeddedExpressionRanges(ast) {
|
|
|
27
28
|
});
|
|
28
29
|
return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
29
30
|
}
|
|
30
|
-
export function
|
|
31
|
-
return getSmallestContainingRange(comment, ranges) !== undefined;
|
|
32
|
-
}
|
|
33
|
-
export function getEmbeddedTrailingLineCommentMove(text, comment, ranges) {
|
|
34
|
-
const range = getSmallestContainingRange(comment, ranges);
|
|
31
|
+
export function getEmbeddedTrailingLineCommentMove(text, comment, range) {
|
|
35
32
|
const expression = range?.expression;
|
|
36
|
-
if (expression === undefined
|
|
37
|
-
expression.end > comment.start ||
|
|
38
|
-
!/^[\t ]*$/u.test(text.slice(expression.end, comment.start))) {
|
|
33
|
+
if (range === undefined || expression === undefined) {
|
|
39
34
|
return undefined;
|
|
40
35
|
}
|
|
41
|
-
|
|
36
|
+
const rootExpression = getRootExpressionRangeBefore(text, expression, comment.start, range.start);
|
|
37
|
+
return rootExpression === undefined ? undefined : { insertAt: rootExpression.start, removeStart: rootExpression.end };
|
|
42
38
|
}
|
|
43
|
-
export function doesBlockCommentSeparateEmbeddedTrailingLineComment(text, blockComment, nextComment,
|
|
39
|
+
export function doesBlockCommentSeparateEmbeddedTrailingLineComment(text, blockComment, nextComment, range, nextRange) {
|
|
44
40
|
if (blockComment.kind !== 'block' ||
|
|
45
41
|
nextComment?.kind !== 'line' ||
|
|
46
42
|
blockComment.end >= nextComment.start ||
|
|
47
43
|
nextComment.end > getLineEnd(text, blockComment.start)) {
|
|
48
44
|
return false;
|
|
49
45
|
}
|
|
50
|
-
const range = getSmallestContainingRange(blockComment, ranges);
|
|
51
|
-
const nextRange = getSmallestContainingRange(nextComment, ranges);
|
|
52
46
|
const expression = range?.expression;
|
|
47
|
+
const rootExpression = range === undefined || expression === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: getRootExpressionRangeBefore(text, expression, blockComment.start, range.start);
|
|
53
50
|
return (range !== undefined &&
|
|
54
51
|
range === nextRange &&
|
|
55
|
-
|
|
56
|
-
expression.end <= blockComment.start &&
|
|
57
|
-
/^[\t ]*$/u.test(text.slice(expression.end, blockComment.start)) &&
|
|
52
|
+
rootExpression !== undefined &&
|
|
58
53
|
/^[\t ]*$/u.test(text.slice(blockComment.end, nextComment.start)));
|
|
59
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
|
+
}
|
|
60
84
|
function collectTemplateInterpolationRanges(node, expressionsKey, ranges) {
|
|
61
85
|
const quasis = node['quasis'];
|
|
62
86
|
const expressions = node[expressionsKey];
|
|
@@ -91,15 +115,3 @@ function getNodeBoundary(value) {
|
|
|
91
115
|
}
|
|
92
116
|
return { end, start };
|
|
93
117
|
}
|
|
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
|
-
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { CommentRange } from './comment-ranges.js';
|
|
2
2
|
import type { PrinterCommentLayout } from './printer-layout.js';
|
|
3
3
|
import type { BlockCommentLayout } from './wrap-block-comment.js';
|
|
4
|
-
import type { SourceRange } from '../utils/ast.js';
|
|
4
|
+
import type { ContainingRangeMatch, SourceRange } from '../utils/ast.js';
|
|
5
5
|
export type JsxExpressionContainerRange = SourceRange & {
|
|
6
6
|
expression: SourceRange | undefined;
|
|
7
7
|
};
|
|
8
8
|
export declare function collectJsxExpressionContainerRanges(ast: unknown): JsxExpressionContainerRange[];
|
|
9
9
|
export declare function getPrintedJsxCommentMarkerColumn(text: string, container: SourceRange, tabWidth: number): number;
|
|
10
|
-
export declare function getJsxExpressionBlockCommentLayout(text: string, comment: CommentRange, previousComment: CommentRange | undefined,
|
|
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;
|
|
@@ -2,7 +2,7 @@ import { isStandaloneBlockComment } from './comment-location.js';
|
|
|
2
2
|
import { getAstNodeRange, visitAstNodes } from '../utils/ast.js';
|
|
3
3
|
import { getColumns } from '../utils/display-width.js';
|
|
4
4
|
import { getLeadingIndent } from '../utils/indentation.js';
|
|
5
|
-
import { getLinePrefix } from '../utils/source-lines.js';
|
|
5
|
+
import { getLinePrefix, getLineStart } from '../utils/source-lines.js';
|
|
6
6
|
import { isRecord } from '../utils/type-guards.js';
|
|
7
7
|
import { skipWhitespace, trimWhitespaceEnd } from '../utils/whitespace.js';
|
|
8
8
|
export function collectJsxExpressionContainerRanges(ast) {
|
|
@@ -28,15 +28,17 @@ export function getPrintedJsxCommentMarkerColumn(text, container, tabWidth) {
|
|
|
28
28
|
const lineIndent = getLeadingIndent(linePrefix);
|
|
29
29
|
return getColumns(lineIndent, tabWidth) + tabWidth;
|
|
30
30
|
}
|
|
31
|
-
export function getJsxExpressionBlockCommentLayout(text, comment, previousComment,
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export function getJsxExpressionBlockCommentLayout(text, comment, previousComment, containerMatch, tabWidth, outputCommentLayout, outputCommentMarkerColumns) {
|
|
32
|
+
if (containerMatch === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const container = containerMatch.range;
|
|
36
|
+
if (text[container.start] !== '{' || text[container.end - 1] !== '}') {
|
|
34
37
|
return undefined;
|
|
35
38
|
}
|
|
36
39
|
const hasExpressionBeforeComment = container.expression !== undefined && container.expression.start < comment.start;
|
|
37
40
|
const hasExpressionAfterComment = container.expression !== undefined && container.expression.end > comment.end;
|
|
38
|
-
const
|
|
39
|
-
const multilineMarkerColumn = outputCommentMarkerColumns[containerIndex] ??
|
|
41
|
+
const multilineMarkerColumn = outputCommentMarkerColumns[containerMatch.index] ??
|
|
40
42
|
getJsxExpressionContainerOutputColumn(text, container, tabWidth) + tabWidth;
|
|
41
43
|
const markerColumn = outputCommentLayout?.markerColumn ?? multilineMarkerColumn;
|
|
42
44
|
const contentColumn = multilineMarkerColumn + 3;
|
|
@@ -78,7 +80,14 @@ export function getJsxExpressionBlockCommentLayout(text, comment, previousCommen
|
|
|
78
80
|
}
|
|
79
81
|
if (!hasExpressionBeforeComment) {
|
|
80
82
|
if (isStandaloneBlockComment(text, comment)) {
|
|
81
|
-
return {
|
|
83
|
+
return {
|
|
84
|
+
contentColumn,
|
|
85
|
+
markerColumn,
|
|
86
|
+
multilineIndent: '',
|
|
87
|
+
placement: 'standalone',
|
|
88
|
+
preserveMultiline: getLineStart(text, comment.start) !== getLineStart(text, comment.end),
|
|
89
|
+
singleLineSuffixWidth: outputCommentLayout?.suffixWidth ?? 0,
|
|
90
|
+
};
|
|
82
91
|
}
|
|
83
92
|
const expressionStart = skipWhitespace(text, comment.end);
|
|
84
93
|
return {
|
|
@@ -103,15 +112,3 @@ function getJsxExpressionContainerOutputColumn(text, container, tabWidth) {
|
|
|
103
112
|
const lineIndent = getLeadingIndent(linePrefix);
|
|
104
113
|
return getColumns(lineIndent, tabWidth) + tabWidth;
|
|
105
114
|
}
|
|
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
|
-
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { CommentEntry, CommentRange } from './comment-ranges.js';
|
|
2
2
|
import type { SourceRange } from '../utils/ast.js';
|
|
3
3
|
export declare function neutralizePrettierIgnoreForIgnoredComments<T>(text: string, ast: T): T;
|
|
4
|
+
export declare function getNeutralizedPrettierIgnoreOriginalText(comment: unknown): string | undefined;
|
|
4
5
|
export declare function collectPrettierIgnoredLineRanges(text: string, ast: unknown, comments: CommentEntry[]): SourceRange[];
|
|
5
|
-
export declare function isCommentInIgnoredLineRange(comment: CommentRange,
|
|
6
|
+
export declare function isCommentInIgnoredLineRange(comment: CommentRange, ignoredLineRange: SourceRange | undefined): boolean;
|
|
6
7
|
export declare function isPrettierIgnoredBlockComment(text: string, comments: CommentEntry[], index: number): boolean;
|
|
7
8
|
export declare function isPrettierIgnoredStandaloneLineComment(text: string, comments: CommentEntry[], index: number): boolean;
|
|
8
9
|
export declare function isPrettierIgnoredTrailingLineComment(text: string, comments: CommentEntry[], index: number): boolean;
|
|
@@ -3,10 +3,12 @@ import { isPrettierIgnoreComment } from './comment-directives.js';
|
|
|
3
3
|
import { shouldSkipBlockComment, shouldSkipLineComment } from './comment-eligibility.js';
|
|
4
4
|
import { areCommentsOnAdjacentLines, isCommentAdjacentBeforeIndex, isStandaloneBlockComment, isStandaloneComment, isStandaloneLineComment, } from './comment-location.js';
|
|
5
5
|
import { collectCommentEntries } from './comment-ranges.js';
|
|
6
|
-
import { getAstNodeRange, visitAstNodes } from '../utils/ast.js';
|
|
6
|
+
import { collectAstNodeRangesByStart, getAstNodeRange, visitAstNodes } from '../utils/ast.js';
|
|
7
7
|
import { getLineEnd, getLineStart } from '../utils/source-lines.js';
|
|
8
|
+
import { isRecord } from '../utils/type-guards.js';
|
|
8
9
|
import { skipWhitespace } from '../utils/whitespace.js';
|
|
9
10
|
const NEUTRALIZED_PRETTIER_IGNORE_COMMENT = 'prettier-ignore wrap-comments';
|
|
11
|
+
const neutralizedPrettierIgnoreOriginalTextKey = Symbol('neutralizedPrettierIgnoreOriginalText');
|
|
10
12
|
export function neutralizePrettierIgnoreForIgnoredComments(text, ast) {
|
|
11
13
|
const comments = collectCommentEntries(ast, text);
|
|
12
14
|
for (let index = 0; index < comments.length; index += 1) {
|
|
@@ -17,38 +19,55 @@ export function neutralizePrettierIgnoreForIgnoredComments(text, ast) {
|
|
|
17
19
|
((isPrettierIgnoredBlockComment(text, comments, index) && !shouldSkipBlockComment(text, entry.range)) ||
|
|
18
20
|
(isPrettierIgnoredStandaloneLineComment(text, comments, index) && !shouldSkipLineComment(text, entry.range)));
|
|
19
21
|
if (shouldNeutralize) {
|
|
22
|
+
if (previousEntry.range.kind === 'block') {
|
|
23
|
+
const originalText = text.slice(previousEntry.range.start, previousEntry.range.end);
|
|
24
|
+
previousEntry.raw[neutralizedPrettierIgnoreOriginalTextKey] =
|
|
25
|
+
originalText;
|
|
26
|
+
}
|
|
20
27
|
previousEntry.raw.value = NEUTRALIZED_PRETTIER_IGNORE_COMMENT;
|
|
21
28
|
}
|
|
22
29
|
}
|
|
23
30
|
return ast;
|
|
24
31
|
}
|
|
32
|
+
export function getNeutralizedPrettierIgnoreOriginalText(comment) {
|
|
33
|
+
if (!isRecord(comment)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const originalText = comment[neutralizedPrettierIgnoreOriginalTextKey];
|
|
37
|
+
return typeof originalText === 'string' ? originalText : undefined;
|
|
38
|
+
}
|
|
25
39
|
export function collectPrettierIgnoredLineRanges(text, ast, comments) {
|
|
26
|
-
const
|
|
40
|
+
const nodeRangesByStart = collectAstNodeRangesByStart(ast);
|
|
41
|
+
const jsxTargetRangesByIgnoreStart = collectJsxPrettierIgnoreTargetRanges(text, ast, comments);
|
|
27
42
|
const ignoredLineRanges = [];
|
|
28
43
|
for (let index = 0; index < comments.length; index += 1) {
|
|
29
44
|
const comment = comments[index]?.range;
|
|
45
|
+
const jsxTargetRange = comment === undefined ? undefined : jsxTargetRangesByIgnoreStart.get(comment.start);
|
|
30
46
|
if (comment === undefined ||
|
|
31
|
-
!isStandaloneComment(text, comment) ||
|
|
47
|
+
(!isStandaloneComment(text, comment) && jsxTargetRange === undefined) ||
|
|
32
48
|
!isPrettierIgnoreComment(getCommentBody(text, comment))) {
|
|
33
49
|
continue;
|
|
34
50
|
}
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
51
|
+
let targetRange = jsxTargetRange;
|
|
52
|
+
if (targetRange === undefined) {
|
|
53
|
+
const targetStart = getPrettierIgnoreTargetStart(text, comments, index);
|
|
54
|
+
if (targetStart === undefined) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
targetRange = nodeRangesByStart.get(targetStart);
|
|
38
58
|
}
|
|
39
|
-
const targetRange = nodeRanges.find((range) => range.start === targetStart);
|
|
40
59
|
if (targetRange === undefined) {
|
|
41
60
|
continue;
|
|
42
61
|
}
|
|
43
|
-
ignoredLineRanges
|
|
62
|
+
appendMergedRange(ignoredLineRanges, {
|
|
44
63
|
end: getLineEnd(text, targetRange.end),
|
|
45
64
|
start: getLineStart(text, targetRange.start),
|
|
46
65
|
});
|
|
47
66
|
}
|
|
48
67
|
return ignoredLineRanges;
|
|
49
68
|
}
|
|
50
|
-
export function isCommentInIgnoredLineRange(comment,
|
|
51
|
-
return
|
|
69
|
+
export function isCommentInIgnoredLineRange(comment, ignoredLineRange) {
|
|
70
|
+
return (ignoredLineRange !== undefined && comment.start >= ignoredLineRange.start && comment.start < ignoredLineRange.end);
|
|
52
71
|
}
|
|
53
72
|
export function isPrettierIgnoredBlockComment(text, comments, index) {
|
|
54
73
|
const comment = comments[index]?.range;
|
|
@@ -107,15 +126,82 @@ export function isPrettierIgnoredTrailingLineComment(text, comments, index) {
|
|
|
107
126
|
}
|
|
108
127
|
return false;
|
|
109
128
|
}
|
|
110
|
-
function
|
|
111
|
-
const
|
|
129
|
+
function collectJsxPrettierIgnoreTargetRanges(text, ast, comments) {
|
|
130
|
+
const commentsByStart = new Map(comments.map((entry) => [entry.range.start, entry.range]));
|
|
131
|
+
const targetRangesByIgnoreStart = new Map();
|
|
112
132
|
visitAstNodes(ast, (node) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
133
|
+
if (!isJsxElementOrFragment(node)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const children = node['children'];
|
|
137
|
+
if (!Array.isArray(children)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
for (let ignoreIndex = 0; ignoreIndex < children.length; ignoreIndex += 1) {
|
|
141
|
+
const ignoreContainer = children[ignoreIndex];
|
|
142
|
+
if (!isRecord(ignoreContainer)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const ignoreComment = getCommentOnlyJsxExpressionContainerComment(text, ignoreContainer, commentsByStart);
|
|
146
|
+
if (ignoreComment === undefined || !isPrettierIgnoreComment(getCommentBody(text, ignoreComment))) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const target = getNextSignificantJsxChild(text, children, ignoreIndex);
|
|
150
|
+
if (target === undefined || !isJsxElementOrFragment(target)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const targetRange = getAstNodeRange(target);
|
|
154
|
+
if (targetRange !== undefined) {
|
|
155
|
+
targetRangesByIgnoreStart.set(ignoreComment.start, targetRange);
|
|
156
|
+
}
|
|
116
157
|
}
|
|
117
158
|
});
|
|
118
|
-
return
|
|
159
|
+
return targetRangesByIgnoreStart;
|
|
160
|
+
}
|
|
161
|
+
function getNextSignificantJsxChild(text, children, ignoreIndex) {
|
|
162
|
+
for (let index = ignoreIndex + 1; index < children.length; index += 1) {
|
|
163
|
+
const child = children[index];
|
|
164
|
+
if (!isRecord(child)) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
if (isMultilineWhitespaceJsxText(text, child)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return child;
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
function isMultilineWhitespaceJsxText(text, node) {
|
|
175
|
+
if (node['type'] !== 'JSXText') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const range = getAstNodeRange(node);
|
|
179
|
+
if (range === undefined) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const raw = text.slice(range.start, range.end);
|
|
183
|
+
return raw.includes('\n') && /^[ \t\r\n]*$/u.test(raw);
|
|
184
|
+
}
|
|
185
|
+
function getCommentOnlyJsxExpressionContainerComment(text, container, commentsByStart) {
|
|
186
|
+
const expression = container['expression'];
|
|
187
|
+
if (container['type'] !== 'JSXExpressionContainer' ||
|
|
188
|
+
!isRecord(expression) ||
|
|
189
|
+
expression['type'] !== 'JSXEmptyExpression') {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const range = getAstNodeRange(container);
|
|
193
|
+
if (range === undefined || text[range.start] !== '{' || text[range.end - 1] !== '}') {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const commentStart = skipWhitespace(text, range.start + 1);
|
|
197
|
+
const comment = commentsByStart.get(commentStart);
|
|
198
|
+
if (comment === undefined || comment.kind !== 'block' || skipWhitespace(text, comment.end) !== range.end - 1) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
return comment;
|
|
202
|
+
}
|
|
203
|
+
function isJsxElementOrFragment(node) {
|
|
204
|
+
return node['type'] === 'JSXElement' || node['type'] === 'JSXFragment';
|
|
119
205
|
}
|
|
120
206
|
function getPrettierIgnoreTargetStart(text, comments, ignoreCommentIndex) {
|
|
121
207
|
const ignoreComment = comments[ignoreCommentIndex]?.range;
|
|
@@ -143,3 +229,11 @@ function isSkippableCommentBetweenIgnoreAndTarget(text, comment) {
|
|
|
143
229
|
}
|
|
144
230
|
return shouldSkipLineComment(text, comment) && !isPrettierIgnoreComment(getCommentBody(text, comment));
|
|
145
231
|
}
|
|
232
|
+
function appendMergedRange(ranges, range) {
|
|
233
|
+
const previousRange = ranges[ranges.length - 1];
|
|
234
|
+
if (previousRange === undefined || range.start > previousRange.end) {
|
|
235
|
+
ranges.push(range);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
previousRange.end = Math.max(previousRange.end, range.end);
|
|
239
|
+
}
|
|
@@ -57,6 +57,7 @@ function getPrinterCommentLayout(text, comment, tabWidth) {
|
|
|
57
57
|
const suffix = text.slice(comment.end, lineEnd).replace(/[ \t]+$/u, '');
|
|
58
58
|
return {
|
|
59
59
|
lineIndentColumn: getColumns(lineIndent, tabWidth),
|
|
60
|
+
lineStart,
|
|
60
61
|
lineWidth: getColumns(lineText, tabWidth),
|
|
61
62
|
markerColumn: getColumns(linePrefix, tabWidth),
|
|
62
63
|
suffixWidth: getColumns(suffix, tabWidth),
|