@aforemendude/prettier-plugin-wrap-comments 1.0.3 → 1.0.4
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 +41 -2
- package/dist/comments/block.d.ts +11 -1
- package/dist/comments/block.js +37 -8
- package/dist/comments/core.d.ts +1 -0
- package/dist/comments/core.js +3 -0
- package/dist/comments/line.d.ts +1 -1
- package/dist/comments/line.js +26 -8
- package/dist/comments/wrap.d.ts +1 -0
- package/dist/comments/wrap.js +323 -9
- package/dist/index.d.ts +2 -3
- package/dist/index.js +2 -2
- package/dist/plugin/parsers.js +5 -1
- package/dist/plugin/printers.d.ts +2 -0
- package/dist/plugin/printers.js +39 -0
- package/dist/shared/text.d.ts +1 -0
- package/dist/shared/text.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,14 +77,53 @@ if (ready) {
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
JSX and TSX expression comments use the expression contents, not the outer React expression braces, to decide whether a
|
|
81
|
+
block comment is standalone, trailing, or inline. A comment-only expression like `{/* ... */}` can wrap as a standalone
|
|
82
|
+
JSX comment. A trailing expression comment like `{label /* ... */}` can move before `label` and wrap. A true inline
|
|
83
|
+
expression comment like `{"abc" + /* ... */ "123"}` is left unchanged when it cannot fit on one line.
|
|
84
|
+
|
|
85
|
+
<!-- prettier-ignore-start -->
|
|
86
|
+
```tsx
|
|
87
|
+
<span>
|
|
88
|
+
{
|
|
89
|
+
/*
|
|
90
|
+
* This expression comment wraps because the surrounding JSX braces do not
|
|
91
|
+
* make it inline.
|
|
92
|
+
*/
|
|
93
|
+
}
|
|
94
|
+
{
|
|
95
|
+
/*
|
|
96
|
+
* This expression comment moved above the expression value because it was
|
|
97
|
+
* trailing.
|
|
98
|
+
*/
|
|
99
|
+
label
|
|
100
|
+
}
|
|
101
|
+
{'abc' + /* This inline expression comment stays in place. */ '123'}
|
|
102
|
+
</span>
|
|
103
|
+
```
|
|
104
|
+
<!-- prettier-ignore-end -->
|
|
105
|
+
|
|
106
|
+
`prettier-ignore` markers are preserved and affect wrapping only when the marker body is exactly `prettier-ignore`. When
|
|
107
|
+
one of these markers is directly above a standalone block comment, the plugin leaves that block comment unchanged. If
|
|
108
|
+
the block comment is one the plugin would otherwise wrap, the following code still formats normally; if the block
|
|
109
|
+
comment is already skipped by the plugin, such as a JSDoc or directive block, Prettier keeps its normal ignore behavior
|
|
110
|
+
for the following code.
|
|
111
|
+
|
|
112
|
+
For trailing line comments, a `// prettier-ignore` line can apply to the code line and its inline comment. The plugin
|
|
113
|
+
walks past adjacent standalone comments that it normally leaves alone, such as an `eslint-disable-next-line` directive,
|
|
114
|
+
so an ignored code line's trailing comment remains inline and unchanged. A directive comment by itself does not ignore
|
|
115
|
+
the following code line; without `// prettier-ignore`, an overlong trailing comment below a directive is still moved
|
|
116
|
+
above the statement and wrapped.
|
|
117
|
+
|
|
80
118
|
The plugin leaves these comments unchanged:
|
|
81
119
|
|
|
82
120
|
- JSDoc comments that start with `/**`
|
|
83
121
|
- bang-preserved comments that start with `/*!` or `//!`
|
|
84
122
|
- TypeScript-style triple-slash line comments that start with `///`
|
|
85
123
|
- empty comment bodies
|
|
86
|
-
-
|
|
87
|
-
|
|
124
|
+
- `prettier-ignore` markers themselves
|
|
125
|
+
- other directive comments such as `@license`, `@preserve`, JSX and TypeScript pragmas, source map directives,
|
|
126
|
+
`#__PURE__`, `@__PURE__`, lint/coverage/formatter directives, `vite-ignore`, and webpack magic comments
|
|
88
127
|
|
|
89
128
|
## Supported Parsers
|
|
90
129
|
|
package/dist/comments/block.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
import type { CommentRange, Replacement, WrapOptions } from '../shared/types.js';
|
|
2
|
-
export
|
|
2
|
+
export type BlockCommentLayout = {
|
|
3
|
+
contentColumn?: number;
|
|
4
|
+
multilineIndent?: string;
|
|
5
|
+
placement: 'inline' | 'standalone' | 'trailing';
|
|
6
|
+
trailingMove?: {
|
|
7
|
+
insertAt: number;
|
|
8
|
+
removeEnd: number;
|
|
9
|
+
removeStart: number;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function wrapBlockComment(text: string, comment: CommentRange, options: WrapOptions, layout?: BlockCommentLayout): Promise<Replacement | Replacement[] | undefined>;
|
package/dist/comments/block.js
CHANGED
|
@@ -2,7 +2,7 @@ import { hasPreserveCommentMarker, isDirectiveComment, normalizeBlockCommentBody
|
|
|
2
2
|
import { formatMarkdownLines } from '../shared/markdown.js';
|
|
3
3
|
import { getAvailableContentWidth, getPrintWidth, getTabWidth } from '../shared/options.js';
|
|
4
4
|
import { getColumnAt, getColumns, getLinePrefix, getPreferredNewline, isStandaloneBlockComment, } from '../shared/text.js';
|
|
5
|
-
export async function wrapBlockComment(text, comment, options) {
|
|
5
|
+
export async function wrapBlockComment(text, comment, options, layout = getDefaultBlockCommentLayout(text, comment)) {
|
|
6
6
|
const raw = text.slice(comment.start, comment.end);
|
|
7
7
|
if (raw.startsWith('/**') || hasPreserveCommentMarker(raw)) {
|
|
8
8
|
return undefined;
|
|
@@ -13,19 +13,25 @@ export async function wrapBlockComment(text, comment, options) {
|
|
|
13
13
|
}
|
|
14
14
|
const tabWidth = getTabWidth(options);
|
|
15
15
|
const markerColumn = getColumnAt(text, comment.start, tabWidth);
|
|
16
|
-
const availableWidth = getAvailableContentWidth(options, markerColumn + 3);
|
|
16
|
+
const availableWidth = getAvailableContentWidth(options, layout.contentColumn ?? markerColumn + 3);
|
|
17
17
|
const formattedLines = await formatMarkdownLines(markdown, availableWidth, options);
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
18
|
+
const replacement = buildBlockReplacement(text, comment, formattedLines, options, layout);
|
|
19
|
+
if (replacement === undefined) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(replacement)) {
|
|
23
|
+
return replacement;
|
|
24
|
+
}
|
|
25
|
+
if (replacement === text.slice(comment.start, comment.end)) {
|
|
20
26
|
return undefined;
|
|
21
27
|
}
|
|
22
28
|
return {
|
|
23
29
|
end: comment.end,
|
|
24
30
|
start: comment.start,
|
|
25
|
-
text:
|
|
31
|
+
text: replacement,
|
|
26
32
|
};
|
|
27
33
|
}
|
|
28
|
-
function buildBlockReplacement(text, comment, formattedLines, options) {
|
|
34
|
+
function buildBlockReplacement(text, comment, formattedLines, options, layout) {
|
|
29
35
|
const tabWidth = getTabWidth(options);
|
|
30
36
|
const markerColumn = getColumnAt(text, comment.start, tabWidth);
|
|
31
37
|
const singleLine = `/* ${formattedLines.join(' ')} */`;
|
|
@@ -33,11 +39,34 @@ function buildBlockReplacement(text, comment, formattedLines, options) {
|
|
|
33
39
|
if (formattedLines.length === 1 && markerColumn + singleLineWidth <= getPrintWidth(options)) {
|
|
34
40
|
return singleLine;
|
|
35
41
|
}
|
|
36
|
-
if (
|
|
42
|
+
if (layout.placement === 'inline') {
|
|
37
43
|
return undefined;
|
|
38
44
|
}
|
|
39
45
|
const newline = getPreferredNewline(text, options);
|
|
40
|
-
const indent = getLinePrefix(text, comment.start);
|
|
46
|
+
const indent = layout.multilineIndent ?? getLinePrefix(text, comment.start);
|
|
47
|
+
const replacementText = buildMultilineBlockReplacement(formattedLines, newline, indent);
|
|
48
|
+
if (layout.trailingMove !== undefined) {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
end: layout.trailingMove.insertAt,
|
|
52
|
+
start: layout.trailingMove.insertAt,
|
|
53
|
+
text: `${replacementText}${newline}`,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
end: layout.trailingMove.removeEnd,
|
|
57
|
+
start: layout.trailingMove.removeStart,
|
|
58
|
+
text: '',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return replacementText;
|
|
63
|
+
}
|
|
64
|
+
function buildMultilineBlockReplacement(formattedLines, newline, indent) {
|
|
41
65
|
const body = formattedLines.map((line) => `${indent} *${line.length === 0 ? '' : ` ${line}`}`).join(newline);
|
|
42
66
|
return `/*${newline}${body}${newline}${indent} */`;
|
|
43
67
|
}
|
|
68
|
+
function getDefaultBlockCommentLayout(text, comment) {
|
|
69
|
+
return {
|
|
70
|
+
placement: isStandaloneBlockComment(text, comment) ? 'standalone' : 'inline',
|
|
71
|
+
};
|
|
72
|
+
}
|
package/dist/comments/core.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare function toCommentRange(comment: RawComment, text: string): Comme
|
|
|
4
4
|
export declare function normalizeLineCommentBody(rawBody: string): string;
|
|
5
5
|
export declare function normalizeBlockCommentBody(rawComment: string): string;
|
|
6
6
|
export declare function isDirectiveComment(body: string): boolean;
|
|
7
|
+
export declare function isPrettierIgnoreComment(body: string): boolean;
|
|
7
8
|
export declare function hasPreserveCommentMarker(rawComment: string): boolean;
|
package/dist/comments/core.js
CHANGED
|
@@ -108,6 +108,9 @@ export function isDirectiveComment(body) {
|
|
|
108
108
|
const normalizedBody = body.trimStart();
|
|
109
109
|
return DIRECTIVE_COMMENT_PATTERNS.some((pattern) => pattern.test(normalizedBody));
|
|
110
110
|
}
|
|
111
|
+
export function isPrettierIgnoreComment(body) {
|
|
112
|
+
return body.trim() === 'prettier-ignore';
|
|
113
|
+
}
|
|
111
114
|
export function hasPreserveCommentMarker(rawComment) {
|
|
112
115
|
return rawComment.startsWith('/*!') || rawComment.startsWith('//!');
|
|
113
116
|
}
|
package/dist/comments/line.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommentRange, Replacement, WrapOptions } from '../shared/types.js';
|
|
2
2
|
export declare function wrapLineCommentGroup(text: string, comments: CommentRange[], options: WrapOptions): Promise<Replacement | undefined>;
|
|
3
|
-
export declare function wrapTrailingLineComment(text: string, comment: CommentRange, options: WrapOptions): Promise<Replacement | undefined>;
|
|
3
|
+
export declare function wrapTrailingLineComment(text: string, comment: CommentRange, options: WrapOptions): Promise<Replacement[] | undefined>;
|
|
4
4
|
export declare function shouldSkipLineComment(text: string, comment: CommentRange): boolean;
|
|
5
5
|
export declare function isStandaloneLineComment(text: string, comment: CommentRange): boolean;
|
|
6
6
|
export declare function areAdjacentLineComments(text: string, previous: CommentRange, next: CommentRange): boolean;
|
package/dist/comments/line.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { hasPreserveCommentMarker, isDirectiveComment, normalizeLineCommentBody } from './core.js';
|
|
2
2
|
import { formatMarkdownLines } from '../shared/markdown.js';
|
|
3
3
|
import { getAvailableContentWidth, getPrintWidth, getTabWidth } from '../shared/options.js';
|
|
4
|
-
import { getColumnAt, getColumns, getContinuationIndent, getLineEnd, getLinePrefix, getLineStart, getPreferredNewline, } from '../shared/text.js';
|
|
4
|
+
import { getColumnAt, getColumns, getContinuationIndent, getLineEnd, getLinePrefix, getLineStart, getPreferredNewline, makeIndent, } from '../shared/text.js';
|
|
5
5
|
export async function wrapLineCommentGroup(text, comments, options) {
|
|
6
6
|
const firstComment = comments[0];
|
|
7
7
|
if (firstComment === undefined) {
|
|
@@ -51,19 +51,26 @@ export async function wrapTrailingLineComment(text, comment, options) {
|
|
|
51
51
|
return undefined;
|
|
52
52
|
}
|
|
53
53
|
const tabWidth = getTabWidth(options);
|
|
54
|
-
const indent =
|
|
54
|
+
const indent = getTrailingCommentIndent(codeText, linePrefix, options);
|
|
55
55
|
const availableWidth = getAvailableContentWidth(options, getColumns(indent, tabWidth) + 3);
|
|
56
56
|
const formattedLines = await formatMarkdownLines(body, availableWidth, options);
|
|
57
57
|
const newline = getPreferredNewline(text, options);
|
|
58
58
|
const leadingCommentText = formattedLines
|
|
59
59
|
.map((line) => `${indent}${line.length === 0 ? '//' : `// ${line}`}`)
|
|
60
60
|
.join(newline);
|
|
61
|
-
const
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
const codeEnd = lineStart + codeText.length;
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
end: lineStart,
|
|
65
|
+
start: lineStart,
|
|
66
|
+
text: `${leadingCommentText}${newline}`,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
end: lineEnd,
|
|
70
|
+
start: codeEnd,
|
|
71
|
+
text: '',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
67
74
|
}
|
|
68
75
|
export function shouldSkipLineComment(text, comment) {
|
|
69
76
|
const raw = text.slice(comment.start, comment.end);
|
|
@@ -88,3 +95,14 @@ function isTrailingLineCommentWithinPrintWidth(text, comment, options) {
|
|
|
88
95
|
function getLineIndent(linePrefix) {
|
|
89
96
|
return /^[ \t]*/u.exec(linePrefix)?.[0] ?? '';
|
|
90
97
|
}
|
|
98
|
+
function getTrailingCommentIndent(codeText, linePrefix, options) {
|
|
99
|
+
const indent = getLineIndent(linePrefix);
|
|
100
|
+
if (!isClosingDelimiterLine(codeText)) {
|
|
101
|
+
return indent;
|
|
102
|
+
}
|
|
103
|
+
const tabWidth = getTabWidth(options);
|
|
104
|
+
return makeIndent(getColumns(indent, tabWidth) + tabWidth, options);
|
|
105
|
+
}
|
|
106
|
+
function isClosingDelimiterLine(codeText) {
|
|
107
|
+
return /^[ \t]*[\])}]+[\])};,]*[ \t]*$/u.test(codeText);
|
|
108
|
+
}
|
package/dist/comments/wrap.d.ts
CHANGED
package/dist/comments/wrap.js
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { wrapBlockComment } from './block.js';
|
|
2
|
-
import { collectComments, toCommentRange } from './core.js';
|
|
2
|
+
import { collectComments, hasPreserveCommentMarker, isDirectiveComment, isPrettierIgnoreComment, normalizeBlockCommentBody, normalizeLineCommentBody, toCommentRange, } from './core.js';
|
|
3
3
|
import { areAdjacentLineComments, isStandaloneLineComment, shouldSkipLineComment, wrapLineCommentGroup, wrapTrailingLineComment, } from './line.js';
|
|
4
4
|
import { getTabWidth } from '../shared/options.js';
|
|
5
|
-
import { applyReplacements, getColumnAt } from '../shared/text.js';
|
|
5
|
+
import { applyReplacements, getColumnAt, getColumns, getLineEnd, getLinePrefix, getLineStart, isStandaloneBlockComment, } from '../shared/text.js';
|
|
6
|
+
const NEUTRALIZED_PRETTIER_IGNORE_COMMENT = 'prettier-ignore wrap-comments';
|
|
7
|
+
const AST_TRAVERSAL_SKIP_KEYS = new Set([
|
|
8
|
+
'comments',
|
|
9
|
+
'errors',
|
|
10
|
+
'innerComments',
|
|
11
|
+
'leadingComments',
|
|
12
|
+
'loc',
|
|
13
|
+
'parent',
|
|
14
|
+
'range',
|
|
15
|
+
'tokens',
|
|
16
|
+
'trailingComments',
|
|
17
|
+
]);
|
|
6
18
|
export async function wrapComments(text, ast, options) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
const commentEntries = collectSortedCommentEntries(ast, text);
|
|
20
|
+
const comments = commentEntries.map((entry) => entry.range);
|
|
21
|
+
const jsxExpressionContainers = collectJsxExpressionContainerRanges(ast);
|
|
22
|
+
const ignoredLineRanges = collectPrettierIgnoredLineRanges(text, ast, commentEntries);
|
|
11
23
|
if (comments.length === 0) {
|
|
12
24
|
return text;
|
|
13
25
|
}
|
|
@@ -18,9 +30,22 @@ export async function wrapComments(text, ast, options) {
|
|
|
18
30
|
if (comment === undefined) {
|
|
19
31
|
continue;
|
|
20
32
|
}
|
|
33
|
+
if (isCommentInIgnoredLineRange(comment, ignoredLineRanges)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
21
36
|
if (comment.kind === 'block') {
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
if (isPrettierIgnoredBlockComment(text, commentEntries, index)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const jsxLayout = getJsxExpressionBlockCommentLayout(text, comment, jsxExpressionContainers, tabWidth);
|
|
41
|
+
if (jsxLayout?.placement === 'inline') {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const replacement = await wrapBlockComment(text, comment, options, jsxLayout);
|
|
45
|
+
if (Array.isArray(replacement)) {
|
|
46
|
+
replacements.push(...replacement);
|
|
47
|
+
}
|
|
48
|
+
else if (replacement !== undefined) {
|
|
24
49
|
replacements.push(replacement);
|
|
25
50
|
}
|
|
26
51
|
continue;
|
|
@@ -29,9 +54,12 @@ export async function wrapComments(text, ast, options) {
|
|
|
29
54
|
continue;
|
|
30
55
|
}
|
|
31
56
|
if (!isStandaloneLineComment(text, comment)) {
|
|
57
|
+
if (isPrettierIgnoredTrailingLineComment(text, commentEntries, index)) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
32
60
|
const replacement = await wrapTrailingLineComment(text, comment, options);
|
|
33
61
|
if (replacement !== undefined) {
|
|
34
|
-
replacements.push(replacement);
|
|
62
|
+
replacements.push(...replacement);
|
|
35
63
|
}
|
|
36
64
|
continue;
|
|
37
65
|
}
|
|
@@ -60,3 +88,289 @@ export async function wrapComments(text, ast, options) {
|
|
|
60
88
|
}
|
|
61
89
|
return applyReplacements(text, replacements);
|
|
62
90
|
}
|
|
91
|
+
export function neutralizePrettierIgnoreForIgnoredBlockComments(text, ast) {
|
|
92
|
+
const comments = collectSortedCommentEntries(ast, text);
|
|
93
|
+
for (let index = 0; index < comments.length; index += 1) {
|
|
94
|
+
const entry = comments[index];
|
|
95
|
+
const previousEntry = comments[index - 1];
|
|
96
|
+
if (entry !== undefined &&
|
|
97
|
+
previousEntry !== undefined &&
|
|
98
|
+
isPrettierIgnoredBlockComment(text, comments, index) &&
|
|
99
|
+
!isBlockCommentNormallyIgnored(text, entry.range)) {
|
|
100
|
+
previousEntry.raw.value = NEUTRALIZED_PRETTIER_IGNORE_COMMENT;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return ast;
|
|
104
|
+
}
|
|
105
|
+
function collectSortedCommentEntries(ast, text) {
|
|
106
|
+
return collectComments(ast)
|
|
107
|
+
.map((raw) => ({ range: toCommentRange(raw, text), raw }))
|
|
108
|
+
.filter((entry) => entry.range !== undefined)
|
|
109
|
+
.sort((left, right) => left.range.start - right.range.start);
|
|
110
|
+
}
|
|
111
|
+
function collectPrettierIgnoredLineRanges(text, ast, comments) {
|
|
112
|
+
const nodeRanges = collectAstNodeRanges(ast);
|
|
113
|
+
const ignoredLineRanges = [];
|
|
114
|
+
for (let index = 0; index < comments.length; index += 1) {
|
|
115
|
+
const comment = comments[index]?.range;
|
|
116
|
+
if (comment === undefined ||
|
|
117
|
+
comment.kind !== 'line' ||
|
|
118
|
+
!isStandaloneLineComment(text, comment) ||
|
|
119
|
+
!isPrettierIgnoreComment(getCommentBody(text, comment))) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const targetStart = getPrettierIgnoreTargetStart(text, comments, index);
|
|
123
|
+
if (targetStart === undefined) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const targetRange = nodeRanges.find((range) => range.start === targetStart);
|
|
127
|
+
if (targetRange === undefined) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
ignoredLineRanges.push({
|
|
131
|
+
end: getLineEnd(text, targetRange.end),
|
|
132
|
+
start: getLineStart(text, targetRange.start),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return ignoredLineRanges;
|
|
136
|
+
}
|
|
137
|
+
function collectAstNodeRanges(ast) {
|
|
138
|
+
const ranges = [];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
visit(ast);
|
|
141
|
+
return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
142
|
+
function visit(value) {
|
|
143
|
+
if (!isRecord(value) || seen.has(value)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
seen.add(value);
|
|
147
|
+
const range = getAstNodeRange(value);
|
|
148
|
+
if (range !== undefined && typeof value['type'] === 'string') {
|
|
149
|
+
ranges.push(range);
|
|
150
|
+
}
|
|
151
|
+
for (const [key, child] of Object.entries(value)) {
|
|
152
|
+
if (AST_TRAVERSAL_SKIP_KEYS.has(key)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(child)) {
|
|
156
|
+
for (const item of child) {
|
|
157
|
+
visit(item);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
visit(child);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function collectJsxExpressionContainerRanges(ast) {
|
|
167
|
+
const ranges = [];
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
visit(ast);
|
|
170
|
+
return ranges.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
171
|
+
function visit(value) {
|
|
172
|
+
if (!isRecord(value) || seen.has(value)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
seen.add(value);
|
|
176
|
+
if (value['type'] === 'JSXExpressionContainer') {
|
|
177
|
+
const range = getAstNodeRange(value);
|
|
178
|
+
if (range !== undefined) {
|
|
179
|
+
ranges.push(range);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
for (const [key, child] of Object.entries(value)) {
|
|
183
|
+
if (AST_TRAVERSAL_SKIP_KEYS.has(key)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (Array.isArray(child)) {
|
|
187
|
+
for (const item of child) {
|
|
188
|
+
visit(item);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
visit(child);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function getAstNodeRange(node) {
|
|
198
|
+
const start = numberOrUndefined(node['start']) ?? getRangeNumber(node['range'], 0);
|
|
199
|
+
const end = numberOrUndefined(node['end']) ?? getRangeNumber(node['range'], 1);
|
|
200
|
+
if (start === undefined || end === undefined || start >= end) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
return { end, start };
|
|
204
|
+
}
|
|
205
|
+
function getRangeNumber(range, index) {
|
|
206
|
+
if (!Array.isArray(range)) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
return numberOrUndefined(range[index]);
|
|
210
|
+
}
|
|
211
|
+
function getPrettierIgnoreTargetStart(text, comments, ignoreCommentIndex) {
|
|
212
|
+
const ignoreComment = comments[ignoreCommentIndex]?.range;
|
|
213
|
+
if (ignoreComment === undefined) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
let cursor = ignoreComment.end;
|
|
217
|
+
for (let index = ignoreCommentIndex + 1; index < comments.length; index += 1) {
|
|
218
|
+
cursor = skipWhitespace(text, cursor);
|
|
219
|
+
const comment = comments[index]?.range;
|
|
220
|
+
if (comment === undefined || comment.start !== cursor) {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
if (!isStandaloneComment(text, comment) || !isCommentNormallyIgnored(text, comment)) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
cursor = comment.end;
|
|
227
|
+
}
|
|
228
|
+
const targetStart = skipWhitespace(text, cursor);
|
|
229
|
+
return targetStart >= text.length ? undefined : targetStart;
|
|
230
|
+
}
|
|
231
|
+
function skipWhitespace(text, index) {
|
|
232
|
+
let cursor = index;
|
|
233
|
+
while (cursor < text.length) {
|
|
234
|
+
const character = text[cursor];
|
|
235
|
+
if (character === undefined || !/\s/u.test(character)) {
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
cursor += 1;
|
|
239
|
+
}
|
|
240
|
+
return cursor;
|
|
241
|
+
}
|
|
242
|
+
function isCommentInIgnoredLineRange(comment, ignoredLineRanges) {
|
|
243
|
+
return ignoredLineRanges.some((range) => comment.start >= range.start && comment.start < range.end);
|
|
244
|
+
}
|
|
245
|
+
function getJsxExpressionBlockCommentLayout(text, comment, jsxExpressionContainers, tabWidth) {
|
|
246
|
+
const container = getSmallestContainingRange(comment, jsxExpressionContainers);
|
|
247
|
+
if (container === undefined || text[container.start] !== '{' || text[container.end - 1] !== '}') {
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
const expressionTextBeforeComment = text.slice(container.start + 1, comment.start).trim();
|
|
251
|
+
const expressionTextAfterComment = text.slice(comment.end, container.end - 1).trim();
|
|
252
|
+
const containerOutputColumn = getJsxExpressionContainerOutputColumn(text, container, tabWidth);
|
|
253
|
+
if (expressionTextBeforeComment === '' && expressionTextAfterComment === '') {
|
|
254
|
+
return { contentColumn: containerOutputColumn + tabWidth + 3, multilineIndent: '', placement: 'standalone' };
|
|
255
|
+
}
|
|
256
|
+
if (expressionTextBeforeComment !== '' && expressionTextAfterComment === '') {
|
|
257
|
+
const expressionStart = skipWhitespace(text, container.start + 1);
|
|
258
|
+
const expressionEnd = trimWhitespaceEnd(text, container.start + 1, comment.start);
|
|
259
|
+
const removalEnd = Math.min(skipWhitespace(text, comment.end), container.end - 1);
|
|
260
|
+
return {
|
|
261
|
+
contentColumn: containerOutputColumn + tabWidth + 3,
|
|
262
|
+
multilineIndent: '',
|
|
263
|
+
placement: 'trailing',
|
|
264
|
+
trailingMove: {
|
|
265
|
+
insertAt: expressionStart,
|
|
266
|
+
removeEnd: removalEnd,
|
|
267
|
+
removeStart: expressionEnd,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
return { placement: 'inline' };
|
|
272
|
+
}
|
|
273
|
+
function getJsxExpressionContainerOutputColumn(text, container, tabWidth) {
|
|
274
|
+
const linePrefix = getLinePrefix(text, container.start);
|
|
275
|
+
if (/^[ \t]*$/u.test(linePrefix)) {
|
|
276
|
+
return getColumns(linePrefix, tabWidth);
|
|
277
|
+
}
|
|
278
|
+
const lineIndent = /^[ \t]*/u.exec(linePrefix)?.[0] ?? '';
|
|
279
|
+
return getColumns(lineIndent, tabWidth) + tabWidth;
|
|
280
|
+
}
|
|
281
|
+
function trimWhitespaceEnd(text, start, end) {
|
|
282
|
+
let cursor = end;
|
|
283
|
+
while (cursor > start) {
|
|
284
|
+
const character = text[cursor - 1];
|
|
285
|
+
if (character === undefined || !/\s/u.test(character)) {
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
cursor -= 1;
|
|
289
|
+
}
|
|
290
|
+
return cursor;
|
|
291
|
+
}
|
|
292
|
+
function getSmallestContainingRange(comment, ranges) {
|
|
293
|
+
let containingRange;
|
|
294
|
+
for (const range of ranges) {
|
|
295
|
+
if (comment.start <= range.start || comment.end >= range.end) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (containingRange === undefined || range.end - range.start < containingRange.end - containingRange.start) {
|
|
299
|
+
containingRange = range;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return containingRange;
|
|
303
|
+
}
|
|
304
|
+
function isPrettierIgnoredBlockComment(text, comments, index) {
|
|
305
|
+
const comment = comments[index]?.range;
|
|
306
|
+
const previousComment = comments[index - 1]?.range;
|
|
307
|
+
if (comment === undefined || comment.kind !== 'block' || previousComment === undefined) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if (!isStandaloneComment(text, previousComment) || !isAdjacentPreviousComment(text, previousComment, comment)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return isPrettierIgnoreComment(getCommentBody(text, previousComment));
|
|
314
|
+
}
|
|
315
|
+
function isPrettierIgnoredTrailingLineComment(text, comments, index) {
|
|
316
|
+
const comment = comments[index]?.range;
|
|
317
|
+
if (comment === undefined || comment.kind !== 'line' || isStandaloneLineComment(text, comment)) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
let cursor = getLineStart(text, comment.start);
|
|
321
|
+
for (let previousIndex = index - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
322
|
+
const previousComment = comments[previousIndex]?.range;
|
|
323
|
+
if (previousComment !== undefined && previousComment.end > cursor) {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (previousComment === undefined || !isStandaloneComment(text, previousComment)) {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
if (!isAdjacentCommentBeforeIndex(text, previousComment, cursor)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const body = getCommentBody(text, previousComment);
|
|
333
|
+
if (previousComment.kind === 'line' && isPrettierIgnoreComment(body)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (!isCommentNormallyIgnored(text, previousComment)) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
cursor = getLineStart(text, previousComment.start);
|
|
340
|
+
}
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
function isCommentNormallyIgnored(text, comment) {
|
|
344
|
+
if (comment.kind === 'line') {
|
|
345
|
+
const raw = text.slice(comment.start, comment.end);
|
|
346
|
+
return shouldSkipLineComment(text, comment) && !isPrettierIgnoreComment(normalizeLineCommentBody(raw.slice(2)));
|
|
347
|
+
}
|
|
348
|
+
return isBlockCommentNormallyIgnored(text, comment);
|
|
349
|
+
}
|
|
350
|
+
function isBlockCommentNormallyIgnored(text, comment) {
|
|
351
|
+
const raw = text.slice(comment.start, comment.end);
|
|
352
|
+
if (raw.startsWith('/**') || hasPreserveCommentMarker(raw)) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
const body = normalizeBlockCommentBody(raw);
|
|
356
|
+
return body.trim() === '' || isDirectiveComment(body);
|
|
357
|
+
}
|
|
358
|
+
function isStandaloneComment(text, comment) {
|
|
359
|
+
return comment.kind === 'line' ? isStandaloneLineComment(text, comment) : isStandaloneBlockComment(text, comment);
|
|
360
|
+
}
|
|
361
|
+
function isAdjacentPreviousComment(text, previousComment, comment) {
|
|
362
|
+
return /^(?:\r\n|\n|\r)[ \t]*$/u.test(text.slice(previousComment.end, comment.start));
|
|
363
|
+
}
|
|
364
|
+
function isAdjacentCommentBeforeIndex(text, comment, index) {
|
|
365
|
+
return /^(?:\r\n|\n|\r)[ \t]*$/u.test(text.slice(comment.end, index));
|
|
366
|
+
}
|
|
367
|
+
function getCommentBody(text, comment) {
|
|
368
|
+
const raw = text.slice(comment.start, comment.end);
|
|
369
|
+
return comment.kind === 'line' ? normalizeLineCommentBody(raw.slice(2)) : normalizeBlockCommentBody(raw);
|
|
370
|
+
}
|
|
371
|
+
function isRecord(value) {
|
|
372
|
+
return typeof value === 'object' && value !== null;
|
|
373
|
+
}
|
|
374
|
+
function numberOrUndefined(value) {
|
|
375
|
+
return typeof value === 'number' ? value : undefined;
|
|
376
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,8 @@ declare const parsers: {
|
|
|
3
3
|
[parserName: string]: import("prettier").Parser<any>;
|
|
4
4
|
} | undefined;
|
|
5
5
|
declare const printers: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
6
|
+
[astFormat: string]: import("prettier").Printer<any>;
|
|
7
|
+
} | undefined;
|
|
9
8
|
declare const plugin: Plugin;
|
|
10
9
|
export { parsers, printers };
|
|
11
10
|
export default plugin;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as estreePlugin from 'prettier/plugins/estree';
|
|
2
1
|
import { buildParsers } from './plugin/parsers.js';
|
|
2
|
+
import { buildPrinters } from './plugin/printers.js';
|
|
3
3
|
const parsers = buildParsers();
|
|
4
|
-
const printers =
|
|
4
|
+
const printers = buildPrinters();
|
|
5
5
|
const plugin = {
|
|
6
6
|
parsers,
|
|
7
7
|
printers,
|
package/dist/plugin/parsers.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as babelPlugin from 'prettier/plugins/babel';
|
|
2
2
|
import * as typescriptPlugin from 'prettier/plugins/typescript';
|
|
3
|
-
import { wrapComments } from '../comments/wrap.js';
|
|
3
|
+
import { neutralizePrettierIgnoreForIgnoredBlockComments, wrapComments } from '../comments/wrap.js';
|
|
4
4
|
const parserNames = ['babel', 'babel-ts', 'typescript'];
|
|
5
5
|
function wrapParser(parser) {
|
|
6
6
|
return {
|
|
7
7
|
...parser,
|
|
8
|
+
async parse(text, options) {
|
|
9
|
+
const ast = await parser.parse(text, options);
|
|
10
|
+
return neutralizePrettierIgnoreForIgnoredBlockComments(text, ast);
|
|
11
|
+
},
|
|
8
12
|
async preprocess(text, options) {
|
|
9
13
|
const preprocessed = parser.preprocess === undefined ? text : await parser.preprocess(text, options);
|
|
10
14
|
let ast;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { doc } from 'prettier';
|
|
2
|
+
import * as estreePlugin from 'prettier/plugins/estree';
|
|
3
|
+
const { hardline, indent } = doc.builders;
|
|
4
|
+
export function buildPrinters() {
|
|
5
|
+
const estreePrinter = estreePlugin.printers.estree;
|
|
6
|
+
return {
|
|
7
|
+
...estreePlugin.printers,
|
|
8
|
+
estree: {
|
|
9
|
+
...estreePrinter,
|
|
10
|
+
print(path, options, print, args) {
|
|
11
|
+
if (isMultilineEmptyJsxExpressionBlockComment(path.node)) {
|
|
12
|
+
return ['{', indent([hardline, path.call(print, 'expression')]), hardline, '}'];
|
|
13
|
+
}
|
|
14
|
+
return estreePrinter.print(path, options, print, args);
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function isMultilineEmptyJsxExpressionBlockComment(node) {
|
|
20
|
+
if (!isRecord(node) || node['type'] !== 'JSXExpressionContainer') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const expression = node['expression'];
|
|
24
|
+
if (!isRecord(expression) || expression['type'] !== 'JSXEmptyExpression') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const comments = expression['comments'];
|
|
28
|
+
return Array.isArray(comments) && comments.some(isMultilineBlockComment);
|
|
29
|
+
}
|
|
30
|
+
function isMultilineBlockComment(comment) {
|
|
31
|
+
if (!isRecord(comment) || (comment['type'] !== 'Block' && comment['type'] !== 'CommentBlock')) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const value = comment['value'];
|
|
35
|
+
return typeof value === 'string' && value.includes('\n');
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return typeof value === 'object' && value !== null;
|
|
39
|
+
}
|
package/dist/shared/text.d.ts
CHANGED
package/dist/shared/text.js
CHANGED
|
@@ -11,13 +11,16 @@ function getNonOverlappingReplacements(replacements) {
|
|
|
11
11
|
.sort((left, right) => left.start - right.start || right.end - left.end)
|
|
12
12
|
.reduce((accepted, replacement) => {
|
|
13
13
|
const previous = accepted.at(-1);
|
|
14
|
-
if (previous === undefined || previous
|
|
14
|
+
if (previous === undefined || !rangesOverlap(previous, replacement)) {
|
|
15
15
|
accepted.push(replacement);
|
|
16
16
|
return accepted;
|
|
17
17
|
}
|
|
18
18
|
return accepted;
|
|
19
19
|
}, []);
|
|
20
20
|
}
|
|
21
|
+
function rangesOverlap(left, right) {
|
|
22
|
+
return left.start < right.end && right.start < left.end;
|
|
23
|
+
}
|
|
21
24
|
export function getPreferredNewline(text, options) {
|
|
22
25
|
if (options.endOfLine === 'crlf') {
|
|
23
26
|
return '\r\n';
|
|
@@ -85,7 +88,7 @@ export function trimBlankEdges(markdown) {
|
|
|
85
88
|
function isBlankLine(line) {
|
|
86
89
|
return line !== undefined && line.trim() === '';
|
|
87
90
|
}
|
|
88
|
-
function makeIndent(column, options) {
|
|
91
|
+
export function makeIndent(column, options) {
|
|
89
92
|
const tabWidth = getTabWidth(options);
|
|
90
93
|
if (options.useTabs === true) {
|
|
91
94
|
const tabs = Math.floor(column / tabWidth);
|
package/package.json
CHANGED