@rigour-labs/core 6.12.2-rc.2 → 6.12.2-rc.4
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/dist/review/typed/redundancy.js +103 -5
- package/package.json +6 -6
|
@@ -264,6 +264,86 @@ function redundancyFailures(typed, changedLines, config) {
|
|
|
264
264
|
escapeMemo.set(t.sym, found);
|
|
265
265
|
return found;
|
|
266
266
|
};
|
|
267
|
+
// Where a value of T is put in a place declared as another type (a property typed as a same-shape inline type, a
|
|
268
|
+
// variable, a parameter, a return), T's members travel as that type: the declared types the place names.
|
|
269
|
+
const isFlowSite = (node) => {
|
|
270
|
+
const p = node.parent;
|
|
271
|
+
return !!p && ((ts.isPropertyAssignment(p) && p.initializer === node) || ts.isSpreadAssignment(p) || (ts.isVariableDeclaration(p) && p.initializer === node)
|
|
272
|
+
|| ts.isReturnStatement(p) || (ts.isArrowFunction(p) && p.body === node) || (ts.isCallExpression(p) && p.arguments.includes(node)));
|
|
273
|
+
};
|
|
274
|
+
// The declared types a place names. A property or spread in an object literal whose own declared type names none
|
|
275
|
+
// (an inline type) is carried by the literal: the declared types of the place the literal goes, a level up at a time.
|
|
276
|
+
const namedAt = (node, t) => {
|
|
277
|
+
for (let at = node; at && ts.isExpression(at); at = ts.isObjectLiteralExpression(at.parent?.parent) ? at.parent.parent : undefined) {
|
|
278
|
+
const target = checker.getContextualType(at);
|
|
279
|
+
if (!target || target.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) || mentionsType(t, target))
|
|
280
|
+
return [];
|
|
281
|
+
const named = declaredTypes.filter(d => d.sym !== t.sym && mentionsType(d, target));
|
|
282
|
+
if (named.length)
|
|
283
|
+
return named;
|
|
284
|
+
}
|
|
285
|
+
return [];
|
|
286
|
+
};
|
|
287
|
+
// The parts of `target` that receive T's values inside `value`, found by walking both the same way: arrays, union
|
|
288
|
+
// members of the value, and same-named properties (`R[]` receiving `{ …, p: T }[]` gives the type of `R.p`).
|
|
289
|
+
const receiving = (t, value, target, depth = 0) => {
|
|
290
|
+
const to = checker.getNonNullableType(target);
|
|
291
|
+
if (value.symbol === t.sym || value.aliasSymbol === t.sym)
|
|
292
|
+
return mentionsType(t, to) ? [] : [to];
|
|
293
|
+
if (depth > MENTION_DEPTH)
|
|
294
|
+
return [];
|
|
295
|
+
if (value.isUnion())
|
|
296
|
+
return value.types.flatMap(v => receiving(t, v, target, depth + 1));
|
|
297
|
+
if (checker.isArrayType(value))
|
|
298
|
+
return checker.isArrayType(to) ? receiving(t, checker.getTypeArguments(value)[0], checker.getTypeArguments(to)[0], depth + 1) : [];
|
|
299
|
+
if (!(value.flags & ts.TypeFlags.Object))
|
|
300
|
+
return [];
|
|
301
|
+
return checker.getPropertiesOfType(value).flatMap(p => {
|
|
302
|
+
const into = checker.getPropertyOfType(to, p.name);
|
|
303
|
+
return into ? receiving(t, checker.getTypeOfSymbol(p), checker.getTypeOfSymbol(into), depth + 1) : [];
|
|
304
|
+
});
|
|
305
|
+
};
|
|
306
|
+
// Where T's values go: the declared types they travel as (`into`), and the types of the places that receive them
|
|
307
|
+
// (`receivers`: a same-shape inline type, a parameter's type), whose members are T's members under another type.
|
|
308
|
+
const flowMemo = new Map();
|
|
309
|
+
const flowsOf = (t) => {
|
|
310
|
+
const cached = flowMemo.get(t.sym);
|
|
311
|
+
if (cached)
|
|
312
|
+
return cached;
|
|
313
|
+
const into = new Set();
|
|
314
|
+
const receivers = new Set();
|
|
315
|
+
for (const sf of typed.appSources) {
|
|
316
|
+
walk(sf, node => {
|
|
317
|
+
if (!ts.isExpression(node) || !isFlowSite(node) || !mentionsType(t, checker.getTypeAtLocation(node)))
|
|
318
|
+
return;
|
|
319
|
+
const target = checker.getContextualType(node);
|
|
320
|
+
if (target && !(target.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) && !mentionsType(t, target))
|
|
321
|
+
for (const r of receiving(t, checker.getTypeAtLocation(node), target))
|
|
322
|
+
receivers.add(r);
|
|
323
|
+
for (const d of namedAt(node, t))
|
|
324
|
+
into.add(d);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const found = { into: [...into], receivers: [...receivers] };
|
|
328
|
+
flowMemo.set(t.sym, found);
|
|
329
|
+
return found;
|
|
330
|
+
};
|
|
331
|
+
const flowsInto = (t) => flowsOf(t).into;
|
|
332
|
+
// T leaves the program where its own values do, or where a type it travels as does.
|
|
333
|
+
const leaves = (t, seen = new Set([t.sym])) => {
|
|
334
|
+
const direct = escapes(t);
|
|
335
|
+
if (direct)
|
|
336
|
+
return direct;
|
|
337
|
+
for (const d of flowsInto(t)) {
|
|
338
|
+
if (seen.has(d.sym))
|
|
339
|
+
continue;
|
|
340
|
+
seen.add(d.sym);
|
|
341
|
+
const via = leaves(d, seen);
|
|
342
|
+
if (via)
|
|
343
|
+
return `${via}, carried as ${d.name}`;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
};
|
|
267
347
|
// Values of the type read back from JSON (JSON.parse, readJson, a response's .json()) as the type: an assertion,
|
|
268
348
|
// an annotated variable, a type argument, or the declared return of the function that returns them. Data written
|
|
269
349
|
// before a member existed lacks it, so the member stays optional however every host in the code supplies it now.
|
|
@@ -318,8 +398,27 @@ function redundancyFailures(typed, changedLines, config) {
|
|
|
318
398
|
if (!sym)
|
|
319
399
|
return 'no such property';
|
|
320
400
|
const declaration = sym.declarations?.[0];
|
|
321
|
-
const
|
|
322
|
-
const
|
|
401
|
+
const ownRead = (s) => !!s && (s === sym || !!s.declarations?.some(d => d === declaration));
|
|
402
|
+
const ownType = (expr) => mentionsType(t, checker.getTypeAtLocation(expr));
|
|
403
|
+
let found = scanReads(name, ownRead, ownType);
|
|
404
|
+
if (found)
|
|
405
|
+
return found;
|
|
406
|
+
// A value of T received as another type (`p?: { x: string }` holding a T) is read through that type's member of the same name.
|
|
407
|
+
const { receivers } = flowsOf(t);
|
|
408
|
+
const members = new Set(receivers.map(r => checker.getPropertyOfType(r, name)).filter((m) => !!m));
|
|
409
|
+
if (members.size) {
|
|
410
|
+
const memberDecls = new Set([...members].flatMap(m => m.declarations ?? []));
|
|
411
|
+
const viaRead = (s) => !!s && (members.has(s) || !!s.declarations?.some(d => memberDecls.has(d)));
|
|
412
|
+
const viaType = (expr) => receivers.includes(checker.getNonNullableType(checker.getTypeAtLocation(expr)));
|
|
413
|
+
found = scanReads(name, viaRead, viaType);
|
|
414
|
+
if (found)
|
|
415
|
+
return found;
|
|
416
|
+
}
|
|
417
|
+
const text = textReads(t, name);
|
|
418
|
+
return text ? `${text.file} (by name)` : undefined;
|
|
419
|
+
};
|
|
420
|
+
// A read of `name` in a non-test file: an access whose symbol `isOurs` accepts, or a destructuring or `['name']` of a value `ofType` accepts.
|
|
421
|
+
const scanReads = (name, isOurs, ofType) => {
|
|
323
422
|
for (const sf of typed.appSources) {
|
|
324
423
|
if (!sf.text.includes(name) && !sf.text.includes('...'))
|
|
325
424
|
continue;
|
|
@@ -337,8 +436,7 @@ function redundancyFailures(typed, changedLines, config) {
|
|
|
337
436
|
if (read)
|
|
338
437
|
return at(read);
|
|
339
438
|
}
|
|
340
|
-
|
|
341
|
-
return text ? `${text.file} (by name)` : undefined;
|
|
439
|
+
return undefined;
|
|
342
440
|
};
|
|
343
441
|
for (const t of declaredTypes) {
|
|
344
442
|
const hosts = hostsOf(t);
|
|
@@ -375,7 +473,7 @@ function redundancyFailures(typed, changedLines, config) {
|
|
|
375
473
|
}
|
|
376
474
|
}
|
|
377
475
|
if (supplied.length && !wire && !isRead(t, name)) {
|
|
378
|
-
const exit =
|
|
476
|
+
const exit = leaves(t);
|
|
379
477
|
if (exit)
|
|
380
478
|
hints.push(`write-only-property ${at(m)}: ${t.name}.${name} is set by ${supplied.length} host(s) and read by no code; the value leaves only through ${exit}. Confirm an external reader needs it, else delete it`);
|
|
381
479
|
else if (published)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/core",
|
|
3
|
-
"version": "6.12.2-rc.
|
|
3
|
+
"version": "6.12.2-rc.4",
|
|
4
4
|
"description": "Rigour's review engine: deterministic gates on changed lines, rules and lessons learned from your team's fixes, and per-check precision from what you fix versus dismiss, across TypeScript, JavaScript, Python, Go, Ruby and C#.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13"
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"@anthropic-ai/sdk": "^0.132.1",
|
|
76
76
|
"pg": "^8.16.3",
|
|
77
77
|
"openai": "^5.23.2",
|
|
78
|
-
"@rigour-labs/brain-darwin-arm64": "6.12.2-rc.
|
|
79
|
-
"@rigour-labs/brain-
|
|
80
|
-
"@rigour-labs/brain-linux-arm64": "6.12.2-rc.
|
|
81
|
-
"@rigour-labs/brain-win-x64": "6.12.2-rc.
|
|
82
|
-
"@rigour-labs/brain-
|
|
78
|
+
"@rigour-labs/brain-darwin-arm64": "6.12.2-rc.4",
|
|
79
|
+
"@rigour-labs/brain-linux-x64": "6.12.2-rc.4",
|
|
80
|
+
"@rigour-labs/brain-linux-arm64": "6.12.2-rc.4",
|
|
81
|
+
"@rigour-labs/brain-win-x64": "6.12.2-rc.4",
|
|
82
|
+
"@rigour-labs/brain-darwin-x64": "6.12.2-rc.4"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/fs-extra": "^11.0.4",
|