@cplieger/deadset-ts 6.1.1-dev.1 → 7.0.0-dev.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/dist/src/analysis.js +45 -32
- package/dist/src/component-layer.js +1 -1
- package/dist/src/configuration-files.js +227 -27
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/contextual-uses.js +68 -10
- package/dist/src/convention-rows.js +48 -0
- package/dist/src/conventions.js +80 -0
- package/dist/src/dependencies.js +121 -46
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +41 -11
- package/dist/src/findings/dependencies-and-module-machinery.js +6 -26
- package/dist/src/findings/emitters.js +0 -4
- package/dist/src/findings/self-check.js +14 -10
- package/dist/src/html-entries.js +29 -14
- package/dist/src/indexed-access.js +377 -81
- package/dist/src/intra-function-parts.js +57 -11
- package/dist/src/inventory.js +3 -12
- package/dist/src/kinds.js +6 -6
- package/dist/src/references.js +62 -23
- package/dist/src/report.js +3 -0
- package/dist/src/roots.js +96 -7
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/session.js +6 -2
- package/dist/src/source-files.js +1 -0
- package/dist/src/suppress-inline.js +1 -1
- package/dist/src/suppress.js +15 -16
- package/dist/src/sweep.js +29 -17
- package/dist/src/symbol-keys.js +114 -0
- package/dist/src/test-joins.js +101 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +21 -15
- package/dist/src/workspace-programs.js +1 -4
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +55 -47
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +1 -1
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +246 -28
- package/src/conformance-record.ts +2 -2
- package/src/contextual-uses.ts +99 -13
- package/src/convention-rows.ts +76 -0
- package/src/conventions.ts +102 -0
- package/src/dependencies.ts +148 -56
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/entry-points.ts +1 -1
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +1 -1
- package/src/finding.ts +5 -1
- package/src/findings/completion.ts +53 -11
- package/src/findings/dependencies-and-module-machinery.ts +9 -28
- package/src/findings/emitters.ts +3 -8
- package/src/findings/self-check.ts +17 -14
- package/src/findings-pass.ts +2 -2
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +34 -16
- package/src/indexed-access.ts +476 -102
- package/src/intra-function-parts.ts +84 -12
- package/src/inventory.ts +5 -33
- package/src/json-document.ts +1 -1
- package/src/kinds.ts +7 -6
- package/src/manifest.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/references.ts +93 -33
- package/src/report.ts +8 -4
- package/src/roots.ts +129 -9
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/session.ts +7 -3
- package/src/source-files.ts +3 -0
- package/src/stores.ts +1 -1
- package/src/suppress-inline.ts +3 -3
- package/src/suppress.ts +21 -24
- package/src/sweep.ts +29 -17
- package/src/symbol-keys.ts +172 -0
- package/src/template-field.ts +1 -1
- package/src/test-joins.ts +120 -0
- package/src/value-reach.ts +1 -1
- package/src/verbs/verb.ts +1 -4
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +23 -16
- package/src/workspace-programs.ts +3 -10
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
package/dist/src/run.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConfigError, REPOSITORY_DOCUMENT } from "./config.js";
|
|
2
2
|
import { DiscoveryError, renderDiagnostic } from "./discover.js";
|
|
3
|
-
import { EMITTERS } from "./findings/emitters.js";
|
|
4
3
|
import { joinPath, resolvePath } from "./paths.js";
|
|
5
4
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
6
5
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
@@ -201,7 +200,6 @@ function invoke(verb, own, args, streams, openClient) {
|
|
|
201
200
|
});
|
|
202
201
|
return opened;
|
|
203
202
|
},
|
|
204
|
-
emitters: EMITTERS,
|
|
205
203
|
});
|
|
206
204
|
}
|
|
207
205
|
catch (error) {
|
package/dist/src/schema.js
CHANGED
|
@@ -216,7 +216,7 @@ export function malformed(label, path, detail) {
|
|
|
216
216
|
* A refusal of a key the closed key list does not declare, naming the nearest key
|
|
217
217
|
* it does.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
function unimplementedKey(label, path) {
|
|
220
220
|
const nearest = nearestKey(path);
|
|
221
221
|
let hint = "";
|
|
222
222
|
if (nearest === path) {
|
package/dist/src/scope.js
CHANGED
|
@@ -2,8 +2,8 @@ import { ConfigError } from "./config.js";
|
|
|
2
2
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
3
3
|
import { checkDocument } from "./schema.js";
|
|
4
4
|
/** The roles a scope document declares for a module. */
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const ROLE_TARGET = "target";
|
|
6
|
+
const ROLE_CONSUMER = "consumer";
|
|
7
7
|
/**
|
|
8
8
|
* A scope document above this length is refused rather than decoded. It names one
|
|
9
9
|
* target and its consumers, so a larger file is not one.
|
package/dist/src/session.js
CHANGED
|
@@ -129,7 +129,12 @@ function viewOf(project, configFile, engine, log) {
|
|
|
129
129
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
130
130
|
return undefined;
|
|
131
131
|
}
|
|
132
|
-
|
|
132
|
+
// An alias the checker synthesizes declares nothing, the default a module that assigns
|
|
133
|
+
// its export gets under esModuleInterop, and the immediate step fails on it while the
|
|
134
|
+
// whole resolution answers.
|
|
135
|
+
const held = symbol.declarations.length === 0
|
|
136
|
+
? queries.aliased(symbol)
|
|
137
|
+
: queries.immediateAliased(symbol);
|
|
133
138
|
// An alias that resolves to nothing answers with the checker's own unknown
|
|
134
139
|
// symbol, which declares nothing; a symbol that is no alias answers with
|
|
135
140
|
// nothing at all.
|
|
@@ -185,7 +190,6 @@ export function ownedAs(project, own) {
|
|
|
185
190
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
186
191
|
export function diagnosticsOf(project) {
|
|
187
192
|
return {
|
|
188
|
-
configFile: project.configFile,
|
|
189
193
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
190
194
|
semantic: project.program.getSemanticDiagnostics(),
|
|
191
195
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
package/dist/src/source-files.js
CHANGED
|
@@ -48,6 +48,7 @@ export function sourceFilesOf(project, targetRoot) {
|
|
|
48
48
|
return {
|
|
49
49
|
byPath,
|
|
50
50
|
byName,
|
|
51
|
+
outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
|
|
51
52
|
named: (path) => {
|
|
52
53
|
if (!path.includes(SUBPATH_WILDCARD)) {
|
|
53
54
|
for (const candidate of candidates(path)) {
|
|
@@ -95,7 +95,7 @@ export function tokenStart(text, from) {
|
|
|
95
95
|
* syntax tree holds are skipped, so a `//` inside a string, a template or a regular
|
|
96
96
|
* expression starts nothing.
|
|
97
97
|
*/
|
|
98
|
-
|
|
98
|
+
function lineComments(file) {
|
|
99
99
|
const text = file.text;
|
|
100
100
|
const spans = [];
|
|
101
101
|
const visit = (node) => {
|
package/dist/src/suppress.js
CHANGED
|
@@ -14,8 +14,6 @@ export const SUPPRESSION_WITHOUT_REASON = "DS1701";
|
|
|
14
14
|
export const UNSCOPED_ENTRY = "DS1702";
|
|
15
15
|
/** The code a suppression that withholds nothing is reported under. */
|
|
16
16
|
export const STALE_SUPPRESSION = "DS1703";
|
|
17
|
-
/** A target that carries no suppression. */
|
|
18
|
-
export const NO_SUPPRESSIONS = { records: [], refusals: [] };
|
|
19
17
|
/**
|
|
20
18
|
* A directive, an entry or a document the grammar refuses before any finding exists: an
|
|
21
19
|
* instruction the analysis cannot carry out. It is a malformed input, so the command line
|
|
@@ -71,22 +69,27 @@ export function blankReason(reason) {
|
|
|
71
69
|
return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
|
|
72
70
|
}
|
|
73
71
|
/**
|
|
74
|
-
* The subject kinds that are a row of a document rather than a declaration of the program
|
|
75
|
-
* A record binds to a declaration, so nothing
|
|
72
|
+
* The subject kinds that are a row of a document rather than a declaration of the program,
|
|
73
|
+
* each with the codes that report one. A record binds to a declaration, so nothing
|
|
74
|
+
* withholds a finding about one.
|
|
76
75
|
*/
|
|
77
|
-
const ROW_SUBJECTS = new
|
|
78
|
-
"file",
|
|
79
|
-
"dependency",
|
|
80
|
-
"module-directive",
|
|
81
|
-
"root",
|
|
82
|
-
"configured-declaration",
|
|
83
|
-
"suppression",
|
|
84
|
-
"edge",
|
|
76
|
+
const ROW_SUBJECTS = new Map([
|
|
77
|
+
["file", ["DS1501", "DS1502"]],
|
|
78
|
+
["dependency", ["DS1601"]],
|
|
79
|
+
["module-directive", ["DS1605"]],
|
|
80
|
+
["root", ["DS1704"]],
|
|
81
|
+
["configured-declaration", ["DS1706"]],
|
|
82
|
+
["suppression", ["DS1701", "DS1702", "DS1703"]],
|
|
83
|
+
["edge", ["DS1705"]],
|
|
85
84
|
]);
|
|
86
85
|
/** Whether a finding's subject is a row of a document, which no record can withhold. */
|
|
87
86
|
export function isRowSubject(kind) {
|
|
88
87
|
return ROW_SUBJECTS.has(kind);
|
|
89
88
|
}
|
|
89
|
+
/** Whether a code reports a row of a document, so no record naming it can match. */
|
|
90
|
+
export function isRowCode(code) {
|
|
91
|
+
return [...ROW_SUBJECTS.values()].some((codes) => codes.includes(code));
|
|
92
|
+
}
|
|
90
93
|
/**
|
|
91
94
|
* The subject kinds that are a part of a declaration rather than a declaration: a part
|
|
92
95
|
* has no reference of its own, so a finding about one names the declaration that holds
|
|
@@ -143,15 +146,12 @@ export function pairs(record, finding) {
|
|
|
143
146
|
export function ledgerOf(records, would, dials) {
|
|
144
147
|
const taken = new Set();
|
|
145
148
|
const verdicts = [];
|
|
146
|
-
const claims = [];
|
|
147
149
|
for (const record of records) {
|
|
148
150
|
if (record.bound !== "" && dials.withholdsCode(record.code, record.bound)) {
|
|
149
151
|
verdicts.push("dormant");
|
|
150
|
-
claims.push(undefined);
|
|
151
152
|
continue;
|
|
152
153
|
}
|
|
153
154
|
const claimed = would.find((finding) => !taken.has(claimKey(finding)) && pairs(record, finding));
|
|
154
|
-
claims.push(claimed);
|
|
155
155
|
if (claimed === undefined) {
|
|
156
156
|
verdicts.push("stale");
|
|
157
157
|
continue;
|
|
@@ -161,7 +161,6 @@ export function ledgerOf(records, would, dials) {
|
|
|
161
161
|
}
|
|
162
162
|
return {
|
|
163
163
|
verdicts,
|
|
164
|
-
claims,
|
|
165
164
|
withheld: (finding) => taken.has(claimKey(finding)) && !ROW_SUBJECTS.has(finding.symbol.kind),
|
|
166
165
|
};
|
|
167
166
|
}
|
package/dist/src/sweep.js
CHANGED
|
@@ -163,30 +163,42 @@ function reachable(graph, held, mode) {
|
|
|
163
163
|
/**
|
|
164
164
|
* Admits every test declaration whose set of referenced production declarations is
|
|
165
165
|
* non-empty and wholly dead, and returns which it admitted. The set is read from every
|
|
166
|
-
* reference the test makes,
|
|
167
|
-
*
|
|
168
|
-
*
|
|
166
|
+
* reference the test makes, which a production sweep does not count. A test-support
|
|
167
|
+
* declaration that a test referencing a live target also references is a live target of
|
|
168
|
+
* every test referencing it. A marked or exempt test is never admitted.
|
|
169
169
|
*/
|
|
170
170
|
function admitTestsOfDeadCode(graph, dead, held) {
|
|
171
|
-
const
|
|
172
|
-
graph.symbols.
|
|
173
|
-
const testCode = graph.test[at] === true || graph.support[at] === true;
|
|
174
|
-
if (!testCode || graph.subject[at] !== true || held[at] === true) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
171
|
+
const testCode = (at) => graph.test[at] === true || graph.support[at] === true;
|
|
172
|
+
const judged = graph.symbols.map((_symbol, at) => {
|
|
177
173
|
let targets = 0;
|
|
178
174
|
let live = 0;
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
175
|
+
if (testCode(at) && graph.subject[at] === true && held[at] !== true) {
|
|
176
|
+
for (const edge of graph.out[at] ?? []) {
|
|
177
|
+
if (testCode(edge.to) || graph.subject[edge.to] !== true) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
targets += 1;
|
|
181
|
+
if (dead[edge.to] !== true) {
|
|
182
|
+
live += 1;
|
|
183
|
+
}
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
}
|
|
186
|
+
return { targets, live };
|
|
187
|
+
});
|
|
188
|
+
const shared = new Set();
|
|
189
|
+
judged.forEach(({ live }, at) => {
|
|
190
|
+
if (live > 0) {
|
|
191
|
+
for (const edge of graph.out[at] ?? []) {
|
|
192
|
+
if (graph.support[edge.to] === true) {
|
|
193
|
+
shared.add(edge.to);
|
|
194
|
+
}
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
|
-
|
|
197
|
+
});
|
|
198
|
+
const admitted = graph.symbols.map(() => false);
|
|
199
|
+
judged.forEach(({ targets, live }, at) => {
|
|
200
|
+
const sharing = (graph.out[at] ?? []).some((edge) => shared.has(edge.to) && edge.to !== at);
|
|
201
|
+
if (targets > 0 && live === 0 && !sharing) {
|
|
190
202
|
dead[at] = true;
|
|
191
203
|
admitted[at] = true;
|
|
192
204
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The members a unique symbol keys, used through element accesses. `value[key]`, where
|
|
3
|
+
* `key` is a constant of a `unique symbol` type, reads the member of the receiver's type
|
|
4
|
+
* whose computed name is that same constant, and a store through the access writes it.
|
|
5
|
+
* No name node resolves to such a member: the key's name resolves to the constant.
|
|
6
|
+
*/
|
|
7
|
+
import { isComputedPropertyName, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
|
|
8
|
+
import { isAnswered } from "./query.js";
|
|
9
|
+
/** The identifier an element access's argument names its key by, where it is a name. */
|
|
10
|
+
export function keyNameOf(argument) {
|
|
11
|
+
let at = argument;
|
|
12
|
+
while (isParenthesizedExpression(at)) {
|
|
13
|
+
at = at.expression;
|
|
14
|
+
}
|
|
15
|
+
if (isIdentifier(at)) {
|
|
16
|
+
return at;
|
|
17
|
+
}
|
|
18
|
+
return isPropertyAccessExpression(at) && isIdentifier(at.name) ? at.name : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The last identifier of each computed member name the inventory holds, `mark` for
|
|
22
|
+
* `[mark]` and `iterator` for `[Symbol.iterator]`: an access whose key is spelled by no
|
|
23
|
+
* such identifier selects no member a unique symbol keys, and is not asked about.
|
|
24
|
+
*/
|
|
25
|
+
export function computedKeyNames(symbols) {
|
|
26
|
+
const names = new Set();
|
|
27
|
+
for (const symbol of symbols) {
|
|
28
|
+
const key = /\[([^\]]+)\]$/u.exec(symbol.name)?.[1];
|
|
29
|
+
const last = key?.split(".").pop()?.trim();
|
|
30
|
+
if (last !== undefined && /^[\p{ID_Start}$_][\p{ID_Continue}$]*$/u.test(last)) {
|
|
31
|
+
names.add(last);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return names;
|
|
35
|
+
}
|
|
36
|
+
/** Whether a computed name's expression is literal text, which names a member by its text. */
|
|
37
|
+
function isLiteralKey(expression) {
|
|
38
|
+
return (isStringLiteral(expression) ||
|
|
39
|
+
isNumericLiteral(expression) ||
|
|
40
|
+
isNoSubstitutionTemplateLiteral(expression));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The members each keyed access selects. The receivers' types are asked in one batch,
|
|
44
|
+
* each object type's properties once, and the key of every property declared under a
|
|
45
|
+
* computed name in one batch more; a property is selected where its key and the
|
|
46
|
+
* access's key are one symbol.
|
|
47
|
+
*/
|
|
48
|
+
export function keyedUses(project, chains, resolved, properties, cap) {
|
|
49
|
+
if (resolved.length === 0) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const queries = project.queries;
|
|
53
|
+
const ownPaths = project.ownPaths();
|
|
54
|
+
const types = queries.typesAt(resolved.map((one) => one.access.access.expression), cap);
|
|
55
|
+
const parts = new Map();
|
|
56
|
+
const partsOf = (type) => {
|
|
57
|
+
const known = parts.get(type.id);
|
|
58
|
+
if (known !== undefined) {
|
|
59
|
+
return known;
|
|
60
|
+
}
|
|
61
|
+
let found = [];
|
|
62
|
+
if (type.isUnionType() || type.isIntersectionType()) {
|
|
63
|
+
const constituents = queries.constituents(type);
|
|
64
|
+
found = isAnswered(constituents) ? constituents.flatMap(partsOf) : [];
|
|
65
|
+
}
|
|
66
|
+
else if (type.isObjectType()) {
|
|
67
|
+
found = [type];
|
|
68
|
+
}
|
|
69
|
+
parts.set(type.id, found);
|
|
70
|
+
return found;
|
|
71
|
+
};
|
|
72
|
+
/** Each receiver's properties declared in the target under a computed name, with that name's expression. */
|
|
73
|
+
const candidates = new Map();
|
|
74
|
+
const keyNodes = [];
|
|
75
|
+
const candidatesOf = (type) => {
|
|
76
|
+
const known = candidates.get(type.id);
|
|
77
|
+
if (known !== undefined) {
|
|
78
|
+
return known;
|
|
79
|
+
}
|
|
80
|
+
const found = [];
|
|
81
|
+
const read = properties.of(type);
|
|
82
|
+
for (const property of isAnswered(read.properties) ? read.properties : []) {
|
|
83
|
+
const handle = property.declarations.find((one) => ownPaths.has(one.path));
|
|
84
|
+
const node = handle === undefined ? undefined : project.declarationAt(handle)?.node;
|
|
85
|
+
const name = node?.name;
|
|
86
|
+
if (name !== undefined && isComputedPropertyName(name) && !isLiteralKey(name.expression)) {
|
|
87
|
+
found.push({ property, key: name.expression });
|
|
88
|
+
keyNodes.push(name.expression);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
candidates.set(type.id, found);
|
|
92
|
+
return found;
|
|
93
|
+
};
|
|
94
|
+
const perAccess = resolved.map((_one, index) => {
|
|
95
|
+
const type = types[index];
|
|
96
|
+
return type === undefined || !isAnswered(type) ? [] : partsOf(type).flatMap(candidatesOf);
|
|
97
|
+
});
|
|
98
|
+
const keyOf = new Map();
|
|
99
|
+
const asked = [...new Set(keyNodes)];
|
|
100
|
+
(asked.length === 0
|
|
101
|
+
? []
|
|
102
|
+
: project.symbolsAt(asked.map((node) => project.handle(node)), cap)).forEach((symbol, index) => {
|
|
103
|
+
const node = asked[index];
|
|
104
|
+
if (node !== undefined && symbol !== undefined && isAnswered(symbol)) {
|
|
105
|
+
keyOf.set(node, symbol.id);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return resolved.flatMap((one, index) => {
|
|
109
|
+
const ids = (perAccess[index] ?? [])
|
|
110
|
+
.filter((candidate) => keyOf.get(candidate.key) === one.symbol.id)
|
|
111
|
+
.flatMap((candidate) => chains.declarationsOf(candidate.property));
|
|
112
|
+
return ids.length === 0 ? [] : [{ access: one.access, ids: [...new Set(ids)] }];
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The test files a derived matrix holds although no configuration's file list names them.
|
|
3
|
+
* The test runner compiles such a file with the options of the nearest compiler
|
|
4
|
+
* configuration at or above it, so the file joins that configuration's program as a file
|
|
5
|
+
* its list names, unless a `package.json` below that configuration's directory makes the
|
|
6
|
+
* file another package's, which its own test runner compiles.
|
|
7
|
+
*/
|
|
8
|
+
import { sourcePaths } from "./file-facts.js";
|
|
9
|
+
import { dirnamePath, joinPath, relativePath } from "./paths.js";
|
|
10
|
+
import { writtenPath } from "./workspace-programs.js";
|
|
11
|
+
const PREFERRED = "/tsconfig.json";
|
|
12
|
+
/** The configurations of one directory in the order a file joins them: `tsconfig.json` first. */
|
|
13
|
+
function byPreference(a, b) {
|
|
14
|
+
const first = a.endsWith(PREFERRED) ? 0 : 1;
|
|
15
|
+
const second = b.endsWith(PREFERRED) ? 0 : 1;
|
|
16
|
+
return first - second || (a < b ? -1 : a > b ? 1 : 0);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Per configuration of `configFiles`, the absolute paths of the test files that join it:
|
|
20
|
+
* each file below `root` a pattern of `patterns` names, that no configuration's file list
|
|
21
|
+
* holds, joins the configuration nearest at or above its directory when no `package.json`
|
|
22
|
+
* sits between them. A configuration the compiler cannot read lists nothing and is joined
|
|
23
|
+
* by nothing.
|
|
24
|
+
*/
|
|
25
|
+
export function joinedTestFiles(engine, host, root, configFiles, patterns) {
|
|
26
|
+
const listed = new Set();
|
|
27
|
+
const byDir = new Map();
|
|
28
|
+
for (const configFile of configFiles) {
|
|
29
|
+
let fileNames;
|
|
30
|
+
try {
|
|
31
|
+
fileNames = engine.parseConfigFile(configFile).fileNames;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
fileNames.forEach((name) => listed.add(name));
|
|
37
|
+
const dir = dirnamePath(configFile);
|
|
38
|
+
byDir.set(dir, [...(byDir.get(dir) ?? []), configFile].sort(byPreference));
|
|
39
|
+
}
|
|
40
|
+
const joins = new Map();
|
|
41
|
+
for (const path of sourcePaths(host, root)) {
|
|
42
|
+
const file = joinPath(root, path);
|
|
43
|
+
if (listed.has(file) || !patterns.some((pattern) => pattern.test(path))) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
for (let dir = dirnamePath(file);; dir = dirnamePath(dir)) {
|
|
47
|
+
const [nearest] = byDir.get(dir) ?? [];
|
|
48
|
+
if (nearest !== undefined) {
|
|
49
|
+
joins.set(nearest, [...(joins.get(nearest) ?? []), file]);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
if (host.kindOf(joinPath(dir, "package.json")) === "file" ||
|
|
53
|
+
dir === root ||
|
|
54
|
+
relativePath(root, dir) === undefined) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return joins;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* How the snapshot reads each configuration a test file joins: the program `programs`
|
|
63
|
+
* describes with the files added to its root files, or a configuration only the snapshot
|
|
64
|
+
* holds that extends the one `programs` writes, or the configuration itself, and lists
|
|
65
|
+
* every file the configuration lists and the joined ones.
|
|
66
|
+
*/
|
|
67
|
+
export function withJoinedTests(engine, host, programs, joins) {
|
|
68
|
+
const found = new Map(programs);
|
|
69
|
+
for (const [configFile, files] of joins) {
|
|
70
|
+
const program = programs.get(configFile);
|
|
71
|
+
if (program?.kind === "program") {
|
|
72
|
+
found.set(configFile, {
|
|
73
|
+
kind: "program",
|
|
74
|
+
params: {
|
|
75
|
+
...program.params,
|
|
76
|
+
rootFiles: [...(program.params.rootFiles ?? []), ...files],
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
let listed;
|
|
82
|
+
try {
|
|
83
|
+
listed =
|
|
84
|
+
program?.kind === "configuration"
|
|
85
|
+
? engine.parseConfigJson(JSON.parse(program.text), program.file).fileNames
|
|
86
|
+
: engine.parseConfigFile(configFile).fileNames;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const value = program?.kind === "configuration"
|
|
92
|
+
? { ...JSON.parse(program.text), files: [...listed, ...files] }
|
|
93
|
+
: { extends: configFile, files: [...listed, ...files] };
|
|
94
|
+
found.set(configFile, {
|
|
95
|
+
kind: "configuration",
|
|
96
|
+
file: program?.kind === "configuration" ? program.file : writtenPath(host, configFile),
|
|
97
|
+
text: `${JSON.stringify({ ...value, include: [] }, null, 2)}\n`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return found;
|
|
101
|
+
}
|
package/dist/src/version.js
CHANGED
|
@@ -50,12 +50,8 @@ export function runCommands(text) {
|
|
|
50
50
|
}
|
|
51
51
|
return found;
|
|
52
52
|
}
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
* `.github/workflows` name: a token equal to a file's path below the target root, with or
|
|
56
|
-
* without a leading `./`, whatever directory the step runs in.
|
|
57
|
-
*/
|
|
58
|
-
export function workflowEntries(host, targetRoot, files) {
|
|
53
|
+
/** Every token the `run:` steps of the workflow files in the target root's `.github/workflows` hold. */
|
|
54
|
+
export function workflowTokens(host, targetRoot) {
|
|
59
55
|
const dir = joinPath(resolvePath(host.workingDirectory(), targetRoot), WORKFLOWS);
|
|
60
56
|
const found = [];
|
|
61
57
|
for (const path of workflowFiles(host, dir)) {
|
|
@@ -67,15 +63,25 @@ export function workflowEntries(host, targetRoot, files) {
|
|
|
67
63
|
continue;
|
|
68
64
|
}
|
|
69
65
|
for (const command of runCommands(text)) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
found.push(...scriptTokens(command));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The own files the `run:` steps of every workflow file in the target root's
|
|
73
|
+
* `.github/workflows` name: a token equal to a file's path below the target root, with or
|
|
74
|
+
* without a leading `./`, whatever directory the step runs in.
|
|
75
|
+
*/
|
|
76
|
+
export function workflowEntries(host, targetRoot, files) {
|
|
77
|
+
const found = [];
|
|
78
|
+
for (const token of workflowTokens(host, targetRoot)) {
|
|
79
|
+
if (COMPUTED.test(token)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const file = files.byPath.get(token.startsWith("./") ? token.slice(2) : token);
|
|
83
|
+
if (file !== undefined) {
|
|
84
|
+
found.push({ file, source: token });
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
return found;
|
|
@@ -48,7 +48,7 @@ function unstatable(reading) {
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
/** The path beside `configFile` its written configuration takes, which no file has. */
|
|
51
|
-
function writtenPath(host, configFile) {
|
|
51
|
+
export function writtenPath(host, configFile) {
|
|
52
52
|
const stem = configFile.replace(/\.json$/u, "");
|
|
53
53
|
for (let counter = 0;; counter += 1) {
|
|
54
54
|
const path = `${stem}.deadset-ts${counter === 0 ? "" : `-${String(counter)}`}.json`;
|
|
@@ -205,8 +205,6 @@ function scan(engine, program, reading, resolver) {
|
|
|
205
205
|
reading.unresolved.push({
|
|
206
206
|
file: usage.file,
|
|
207
207
|
specifier,
|
|
208
|
-
member: answer.member,
|
|
209
|
-
subpath: answer.subpath,
|
|
210
208
|
reason: answer.reason,
|
|
211
209
|
});
|
|
212
210
|
}
|
|
@@ -309,7 +307,6 @@ export function workspacePrograms(engine, host, configFiles, resolver) {
|
|
|
309
307
|
readings: readings.map((reading) => ({
|
|
310
308
|
configFile: reading.configFile,
|
|
311
309
|
fileNames: reading.parsed.fileNames,
|
|
312
|
-
entries: reading.entries,
|
|
313
310
|
unresolved: reading.unresolved,
|
|
314
311
|
componentsUnread: unread.get(reading.configFile),
|
|
315
312
|
})),
|
|
@@ -259,7 +259,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
259
259
|
return {
|
|
260
260
|
kind: "unresolved",
|
|
261
261
|
member: name,
|
|
262
|
-
subpath,
|
|
263
262
|
reason: `${String(linked.dirs.length)} workspace packages are named ${name} (${linked.dirs.join(", ")}) and nothing links the importer to one of them; install the workspace, or declare the dependency with a range only one of them satisfies`,
|
|
264
263
|
};
|
|
265
264
|
}
|
|
@@ -295,7 +294,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
295
294
|
return {
|
|
296
295
|
kind: "unresolved",
|
|
297
296
|
member: name,
|
|
298
|
-
subpath,
|
|
299
297
|
reason: built
|
|
300
298
|
? `its manifest names ${named}, output no tsconfig*.json of the package maps back to source, and nothing installed links the importer to it; install the workspace, or point an exports condition the importing configuration's customConditions lists at its source`
|
|
301
299
|
: member.configurations.length === 0
|
package/dist/src/workspace.js
CHANGED
|
@@ -256,7 +256,6 @@ function configurationsOf(host, dir, parse) {
|
|
|
256
256
|
? []
|
|
257
257
|
: [
|
|
258
258
|
{
|
|
259
|
-
configFile,
|
|
260
259
|
fileNames: new Set(parsed.fileNames),
|
|
261
260
|
layout: layoutOf(configFile, parsed),
|
|
262
261
|
},
|
|
@@ -338,8 +337,6 @@ export function findWorkspace(host, targetRoot, parse) {
|
|
|
338
337
|
return found;
|
|
339
338
|
};
|
|
340
339
|
return {
|
|
341
|
-
root: declared.root,
|
|
342
|
-
declaredIn: declared.file,
|
|
343
340
|
members,
|
|
344
341
|
named,
|
|
345
342
|
memberOf: (path) => {
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-dev.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"typecheck:tests": "tsc -p tsconfig.test.json",
|
|
25
25
|
"test": "vitest --run",
|
|
26
26
|
"test:starters": "vitest --run --config starters/vitest.config.ts",
|
|
27
|
+
"test:mutation": "stryker run",
|
|
27
28
|
"lint": "eslint .",
|
|
28
29
|
"lint:prettier": "prettier --check .",
|
|
29
30
|
"lint:fix": "eslint . --fix && prettier --write ."
|