@danielsimonjr/repo-tools 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +2 -2
- package/dist/cli.js +109 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,67 @@ All notable changes to this project are recorded in this file. The format follow
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.2.3] - 2026-10-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `repo-tools map` reads a `.tsx` or `.jsx` file with the TSX grammar. It read the file with the TypeScript grammar,
|
|
14
|
+
which cannot read JSX. That grammar gives error nodes, and the export statements that survive the error recovery
|
|
15
|
+
depend on the text of the file. The same component file gave 1 export without doc comments and 2 exports with doc
|
|
16
|
+
comments. In one application repository of 746 source files, the export count rises from 3259 to 3406, and the
|
|
17
|
+
count is the same with and without the doc comments. A drift gate (`repo-tools check`) that holds the earlier count
|
|
18
|
+
fails until `repo-tools map` writes the reports again.
|
|
19
|
+
A `.js`, `.mjs` or `.cjs` file that holds JSX still uses the TypeScript grammar.
|
|
20
|
+
|
|
21
|
+
## [2.2.2] - 2026-10-15
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The workflows pin three GitHub Actions to a newer release: `actions/upload-artifact` v7.0.1 to v7.0.2 (in `ci.yml` and
|
|
26
|
+
`build.yml`), `actions/download-artifact` v8.0.1 to v8.0.2 (in `build.yml`) and `actions/setup-node` v7.0.0 to v7.1.0 (in
|
|
27
|
+
`ci.yml`). Each pin is the commit of the upstream release tag. `build.yml` runs only on a `v*` tag, so the next release
|
|
28
|
+
build is the first run of the new `download-artifact` pin.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- `repo-tools docs` reads a `.tsx` or `.jsx` file that has a bare `&` in JSX text (`<h3>Review & Start</h3>`).
|
|
33
|
+
React and `tsc` accept the text, and the grammar gave an error node, so the file was an unparsed file and its
|
|
34
|
+
symbols were not measured. The analyser now writes a space over a `&` that is the first character of an error
|
|
35
|
+
node directly inside a JSX element, which keeps the length, and parses again. Any other error stays an error.
|
|
36
|
+
A real syntax error beside such a text is still reported. In one application repository four files moved from
|
|
37
|
+
unparsed to measured, and two files with `import()` type expressions stay unparsed.
|
|
38
|
+
- `repo-tools docs check` reads the documentation of an overload set from its first overload. Before, the analyser read only
|
|
39
|
+
the comment that directly precedes a declaration, so an implementation signature that follows overload signatures counted
|
|
40
|
+
as undocumented (M1), although a caller sees only the overloads. A repository then needed a comment that no reader sees.
|
|
41
|
+
The implementation signature now takes the doc of the first overload of the same name, in a function set and in a class
|
|
42
|
+
member set. A doc that directly precedes the implementation still wins. A statement between the overloads and the
|
|
43
|
+
implementation ends the set. The `;` that ends a member signature in a class body does not end the set.
|
|
44
|
+
Rule M3 still compares the documented parameters with the implementation.
|
|
45
|
+
- `repo-tools docs` reads a file that has an `import("m")` type expression (`let v: import("fs").Dirent[]` and
|
|
46
|
+
`f<typeof import("m")>()`). `tsc` accepts both. The grammar reads the call as a member expression of a value, so it
|
|
47
|
+
cannot take `[]` after it, and it reads the call in a type argument as a comparison. Both gave error nodes, so the
|
|
48
|
+
file was an unparsed file. When the first parse has an error, the analyser now writes an identifier of equal length
|
|
49
|
+
over each `import("m")` call that holds a string literal, keeps its line breaks, and parses again. A file that
|
|
50
|
+
parses is never rewritten, and any other error stays an error. In the application repository of the earlier entry
|
|
51
|
+
the last two unparsed files move to measured.
|
|
52
|
+
- Two fan-out tests (`map-graph.test.ts` and `map-query.test.ts`) have an explicit 30 s timeout. Each builds a graph of about
|
|
53
|
+
9,000 nodes. The graph test took 0.85 s on a fast machine and 4.57 s on a slower one, against bun's 5 s default, so it
|
|
54
|
+
failed there under any load. The tests check a step bound, not time, so only the timeout changes. A comment on each test records the measurement and
|
|
55
|
+
names no machine, because the privacy check rejects machine names.
|
|
56
|
+
- The privacy check accepts one finding in the history: the squash commit of #18 (`46a45d4c19af`) carries a
|
|
57
|
+
`Co-authored-by` trailer that GitHub added for the maintainer identity on the pull request branch, at line 53 of the
|
|
58
|
+
message. A commit message cannot change after a merge, so `scripts/privacy-accepted.txt` holds the entry, as it does
|
|
59
|
+
for `fc930a32f5d2`. Before, the `privacy` job and the test `this repository scans clean` failed on `main` after the
|
|
60
|
+
merge, although the pull request was green. A squash whose commits are all by the pull request opener adds no trailer.
|
|
61
|
+
- `repo-tools docs` keeps the doc of a declaration when a tool directive stands between the doc comment and the
|
|
62
|
+
declaration (`// eslint-disable-next-line`, `// @ts-expect-error`, `// prettier-ignore`, `// biome-ignore` and the
|
|
63
|
+
other directives of a closed list). Before, the directive hid the doc and the symbol counted as undocumented (M1),
|
|
64
|
+
although a directive applies to the next line and so must stay directly above the declaration; a React repository
|
|
65
|
+
with the `react-refresh` lint rule had four such hooks and components. A plain line comment can move, so it still
|
|
66
|
+
separates a doc comment from its declaration. `docs stub` also writes
|
|
67
|
+
a new block above the directive and not between the directive and the declaration, where the directive would apply
|
|
68
|
+
to the block.
|
|
69
|
+
|
|
9
70
|
## [2.2.1] - 2026-10-08
|
|
10
71
|
|
|
11
72
|
### Fixed
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| `query` | Answers structural questions from the graph of `map`, and writes two derived reports. |
|
|
14
14
|
| `ste` | Checks Markdown or docstring prose against Simplified Technical English. |
|
|
15
15
|
|
|
16
|
-
Status: version 2.2.
|
|
16
|
+
Status: version 2.2.3.
|
|
17
17
|
|
|
18
18
|
- `map`, `check` and `query` read their settings from `repo-tools.config.json` in the root. The
|
|
19
19
|
section of `map` is `map` (its old name, `depgraph`, is also read). `--config=<file>` makes any
|
|
@@ -41,7 +41,7 @@ repo-tools --version
|
|
|
41
41
|
|
|
42
42
|
The executable holds the tool and the Bun runtime. It needs no Bun, Node or `node_modules`.
|
|
43
43
|
Download the file for your platform and `SHA256SUMS` from the
|
|
44
|
-
[GitHub release](https://github.com/danielsimonjr/repo-tools/releases/tag/v2.2.
|
|
44
|
+
[GitHub release](https://github.com/danielsimonjr/repo-tools/releases/tag/v2.2.3):
|
|
45
45
|
|
|
46
46
|
| Platform | File |
|
|
47
47
|
|---|---|
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "@danielsimonjr/repo-tools",
|
|
5
|
-
version: "2.2.
|
|
5
|
+
version: "2.2.3",
|
|
6
6
|
description: "One CLI for repository analysis: dependency graphs creation and query with per-export API facts, file chunking, and context compression. TypeScript on Bun, compiled to a single executable.",
|
|
7
7
|
type: "module",
|
|
8
8
|
license: "MIT",
|
|
@@ -10895,6 +10895,9 @@ function loadGrammar(name2) {
|
|
|
10895
10895
|
}
|
|
10896
10896
|
return pending;
|
|
10897
10897
|
}
|
|
10898
|
+
function grammarFor(path) {
|
|
10899
|
+
return path.endsWith(".tsx") || path.endsWith(".jsx") ? "tsx" : "typescript";
|
|
10900
|
+
}
|
|
10898
10901
|
function parserFor(name2) {
|
|
10899
10902
|
const parser = parsers.get(name2);
|
|
10900
10903
|
if (!parser)
|
|
@@ -11122,8 +11125,8 @@ function addDynamicImportEdges(mod, calls) {
|
|
|
11122
11125
|
});
|
|
11123
11126
|
}
|
|
11124
11127
|
}
|
|
11125
|
-
function parseTs(source) {
|
|
11126
|
-
const tree = parserFor(
|
|
11128
|
+
function parseTs(source, path = "") {
|
|
11129
|
+
const tree = parserFor(grammarFor(path)).parse(source);
|
|
11127
11130
|
if (!tree)
|
|
11128
11131
|
throw new Error("tree-sitter returned no tree");
|
|
11129
11132
|
const mod = emptyModule();
|
|
@@ -12568,6 +12571,8 @@ async function buildGraph(root) {
|
|
|
12568
12571
|
}
|
|
12569
12572
|
if (language === "typescript" || language === "python")
|
|
12570
12573
|
await loadGrammar(language);
|
|
12574
|
+
if (language === "typescript" && found.some((f) => grammarFor(f.path) === "tsx"))
|
|
12575
|
+
await loadGrammar("tsx");
|
|
12571
12576
|
const resolver = getResolver(language);
|
|
12572
12577
|
const read = READERS[language];
|
|
12573
12578
|
const parsed = new Map;
|
|
@@ -12576,7 +12581,7 @@ async function buildGraph(root) {
|
|
|
12576
12581
|
const text = readSource(join11(root, f.path)).replace(/\r\n?/g, `
|
|
12577
12582
|
`);
|
|
12578
12583
|
sources.set(f.path, text);
|
|
12579
|
-
parsed.set(f.path, read(text));
|
|
12584
|
+
parsed.set(f.path, read(text, f.path));
|
|
12580
12585
|
}
|
|
12581
12586
|
resolver.indexNamespaces?.(parsed);
|
|
12582
12587
|
const files = new Map;
|
|
@@ -18164,9 +18169,6 @@ var DECLARATIONS = {
|
|
|
18164
18169
|
method_definition: "method"
|
|
18165
18170
|
};
|
|
18166
18171
|
var DECLARATION_TYPES = Object.keys(DECLARATIONS);
|
|
18167
|
-
function grammarFor(path) {
|
|
18168
|
-
return path.endsWith(".tsx") || path.endsWith(".jsx") ? "tsx" : "typescript";
|
|
18169
|
-
}
|
|
18170
18172
|
var SPACE_CLASS = "[ \\t\\n\\r\\f\\v]";
|
|
18171
18173
|
var ABSTRACT_PROPERTY = new RegExp(`\\babstract(${SPACE_CLASS}*\\??${SPACE_CLASS}*:)`, "g");
|
|
18172
18174
|
var EXPORT_TYPE_STAR = new RegExp(`(\\bexport${SPACE_CLASS}+)type(${SPACE_CLASS}*\\*)`, "g");
|
|
@@ -18208,13 +18210,60 @@ function anchorOf(node) {
|
|
|
18208
18210
|
target = target.parent;
|
|
18209
18211
|
return target;
|
|
18210
18212
|
}
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
+
var DIRECTIVE = new RegExp(`^//${SPACE_CLASS}*(?:eslint-(?:disable|enable)(?:-next-line|-line)?|@ts-(?:ignore|expect-error|nocheck|check)|` + "prettier-ignore|biome-ignore|oxlint-disable|deno-lint-ignore|tslint:(?:disable|enable)|" + `(?:istanbul|c8|v8)${SPACE_CLASS}+ignore)(?![\\w-])`);
|
|
18214
|
+
function isDirective(node, text) {
|
|
18215
|
+
return node.type === "comment" && DIRECTIVE.test(text(node));
|
|
18216
|
+
}
|
|
18217
|
+
function docBefore(anchor, text) {
|
|
18218
|
+
let prev = anchor.previousSibling;
|
|
18219
|
+
while (prev && isDirective(prev, text))
|
|
18220
|
+
prev = prev.previousSibling;
|
|
18213
18221
|
if (prev?.type !== "comment")
|
|
18214
18222
|
return "";
|
|
18215
18223
|
const comment = text(prev);
|
|
18216
18224
|
return comment.startsWith("/**") ? comment : "";
|
|
18217
18225
|
}
|
|
18226
|
+
function stubAnchor(anchor, source, text) {
|
|
18227
|
+
let first = anchor;
|
|
18228
|
+
for (let prev = anchor.previousSibling;prev && isDirective(prev, text); ) {
|
|
18229
|
+
if (prev.endPosition.row + 1 < first.startPosition.row)
|
|
18230
|
+
break;
|
|
18231
|
+
const lineStart = source.lastIndexOf(`
|
|
18232
|
+
`, prev.startIndex - 1) + 1;
|
|
18233
|
+
if (source.slice(lineStart, prev.startIndex).trim() !== "")
|
|
18234
|
+
break;
|
|
18235
|
+
first = prev;
|
|
18236
|
+
prev = prev.previousSibling;
|
|
18237
|
+
}
|
|
18238
|
+
return first;
|
|
18239
|
+
}
|
|
18240
|
+
var SIGNATURES = new Set(["function_signature", "method_signature"]);
|
|
18241
|
+
function signatureName(sibling, text) {
|
|
18242
|
+
const inner = sibling.type === "export_statement" ? sibling.childForFieldName("declaration") : sibling;
|
|
18243
|
+
if (!inner || !SIGNATURES.has(inner.type))
|
|
18244
|
+
return null;
|
|
18245
|
+
const name2 = inner.childForFieldName("name");
|
|
18246
|
+
return name2 ? text(name2) : null;
|
|
18247
|
+
}
|
|
18248
|
+
function leadingDoc(anchor, node, text) {
|
|
18249
|
+
const own = docBefore(anchor, text);
|
|
18250
|
+
if (own || node.type !== "function_declaration" && node.type !== "method_definition") {
|
|
18251
|
+
return own;
|
|
18252
|
+
}
|
|
18253
|
+
const name2 = node.childForFieldName("name");
|
|
18254
|
+
if (!name2)
|
|
18255
|
+
return "";
|
|
18256
|
+
const implementation = text(name2);
|
|
18257
|
+
let first = null;
|
|
18258
|
+
for (let sibling = anchor.previousSibling;sibling; sibling = sibling.previousSibling) {
|
|
18259
|
+
if (sibling.type === "comment" || sibling.type === ";")
|
|
18260
|
+
continue;
|
|
18261
|
+
if (signatureName(sibling, text) !== implementation)
|
|
18262
|
+
break;
|
|
18263
|
+
first = sibling;
|
|
18264
|
+
}
|
|
18265
|
+
return first ? docBefore(first, text) : "";
|
|
18266
|
+
}
|
|
18218
18267
|
function isExported2(node) {
|
|
18219
18268
|
let parent = node.parent;
|
|
18220
18269
|
while (parent) {
|
|
@@ -18240,8 +18289,53 @@ function proseOfComment(doc) {
|
|
|
18240
18289
|
return proseOf(lines.join(`
|
|
18241
18290
|
`));
|
|
18242
18291
|
}
|
|
18292
|
+
var JSX_PARENTS = new Set(["jsx_element", "jsx_fragment"]);
|
|
18293
|
+
var MAX_REPAIRS = 16;
|
|
18294
|
+
var IMPORT_CALL = new RegExp(`\\bimport${SPACE_CLASS}*\\(${SPACE_CLASS}*(?:"[^"\\n]*"|'[^'\\n]*')${SPACE_CLASS}*\\)`, "g");
|
|
18295
|
+
function blankImportCalls(text) {
|
|
18296
|
+
return text.replace(IMPORT_CALL, (call) => {
|
|
18297
|
+
let name2 = true;
|
|
18298
|
+
let out2 = "";
|
|
18299
|
+
for (const ch of call) {
|
|
18300
|
+
if (/\s/u.test(ch)) {
|
|
18301
|
+
name2 = false;
|
|
18302
|
+
out2 += ch;
|
|
18303
|
+
} else {
|
|
18304
|
+
out2 += (name2 ? "_" : " ").repeat(ch.length);
|
|
18305
|
+
}
|
|
18306
|
+
}
|
|
18307
|
+
return out2;
|
|
18308
|
+
});
|
|
18309
|
+
}
|
|
18310
|
+
function parseSource(parser, text) {
|
|
18311
|
+
let current = sanitise(text);
|
|
18312
|
+
let tree = parser.parse(current);
|
|
18313
|
+
let importsBlanked = false;
|
|
18314
|
+
for (let pass = 0;tree?.rootNode.hasError && pass < MAX_REPAIRS; pass++) {
|
|
18315
|
+
const marks = [];
|
|
18316
|
+
for (const node of tree.rootNode.descendantsOfType("ERROR")) {
|
|
18317
|
+
const start2 = node.startIndex;
|
|
18318
|
+
if (current[start2] === "&" && node.parent && JSX_PARENTS.has(node.parent.type)) {
|
|
18319
|
+
marks.push(start2);
|
|
18320
|
+
}
|
|
18321
|
+
}
|
|
18322
|
+
let next = current;
|
|
18323
|
+
for (const at of marks)
|
|
18324
|
+
next = `${next.slice(0, at)} ${next.slice(at + 1)}`;
|
|
18325
|
+
if (!importsBlanked) {
|
|
18326
|
+
importsBlanked = true;
|
|
18327
|
+
next = blankImportCalls(next);
|
|
18328
|
+
}
|
|
18329
|
+
if (next === current)
|
|
18330
|
+
break;
|
|
18331
|
+
tree.delete();
|
|
18332
|
+
current = next;
|
|
18333
|
+
tree = parser.parse(current);
|
|
18334
|
+
}
|
|
18335
|
+
return tree;
|
|
18336
|
+
}
|
|
18243
18337
|
function parsesAsTypeScript(path, text) {
|
|
18244
|
-
const tree = parserFor(grammarFor(path))
|
|
18338
|
+
const tree = parseSource(parserFor(grammarFor(path)), text.replace(/^\uFEFF/, ""));
|
|
18245
18339
|
if (!tree)
|
|
18246
18340
|
return false;
|
|
18247
18341
|
try {
|
|
@@ -18254,7 +18348,7 @@ async function analyseTypeScript(path, source) {
|
|
|
18254
18348
|
const report = newReport(path, "typescript");
|
|
18255
18349
|
const grammar = grammarFor(path);
|
|
18256
18350
|
await loadGrammar(grammar);
|
|
18257
|
-
const tree = parserFor(grammar)
|
|
18351
|
+
const tree = parseSource(parserFor(grammar), source);
|
|
18258
18352
|
if (!tree) {
|
|
18259
18353
|
report.error = "parse error (the parser returned no tree)";
|
|
18260
18354
|
return report;
|
|
@@ -18268,7 +18362,8 @@ async function analyseTypeScript(path, source) {
|
|
|
18268
18362
|
for (const node of tree.rootNode.descendantsOfType(DECLARATION_TYPES)) {
|
|
18269
18363
|
const kind = DECLARATIONS[node.type];
|
|
18270
18364
|
const anchor = anchorOf(node);
|
|
18271
|
-
const
|
|
18365
|
+
const lead = stubAnchor(anchor, source, text);
|
|
18366
|
+
const doc = leadingDoc(anchor, node, text);
|
|
18272
18367
|
const dialect = dialectOf(doc);
|
|
18273
18368
|
const typeLike = kind === "class" || kind === "interface" || kind === "type";
|
|
18274
18369
|
const line = node.startPosition.row + 1;
|
|
@@ -18276,8 +18371,8 @@ async function analyseTypeScript(path, source) {
|
|
|
18276
18371
|
const sym = {
|
|
18277
18372
|
file: path,
|
|
18278
18373
|
line,
|
|
18279
|
-
anchorLine:
|
|
18280
|
-
anchorColumn:
|
|
18374
|
+
anchorLine: lead.startPosition.row + 1,
|
|
18375
|
+
anchorColumn: lead.startPosition.column,
|
|
18281
18376
|
name: nameNode ? text(nameNode) : "<anonymous>",
|
|
18282
18377
|
kind,
|
|
18283
18378
|
exported: isExported2(node),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielsimonjr/repo-tools",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "One CLI for repository analysis: dependency graphs creation and query with per-export API facts, file chunking, and context compression. TypeScript on Bun, compiled to a single executable.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|