@cplieger/deadset-ts 6.0.0 → 6.1.0
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/README.md +1 -1
- package/dist/src/configured-declarations.js +7 -5
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +67 -1
- package/dist/src/conventions.js +51 -9
- package/dist/src/edges.js +19 -16
- package/dist/src/findings/completion.js +9 -4
- package/dist/src/findings/emitters.js +35 -34
- package/dist/src/findings/reads-and-writes.js +3 -2
- package/dist/src/findings/visibility-narrowing.js +2 -1
- package/dist/src/inventory.js +6 -1
- package/dist/src/module-declarations.js +3 -3
- package/dist/src/outside-tests.js +5 -11
- package/dist/src/overrides.js +22 -10
- package/dist/src/ref.js +18 -2
- package/dist/src/reporters/sarif.js +3 -1
- package/dist/src/suppress-file.js +3 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-programs.js +6 -31
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/configured-declarations.ts +7 -5
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +84 -1
- package/src/conventions.ts +77 -9
- package/src/edges.ts +20 -17
- package/src/findings/completion.ts +11 -4
- package/src/findings/emitters.ts +38 -51
- package/src/findings/reads-and-writes.ts +7 -2
- package/src/findings/visibility-narrowing.ts +2 -1
- package/src/inventory.ts +6 -1
- package/src/module-declarations.ts +3 -3
- package/src/outside-tests.ts +5 -11
- package/src/overrides.ts +26 -11
- package/src/ref.ts +27 -4
- package/src/reporters/sarif.ts +5 -1
- package/src/suppress-file.ts +3 -6
- package/src/version.ts +1 -1
- package/src/workspace-programs.ts +6 -35
package/dist/src/overrides.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The overrides of the target's class members. A call through the base class names the
|
|
3
3
|
* base's member and runs the override of whichever subclass the value is, so the base
|
|
4
|
-
* member's declaration references every override. A method
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* member's declaration references every override. A method, an accessor or a property,
|
|
5
|
+
* static or not, overriding a member of a class declared outside the target, at any depth
|
|
6
|
+
* of its `extends` chain, is used by outside code holding the instance or the class
|
|
7
|
+
* through that class, so its own class references it.
|
|
7
8
|
*/
|
|
8
9
|
import { isClassDeclaration, isClassExpression, isExpressionWithTypeArguments, isPropertyAccessExpression, isQualifiedName, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
10
|
import { memberComponent } from "./class-members.js";
|
|
10
11
|
import { nodeKey } from "./inventory.js";
|
|
11
12
|
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
12
13
|
import { UNANSWERED } from "./query.js";
|
|
14
|
+
import { nameComponent } from "./ref.js";
|
|
13
15
|
/** The member kinds a subclass overrides. */
|
|
14
16
|
const OVERRIDABLE = new Set(["method", "class-member"]);
|
|
15
17
|
/** The node naming the class one `extends` entry names. */
|
|
@@ -40,7 +42,7 @@ function extending(project, held) {
|
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* The references each base class member makes to its overrides, for the classes of one
|
|
43
|
-
* project that extend another class of the target, and each class makes to its
|
|
45
|
+
* project that extend another class of the target, and each class makes to its members
|
|
44
46
|
* that override a member of a class declared outside the target. A member a nearer base
|
|
45
47
|
* already overrides is reached through that override. A private member is not
|
|
46
48
|
* overridden. Each reference is placed at the override and made by its file.
|
|
@@ -102,7 +104,7 @@ export function overrideReferences(project, held, chains, testFiles) {
|
|
|
102
104
|
const outsideNames = outsideMemberNames(project, bases, outsideBases);
|
|
103
105
|
for (const [classId, names] of outsideNames) {
|
|
104
106
|
for (const [component, member] of members.get(classId) ?? []) {
|
|
105
|
-
if (
|
|
107
|
+
if (names.has(component)) {
|
|
106
108
|
found.push({
|
|
107
109
|
from: classId,
|
|
108
110
|
to: member.id,
|
|
@@ -133,15 +135,20 @@ export function overrideReferences(project, held, chains, testFiles) {
|
|
|
133
135
|
}
|
|
134
136
|
/**
|
|
135
137
|
* Per class of the target with a class declared outside the target in its `extends`
|
|
136
|
-
* chain, the
|
|
138
|
+
* chain, the member components that outside class's instances and constructor carry, a
|
|
139
|
+
* static one spelled as a static member's component is.
|
|
137
140
|
*/
|
|
138
141
|
function outsideMemberNames(project, bases, outsideBases) {
|
|
139
142
|
const direct = new Map();
|
|
140
143
|
for (const [classId, base] of outsideBases) {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
const instance = project.queries.declaredTypeOf(base);
|
|
145
|
+
const statics = project.queries.typeOfSymbol(base);
|
|
146
|
+
const components = [
|
|
147
|
+
...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
|
|
148
|
+
...namesOf(project, statics === UNANSWERED ? undefined : statics).map((name) => `${nameComponent(name)}:static`),
|
|
149
|
+
];
|
|
150
|
+
if (components.length > 0) {
|
|
151
|
+
direct.set(classId, new Set(components));
|
|
145
152
|
}
|
|
146
153
|
}
|
|
147
154
|
const names = new Map();
|
|
@@ -163,3 +170,8 @@ function outsideMemberNames(project, bases, outsideBases) {
|
|
|
163
170
|
}
|
|
164
171
|
return names;
|
|
165
172
|
}
|
|
173
|
+
/** The names of a type's properties; none where the type or its properties are unanswered. */
|
|
174
|
+
function namesOf(project, type) {
|
|
175
|
+
const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
|
|
176
|
+
return properties === UNANSWERED ? [] : properties.map((property) => property.name);
|
|
177
|
+
}
|
package/dist/src/ref.js
CHANGED
|
@@ -43,7 +43,7 @@ const MANIFEST_SCOPE = `${PREFIX}${TS_PACKAGE}/package\\.json`;
|
|
|
43
43
|
export const REF_EXPRESSIONS = [
|
|
44
44
|
`^${SCOPE}#$`,
|
|
45
45
|
`^${SCOPE}#${TS_HEAD}$`,
|
|
46
|
-
`^${SCOPE}
|
|
46
|
+
`^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
|
|
47
47
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
|
|
48
48
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
|
|
49
49
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
|
|
@@ -66,6 +66,9 @@ export function nameComponent(name) {
|
|
|
66
66
|
if (IDENTIFIER.test(name) || PRIVATE_NAME.test(name)) {
|
|
67
67
|
return name;
|
|
68
68
|
}
|
|
69
|
+
return quoted(name);
|
|
70
|
+
}
|
|
71
|
+
function quoted(name) {
|
|
69
72
|
const escaped = name
|
|
70
73
|
.replaceAll("\\", "\\\\")
|
|
71
74
|
.replaceAll("'", "\\'")
|
|
@@ -82,6 +85,11 @@ export function nameComponent(name) {
|
|
|
82
85
|
export function computedComponent(text) {
|
|
83
86
|
return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
|
|
84
87
|
}
|
|
88
|
+
/** The fragment of an export alias, inside the `declare module` block named, if one is. */
|
|
89
|
+
export function aliasFragment(name, block) {
|
|
90
|
+
const head = block === undefined ? "" : `${quoted(block)}.`;
|
|
91
|
+
return `${head}${nameComponent(name)}:alias`;
|
|
92
|
+
}
|
|
85
93
|
function spell(component) {
|
|
86
94
|
return component.computed ? computedComponent(component.text) : nameComponent(component.text);
|
|
87
95
|
}
|
|
@@ -96,7 +104,7 @@ export function renderRef(module, fragment) {
|
|
|
96
104
|
case "module":
|
|
97
105
|
return `${scope}#`;
|
|
98
106
|
case "alias":
|
|
99
|
-
return `${scope}#${
|
|
107
|
+
return `${scope}#${aliasFragment(fragment.name, fragment.block)}`;
|
|
100
108
|
case "dependency":
|
|
101
109
|
return `${scope}#${fragment.name}:${fragment.section}`;
|
|
102
110
|
case "declaration": {
|
|
@@ -107,6 +115,14 @@ export function renderRef(module, fragment) {
|
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* One declaration's key among a run's: its reference and the file that declares it. Two
|
|
120
|
+
* packages of one name spell one reference for two declarations, so the reference alone
|
|
121
|
+
* keys neither.
|
|
122
|
+
*/
|
|
123
|
+
export function declarationKey(ref, path) {
|
|
124
|
+
return `${ref}\u0000${path}`;
|
|
125
|
+
}
|
|
110
126
|
/**
|
|
111
127
|
* Whether one string is a reference of this language's grammar, in its canonical
|
|
112
128
|
* spelling. A string that is not, whether misspelled or spelled in a form the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsonChunks } from "../json-chunks.js";
|
|
2
2
|
import { KINDS } from "../kinds.js";
|
|
3
|
+
import { declarationKey } from "../ref.js";
|
|
3
4
|
import { lineHashes, symbolFingerprint } from "./fingerprint.js";
|
|
4
5
|
import { RenderError } from "./reporter.js";
|
|
5
6
|
import { utf8Bytes } from "./sha256.js";
|
|
@@ -122,7 +123,8 @@ function relatedOf(found) {
|
|
|
122
123
|
related.push({ label: "write", at, endLine: at.end_line });
|
|
123
124
|
}
|
|
124
125
|
for (const one of found.component.members ?? []) {
|
|
125
|
-
if (one.ref
|
|
126
|
+
if (declarationKey(one.ref, one.position.path) !==
|
|
127
|
+
declarationKey(found.symbol.ref, found.position.path)) {
|
|
126
128
|
related.push({ label: "member", at: one.position, endLine: one.position.end_line });
|
|
127
129
|
}
|
|
128
130
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { jsonChunks } from "./json-chunks.js";
|
|
8
8
|
import { joinPath } from "./paths.js";
|
|
9
9
|
import { positionAt, walkDocument } from "./json-document.js";
|
|
10
|
+
import { declarationKey } from "./ref.js";
|
|
10
11
|
import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isSymbolRef, siteText, } from "./suppress.js";
|
|
11
12
|
/** The name and the location the grammar fixes for the ignore file. */
|
|
12
13
|
export const IGNORE_FILE = "deadset-ignore.json";
|
|
@@ -38,9 +39,6 @@ function documentSite(shape) {
|
|
|
38
39
|
function isObject(value) {
|
|
39
40
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
41
|
}
|
|
41
|
-
function referenceKey(ref, path) {
|
|
42
|
-
return `${ref}\u0000${path}`;
|
|
43
|
-
}
|
|
44
42
|
/**
|
|
45
43
|
* Reads one entry or row: the records it binds, the refusals it carries, or the
|
|
46
44
|
* malformed value that ends the run.
|
|
@@ -100,7 +98,7 @@ function readRecord(value, site, byReference, shape) {
|
|
|
100
98
|
if (refusals.length > 0) {
|
|
101
99
|
return { records: [], refusals };
|
|
102
100
|
}
|
|
103
|
-
const named = byReference.get(
|
|
101
|
+
const named = byReference.get(declarationKey(symbol, written.path)) ?? [];
|
|
104
102
|
if (named.length === 0) {
|
|
105
103
|
return { records: [{ ...written, bound: "" }], refusals: [] };
|
|
106
104
|
}
|
|
@@ -109,7 +107,7 @@ function readRecord(value, site, byReference, shape) {
|
|
|
109
107
|
function byReferenceOf(symbols) {
|
|
110
108
|
const byReference = new Map();
|
|
111
109
|
for (const symbol of symbols) {
|
|
112
|
-
const key =
|
|
110
|
+
const key = declarationKey(symbol.ref, symbol.position.path);
|
|
113
111
|
byReference.set(key, [...(byReference.get(key) ?? []), symbol]);
|
|
114
112
|
}
|
|
115
113
|
return byReference;
|
package/dist/src/version.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { ModuleKind, ModuleResolutionKind } from "@typescript/native/unstable/sync";
|
|
9
9
|
import { isStringLiteralLikeNode } from "@typescript/native/unstable/ast";
|
|
10
|
-
import { dirnamePath,
|
|
10
|
+
import { dirnamePath, resolvePath } from "./paths.js";
|
|
11
11
|
/** The most questions one round trip carries. */
|
|
12
12
|
const BATCH = 2048;
|
|
13
13
|
/**
|
|
@@ -20,31 +20,6 @@ function analysisOptions(parsed) {
|
|
|
20
20
|
const { composite, rootDir: _rootDir, outDir: _outDir, declarationDir: _declarationDir, tsBuildInfoFile: _tsBuildInfoFile, ...kept } = parsed.options;
|
|
21
21
|
return composite === true ? { ...kept, declaration: true } : kept;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
24
|
-
* The references a built program can carry: only where every referenced
|
|
25
|
-
* configuration reads and is composite with output, because the compiler reports a
|
|
26
|
-
* broken reference at syntax a built program does not have.
|
|
27
|
-
*/
|
|
28
|
-
function carriedReferences(engine, parsed) {
|
|
29
|
-
const references = parsed.projectReferences ?? [];
|
|
30
|
-
for (const reference of references) {
|
|
31
|
-
// A reference that names a directory names the `tsconfig.json` in it.
|
|
32
|
-
const configFile = reference.path.endsWith(".json")
|
|
33
|
-
? reference.path
|
|
34
|
-
: joinPath(reference.path, "tsconfig.json");
|
|
35
|
-
let options;
|
|
36
|
-
try {
|
|
37
|
-
options = engine.parseConfigFile(configFile).options;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
if (options.composite !== true || options.noEmit === true) {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return references;
|
|
47
|
-
}
|
|
48
23
|
/** The conditions an `exports` lookup under one configuration and one resolution mode reads. */
|
|
49
24
|
function conditionsOf(parsed, mode) {
|
|
50
25
|
const resolution = parsed.options.moduleResolution;
|
|
@@ -94,7 +69,7 @@ function configurationOf(engine, host, reading) {
|
|
|
94
69
|
if (reason !== undefined) {
|
|
95
70
|
return { kind: "unread", reason };
|
|
96
71
|
}
|
|
97
|
-
const { options } = reading.parsed;
|
|
72
|
+
const { options, projectReferences: references = [] } = reading.parsed;
|
|
98
73
|
const raw = options["pathsBasePath"];
|
|
99
74
|
const base = typeof raw === "string" ? raw : dirnamePath(reading.configFile);
|
|
100
75
|
const paths = {};
|
|
@@ -117,8 +92,8 @@ function configurationOf(engine, host, reading) {
|
|
|
117
92
|
...(options.composite === true ? { composite: false, declaration: true } : {}),
|
|
118
93
|
paths,
|
|
119
94
|
},
|
|
120
|
-
...(
|
|
121
|
-
? { references:
|
|
95
|
+
...(references.length > 0
|
|
96
|
+
? { references: references.map((reference) => ({ path: reference.path })) }
|
|
122
97
|
: {}),
|
|
123
98
|
...(exclude === undefined ? {} : { exclude }),
|
|
124
99
|
});
|
|
@@ -150,6 +125,7 @@ function configurationOf(engine, host, reading) {
|
|
|
150
125
|
/** The program one configuration is built as, under the answers it holds so far. */
|
|
151
126
|
function programOf(engine, reading) {
|
|
152
127
|
const compilerOptions = analysisOptions(reading.parsed);
|
|
128
|
+
const references = reading.parsed.projectReferences ?? [];
|
|
153
129
|
const resolver = engine.createModuleResolver(compilerOptions, reading.entries);
|
|
154
130
|
return {
|
|
155
131
|
resolver,
|
|
@@ -159,7 +135,7 @@ function programOf(engine, reading) {
|
|
|
159
135
|
options: {
|
|
160
136
|
moduleResolver: resolver,
|
|
161
137
|
configFileParsingDiagnostics: reading.parsed.errors,
|
|
162
|
-
...(
|
|
138
|
+
...(references.length > 0 ? { projectReferences: references } : {}),
|
|
163
139
|
},
|
|
164
140
|
},
|
|
165
141
|
};
|
|
@@ -292,7 +268,6 @@ export function workspacePrograms(engine, host, configFiles, resolver) {
|
|
|
292
268
|
{
|
|
293
269
|
configFile,
|
|
294
270
|
parsed,
|
|
295
|
-
references: carriedReferences(engine, parsed),
|
|
296
271
|
entries: [],
|
|
297
272
|
seen: new Set(),
|
|
298
273
|
bound: new Map(),
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lint:fix": "eslint . --fix && prettier --write ."
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@typescript/native": "npm:typescript@7.1.0-dev.
|
|
32
|
+
"@typescript/native": "npm:typescript@7.1.0-dev.20261007.1"
|
|
33
33
|
},
|
|
34
34
|
"overrides": {
|
|
35
35
|
"qs": "^6.15.2"
|
|
@@ -207,8 +207,8 @@ function modulesNamed<Brand>(
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
* The declaration each entry names in one project. A symbol entry names
|
|
211
|
-
* inventory
|
|
210
|
+
* The declaration each entry names in one project. A symbol entry names every
|
|
211
|
+
* declaration of the inventory whose reference it spells exactly; a module entry names the
|
|
212
212
|
* declaration its path reaches from the exports of the module its specifier resolves
|
|
213
213
|
* to; a global entry names the declaration its path reaches from the global scope.
|
|
214
214
|
* Every alias along a path is followed to the declaration it stands for.
|
|
@@ -221,15 +221,17 @@ export function resolveEntries<Brand>(
|
|
|
221
221
|
if (entries.length === 0) {
|
|
222
222
|
return [];
|
|
223
223
|
}
|
|
224
|
-
const byRef = new Map
|
|
224
|
+
const byRef = new Map<string, string[]>();
|
|
225
|
+
for (const symbol of held.symbols) {
|
|
226
|
+
byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
|
|
227
|
+
}
|
|
225
228
|
const modules = modulesNamed(
|
|
226
229
|
project,
|
|
227
230
|
new Set(entries.flatMap((entry) => (entry.shape === "module" ? [entry.module] : []))),
|
|
228
231
|
);
|
|
229
232
|
return entries.map((entry) => {
|
|
230
233
|
if (entry.shape === "symbol") {
|
|
231
|
-
|
|
232
|
-
return { entry, handles: new Set<string>(), ids: new Set(id === undefined ? [] : [id]) };
|
|
234
|
+
return { entry, handles: new Set<string>(), ids: new Set(byRef.get(entry.symbol) ?? []) };
|
|
233
235
|
}
|
|
234
236
|
const { components, static: isStatic } = componentsOf(
|
|
235
237
|
entry.shape === "module" ? entry.name : entry.global,
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "4.
|
|
5
|
+
corpusVersion: "4.1.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:167f770cdff2776fa7ab28b7ff6121c4d7bdb01d83c5ba0057d9303281614008",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
} as const;
|
package/src/convention-rows.ts
CHANGED
|
@@ -31,6 +31,20 @@ export interface ShortNameReading extends MoveReading {
|
|
|
31
31
|
readonly package: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* One configuration property whose value makes the tool's output import a runtime helper
|
|
36
|
+
* package, so the package is used where the property holds that value.
|
|
37
|
+
*/
|
|
38
|
+
export interface HelperReading extends MoveReading {
|
|
39
|
+
/**
|
|
40
|
+
* The value: `true`, or a string the property holds, alone, as an element of an array,
|
|
41
|
+
* or as the first element of an array that is such an element.
|
|
42
|
+
*/
|
|
43
|
+
readonly value: true | string;
|
|
44
|
+
/** The package the output imports its helpers from. */
|
|
45
|
+
readonly package: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
/** One directory a row's globs name that a configuration property moves. */
|
|
35
49
|
export interface DirectoryMove {
|
|
36
50
|
/** The placeholder the row's globs and later moves write as `<id>`. */
|
|
@@ -71,6 +85,8 @@ export interface ConventionRow {
|
|
|
71
85
|
readonly selections?: readonly MoveReading[];
|
|
72
86
|
/** The members of a `package.json` the tool reads its configuration from. */
|
|
73
87
|
readonly manifestKeys?: readonly string[];
|
|
88
|
+
/** The settings that make the tool's output import a runtime helper package. */
|
|
89
|
+
readonly helpers?: readonly HelperReading[];
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
const SCRIPT = "{js,jsx,ts,tsx}";
|
|
@@ -105,6 +121,12 @@ const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart
|
|
|
105
121
|
|
|
106
122
|
const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
|
|
107
123
|
|
|
124
|
+
const BABEL_CONFIG = [
|
|
125
|
+
"babel.config.{json,js,cjs,mjs,cts,mts,ts}",
|
|
126
|
+
".babelrc",
|
|
127
|
+
".babelrc.{json,js,cjs,mjs,cts}",
|
|
128
|
+
];
|
|
129
|
+
|
|
108
130
|
const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
|
|
109
131
|
const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
|
|
110
132
|
const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
|
|
@@ -132,6 +154,19 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
132
154
|
},
|
|
133
155
|
],
|
|
134
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "babel",
|
|
159
|
+
package: "@babel/core",
|
|
160
|
+
range: ">=7.0.0 <9.0.0",
|
|
161
|
+
entries: [],
|
|
162
|
+
moves: [],
|
|
163
|
+
helpers: ["@babel/plugin-transform-runtime", "@babel/transform-runtime"].map((value) => ({
|
|
164
|
+
property: "plugins",
|
|
165
|
+
files: BABEL_CONFIG,
|
|
166
|
+
value,
|
|
167
|
+
package: "@babel/runtime",
|
|
168
|
+
})),
|
|
169
|
+
},
|
|
135
170
|
{
|
|
136
171
|
name: "eslint",
|
|
137
172
|
package: "eslint",
|
|
@@ -304,6 +339,21 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
304
339
|
},
|
|
305
340
|
],
|
|
306
341
|
},
|
|
342
|
+
{
|
|
343
|
+
name: "rolldown",
|
|
344
|
+
package: "rolldown",
|
|
345
|
+
range: ">=1.0.0 <2.0.0",
|
|
346
|
+
entries: [],
|
|
347
|
+
moves: [],
|
|
348
|
+
helpers: [
|
|
349
|
+
{
|
|
350
|
+
property: "transform.helpers.mode",
|
|
351
|
+
files: [`rolldown.config.${ANY_MODULE}`],
|
|
352
|
+
value: "Runtime",
|
|
353
|
+
package: "@oxc-project/runtime",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
},
|
|
307
357
|
{
|
|
308
358
|
name: "solidstart",
|
|
309
359
|
package: "@solidjs/start",
|
|
@@ -350,7 +400,10 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
350
400
|
range: ">=7.0.0 <11.0.0",
|
|
351
401
|
entries: [STRYKER_MODULE],
|
|
352
402
|
moves: [],
|
|
353
|
-
selections: [
|
|
403
|
+
selections: [
|
|
404
|
+
{ property: "mutate", files: [STRYKER_CONFIG] },
|
|
405
|
+
{ property: "ignorePatterns", files: [STRYKER_CONFIG] },
|
|
406
|
+
],
|
|
354
407
|
shortNames: [
|
|
355
408
|
{
|
|
356
409
|
property: "testRunner",
|
|
@@ -481,6 +534,36 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
|
|
|
481
534
|
},
|
|
482
535
|
],
|
|
483
536
|
},
|
|
537
|
+
{
|
|
538
|
+
name: "swc",
|
|
539
|
+
package: "@swc/core",
|
|
540
|
+
range: ">=1.0.0 <2.0.0",
|
|
541
|
+
entries: [],
|
|
542
|
+
moves: [],
|
|
543
|
+
helpers: [
|
|
544
|
+
{
|
|
545
|
+
property: "jsc.externalHelpers",
|
|
546
|
+
files: [".swcrc"],
|
|
547
|
+
value: true,
|
|
548
|
+
package: "@swc/helpers",
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "vite",
|
|
554
|
+
package: "vite",
|
|
555
|
+
range: ">=8.0.0 <9.0.0",
|
|
556
|
+
entries: [],
|
|
557
|
+
moves: [],
|
|
558
|
+
helpers: [
|
|
559
|
+
{
|
|
560
|
+
property: "oxc.helpers.mode",
|
|
561
|
+
files: [VITE_CONFIG],
|
|
562
|
+
value: "Runtime",
|
|
563
|
+
package: "@oxc-project/runtime",
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
},
|
|
484
567
|
{
|
|
485
568
|
name: "vitest",
|
|
486
569
|
package: "vitest",
|
package/src/conventions.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
CONVENTION_ROWS,
|
|
51
51
|
type ConventionRow,
|
|
52
52
|
type DirectoryMove,
|
|
53
|
+
type HelperReading,
|
|
53
54
|
type MoveReading,
|
|
54
55
|
type OptionsCall,
|
|
55
56
|
type ShortNameReading,
|
|
@@ -110,7 +111,10 @@ export interface Conventions {
|
|
|
110
111
|
readonly globs: readonly ConventionGlob[];
|
|
111
112
|
readonly generated: readonly GeneratedDirectory[];
|
|
112
113
|
readonly failures: readonly ConventionFailure[];
|
|
113
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* The dependencies the applied rows' short names, manifest keys and helper settings use,
|
|
116
|
+
* by name, each once.
|
|
117
|
+
*/
|
|
114
118
|
readonly uses: readonly string[];
|
|
115
119
|
/** Per configuration file, absolute, the strings its applied rows' selection keys hold. */
|
|
116
120
|
readonly selected: ReadonlyMap<string, Selected>;
|
|
@@ -136,6 +140,9 @@ type ParseSource = (fileName: string, text: string) => SourceFile;
|
|
|
136
140
|
|
|
137
141
|
const JSON_EXTENSION = ".json";
|
|
138
142
|
|
|
143
|
+
/** A configuration file with no extension that its tool reads as a JSON document. */
|
|
144
|
+
const JSON_RC = /^\.[\w-]+rc$/u;
|
|
145
|
+
|
|
139
146
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
140
147
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"] as const;
|
|
141
148
|
|
|
@@ -533,11 +540,10 @@ function readPath(
|
|
|
533
540
|
}
|
|
534
541
|
|
|
535
542
|
/**
|
|
536
|
-
* Every
|
|
537
|
-
*
|
|
538
|
-
* array value, whatever else the path holds.
|
|
543
|
+
* Every value one file writes at one property's path inside the objects it is read from,
|
|
544
|
+
* as {@link readProperty} reads them, whatever else the path holds.
|
|
539
545
|
*/
|
|
540
|
-
function
|
|
546
|
+
function valuesAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
541
547
|
const declared = declaredValues(file);
|
|
542
548
|
const objects =
|
|
543
549
|
call === undefined
|
|
@@ -558,10 +564,8 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
|
|
|
558
564
|
const held = unwrapped(member.initializer);
|
|
559
565
|
if (rest.length > 0) {
|
|
560
566
|
visit(held, rest);
|
|
561
|
-
} else
|
|
567
|
+
} else {
|
|
562
568
|
found.push(held);
|
|
563
|
-
} else if (isArrayLiteralExpression(held)) {
|
|
564
|
-
found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
|
|
565
569
|
}
|
|
566
570
|
}
|
|
567
571
|
};
|
|
@@ -573,6 +577,36 @@ function stringsAt(file: SourceFile, property: string, call?: OptionsCall): read
|
|
|
573
577
|
return found;
|
|
574
578
|
}
|
|
575
579
|
|
|
580
|
+
/** The string literals among values: each value itself, or each element of an array value. */
|
|
581
|
+
function stringsAt(file: SourceFile, property: string, call?: OptionsCall): readonly Node[] {
|
|
582
|
+
return valuesAt(file, property, call).flatMap((held) => {
|
|
583
|
+
if (literalText(held) !== undefined) {
|
|
584
|
+
return [held];
|
|
585
|
+
}
|
|
586
|
+
return isArrayLiteralExpression(held)
|
|
587
|
+
? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
|
|
588
|
+
: [];
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
593
|
+
function holdsValue(held: Node, value: true | string): boolean {
|
|
594
|
+
if (value === true) {
|
|
595
|
+
return held.kind === SyntaxKind.TrueKeyword;
|
|
596
|
+
}
|
|
597
|
+
if (literalText(held) === value) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
return (
|
|
601
|
+
isArrayLiteralExpression(held) &&
|
|
602
|
+
held.elements.some((element) => {
|
|
603
|
+
const one = unwrapped(element);
|
|
604
|
+
const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
|
|
605
|
+
return first !== undefined && literalText(first) === value;
|
|
606
|
+
})
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
576
610
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
577
611
|
function selectedStrings(
|
|
578
612
|
host: Host,
|
|
@@ -760,7 +794,11 @@ function readingFiles(
|
|
|
760
794
|
} catch {
|
|
761
795
|
continue;
|
|
762
796
|
}
|
|
763
|
-
found.push({
|
|
797
|
+
found.push({
|
|
798
|
+
name,
|
|
799
|
+
text,
|
|
800
|
+
file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
|
|
801
|
+
});
|
|
764
802
|
}
|
|
765
803
|
}
|
|
766
804
|
return found;
|
|
@@ -808,6 +846,33 @@ function shortNameUses(
|
|
|
808
846
|
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
809
847
|
}
|
|
810
848
|
|
|
849
|
+
/** The declared dependencies an applied row's helper settings use. */
|
|
850
|
+
function helperUses(
|
|
851
|
+
host: Host,
|
|
852
|
+
parse: ParseSource,
|
|
853
|
+
dir: string,
|
|
854
|
+
manifest: unknown,
|
|
855
|
+
readings: readonly HelperReading[],
|
|
856
|
+
): readonly string[] {
|
|
857
|
+
const declared = new Set(
|
|
858
|
+
DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
859
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
860
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
861
|
+
}),
|
|
862
|
+
);
|
|
863
|
+
return readings
|
|
864
|
+
.filter(
|
|
865
|
+
(reading) =>
|
|
866
|
+
declared.has(reading.package) &&
|
|
867
|
+
readingFiles(host, parse, dir, reading.files).some(({ file }) =>
|
|
868
|
+
valuesAt(file, reading.property, reading.call).some((held) =>
|
|
869
|
+
holdsValue(held, reading.value),
|
|
870
|
+
),
|
|
871
|
+
),
|
|
872
|
+
)
|
|
873
|
+
.map((reading) => reading.package);
|
|
874
|
+
}
|
|
875
|
+
|
|
811
876
|
/** Two strings ordered bytewise. */
|
|
812
877
|
function compare(a: string, b: string): number {
|
|
813
878
|
if (a === b) {
|
|
@@ -897,6 +962,9 @@ export function readConventions(
|
|
|
897
962
|
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
898
963
|
uses.add(name);
|
|
899
964
|
}
|
|
965
|
+
for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
|
|
966
|
+
uses.add(name);
|
|
967
|
+
}
|
|
900
968
|
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
901
969
|
const excludes = [
|
|
902
970
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|