@danielsimonjr/repo-tools 2.2.1 → 2.2.2

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 CHANGED
@@ -6,6 +6,55 @@ All notable changes to this project are recorded in this file. The format follow
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.2.2] - 2026-10-15
10
+
11
+ ### Changed
12
+
13
+ - The workflows pin three GitHub Actions to a newer release: `actions/upload-artifact` v7.0.1 to v7.0.2 (in `ci.yml` and
14
+ `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
15
+ `ci.yml`). Each pin is the commit of the upstream release tag. `build.yml` runs only on a `v*` tag, so the next release
16
+ build is the first run of the new `download-artifact` pin.
17
+
18
+ ### Fixed
19
+
20
+ - `repo-tools docs` reads a `.tsx` or `.jsx` file that has a bare `&` in JSX text (`<h3>Review & Start</h3>`).
21
+ React and `tsc` accept the text, and the grammar gave an error node, so the file was an unparsed file and its
22
+ symbols were not measured. The analyser now writes a space over a `&` that is the first character of an error
23
+ node directly inside a JSX element, which keeps the length, and parses again. Any other error stays an error.
24
+ A real syntax error beside such a text is still reported. In one application repository four files moved from
25
+ unparsed to measured, and two files with `import()` type expressions stay unparsed.
26
+ - `repo-tools docs check` reads the documentation of an overload set from its first overload. Before, the analyser read only
27
+ the comment that directly precedes a declaration, so an implementation signature that follows overload signatures counted
28
+ as undocumented (M1), although a caller sees only the overloads. A repository then needed a comment that no reader sees.
29
+ The implementation signature now takes the doc of the first overload of the same name, in a function set and in a class
30
+ member set. A doc that directly precedes the implementation still wins. A statement between the overloads and the
31
+ implementation ends the set. The `;` that ends a member signature in a class body does not end the set.
32
+ Rule M3 still compares the documented parameters with the implementation.
33
+ - `repo-tools docs` reads a file that has an `import("m")` type expression (`let v: import("fs").Dirent[]` and
34
+ `f<typeof import("m")>()`). `tsc` accepts both. The grammar reads the call as a member expression of a value, so it
35
+ cannot take `[]` after it, and it reads the call in a type argument as a comparison. Both gave error nodes, so the
36
+ file was an unparsed file. When the first parse has an error, the analyser now writes an identifier of equal length
37
+ over each `import("m")` call that holds a string literal, keeps its line breaks, and parses again. A file that
38
+ parses is never rewritten, and any other error stays an error. In the application repository of the earlier entry
39
+ the last two unparsed files move to measured.
40
+ - 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
41
+ 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
42
+ 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
43
+ names no machine, because the privacy check rejects machine names.
44
+ - The privacy check accepts one finding in the history: the squash commit of #18 (`46a45d4c19af`) carries a
45
+ `Co-authored-by` trailer that GitHub added for the maintainer identity on the pull request branch, at line 53 of the
46
+ message. A commit message cannot change after a merge, so `scripts/privacy-accepted.txt` holds the entry, as it does
47
+ for `fc930a32f5d2`. Before, the `privacy` job and the test `this repository scans clean` failed on `main` after the
48
+ merge, although the pull request was green. A squash whose commits are all by the pull request opener adds no trailer.
49
+ - `repo-tools docs` keeps the doc of a declaration when a tool directive stands between the doc comment and the
50
+ declaration (`// eslint-disable-next-line`, `// @ts-expect-error`, `// prettier-ignore`, `// biome-ignore` and the
51
+ other directives of a closed list). Before, the directive hid the doc and the symbol counted as undocumented (M1),
52
+ although a directive applies to the next line and so must stay directly above the declaration; a React repository
53
+ with the `react-refresh` lint rule had four such hooks and components. A plain line comment can move, so it still
54
+ separates a doc comment from its declaration. `docs stub` also writes
55
+ a new block above the directive and not between the directive and the declaration, where the directive would apply
56
+ to the block.
57
+
9
58
  ## [2.2.1] - 2026-10-08
10
59
 
11
60
  ### 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.1.
16
+ Status: version 2.2.2.
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.1):
44
+ [GitHub release](https://github.com/danielsimonjr/repo-tools/releases/tag/v2.2.2):
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.1",
5
+ version: "2.2.2",
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",
@@ -18208,13 +18208,60 @@ function anchorOf(node) {
18208
18208
  target = target.parent;
18209
18209
  return target;
18210
18210
  }
18211
- function leadingDoc(anchor, text) {
18212
- const prev = anchor.previousSibling;
18211
+ 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-])`);
18212
+ function isDirective(node, text) {
18213
+ return node.type === "comment" && DIRECTIVE.test(text(node));
18214
+ }
18215
+ function docBefore(anchor, text) {
18216
+ let prev = anchor.previousSibling;
18217
+ while (prev && isDirective(prev, text))
18218
+ prev = prev.previousSibling;
18213
18219
  if (prev?.type !== "comment")
18214
18220
  return "";
18215
18221
  const comment = text(prev);
18216
18222
  return comment.startsWith("/**") ? comment : "";
18217
18223
  }
18224
+ function stubAnchor(anchor, source, text) {
18225
+ let first = anchor;
18226
+ for (let prev = anchor.previousSibling;prev && isDirective(prev, text); ) {
18227
+ if (prev.endPosition.row + 1 < first.startPosition.row)
18228
+ break;
18229
+ const lineStart = source.lastIndexOf(`
18230
+ `, prev.startIndex - 1) + 1;
18231
+ if (source.slice(lineStart, prev.startIndex).trim() !== "")
18232
+ break;
18233
+ first = prev;
18234
+ prev = prev.previousSibling;
18235
+ }
18236
+ return first;
18237
+ }
18238
+ var SIGNATURES = new Set(["function_signature", "method_signature"]);
18239
+ function signatureName(sibling, text) {
18240
+ const inner = sibling.type === "export_statement" ? sibling.childForFieldName("declaration") : sibling;
18241
+ if (!inner || !SIGNATURES.has(inner.type))
18242
+ return null;
18243
+ const name2 = inner.childForFieldName("name");
18244
+ return name2 ? text(name2) : null;
18245
+ }
18246
+ function leadingDoc(anchor, node, text) {
18247
+ const own = docBefore(anchor, text);
18248
+ if (own || node.type !== "function_declaration" && node.type !== "method_definition") {
18249
+ return own;
18250
+ }
18251
+ const name2 = node.childForFieldName("name");
18252
+ if (!name2)
18253
+ return "";
18254
+ const implementation = text(name2);
18255
+ let first = null;
18256
+ for (let sibling = anchor.previousSibling;sibling; sibling = sibling.previousSibling) {
18257
+ if (sibling.type === "comment" || sibling.type === ";")
18258
+ continue;
18259
+ if (signatureName(sibling, text) !== implementation)
18260
+ break;
18261
+ first = sibling;
18262
+ }
18263
+ return first ? docBefore(first, text) : "";
18264
+ }
18218
18265
  function isExported2(node) {
18219
18266
  let parent = node.parent;
18220
18267
  while (parent) {
@@ -18240,8 +18287,53 @@ function proseOfComment(doc) {
18240
18287
  return proseOf(lines.join(`
18241
18288
  `));
18242
18289
  }
18290
+ var JSX_PARENTS = new Set(["jsx_element", "jsx_fragment"]);
18291
+ var MAX_REPAIRS = 16;
18292
+ var IMPORT_CALL = new RegExp(`\\bimport${SPACE_CLASS}*\\(${SPACE_CLASS}*(?:"[^"\\n]*"|'[^'\\n]*')${SPACE_CLASS}*\\)`, "g");
18293
+ function blankImportCalls(text) {
18294
+ return text.replace(IMPORT_CALL, (call) => {
18295
+ let name2 = true;
18296
+ let out2 = "";
18297
+ for (const ch of call) {
18298
+ if (/\s/u.test(ch)) {
18299
+ name2 = false;
18300
+ out2 += ch;
18301
+ } else {
18302
+ out2 += (name2 ? "_" : " ").repeat(ch.length);
18303
+ }
18304
+ }
18305
+ return out2;
18306
+ });
18307
+ }
18308
+ function parseSource(parser, text) {
18309
+ let current = sanitise(text);
18310
+ let tree = parser.parse(current);
18311
+ let importsBlanked = false;
18312
+ for (let pass = 0;tree?.rootNode.hasError && pass < MAX_REPAIRS; pass++) {
18313
+ const marks = [];
18314
+ for (const node of tree.rootNode.descendantsOfType("ERROR")) {
18315
+ const start2 = node.startIndex;
18316
+ if (current[start2] === "&" && node.parent && JSX_PARENTS.has(node.parent.type)) {
18317
+ marks.push(start2);
18318
+ }
18319
+ }
18320
+ let next = current;
18321
+ for (const at of marks)
18322
+ next = `${next.slice(0, at)} ${next.slice(at + 1)}`;
18323
+ if (!importsBlanked) {
18324
+ importsBlanked = true;
18325
+ next = blankImportCalls(next);
18326
+ }
18327
+ if (next === current)
18328
+ break;
18329
+ tree.delete();
18330
+ current = next;
18331
+ tree = parser.parse(current);
18332
+ }
18333
+ return tree;
18334
+ }
18243
18335
  function parsesAsTypeScript(path, text) {
18244
- const tree = parserFor(grammarFor(path)).parse(sanitise(text.replace(/^\uFEFF/, "")));
18336
+ const tree = parseSource(parserFor(grammarFor(path)), text.replace(/^\uFEFF/, ""));
18245
18337
  if (!tree)
18246
18338
  return false;
18247
18339
  try {
@@ -18254,7 +18346,7 @@ async function analyseTypeScript(path, source) {
18254
18346
  const report = newReport(path, "typescript");
18255
18347
  const grammar = grammarFor(path);
18256
18348
  await loadGrammar(grammar);
18257
- const tree = parserFor(grammar).parse(sanitise(source));
18349
+ const tree = parseSource(parserFor(grammar), source);
18258
18350
  if (!tree) {
18259
18351
  report.error = "parse error (the parser returned no tree)";
18260
18352
  return report;
@@ -18268,7 +18360,8 @@ async function analyseTypeScript(path, source) {
18268
18360
  for (const node of tree.rootNode.descendantsOfType(DECLARATION_TYPES)) {
18269
18361
  const kind = DECLARATIONS[node.type];
18270
18362
  const anchor = anchorOf(node);
18271
- const doc = leadingDoc(anchor, text);
18363
+ const lead = stubAnchor(anchor, source, text);
18364
+ const doc = leadingDoc(anchor, node, text);
18272
18365
  const dialect = dialectOf(doc);
18273
18366
  const typeLike = kind === "class" || kind === "interface" || kind === "type";
18274
18367
  const line = node.startPosition.row + 1;
@@ -18276,8 +18369,8 @@ async function analyseTypeScript(path, source) {
18276
18369
  const sym = {
18277
18370
  file: path,
18278
18371
  line,
18279
- anchorLine: anchor.startPosition.row + 1,
18280
- anchorColumn: anchor.startPosition.column,
18372
+ anchorLine: lead.startPosition.row + 1,
18373
+ anchorColumn: lead.startPosition.column,
18281
18374
  name: nameNode ? text(nameNode) : "<anonymous>",
18282
18375
  kind,
18283
18376
  exported: isExported2(node),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/repo-tools",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
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",