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
41 changes: 40 additions & 1 deletion scripts/check-comment-citations.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,45 @@ function stripStrings(line) {
return line.replace(/(["'`])(?:\\.|(?!\1)[^\\])*\1/g, (m) => " ".repeat(m.length));
}

// Index just past the literal opened at `start`, or -1 when it does not close on this line.
function literalEnd(line, start, regex) {
const close = regex ? "/" : line[start];
let inClass = false;
for (let i = start + 1; i < line.length; i++) {
const ch = line[i];
if (ch === "\\") i++;
else if (regex && ch === "[") inClass = true;
else if (regex && ch === "]") inClass = false;
else if (ch === close && !inClass) return i + 1;
}
return -1;
}

/** stripStrings for C-like code, plus regex literals, so `/["'`]/` opens no string or template.
* A `/` after a value, `<`, `++` or `--` is division or a tag (`a / b`, `</p>`, `i++ / 2`).
* Stops at the first comment. */
function stripLiterals(line) {
let out = "";
for (let i = 0; i < line.length; ) {
const ch = line[i];
const comment = ch === "/" && (line[i + 1] === "/" || line[i + 1] === "*");
if (comment) return out + line.slice(i);
// ponytail: `return /re/` reads as division; add a keyword check if that ever hides a comment.
const regex = ch === "/" && !/(?:[\w$)\]<]|\+\+|--)$/.test(out.trimEnd());
let end = regex || `"'\``.includes(ch) ? literalEnd(line, i, regex) : -1;
// A regex never closes on a comment opener: this `/` is division, e.g. continued from the line above.
if (regex && (line[end] === "/" || line[end] === "*")) end = -1;
if (end === -1) {
out += ch;
i++;
} else {
out += " ".repeat(end - i);
i = end;
}
}
return out;
}

function scanCLike(lines) {
const out = [];
let inBlock = false;
Expand Down Expand Up @@ -177,7 +216,7 @@ function scanCLike(lines) {
continue;
}
// Complete literals are blanked, so a backtick left over opens a multi-line template.
const code = stripStrings(rest);
const code = stripLiterals(rest);
const marks = [
[code.indexOf("//"), "line"],
[code.indexOf("/*"), "block"],
Expand Down
31 changes: 31 additions & 0 deletions scripts/comment-ratchet.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,37 @@ test("a JSX comment counts as comment, and code does not", () => {
assert.equal(measure("const a = 1;\n", ".ts").comment, 0);
});

test("quotes inside a regex literal open no string, so later comments still count", () => {
const source = [
"const keys = text.match(/[\"'`]([^\"'`]+)[\"'`]/g);",
"// one",
"const half = (a + b) / 2; // two",
"/* three */",
"const n = total / count / 2;",
"// four",
].join("\n");
assert.equal(measure(source, ".ts").comment, 4);
});

test("a JSX closing tag's slash opens no regex", () => {
assert.equal(measure("const a = <p>x</p>; // c\n", ".tsx").comment, 1);
assert.equal(measure("const a = <a>x</a>{/* note */};\n", ".tsx").comment, 1);
assert.equal(measure("const a = <div></div> {/* note */};\n", ".tsx").comment, 1);
assert.equal(measure("const a = <p>x</p>, b = 'a/b'; // it's\n", ".tsx").comment, 1);
});

test("a slash after ++ or -- is division", () => {
assert.equal(measure("const a = i++ / 2; // c\n", ".ts").comment, 1);
assert.equal(measure("const a = i-- / 2; // c\n", ".ts").comment, 1);
assert.equal(measure("const a = i++ / 2 + 'a/b'; // it's\n", ".ts").comment, 1);
});

test("a line opening with division continued from the line above keeps its comment", () => {
assert.equal(measure("const a = total\n / 2; // c\n", ".ts").comment, 1);
assert.equal(measure("const a = total\n / 2; /* c */\n", ".ts").comment, 1);
assert.equal(measure("const a = ok &&\n /[\"'`]/.test(s); // c\n", ".ts").comment, 1);
});

// 14 physical lines: a bare opener, 12 of prose, a bare closer.
test("a docblock is measured from its opener to its closer", () => {
const body = Array.from({ length: 12 }, (_, i) => ` * line ${i + 1}`).join("\n");
Expand Down
Loading