@aforemendude/prettier-plugin-wrap-comments 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -14
  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 +48 -0
  7. package/dist/comments/comment-eligibility.d.ts +4 -0
  8. package/dist/comments/comment-eligibility.js +20 -0
  9. package/dist/comments/comment-location.d.ts +9 -0
  10. package/dist/comments/comment-location.js +21 -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 +13 -0
  14. package/dist/comments/embedded-expression-ranges.js +105 -0
  15. package/dist/comments/jsx-expression-layout.d.ts +10 -0
  16. package/dist/comments/jsx-expression-layout.js +117 -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 +8 -0
  20. package/dist/comments/prettier-ignore.js +145 -0
  21. package/dist/comments/printer-layout.d.ts +17 -0
  22. package/dist/comments/printer-layout.js +64 -0
  23. package/dist/comments/{block.d.ts → wrap-block-comment.d.ts} +9 -1
  24. package/dist/comments/{block.js → wrap-block-comment.js} +30 -14
  25. package/dist/comments/wrap-comments.d.ts +9 -0
  26. package/dist/comments/wrap-comments.js +101 -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 +57 -0
  35. package/dist/plugin/create-printers.d.ts +2 -0
  36. package/dist/plugin/{printers.js → create-printers.js} +9 -10
  37. package/dist/plugin/get-printer-layout-source.d.ts +4 -0
  38. package/dist/plugin/get-printer-layout-source.js +27 -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 +6 -0
  44. package/dist/utils/ast.js +52 -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/{shared/markdown.js → utils/format-markdown.js} +12 -2
  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 +6 -0
  54. package/dist/utils/source-lines.js +40 -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 +2 -0
  58. package/dist/utils/whitespace.js +22 -0
  59. package/dist/{shared/options.d.ts → utils/wrap-options.d.ts} +2 -1
  60. package/package.json +17 -9
  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 -375
  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/shared/text.d.ts +0 -15
  71. package/dist/shared/text.js +0 -99
  72. package/dist/shared/types.d.ts +0 -30
  73. package/dist/shared/types.js +0 -1
  74. /package/dist/{shared/options.js → utils/wrap-options.js} +0 -0
@@ -0,0 +1,57 @@
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
+ let ast;
36
+ try {
37
+ ast = await parser.parse(preprocessed, options);
38
+ }
39
+ catch {
40
+ return preprocessed;
41
+ }
42
+ if (collectAstComments(ast).length === 0) {
43
+ return preprocessed;
44
+ }
45
+ const printerLayoutSource = await getPrinterLayoutSource(preprocessed, ast, parserName, parser, options);
46
+ const result = await wrapCommentsWithMetadata(preprocessed, ast, options, printerLayoutSource);
47
+ setJsxBlockCommentRewrites(options, result.jsxBlockCommentRewrites);
48
+ return result.text;
49
+ },
50
+ };
51
+ }
52
+ function hasOffsetSensitiveFormatting(text, options) {
53
+ const cursorOffset = options['cursorOffset'];
54
+ const hasCursor = typeof cursorOffset === 'number' && cursorOffset >= 0;
55
+ const hasPartialRange = options.rangeStart > 0 || options.rangeEnd < text.length;
56
+ return hasCursor || hasPartialRange;
57
+ }
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'prettier';
2
+ export declare function createPrinters(): NonNullable<Plugin['printers']>;
@@ -1,22 +1,24 @@
1
1
  import { doc } from 'prettier';
2
2
  import * as estreePlugin from 'prettier/plugins/estree';
3
+ import { isRecord } from '../utils/type-guards.js';
4
+ import { isRewrittenJsxBlockComment } from './jsx-comment-rewrite-metadata.js';
3
5
  const { hardline, indent } = doc.builders;
4
- export function buildPrinters() {
6
+ export function createPrinters() {
5
7
  const estreePrinter = estreePlugin.printers.estree;
6
8
  return {
7
9
  ...estreePlugin.printers,
8
10
  estree: {
9
11
  ...estreePrinter,
10
12
  print(path, options, print, args) {
11
- if (isMultilineEmptyJsxExpressionBlockComment(path.node)) {
12
- return ['{', indent([hardline, path.call(print, 'expression')]), hardline, '}'];
13
+ if (isRewrittenMultilineEmptyJsxExpressionBlockComment(path.node)) {
14
+ return ['{', indent([hardline, print('expression')]), hardline, '}'];
13
15
  }
14
16
  return estreePrinter.print(path, options, print, args);
15
17
  },
16
18
  },
17
19
  };
18
20
  }
19
- function isMultilineEmptyJsxExpressionBlockComment(node) {
21
+ function isRewrittenMultilineEmptyJsxExpressionBlockComment(node) {
20
22
  if (!isRecord(node) || node['type'] !== 'JSXExpressionContainer') {
21
23
  return false;
22
24
  }
@@ -25,15 +27,12 @@ function isMultilineEmptyJsxExpressionBlockComment(node) {
25
27
  return false;
26
28
  }
27
29
  const comments = expression['comments'];
28
- return Array.isArray(comments) && comments.some(isMultilineBlockComment);
30
+ return Array.isArray(comments) && comments.some(isRewrittenMultilineBlockComment);
29
31
  }
30
- function isMultilineBlockComment(comment) {
32
+ function isRewrittenMultilineBlockComment(comment) {
31
33
  if (!isRecord(comment) || (comment['type'] !== 'Block' && comment['type'] !== 'CommentBlock')) {
32
34
  return false;
33
35
  }
34
36
  const value = comment['value'];
35
- return typeof value === 'string' && value.includes('\n');
36
- }
37
- function isRecord(value) {
38
- return typeof value === 'object' && value !== null;
37
+ return typeof value === 'string' && value.includes('\n') && isRewrittenJsxBlockComment(comment);
39
38
  }
@@ -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,27 @@
1
+ import { format } from 'prettier';
2
+ import { createPrinters } from './create-printers.js';
3
+ export async function getPrinterLayoutSource(text, ast, parserName, parser, options) {
4
+ const printerLayoutPlugin = {
5
+ parsers: { [parserName]: parser },
6
+ printers: createPrinters(),
7
+ };
8
+ try {
9
+ const formattedText = await format(text, {
10
+ ...options,
11
+ endOfLine: 'lf',
12
+ parser: parserName,
13
+ plugins: [printerLayoutPlugin],
14
+ });
15
+ if (formattedText === text) {
16
+ return { ast, text };
17
+ }
18
+ const formattedAst = await parser.parse(formattedText, options);
19
+ return {
20
+ ast: formattedAst,
21
+ text: formattedText,
22
+ };
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
@@ -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,6 @@
1
+ export type SourceRange = {
2
+ end: number;
3
+ start: number;
4
+ };
5
+ export declare function visitAstNodes(ast: unknown, visitor: (node: Record<string, unknown>) => void): void;
6
+ export declare function getAstNodeRange(node: Record<string, unknown>): SourceRange | undefined;
@@ -0,0 +1,52 @@
1
+ import { isRecord, numberOrUndefined } from './type-guards.js';
2
+ const AST_TRAVERSAL_SKIP_KEYS = new Set([
3
+ 'comments',
4
+ 'errors',
5
+ 'innerComments',
6
+ 'leadingComments',
7
+ 'loc',
8
+ 'parent',
9
+ 'range',
10
+ 'tokens',
11
+ 'trailingComments',
12
+ ]);
13
+ export function visitAstNodes(ast, visitor) {
14
+ const seen = new Set();
15
+ visit(ast);
16
+ function visit(value) {
17
+ if (!isRecord(value) || seen.has(value)) {
18
+ return;
19
+ }
20
+ seen.add(value);
21
+ if (typeof value['type'] === 'string') {
22
+ visitor(value);
23
+ }
24
+ for (const [key, child] of Object.entries(value)) {
25
+ if (AST_TRAVERSAL_SKIP_KEYS.has(key)) {
26
+ continue;
27
+ }
28
+ if (Array.isArray(child)) {
29
+ for (const item of child) {
30
+ visit(item);
31
+ }
32
+ }
33
+ else {
34
+ visit(child);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ export function getAstNodeRange(node) {
40
+ const start = numberOrUndefined(node['start']) ?? getRangeNumber(node['range'], 0);
41
+ const end = numberOrUndefined(node['end']) ?? getRangeNumber(node['range'], 1);
42
+ if (start === undefined || end === undefined || start >= end) {
43
+ return undefined;
44
+ }
45
+ return { end, start };
46
+ }
47
+ function getRangeNumber(range, index) {
48
+ if (!Array.isArray(range)) {
49
+ return undefined;
50
+ }
51
+ return numberOrUndefined(range[index]);
52
+ }
@@ -0,0 +1,2 @@
1
+ export declare function getColumnAt(text: string, index: number, tabWidth: number): number;
2
+ export declare function getColumns(text: string, tabWidth: number): number;
@@ -0,0 +1,19 @@
1
+ import { util } from 'prettier';
2
+ import { getLineStart } from './source-lines.js';
3
+ export function getColumnAt(text, index, tabWidth) {
4
+ return getColumns(text.slice(getLineStart(text, index), index), tabWidth);
5
+ }
6
+ export function getColumns(text, tabWidth) {
7
+ let column = 0;
8
+ let segmentStart = 0;
9
+ for (let index = 0; index < text.length; index += 1) {
10
+ if (text[index] === '\t') {
11
+ column += util.getStringWidth(text.slice(segmentStart, index));
12
+ if (tabWidth > 0) {
13
+ column += tabWidth - (column % tabWidth);
14
+ }
15
+ segmentStart = index + 1;
16
+ }
17
+ }
18
+ return column + util.getStringWidth(text.slice(segmentStart));
19
+ }
@@ -1,2 +1,2 @@
1
- import type { WrapOptions } from './types.js';
1
+ import type { WrapOptions } from './wrap-options.js';
2
2
  export declare function formatMarkdownLines(markdown: string, printWidth: number, options: WrapOptions): Promise<string[]>;
@@ -1,6 +1,6 @@
1
1
  import { format } from 'prettier';
2
- import { getTabWidth } from './options.js';
3
- import { trimBlankEdges } from './text.js';
2
+ import { isBlankLine } from './source-lines.js';
3
+ import { getTabWidth } from './wrap-options.js';
4
4
  export async function formatMarkdownLines(markdown, printWidth, options) {
5
5
  const normalized = trimBlankEdges(markdown.replace(/\r\n?/g, '\n'));
6
6
  try {
@@ -18,3 +18,13 @@ export async function formatMarkdownLines(markdown, printWidth, options) {
18
18
  return normalized.split('\n');
19
19
  }
20
20
  }
21
+ function trimBlankEdges(markdown) {
22
+ const lines = markdown.split('\n');
23
+ while (isBlankLine(lines[0])) {
24
+ lines.shift();
25
+ }
26
+ while (isBlankLine(lines.at(-1))) {
27
+ lines.pop();
28
+ }
29
+ return lines.join('\n');
30
+ }
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from './wrap-options.js';
2
+ export declare function getContinuationIndent(text: string, commentStart: number, markerColumn: number, options: WrapOptions): string;
3
+ export declare function getLeadingIndent(text: string): string;
4
+ export declare function makeIndent(column: number, options: WrapOptions): string;
@@ -0,0 +1,21 @@
1
+ import { getLinePrefix } from './source-lines.js';
2
+ import { getTabWidth } from './wrap-options.js';
3
+ export function getContinuationIndent(text, commentStart, markerColumn, options) {
4
+ const linePrefix = getLinePrefix(text, commentStart);
5
+ if (/^[ \t]*$/u.test(linePrefix)) {
6
+ return linePrefix;
7
+ }
8
+ return makeIndent(markerColumn, options);
9
+ }
10
+ export function getLeadingIndent(text) {
11
+ return /^[ \t]*/u.exec(text)?.[0] ?? '';
12
+ }
13
+ export function makeIndent(column, options) {
14
+ const tabWidth = getTabWidth(options);
15
+ if (options.useTabs === true && tabWidth > 0) {
16
+ const tabs = Math.floor(column / tabWidth);
17
+ const spaces = column % tabWidth;
18
+ return `${'\t'.repeat(tabs)}${' '.repeat(spaces)}`;
19
+ }
20
+ return ' '.repeat(column);
21
+ }
@@ -0,0 +1,6 @@
1
+ export type Replacement = {
2
+ end: number;
3
+ start: number;
4
+ text: string;
5
+ };
6
+ export declare function applyReplacements(text: string, replacements: Replacement[]): string;
@@ -0,0 +1,21 @@
1
+ export function applyReplacements(text, replacements) {
2
+ let result = text;
3
+ for (const replacement of getNonOverlappingReplacements(replacements).sort((left, right) => right.start - left.start)) {
4
+ result = result.slice(0, replacement.start) + replacement.text + result.slice(replacement.end);
5
+ }
6
+ return result;
7
+ }
8
+ function getNonOverlappingReplacements(replacements) {
9
+ return [...replacements]
10
+ .sort((left, right) => left.start - right.start || right.end - left.end)
11
+ .reduce((accepted, replacement) => {
12
+ const previous = accepted.at(-1);
13
+ if (previous === undefined || !rangesOverlap(previous, replacement)) {
14
+ accepted.push(replacement);
15
+ }
16
+ return accepted;
17
+ }, []);
18
+ }
19
+ function rangesOverlap(left, right) {
20
+ return left.start < right.end && right.start < left.end;
21
+ }
@@ -0,0 +1,6 @@
1
+ import type { WrapOptions } from './wrap-options.js';
2
+ export declare function getPreferredNewline(text: string, options: WrapOptions): string;
3
+ export declare function getLinePrefix(text: string, index: number): string;
4
+ export declare function getLineStart(text: string, index: number): number;
5
+ export declare function getLineEnd(text: string, index: number): number;
6
+ export declare function isBlankLine(line: string | undefined): boolean;
@@ -0,0 +1,40 @@
1
+ export function getPreferredNewline(text, options) {
2
+ if (options.endOfLine === 'crlf') {
3
+ return '\r\n';
4
+ }
5
+ if (options.endOfLine === 'cr') {
6
+ return '\r';
7
+ }
8
+ if (options.endOfLine === 'auto') {
9
+ const match = /\r\n|\n|\r/u.exec(text);
10
+ return match?.[0] ?? '\n';
11
+ }
12
+ return '\n';
13
+ }
14
+ export function getLinePrefix(text, index) {
15
+ return text.slice(getLineStart(text, index), index);
16
+ }
17
+ export function getLineStart(text, index) {
18
+ for (let cursor = Math.min(index, text.length) - 1; cursor >= 0; cursor -= 1) {
19
+ const character = text[cursor];
20
+ if (character === '\n' || character === '\u2028' || character === '\u2029') {
21
+ return cursor + 1;
22
+ }
23
+ if (character === '\r' && text[cursor + 1] !== '\n') {
24
+ return cursor + 1;
25
+ }
26
+ }
27
+ return 0;
28
+ }
29
+ export function getLineEnd(text, index) {
30
+ for (let cursor = Math.max(index, 0); cursor < text.length; cursor += 1) {
31
+ const character = text[cursor];
32
+ if (character === '\r' || character === '\n' || character === '\u2028' || character === '\u2029') {
33
+ return character === '\n' && text[cursor - 1] === '\r' ? cursor - 1 : cursor;
34
+ }
35
+ }
36
+ return text.length;
37
+ }
38
+ export function isBlankLine(line) {
39
+ return line !== undefined && line.trim() === '';
40
+ }
@@ -0,0 +1,2 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ export declare function numberOrUndefined(value: unknown): number | undefined;
@@ -0,0 +1,6 @@
1
+ export function isRecord(value) {
2
+ return typeof value === 'object' && value !== null;
3
+ }
4
+ export function numberOrUndefined(value) {
5
+ return typeof value === 'number' ? value : undefined;
6
+ }
@@ -0,0 +1,2 @@
1
+ export declare function skipWhitespace(text: string, index: number): number;
2
+ export declare function trimWhitespaceEnd(text: string, start: number, end: number): number;
@@ -0,0 +1,22 @@
1
+ export function skipWhitespace(text, index) {
2
+ let cursor = index;
3
+ while (cursor < text.length) {
4
+ const character = text[cursor];
5
+ if (character === undefined || !/\s/u.test(character)) {
6
+ break;
7
+ }
8
+ cursor += 1;
9
+ }
10
+ return cursor;
11
+ }
12
+ export function trimWhitespaceEnd(text, start, end) {
13
+ let cursor = end;
14
+ while (cursor > start) {
15
+ const character = text[cursor - 1];
16
+ if (character === undefined || !/\s/u.test(character)) {
17
+ break;
18
+ }
19
+ cursor -= 1;
20
+ }
21
+ return cursor;
22
+ }
@@ -1,4 +1,5 @@
1
- import type { WrapOptions } from './types.js';
1
+ import type { ParserOptions } from 'prettier';
2
+ export type WrapOptions = Pick<ParserOptions, 'endOfLine' | 'printWidth' | 'tabWidth' | 'useTabs'>;
2
3
  export declare function getAvailableContentWidth(options: WrapOptions, contentStartColumn: number): number;
3
4
  export declare function getPrintWidth(options: WrapOptions): number;
4
5
  export declare function getTabWidth(options: WrapOptions): number;
package/package.json CHANGED
@@ -5,11 +5,15 @@
5
5
  },
6
6
  "description": "A Prettier plugin that wraps non-JSDoc JavaScript and TypeScript comments as Markdown.",
7
7
  "devDependencies": {
8
- "prettier": "3.8.3",
9
- "typescript": "6.0.3"
8
+ "@types/node": "20.19.43",
9
+ "prettier": "3.9.6",
10
+ "prettier-plugin-wrap-comments-stable": "npm:@aforemendude/prettier-plugin-wrap-comments@1.0.6",
11
+ "typescript": "7.0.2",
12
+ "vite": "8.1.5",
13
+ "vitest": "4.1.10"
10
14
  },
11
15
  "engines": {
12
- "node": ">=18"
16
+ "node": ">=20.0.0"
13
17
  },
14
18
  "exports": {
15
19
  ".": {
@@ -32,7 +36,7 @@
32
36
  "main": "./dist/index.js",
33
37
  "name": "@aforemendude/prettier-plugin-wrap-comments",
34
38
  "peerDependencies": {
35
- "prettier": ">=3.0.0"
39
+ "prettier": ">=3.0.0 <4.0.0"
36
40
  },
37
41
  "publishConfig": {
38
42
  "access": "public"
@@ -42,14 +46,18 @@
42
46
  "url": "git+https://github.com/aforemendude/prettier-plugin-wrap-comments.git"
43
47
  },
44
48
  "scripts": {
45
- "build": "rm -rf dist && tsc -p tsconfig.json",
49
+ "build": "npm run clean && tsc -p tsconfig.build.json",
50
+ "clean": "node scripts/clean.mjs",
46
51
  "format": "prettier --write .",
47
52
  "format:check": "prettier --check .",
48
- "prepack": "npm run verify",
49
- "test": "npm run build && node --test test/*.test.mjs",
50
- "verify": "npm install && npm run format:check && npm run test"
53
+ "prepack": "npm install && git diff --exit-code -- package-lock.json && npm run verify",
54
+ "test": "vitest run",
55
+ "test:integration": "vitest run test/integration",
56
+ "test:unit": "vitest run test/unit",
57
+ "typecheck": "tsc -p tsconfig.json",
58
+ "verify": "npm run format:check && npm run typecheck && npm run build && npm run test"
51
59
  },
52
60
  "type": "module",
53
61
  "types": "./dist/index.d.ts",
54
- "version": "1.0.5"
62
+ "version": "1.1.0"
55
63
  }
@@ -1,8 +0,0 @@
1
- import type { CommentRange, RawComment } from '../shared/types.js';
2
- export declare function collectComments(ast: unknown): RawComment[];
3
- export declare function toCommentRange(comment: RawComment, text: string): CommentRange | undefined;
4
- export declare function normalizeLineCommentBody(rawBody: string): string;
5
- export declare function normalizeBlockCommentBody(rawComment: string): string;
6
- export declare function isDirectiveComment(body: string): boolean;
7
- export declare function isPrettierIgnoreComment(body: string): boolean;
8
- export declare function hasPreserveCommentMarker(rawComment: string): boolean;
@@ -1,114 +0,0 @@
1
- export function collectComments(ast) {
2
- const candidate = ast;
3
- if (Array.isArray(candidate.comments)) {
4
- return candidate.comments;
5
- }
6
- if (Array.isArray(candidate.program?.comments)) {
7
- return candidate.program.comments;
8
- }
9
- return [];
10
- }
11
- export function toCommentRange(comment, text) {
12
- const range = Array.isArray(comment.range) ? comment.range : undefined;
13
- const start = numberOrUndefined(comment.start) ?? numberOrUndefined(range?.[0]);
14
- const end = numberOrUndefined(comment.end) ?? numberOrUndefined(range?.[1]);
15
- if (start === undefined || end === undefined || start >= end) {
16
- return undefined;
17
- }
18
- const rawStart = text.slice(start, start + 3);
19
- if (rawStart.startsWith('//')) {
20
- return { end, kind: 'line', start };
21
- }
22
- if (rawStart.startsWith('/*')) {
23
- return { end, kind: 'block', start };
24
- }
25
- return undefined;
26
- }
27
- export function normalizeLineCommentBody(rawBody) {
28
- if (rawBody.trim() === '') {
29
- return '';
30
- }
31
- return rawBody.replace(/^[ \t]?/, '').replace(/[ \t]+$/u, '');
32
- }
33
- export function normalizeBlockCommentBody(rawComment) {
34
- const body = rawComment.slice(2, -2).replace(/\r\n?/g, '\n');
35
- const lines = body.split('\n');
36
- if (lines.length === 1) {
37
- return lines[0]?.trim() ?? '';
38
- }
39
- while (isBlankLine(lines[0])) {
40
- lines.shift();
41
- }
42
- while (isBlankLine(lines.at(-1))) {
43
- lines.pop();
44
- }
45
- return lines
46
- .map((line) => {
47
- const withoutIndent = line.replace(/^[ \t]*/u, '');
48
- if (!withoutIndent.startsWith('*')) {
49
- return withoutIndent.replace(/[ \t]+$/u, '');
50
- }
51
- return withoutIndent
52
- .slice(1)
53
- .replace(/^[ \t]?/u, '')
54
- .replace(/[ \t]+$/u, '');
55
- })
56
- .join('\n');
57
- }
58
- const PRAGMA_DIRECTIVE_COMMENT_PATTERNS = [
59
- /^@(?:license|preserve)\b/u,
60
- /^@(?:jsxFrag|jsxImportSource|jsxRuntime|jsx)\b/u,
61
- /^@(?:ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b/u,
62
- /^[@#]__(?:NO_SIDE_EFFECTS|PURE)__\b/u,
63
- ];
64
- const SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS = [
65
- /^[#@][ \t]*sourceMappingURL=/u,
66
- /^[#@][ \t]*sourceURL=/u,
67
- /^sourceMappingURL=/u,
68
- /^sourceURL=/u,
69
- ];
70
- const TOOL_DIRECTIVE_COMMENT_PATTERNS = [
71
- /^biome-ignore\b/u,
72
- /^c8\b/u,
73
- /^deno-lint-ignore\b/u,
74
- /^eslint\b/u,
75
- /^eslint-/u,
76
- /^exported\b/u,
77
- /^globals?\b/u,
78
- /^istanbul\b/u,
79
- /^jshint\b/u,
80
- /^nyc\b/u,
81
- /^oxlint\b/u,
82
- /^prettier-ignore\b/u,
83
- /^prettier-ignore-start\b/u,
84
- /^prettier-ignore-end\b/u,
85
- /^stylelint\b/u,
86
- /^tslint\b/u,
87
- /^v8\b/u,
88
- /^vite-ignore\b/u,
89
- ];
90
- const BUNDLER_DIRECTIVE_COMMENT_PATTERNS = [
91
- /^webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b/u,
92
- ];
93
- const DIRECTIVE_COMMENT_PATTERNS = [
94
- ...PRAGMA_DIRECTIVE_COMMENT_PATTERNS,
95
- ...SOURCE_MAP_DIRECTIVE_COMMENT_PATTERNS,
96
- ...TOOL_DIRECTIVE_COMMENT_PATTERNS,
97
- ...BUNDLER_DIRECTIVE_COMMENT_PATTERNS,
98
- ];
99
- export function isDirectiveComment(body) {
100
- const normalizedBody = body.trimStart();
101
- return DIRECTIVE_COMMENT_PATTERNS.some((pattern) => pattern.test(normalizedBody));
102
- }
103
- export function isPrettierIgnoreComment(body) {
104
- return body.trim() === 'prettier-ignore';
105
- }
106
- export function hasPreserveCommentMarker(rawComment) {
107
- return rawComment.startsWith('/*!') || rawComment.startsWith('//!');
108
- }
109
- function isBlankLine(line) {
110
- return line !== undefined && line.trim() === '';
111
- }
112
- function numberOrUndefined(value) {
113
- return typeof value === 'number' ? value : undefined;
114
- }
@@ -1,6 +0,0 @@
1
- import type { CommentRange, Replacement, WrapOptions } from '../shared/types.js';
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>;
4
- export declare function shouldSkipLineComment(text: string, comment: CommentRange): boolean;
5
- export declare function isStandaloneLineComment(text: string, comment: CommentRange): boolean;
6
- export declare function areAdjacentLineComments(text: string, previous: CommentRange, next: CommentRange): boolean;