@aforemendude/prettier-plugin-wrap-comments 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -6
- package/dist/comments/comment-body.js +2 -2
- package/dist/comments/comment-directives.js +4 -0
- package/dist/comments/comment-eligibility.d.ts +1 -0
- package/dist/comments/comment-eligibility.js +4 -1
- package/dist/comments/comment-location.js +24 -6
- package/dist/comments/embedded-expression-ranges.d.ts +2 -3
- package/dist/comments/embedded-expression-ranges.js +39 -27
- package/dist/comments/jsx-expression-layout.d.ts +2 -2
- package/dist/comments/jsx-expression-layout.js +16 -19
- package/dist/comments/prettier-ignore.d.ts +2 -1
- package/dist/comments/prettier-ignore.js +110 -16
- package/dist/comments/printer-layout.d.ts +1 -0
- package/dist/comments/printer-layout.js +1 -0
- package/dist/comments/wrap-block-comment.d.ts +1 -0
- package/dist/comments/wrap-block-comment.js +3 -1
- package/dist/comments/wrap-comments.js +75 -7
- package/dist/comments/wrap-trailing-line-comment.d.ts +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -1
- package/dist/plugin/create-parsers.js +92 -5
- package/dist/plugin/create-printers.js +23 -2
- package/dist/plugin/get-printer-layout-source.js +10 -2
- package/dist/plugin/plugin-name.d.ts +1 -0
- package/dist/plugin/plugin-name.js +1 -0
- package/dist/utils/ast.d.ts +6 -0
- package/dist/utils/ast.js +43 -0
- package/dist/utils/display-width.d.ts +1 -1
- package/dist/utils/display-width.js +3 -3
- package/dist/utils/format-markdown.js +50 -11
- package/dist/utils/source-lines.d.ts +1 -0
- package/dist/utils/source-lines.js +3 -0
- package/dist/utils/whitespace.d.ts +1 -0
- package/dist/utils/whitespace.js +4 -0
- package/package.json +15 -6
|
@@ -37,7 +37,9 @@ function buildBlockReplacement(text, comment, formattedLines, options, layout) {
|
|
|
37
37
|
const singleLine = `/* ${formattedLines.join(' ')} */`;
|
|
38
38
|
const singleLineWidth = getColumns(singleLine, tabWidth);
|
|
39
39
|
const singleLineSuffixWidth = layout.singleLineSuffixWidth ?? 0;
|
|
40
|
-
if (
|
|
40
|
+
if (layout.preserveMultiline !== true &&
|
|
41
|
+
formattedLines.length === 1 &&
|
|
42
|
+
markerColumn + singleLineWidth + singleLineSuffixWidth <= getPrintWidth(options)) {
|
|
41
43
|
return singleLine;
|
|
42
44
|
}
|
|
43
45
|
if (layout.placement === 'inline') {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { shouldSkipLineComment } from './comment-eligibility.js';
|
|
2
2
|
import { isStandaloneBlockComment, isStandaloneLineComment } from './comment-location.js';
|
|
3
3
|
import { collectCommentEntries } from './comment-ranges.js';
|
|
4
|
-
import { collectEmbeddedExpressionRanges, doesBlockCommentSeparateEmbeddedTrailingLineComment, getEmbeddedTrailingLineCommentMove,
|
|
4
|
+
import { collectEmbeddedExpressionRanges, doesBlockCommentSeparateEmbeddedTrailingLineComment, getEmbeddedTrailingLineCommentMove, } from './embedded-expression-ranges.js';
|
|
5
5
|
import { collectJsxExpressionContainerRanges, getJsxExpressionBlockCommentLayout } from './jsx-expression-layout.js';
|
|
6
6
|
import { collectLineCommentGroup } from './line-comment-groups.js';
|
|
7
7
|
import { collectPrettierIgnoredLineRanges, isCommentInIgnoredLineRange, isPrettierIgnoredBlockComment, isPrettierIgnoredStandaloneLineComment, isPrettierIgnoredTrailingLineComment, } from './prettier-ignore.js';
|
|
@@ -9,7 +9,10 @@ import { getPrinterLayout } from './printer-layout.js';
|
|
|
9
9
|
import { wrapBlockComment } from './wrap-block-comment.js';
|
|
10
10
|
import { wrapLineCommentGroup } from './wrap-line-comment-group.js';
|
|
11
11
|
import { wrapTrailingLineComment } from './wrap-trailing-line-comment.js';
|
|
12
|
+
import { matchOrderedRangesToSmallestContainers } from '../utils/ast.js';
|
|
13
|
+
import { getColumnAt, getColumns } from '../utils/display-width.js';
|
|
12
14
|
import { applyReplacements } from '../utils/replacements.js';
|
|
15
|
+
import { getLineEnd, getLineStart } from '../utils/source-lines.js';
|
|
13
16
|
import { getTabWidth } from '../utils/wrap-options.js';
|
|
14
17
|
export async function wrapComments(text, ast, options, printerLayoutSource) {
|
|
15
18
|
return (await wrapCommentsWithMetadata(text, ast, options, printerLayoutSource)).text;
|
|
@@ -20,6 +23,8 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
20
23
|
const embeddedExpressionRanges = collectEmbeddedExpressionRanges(ast);
|
|
21
24
|
const jsxExpressionContainers = collectJsxExpressionContainerRanges(ast);
|
|
22
25
|
const ignoredLineRanges = collectPrettierIgnoredLineRanges(text, ast, commentEntries);
|
|
26
|
+
const embeddedExpressionRangeMatches = matchOrderedRangesToSmallestContainers(comments, embeddedExpressionRanges);
|
|
27
|
+
const jsxExpressionContainerMatches = matchOrderedRangesToSmallestContainers(comments, jsxExpressionContainers);
|
|
23
28
|
if (comments.length === 0) {
|
|
24
29
|
return { jsxBlockCommentRewrites: [], text };
|
|
25
30
|
}
|
|
@@ -27,21 +32,34 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
27
32
|
const jsxBlockCommentRewrites = [];
|
|
28
33
|
const tabWidth = getTabWidth(options);
|
|
29
34
|
const printerLayout = getPrinterLayout(text, commentEntries, jsxExpressionContainers, printerLayoutSource, tabWidth);
|
|
35
|
+
const sameLineWidthDeltas = {
|
|
36
|
+
output: new Map(),
|
|
37
|
+
source: new Map(),
|
|
38
|
+
};
|
|
30
39
|
let blockCommentIndex = -1;
|
|
40
|
+
let ignoredLineRangeIndex = 0;
|
|
31
41
|
for (let index = 0; index < comments.length; index += 1) {
|
|
32
42
|
const comment = comments[index];
|
|
33
43
|
const outputCommentLayout = printerLayout.comments[index];
|
|
34
44
|
if (comment?.kind === 'block') {
|
|
35
45
|
blockCommentIndex += 1;
|
|
36
46
|
}
|
|
37
|
-
if (comment === undefined
|
|
47
|
+
if (comment === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
let ignoredLineRange = ignoredLineRanges[ignoredLineRangeIndex];
|
|
51
|
+
while (ignoredLineRange !== undefined && ignoredLineRange.end <= comment.start) {
|
|
52
|
+
ignoredLineRangeIndex += 1;
|
|
53
|
+
ignoredLineRange = ignoredLineRanges[ignoredLineRangeIndex];
|
|
54
|
+
}
|
|
55
|
+
if (isCommentInIgnoredLineRange(comment, ignoredLineRange)) {
|
|
38
56
|
continue;
|
|
39
57
|
}
|
|
40
58
|
if (comment.kind === 'block') {
|
|
41
59
|
if (isPrettierIgnoredBlockComment(text, commentEntries, index)) {
|
|
42
60
|
continue;
|
|
43
61
|
}
|
|
44
|
-
const jsxLayout = getJsxExpressionBlockCommentLayout(text, comment, comments[index - 1],
|
|
62
|
+
const jsxLayout = getJsxExpressionBlockCommentLayout(text, comment, comments[index - 1], jsxExpressionContainerMatches[index], tabWidth, outputCommentLayout, printerLayout.jsxCommentMarkerColumns);
|
|
45
63
|
if (jsxLayout?.placement === 'inline') {
|
|
46
64
|
continue;
|
|
47
65
|
}
|
|
@@ -52,7 +70,7 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
52
70
|
placement: isStandaloneBlockComment(text, comment) ? 'standalone' : 'inline',
|
|
53
71
|
};
|
|
54
72
|
const replacement = await wrapBlockComment(text, comment, options, jsxLayout ?? outputLayout);
|
|
55
|
-
const preservesEmbeddedCommentOrder = doesBlockCommentSeparateEmbeddedTrailingLineComment(text, comment, comments[index + 1],
|
|
73
|
+
const preservesEmbeddedCommentOrder = doesBlockCommentSeparateEmbeddedTrailingLineComment(text, comment, comments[index + 1], embeddedExpressionRangeMatches[index]?.range, embeddedExpressionRangeMatches[index + 1]?.range);
|
|
56
74
|
if (Array.isArray(replacement)) {
|
|
57
75
|
if (!preservesEmbeddedCommentOrder) {
|
|
58
76
|
replacements.push(...replacement);
|
|
@@ -60,6 +78,7 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
60
78
|
}
|
|
61
79
|
else if (replacement !== undefined) {
|
|
62
80
|
replacements.push(replacement);
|
|
81
|
+
recordSameLineReplacementWidthDelta(text, comment, replacement, outputCommentLayout, tabWidth, sameLineWidthDeltas);
|
|
63
82
|
if (jsxLayout !== undefined) {
|
|
64
83
|
jsxBlockCommentRewrites.push({ blockCommentIndex, text: replacement.text });
|
|
65
84
|
}
|
|
@@ -77,11 +96,13 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
77
96
|
if (isPrettierIgnoredTrailingLineComment(text, commentEntries, index)) {
|
|
78
97
|
continue;
|
|
79
98
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
99
|
+
const embeddedExpressionRange = embeddedExpressionRangeMatches[index]?.range;
|
|
100
|
+
const embeddedMove = getEmbeddedTrailingLineCommentMove(text, comment, embeddedExpressionRange);
|
|
101
|
+
if (embeddedMove === undefined && embeddedExpressionRange !== undefined) {
|
|
82
102
|
continue;
|
|
83
103
|
}
|
|
84
|
-
const
|
|
104
|
+
const trailingLayout = getTrailingLineCommentLayout(text, comment, outputCommentLayout, tabWidth, sameLineWidthDeltas);
|
|
105
|
+
const replacement = await wrapTrailingLineComment(text, comment, options, trailingLayout, embeddedMove);
|
|
85
106
|
if (replacement !== undefined) {
|
|
86
107
|
replacements.push(...replacement);
|
|
87
108
|
}
|
|
@@ -99,3 +120,50 @@ export async function wrapCommentsWithMetadata(text, ast, options, printerLayout
|
|
|
99
120
|
text: applyReplacements(text, replacements),
|
|
100
121
|
};
|
|
101
122
|
}
|
|
123
|
+
function recordSameLineReplacementWidthDelta(text, comment, replacement, outputLayout, tabWidth, sameLineWidthDeltas) {
|
|
124
|
+
const original = text.slice(comment.start, comment.end);
|
|
125
|
+
if (containsLineTerminator(original) || containsLineTerminator(replacement.text)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const sourceLineStart = getLineStart(text, comment.start);
|
|
129
|
+
const sourceWidthDelta = sameLineWidthDeltas.source.get(sourceLineStart) ?? 0;
|
|
130
|
+
const sourceMarkerColumn = getColumnAt(text, comment.start, tabWidth) + sourceWidthDelta;
|
|
131
|
+
const sourceReplacementWidthDelta = getReplacementWidthDelta(original, replacement.text, sourceMarkerColumn, tabWidth);
|
|
132
|
+
addLineWidthDelta(sameLineWidthDeltas.source, sourceLineStart, sourceReplacementWidthDelta);
|
|
133
|
+
if (outputLayout === undefined) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const outputWidthDelta = sameLineWidthDeltas.output.get(outputLayout.lineStart) ?? 0;
|
|
137
|
+
const outputMarkerColumn = outputLayout.markerColumn + outputWidthDelta;
|
|
138
|
+
const outputReplacementWidthDelta = getReplacementWidthDelta(original, replacement.text, outputMarkerColumn, tabWidth);
|
|
139
|
+
addLineWidthDelta(sameLineWidthDeltas.output, outputLayout.lineStart, outputReplacementWidthDelta);
|
|
140
|
+
}
|
|
141
|
+
function getTrailingLineCommentLayout(text, comment, outputLayout, tabWidth, sameLineWidthDeltas) {
|
|
142
|
+
if (outputLayout !== undefined) {
|
|
143
|
+
const lineWidthDelta = sameLineWidthDeltas.output.get(outputLayout.lineStart) ?? 0;
|
|
144
|
+
return lineWidthDelta === 0
|
|
145
|
+
? outputLayout
|
|
146
|
+
: { ...outputLayout, lineWidth: outputLayout.lineWidth + lineWidthDelta };
|
|
147
|
+
}
|
|
148
|
+
const lineStart = getLineStart(text, comment.start);
|
|
149
|
+
const lineWidthDelta = sameLineWidthDeltas.source.get(lineStart) ?? 0;
|
|
150
|
+
if (lineWidthDelta === 0) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
const lineEnd = getLineEnd(text, comment.start);
|
|
154
|
+
const lineText = text.slice(lineStart, lineEnd).replace(/[ \t]+$/u, '');
|
|
155
|
+
return { lineWidth: getColumns(lineText, tabWidth) + lineWidthDelta };
|
|
156
|
+
}
|
|
157
|
+
function getReplacementWidthDelta(original, replacement, markerColumn, tabWidth) {
|
|
158
|
+
const originalWidth = getColumns(original, tabWidth, markerColumn);
|
|
159
|
+
const replacementWidth = getColumns(replacement, tabWidth, markerColumn);
|
|
160
|
+
return replacementWidth - originalWidth;
|
|
161
|
+
}
|
|
162
|
+
function addLineWidthDelta(lineWidthDeltas, lineStart, delta) {
|
|
163
|
+
if (delta !== 0) {
|
|
164
|
+
lineWidthDeltas.set(lineStart, (lineWidthDeltas.get(lineStart) ?? 0) + delta);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function containsLineTerminator(text) {
|
|
168
|
+
return /[\r\n\u2028\u2029]/u.test(text);
|
|
169
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { EmbeddedTrailingLineCommentMove } from './embedded-expression-rang
|
|
|
3
3
|
import type { Replacement } from '../utils/replacements.js';
|
|
4
4
|
import type { WrapOptions } from '../utils/wrap-options.js';
|
|
5
5
|
export type TrailingLineCommentLayout = {
|
|
6
|
-
lineIndentColumn
|
|
6
|
+
lineIndentColumn?: number;
|
|
7
7
|
lineWidth: number;
|
|
8
8
|
};
|
|
9
9
|
export declare function wrapTrailingLineComment(text: string, comment: CommentRange, options: WrapOptions, outputLayout?: TrailingLineCommentLayout, move?: EmbeddedTrailingLineCommentMove): Promise<Replacement[] | undefined>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Plugin } from 'prettier';
|
|
2
|
+
declare const name = "@aforemendude/prettier-plugin-wrap-comments";
|
|
2
3
|
declare const parsers: {
|
|
3
4
|
[parserName: string]: import("prettier").Parser<any>;
|
|
4
5
|
};
|
|
5
6
|
declare const printers: {
|
|
6
7
|
[astFormat: string]: import("prettier").Printer<any>;
|
|
7
8
|
};
|
|
8
|
-
declare const plugin:
|
|
9
|
-
export { parsers, printers };
|
|
9
|
+
declare const plugin: NamedPlugin;
|
|
10
|
+
export { name, parsers, printers };
|
|
10
11
|
export default plugin;
|
|
12
|
+
interface NamedPlugin extends Plugin {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { createParsers } from './plugin/create-parsers.js';
|
|
2
2
|
import { createPrinters } from './plugin/create-printers.js';
|
|
3
|
+
import { PLUGIN_NAME } from './plugin/plugin-name.js';
|
|
4
|
+
const name = PLUGIN_NAME;
|
|
3
5
|
const parsers = createParsers();
|
|
4
6
|
const printers = createPrinters();
|
|
5
7
|
const plugin = {
|
|
8
|
+
name,
|
|
6
9
|
parsers,
|
|
7
10
|
printers,
|
|
8
11
|
};
|
|
9
|
-
export { parsers, printers };
|
|
12
|
+
export { name, parsers, printers };
|
|
10
13
|
export default plugin;
|
|
@@ -6,6 +6,7 @@ import { wrapCommentsWithMetadata } from '../comments/wrap-comments.js';
|
|
|
6
6
|
import { getPrinterLayoutSource } from './get-printer-layout-source.js';
|
|
7
7
|
import { markRewrittenJsxBlockComments, setJsxBlockCommentRewrites } from './jsx-comment-rewrite-metadata.js';
|
|
8
8
|
import { SUPPORTED_PARSER_NAMES } from './parser-names.js';
|
|
9
|
+
import { PLUGIN_NAME } from './plugin-name.js';
|
|
9
10
|
export function createParsers() {
|
|
10
11
|
const parsers = {};
|
|
11
12
|
for (const parserName of SUPPORTED_PARSER_NAMES) {
|
|
@@ -18,23 +19,28 @@ export function createParsers() {
|
|
|
18
19
|
return parsers;
|
|
19
20
|
}
|
|
20
21
|
function createWrappedParser(parserName, parser) {
|
|
21
|
-
|
|
22
|
+
const wrappedParser = {
|
|
22
23
|
...parser,
|
|
23
24
|
async parse(text, options) {
|
|
24
|
-
const
|
|
25
|
+
const delegate = await resolveParserDelegate(parserName, parser, wrappedParser, options);
|
|
26
|
+
const ast = await parseWithDelegate(text, delegate, options);
|
|
25
27
|
const neutralizedAst = neutralizePrettierIgnoreForIgnoredComments(text, ast);
|
|
26
28
|
markRewrittenJsxBlockComments(text, neutralizedAst, options);
|
|
27
29
|
return neutralizedAst;
|
|
28
30
|
},
|
|
29
31
|
async preprocess(text, options) {
|
|
30
32
|
setJsxBlockCommentRewrites(options, []);
|
|
31
|
-
const
|
|
33
|
+
const delegate = await resolveParserDelegate(parserName, parser, wrappedParser, options);
|
|
34
|
+
const preprocessed = await preprocessWithDelegate(text, delegate, options);
|
|
32
35
|
if (hasOffsetSensitiveFormatting(text, options)) {
|
|
33
36
|
return preprocessed;
|
|
34
37
|
}
|
|
38
|
+
if (!preprocessed.includes('//') && !preprocessed.includes('/*')) {
|
|
39
|
+
return preprocessed;
|
|
40
|
+
}
|
|
35
41
|
let ast;
|
|
36
42
|
try {
|
|
37
|
-
ast = await
|
|
43
|
+
ast = await parseWithDelegate(preprocessed, delegate, { ...options, parser: parserName });
|
|
38
44
|
}
|
|
39
45
|
catch {
|
|
40
46
|
return preprocessed;
|
|
@@ -42,12 +48,93 @@ function createWrappedParser(parserName, parser) {
|
|
|
42
48
|
if (collectAstComments(ast).length === 0) {
|
|
43
49
|
return preprocessed;
|
|
44
50
|
}
|
|
45
|
-
const printerLayoutSource = await getPrinterLayoutSource(preprocessed, ast, parserName, parser, options);
|
|
51
|
+
const printerLayoutSource = await getPrinterLayoutSource(preprocessed, ast, parserName, delegate.parser, createDelegateOptions(options, delegate.plugins));
|
|
46
52
|
const result = await wrapCommentsWithMetadata(preprocessed, ast, options, printerLayoutSource);
|
|
47
53
|
setJsxBlockCommentRewrites(options, result.jsxBlockCommentRewrites);
|
|
48
54
|
return result.text;
|
|
49
55
|
},
|
|
50
56
|
};
|
|
57
|
+
return wrappedParser;
|
|
58
|
+
}
|
|
59
|
+
async function resolveParserDelegate(parserName, nativeParser, wrappedParser, options) {
|
|
60
|
+
const plugins = options.plugins ?? [];
|
|
61
|
+
const wrappedPluginIndex = findWrappedPluginIndex(plugins, parserName, wrappedParser);
|
|
62
|
+
if (wrappedPluginIndex === -1) {
|
|
63
|
+
return { parser: nativeParser };
|
|
64
|
+
}
|
|
65
|
+
for (let index = wrappedPluginIndex - 1; index >= 0; index -= 1) {
|
|
66
|
+
const plugin = getPlugin(plugins[index]);
|
|
67
|
+
const parserEntry = getParserEntry(plugin, parserName);
|
|
68
|
+
if (parserEntry === undefined) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const precedingParser = typeof parserEntry === 'function' ? await parserEntry() : parserEntry;
|
|
72
|
+
if (isParser(precedingParser)) {
|
|
73
|
+
return {
|
|
74
|
+
parser: precedingParser,
|
|
75
|
+
plugins: plugins.slice(0, wrappedPluginIndex),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { parser: nativeParser };
|
|
80
|
+
}
|
|
81
|
+
async function parseWithDelegate(text, delegate, options) {
|
|
82
|
+
return withDelegatePlugins(options, delegate.plugins, async () => delegate.parser.parse(text, options));
|
|
83
|
+
}
|
|
84
|
+
async function preprocessWithDelegate(text, delegate, options) {
|
|
85
|
+
const preprocess = delegate.parser.preprocess;
|
|
86
|
+
if (preprocess === undefined) {
|
|
87
|
+
return text;
|
|
88
|
+
}
|
|
89
|
+
return withDelegatePlugins(options, delegate.plugins, async () => preprocess(text, options));
|
|
90
|
+
}
|
|
91
|
+
async function withDelegatePlugins(options, plugins, callback) {
|
|
92
|
+
if (plugins === undefined) {
|
|
93
|
+
return callback();
|
|
94
|
+
}
|
|
95
|
+
const originalPlugins = options.plugins;
|
|
96
|
+
options.plugins = plugins;
|
|
97
|
+
try {
|
|
98
|
+
return await callback();
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
options.plugins = originalPlugins;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function createDelegateOptions(options, plugins) {
|
|
105
|
+
return plugins === undefined ? options : { ...options, plugins };
|
|
106
|
+
}
|
|
107
|
+
function findWrappedPluginIndex(plugins, parserName, wrappedParser) {
|
|
108
|
+
for (let index = plugins.length - 1; index >= 0; index -= 1) {
|
|
109
|
+
const plugin = getPlugin(plugins[index]);
|
|
110
|
+
const parserEntry = getParserEntry(plugin, parserName);
|
|
111
|
+
if (parserEntry === wrappedParser) {
|
|
112
|
+
return index;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (let index = plugins.length - 1; index >= 0; index -= 1) {
|
|
116
|
+
const plugin = getPlugin(plugins[index]);
|
|
117
|
+
const parserEntry = getParserEntry(plugin, parserName);
|
|
118
|
+
if (plugin?.name === PLUGIN_NAME && parserEntry !== undefined) {
|
|
119
|
+
return index;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return -1;
|
|
123
|
+
}
|
|
124
|
+
function getPlugin(plugin) {
|
|
125
|
+
if (typeof plugin !== 'object' || plugin === null || plugin instanceof URL) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return plugin;
|
|
129
|
+
}
|
|
130
|
+
function getParserEntry(plugin, parserName) {
|
|
131
|
+
if (plugin?.parsers === undefined || !Object.hasOwn(plugin.parsers, parserName)) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return plugin.parsers[parserName];
|
|
135
|
+
}
|
|
136
|
+
function isParser(value) {
|
|
137
|
+
return typeof value === 'object' && value !== null && 'parse' in value && typeof value.parse === 'function';
|
|
51
138
|
}
|
|
52
139
|
function hasOffsetSensitiveFormatting(text, options) {
|
|
53
140
|
const cursorOffset = options['cursorOffset'];
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { doc } from 'prettier';
|
|
2
2
|
import * as estreePlugin from 'prettier/plugins/estree';
|
|
3
|
+
import { getNeutralizedPrettierIgnoreOriginalText } from '../comments/prettier-ignore.js';
|
|
4
|
+
import { normalizeLineTerminators } from '../utils/source-lines.js';
|
|
3
5
|
import { isRecord } from '../utils/type-guards.js';
|
|
4
6
|
import { isRewrittenJsxBlockComment } from './jsx-comment-rewrite-metadata.js';
|
|
5
7
|
const { hardline, indent } = doc.builders;
|
|
8
|
+
const { mapDoc, replaceEndOfLine } = doc.utils;
|
|
6
9
|
export function createPrinters() {
|
|
7
10
|
const estreePrinter = estreePlugin.printers.estree;
|
|
11
|
+
const estreePrintComment = estreePrinter.printComment;
|
|
12
|
+
if (estreePrintComment === undefined) {
|
|
13
|
+
throw new Error('Expected the native estree printer to provide printComment');
|
|
14
|
+
}
|
|
8
15
|
return {
|
|
9
|
-
...estreePlugin.printers,
|
|
10
16
|
estree: {
|
|
11
17
|
...estreePrinter,
|
|
12
18
|
print(path, options, print, args) {
|
|
@@ -15,6 +21,21 @@ export function createPrinters() {
|
|
|
15
21
|
}
|
|
16
22
|
return estreePrinter.print(path, options, print, args);
|
|
17
23
|
},
|
|
24
|
+
printComment(path, options) {
|
|
25
|
+
const originalText = getNeutralizedPrettierIgnoreOriginalText(path.node);
|
|
26
|
+
if (originalText !== undefined) {
|
|
27
|
+
const normalizedOriginalText = normalizeLineTerminators(originalText);
|
|
28
|
+
return normalizedOriginalText.includes('\n')
|
|
29
|
+
? replaceEndOfLine(normalizedOriginalText)
|
|
30
|
+
: normalizedOriginalText;
|
|
31
|
+
}
|
|
32
|
+
if (isRewrittenMultilineBlockComment(path.node)) {
|
|
33
|
+
const printedComment = estreePrintComment(path, options);
|
|
34
|
+
const normalizedComment = mapDoc(printedComment, (currentDoc) => typeof currentDoc === 'string' ? normalizeLineTerminators(currentDoc) : currentDoc);
|
|
35
|
+
return replaceEndOfLine(normalizedComment, hardline);
|
|
36
|
+
}
|
|
37
|
+
return estreePrintComment(path, options);
|
|
38
|
+
},
|
|
18
39
|
},
|
|
19
40
|
};
|
|
20
41
|
}
|
|
@@ -34,5 +55,5 @@ function isRewrittenMultilineBlockComment(comment) {
|
|
|
34
55
|
return false;
|
|
35
56
|
}
|
|
36
57
|
const value = comment['value'];
|
|
37
|
-
return typeof value === 'string' &&
|
|
58
|
+
return typeof value === 'string' && /[\r\n\u2028\u2029]/u.test(value) && isRewrittenJsxBlockComment(comment);
|
|
38
59
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { format } from 'prettier';
|
|
2
|
+
import { neutralizePrettierIgnoreForIgnoredComments } from '../comments/prettier-ignore.js';
|
|
2
3
|
import { createPrinters } from './create-printers.js';
|
|
3
4
|
export async function getPrinterLayoutSource(text, ast, parserName, parser, options) {
|
|
5
|
+
const printerLayoutParser = {
|
|
6
|
+
...parser,
|
|
7
|
+
async parse(source, parserOptions) {
|
|
8
|
+
const printerLayoutAst = await parser.parse(source, parserOptions);
|
|
9
|
+
return neutralizePrettierIgnoreForIgnoredComments(source, printerLayoutAst);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
4
12
|
const printerLayoutPlugin = {
|
|
5
|
-
parsers: { [parserName]:
|
|
13
|
+
parsers: { [parserName]: printerLayoutParser },
|
|
6
14
|
printers: createPrinters(),
|
|
7
15
|
};
|
|
8
16
|
try {
|
|
@@ -15,7 +23,7 @@ export async function getPrinterLayoutSource(text, ast, parserName, parser, opti
|
|
|
15
23
|
if (formattedText === text) {
|
|
16
24
|
return { ast, text };
|
|
17
25
|
}
|
|
18
|
-
const formattedAst = await parser.parse(formattedText, options);
|
|
26
|
+
const formattedAst = await parser.parse(formattedText, { ...options, parser: parserName });
|
|
19
27
|
return {
|
|
20
28
|
ast: formattedAst,
|
|
21
29
|
text: formattedText,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_NAME = "@aforemendude/prettier-plugin-wrap-comments";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PLUGIN_NAME = '@aforemendude/prettier-plugin-wrap-comments';
|
package/dist/utils/ast.d.ts
CHANGED
|
@@ -2,5 +2,11 @@ export type SourceRange = {
|
|
|
2
2
|
end: number;
|
|
3
3
|
start: number;
|
|
4
4
|
};
|
|
5
|
+
export type ContainingRangeMatch<Range extends SourceRange> = {
|
|
6
|
+
index: number;
|
|
7
|
+
range: Range;
|
|
8
|
+
};
|
|
5
9
|
export declare function visitAstNodes(ast: unknown, visitor: (node: Record<string, unknown>) => void): void;
|
|
6
10
|
export declare function getAstNodeRange(node: Record<string, unknown>): SourceRange | undefined;
|
|
11
|
+
export declare function collectAstNodeRangesByStart(ast: unknown): Map<number, SourceRange>;
|
|
12
|
+
export declare function matchOrderedRangesToSmallestContainers<Range extends SourceRange>(targets: readonly SourceRange[], containers: readonly Range[]): Array<ContainingRangeMatch<Range> | undefined>;
|
package/dist/utils/ast.js
CHANGED
|
@@ -44,6 +44,49 @@ export function getAstNodeRange(node) {
|
|
|
44
44
|
}
|
|
45
45
|
return { end, start };
|
|
46
46
|
}
|
|
47
|
+
export function collectAstNodeRangesByStart(ast) {
|
|
48
|
+
const rangesByStart = new Map();
|
|
49
|
+
visitAstNodes(ast, (node) => {
|
|
50
|
+
const range = getAstNodeRange(node);
|
|
51
|
+
if (range === undefined) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const existingRange = rangesByStart.get(range.start);
|
|
55
|
+
if (existingRange === undefined || range.end > existingRange.end) {
|
|
56
|
+
rangesByStart.set(range.start, range);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return rangesByStart;
|
|
60
|
+
}
|
|
61
|
+
export function matchOrderedRangesToSmallestContainers(targets, containers) {
|
|
62
|
+
const matches = [];
|
|
63
|
+
const openContainers = [];
|
|
64
|
+
let containerIndex = 0;
|
|
65
|
+
for (const target of targets) {
|
|
66
|
+
let container = containers[containerIndex];
|
|
67
|
+
while (container !== undefined && container.start < target.start) {
|
|
68
|
+
let previousContainer = openContainers[openContainers.length - 1];
|
|
69
|
+
while (previousContainer !== undefined && previousContainer.range.end <= container.start) {
|
|
70
|
+
openContainers.pop();
|
|
71
|
+
previousContainer = openContainers[openContainers.length - 1];
|
|
72
|
+
}
|
|
73
|
+
if (previousContainer === undefined ||
|
|
74
|
+
previousContainer.range.start !== container.start ||
|
|
75
|
+
previousContainer.range.end !== container.end) {
|
|
76
|
+
openContainers.push({ index: containerIndex, range: container });
|
|
77
|
+
}
|
|
78
|
+
containerIndex += 1;
|
|
79
|
+
container = containers[containerIndex];
|
|
80
|
+
}
|
|
81
|
+
let containingRange = openContainers[openContainers.length - 1];
|
|
82
|
+
while (containingRange !== undefined && containingRange.range.end <= target.end) {
|
|
83
|
+
openContainers.pop();
|
|
84
|
+
containingRange = openContainers[openContainers.length - 1];
|
|
85
|
+
}
|
|
86
|
+
matches.push(containingRange);
|
|
87
|
+
}
|
|
88
|
+
return matches;
|
|
89
|
+
}
|
|
47
90
|
function getRangeNumber(range, index) {
|
|
48
91
|
if (!Array.isArray(range)) {
|
|
49
92
|
return undefined;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function getColumnAt(text: string, index: number, tabWidth: number): number;
|
|
2
|
-
export declare function getColumns(text: string, tabWidth: number): number;
|
|
2
|
+
export declare function getColumns(text: string, tabWidth: number, startColumn?: number): number;
|
|
@@ -3,8 +3,8 @@ import { getLineStart } from './source-lines.js';
|
|
|
3
3
|
export function getColumnAt(text, index, tabWidth) {
|
|
4
4
|
return getColumns(text.slice(getLineStart(text, index), index), tabWidth);
|
|
5
5
|
}
|
|
6
|
-
export function getColumns(text, tabWidth) {
|
|
7
|
-
let column =
|
|
6
|
+
export function getColumns(text, tabWidth, startColumn = 0) {
|
|
7
|
+
let column = startColumn;
|
|
8
8
|
let segmentStart = 0;
|
|
9
9
|
for (let index = 0; index < text.length; index += 1) {
|
|
10
10
|
if (text[index] === '\t') {
|
|
@@ -15,5 +15,5 @@ export function getColumns(text, tabWidth) {
|
|
|
15
15
|
segmentStart = index + 1;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
return column + util.getStringWidth(text.slice(segmentStart));
|
|
18
|
+
return column + util.getStringWidth(text.slice(segmentStart)) - startColumn;
|
|
19
19
|
}
|
|
@@ -1,22 +1,61 @@
|
|
|
1
1
|
import { format } from 'prettier';
|
|
2
|
-
import { isBlankLine } from './source-lines.js';
|
|
2
|
+
import { isBlankLine, normalizeLineTerminators } from './source-lines.js';
|
|
3
3
|
import { getTabWidth } from './wrap-options.js';
|
|
4
|
+
const MAX_MARKDOWN_FORMAT_PASSES = 3;
|
|
4
5
|
export async function formatMarkdownLines(markdown, printWidth, options) {
|
|
5
|
-
const normalized = trimBlankEdges(markdown
|
|
6
|
+
const normalized = trimBlankEdges(normalizeLineTerminators(markdown));
|
|
7
|
+
let formatted;
|
|
6
8
|
try {
|
|
7
|
-
|
|
8
|
-
endOfLine: 'lf',
|
|
9
|
-
parser: 'markdown',
|
|
10
|
-
printWidth,
|
|
11
|
-
proseWrap: 'always',
|
|
12
|
-
tabWidth: getTabWidth(options),
|
|
13
|
-
useTabs: options.useTabs,
|
|
14
|
-
});
|
|
15
|
-
return formatted.replace(/\n$/, '').split('\n');
|
|
9
|
+
formatted = await formatMarkdown(normalized, printWidth, options);
|
|
16
10
|
}
|
|
17
11
|
catch {
|
|
18
12
|
return normalized.split('\n');
|
|
19
13
|
}
|
|
14
|
+
if (startsWithThematicRule(normalized)) {
|
|
15
|
+
formatted = await stabilizePotentialFrontMatter(formatted, printWidth, options);
|
|
16
|
+
}
|
|
17
|
+
return formatted.replace(/\n$/, '').split('\n');
|
|
18
|
+
}
|
|
19
|
+
async function formatMarkdown(markdown, printWidth, options) {
|
|
20
|
+
return format(markdown, {
|
|
21
|
+
endOfLine: 'lf',
|
|
22
|
+
parser: 'markdown',
|
|
23
|
+
printWidth,
|
|
24
|
+
proseWrap: 'always',
|
|
25
|
+
tabWidth: getTabWidth(options),
|
|
26
|
+
useTabs: options.useTabs,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function stabilizePotentialFrontMatter(formatted, printWidth, options) {
|
|
30
|
+
const seen = new Set([formatted]);
|
|
31
|
+
let current = formatted;
|
|
32
|
+
for (let pass = 1; pass < MAX_MARKDOWN_FORMAT_PASSES && hasPotentialFrontMatterAmbiguity(current); pass += 1) {
|
|
33
|
+
let next;
|
|
34
|
+
try {
|
|
35
|
+
next = await formatMarkdown(current, printWidth, options);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
if (next === current || seen.has(next)) {
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
43
|
+
seen.add(next);
|
|
44
|
+
current = next;
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
function startsWithThematicRule(markdown) {
|
|
49
|
+
const [firstLine = ''] = markdown.split('\n', 1);
|
|
50
|
+
return /^[ \t]{0,3}([*_-])(?:[ \t]*\1){2,}[ \t]*$/u.test(firstLine);
|
|
51
|
+
}
|
|
52
|
+
function hasPotentialFrontMatterAmbiguity(markdown) {
|
|
53
|
+
const lines = markdown.replace(/\n$/u, '').split('\n');
|
|
54
|
+
if (lines[0] !== '---') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const closingDelimiterIndex = lines.indexOf('---', 1);
|
|
58
|
+
return closingDelimiterIndex > 1 && (isBlankLine(lines[1]) || isBlankLine(lines[closingDelimiterIndex - 1]));
|
|
20
59
|
}
|
|
21
60
|
function trimBlankEdges(markdown) {
|
|
22
61
|
const lines = markdown.split('\n');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WrapOptions } from './wrap-options.js';
|
|
2
|
+
export declare function normalizeLineTerminators(text: string): string;
|
|
2
3
|
export declare function getPreferredNewline(text: string, options: WrapOptions): string;
|
|
3
4
|
export declare function getLinePrefix(text: string, index: number): string;
|
|
4
5
|
export declare function getLineStart(text: string, index: number): number;
|
package/dist/utils/whitespace.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const ECMASCRIPT_HORIZONTAL_WHITESPACE_PATTERN = /^[\t\u000b\u000c \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]$/u;
|
|
2
|
+
export function isEcmaScriptHorizontalWhitespace(character) {
|
|
3
|
+
return ECMASCRIPT_HORIZONTAL_WHITESPACE_PATTERN.test(character);
|
|
4
|
+
}
|
|
1
5
|
export function skipWhitespace(text, index) {
|
|
2
6
|
let cursor = index;
|
|
3
7
|
while (cursor < text.length) {
|