@aforemendude/prettier-plugin-wrap-comments 1.0.6 → 1.1.1

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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -8
  3. package/dist/comments/comment-body.d.ts +4 -0
  4. package/dist/comments/comment-body.js +36 -0
  5. package/dist/comments/comment-directives.d.ts +2 -0
  6. package/dist/comments/comment-directives.js +52 -0
  7. package/dist/comments/comment-eligibility.d.ts +5 -0
  8. package/dist/comments/comment-eligibility.js +23 -0
  9. package/dist/comments/comment-location.d.ts +9 -0
  10. package/dist/comments/comment-location.js +39 -0
  11. package/dist/comments/comment-ranges.d.ts +24 -0
  12. package/dist/comments/comment-ranges.js +36 -0
  13. package/dist/comments/embedded-expression-ranges.d.ts +12 -0
  14. package/dist/comments/embedded-expression-ranges.js +117 -0
  15. package/dist/comments/jsx-expression-layout.d.ts +10 -0
  16. package/dist/comments/jsx-expression-layout.js +114 -0
  17. package/dist/comments/line-comment-groups.d.ts +6 -0
  18. package/dist/comments/line-comment-groups.js +28 -0
  19. package/dist/comments/prettier-ignore.d.ts +9 -0
  20. package/dist/comments/prettier-ignore.js +239 -0
  21. package/dist/comments/printer-layout.d.ts +18 -0
  22. package/dist/comments/printer-layout.js +65 -0
  23. package/dist/comments/{block.d.ts → wrap-block-comment.d.ts} +5 -1
  24. package/dist/comments/{block.js → wrap-block-comment.js} +18 -15
  25. package/dist/comments/wrap-comments.d.ts +9 -0
  26. package/dist/comments/wrap-comments.js +169 -0
  27. package/dist/comments/wrap-line-comment-group.d.ts +4 -0
  28. package/dist/comments/wrap-line-comment-group.js +39 -0
  29. package/dist/comments/wrap-trailing-line-comment.d.ts +9 -0
  30. package/dist/comments/wrap-trailing-line-comment.js +78 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +4 -4
  33. package/dist/plugin/create-parsers.d.ts +2 -0
  34. package/dist/plugin/create-parsers.js +60 -0
  35. package/dist/plugin/create-printers.d.ts +2 -0
  36. package/dist/plugin/create-printers.js +59 -0
  37. package/dist/plugin/get-printer-layout-source.d.ts +4 -0
  38. package/dist/plugin/get-printer-layout-source.js +35 -0
  39. package/dist/plugin/jsx-comment-rewrite-metadata.d.ts +8 -0
  40. package/dist/plugin/jsx-comment-rewrite-metadata.js +40 -0
  41. package/dist/plugin/parser-names.d.ts +2 -0
  42. package/dist/plugin/parser-names.js +1 -0
  43. package/dist/utils/ast.d.ts +12 -0
  44. package/dist/utils/ast.js +95 -0
  45. package/dist/utils/display-width.d.ts +2 -0
  46. package/dist/utils/display-width.js +19 -0
  47. package/dist/{shared/markdown.d.ts → utils/format-markdown.d.ts} +1 -1
  48. package/dist/utils/format-markdown.js +69 -0
  49. package/dist/utils/indentation.d.ts +4 -0
  50. package/dist/utils/indentation.js +21 -0
  51. package/dist/utils/replacements.d.ts +6 -0
  52. package/dist/utils/replacements.js +21 -0
  53. package/dist/utils/source-lines.d.ts +7 -0
  54. package/dist/utils/source-lines.js +43 -0
  55. package/dist/utils/type-guards.d.ts +2 -0
  56. package/dist/utils/type-guards.js +6 -0
  57. package/dist/utils/whitespace.d.ts +3 -0
  58. package/dist/utils/whitespace.js +26 -0
  59. package/dist/{shared/options.d.ts → utils/wrap-options.d.ts} +2 -1
  60. package/package.json +27 -11
  61. package/dist/comments/core.d.ts +0 -8
  62. package/dist/comments/core.js +0 -114
  63. package/dist/comments/line.d.ts +0 -6
  64. package/dist/comments/line.js +0 -108
  65. package/dist/comments/wrap.d.ts +0 -3
  66. package/dist/comments/wrap.js +0 -439
  67. package/dist/plugin/parsers.d.ts +0 -2
  68. package/dist/plugin/parsers.js +0 -35
  69. package/dist/plugin/printers.d.ts +0 -2
  70. package/dist/plugin/printers.js +0 -39
  71. package/dist/shared/markdown.js +0 -20
  72. package/dist/shared/text.d.ts +0 -15
  73. package/dist/shared/text.js +0 -99
  74. package/dist/shared/types.d.ts +0 -30
  75. package/dist/shared/types.js +0 -1
  76. /package/dist/{shared/options.js → utils/wrap-options.js} +0 -0
@@ -0,0 +1,169 @@
1
+ import { shouldSkipLineComment } from './comment-eligibility.js';
2
+ import { isStandaloneBlockComment, isStandaloneLineComment } from './comment-location.js';
3
+ import { collectCommentEntries } from './comment-ranges.js';
4
+ import { collectEmbeddedExpressionRanges, doesBlockCommentSeparateEmbeddedTrailingLineComment, getEmbeddedTrailingLineCommentMove, } from './embedded-expression-ranges.js';
5
+ import { collectJsxExpressionContainerRanges, getJsxExpressionBlockCommentLayout } from './jsx-expression-layout.js';
6
+ import { collectLineCommentGroup } from './line-comment-groups.js';
7
+ import { collectPrettierIgnoredLineRanges, isCommentInIgnoredLineRange, isPrettierIgnoredBlockComment, isPrettierIgnoredStandaloneLineComment, isPrettierIgnoredTrailingLineComment, } from './prettier-ignore.js';
8
+ import { getPrinterLayout } from './printer-layout.js';
9
+ import { wrapBlockComment } from './wrap-block-comment.js';
10
+ import { wrapLineCommentGroup } from './wrap-line-comment-group.js';
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';
14
+ import { applyReplacements } from '../utils/replacements.js';
15
+ import { getLineEnd, getLineStart } from '../utils/source-lines.js';
16
+ import { getTabWidth } from '../utils/wrap-options.js';
17
+ export async function wrapComments(text, ast, options, printerLayoutSource) {
18
+ return (await wrapCommentsWithMetadata(text, ast, options, printerLayoutSource)).text;
19
+ }
20
+ export async function wrapCommentsWithMetadata(text, ast, options, printerLayoutSource) {
21
+ const commentEntries = collectCommentEntries(ast, text);
22
+ const comments = commentEntries.map((entry) => entry.range);
23
+ const embeddedExpressionRanges = collectEmbeddedExpressionRanges(ast);
24
+ const jsxExpressionContainers = collectJsxExpressionContainerRanges(ast);
25
+ const ignoredLineRanges = collectPrettierIgnoredLineRanges(text, ast, commentEntries);
26
+ const embeddedExpressionRangeMatches = matchOrderedRangesToSmallestContainers(comments, embeddedExpressionRanges);
27
+ const jsxExpressionContainerMatches = matchOrderedRangesToSmallestContainers(comments, jsxExpressionContainers);
28
+ if (comments.length === 0) {
29
+ return { jsxBlockCommentRewrites: [], text };
30
+ }
31
+ const replacements = [];
32
+ const jsxBlockCommentRewrites = [];
33
+ const tabWidth = getTabWidth(options);
34
+ const printerLayout = getPrinterLayout(text, commentEntries, jsxExpressionContainers, printerLayoutSource, tabWidth);
35
+ const sameLineWidthDeltas = {
36
+ output: new Map(),
37
+ source: new Map(),
38
+ };
39
+ let blockCommentIndex = -1;
40
+ let ignoredLineRangeIndex = 0;
41
+ for (let index = 0; index < comments.length; index += 1) {
42
+ const comment = comments[index];
43
+ const outputCommentLayout = printerLayout.comments[index];
44
+ if (comment?.kind === 'block') {
45
+ blockCommentIndex += 1;
46
+ }
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)) {
56
+ continue;
57
+ }
58
+ if (comment.kind === 'block') {
59
+ if (isPrettierIgnoredBlockComment(text, commentEntries, index)) {
60
+ continue;
61
+ }
62
+ const jsxLayout = getJsxExpressionBlockCommentLayout(text, comment, comments[index - 1], jsxExpressionContainerMatches[index], tabWidth, outputCommentLayout, printerLayout.jsxCommentMarkerColumns);
63
+ if (jsxLayout?.placement === 'inline') {
64
+ continue;
65
+ }
66
+ const outputLayout = outputCommentLayout === undefined
67
+ ? undefined
68
+ : {
69
+ markerColumn: outputCommentLayout.markerColumn,
70
+ placement: isStandaloneBlockComment(text, comment) ? 'standalone' : 'inline',
71
+ };
72
+ const replacement = await wrapBlockComment(text, comment, options, jsxLayout ?? outputLayout);
73
+ const preservesEmbeddedCommentOrder = doesBlockCommentSeparateEmbeddedTrailingLineComment(text, comment, comments[index + 1], embeddedExpressionRangeMatches[index]?.range, embeddedExpressionRangeMatches[index + 1]?.range);
74
+ if (Array.isArray(replacement)) {
75
+ if (!preservesEmbeddedCommentOrder) {
76
+ replacements.push(...replacement);
77
+ }
78
+ }
79
+ else if (replacement !== undefined) {
80
+ replacements.push(replacement);
81
+ recordSameLineReplacementWidthDelta(text, comment, replacement, outputCommentLayout, tabWidth, sameLineWidthDeltas);
82
+ if (jsxLayout !== undefined) {
83
+ jsxBlockCommentRewrites.push({ blockCommentIndex, text: replacement.text });
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+ if (shouldSkipLineComment(text, comment)) {
89
+ continue;
90
+ }
91
+ if (isPrettierIgnoredStandaloneLineComment(text, commentEntries, index)) {
92
+ index = collectLineCommentGroup(text, comments, index, tabWidth).endIndex;
93
+ continue;
94
+ }
95
+ if (!isStandaloneLineComment(text, comment)) {
96
+ if (isPrettierIgnoredTrailingLineComment(text, commentEntries, index)) {
97
+ continue;
98
+ }
99
+ const embeddedExpressionRange = embeddedExpressionRangeMatches[index]?.range;
100
+ const embeddedMove = getEmbeddedTrailingLineCommentMove(text, comment, embeddedExpressionRange);
101
+ if (embeddedMove === undefined && embeddedExpressionRange !== undefined) {
102
+ continue;
103
+ }
104
+ const trailingLayout = getTrailingLineCommentLayout(text, comment, outputCommentLayout, tabWidth, sameLineWidthDeltas);
105
+ const replacement = await wrapTrailingLineComment(text, comment, options, trailingLayout, embeddedMove);
106
+ if (replacement !== undefined) {
107
+ replacements.push(...replacement);
108
+ }
109
+ continue;
110
+ }
111
+ const group = collectLineCommentGroup(text, comments, index, tabWidth);
112
+ index = group.endIndex;
113
+ const replacement = await wrapLineCommentGroup(text, group.comments, options, outputCommentLayout?.markerColumn);
114
+ if (replacement !== undefined) {
115
+ replacements.push(replacement);
116
+ }
117
+ }
118
+ return {
119
+ jsxBlockCommentRewrites,
120
+ text: applyReplacements(text, replacements),
121
+ };
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
+ }
@@ -0,0 +1,4 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ import type { Replacement } from '../utils/replacements.js';
3
+ import type { WrapOptions } from '../utils/wrap-options.js';
4
+ export declare function wrapLineCommentGroup(text: string, comments: CommentRange[], options: WrapOptions, outputMarkerColumn?: number): Promise<Replacement | undefined>;
@@ -0,0 +1,39 @@
1
+ import { normalizeLineCommentBody } from './comment-body.js';
2
+ import { getColumnAt } from '../utils/display-width.js';
3
+ import { formatMarkdownLines } from '../utils/format-markdown.js';
4
+ import { getContinuationIndent } from '../utils/indentation.js';
5
+ import { getPreferredNewline } from '../utils/source-lines.js';
6
+ import { getAvailableContentWidth, getTabWidth } from '../utils/wrap-options.js';
7
+ export async function wrapLineCommentGroup(text, comments, options, outputMarkerColumn) {
8
+ const firstComment = comments[0];
9
+ if (firstComment === undefined) {
10
+ return undefined;
11
+ }
12
+ const lastComment = comments.at(-1) ?? firstComment;
13
+ const bodyLines = comments.map((comment) => normalizeLineCommentBody(text.slice(comment.start + 2, comment.end)));
14
+ if (bodyLines.every((line) => line.trim() === '')) {
15
+ return undefined;
16
+ }
17
+ const tabWidth = getTabWidth(options);
18
+ const markerColumn = outputMarkerColumn ?? getColumnAt(text, firstComment.start, tabWidth);
19
+ const availableWidth = getAvailableContentWidth(options, markerColumn + 3);
20
+ const formattedLines = await formatMarkdownLines(bodyLines.join('\n'), availableWidth, options);
21
+ const newline = getPreferredNewline(text, options);
22
+ const continuationIndent = getContinuationIndent(text, firstComment.start, markerColumn, options);
23
+ const replacementText = formattedLines
24
+ .map((line, index) => {
25
+ const commentText = line.length === 0 ? '//' : `// ${line}`;
26
+ return `${index === 0 ? '' : continuationIndent}${commentText}`;
27
+ })
28
+ .join(newline);
29
+ const start = firstComment.start;
30
+ const end = lastComment.end;
31
+ if (replacementText === text.slice(start, end)) {
32
+ return undefined;
33
+ }
34
+ return {
35
+ end,
36
+ start,
37
+ text: replacementText,
38
+ };
39
+ }
@@ -0,0 +1,9 @@
1
+ import type { CommentRange } from './comment-ranges.js';
2
+ import type { EmbeddedTrailingLineCommentMove } from './embedded-expression-ranges.js';
3
+ import type { Replacement } from '../utils/replacements.js';
4
+ import type { WrapOptions } from '../utils/wrap-options.js';
5
+ export type TrailingLineCommentLayout = {
6
+ lineIndentColumn?: number;
7
+ lineWidth: number;
8
+ };
9
+ export declare function wrapTrailingLineComment(text: string, comment: CommentRange, options: WrapOptions, outputLayout?: TrailingLineCommentLayout, move?: EmbeddedTrailingLineCommentMove): Promise<Replacement[] | undefined>;
@@ -0,0 +1,78 @@
1
+ import { normalizeLineCommentBody } from './comment-body.js';
2
+ import { getColumnAt, getColumns } from '../utils/display-width.js';
3
+ import { formatMarkdownLines } from '../utils/format-markdown.js';
4
+ import { getLeadingIndent, makeIndent } from '../utils/indentation.js';
5
+ import { getLineEnd, getLineStart, getPreferredNewline } from '../utils/source-lines.js';
6
+ import { getAvailableContentWidth, getPrintWidth, getTabWidth } from '../utils/wrap-options.js';
7
+ export async function wrapTrailingLineComment(text, comment, options, outputLayout, move) {
8
+ const lineStart = getLineStart(text, comment.start);
9
+ const lineEnd = getLineEnd(text, comment.start);
10
+ if (comment.end > lineEnd) {
11
+ return undefined;
12
+ }
13
+ if (isTrailingLineCommentWithinPrintWidth(text, lineStart, lineEnd, options, outputLayout?.lineWidth)) {
14
+ return undefined;
15
+ }
16
+ const linePrefix = text.slice(lineStart, comment.start);
17
+ const codeText = linePrefix.replace(/[ \t]+$/u, '');
18
+ if (codeText.trim() === '') {
19
+ return undefined;
20
+ }
21
+ const body = normalizeLineCommentBody(text.slice(comment.start + 2, comment.end));
22
+ if (body.trim() === '') {
23
+ return undefined;
24
+ }
25
+ const tabWidth = getTabWidth(options);
26
+ const markerColumn = move === undefined ? undefined : (outputLayout?.lineIndentColumn ?? getColumnAt(text, move.insertAt, tabWidth));
27
+ const indent = markerColumn === undefined
28
+ ? getTrailingCommentIndent(codeText, linePrefix, options, outputLayout?.lineIndentColumn)
29
+ : makeIndent(markerColumn, options);
30
+ const availableWidth = getAvailableContentWidth(options, getColumns(indent, tabWidth) + 3);
31
+ const formattedLines = await formatMarkdownLines(body, availableWidth, options);
32
+ const newline = getPreferredNewline(text, options);
33
+ const leadingCommentText = buildLeadingCommentText(formattedLines, indent, newline, move !== undefined);
34
+ const codeEnd = lineStart + codeText.length;
35
+ const insertAt = move?.insertAt ?? lineStart;
36
+ const insertionSuffix = move === undefined ? '' : indent;
37
+ return [
38
+ {
39
+ end: insertAt,
40
+ start: insertAt,
41
+ text: [leadingCommentText, insertionSuffix].join(newline),
42
+ },
43
+ {
44
+ end: lineEnd,
45
+ start: move?.removeStart ?? codeEnd,
46
+ text: '',
47
+ },
48
+ ];
49
+ }
50
+ function buildLeadingCommentText(formattedLines, indent, newline, startsAtExpression) {
51
+ return formattedLines
52
+ .map((line, index) => {
53
+ const lineIndent = startsAtExpression && index === 0 ? '' : indent;
54
+ return `${lineIndent}${line.length === 0 ? '//' : `// ${line}`}`;
55
+ })
56
+ .join(newline);
57
+ }
58
+ function isTrailingLineCommentWithinPrintWidth(text, lineStart, lineEnd, options, outputLineWidth) {
59
+ if (outputLineWidth !== undefined) {
60
+ return outputLineWidth <= getPrintWidth(options);
61
+ }
62
+ const tabWidth = getTabWidth(options);
63
+ const lineText = text.slice(lineStart, lineEnd).replace(/[ \t]+$/u, '');
64
+ return getColumns(lineText, tabWidth) <= getPrintWidth(options);
65
+ }
66
+ function getTrailingCommentIndent(codeText, linePrefix, options, outputLineIndentColumn) {
67
+ const indent = getLeadingIndent(linePrefix);
68
+ if (outputLineIndentColumn === undefined && !isClosingDelimiterLine(codeText)) {
69
+ return indent;
70
+ }
71
+ const tabWidth = getTabWidth(options);
72
+ const indentColumn = outputLineIndentColumn ?? getColumns(indent, tabWidth);
73
+ const commentIndentColumn = isClosingDelimiterLine(codeText) ? indentColumn + tabWidth : indentColumn;
74
+ return makeIndent(commentIndentColumn, options);
75
+ }
76
+ function isClosingDelimiterLine(codeText) {
77
+ return /^[ \t]*[\])}]+[\])};,]*[ \t]*$/u.test(codeText);
78
+ }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { Plugin } from 'prettier';
2
2
  declare const parsers: {
3
3
  [parserName: string]: import("prettier").Parser<any>;
4
- } | undefined;
4
+ };
5
5
  declare const printers: {
6
6
  [astFormat: string]: import("prettier").Printer<any>;
7
- } | undefined;
7
+ };
8
8
  declare const plugin: Plugin;
9
9
  export { parsers, printers };
10
10
  export default plugin;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { buildParsers } from './plugin/parsers.js';
2
- import { buildPrinters } from './plugin/printers.js';
3
- const parsers = buildParsers();
4
- const printers = buildPrinters();
1
+ import { createParsers } from './plugin/create-parsers.js';
2
+ import { createPrinters } from './plugin/create-printers.js';
3
+ const parsers = createParsers();
4
+ const printers = createPrinters();
5
5
  const plugin = {
6
6
  parsers,
7
7
  printers,
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'prettier';
2
+ export declare function createParsers(): NonNullable<Plugin['parsers']>;
@@ -0,0 +1,60 @@
1
+ import * as babelPlugin from 'prettier/plugins/babel';
2
+ import * as typescriptPlugin from 'prettier/plugins/typescript';
3
+ import { collectAstComments } from '../comments/comment-ranges.js';
4
+ import { neutralizePrettierIgnoreForIgnoredComments } from '../comments/prettier-ignore.js';
5
+ import { wrapCommentsWithMetadata } from '../comments/wrap-comments.js';
6
+ import { getPrinterLayoutSource } from './get-printer-layout-source.js';
7
+ import { markRewrittenJsxBlockComments, setJsxBlockCommentRewrites } from './jsx-comment-rewrite-metadata.js';
8
+ import { SUPPORTED_PARSER_NAMES } from './parser-names.js';
9
+ export function createParsers() {
10
+ const parsers = {};
11
+ for (const parserName of SUPPORTED_PARSER_NAMES) {
12
+ const sourceParsers = parserName === 'typescript' ? typescriptPlugin.parsers : babelPlugin.parsers;
13
+ const parser = sourceParsers[parserName];
14
+ if (parser !== undefined) {
15
+ parsers[parserName] = createWrappedParser(parserName, parser);
16
+ }
17
+ }
18
+ return parsers;
19
+ }
20
+ function createWrappedParser(parserName, parser) {
21
+ return {
22
+ ...parser,
23
+ async parse(text, options) {
24
+ const ast = await parser.parse(text, options);
25
+ const neutralizedAst = neutralizePrettierIgnoreForIgnoredComments(text, ast);
26
+ markRewrittenJsxBlockComments(text, neutralizedAst, options);
27
+ return neutralizedAst;
28
+ },
29
+ async preprocess(text, options) {
30
+ setJsxBlockCommentRewrites(options, []);
31
+ const preprocessed = parser.preprocess === undefined ? text : await parser.preprocess(text, options);
32
+ if (hasOffsetSensitiveFormatting(text, options)) {
33
+ return preprocessed;
34
+ }
35
+ if (!preprocessed.includes('//') && !preprocessed.includes('/*')) {
36
+ return preprocessed;
37
+ }
38
+ let ast;
39
+ try {
40
+ ast = await parser.parse(preprocessed, { ...options, parser: parserName });
41
+ }
42
+ catch {
43
+ return preprocessed;
44
+ }
45
+ if (collectAstComments(ast).length === 0) {
46
+ return preprocessed;
47
+ }
48
+ const printerLayoutSource = await getPrinterLayoutSource(preprocessed, ast, parserName, parser, options);
49
+ const result = await wrapCommentsWithMetadata(preprocessed, ast, options, printerLayoutSource);
50
+ setJsxBlockCommentRewrites(options, result.jsxBlockCommentRewrites);
51
+ return result.text;
52
+ },
53
+ };
54
+ }
55
+ function hasOffsetSensitiveFormatting(text, options) {
56
+ const cursorOffset = options['cursorOffset'];
57
+ const hasCursor = typeof cursorOffset === 'number' && cursorOffset >= 0;
58
+ const hasPartialRange = options.rangeStart > 0 || options.rangeEnd < text.length;
59
+ return hasCursor || hasPartialRange;
60
+ }
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'prettier';
2
+ export declare function createPrinters(): NonNullable<Plugin['printers']>;
@@ -0,0 +1,59 @@
1
+ import { doc } from 'prettier';
2
+ import * as estreePlugin from 'prettier/plugins/estree';
3
+ import { getNeutralizedPrettierIgnoreOriginalText } from '../comments/prettier-ignore.js';
4
+ import { normalizeLineTerminators } from '../utils/source-lines.js';
5
+ import { isRecord } from '../utils/type-guards.js';
6
+ import { isRewrittenJsxBlockComment } from './jsx-comment-rewrite-metadata.js';
7
+ const { hardline, indent } = doc.builders;
8
+ const { mapDoc, replaceEndOfLine } = doc.utils;
9
+ export function createPrinters() {
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
+ }
15
+ return {
16
+ estree: {
17
+ ...estreePrinter,
18
+ print(path, options, print, args) {
19
+ if (isRewrittenMultilineEmptyJsxExpressionBlockComment(path.node)) {
20
+ return ['{', indent([hardline, print('expression')]), hardline, '}'];
21
+ }
22
+ return estreePrinter.print(path, options, print, args);
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
+ },
39
+ },
40
+ };
41
+ }
42
+ function isRewrittenMultilineEmptyJsxExpressionBlockComment(node) {
43
+ if (!isRecord(node) || node['type'] !== 'JSXExpressionContainer') {
44
+ return false;
45
+ }
46
+ const expression = node['expression'];
47
+ if (!isRecord(expression) || expression['type'] !== 'JSXEmptyExpression') {
48
+ return false;
49
+ }
50
+ const comments = expression['comments'];
51
+ return Array.isArray(comments) && comments.some(isRewrittenMultilineBlockComment);
52
+ }
53
+ function isRewrittenMultilineBlockComment(comment) {
54
+ if (!isRecord(comment) || (comment['type'] !== 'Block' && comment['type'] !== 'CommentBlock')) {
55
+ return false;
56
+ }
57
+ const value = comment['value'];
58
+ return typeof value === 'string' && /[\r\n\u2028\u2029]/u.test(value) && isRewrittenJsxBlockComment(comment);
59
+ }
@@ -0,0 +1,4 @@
1
+ import type { Parser, ParserOptions } from 'prettier';
2
+ import type { PrinterLayoutSource } from '../comments/printer-layout.js';
3
+ import type { SupportedParserName } from './parser-names.js';
4
+ export declare function getPrinterLayoutSource<T>(text: string, ast: T, parserName: SupportedParserName, parser: Parser<T>, options: ParserOptions): Promise<PrinterLayoutSource | undefined>;
@@ -0,0 +1,35 @@
1
+ import { format } from 'prettier';
2
+ import { neutralizePrettierIgnoreForIgnoredComments } from '../comments/prettier-ignore.js';
3
+ import { createPrinters } from './create-printers.js';
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
+ };
12
+ const printerLayoutPlugin = {
13
+ parsers: { [parserName]: printerLayoutParser },
14
+ printers: createPrinters(),
15
+ };
16
+ try {
17
+ const formattedText = await format(text, {
18
+ ...options,
19
+ endOfLine: 'lf',
20
+ parser: parserName,
21
+ plugins: [printerLayoutPlugin],
22
+ });
23
+ if (formattedText === text) {
24
+ return { ast, text };
25
+ }
26
+ const formattedAst = await parser.parse(formattedText, { ...options, parser: parserName });
27
+ return {
28
+ ast: formattedAst,
29
+ text: formattedText,
30
+ };
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
@@ -0,0 +1,8 @@
1
+ import type { ParserOptions } from 'prettier';
2
+ export type JsxBlockCommentRewrite = {
3
+ blockCommentIndex: number;
4
+ text: string;
5
+ };
6
+ export declare function setJsxBlockCommentRewrites(options: ParserOptions, rewrites: readonly JsxBlockCommentRewrite[]): void;
7
+ export declare function markRewrittenJsxBlockComments(text: string, ast: unknown, options: ParserOptions): void;
8
+ export declare function isRewrittenJsxBlockComment(comment: unknown): boolean;
@@ -0,0 +1,40 @@
1
+ import { collectAstComments } from '../comments/comment-ranges.js';
2
+ import { getAstNodeRange } from '../utils/ast.js';
3
+ import { isRecord } from '../utils/type-guards.js';
4
+ const jsxBlockCommentRewritesKey = Symbol('jsxBlockCommentRewrites');
5
+ const rewrittenJsxBlockCommentKey = Symbol('rewrittenJsxBlockComment');
6
+ export function setJsxBlockCommentRewrites(options, rewrites) {
7
+ const metadataOptions = options;
8
+ if (rewrites.length === 0) {
9
+ delete metadataOptions[jsxBlockCommentRewritesKey];
10
+ return;
11
+ }
12
+ metadataOptions[jsxBlockCommentRewritesKey] = [...rewrites];
13
+ }
14
+ export function markRewrittenJsxBlockComments(text, ast, options) {
15
+ const metadataOptions = options;
16
+ const rewrites = metadataOptions[jsxBlockCommentRewritesKey];
17
+ delete metadataOptions[jsxBlockCommentRewritesKey];
18
+ if (rewrites === undefined) {
19
+ return;
20
+ }
21
+ const blockComments = collectAstComments(ast)
22
+ .filter(isBlockComment)
23
+ .sort((left, right) => getCommentStart(left) - getCommentStart(right));
24
+ for (const rewrite of rewrites) {
25
+ const comment = blockComments[rewrite.blockCommentIndex];
26
+ const range = comment === undefined ? undefined : getAstNodeRange(comment);
27
+ if (comment !== undefined && range !== undefined && text.slice(range.start, range.end) === rewrite.text) {
28
+ comment[rewrittenJsxBlockCommentKey] = true;
29
+ }
30
+ }
31
+ }
32
+ export function isRewrittenJsxBlockComment(comment) {
33
+ return isRecord(comment) && comment[rewrittenJsxBlockCommentKey] === true;
34
+ }
35
+ function isBlockComment(comment) {
36
+ return isRecord(comment) && (comment['type'] === 'Block' || comment['type'] === 'CommentBlock');
37
+ }
38
+ function getCommentStart(comment) {
39
+ return getAstNodeRange(comment)?.start ?? Number.POSITIVE_INFINITY;
40
+ }
@@ -0,0 +1,2 @@
1
+ export declare const SUPPORTED_PARSER_NAMES: readonly ['babel', 'babel-ts', 'typescript'];
2
+ export type SupportedParserName = (typeof SUPPORTED_PARSER_NAMES)[number];
@@ -0,0 +1 @@
1
+ export const SUPPORTED_PARSER_NAMES = ['babel', 'babel-ts', 'typescript'];
@@ -0,0 +1,12 @@
1
+ export type SourceRange = {
2
+ end: number;
3
+ start: number;
4
+ };
5
+ export type ContainingRangeMatch<Range extends SourceRange> = {
6
+ index: number;
7
+ range: Range;
8
+ };
9
+ export declare function visitAstNodes(ast: unknown, visitor: (node: Record<string, unknown>) => void): void;
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>;