@aforemendude/prettier-plugin-wrap-comments 1.0.2 → 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 +90 -26
- package/dist/comments/block.d.ts +11 -1
- package/dist/comments/block.js +39 -10
- package/dist/comments/core.d.ts +2 -0
- package/dist/comments/core.js +49 -1
- package/dist/comments/line.d.ts +1 -1
- package/dist/comments/line.js +28 -10
- 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 +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# @aforemendude/prettier-plugin-wrap-comments
|
|
2
2
|
|
|
3
|
-
A Prettier plugin that wraps non-JSDoc JavaScript and TypeScript comments as Markdown. It uses
|
|
4
|
-
column to calculate the available content width, so nested comments wrap more narrowly than top-level comments.
|
|
3
|
+
A Prettier plugin that wraps non-JSDoc JavaScript and TypeScript comments as Markdown. It uses each comment marker's
|
|
4
|
+
real column to calculate the available content width, so nested comments wrap more narrowly than top-level comments.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
|
|
8
|
+
- Node.js 18 or newer
|
|
9
|
+
- Prettier 3 or newer
|
|
5
10
|
|
|
6
11
|
## Install
|
|
7
12
|
|
|
@@ -27,8 +32,17 @@ npx prettier --write .
|
|
|
27
32
|
|
|
28
33
|
## Behavior
|
|
29
34
|
|
|
30
|
-
The plugin wraps
|
|
31
|
-
Prettier
|
|
35
|
+
The plugin wraps comments for Prettier's `babel`, `babel-ts`, and `typescript` parsers. It runs during parser
|
|
36
|
+
preprocessing: the underlying Prettier parser preprocesses and parses the source first, the plugin rewrites eligible
|
|
37
|
+
comments from that parsed comment list, and Prettier then formats the rewritten source with its built-in JavaScript and
|
|
38
|
+
TypeScript printers. If the parser cannot parse the preprocessed source, the plugin leaves the source unchanged.
|
|
39
|
+
|
|
40
|
+
Comment text is normalized and reflowed with Prettier's Markdown parser. The available content width is based on
|
|
41
|
+
Prettier's `printWidth` minus the column where the comment text starts. `tabWidth`, `useTabs`, and `endOfLine` are used
|
|
42
|
+
when measuring and rebuilding comments.
|
|
43
|
+
|
|
44
|
+
Standalone `//` comments are wrapped in place. Adjacent standalone line comments are combined and reflowed as one
|
|
45
|
+
Markdown block when they are directly next to each other and their `//` markers start in the same column.
|
|
32
46
|
|
|
33
47
|
```ts
|
|
34
48
|
function example() {
|
|
@@ -38,18 +52,78 @@ function example() {
|
|
|
38
52
|
}
|
|
39
53
|
```
|
|
40
54
|
|
|
41
|
-
|
|
55
|
+
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.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
function example() {
|
|
60
|
+
// This trailing comment moved above the statement because the original line
|
|
61
|
+
// was too long.
|
|
62
|
+
const value = 1;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Non-JSDoc `/* ... */` comments are also normalized as Markdown. A block comment may stay on one line if the normalized
|
|
67
|
+
comment fits within `printWidth`; otherwise, only standalone block comments are expanded into star-prefixed blocks. Long
|
|
68
|
+
inline block comments are left unchanged when they cannot fit on one line.
|
|
42
69
|
|
|
43
70
|
```ts
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
if (ready) {
|
|
72
|
+
/*
|
|
73
|
+
* This block comment is wrapped with the nested indentation included in the
|
|
74
|
+
* available width calculation.
|
|
75
|
+
*/
|
|
76
|
+
run();
|
|
77
|
+
}
|
|
47
78
|
```
|
|
48
79
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
|
|
118
|
+
The plugin leaves these comments unchanged:
|
|
119
|
+
|
|
120
|
+
- JSDoc comments that start with `/**`
|
|
121
|
+
- bang-preserved comments that start with `/*!` or `//!`
|
|
122
|
+
- TypeScript-style triple-slash line comments that start with `///`
|
|
123
|
+
- empty comment bodies
|
|
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
|
|
53
127
|
|
|
54
128
|
## Supported Parsers
|
|
55
129
|
|
|
@@ -61,19 +135,9 @@ and wrapped there.
|
|
|
61
135
|
|
|
62
136
|
```sh
|
|
63
137
|
npm install
|
|
64
|
-
npm run check
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
## Publishing
|
|
68
|
-
|
|
69
|
-
The npm package name is `@aforemendude/prettier-plugin-wrap-comments`.
|
|
70
|
-
|
|
71
|
-
Before publishing, make sure the version in `package.json` has been updated, then run:
|
|
72
|
-
|
|
73
|
-
```sh
|
|
74
|
-
npm run check
|
|
75
|
-
npm publish --access public
|
|
138
|
+
npm run format:check
|
|
139
|
+
npm run test
|
|
76
140
|
```
|
|
77
141
|
|
|
78
|
-
|
|
79
|
-
|
|
142
|
+
`npm run test` builds `dist` before running the Node test suite. `npm run verify` runs `npm install`,
|
|
143
|
+
`npm run format:check`, and `npm run test`.
|
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
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { isDirectiveComment, normalizeBlockCommentBody } from './core.js';
|
|
1
|
+
import { hasPreserveCommentMarker, isDirectiveComment, normalizeBlockCommentBody } from './core.js';
|
|
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
|
-
if (raw.startsWith('/**')) {
|
|
7
|
+
if (raw.startsWith('/**') || hasPreserveCommentMarker(raw)) {
|
|
8
8
|
return undefined;
|
|
9
9
|
}
|
|
10
10
|
const markdown = normalizeBlockCommentBody(raw);
|
|
@@ -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,3 +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;
|
|
8
|
+
export declare function hasPreserveCommentMarker(rawComment: string): boolean;
|
package/dist/comments/core.js
CHANGED
|
@@ -63,8 +63,56 @@ export function normalizeBlockCommentBody(rawComment) {
|
|
|
63
63
|
})
|
|
64
64
|
.join('\n');
|
|
65
65
|
}
|
|
66
|
+
const PRAGMA_DIRECTIVE_COMMENT_PATTERNS = [
|
|
67
|
+
/^@(?:license|preserve)\b/u,
|
|
68
|
+
/^@(?:jsxFrag|jsxImportSource|jsxRuntime|jsx)\b/u,
|
|
69
|
+
/^@(?:ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b/u,
|
|
70
|
+
/^[@#]__(?:NO_SIDE_EFFECTS|PURE)__\b/u,
|
|
71
|
+
];
|
|
72
|
+
const SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS = [
|
|
73
|
+
/^[#@][ \t]*sourceMappingURL=/u,
|
|
74
|
+
/^[#@][ \t]*sourceURL=/u,
|
|
75
|
+
/^sourceMappingURL=/u,
|
|
76
|
+
/^sourceURL=/u,
|
|
77
|
+
];
|
|
78
|
+
const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
|
|
79
|
+
/^biome-ignore\b/u,
|
|
80
|
+
/^c8\b/u,
|
|
81
|
+
/^deno-lint-ignore\b/u,
|
|
82
|
+
/^eslint\b/u,
|
|
83
|
+
/^eslint-/u,
|
|
84
|
+
/^exported\b/u,
|
|
85
|
+
/^globals?\b/u,
|
|
86
|
+
/^istanbul\b/u,
|
|
87
|
+
/^jshint\b/u,
|
|
88
|
+
/^nyc\b/u,
|
|
89
|
+
/^oxlint\b/u,
|
|
90
|
+
/^prettier-ignore\b/u,
|
|
91
|
+
/^prettier-ignore-start\b/u,
|
|
92
|
+
/^prettier-ignore-end\b/u,
|
|
93
|
+
/^stylelint\b/u,
|
|
94
|
+
/^tslint\b/u,
|
|
95
|
+
/^v8\b/u,
|
|
96
|
+
/^vite-ignore\b/u,
|
|
97
|
+
];
|
|
98
|
+
const BUNDLER_DIRECTIVE_COMMENT_PATTERNS = [
|
|
99
|
+
/^webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b/u,
|
|
100
|
+
];
|
|
101
|
+
const DIRECTIVE_COMMENT_PATTERNS = [
|
|
102
|
+
...PRAGMA_DIRECTIVE_COMMENT_PATTERNS,
|
|
103
|
+
...SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS,
|
|
104
|
+
...TOOL_DIRECTIVE_COMMENT_PATTERNS,
|
|
105
|
+
...BUNDLER_DIRECTIVE_COMMENT_PATTERNS,
|
|
106
|
+
];
|
|
66
107
|
export function isDirectiveComment(body) {
|
|
67
|
-
|
|
108
|
+
const normalizedBody = body.trimStart();
|
|
109
|
+
return DIRECTIVE_COMMENT_PATTERNS.some((pattern) => pattern.test(normalizedBody));
|
|
110
|
+
}
|
|
111
|
+
export function isPrettierIgnoreComment(body) {
|
|
112
|
+
return body.trim() === 'prettier-ignore';
|
|
113
|
+
}
|
|
114
|
+
export function hasPreserveCommentMarker(rawComment) {
|
|
115
|
+
return rawComment.startsWith('/*!') || rawComment.startsWith('//!');
|
|
68
116
|
}
|
|
69
117
|
function isBlankLine(line) {
|
|
70
118
|
return line !== undefined && line.trim() === '';
|
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
|
-
import { isDirectiveComment, normalizeLineCommentBody } from './core.js';
|
|
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,23 +51,30 @@ 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);
|
|
70
|
-
if (raw.startsWith('///')) {
|
|
77
|
+
if (raw.startsWith('///') || hasPreserveCommentMarker(raw)) {
|
|
71
78
|
return true;
|
|
72
79
|
}
|
|
73
80
|
return isDirectiveComment(normalizeLineCommentBody(raw.slice(2)));
|
|
@@ -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
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { getTabWidth } from './options.js';
|
|
2
2
|
export function applyReplacements(text, replacements) {
|
|
3
3
|
let result = text;
|
|
4
|
-
for (const replacement of
|
|
4
|
+
for (const replacement of getNonOverlappingReplacements(replacements).sort((left, right) => right.start - left.start)) {
|
|
5
5
|
result = result.slice(0, replacement.start) + replacement.text + result.slice(replacement.end);
|
|
6
6
|
}
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
|
+
function getNonOverlappingReplacements(replacements) {
|
|
10
|
+
return [...replacements]
|
|
11
|
+
.sort((left, right) => left.start - right.start || right.end - left.end)
|
|
12
|
+
.reduce((accepted, replacement) => {
|
|
13
|
+
const previous = accepted.at(-1);
|
|
14
|
+
if (previous === undefined || !rangesOverlap(previous, replacement)) {
|
|
15
|
+
accepted.push(replacement);
|
|
16
|
+
return accepted;
|
|
17
|
+
}
|
|
18
|
+
return accepted;
|
|
19
|
+
}, []);
|
|
20
|
+
}
|
|
21
|
+
function rangesOverlap(left, right) {
|
|
22
|
+
return left.start < right.end && right.start < left.end;
|
|
23
|
+
}
|
|
9
24
|
export function getPreferredNewline(text, options) {
|
|
10
25
|
if (options.endOfLine === 'crlf') {
|
|
11
26
|
return '\r\n';
|
|
@@ -73,7 +88,7 @@ export function trimBlankEdges(markdown) {
|
|
|
73
88
|
function isBlankLine(line) {
|
|
74
89
|
return line !== undefined && line.trim() === '';
|
|
75
90
|
}
|
|
76
|
-
function makeIndent(column, options) {
|
|
91
|
+
export function makeIndent(column, options) {
|
|
77
92
|
const tabWidth = getTabWidth(options);
|
|
78
93
|
if (options.useTabs === true) {
|
|
79
94
|
const tabs = Math.floor(column / tabWidth);
|
package/package.json
CHANGED