test/scripts/check-comments-ste.test.ts
Rama main · réplica de lectura del repositorio original.
import { assertArrayIncludes } from "@std/assert";
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import {
findCommentSteIssues,
MAX_SENTENCE_WORDS,
} from "#scripts/check-comments/ste.ts";
/** The rules one comment raises, failing loudly when it raises none. */
const rulesOf = (comment: string): string[] => {
const rules = findCommentSteIssues(comment).map(({ rule }) => rule);
if (rules.length === 0) throw new Error(`expected a finding for: ${comment}`);
return rules;
};
const sentenceOf = (words: number): string =>
Array.from({ length: words }, (_, index) => `word${index + 1}`).join(" ");
/** The one banned-modal finding a comment raises, failing loudly when it
* raises none. */
const findsOne = (comment: string, rule: string, line: number): void => {
const issues = findCommentSteIssues(comment);
expect(issues).toHaveLength(1);
expect(issues[0]!.rule).toBe(rule);
expect(issues[0]!.line).toBe(line);
};
describe("comment language rules", () => {
test("flags the mechanical patterns by rule name", () => {
assertArrayIncludes(rulesOf("// We don't ship this yet."), ["contraction"]);
assertArrayIncludes(rulesOf("// The form should save itself."), [
"banned-modal",
]);
assertArrayIncludes(rulesOf("// Load the row; then act."), ["semicolon"]);
assertArrayIncludes(rulesOf("// The row saves, allowing a retry."), [
"participle",
]);
assertArrayIncludes(rulesOf("// The row has been saved."), [
"present-perfect",
]);
assertArrayIncludes(rulesOf("// The check simply passes."), ["wordy"]);
});
test("leaves a possessive alone", () => {
expect(() => rulesOf("// The listing's own price applies.")).toThrow();
});
test("leaves a backticked span alone", () => {
expect(() => rulesOf("// The marker is `don't ship`.")).toThrow();
});
test("leaves a quoted error message alone", () => {
expect(() => rulesOf('// The write fails: "wasn\'t finalized".')).toThrow();
expect(() =>
rulesOf('// The error "wasn\'t finalized" repeats.'),
).toThrow();
expect(() =>
rulesOf('// Throws new Error("it should not happen").'),
).toThrow();
// The exemption is asserted on the returned list, so a crash while
// reading the comment cannot pass for an empty one.
expect(
findCommentSteIssues(
'// the error sentence — e.g. "its secrets can\'t be read".',
),
).toEqual([]);
});
test("judges quoted prose", () => {
assertArrayIncludes(rulesOf('// The path "should retry" after failure.'), [
"banned-modal",
]);
assertArrayIncludes(rulesOf('// See e.g. "should retry" now.'), [
"banned-modal",
]);
assertArrayIncludes(rulesOf('// The error is "should retry".'), [
"banned-modal",
]);
});
test("leaves a camelCase name that starts with a banned modal alone", () => {
expect(() => rulesOf("// The helper shouldRetry twice.")).toThrow();
});
test("finds a pattern wrapped across block-comment lines", () => {
// "has been" spans two JSDoc lines, so a per-line match would miss it.
assertArrayIncludes(rulesOf("/**\n * The row has\n * been saved.\n */"), [
"present-perfect",
]);
});
test("finds a phrase wrapped across neighbouring line comments", () => {
assertArrayIncludes(rulesOf("// Do this in\n// order to proceed."), [
"wordy",
]);
});
test("finds a participle whose comma ends the line above", () => {
assertArrayIncludes(rulesOf("// The row saves,\n// allowing a retry."), [
"participle",
]);
});
test("flags a sentence past the word limit and names its length", () => {
const issue = findCommentSteIssues(
`/** ${sentenceOf(MAX_SENTENCE_WORDS + 1)}. */`,
);
expect(issue).toHaveLength(1);
expect(issue[0]!.rule).toBe("long-sentence");
expect(issue[0]!.problem).toBe(`${MAX_SENTENCE_WORDS + 1}-word sentence`);
});
test("holds a sentence wrapped across block lines to the limit", () => {
// The sentence spans three JSDoc lines; splitting per line would hide it.
const issue = findCommentSteIssues(`/**
* ${sentenceOf(MAX_SENTENCE_WORDS + 1)}.
*/`);
expect(issue).toHaveLength(1);
});
test("holds a sentence wrapped across line comments to the limit", () => {
// Consecutive // comments are one prose block, so the wrapped sentence
// counts as the one sentence it is.
const issue = findCommentSteIssues(
`// ${sentenceOf(MAX_SENTENCE_WORDS - 2)}\n// and three more words.`,
);
expect(issue).toHaveLength(1);
expect(issue[0]!.line).toBe(1);
});
test("code between line comments keeps their sentences apart", () => {
const issues = findCommentSteIssues(
`// ${sentenceOf(MAX_SENTENCE_WORDS - 5)}\ncode();\n// ${sentenceOf(MAX_SENTENCE_WORDS - 5)}`,
);
expect(issues).toHaveLength(0);
});
test("attribution names the line the sentence starts on", () => {
const issue = findCommentSteIssues(`/**
* A heading line.
*
* ${sentenceOf(MAX_SENTENCE_WORDS + 1)}.
*/`);
expect(issue).toHaveLength(1);
expect(issue[0]!.line).toBe(4);
});
test("a sentence at the limit is not a finding", () => {
expect(
findCommentSteIssues(`/** ${sentenceOf(MAX_SENTENCE_WORDS)}. */`),
).toHaveLength(0);
});
test("a bare block-comment closer adds no word to the last sentence", () => {
expect(
findCommentSteIssues(`/**\n * ${sentenceOf(MAX_SENTENCE_WORDS)}\n */`),
).toHaveLength(0);
});
test("a fenced code block is not prose", () => {
const issues = findCommentSteIssues(`/**
* The gate should stay off by default.
*
* \`\`\`js
* const config = shouldRetryWithBackoff(pending, ${sentenceOf(30)});
* \`\`\`
*
* A closing don't below the fence is still prose.
*/`);
expect(issues.map(({ line, rule }) => [line, rule])).toEqual([
[2, "banned-modal"],
[8, "contraction"],
]);
});
test("a fence stays closed across neighbouring line comments", () => {
findsOne(
"// ```ts\n// const config = shouldRetry(pending);\n// ```\n// A closing don't after the fence.",
"contraction",
4,
);
});
test("a fence with trailing text does not close", () => {
// ```example is fence text in Markdown, not a closing marker, so the
// lines after it stay inside the fence.
expect(
findCommentSteIssues(
"// ```ts\n// const value = 1;\n// ```example\n// don't trust this line.",
),
).toHaveLength(0);
});
test("code between comments ends an open fence", () => {
findsOne(
"// ```ts\nconst code = 1;\n// The form should save.",
"banned-modal",
3,
);
});
test("an indented code block inside a comment is not prose", () => {
expect(
findCommentSteIssues("// Example:\n//\n// const value = 1;"),
).toHaveLength(0);
});
test("an example carries across neighbouring line comments", () => {
const issues = findCommentSteIssues(
"// @example\n//\n// const value = 1;\n//\n// @returns the opened resource, giving it back after use",
);
expect(issues).toHaveLength(1);
expect(issues[0]!.rule).toBe("participle");
expect(issues[0]!.line).toBe(5);
});
test("a comment that only mentions a directive is prose", () => {
findsOne(
"// Remove @ts-ignore because this should compile.",
"banned-modal",
1,
);
});
test("a star list's items hold their sentences separately", () => {
const issues = findCommentSteIssues(
`/**\n * * ${sentenceOf(MAX_SENTENCE_WORDS)}\n * * ${sentenceOf(MAX_SENTENCE_WORDS + 3)}\n */`,
);
expect(issues).toHaveLength(1);
expect(issues[0]!.problem).toBe(`${MAX_SENTENCE_WORDS + 3}-word sentence`);
});
test("two comments on one line are separate prose blocks", () => {
expect(
findCommentSteIssues("/* It has */ save(); /* been stopped. */"),
).toHaveLength(0);
});
test("a JSDoc tag's own token is not a word of its description", () => {
expect(
findCommentSteIssues(
`/**\n * @returns ${sentenceOf(MAX_SENTENCE_WORDS)}.\n */`,
),
).toHaveLength(0);
});
test("a JSDoc tag's argument is not a word of its description", () => {
expect(
findCommentSteIssues(
`/**\n * @param attendee ${sentenceOf(MAX_SENTENCE_WORDS)}.\n */`,
),
).toHaveLength(0);
});
test("a four-backtick fence closes only at its own marker", () => {
const issues = findCommentSteIssues(
"// ````ts\n// const value = shouldRetry;\n// ```\n// don't trust this line.\n// ````",
);
expect(issues).toHaveLength(0);
});
test("a tilde fence ends when code separates comments", () => {
findsOne(
"// ~~~\n// const value = 1;\ncode();\n// The form should save.",
"banned-modal",
4,
);
});
test("a spaced dash is not a word", () => {
const issues = findCommentSteIssues(
`// ${sentenceOf(MAX_SENTENCE_WORDS - 1)} — more.`,
);
expect(issues).toHaveLength(0);
});
test("code on the comment's own line ends an open fence", () => {
findsOne("// ```ts\ncode(); // The form should save.", "banned-modal", 2);
});
test("neighbouring block comments hold separate sentences", () => {
expect(
findCommentSteIssues("/* It has */\n/* been stopped. */"),
).toHaveLength(0);
});
test("an abbreviation's period does not end a sentence", () => {
const issues = findCommentSteIssues(
`// Load the rows, the groups, the bookings, the listings, and the settings etc. ${sentenceOf(MAX_SENTENCE_WORDS)} flow.`,
);
expect(issues).toHaveLength(1);
expect(issues[0]!.rule).toBe("long-sentence");
});
test("an inline link's target is not prose and its label stays", () => {
expect(() =>
rulesOf("// Delegates to {@link should} when the asset is ready."),
).toThrow();
findsOne("// The {@link guide should} page loads.", "banned-modal", 1);
});
test("a decorator line stays inside an unfenced example", () => {
expect(
findCommentSteIssues(
"/**\n * @example\n * @sealed\n * class Example { should = true; }\n */",
),
).toHaveLength(0);
});
test("a known tag still ends an unfenced example", () => {
findsOne(
"/**\n * @example\n * const value = 1;\n * @returns the resource\n */\n// The form should save.",
"banned-modal",
6,
);
});
test("a tag inside a fence leaves no example state behind", () => {
findsOne(
"// ```ts\n// @example\n// ```\n// The form should save.",
"banned-modal",
4,
);
});
test("a block comment's example ends with its closer", () => {
findsOne(
"/**\n * @example\n * const value = 1;\n */\n// The form should save.",
"banned-modal",
5,
);
});
test("a line comment after a block comment holds its own sentences", () => {
expect(
findCommentSteIssues("/* It has */\n// been stopped. */"),
).toHaveLength(0);
});
test("an indented fence marker opens and ends its fence", () => {
findsOne(
"// ```ts\ncode();\n// The form should save.",
"banned-modal",
3,
);
});
test("a block comment starts fresh behind a blanked fence row", () => {
findsOne("// ```ts\n/** The form should save. */", "banned-modal", 2);
});
test("a nested JSDoc type argument is not prose", () => {
expect(
findCommentSteIssues(
`/**\n * @param {{label: string}} options ${sentenceOf(MAX_SENTENCE_WORDS)}.\n */`,
),
).toHaveLength(0);
});
test("a tag's type is not a word of its description", () => {
expect(
findCommentSteIssues(
`/**\n * @returns {boolean} ${sentenceOf(MAX_SENTENCE_WORDS)}.\n */`,
),
).toHaveLength(0);
});
test("an indented closer ends an indented fence", () => {
findsOne(
"// ```ts\n// const value = 1;\n// ```\n// don't trust this line.",
"contraction",
4,
);
});
test("prose before a directive line is still prose", () => {
findsOne(
"/**\n * We should retry.\n * biome-ignore lint/x/y: reason\n */",
"banned-modal",
2,
);
});
test("line-comment bullets keep their own blocks", () => {
// The `*` after the `//` is a bullet marker, not comment gutter, so the
// two entries stay two blocks and their words never add up.
expect(
findCommentSteIssues(
`// * ${sentenceOf(MAX_SENTENCE_WORDS - 4)}\n// * ${sentenceOf(MAX_SENTENCE_WORDS - 4)}`,
),
).toHaveLength(0);
});
test("an example body is not prose, the tags around it are", () => {
const issues = findCommentSteIssues(`/**
* Frees the resource even when the work throws.
*
* @example
* const withConnection = bracket(
* () => openConnection(),
* (conn) => conn.close()
* );
* const result = await withConnection(async (conn) => conn.query('SELECT 1'));
*
* @returns the opened resource, giving it back after use
*/`);
expect(issues).toHaveLength(1);
expect(issues[0]!.rule).toBe("participle");
expect(issues[0]!.line).toBe(11);
});
test("a bullet's marker is not a word, its sentence is still held", () => {
const issue = findCommentSteIssues(
`// - ${sentenceOf(MAX_SENTENCE_WORDS + 1)}.`,
);
expect(issue).toHaveLength(1);
expect(issue[0]!.problem).toBe(`${MAX_SENTENCE_WORDS + 1}-word sentence`);
});
test("a table follows the Markdown rules the policy check uses", () => {
// A lone row reads as one paragraph, so its words face every rule.
assertArrayIncludes(rulesOf("// | don't |"), ["contraction"]);
// A real table is not prose: marked leaves its rows out entirely.
expect(
findCommentSteIssues(
`// | should | could |\n// | --- | --- |\n// | ${sentenceOf(MAX_SENTENCE_WORDS + 5)} |`,
),
).toHaveLength(0);
});
test("a directive's explanation is prose, its machine part is not", () => {
const issues = findCommentSteIssues(
"// biome-ignore lint/suspicious/noControlCharactersInRegex: don't allow tabs here.",
);
expect(issues).toHaveLength(1);
expect(issues[0]!.rule).toBe("contraction");
expect(issues[0]!.line).toBe(1);
expect(
findCommentSteIssues("/* jscpd:ignore-start -- imports */"),
).toHaveLength(0);
});
test("a suppression's reason without a colon is prose too", () => {
findsOne(
"// @ts-expect-error The call should fail in this test.",
"banned-modal",
1,
);
});
test("a suppression's reason keeps its words before a colon", () => {
findsOne(
"// @ts-expect-error The call should fail: invalid input.",
"banned-modal",
1,
);
});
test("a remarks tag ends an example", () => {
findsOne(
"/**\n * @example\n * const value = 1;\n * @remarks The form should save.\n */",
"banned-modal",
4,
);
});
test("a sentence ends at a period inside a closing parenthesis", () => {
expect(
findCommentSteIssues(
"// (The panel hides the rows from the operator because the dashboard owns them.) Then the page shows one card for every day of the week that holds slots.",
),
).toHaveLength(0);
});
test("an HTML entity's semicolon is not a sentence separator", () => {
expect(() =>
rulesOf("// The Google URL's & is escaped to & in the href."),
).toThrow();
});
test("findings report in source order across comment kinds", () => {
// The sentence on line 2 is found after line 3's contraction, so the
// finder has to sort them back into source order.
const issues = findCommentSteIssues(`/**
* ${sentenceOf(MAX_SENTENCE_WORDS + 1)}.
* and don't stop here.
*/`);
expect(issues.map(({ line, rule }) => [line, rule])).toEqual([
[2, "long-sentence"],
[3, "contraction"],
]);
});
});