File size: 4,037 Bytes
6a2bc3b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
const ROOT = path.resolve(import.meta.dirname, '..');
const PACKAGES = ['packages/agent-core-v2', 'packages/kap-server', 'packages/transcript'];
const DIRS = ['src', 'test', 'scripts'];
function collectLeaves(node, leaves, jsdocNodes) {
if (ts.isJSDoc(node)) {
jsdocNodes.push(node);
return;
}
const children = node.getChildren();
if (children.length === 0) {
if (node.getWidth() > 0) leaves.push(node);
return;
}
for (const c of children) collectLeaves(c, leaves, jsdocNodes);
}
function extractGapComments(gap, offset, out) {
let i = 0;
while (i < gap.length) {
const ch = gap[i];
if (' \t\n\r\f\v'.includes(ch)) {
i++;
continue;
}
if (ch === '/' && gap[i + 1] === '/') {
let j = gap.indexOf('\n', i);
if (j === -1) j = gap.length;
out.push({ pos: offset + i, end: offset + j, jsdoc: false });
i = j;
continue;
}
if (ch === '/' && gap[i + 1] === '*') {
const close = gap.indexOf('*/', i + 2);
const j = close === -1 ? gap.length : close + 2;
out.push({ pos: offset + i, end: offset + j, jsdoc: gap.startsWith('/**', i) });
i = j;
continue;
}
break;
}
}
function checkFile(file) {
const text = fs.readFileSync(file, 'utf8');
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
const leaves = [];
const jsdocNodes = [];
collectLeaves(sf, leaves, jsdocNodes);
leaves.sort((a, b) => a.getStart(sf) - b.getStart(sf));
const comments = [];
let cursor = 0;
if (text.startsWith('#!')) {
const nl = text.indexOf('\n');
cursor = nl === -1 ? text.length : nl + 1;
}
for (const leaf of leaves) {
const s = leaf.getStart(sf);
if (s > cursor) extractGapComments(text.slice(cursor, s), cursor, comments);
cursor = Math.max(cursor, leaf.getEnd());
}
if (cursor < text.length) extractGapComments(text.slice(cursor), cursor, comments);
for (const d of jsdocNodes) {
comments.push({ pos: d.getStart(sf), end: d.getEnd(), jsdoc: true });
}
const seen = new Set();
const violations = [];
for (const c of comments) {
const key = `${c.pos}:${c.end}`;
if (seen.has(key)) continue;
seen.add(key);
const line = text.slice(0, c.pos).split('\n').length;
const snippet = text.slice(c.pos, Math.min(c.end, c.pos + 60)).replaceAll(/\s+/g, ' ');
if (/(?:oxlint|eslint)-disable/.test(snippet)) continue;
const isDirective = /@ts-(expect-error|ignore|nocheck)|prettier-ignore|istanbul|c8 ignore/.test(
snippet,
);
violations.push({ line, snippet, isDirective, jsdoc: c.jsdoc });
}
return violations;
}
const files = [];
for (const pkg of PACKAGES) {
for (const dir of DIRS) {
const root = path.join(ROOT, pkg, dir);
if (!fs.existsSync(root)) continue;
const stack = [root];
while (stack.length > 0) {
const d = stack.pop();
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const p = path.join(d, e.name);
if (e.isDirectory()) {
if (e.name !== 'node_modules') stack.push(p);
} else if (/\.(ts|tsx|mts|mjs)$/.test(e.name)) {
files.push(p);
}
}
}
}
}
let total = 0;
for (const f of files) {
const violations = checkFile(f);
for (const v of violations) {
total++;
const rel = path.relative(ROOT, f);
if (v.isDirective) {
console.error(`${rel}:${v.line}: tooling directives are not allowed — fix the underlying lint/type problem instead: ${v.snippet}`);
} else if (v.jsdoc) {
console.error(`${rel}:${v.line}: JSDoc is not allowed in this package: ${v.snippet}`);
} else {
console.error(`${rel}:${v.line}: comments are not allowed in this package: ${v.snippet}`);
}
}
}
if (total > 0) {
console.error(`check-no-comments: ${total} violation(s) in ${PACKAGES.join(', ')}`);
process.exit(1);
}
console.log(`check-no-comments: OK (${files.length} files)`);
|