@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.1
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 +106 -10
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +50 -12
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +183 -28
- package/dist/src/discover.js +132 -11
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +297 -45
- package/dist/src/inventory.js +52 -8
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/overrides.js +41 -10
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +4 -1
- package/dist/src/report.js +4 -2
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +54 -8
- package/dist/src/session.js +7 -0
- package/dist/src/source-files.js +39 -14
- package/dist/src/suppress-file.js +10 -2
- package/dist/src/suppress-inline.js +18 -2
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +34 -7
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/verbs/analyze.js +16 -0
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +53 -10
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +143 -10
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +66 -12
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +247 -41
- package/src/discover.ts +156 -13
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +394 -59
- package/src/inventory.ts +71 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/overrides.ts +51 -12
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +10 -1
- package/src/report.ts +16 -3
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +55 -4
- package/src/session.ts +7 -0
- package/src/source-files.ts +66 -16
- package/src/suppress-file.ts +12 -1
- package/src/suppress-inline.ts +18 -1
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +38 -6
- package/src/type-parameter-reads.ts +227 -0
- package/src/verbs/analyze.ts +22 -0
- package/src/verbs/explain.ts +5 -2
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +62 -10
|
@@ -104,6 +104,8 @@ function namesModule(specifier: string, name: string): boolean {
|
|
|
104
104
|
interface Named {
|
|
105
105
|
readonly id: string;
|
|
106
106
|
readonly members: readonly string[];
|
|
107
|
+
/** Whether the declaration's name is a pattern, which a specifier names by matching it. */
|
|
108
|
+
readonly wildcard: boolean;
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
/**
|
|
@@ -140,7 +142,7 @@ function namedBy(
|
|
|
140
142
|
]),
|
|
141
143
|
);
|
|
142
144
|
const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
|
|
143
|
-
return [{ id, members: members.map((child) => child.id) }];
|
|
145
|
+
return [{ id, members: members.map((child) => child.id), wildcard: name.includes("*") }];
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -208,28 +210,24 @@ export function moduleDeclarationReferences<Brand>(
|
|
|
208
210
|
imports.forEach((one, index) => {
|
|
209
211
|
const resolved = moduleOf(declared.length + index);
|
|
210
212
|
const position = renderPosition(one.file, root, one.specifier.getStart());
|
|
211
|
-
const read = (to: string, resolution: Reference["resolution"]): void => {
|
|
212
|
-
found.push({
|
|
213
|
-
from: one.from,
|
|
214
|
-
to,
|
|
215
|
-
position,
|
|
216
|
-
use: "read",
|
|
217
|
-
resolution,
|
|
218
|
-
test: testFiles.has(position.path),
|
|
219
|
-
});
|
|
213
|
+
const read = (to: string, resolution: Reference["resolution"], test: boolean): void => {
|
|
214
|
+
found.push({ from: one.from, to, position, use: "read", resolution, test });
|
|
220
215
|
};
|
|
216
|
+
const test = testFiles.has(position.path);
|
|
221
217
|
const same = new Set(
|
|
222
218
|
declared
|
|
223
219
|
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
224
220
|
.map((declaration) => declaration.id),
|
|
225
221
|
);
|
|
226
222
|
for (const id of same) {
|
|
227
|
-
read(id, "batch");
|
|
223
|
+
read(id, "batch", test);
|
|
228
224
|
}
|
|
229
225
|
for (const declaration of named(one.specifier.text, one.names)) {
|
|
230
|
-
|
|
226
|
+
// A wildcard declaration is how the compiler types the file a matching specifier
|
|
227
|
+
// names, so the import uses it whoever writes it, a test included.
|
|
228
|
+
if (declaration.wildcard || !same.has(declaration.id)) {
|
|
231
229
|
for (const to of [declaration.id, ...declaration.members]) {
|
|
232
|
-
read(to, "syntax");
|
|
230
|
+
read(to, "syntax", test && !declaration.wildcard);
|
|
233
231
|
}
|
|
234
232
|
}
|
|
235
233
|
}
|
package/src/overrides.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overrides of the target's class members. A call through the base class
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* through that class, so its own class references it.
|
|
2
|
+
* The overrides of the target's class members. A call through the base class runs the
|
|
3
|
+
* override of whichever subclass the value is, so the base member references every
|
|
4
|
+
* override. A member, static or not, overriding one of a class declared outside the target
|
|
5
|
+
* at any depth of its `extends` chain, a library's interface-and-variable class such as
|
|
6
|
+
* `Error` included, is used by outside code holding the value, so its own class references it.
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import {
|
|
@@ -115,12 +114,16 @@ export function overrideReferences<Brand>(
|
|
|
115
114
|
if (
|
|
116
115
|
inTarget.length === 0 &&
|
|
117
116
|
declared !== UNANSWERED &&
|
|
118
|
-
(declared.flags & SymbolFlags.Class) !== 0 &&
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
117
|
+
(((declared.flags & SymbolFlags.Class) !== 0 &&
|
|
118
|
+
// The target's augmentation of an outside class merges an interface into it,
|
|
119
|
+
// so only a class declaration says where the class is.
|
|
120
|
+
declared.declarations.some(
|
|
121
|
+
(handle) => CLASS_DECLARATIONS.has(handle.kind) && !own.has(handle.path),
|
|
122
|
+
)) ||
|
|
123
|
+
((declared.flags & SymbolFlags.Interface) !== 0 &&
|
|
124
|
+
declared.declarations.some(
|
|
125
|
+
(handle) => handle.kind === SyntaxKind.VariableDeclaration && !own.has(handle.path),
|
|
126
|
+
)))
|
|
124
127
|
) {
|
|
125
128
|
outsideBases.set(id, declared);
|
|
126
129
|
}
|
|
@@ -185,11 +188,13 @@ function outsideMemberNames<Brand>(
|
|
|
185
188
|
outsideBases: ReadonlyMap<string, TSSymbol>,
|
|
186
189
|
): ReadonlyMap<string, ReadonlySet<string>> {
|
|
187
190
|
const direct = new Map<string, ReadonlySet<string>>();
|
|
191
|
+
const error = errorType(project);
|
|
188
192
|
for (const [classId, base] of outsideBases) {
|
|
189
193
|
const instance = project.queries.declaredTypeOf(base);
|
|
190
194
|
const statics = project.queries.typeOfSymbol(base);
|
|
191
195
|
const components = [
|
|
192
196
|
...namesOf(project, instance === UNANSWERED ? undefined : instance).map(nameComponent),
|
|
197
|
+
...(instance !== UNANSWERED && reaches(project, instance, error) ? ["toString"] : []),
|
|
193
198
|
...namesOf(project, statics === UNANSWERED ? undefined : statics).map(
|
|
194
199
|
(name) => `${nameComponent(name)}:static`,
|
|
195
200
|
),
|
|
@@ -218,6 +223,40 @@ function outsideMemberNames<Brand>(
|
|
|
218
223
|
return names;
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
/** The global `Error` instance type's identifier, where the program's library declares one. */
|
|
227
|
+
function errorType<Brand>(project: ProjectView<Brand>): number | undefined {
|
|
228
|
+
const symbol = project.queries.resolveName("Error", SymbolFlags.Type, undefined, false);
|
|
229
|
+
if (symbol === UNANSWERED || symbol === undefined) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const type = project.queries.declaredTypeOf(symbol);
|
|
233
|
+
return type === UNANSWERED ? undefined : type.id;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Whether one type is the `Error` instance type or extends it at any depth: the runtime
|
|
238
|
+
* prints an error through its `toString`, a member the library's `Error` interface omits.
|
|
239
|
+
*/
|
|
240
|
+
function reaches<Brand>(
|
|
241
|
+
project: ProjectView<Brand>,
|
|
242
|
+
type: Type,
|
|
243
|
+
target: number | undefined,
|
|
244
|
+
): boolean {
|
|
245
|
+
const seen = new Set<number>();
|
|
246
|
+
const pending = [type];
|
|
247
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
248
|
+
if (next.id === target) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
if (!seen.has(next.id)) {
|
|
252
|
+
seen.add(next.id);
|
|
253
|
+
const bases = project.queries.baseTypes(next);
|
|
254
|
+
pending.push(...(bases === UNANSWERED ? [] : bases));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
221
260
|
/** The names of a type's properties; none where the type or its properties are unanswered. */
|
|
222
261
|
function namesOf<Brand>(project: ProjectView<Brand>, type: Type | undefined): readonly string[] {
|
|
223
262
|
const properties = type === undefined ? UNANSWERED : project.queries.propertiesOf(type);
|
package/src/print.ts
CHANGED
|
@@ -84,6 +84,7 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
84
84
|
members: entry.members,
|
|
85
85
|
})),
|
|
86
86
|
serializers: config.ts.serializers.map(declarationOf),
|
|
87
|
+
roots_complete: config.ts.rootsComplete,
|
|
87
88
|
},
|
|
88
89
|
provenance: Object.fromEntries(
|
|
89
90
|
[...provenance]
|
package/src/ref.ts
CHANGED
|
@@ -49,9 +49,11 @@ export const REF_EXPRESSIONS: readonly string[] = [
|
|
|
49
49
|
`^${SCOPE}#$`,
|
|
50
50
|
`^${SCOPE}#${TS_HEAD}$`,
|
|
51
51
|
`^${SCOPE}#(?:${TS_QUOTED}\\.)?${TS_HEAD}:alias$`,
|
|
52
|
+
`^${SCOPE}#${TS_HEAD}:import$`,
|
|
52
53
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+$`,
|
|
53
54
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})+:static$`,
|
|
54
55
|
`^${SCOPE}#${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?<${TS_IDENT}>$`,
|
|
56
|
+
`^${SCOPE}#(?:${TS_HEAD}(?:\\.${TS_MEMBER})*(?::static)?)?\\(${TS_IDENT}\\)$`,
|
|
55
57
|
`^${MANIFEST_SCOPE}#${TS_PACKAGE}:(?:dependency|dev-dependency|peer-dependency)$`,
|
|
56
58
|
];
|
|
57
59
|
|
|
@@ -151,6 +153,16 @@ export function computedComponent(text: string): string {
|
|
|
151
153
|
return `[${text.replaceAll(ASCII_WHITESPACE, "")}]`;
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
/** The reference of an import binding, read against the reference of the module that holds it. */
|
|
157
|
+
export function importRef(moduleRef: string, name: string): string {
|
|
158
|
+
return `${moduleRef}${nameComponent(name)}:import`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The reference of a parameter, read against the reference of the declaration that holds it. */
|
|
162
|
+
export function parameterRef(holderRef: string, name: string): string {
|
|
163
|
+
return `${holderRef}(${name})`;
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
/** The fragment of an export alias, inside the `declare module` block named, if one is. */
|
|
155
167
|
export function aliasFragment(name: string, block?: string): string {
|
|
156
168
|
const head = block === undefined ? "" : `${quoted(block)}.`;
|
package/src/references.ts
CHANGED
|
@@ -60,8 +60,15 @@ import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./pat
|
|
|
60
60
|
import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
|
|
61
61
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
62
62
|
import type { ProjectView } from "./session.ts";
|
|
63
|
+
import { typeParameterReads } from "./type-parameter-reads.ts";
|
|
63
64
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
64
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
computedKeyNames,
|
|
67
|
+
iterationReferences,
|
|
68
|
+
keyNameOf,
|
|
69
|
+
keyedUses,
|
|
70
|
+
type KeyedAccess,
|
|
71
|
+
} from "./symbol-keys.ts";
|
|
65
72
|
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -1621,6 +1628,8 @@ export function references<Brand>(
|
|
|
1621
1628
|
found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1622
1629
|
found.push(...elementAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1623
1630
|
found.push(...keyofComparisonReferences(project, held, chains, root, new Set(tests)));
|
|
1631
|
+
found.push(...iterationReferences(project, held, chains, root, new Set(tests)));
|
|
1632
|
+
found.push(...typeParameterReads(project, held, chains, root, new Set(tests)));
|
|
1624
1633
|
found.push(...unimplementedCalleeReads(project, held, chains, root, new Set(tests)));
|
|
1625
1634
|
found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
|
|
1626
1635
|
typedLookups = (options.requestCount?.() ?? 0) - before;
|
package/src/report.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { StaleSuppression } from "./findings/self-check.ts";
|
|
|
19
19
|
import type { TestFileRule } from "./references.ts";
|
|
20
20
|
|
|
21
21
|
/** The version of the report schema this analyzer writes a report to. */
|
|
22
|
-
const SCHEMA_VERSION = "
|
|
22
|
+
const SCHEMA_VERSION = "9.0.0";
|
|
23
23
|
|
|
24
24
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
25
25
|
export const SCHEMA_VERSIONS_ACCEPTED: readonly string[] = [SCHEMA_VERSION];
|
|
@@ -224,12 +224,21 @@ export interface Report {
|
|
|
224
224
|
readonly excluded_by_cgo: readonly never[];
|
|
225
225
|
readonly test_file_rules: readonly TestFileRule[];
|
|
226
226
|
readonly type_error_skips: readonly TypeErrorSkip[];
|
|
227
|
-
readonly notes: readonly
|
|
227
|
+
readonly notes: readonly ReportNote[];
|
|
228
228
|
readonly unanswered_questions: readonly UnansweredCount[];
|
|
229
229
|
readonly conventions_applied: readonly AppliedConvention[];
|
|
230
230
|
readonly totals: WireTotals;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/** One hint about the run's setup, as the report schema's `notes` spells it. */
|
|
234
|
+
export interface ReportNote {
|
|
235
|
+
readonly kind: "unmapped-entry";
|
|
236
|
+
/** The directory of the manifest the note is about, below the target root. */
|
|
237
|
+
readonly path: string;
|
|
238
|
+
readonly key: "ts.entry_files";
|
|
239
|
+
readonly message: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
233
242
|
/** What one run answered, from which its report is built. */
|
|
234
243
|
export interface ReportInput {
|
|
235
244
|
readonly contractVersion: string;
|
|
@@ -247,6 +256,8 @@ export interface ReportInput {
|
|
|
247
256
|
readonly unanswered: readonly UnansweredCount[];
|
|
248
257
|
/** The convention rows the run applied, in the report's order. */
|
|
249
258
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
259
|
+
/** The notes the run states, in any order. */
|
|
260
|
+
readonly notes?: readonly ReportNote[];
|
|
250
261
|
}
|
|
251
262
|
|
|
252
263
|
/** Two strings ordered bytewise. */
|
|
@@ -510,7 +521,9 @@ export function buildReport(input: ReportInput): Report {
|
|
|
510
521
|
]
|
|
511
522
|
.sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
|
|
512
523
|
.map(([, skip]) => skip),
|
|
513
|
-
notes: [],
|
|
524
|
+
notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
|
|
525
|
+
.sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
|
|
526
|
+
.map(([, note]) => note),
|
|
514
527
|
unanswered_questions: [...input.unanswered]
|
|
515
528
|
.sort(
|
|
516
529
|
(a, b) =>
|
package/src/resolve.ts
CHANGED
|
@@ -112,6 +112,7 @@ interface Doc {
|
|
|
112
112
|
readonly injectionRegistrations: readonly DeclarationEntry[] | undefined;
|
|
113
113
|
readonly lifecycleContracts: readonly LifecycleContract[] | undefined;
|
|
114
114
|
readonly serializers: readonly DeclarationEntry[] | undefined;
|
|
115
|
+
readonly rootsComplete: boolean | undefined;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
/** One document the resolution reads, with the origin its settings carry. */
|
|
@@ -894,6 +895,7 @@ function readDoc(value: unknown, label: string): Doc {
|
|
|
894
895
|
),
|
|
895
896
|
lifecycleContracts: readLifecycleContracts(ts, label),
|
|
896
897
|
serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
|
|
898
|
+
rootsComplete: readBoolean(ts, "roots_complete", "ts.roots_complete", label),
|
|
897
899
|
};
|
|
898
900
|
}
|
|
899
901
|
|
|
@@ -1191,6 +1193,7 @@ function settleAll(inputs: Inputs): { config: Config; provenance: Map<string, Or
|
|
|
1191
1193
|
(doc) => doc.lifecycleContracts,
|
|
1192
1194
|
),
|
|
1193
1195
|
serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
|
|
1196
|
+
rootsComplete: at("ts.roots_complete", defaults.ts.rootsComplete, (doc) => doc.rootsComplete),
|
|
1194
1197
|
},
|
|
1195
1198
|
};
|
|
1196
1199
|
|
package/src/roots.ts
CHANGED
|
@@ -22,19 +22,24 @@ import {
|
|
|
22
22
|
type SourceFile,
|
|
23
23
|
} from "@typescript/native/ast";
|
|
24
24
|
import { aliasChains } from "./alias-chain.ts";
|
|
25
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
filesNamedBy,
|
|
27
|
+
outsideChain,
|
|
28
|
+
relativeSpecifiers,
|
|
29
|
+
type ParseFile,
|
|
30
|
+
} from "./configuration-files.ts";
|
|
26
31
|
import { conventionEntries, type Conventions } from "./conventions.ts";
|
|
27
32
|
import { entryPoints, type EntryRule } from "./entry-points.ts";
|
|
28
33
|
import { globExpression } from "./glob.ts";
|
|
29
34
|
import type { Host } from "./host.ts";
|
|
30
35
|
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
31
36
|
import type { Manifest, ManifestEntry } from "./manifest.ts";
|
|
32
|
-
import { relativePath, resolvePath } from "./paths.ts";
|
|
37
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
33
38
|
import { byPosition } from "./position.ts";
|
|
34
39
|
import { UNANSWERED } from "./query.ts";
|
|
35
40
|
import type { ProjectView } from "./session.ts";
|
|
36
41
|
import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
|
|
37
|
-
import { sourceFilesOf } from "./source-files.ts";
|
|
42
|
+
import { sourceFilesOf, type SourceFiles } from "./source-files.ts";
|
|
38
43
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
39
44
|
|
|
40
45
|
/** Why one declaration is a root. */
|
|
@@ -104,6 +109,8 @@ export interface Roots {
|
|
|
104
109
|
* unknown; otherwise each one an unanswered published type name may name.
|
|
105
110
|
*/
|
|
106
111
|
readonly unanswered: readonly string[];
|
|
112
|
+
/** Every manifest entry naming a file to import or run that reads back to no file of this project. */
|
|
113
|
+
readonly unmappedEntries: readonly ManifestEntry[];
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
/** What decides the roots beside the declarations themselves. */
|
|
@@ -134,6 +141,8 @@ export interface RootOptions {
|
|
|
134
141
|
* inside the target whose entries are published API whatever the target's kind.
|
|
135
142
|
*/
|
|
136
143
|
readonly publishedMembers?: ReadonlySet<string> | undefined;
|
|
144
|
+
/** Every file a program of the run holds, so a file another configuration compiles is no outside file. */
|
|
145
|
+
readonly runFiles?: ReadonlySet<string> | undefined;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
148
|
/** Whether one declaration is written below the directory of a published workspace member. */
|
|
@@ -171,11 +180,25 @@ const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
|
|
|
171
180
|
/** The extension a source file's name drops. */
|
|
172
181
|
const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
|
|
173
182
|
|
|
183
|
+
/** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
|
|
184
|
+
function ofPackage(host: Host, dir: string, file: string): boolean {
|
|
185
|
+
for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
|
|
186
|
+
if (
|
|
187
|
+
relativePath(dir, at) === undefined ||
|
|
188
|
+
host.kindOf(joinPath(at, "package.json")) === "file"
|
|
189
|
+
) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
174
196
|
/**
|
|
175
197
|
* The rooted files one manifest target reads back to by name: its path below the
|
|
176
198
|
* manifest's directory, past its first directory where it names one, without its output
|
|
177
|
-
* extension, is the name, and a rooted file
|
|
178
|
-
* without its source extension, ends with that name at a
|
|
199
|
+
* extension, is the name, and a rooted file of the manifest's own package is one whose
|
|
200
|
+
* path below the same directory, without its source extension, ends with that name at a
|
|
201
|
+
* directory boundary.
|
|
179
202
|
*/
|
|
180
203
|
function readBackByName(
|
|
181
204
|
host: Host,
|
|
@@ -195,13 +218,52 @@ function readBackByName(
|
|
|
195
218
|
const found = new Map<string, SourceFile>();
|
|
196
219
|
for (const file of rooted) {
|
|
197
220
|
const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
|
|
198
|
-
if (
|
|
221
|
+
if (
|
|
222
|
+
path !== undefined &&
|
|
223
|
+
(path === name || path.endsWith(`/${name}`)) &&
|
|
224
|
+
ofPackage(host, dir, file.fileName)
|
|
225
|
+
) {
|
|
199
226
|
found.set(file.fileName, file);
|
|
200
227
|
}
|
|
201
228
|
}
|
|
202
229
|
return [...found.values()];
|
|
203
230
|
}
|
|
204
231
|
|
|
232
|
+
/**
|
|
233
|
+
* The program files a manifest target no program holds reaches: the target is a file
|
|
234
|
+
* read as a configuration file outside the program is, and each file of the chain it
|
|
235
|
+
* starts loads the program files its relative specifiers name, read through the emit
|
|
236
|
+
* mapping, as a command's shim loads the compiled entry it runs.
|
|
237
|
+
*/
|
|
238
|
+
function throughOutside(
|
|
239
|
+
options: RootOptions,
|
|
240
|
+
targetRoot: string,
|
|
241
|
+
files: SourceFiles,
|
|
242
|
+
entry: ManifestEntry,
|
|
243
|
+
): readonly SourceFile[] {
|
|
244
|
+
const cwd = options.host.workingDirectory();
|
|
245
|
+
const path = resolvePath(cwd, entry.path);
|
|
246
|
+
if (
|
|
247
|
+
options.parse === undefined ||
|
|
248
|
+
options.runFiles?.has(path) === true ||
|
|
249
|
+
options.host.kindOf(path) !== "file"
|
|
250
|
+
) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
const chain = outsideChain(options.host, targetRoot, files, options.parse, [
|
|
254
|
+
{ path, manifestDir: resolvePath(cwd, entry.dir) },
|
|
255
|
+
]);
|
|
256
|
+
const found = new Map<string, SourceFile>();
|
|
257
|
+
for (const { file } of chain) {
|
|
258
|
+
for (const specifier of relativeSpecifiers(file)) {
|
|
259
|
+
for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
|
|
260
|
+
found.set(named.fileName, named);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return [...found.values()];
|
|
265
|
+
}
|
|
266
|
+
|
|
205
267
|
/** Whether a configured string carries either special character. */
|
|
206
268
|
function isPattern(text: string): boolean {
|
|
207
269
|
return text.includes("*") || text.includes("?");
|
|
@@ -307,9 +369,21 @@ export function roots<Brand>(
|
|
|
307
369
|
found.set([id, kind, source].join("\u0000"), { id, kind, source });
|
|
308
370
|
};
|
|
309
371
|
|
|
310
|
-
const entered: {
|
|
311
|
-
|
|
312
|
-
|
|
372
|
+
const entered: {
|
|
373
|
+
file: SourceFile;
|
|
374
|
+
kind: RootKind;
|
|
375
|
+
source: string;
|
|
376
|
+
exports: boolean;
|
|
377
|
+
names?: readonly string[];
|
|
378
|
+
}[] = [];
|
|
379
|
+
const enter = (
|
|
380
|
+
file: SourceFile,
|
|
381
|
+
kind: RootKind,
|
|
382
|
+
source: string,
|
|
383
|
+
exports: boolean,
|
|
384
|
+
names?: readonly string[],
|
|
385
|
+
): void => {
|
|
386
|
+
entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
|
|
313
387
|
};
|
|
314
388
|
|
|
315
389
|
const points = entryPoints(
|
|
@@ -344,11 +418,28 @@ export function roots<Brand>(
|
|
|
344
418
|
...scripted,
|
|
345
419
|
...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
|
|
346
420
|
];
|
|
421
|
+
const entryFilesOf = new Map<ManifestEntry, readonly SourceFile[]>();
|
|
347
422
|
const entryFiles = (entry: ManifestEntry): readonly SourceFile[] => {
|
|
423
|
+
const known = entryFilesOf.get(entry);
|
|
424
|
+
if (known !== undefined) {
|
|
425
|
+
return known;
|
|
426
|
+
}
|
|
427
|
+
const found = readEntryFiles(entry);
|
|
428
|
+
entryFilesOf.set(entry, found);
|
|
429
|
+
return found;
|
|
430
|
+
};
|
|
431
|
+
const readEntryFiles = (entry: ManifestEntry): readonly SourceFile[] => {
|
|
348
432
|
const named = files.named(entry.path);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
433
|
+
if (named.length > 0 || entry.role === "script") {
|
|
434
|
+
return named;
|
|
435
|
+
}
|
|
436
|
+
const cwd = options.host.workingDirectory();
|
|
437
|
+
const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
|
|
438
|
+
if (shipped.length > 0) {
|
|
439
|
+
return shipped;
|
|
440
|
+
}
|
|
441
|
+
const byName = readBackByName(options.host, entry, readBackTo);
|
|
442
|
+
return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
|
|
352
443
|
};
|
|
353
444
|
for (const { entry, publishes } of entries) {
|
|
354
445
|
if (publishes) {
|
|
@@ -356,27 +447,41 @@ export function roots<Brand>(
|
|
|
356
447
|
}
|
|
357
448
|
}
|
|
358
449
|
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
359
|
-
|
|
360
|
-
for (const file of entryFiles(entry)) {
|
|
361
|
-
const kind = ENTRY_KINDS[entry.role];
|
|
362
|
-
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
363
|
-
}
|
|
364
|
-
}
|
|
450
|
+
const patterned: { file: SourceFile; pattern: string }[] = [];
|
|
365
451
|
for (const pattern of options.entryFiles) {
|
|
366
452
|
const expression = globExpression(pattern);
|
|
367
453
|
for (const [path, file] of files.byPath) {
|
|
368
454
|
if (expression.test(path)) {
|
|
369
|
-
|
|
455
|
+
patterned.push({ file, pattern });
|
|
370
456
|
}
|
|
371
457
|
}
|
|
372
458
|
}
|
|
459
|
+
const unmapped: ManifestEntry[] = [];
|
|
460
|
+
const mapped = patterned.map(({ file }) => file);
|
|
461
|
+
for (const { entry } of entries) {
|
|
462
|
+
const named = entryFiles(entry);
|
|
463
|
+
if (named.length === 0 && entry.role !== "script") {
|
|
464
|
+
unmapped.push(entry);
|
|
465
|
+
}
|
|
466
|
+
mapped.push(...named);
|
|
467
|
+
for (const file of named) {
|
|
468
|
+
const kind = ENTRY_KINDS[entry.role];
|
|
469
|
+
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const unmappedEntries = unmapped.filter(
|
|
473
|
+
(entry) => readBackByName(options.host, entry, mapped).length === 0,
|
|
474
|
+
);
|
|
475
|
+
for (const { file, pattern } of patterned) {
|
|
476
|
+
enter(file, "entry-file", pattern, true);
|
|
477
|
+
}
|
|
373
478
|
for (const { file, row } of options.conventions === undefined
|
|
374
479
|
? []
|
|
375
480
|
: conventionEntries(options.conventions, files)) {
|
|
376
481
|
enter(file, "convention", row, true);
|
|
377
482
|
}
|
|
378
483
|
for (const point of points) {
|
|
379
|
-
enter(point.file, point.rule, point.source, point.exports);
|
|
484
|
+
enter(point.file, point.rule, point.source, point.exports, point.names);
|
|
380
485
|
}
|
|
381
486
|
for (const file of files.byPath.values()) {
|
|
382
487
|
for (const alias of typeQueryAliases(file)) {
|
|
@@ -390,7 +495,11 @@ export function roots<Brand>(
|
|
|
390
495
|
}
|
|
391
496
|
}
|
|
392
497
|
|
|
393
|
-
const {
|
|
498
|
+
const {
|
|
499
|
+
tables: exportsOf,
|
|
500
|
+
named,
|
|
501
|
+
answered,
|
|
502
|
+
} = exportTables(project, held, [
|
|
394
503
|
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
395
504
|
...published,
|
|
396
505
|
]);
|
|
@@ -401,8 +510,13 @@ export function roots<Brand>(
|
|
|
401
510
|
}
|
|
402
511
|
add(id, entry.kind, entry.source);
|
|
403
512
|
if (entry.exports) {
|
|
404
|
-
|
|
405
|
-
|
|
513
|
+
const byName = named.get(entry.file.fileName);
|
|
514
|
+
const exported =
|
|
515
|
+
entry.names === undefined || byName === undefined
|
|
516
|
+
? (exportsOf.get(entry.file.fileName) ?? [])
|
|
517
|
+
: entry.names.flatMap((name) => byName.get(name) ?? []);
|
|
518
|
+
for (const one of exported) {
|
|
519
|
+
add(one, entry.kind, entry.source);
|
|
406
520
|
}
|
|
407
521
|
}
|
|
408
522
|
}
|
|
@@ -433,6 +547,7 @@ export function roots<Brand>(
|
|
|
433
547
|
liveUnderReachability: ordered,
|
|
434
548
|
unmatched,
|
|
435
549
|
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
|
550
|
+
unmappedEntries,
|
|
436
551
|
};
|
|
437
552
|
}
|
|
438
553
|
|
|
@@ -448,12 +563,18 @@ function exportTables<Brand>(
|
|
|
448
563
|
project: ProjectView<Brand>,
|
|
449
564
|
held: Inventory,
|
|
450
565
|
wanted: readonly SourceFile[],
|
|
451
|
-
): {
|
|
566
|
+
): {
|
|
567
|
+
readonly tables: ReadonlyMap<string, readonly string[]>;
|
|
568
|
+
/** Per file whose table the checker answered, the declarations each export name reaches. */
|
|
569
|
+
readonly named: ReadonlyMap<string, ReadonlyMap<string, readonly string[]>>;
|
|
570
|
+
readonly answered: boolean;
|
|
571
|
+
} {
|
|
452
572
|
const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
|
|
453
573
|
const tables = new Map<string, readonly string[]>();
|
|
574
|
+
const named = new Map<string, Map<string, string[]>>();
|
|
454
575
|
let answered = true;
|
|
455
576
|
if (unique.length === 0) {
|
|
456
|
-
return { tables, answered };
|
|
577
|
+
return { tables, named, answered };
|
|
457
578
|
}
|
|
458
579
|
const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
|
|
459
580
|
const chains = aliasChains(project, held);
|
|
@@ -475,15 +596,18 @@ function exportTables<Brand>(
|
|
|
475
596
|
return;
|
|
476
597
|
}
|
|
477
598
|
const ids = new Set<string>();
|
|
599
|
+
const byName = new Map<string, string[]>();
|
|
478
600
|
for (const exported of table) {
|
|
479
601
|
for (const target of chains.chainOf(exported)) {
|
|
480
602
|
answered &&= !target.guessed;
|
|
481
603
|
ids.add(target.id);
|
|
604
|
+
byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
|
|
482
605
|
}
|
|
483
606
|
}
|
|
484
607
|
tables.set(file.fileName, [...ids]);
|
|
608
|
+
named.set(file.fileName, byName);
|
|
485
609
|
});
|
|
486
|
-
return { tables, answered };
|
|
610
|
+
return { tables, named, answered };
|
|
487
611
|
}
|
|
488
612
|
|
|
489
613
|
/** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
|
|
@@ -553,11 +677,13 @@ function publishedAPI<Brand>(
|
|
|
553
677
|
const nodes = declarationNodes(held, byPath);
|
|
554
678
|
const chains = aliasChains(project, held);
|
|
555
679
|
const reached = new Set<string>();
|
|
680
|
+
const entered = new Set<string>();
|
|
556
681
|
const guessed = new Set<string>();
|
|
557
682
|
const pending: string[] = [];
|
|
558
683
|
const reach = (id: string): void => {
|
|
559
|
-
|
|
560
|
-
|
|
684
|
+
const file = byId.get(id)?.kind === "file";
|
|
685
|
+
if (!(file ? entered : reached).has(id)) {
|
|
686
|
+
(file ? entered : reached).add(id);
|
|
561
687
|
pending.push(id);
|
|
562
688
|
}
|
|
563
689
|
};
|
|
@@ -566,11 +692,7 @@ function publishedAPI<Brand>(
|
|
|
566
692
|
}
|
|
567
693
|
for (const file of published) {
|
|
568
694
|
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
569
|
-
|
|
570
|
-
pending.push(id);
|
|
571
|
-
} else {
|
|
572
|
-
reach(id);
|
|
573
|
-
}
|
|
695
|
+
reach(id);
|
|
574
696
|
}
|
|
575
697
|
}
|
|
576
698
|
while (pending.length > 0) {
|
|
@@ -595,8 +717,7 @@ function publishedAPI<Brand>(
|
|
|
595
717
|
project,
|
|
596
718
|
round.flatMap((id) => {
|
|
597
719
|
const symbol = byId.get(id);
|
|
598
|
-
|
|
599
|
-
return node === undefined ? [] : [node];
|
|
720
|
+
return symbol === undefined ? [] : nodes.nodesOf(symbol);
|
|
600
721
|
}),
|
|
601
722
|
)) {
|
|
602
723
|
for (const target of chains.chainOf(symbol)) {
|
|
@@ -625,24 +746,22 @@ function declarationNodes(
|
|
|
625
746
|
byPath: ReadonlyMap<string, SourceFile>,
|
|
626
747
|
): {
|
|
627
748
|
typeNamesOf(symbol: InventorySymbol): readonly Node[];
|
|
628
|
-
|
|
749
|
+
nodesOf(symbol: InventorySymbol): readonly Node[];
|
|
629
750
|
} {
|
|
630
|
-
const walked = new Map<string, { byId: Map<string, Node>; nodes: Set<Node> }>();
|
|
631
|
-
const nodesIn = (path: string): { byId: Map<string, Node>; nodes: Set<Node> } => {
|
|
751
|
+
const walked = new Map<string, { byId: Map<string, Node[]>; nodes: Set<Node> }>();
|
|
752
|
+
const nodesIn = (path: string): { byId: Map<string, Node[]>; nodes: Set<Node> } => {
|
|
632
753
|
const known = walked.get(path);
|
|
633
754
|
if (known !== undefined) {
|
|
634
755
|
return known;
|
|
635
756
|
}
|
|
636
|
-
const found = { byId: new Map<string, Node>(), nodes: new Set<Node>() };
|
|
757
|
+
const found = { byId: new Map<string, Node[]>(), nodes: new Set<Node>() };
|
|
637
758
|
const file = byPath.get(path);
|
|
638
759
|
if (file !== undefined) {
|
|
639
760
|
const visit = (node: Node): void => {
|
|
640
761
|
const id = held.declarations.get(nodeKey(file, node));
|
|
641
762
|
if (id !== undefined) {
|
|
642
763
|
found.nodes.add(node);
|
|
643
|
-
|
|
644
|
-
found.byId.set(id, node);
|
|
645
|
-
}
|
|
764
|
+
found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
|
|
646
765
|
}
|
|
647
766
|
node.forEachChild(visit);
|
|
648
767
|
};
|
|
@@ -654,10 +773,9 @@ function declarationNodes(
|
|
|
654
773
|
return {
|
|
655
774
|
typeNamesOf: (symbol) => {
|
|
656
775
|
const { byId, nodes } = nodesIn(symbol.position.path);
|
|
657
|
-
|
|
658
|
-
return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
|
|
776
|
+
return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
|
|
659
777
|
},
|
|
660
|
-
|
|
778
|
+
nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
|
|
661
779
|
};
|
|
662
780
|
}
|
|
663
781
|
|