@aforemendude/prettier-plugin-wrap-comments 1.0.2

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 ADDED
@@ -0,0 +1,79 @@
1
+ # @aforemendude/prettier-plugin-wrap-comments
2
+
3
+ A Prettier plugin that wraps non-JSDoc JavaScript and TypeScript comments as Markdown. It uses the comment marker's real
4
+ column to calculate the available content width, so nested comments wrap more narrowly than top-level comments.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install --save-dev @aforemendude/prettier-plugin-wrap-comments prettier
10
+ ```
11
+
12
+ ## Use
13
+
14
+ Add the plugin to your Prettier config:
15
+
16
+ ```json
17
+ {
18
+ "plugins": ["@aforemendude/prettier-plugin-wrap-comments"]
19
+ }
20
+ ```
21
+
22
+ Then run Prettier normally:
23
+
24
+ ```sh
25
+ npx prettier --write .
26
+ ```
27
+
28
+ ## Behavior
29
+
30
+ The plugin wraps standalone `//` and `/* ... */` comments before Prettier parses the file, then delegates formatting to
31
+ Prettier's built-in JavaScript and TypeScript parsers.
32
+
33
+ ```ts
34
+ function example() {
35
+ // This long paragraph is wrapped as Markdown, and the nested indentation is
36
+ // subtracted from the configured print width.
37
+ return true;
38
+ }
39
+ ```
40
+
41
+ JSDoc comments are left unchanged:
42
+
43
+ ```ts
44
+ /**
45
+ * This documentation comment is not wrapped by the plugin.
46
+ */
47
+ ```
48
+
49
+ Tooling directives such as `eslint-disable`, `@ts-expect-error`, `prettier-ignore`, source maps, and TypeScript
50
+ triple-slash directives are also left untouched so their meaning is not changed. Trailing comments after code stay in
51
+ place when the full line fits the configured print width. If the line is too long, the comment is moved above the code
52
+ and wrapped there.
53
+
54
+ ## Supported Parsers
55
+
56
+ - `babel`
57
+ - `babel-ts`
58
+ - `typescript`
59
+
60
+ ## Development
61
+
62
+ ```sh
63
+ npm install
64
+ npm run check
65
+ ```
66
+
67
+ ## Publishing
68
+
69
+ The npm package name is `@aforemendude/prettier-plugin-wrap-comments`.
70
+
71
+ Before publishing, make sure the version in `package.json` has been updated, then run:
72
+
73
+ ```sh
74
+ npm run check
75
+ npm publish --access public
76
+ ```
77
+
78
+ The package scripts already run the important publish checks: `prepublishOnly` runs `npm run check`, and `prepack`
79
+ builds the `dist` files that are included in the published package.
@@ -0,0 +1,2 @@
1
+ import type { CommentRange, Replacement, WrapOptions } from '../shared/types.js';
2
+ export declare function wrapBlockComment(text: string, comment: CommentRange, options: WrapOptions): Promise<Replacement | undefined>;
@@ -0,0 +1,43 @@
1
+ import { isDirectiveComment, normalizeBlockCommentBody } from './core.js';
2
+ import { formatMarkdownLines } from '../shared/markdown.js';
3
+ import { getAvailableContentWidth, getPrintWidth, getTabWidth } from '../shared/options.js';
4
+ import { getColumnAt, getColumns, getLinePrefix, getPreferredNewline, isStandaloneBlockComment, } from '../shared/text.js';
5
+ export async function wrapBlockComment(text, comment, options) {
6
+ const raw = text.slice(comment.start, comment.end);
7
+ if (raw.startsWith('/**')) {
8
+ return undefined;
9
+ }
10
+ const markdown = normalizeBlockCommentBody(raw);
11
+ if (markdown.trim() === '' || isDirectiveComment(markdown)) {
12
+ return undefined;
13
+ }
14
+ const tabWidth = getTabWidth(options);
15
+ const markerColumn = getColumnAt(text, comment.start, tabWidth);
16
+ const availableWidth = getAvailableContentWidth(options, markerColumn + 3);
17
+ const formattedLines = await formatMarkdownLines(markdown, availableWidth, options);
18
+ const replacementText = buildBlockReplacement(text, comment, formattedLines, options);
19
+ if (replacementText === undefined || replacementText === text.slice(comment.start, comment.end)) {
20
+ return undefined;
21
+ }
22
+ return {
23
+ end: comment.end,
24
+ start: comment.start,
25
+ text: replacementText,
26
+ };
27
+ }
28
+ function buildBlockReplacement(text, comment, formattedLines, options) {
29
+ const tabWidth = getTabWidth(options);
30
+ const markerColumn = getColumnAt(text, comment.start, tabWidth);
31
+ const singleLine = `/* ${formattedLines.join(' ')} */`;
32
+ const singleLineWidth = getColumns(singleLine, tabWidth);
33
+ if (formattedLines.length === 1 && markerColumn + singleLineWidth <= getPrintWidth(options)) {
34
+ return singleLine;
35
+ }
36
+ if (!isStandaloneBlockComment(text, comment)) {
37
+ return undefined;
38
+ }
39
+ const newline = getPreferredNewline(text, options);
40
+ const indent = getLinePrefix(text, comment.start);
41
+ const body = formattedLines.map((line) => `${indent} *${line.length === 0 ? '' : ` ${line}`}`).join(newline);
42
+ return `/*${newline}${body}${newline}${indent} */`;
43
+ }
@@ -0,0 +1,6 @@
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;
@@ -0,0 +1,74 @@
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
+ if (typeof comment.type === 'string') {
26
+ if (comment.type.includes('Line')) {
27
+ return { end, kind: 'line', start };
28
+ }
29
+ if (comment.type.includes('Block')) {
30
+ return { end, kind: 'block', start };
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ export function normalizeLineCommentBody(rawBody) {
36
+ if (rawBody.trim() === '') {
37
+ return '';
38
+ }
39
+ return rawBody.replace(/^[ \t]?/, '').replace(/[ \t]+$/u, '');
40
+ }
41
+ export function normalizeBlockCommentBody(rawComment) {
42
+ const body = rawComment.slice(2, -2).replace(/\r\n?/g, '\n');
43
+ const lines = body.split('\n');
44
+ if (lines.length === 1) {
45
+ return lines[0]?.trim() ?? '';
46
+ }
47
+ while (isBlankLine(lines[0])) {
48
+ lines.shift();
49
+ }
50
+ while (isBlankLine(lines.at(-1))) {
51
+ lines.pop();
52
+ }
53
+ return lines
54
+ .map((line) => {
55
+ const withoutIndent = line.replace(/^[ \t]*/u, '');
56
+ if (!withoutIndent.startsWith('*')) {
57
+ return withoutIndent.replace(/[ \t]+$/u, '');
58
+ }
59
+ return withoutIndent
60
+ .slice(1)
61
+ .replace(/^[ \t]?/u, '')
62
+ .replace(/[ \t]+$/u, '');
63
+ })
64
+ .join('\n');
65
+ }
66
+ export function isDirectiveComment(body) {
67
+ return /^(?:@(?:__NO_SIDE_EFFECTS__|__PURE__|jsx|jsxImportSource|license|preserve|ts-check|ts-expect-error|ts-ignore|ts-nocheck)\b|#\s*sourceMappingURL=|[@#]__PURE__\b|biome-ignore\b|c8\b|deno-lint-ignore\b|eslint\b|eslint-|exported\b|globals?\b|istanbul\b|jshint\b|nyc\b|oxlint\b|prettier-ignore\b|prettier-ignore-start\b|prettier-ignore-end\b|sourceMappingURL=|stylelint\b|tslint\b|v8\b|vite-ignore\b|webpack(?:ChunkName|Exclude|Ignore|Include|Mode|Prefetch|Preload)\b)/u.test(body.trimStart());
68
+ }
69
+ function isBlankLine(line) {
70
+ return line !== undefined && line.trim() === '';
71
+ }
72
+ function numberOrUndefined(value) {
73
+ return typeof value === 'number' ? value : undefined;
74
+ }
@@ -0,0 +1,6 @@
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;
@@ -0,0 +1,90 @@
1
+ import { isDirectiveComment, normalizeLineCommentBody } from './core.js';
2
+ import { formatMarkdownLines } from '../shared/markdown.js';
3
+ import { getAvailableContentWidth, getPrintWidth, getTabWidth } from '../shared/options.js';
4
+ import { getColumnAt, getColumns, getContinuationIndent, getLineEnd, getLinePrefix, getLineStart, getPreferredNewline, } from '../shared/text.js';
5
+ export async function wrapLineCommentGroup(text, comments, options) {
6
+ const firstComment = comments[0];
7
+ if (firstComment === undefined) {
8
+ return undefined;
9
+ }
10
+ const lastComment = comments.at(-1) ?? firstComment;
11
+ const bodyLines = comments.map((comment) => normalizeLineCommentBody(text.slice(comment.start + 2, comment.end)));
12
+ if (bodyLines.every((line) => line.trim() === '')) {
13
+ return undefined;
14
+ }
15
+ const tabWidth = getTabWidth(options);
16
+ const markerColumn = getColumnAt(text, firstComment.start, tabWidth);
17
+ const availableWidth = getAvailableContentWidth(options, markerColumn + 3);
18
+ const formattedLines = await formatMarkdownLines(bodyLines.join('\n'), availableWidth, options);
19
+ const newline = getPreferredNewline(text, options);
20
+ const continuationIndent = getContinuationIndent(text, firstComment.start, markerColumn, options);
21
+ const replacementText = formattedLines
22
+ .map((line, index) => {
23
+ const commentText = line.length === 0 ? '//' : `// ${line}`;
24
+ return index === 0 ? commentText : `${newline}${continuationIndent}${commentText}`;
25
+ })
26
+ .join('');
27
+ const start = firstComment.start;
28
+ const end = lastComment.end;
29
+ if (replacementText === text.slice(start, end)) {
30
+ return undefined;
31
+ }
32
+ return {
33
+ end,
34
+ start,
35
+ text: replacementText,
36
+ };
37
+ }
38
+ export async function wrapTrailingLineComment(text, comment, options) {
39
+ if (isTrailingLineCommentWithinPrintWidth(text, comment, options)) {
40
+ return undefined;
41
+ }
42
+ const lineStart = getLineStart(text, comment.start);
43
+ const lineEnd = getLineEnd(text, comment.end);
44
+ const linePrefix = text.slice(lineStart, comment.start);
45
+ const codeText = linePrefix.replace(/[ \t]+$/u, '');
46
+ if (codeText.trim() === '') {
47
+ return undefined;
48
+ }
49
+ const body = normalizeLineCommentBody(text.slice(comment.start + 2, comment.end));
50
+ if (body.trim() === '') {
51
+ return undefined;
52
+ }
53
+ const tabWidth = getTabWidth(options);
54
+ const indent = getLineIndent(linePrefix);
55
+ const availableWidth = getAvailableContentWidth(options, getColumns(indent, tabWidth) + 3);
56
+ const formattedLines = await formatMarkdownLines(body, availableWidth, options);
57
+ const newline = getPreferredNewline(text, options);
58
+ const leadingCommentText = formattedLines
59
+ .map((line) => `${indent}${line.length === 0 ? '//' : `// ${line}`}`)
60
+ .join(newline);
61
+ const replacementText = `${leadingCommentText}${newline}${codeText}`;
62
+ return {
63
+ end: lineEnd,
64
+ start: lineStart,
65
+ text: replacementText,
66
+ };
67
+ }
68
+ export function shouldSkipLineComment(text, comment) {
69
+ const raw = text.slice(comment.start, comment.end);
70
+ if (raw.startsWith('///')) {
71
+ return true;
72
+ }
73
+ return isDirectiveComment(normalizeLineCommentBody(raw.slice(2)));
74
+ }
75
+ export function isStandaloneLineComment(text, comment) {
76
+ return /^[ \t]*$/u.test(getLinePrefix(text, comment.start));
77
+ }
78
+ export function areAdjacentLineComments(text, previous, next) {
79
+ return /^(?:\r\n|\n|\r)[ \t]*$/u.test(text.slice(previous.end, next.start));
80
+ }
81
+ function isTrailingLineCommentWithinPrintWidth(text, comment, options) {
82
+ const tabWidth = getTabWidth(options);
83
+ const lineStart = getLineStart(text, comment.start);
84
+ const lineEnd = getLineEnd(text, comment.end);
85
+ const lineText = text.slice(lineStart, lineEnd).replace(/[ \t]+$/u, '');
86
+ return getColumns(lineText, tabWidth) <= getPrintWidth(options);
87
+ }
88
+ function getLineIndent(linePrefix) {
89
+ return /^[ \t]*/u.exec(linePrefix)?.[0] ?? '';
90
+ }
@@ -0,0 +1,2 @@
1
+ import type { WrapOptions } from '../shared/types.js';
2
+ export declare function wrapComments<T>(text: string, ast: T, options: WrapOptions): Promise<string>;
@@ -0,0 +1,62 @@
1
+ import { wrapBlockComment } from './block.js';
2
+ import { collectComments, toCommentRange } from './core.js';
3
+ import { areAdjacentLineComments, isStandaloneLineComment, shouldSkipLineComment, wrapLineCommentGroup, wrapTrailingLineComment, } from './line.js';
4
+ import { getTabWidth } from '../shared/options.js';
5
+ import { applyReplacements, getColumnAt } from '../shared/text.js';
6
+ export async function wrapComments(text, ast, options) {
7
+ const comments = collectComments(ast)
8
+ .map((comment) => toCommentRange(comment, text))
9
+ .filter((comment) => comment !== undefined)
10
+ .sort((left, right) => left.start - right.start);
11
+ if (comments.length === 0) {
12
+ return text;
13
+ }
14
+ const replacements = [];
15
+ const tabWidth = getTabWidth(options);
16
+ for (let index = 0; index < comments.length; index += 1) {
17
+ const comment = comments[index];
18
+ if (comment === undefined) {
19
+ continue;
20
+ }
21
+ if (comment.kind === 'block') {
22
+ const replacement = await wrapBlockComment(text, comment, options);
23
+ if (replacement !== undefined) {
24
+ replacements.push(replacement);
25
+ }
26
+ continue;
27
+ }
28
+ if (shouldSkipLineComment(text, comment)) {
29
+ continue;
30
+ }
31
+ if (!isStandaloneLineComment(text, comment)) {
32
+ const replacement = await wrapTrailingLineComment(text, comment, options);
33
+ if (replacement !== undefined) {
34
+ replacements.push(replacement);
35
+ }
36
+ continue;
37
+ }
38
+ const group = [comment];
39
+ let previousComment = comment;
40
+ while (index + 1 < comments.length) {
41
+ const nextComment = comments[index + 1];
42
+ if (nextComment === undefined) {
43
+ break;
44
+ }
45
+ if (nextComment.kind !== 'line' ||
46
+ !isStandaloneLineComment(text, nextComment) ||
47
+ shouldSkipLineComment(text, nextComment) ||
48
+ !areAdjacentLineComments(text, previousComment, nextComment) ||
49
+ getColumnAt(text, comment.start, tabWidth) !== getColumnAt(text, nextComment.start, tabWidth)) {
50
+ break;
51
+ }
52
+ group.push(nextComment);
53
+ previousComment = nextComment;
54
+ index += 1;
55
+ }
56
+ const replacement = await wrapLineCommentGroup(text, group, options);
57
+ if (replacement !== undefined) {
58
+ replacements.push(replacement);
59
+ }
60
+ }
61
+ return applyReplacements(text, replacements);
62
+ }
@@ -0,0 +1,11 @@
1
+ import type { Plugin } from 'prettier';
2
+ declare const parsers: {
3
+ [parserName: string]: import("prettier").Parser<any>;
4
+ } | undefined;
5
+ declare const printers: {
6
+ estree: import("prettier").Printer;
7
+ "estree-json": import("prettier").Printer;
8
+ };
9
+ declare const plugin: Plugin;
10
+ export { parsers, printers };
11
+ export default plugin;
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import * as estreePlugin from 'prettier/plugins/estree';
2
+ import { buildParsers } from './plugin/parsers.js';
3
+ const parsers = buildParsers();
4
+ const printers = estreePlugin.printers;
5
+ const plugin = {
6
+ parsers,
7
+ printers,
8
+ };
9
+ export { parsers, printers };
10
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ import type { Plugin } from 'prettier';
2
+ export declare function buildParsers(): Plugin['parsers'];
@@ -0,0 +1,31 @@
1
+ import * as babelPlugin from 'prettier/plugins/babel';
2
+ import * as typescriptPlugin from 'prettier/plugins/typescript';
3
+ import { wrapComments } from '../comments/wrap.js';
4
+ const parserNames = ['babel', 'babel-ts', 'typescript'];
5
+ function wrapParser(parser) {
6
+ return {
7
+ ...parser,
8
+ async preprocess(text, options) {
9
+ const preprocessed = parser.preprocess === undefined ? text : await parser.preprocess(text, options);
10
+ let ast;
11
+ try {
12
+ ast = await parser.parse(preprocessed, options);
13
+ }
14
+ catch {
15
+ return preprocessed;
16
+ }
17
+ return wrapComments(preprocessed, ast, options);
18
+ },
19
+ };
20
+ }
21
+ export function buildParsers() {
22
+ const parsers = {};
23
+ for (const parserName of parserNames) {
24
+ const sourceParsers = parserName === 'typescript' ? typescriptPlugin.parsers : babelPlugin.parsers;
25
+ const parser = sourceParsers[parserName];
26
+ if (parser !== undefined) {
27
+ parsers[parserName] = wrapParser(parser);
28
+ }
29
+ }
30
+ return parsers;
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { WrapOptions } from './types.js';
2
+ export declare function formatMarkdownLines(markdown: string, printWidth: number, options: WrapOptions): Promise<string[]>;
@@ -0,0 +1,20 @@
1
+ import { format } from 'prettier';
2
+ import { getTabWidth } from './options.js';
3
+ import { trimBlankEdges } from './text.js';
4
+ export async function formatMarkdownLines(markdown, printWidth, options) {
5
+ const normalized = trimBlankEdges(markdown.replace(/\r\n?/g, '\n'));
6
+ try {
7
+ const formatted = await format(normalized, {
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');
16
+ }
17
+ catch {
18
+ return normalized.split('\n');
19
+ }
20
+ }
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from './types.js';
2
+ export declare function getAvailableContentWidth(options: WrapOptions, contentStartColumn: number): number;
3
+ export declare function getPrintWidth(options: WrapOptions): number;
4
+ export declare function getTabWidth(options: WrapOptions): number;
@@ -0,0 +1,11 @@
1
+ const DEFAULT_PRINT_WIDTH = 80;
2
+ const DEFAULT_TAB_WIDTH = 2;
3
+ export function getAvailableContentWidth(options, contentStartColumn) {
4
+ return Math.max(1, getPrintWidth(options) - contentStartColumn);
5
+ }
6
+ export function getPrintWidth(options) {
7
+ return typeof options.printWidth === 'number' ? options.printWidth : DEFAULT_PRINT_WIDTH;
8
+ }
9
+ export function getTabWidth(options) {
10
+ return typeof options.tabWidth === 'number' ? options.tabWidth : DEFAULT_TAB_WIDTH;
11
+ }
@@ -0,0 +1,14 @@
1
+ import type { Replacement, WrapOptions } from './types.js';
2
+ export declare function applyReplacements(text: string, replacements: Replacement[]): string;
3
+ export declare function getPreferredNewline(text: string, options: WrapOptions): string;
4
+ export declare function getContinuationIndent(text: string, commentStart: number, markerColumn: number, options: WrapOptions): string;
5
+ export declare function getLinePrefix(text: string, index: number): string;
6
+ export declare function getLineStart(text: string, index: number): number;
7
+ export declare function getLineEnd(text: string, index: number): number;
8
+ export declare function getColumnAt(text: string, index: number, tabWidth: number): number;
9
+ export declare function getColumns(text: string, tabWidth: number): number;
10
+ export declare function isStandaloneBlockComment(text: string, comment: {
11
+ end: number;
12
+ start: number;
13
+ }): boolean;
14
+ export declare function trimBlankEdges(markdown: string): string;
@@ -0,0 +1,84 @@
1
+ import { getTabWidth } from './options.js';
2
+ export function applyReplacements(text, replacements) {
3
+ let result = text;
4
+ for (const replacement of [...replacements].sort((left, right) => right.start - left.start)) {
5
+ result = result.slice(0, replacement.start) + replacement.text + result.slice(replacement.end);
6
+ }
7
+ return result;
8
+ }
9
+ export function getPreferredNewline(text, options) {
10
+ if (options.endOfLine === 'crlf') {
11
+ return '\r\n';
12
+ }
13
+ if (options.endOfLine === 'cr') {
14
+ return '\r';
15
+ }
16
+ if (options.endOfLine === 'auto') {
17
+ const match = /\r\n|\n|\r/u.exec(text);
18
+ return match?.[0] ?? '\n';
19
+ }
20
+ return '\n';
21
+ }
22
+ export function getContinuationIndent(text, commentStart, markerColumn, options) {
23
+ const linePrefix = getLinePrefix(text, commentStart);
24
+ if (/^[ \t]*$/u.test(linePrefix)) {
25
+ return linePrefix;
26
+ }
27
+ return makeIndent(markerColumn, options);
28
+ }
29
+ export function getLinePrefix(text, index) {
30
+ return text.slice(getLineStart(text, index), index);
31
+ }
32
+ export function getLineStart(text, index) {
33
+ const newlineIndex = text.lastIndexOf('\n', index - 1);
34
+ return newlineIndex === -1 ? 0 : newlineIndex + 1;
35
+ }
36
+ export function getLineEnd(text, index) {
37
+ const newlineIndex = text.indexOf('\n', index);
38
+ if (newlineIndex === -1) {
39
+ return text.length;
40
+ }
41
+ return text[newlineIndex - 1] === '\r' ? newlineIndex - 1 : newlineIndex;
42
+ }
43
+ export function getColumnAt(text, index, tabWidth) {
44
+ return getColumns(text.slice(getLineStart(text, index), index), tabWidth);
45
+ }
46
+ export function getColumns(text, tabWidth) {
47
+ let column = 0;
48
+ for (const character of text) {
49
+ if (character === '\t') {
50
+ column += tabWidth - (column % tabWidth);
51
+ }
52
+ else {
53
+ column += 1;
54
+ }
55
+ }
56
+ return column;
57
+ }
58
+ export function isStandaloneBlockComment(text, comment) {
59
+ const before = text.slice(getLineStart(text, comment.start), comment.start);
60
+ const after = text.slice(comment.end, getLineEnd(text, comment.end));
61
+ return /^[ \t]*$/u.test(before) && /^[ \t]*$/u.test(after);
62
+ }
63
+ export function trimBlankEdges(markdown) {
64
+ const lines = markdown.split('\n');
65
+ while (isBlankLine(lines[0])) {
66
+ lines.shift();
67
+ }
68
+ while (isBlankLine(lines.at(-1))) {
69
+ lines.pop();
70
+ }
71
+ return lines.join('\n');
72
+ }
73
+ function isBlankLine(line) {
74
+ return line !== undefined && line.trim() === '';
75
+ }
76
+ function makeIndent(column, options) {
77
+ const tabWidth = getTabWidth(options);
78
+ if (options.useTabs === true) {
79
+ const tabs = Math.floor(column / tabWidth);
80
+ const spaces = column % tabWidth;
81
+ return `${'\t'.repeat(tabs)}${' '.repeat(spaces)}`;
82
+ }
83
+ return ' '.repeat(column);
84
+ }
@@ -0,0 +1,30 @@
1
+ import type { ParserOptions } from 'prettier';
2
+ export type AstWithComments = {
3
+ comments?: unknown;
4
+ program?: {
5
+ comments?: unknown;
6
+ };
7
+ };
8
+ export type RawComment = {
9
+ end?: unknown;
10
+ loc?: {
11
+ start?: {
12
+ column?: unknown;
13
+ };
14
+ };
15
+ range?: unknown;
16
+ start?: unknown;
17
+ type?: unknown;
18
+ value?: unknown;
19
+ };
20
+ export type CommentRange = {
21
+ end: number;
22
+ kind: 'block' | 'line';
23
+ start: number;
24
+ };
25
+ export type Replacement = {
26
+ end: number;
27
+ start: number;
28
+ text: string;
29
+ };
30
+ export type WrapOptions = Pick<ParserOptions, 'endOfLine' | 'printWidth' | 'tabWidth' | 'useTabs'>;
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "author": "aforemendude",
3
+ "bugs": {
4
+ "url": "https://github.com/aforemendude/prettier-plugin-wrap-comments/issues"
5
+ },
6
+ "description": "A Prettier plugin that wraps non-JSDoc JavaScript and TypeScript comments as Markdown.",
7
+ "devDependencies": {
8
+ "prettier": "3.8.3",
9
+ "typescript": "6.0.3"
10
+ },
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "default": "./dist/index.js",
17
+ "types": "./dist/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "homepage": "https://github.com/aforemendude/prettier-plugin-wrap-comments",
24
+ "keywords": [
25
+ "prettier",
26
+ "prettier-plugin",
27
+ "comments",
28
+ "markdown",
29
+ "typescript"
30
+ ],
31
+ "license": "MIT",
32
+ "main": "./dist/index.js",
33
+ "name": "@aforemendude/prettier-plugin-wrap-comments",
34
+ "peerDependencies": {
35
+ "prettier": ">=3.0.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/aforemendude/prettier-plugin-wrap-comments.git"
43
+ },
44
+ "scripts": {
45
+ "build": "rm -rf dist && tsc -p tsconfig.json",
46
+ "format": "prettier --write .",
47
+ "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"
51
+ },
52
+ "type": "module",
53
+ "types": "./dist/index.d.ts",
54
+ "version": "1.0.2"
55
+ }