Ctrl K
ComunidadGuías…

test/scripts/check-comments-ste.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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"],
    ]);
  });
});