@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.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/dist/src/analysis.js +30 -6
- package/dist/src/augmentations.js +105 -12
- package/dist/src/build-scripts.js +437 -0
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/convention-rows.js +62 -2
- package/dist/src/conventions.js +15 -0
- package/dist/src/dependencies.js +89 -0
- package/dist/src/entry-points.js +9 -18
- package/dist/src/findings/completion.js +122 -49
- package/dist/src/findings/intra-function.js +4 -2
- package/dist/src/findings/reads-and-writes.js +7 -6
- package/dist/src/findings/unused-declarations.js +13 -5
- package/dist/src/findings/visibility-narrowing.js +8 -3
- package/dist/src/graph.js +5 -3
- package/dist/src/intra-function-parts.js +45 -17
- package/dist/src/kinds.js +8 -8
- package/dist/src/manifest.js +18 -0
- package/dist/src/matrix.js +76 -4
- package/dist/src/references.js +34 -3
- package/dist/src/report.js +23 -4
- package/dist/src/reporters/sarif.js +12 -8
- package/dist/src/roots.js +138 -11
- package/dist/src/serialization-contract.js +258 -8
- package/dist/src/suppress.js +1 -0
- package/dist/src/sweep.js +7 -9
- package/dist/src/test-globals.js +234 -0
- package/dist/src/test-support.js +8 -11
- package/dist/src/type-errors.js +11 -3
- package/dist/src/unseen-loads.js +107 -0
- package/dist/src/verbs/analyze.js +24 -2
- package/dist/src/yaml-document.js +355 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +48 -8
- package/src/augmentations.ts +134 -16
- package/src/build-scripts.ts +564 -0
- package/src/conformance-record.ts +1 -1
- package/src/convention-rows.ts +72 -2
- package/src/conventions.ts +16 -0
- package/src/dependencies.ts +96 -0
- package/src/entry-points.ts +10 -25
- package/src/file-facts.ts +3 -0
- package/src/finding.ts +5 -0
- package/src/findings/completion.ts +153 -56
- package/src/findings/intra-function.ts +4 -2
- package/src/findings/reads-and-writes.ts +7 -6
- package/src/findings/unused-declarations.ts +17 -5
- package/src/findings/visibility-narrowing.ts +9 -3
- package/src/graph.ts +5 -3
- package/src/intra-function-parts.ts +55 -16
- package/src/kinds.ts +8 -8
- package/src/manifest.ts +21 -3
- package/src/matrix.ts +122 -4
- package/src/references.ts +38 -2
- package/src/report.ts +56 -16
- package/src/reporters/sarif.ts +16 -8
- package/src/roots.ts +162 -12
- package/src/serialization-contract.ts +309 -7
- package/src/suppress.ts +1 -0
- package/src/sweep.ts +21 -9
- package/src/test-globals.ts +275 -0
- package/src/test-support.ts +17 -13
- package/src/type-errors.ts +14 -3
- package/src/unseen-loads.ts +145 -0
- package/src/verbs/analyze.ts +28 -2
- package/src/yaml-document.ts +377 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
isClassStaticBlockDeclaration,
|
|
18
18
|
isFunctionDeclaration,
|
|
19
19
|
isFunctionExpression,
|
|
20
|
+
isFunctionLikeDeclaration,
|
|
20
21
|
isGetAccessorDeclaration,
|
|
21
22
|
isIdentifier,
|
|
22
23
|
isInterfaceDeclaration,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
isNumericLiteral,
|
|
26
27
|
isObjectLiteralExpression,
|
|
27
28
|
isParenthesizedExpression,
|
|
29
|
+
isForInStatement,
|
|
28
30
|
isPropertyAccessExpression,
|
|
29
31
|
isPropertyDeclaration,
|
|
30
32
|
isSatisfiesExpression,
|
|
@@ -307,9 +309,37 @@ function walkProject<Brand>(project: ProjectView<Brand>, held: Inventory): Walke
|
|
|
307
309
|
return { sites, parameters };
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
/** The calls of `Object` that read every own property of their argument by name. */
|
|
313
|
+
const ENUMERATIONS: ReadonlySet<string> = new Set([
|
|
314
|
+
"keys",
|
|
315
|
+
"entries",
|
|
316
|
+
"values",
|
|
317
|
+
"getOwnPropertyNames",
|
|
318
|
+
"assign",
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Whether a parameter type keeps no member name of the value it is given: `unknown`, `any`,
|
|
323
|
+
* or an object type of a string index signature and no property.
|
|
324
|
+
*/
|
|
325
|
+
function keepsNoName<Brand>(project: ProjectView<Brand>, type: Type): boolean {
|
|
326
|
+
if ((type.flags & ERASED) !== 0) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
(type.flags & TypeFlags.Object) === 0 ||
|
|
331
|
+
must(project.queries.propertiesOf(type)).length > 0
|
|
332
|
+
) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
return must(project.queries.indexInfosOf(type)).some(
|
|
336
|
+
(info) => (info.keyType.flags & TypeFlags.String) !== 0,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
310
340
|
/**
|
|
311
|
-
* The parameters that keep
|
|
312
|
-
*
|
|
341
|
+
* The parameters that keep no member name of the value they are given, as `keepsNoName`
|
|
342
|
+
* reads it, and a rest parameter whose elements keep none, each by its symbol.
|
|
313
343
|
*/
|
|
314
344
|
function erasedParameters<Brand>(
|
|
315
345
|
project: ProjectView<Brand>,
|
|
@@ -325,10 +355,10 @@ function erasedParameters<Brand>(
|
|
|
325
355
|
return false;
|
|
326
356
|
}
|
|
327
357
|
if (!parameter.rest) {
|
|
328
|
-
return (type
|
|
358
|
+
return keepsNoName(project, type);
|
|
329
359
|
}
|
|
330
360
|
const [element] = must(project.queries.typeArguments(type));
|
|
331
|
-
return element !== undefined && (element
|
|
361
|
+
return element !== undefined && keepsNoName(project, element);
|
|
332
362
|
});
|
|
333
363
|
const bySymbol = new Map<number, Parameter>();
|
|
334
364
|
project
|
|
@@ -343,6 +373,124 @@ function erasedParameters<Brand>(
|
|
|
343
373
|
return bySymbol;
|
|
344
374
|
}
|
|
345
375
|
|
|
376
|
+
/** Whether one node is a function, whose body runs only where something calls it. */
|
|
377
|
+
function isFunctionNode(node: Node): boolean {
|
|
378
|
+
return (
|
|
379
|
+
isFunctionDeclaration(node) ||
|
|
380
|
+
isFunctionExpression(node) ||
|
|
381
|
+
isArrowFunction(node) ||
|
|
382
|
+
isMethodDeclaration(node) ||
|
|
383
|
+
isGetAccessorDeclaration(node) ||
|
|
384
|
+
isSetAccessorDeclaration(node)
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** The name a function nested in another is called by: its own, or the variable it initializes. */
|
|
389
|
+
function nestedName(fn: Node): string | undefined {
|
|
390
|
+
if (isFunctionDeclaration(fn)) {
|
|
391
|
+
return fn.name?.text;
|
|
392
|
+
}
|
|
393
|
+
let at = fn.parent;
|
|
394
|
+
while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
|
|
395
|
+
at = at.parent;
|
|
396
|
+
}
|
|
397
|
+
return isVariableDeclaration(at) && isIdentifier(at.name) ? at.name.text : undefined;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** The names one function's body writes as identifiers, other than the names its declarations bind. */
|
|
401
|
+
function namesWritten(fn: Node): ReadonlySet<string> {
|
|
402
|
+
const found = new Set<string>();
|
|
403
|
+
const visit = (node: Node): void => {
|
|
404
|
+
const declares =
|
|
405
|
+
(isFunctionDeclaration(node.parent) || isVariableDeclaration(node.parent)) &&
|
|
406
|
+
node.parent.name === node;
|
|
407
|
+
if (isIdentifier(node) && !declares) {
|
|
408
|
+
found.add(node.text);
|
|
409
|
+
}
|
|
410
|
+
node.forEachChild(visit);
|
|
411
|
+
};
|
|
412
|
+
fn.forEachChild(visit);
|
|
413
|
+
return found;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The erased parameters their own function enumerates: one an `Object.keys`, `entries`,
|
|
418
|
+
* `values`, `getOwnPropertyNames` or `assign` call takes, `Object` the standard library's,
|
|
419
|
+
* or a `for…in` statement walks, which reads every property of the value by its name. A
|
|
420
|
+
* nested function is read as the function's own only where its name is written in it, or
|
|
421
|
+
* it has none, a callback or a function called where it is written.
|
|
422
|
+
*/
|
|
423
|
+
function enumeratedParameters<Brand>(
|
|
424
|
+
project: ProjectView<Brand>,
|
|
425
|
+
erased: ReadonlyMap<number, Parameter>,
|
|
426
|
+
): readonly Parameter[] {
|
|
427
|
+
const asked: {
|
|
428
|
+
readonly node: Node;
|
|
429
|
+
readonly parameter: Parameter;
|
|
430
|
+
readonly object: Node | undefined;
|
|
431
|
+
}[] = [];
|
|
432
|
+
for (const parameter of erased.values()) {
|
|
433
|
+
const fn = parameter.name.parent.parent;
|
|
434
|
+
const spelled = parameter.name.getText();
|
|
435
|
+
const written = namesWritten(fn);
|
|
436
|
+
const visit = (node: Node): void => {
|
|
437
|
+
if (isFunctionNode(node)) {
|
|
438
|
+
const name = nestedName(node);
|
|
439
|
+
if (name !== undefined && !written.has(name)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const read: Expression[] = [];
|
|
444
|
+
let object: Node | undefined;
|
|
445
|
+
if (isCallExpression(node)) {
|
|
446
|
+
const callee = node.expression;
|
|
447
|
+
if (
|
|
448
|
+
isPropertyAccessExpression(callee) &&
|
|
449
|
+
isIdentifier(callee.expression) &&
|
|
450
|
+
callee.expression.text === "Object" &&
|
|
451
|
+
ENUMERATIONS.has(callee.name.text)
|
|
452
|
+
) {
|
|
453
|
+
object = callee.expression;
|
|
454
|
+
// `Object.assign` reads its sources, every argument after the target.
|
|
455
|
+
read.push(...node.arguments.slice(...(callee.name.text === "assign" ? [1] : [0, 1])));
|
|
456
|
+
}
|
|
457
|
+
} else if (isForInStatement(node)) {
|
|
458
|
+
read.push(node.expression);
|
|
459
|
+
}
|
|
460
|
+
for (const value of read.map(evaluated)) {
|
|
461
|
+
if (isIdentifier(value) && value.text === spelled) {
|
|
462
|
+
asked.push({ node: value, parameter, object });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
node.forEachChild(visit);
|
|
466
|
+
};
|
|
467
|
+
fn.forEachChild(visit);
|
|
468
|
+
}
|
|
469
|
+
const symbols = project.symbolsAt(asked.map((one) => project.handle(one.node))).map(must);
|
|
470
|
+
const objects = asked.flatMap((one) => (one.object === undefined ? [] : [one.object]));
|
|
471
|
+
const own = project.ownPaths();
|
|
472
|
+
const standard = new Set(
|
|
473
|
+
project
|
|
474
|
+
.symbolsAt(objects.map((node) => project.handle(node)))
|
|
475
|
+
.map(must)
|
|
476
|
+
.flatMap((symbol, at) =>
|
|
477
|
+
symbol?.declarations.every((handle) => !own.has(handle.path)) === true ? [objects[at]] : [],
|
|
478
|
+
),
|
|
479
|
+
);
|
|
480
|
+
return [
|
|
481
|
+
...new Set(
|
|
482
|
+
asked.flatMap((one, at) => {
|
|
483
|
+
const symbol = symbols[at];
|
|
484
|
+
return symbol !== undefined &&
|
|
485
|
+
erased.get(symbol.id) === one.parameter &&
|
|
486
|
+
(one.object === undefined || standard.has(one.object))
|
|
487
|
+
? [one.parameter]
|
|
488
|
+
: [];
|
|
489
|
+
}),
|
|
490
|
+
),
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
|
|
346
494
|
/** Each place a call's argument passes one of the erased parameters it can see. */
|
|
347
495
|
function handedOn<Brand>(
|
|
348
496
|
project: ProjectView<Brand>,
|
|
@@ -452,8 +600,8 @@ interface Destinations {
|
|
|
452
600
|
* callee is a serializer an entry names; at the arguments its resolved signature types
|
|
453
601
|
* `unknown` or `any` where its callee is declared outside the analysis or only ambiently
|
|
454
602
|
* with no implementation in it; and, where its callee is a function of the project, at each
|
|
455
|
-
* parameter the function hands on to a destination, holding back the widest
|
|
456
|
-
*
|
|
603
|
+
* parameter the function enumerates or hands on to a destination, holding back the widest
|
|
604
|
+
* of what those hold back, settled by widening until no parameter moves.
|
|
457
605
|
*/
|
|
458
606
|
function destinations<Brand>(
|
|
459
607
|
input: DetectorInput<Brand>,
|
|
@@ -674,6 +822,9 @@ function destinations<Brand>(
|
|
|
674
822
|
return undefined;
|
|
675
823
|
};
|
|
676
824
|
|
|
825
|
+
for (const parameter of enumeratedParameters(project, erased)) {
|
|
826
|
+
forwards.set(keyOf(parameter), "properties");
|
|
827
|
+
}
|
|
677
828
|
const forwarded = handedOn(project, walked.sites, erased, invocation);
|
|
678
829
|
for (let moved = true; moved;) {
|
|
679
830
|
moved = false;
|
|
@@ -776,6 +927,19 @@ function flowsOf<Brand>(
|
|
|
776
927
|
},
|
|
777
928
|
];
|
|
778
929
|
});
|
|
930
|
+
const generic = typeParameterFlows(
|
|
931
|
+
project,
|
|
932
|
+
reach,
|
|
933
|
+
sites,
|
|
934
|
+
arguments_.flatMap((one) => {
|
|
935
|
+
const type = typeOf.get(one.value);
|
|
936
|
+
const parameters = type === undefined ? [] : typeParametersOf(project, type, 0);
|
|
937
|
+
const destination = parameters.length === 0 ? undefined : to.at(one.site.call, one.argument);
|
|
938
|
+
return destination === undefined
|
|
939
|
+
? []
|
|
940
|
+
: parameters.map((parameter) => ({ parameter, to: destination }));
|
|
941
|
+
}),
|
|
942
|
+
);
|
|
779
943
|
const left = stores.outside.flatMap((store) => {
|
|
780
944
|
const reached = reach.reachedFrom(store.type);
|
|
781
945
|
return reached.classes.size === 0 && reached.members.length === 0
|
|
@@ -790,7 +954,145 @@ function flowsOf<Brand>(
|
|
|
790
954
|
},
|
|
791
955
|
];
|
|
792
956
|
});
|
|
793
|
-
return [...passed, ...left];
|
|
957
|
+
return [...passed, ...generic, ...left];
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** The type parameters one type names, through constituents and type arguments. */
|
|
961
|
+
function typeParametersOf<Brand>(project: ProjectView<Brand>, type: Type, depth: number): Type[] {
|
|
962
|
+
if (type.isTypeParameter()) {
|
|
963
|
+
return [type];
|
|
964
|
+
}
|
|
965
|
+
if (depth >= MENTION_DEPTH) {
|
|
966
|
+
return [];
|
|
967
|
+
}
|
|
968
|
+
const { queries } = project;
|
|
969
|
+
return [
|
|
970
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
971
|
+
...must(queries.typeArguments(type)),
|
|
972
|
+
].flatMap((one) => typeParametersOf(project, one, depth + 1));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* The types an instantiated type puts where a declared type names `parameter`: matched
|
|
977
|
+
* through type arguments, and in a union every constituent the declared union does not
|
|
978
|
+
* itself name.
|
|
979
|
+
*/
|
|
980
|
+
function bound<Brand>(
|
|
981
|
+
project: ProjectView<Brand>,
|
|
982
|
+
declared: Type,
|
|
983
|
+
instantiated: Type,
|
|
984
|
+
parameter: Type,
|
|
985
|
+
depth: number,
|
|
986
|
+
): Type[] {
|
|
987
|
+
if (declared.id === parameter.id) {
|
|
988
|
+
return [instantiated];
|
|
989
|
+
}
|
|
990
|
+
if (depth >= MENTION_DEPTH) {
|
|
991
|
+
return [];
|
|
992
|
+
}
|
|
993
|
+
const { queries } = project;
|
|
994
|
+
if (declared.isUnionType()) {
|
|
995
|
+
const parts = must(queries.constituents(declared));
|
|
996
|
+
if (parts.some((part) => part.id === parameter.id)) {
|
|
997
|
+
const kept = new Set(parts.map((part) => part.id));
|
|
998
|
+
const given = instantiated.isUnionType()
|
|
999
|
+
? must(queries.constituents(instantiated))
|
|
1000
|
+
: [instantiated];
|
|
1001
|
+
return given.filter((part) => !kept.has(part.id));
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const declaredArguments = must(queries.typeArguments(declared));
|
|
1005
|
+
const givenArguments = must(queries.typeArguments(instantiated));
|
|
1006
|
+
return declaredArguments.length !== givenArguments.length
|
|
1007
|
+
? []
|
|
1008
|
+
: declaredArguments.flatMap((one, at) => {
|
|
1009
|
+
const given = givenArguments[at];
|
|
1010
|
+
return given === undefined ? [] : bound(project, one, given, parameter, depth + 1);
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* The flows a type parameter of a function of the project carries where a value its type
|
|
1016
|
+
* names reaches a destination: each call of the function passes its type argument there,
|
|
1017
|
+
* written or inferred from the arguments, and a type argument that is a type parameter
|
|
1018
|
+
* of the calling function is followed on to that function's calls, to a fixpoint.
|
|
1019
|
+
*/
|
|
1020
|
+
function typeParameterFlows<Brand>(
|
|
1021
|
+
project: ProjectView<Brand>,
|
|
1022
|
+
reach: ReturnType<typeof valueReach>,
|
|
1023
|
+
sites: readonly Site[],
|
|
1024
|
+
pending: { readonly parameter: Type; readonly to: Destination }[],
|
|
1025
|
+
): readonly Flow[] {
|
|
1026
|
+
const { queries } = project;
|
|
1027
|
+
const byName = new Map<string, Site[]>();
|
|
1028
|
+
for (const site of sites) {
|
|
1029
|
+
const name = calleeName(site.call.expression)?.getText();
|
|
1030
|
+
if (name !== undefined) {
|
|
1031
|
+
byName.set(name, [...(byName.get(name) ?? []), site]);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
const found: Flow[] = [];
|
|
1035
|
+
const seen = new Set<number>();
|
|
1036
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
1037
|
+
const { parameter, to } = next;
|
|
1038
|
+
if (seen.has(parameter.id)) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
seen.add(parameter.id);
|
|
1042
|
+
const symbol = must(queries.symbolOfType(parameter));
|
|
1043
|
+
const [handle] = symbol?.declarations ?? [];
|
|
1044
|
+
const declaration = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
1045
|
+
const fn = declaration?.parent;
|
|
1046
|
+
const index =
|
|
1047
|
+
fn === undefined || !isFunctionLikeDeclaration(fn)
|
|
1048
|
+
? -1
|
|
1049
|
+
: (fn.typeParameters ?? []).findIndex((one) => one === declaration);
|
|
1050
|
+
const name =
|
|
1051
|
+
fn === undefined
|
|
1052
|
+
? undefined
|
|
1053
|
+
: isMethodDeclaration(fn) && isIdentifier(fn.name)
|
|
1054
|
+
? fn.name.text
|
|
1055
|
+
: nestedName(fn);
|
|
1056
|
+
if (symbol === undefined || fn === undefined || index < 0 || name === undefined) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
for (const site of byName.get(name) ?? []) {
|
|
1060
|
+
const resolved = must(queries.resolvedSignature(site.call));
|
|
1061
|
+
const declared = resolved === undefined ? undefined : must(queries.genericOf(resolved));
|
|
1062
|
+
const called =
|
|
1063
|
+
declared?.declaration === undefined
|
|
1064
|
+
? undefined
|
|
1065
|
+
: project.declarationAt(declared.declaration)?.node;
|
|
1066
|
+
if (resolved === undefined || declared === undefined || called !== fn) {
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const written = site.call.typeArguments?.[index];
|
|
1070
|
+
const given =
|
|
1071
|
+
written === undefined
|
|
1072
|
+
? site.call.arguments.flatMap((_argument, at) => {
|
|
1073
|
+
const from = must(queries.parameterType(declared, at));
|
|
1074
|
+
const into = must(queries.parameterType(resolved, at));
|
|
1075
|
+
return from === undefined || into === undefined
|
|
1076
|
+
? []
|
|
1077
|
+
: bound(project, from, into, parameter, 0);
|
|
1078
|
+
})
|
|
1079
|
+
: typesAt(project, [written]).filter((type) => type !== undefined);
|
|
1080
|
+
for (const type of given) {
|
|
1081
|
+
const reached = reach.reachedFrom(type);
|
|
1082
|
+
if (reached.classes.size > 0 || reached.members.length > 0) {
|
|
1083
|
+
found.push({
|
|
1084
|
+
site: site.call,
|
|
1085
|
+
classes: reached.classes,
|
|
1086
|
+
members: reached.members,
|
|
1087
|
+
to,
|
|
1088
|
+
detail: `passed to ${to.callee} through the type parameter ${symbol.name}`,
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
pending.push(...typeParametersOf(project, type, 0).map((one) => ({ parameter: one, to })));
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return found;
|
|
794
1096
|
}
|
|
795
1097
|
|
|
796
1098
|
/**
|
package/src/suppress.ts
CHANGED
|
@@ -115,6 +115,7 @@ export const GO_REF_EXPRESSIONS: readonly string[] = [
|
|
|
115
115
|
`^go://${GO_PATH}#${GO_IDENT}$`,
|
|
116
116
|
`^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})+$`,
|
|
117
117
|
`^go://${GO_PATH}#${GO_IDENT}(?:\\.${GO_IDENT})?\\[${GO_IDENT}\\]$`,
|
|
118
|
+
`^go://${GO_PATH}#(?:${GO_IDENT}(?:\\.${GO_IDENT})?)?\\(${GO_IDENT}\\)$`,
|
|
118
119
|
`^go://${GO_PATH}#${GO_FILE}:file$`,
|
|
119
120
|
`^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:require$`,
|
|
120
121
|
`^go://${GO_PATH}#${GO_PATH}(?:@${GO_VERSION})?:replace$`,
|
package/src/sweep.ts
CHANGED
|
@@ -195,10 +195,14 @@ export function runnerCalls(symbol: InventorySymbol): boolean {
|
|
|
195
195
|
* That run's roots are the graph's own and, of each test file, its top level and every
|
|
196
196
|
* function it exports, which a test runner may call, and every export of a manual mock,
|
|
197
197
|
* which the runner reads; any other export of a test file, and test-support code, which
|
|
198
|
-
* runs where a test imports it, are judged as any file's are.
|
|
199
|
-
* `
|
|
198
|
+
* runs where a test imports it, are judged as any file's are. `held` seeds it as a mark
|
|
199
|
+
* seeds a sweep; with `namespaces` false a file read reaches none of the file's exports.
|
|
200
200
|
*/
|
|
201
|
-
export function reachedWithTheTests(
|
|
201
|
+
export function reachedWithTheTests(
|
|
202
|
+
graph: Graph,
|
|
203
|
+
held: readonly string[],
|
|
204
|
+
namespaces = true,
|
|
205
|
+
): readonly boolean[] {
|
|
202
206
|
const tests = graph.symbols.flatMap((symbol, at) => {
|
|
203
207
|
const exported = symbol.exported && graph.symbols[graph.parent[at] ?? OUTSIDE]?.kind === "file";
|
|
204
208
|
const called = exported && runnerCalls(symbol);
|
|
@@ -208,9 +212,12 @@ export function reachedWithTheTests(graph: Graph, held: readonly string[]): read
|
|
|
208
212
|
? [{ at, kind: "test-runner" as const }]
|
|
209
213
|
: [];
|
|
210
214
|
});
|
|
211
|
-
return reachable(
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
return reachable(
|
|
216
|
+
{ ...graph, rooted: [...graph.rooted, ...tests] },
|
|
217
|
+
flagged(graph, held),
|
|
218
|
+
{ production: false },
|
|
219
|
+
namespaces,
|
|
220
|
+
);
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
/**
|
|
@@ -240,7 +247,12 @@ function flagged(graph: Graph, ids: readonly string[]): boolean[] {
|
|
|
240
247
|
* included, while a root reaches through the references the mode counts. The held seed
|
|
241
248
|
* runs first, so what it reaches is expanded under the wider rule.
|
|
242
249
|
*/
|
|
243
|
-
function reachable(
|
|
250
|
+
function reachable(
|
|
251
|
+
graph: Graph,
|
|
252
|
+
held: readonly boolean[],
|
|
253
|
+
mode: Mode,
|
|
254
|
+
namespaces = true,
|
|
255
|
+
): readonly boolean[] {
|
|
244
256
|
const reached = graph.symbols.map(() => false);
|
|
245
257
|
const expanded = graph.symbols.map(() => false);
|
|
246
258
|
const queue: number[] = [];
|
|
@@ -269,7 +281,7 @@ function reachable(graph: Graph, held: readonly boolean[], mode: Mode): readonly
|
|
|
269
281
|
if (!holding && mode.production && edge.test) {
|
|
270
282
|
continue;
|
|
271
283
|
}
|
|
272
|
-
enter(edge.to, !edge.evaluation);
|
|
284
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
};
|
|
@@ -285,7 +297,7 @@ function reachable(graph: Graph, held: readonly boolean[], mode: Mode): readonly
|
|
|
285
297
|
}
|
|
286
298
|
// A consumer's reference reaches what it names as one of the target's own does.
|
|
287
299
|
for (const edge of callsOf(graph, mode)) {
|
|
288
|
-
enter(edge.to, !edge.evaluation);
|
|
300
|
+
enter(edge.to, namespaces && !edge.evaluation);
|
|
289
301
|
}
|
|
290
302
|
walk(false);
|
|
291
303
|
return reached;
|