@aforemendude/prettier-plugin-wrap-comments 1.0.6 → 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 +24 -6
- 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} +4 -1
- package/dist/comments/{block.js → wrap-block-comment.js} +15 -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 -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/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() {
|
|
@@ -151,11 +161,19 @@ The plugin leaves these comments unchanged:
|
|
|
151
161
|
|
|
152
162
|
## Development
|
|
153
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
|
+
|
|
154
169
|
```sh
|
|
155
170
|
npm install
|
|
156
171
|
npm run format:check
|
|
172
|
+
npm run typecheck
|
|
157
173
|
npm run test
|
|
174
|
+
npm run build
|
|
158
175
|
```
|
|
159
176
|
|
|
160
|
-
`npm run test`
|
|
161
|
-
`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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CommentEntry, CommentRange } from './comment-ranges.js';
|
|
2
|
+
import type { SourceRange } from '../utils/ast.js';
|
|
3
|
+
export declare function neutralizePrettierIgnoreForIgnoredComments<T>(text: string, ast: T): T;
|
|
4
|
+
export declare function collectPrettierIgnoredLineRanges(text: string, ast: unknown, comments: CommentEntry[]): SourceRange[];
|
|
5
|
+
export declare function isCommentInIgnoredLineRange(comment: CommentRange, ignoredLineRanges: SourceRange[]): boolean;
|
|
6
|
+
export declare function isPrettierIgnoredBlockComment(text: string, comments: CommentEntry[], index: number): boolean;
|
|
7
|
+
export declare function isPrettierIgnoredStandaloneLineComment(text: string, comments: CommentEntry[], index: number): boolean;
|
|
8
|
+
export declare function isPrettierIgnoredTrailingLineComment(text: string, comments: CommentEntry[], index: number): boolean;
|