Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/comments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ interface FileResult {
## Heuristics Overview

- Adds a terminal period to sentences lacking final punctuation (unless ending with a colon, tag line, list marker, or detected continuation).
- Treats `@tag` lines as JSDoc tags, except scoped package names (`@auth0/auth0-react`) and CSS at-rules (`@keyframes`, `@media`, `@layer`, ...) that open a wrapped prose line; those stay part of the sentence.
- Normalizes `NOTE:` capitalization and splits multiple NOTE sentences safely.
- Skips wrapping/merging for linter and tool directive comments, including:
- ESLint (`eslint-disable`, `eslint-enable`, `eslint-disable-next-line`, etc.)
Expand Down
71 changes: 71 additions & 0 deletions packages/comments/src/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,77 @@ describe("parseAndTransformComments", () => {
expect(transformed).toMatch(/future @auth0\/auth0-react/);
});

it("does not treat a CSS at-rule as a JSDoc tag", () => {
const cases: [string, RegExp, RegExp][] = [
[
[
"/**",
" * Global styles for the UI. Animations are registered through the",
" * @keyframes rule and referenced by name in each component.",
" */",
].join("\n") + "\n",
/through the\./,
/through the @keyframes/,
],
[
[
"/**",
" * Fade the panel in. Uses",
" * @media (prefers-reduced-motion) to disable the transition entirely.",
" */",
].join("\n") + "\n",
/Uses\./,
/Uses @media \(prefers-reduced-motion\)/,
],
[
[
"/**",
" * Layout tokens live under the",
" * @layer base so they can be overridden by utilities.",
" */",
].join("\n") + "\n",
/under the\./,
/under the @layer/,
],
];
for (const [input, spuriousPeriod, joined] of cases) {
const { transformed } = parseAndTransformComments(input, {
width: 80,
wrapLineComments: true,
mergeLineComments: true,
});
// No period injected mid-sentence.
expect(transformed).not.toMatch(spuriousPeriod);
// Sentence joined, at-rule kept inline as prose.
expect(transformed).toMatch(joined);
}
});

it("still treats JSDoc tags that share a name with a CSS at-rule as tags", () => {
const input =
[
"/**",
" * Describes a thing",
" * @property {string} name The display name",
" * @function",
" * @namespace Foo",
" * @return {number} the result",
" */",
].join("\n") + "\n";
const { transformed } = parseAndTransformComments(input, {
width: 80,
wrapLineComments: true,
mergeLineComments: true,
});
expect(transformed).toMatch(/^ \* Describes a thing\.$/m);
expect(transformed).toMatch(
/^ \* @property \{string\} name The display name$/m,
);
expect(transformed).toMatch(/^ \* @function$/m);
expect(transformed).toMatch(/^ \* @namespace Foo$/m);
expect(transformed).toMatch(/^ \* @return \{number\} the result$/m);
});

it("still treats real JSDoc tags as tags after the scoped-package fix", () => {
const input =
"/**\n * Does a thing.\n * @param x in\n * @returns out\n */\n";
Expand Down
40 changes: 38 additions & 2 deletions packages/comments/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,51 @@ function isListLike(line: string): boolean {
return /^(?:[-*+] |\d+\. )/.test(line.trim());
}

/**
* CSS at-rules that can legitimately open a wrapped prose line in a comment
* describing styles (e.g. "... are registered through the" followed by
* "@keyframes rule and referenced by name"). None of these names doubles as a
* JSDoc/TSDoc tag, so a line starting with one of them is sentence
* continuation, not a tag. At-rules whose name IS also a JSDoc tag (@function,
* @import, @mixin, @namespace, @property, @return) are deliberately absent so
* real tags keep working.
*/
const CSS_AT_RULES = new Set([
"apply",
"charset",
"color-profile",
"container",
"counter-style",
"font-face",
"font-feature-values",
"font-palette-values",
"keyframes",
"layer",
"media",
"page",
"position-try",
"scope",
"screen",
"starting-style",
"supports",
"tailwind",
"view-transition",
]);

function isTagLine(line: string): boolean {
/**
* A JSDoc/TSDoc tag is `@name` (optionally hyphenated) and is never
* immediately followed by `/`. The negative lookahead excludes scoped npm
* package specifiers like `@auth0/auth0-react` or `@versini/ui-main` that can
* legitimately open a wrapped prose line; treating those as tags severs the
* sentence and injects a spurious period.
* sentence and injects a spurious period. CSS at-rules (see CSS_AT_RULES) are
* excluded for the same reason.
*/
return /^@[A-Za-z][\w-]*(?![\w/-])/.test(line.trim());
const m = /^@([A-Za-z][\w-]*)(?![\w/-])/.exec(line.trim());
if (!m) {
return false;
}
return !CSS_AT_RULES.has(m[1].toLowerCase());
}

function isHeadingLike(line: string): boolean {
Expand Down