@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/src/references.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { byPosition, isComponentFile, renderPosition, type Position } from "./po
|
|
|
51
51
|
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
52
52
|
import type { ProjectView } from "./session.ts";
|
|
53
53
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
54
|
+
import { computedKeyNames, keyNameOf, keyedUses, type KeyedAccess } from "./symbol-keys.ts";
|
|
54
55
|
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -77,17 +78,17 @@ import { handedOn, valuesOf } from "./value-flow.ts";
|
|
|
77
78
|
* `decorator` names the declaration a decorator expression is attached to, which the
|
|
78
79
|
* decorator receives when the class is defined.
|
|
79
80
|
*/
|
|
80
|
-
|
|
81
|
+
type Use = "read" | "write" | "evaluation" | "decorator";
|
|
81
82
|
|
|
82
83
|
/**
|
|
83
|
-
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`,
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* member
|
|
84
|
+
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`, the
|
|
85
|
+
* per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step along an
|
|
86
|
+
* import or export chain; `syntax`, the tree alone (a decorator's target, a component's markup);
|
|
87
|
+
* `destructured`, the property a pattern names; `contextual`, the member a literal property writes
|
|
88
|
+
* or a value's position reads; `override`, the override a base call dispatches to; `keyed`, the
|
|
89
|
+
* member a symbol key selects; `by-name`, each declaration so spelled, where the checker failed.
|
|
89
90
|
*/
|
|
90
|
-
|
|
91
|
+
type Resolution =
|
|
91
92
|
| "batch"
|
|
92
93
|
| "resolved-symbol"
|
|
93
94
|
| "shorthand"
|
|
@@ -96,6 +97,7 @@ export type Resolution =
|
|
|
96
97
|
| "destructured"
|
|
97
98
|
| "contextual"
|
|
98
99
|
| "override"
|
|
100
|
+
| "keyed"
|
|
99
101
|
| "by-name";
|
|
100
102
|
|
|
101
103
|
/** One use of one declaration by one declaration. */
|
|
@@ -187,8 +189,6 @@ export interface ReferenceCost {
|
|
|
187
189
|
|
|
188
190
|
/** One project's references, in position order, and what reading them cost. */
|
|
189
191
|
export interface References {
|
|
190
|
-
/** The compiler configuration the project was opened from. */
|
|
191
|
-
readonly configFile: string;
|
|
192
192
|
readonly references: readonly Reference[];
|
|
193
193
|
/**
|
|
194
194
|
* Every file the rules classified as a test file, by its path below the target root,
|
|
@@ -212,6 +212,8 @@ export interface ReferenceOptions {
|
|
|
212
212
|
* Each pattern is a rule of its own, which {@link testFileRulesOf} counts.
|
|
213
213
|
*/
|
|
214
214
|
readonly testFiles: readonly string[];
|
|
215
|
+
/** The files, absolute, that are test files beside those the patterns name. */
|
|
216
|
+
readonly testFileNames?: ReadonlySet<string>;
|
|
215
217
|
/**
|
|
216
218
|
* The consumer whose files the pass walks instead of the target's. Its files are the
|
|
217
219
|
* program's own files below its root and not below the target root, each position is
|
|
@@ -297,12 +299,11 @@ function unparenthesized(node: Node): Node {
|
|
|
297
299
|
* Records the name one store writes into.
|
|
298
300
|
*
|
|
299
301
|
* A property access writes the member it names. An element access writes the collection
|
|
300
|
-
* it stores into,
|
|
301
|
-
*
|
|
302
|
-
* writes each name it binds, through
|
|
303
|
-
* in.
|
|
302
|
+
* it stores into, since a collection a module only stores into holds nothing anything
|
|
303
|
+
* reads, and joins `accesses` for the member a unique-symbol key selects. A destructuring
|
|
304
|
+
* target writes each name it binds, through its spreads and nested patterns.
|
|
304
305
|
*/
|
|
305
|
-
function markWrite(target: Node, writes: Set<number>): void {
|
|
306
|
+
function markWrite(target: Node, writes: Set<number>, accesses: Set<Node>): void {
|
|
306
307
|
const held = unparenthesized(target);
|
|
307
308
|
if (isName(held)) {
|
|
308
309
|
writes.add(held.pos);
|
|
@@ -315,37 +316,38 @@ function markWrite(target: Node, writes: Set<number>): void {
|
|
|
315
316
|
return;
|
|
316
317
|
}
|
|
317
318
|
if (isElementAccessExpression(held)) {
|
|
319
|
+
accesses.add(held);
|
|
318
320
|
// A store through a member's value lands in storage every holder of the value
|
|
319
321
|
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
320
322
|
if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
|
|
321
|
-
markWrite(held.expression, writes);
|
|
323
|
+
markWrite(held.expression, writes, accesses);
|
|
322
324
|
}
|
|
323
325
|
return;
|
|
324
326
|
}
|
|
325
327
|
if (isArrayLiteralExpression(held)) {
|
|
326
328
|
for (const element of held.elements) {
|
|
327
|
-
markWrite(element, writes);
|
|
329
|
+
markWrite(element, writes, accesses);
|
|
328
330
|
}
|
|
329
331
|
return;
|
|
330
332
|
}
|
|
331
333
|
if (isObjectLiteralExpression(held)) {
|
|
332
334
|
for (const property of held.properties) {
|
|
333
335
|
if (isPropertyAssignment(property)) {
|
|
334
|
-
markWrite(property.initializer, writes);
|
|
336
|
+
markWrite(property.initializer, writes, accesses);
|
|
335
337
|
continue;
|
|
336
338
|
}
|
|
337
339
|
if (isShorthandPropertyAssignment(property)) {
|
|
338
|
-
markWrite(property.name, writes);
|
|
340
|
+
markWrite(property.name, writes, accesses);
|
|
339
341
|
continue;
|
|
340
342
|
}
|
|
341
343
|
if (isSpreadAssignment(property)) {
|
|
342
|
-
markWrite(property.expression, writes);
|
|
344
|
+
markWrite(property.expression, writes, accesses);
|
|
343
345
|
}
|
|
344
346
|
}
|
|
345
347
|
return;
|
|
346
348
|
}
|
|
347
349
|
if (isSpreadElement(held)) {
|
|
348
|
-
markWrite(held.expression, writes);
|
|
350
|
+
markWrite(held.expression, writes, accesses);
|
|
349
351
|
}
|
|
350
352
|
}
|
|
351
353
|
|
|
@@ -356,16 +358,21 @@ function markWrite(target: Node, writes: Set<number>): void {
|
|
|
356
358
|
* holds. A store that reads its target as well records it in `reads`: a logical
|
|
357
359
|
* assignment, and a compound assignment, an increment or a decrement whose value is used.
|
|
358
360
|
*/
|
|
359
|
-
function markStores(
|
|
361
|
+
function markStores(
|
|
362
|
+
node: Node,
|
|
363
|
+
writes: Set<number>,
|
|
364
|
+
reads: Set<number>,
|
|
365
|
+
accesses: { readonly written: Set<Node>; readonly read: Set<Node> },
|
|
366
|
+
): void {
|
|
360
367
|
if (isBinaryExpression(node)) {
|
|
361
368
|
const operator = node.operatorToken.kind;
|
|
362
369
|
if (ASSIGNMENTS.has(operator)) {
|
|
363
|
-
markWrite(node.left, writes);
|
|
370
|
+
markWrite(node.left, writes, accesses.written);
|
|
364
371
|
if (
|
|
365
372
|
LOGICAL_ASSIGNMENTS.has(operator) ||
|
|
366
373
|
(operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))
|
|
367
374
|
) {
|
|
368
|
-
markWrite(node.left, reads);
|
|
375
|
+
markWrite(node.left, reads, accesses.read);
|
|
369
376
|
}
|
|
370
377
|
}
|
|
371
378
|
return;
|
|
@@ -375,20 +382,20 @@ function markStores(node: Node, writes: Set<number>, reads: Set<number>): void {
|
|
|
375
382
|
node.operator === SyntaxKind.PlusPlusToken ||
|
|
376
383
|
node.operator === SyntaxKind.MinusMinusToken
|
|
377
384
|
) {
|
|
378
|
-
markWrite(node.operand, writes);
|
|
385
|
+
markWrite(node.operand, writes, accesses.written);
|
|
379
386
|
if (!valueDiscarded(node)) {
|
|
380
|
-
markWrite(node.operand, reads);
|
|
387
|
+
markWrite(node.operand, reads, accesses.read);
|
|
381
388
|
}
|
|
382
389
|
}
|
|
383
390
|
return;
|
|
384
391
|
}
|
|
385
392
|
if (isDeleteExpression(node)) {
|
|
386
|
-
markWrite(node.expression, writes);
|
|
393
|
+
markWrite(node.expression, writes, accesses.written);
|
|
387
394
|
return;
|
|
388
395
|
}
|
|
389
396
|
if (isForInStatement(node) || isForOfStatement(node)) {
|
|
390
397
|
if (node.initializer.kind !== SyntaxKind.VariableDeclarationList) {
|
|
391
|
-
markWrite(node.initializer, writes);
|
|
398
|
+
markWrite(node.initializer, writes, accesses.written);
|
|
392
399
|
}
|
|
393
400
|
}
|
|
394
401
|
}
|
|
@@ -475,6 +482,7 @@ interface FileSites {
|
|
|
475
482
|
readonly wholes: readonly WholeRead[];
|
|
476
483
|
readonly urls: readonly ModuleURL[];
|
|
477
484
|
readonly decorated: readonly Decorated[];
|
|
485
|
+
readonly keyed: readonly KeyedAccess[];
|
|
478
486
|
}
|
|
479
487
|
|
|
480
488
|
/** Whether one node is `import.meta.url`. */
|
|
@@ -667,6 +675,8 @@ function sitesOf(
|
|
|
667
675
|
const flows: FlowAt[] = [];
|
|
668
676
|
const writes = new Set<number>();
|
|
669
677
|
const alsoRead = new Set<number>();
|
|
678
|
+
const accesses = { written: new Set<Node>(), read: new Set<Node>() };
|
|
679
|
+
const keyed: KeyedAccess[] = [];
|
|
670
680
|
const declared = new Set<number>();
|
|
671
681
|
const shorthands = new Map<number, Node>();
|
|
672
682
|
let enclosing = fileId;
|
|
@@ -712,7 +722,17 @@ function sitesOf(
|
|
|
712
722
|
return;
|
|
713
723
|
}
|
|
714
724
|
markNames(node);
|
|
715
|
-
markStores(node, writes, alsoRead);
|
|
725
|
+
markStores(node, writes, alsoRead, accesses);
|
|
726
|
+
const key = isElementAccessExpression(node) ? keyNameOf(node.argumentExpression) : undefined;
|
|
727
|
+
if (key !== undefined && isElementAccessExpression(node)) {
|
|
728
|
+
const written = accesses.written.has(node);
|
|
729
|
+
keyed.push({
|
|
730
|
+
access: node,
|
|
731
|
+
key,
|
|
732
|
+
from: enclosing,
|
|
733
|
+
use: written ? (accesses.read.has(node) ? "both" : "write") : "read",
|
|
734
|
+
});
|
|
735
|
+
}
|
|
716
736
|
if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
|
|
717
737
|
literals.push({ literal: node, from: enclosing });
|
|
718
738
|
}
|
|
@@ -747,7 +767,18 @@ function sitesOf(
|
|
|
747
767
|
};
|
|
748
768
|
|
|
749
769
|
file.forEachChild(visit);
|
|
750
|
-
return {
|
|
770
|
+
return {
|
|
771
|
+
uses: found,
|
|
772
|
+
literals,
|
|
773
|
+
flows,
|
|
774
|
+
reads,
|
|
775
|
+
links,
|
|
776
|
+
evaluations,
|
|
777
|
+
wholes,
|
|
778
|
+
urls,
|
|
779
|
+
decorated,
|
|
780
|
+
keyed,
|
|
781
|
+
};
|
|
751
782
|
}
|
|
752
783
|
|
|
753
784
|
/** The declaration kinds of a property an object literal declares. */
|
|
@@ -1194,7 +1225,9 @@ export function references<Brand>(
|
|
|
1194
1225
|
const testFile = new Map<SourceFile, boolean>();
|
|
1195
1226
|
for (const { file } of walked) {
|
|
1196
1227
|
const path = renderPosition(file, root, 0).path;
|
|
1197
|
-
const test =
|
|
1228
|
+
const test =
|
|
1229
|
+
patterns.some((pattern) => pattern.test(path)) ||
|
|
1230
|
+
options.testFileNames?.has(file.fileName) === true;
|
|
1198
1231
|
testFile.set(file, test);
|
|
1199
1232
|
if (test) {
|
|
1200
1233
|
tests.push(path);
|
|
@@ -1230,7 +1263,19 @@ export function references<Brand>(
|
|
|
1230
1263
|
}
|
|
1231
1264
|
}
|
|
1232
1265
|
|
|
1233
|
-
|
|
1266
|
+
const keyNames = computedKeyNames(held.symbols);
|
|
1267
|
+
for (const {
|
|
1268
|
+
file,
|
|
1269
|
+
fileId,
|
|
1270
|
+
uses,
|
|
1271
|
+
reads,
|
|
1272
|
+
links,
|
|
1273
|
+
evaluations,
|
|
1274
|
+
wholes,
|
|
1275
|
+
urls,
|
|
1276
|
+
decorated,
|
|
1277
|
+
keyed,
|
|
1278
|
+
} of walked) {
|
|
1234
1279
|
const test = testFile.get(file) === true;
|
|
1235
1280
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
1236
1281
|
const markup = isComponentFile(file);
|
|
@@ -1449,6 +1494,22 @@ export function references<Brand>(
|
|
|
1449
1494
|
}
|
|
1450
1495
|
});
|
|
1451
1496
|
|
|
1497
|
+
const resolvedKeys = keyed.flatMap((access) => {
|
|
1498
|
+
const symbol =
|
|
1499
|
+
isIdentifier(access.key) && keyNames.has(access.key.text)
|
|
1500
|
+
? answered.get(access.key)
|
|
1501
|
+
: undefined;
|
|
1502
|
+
return typeof symbol === "object" ? [{ access, symbol }] : [];
|
|
1503
|
+
});
|
|
1504
|
+
for (const { access, ids } of keyedUses(project, chains, resolvedKeys, tables, cap)) {
|
|
1505
|
+
const position = renderPosition(file, root, access.access.argumentExpression.getStart());
|
|
1506
|
+
for (const id of ids) {
|
|
1507
|
+
for (const use of access.use === "both" ? (["read", "write"] as const) : [access.use]) {
|
|
1508
|
+
found.push({ from: access.from, to: id, position, use, resolution: "keyed", test });
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1452
1513
|
for (const site of uses) {
|
|
1453
1514
|
const direct: Resolution =
|
|
1454
1515
|
site.shorthand !== undefined
|
|
@@ -1510,7 +1571,6 @@ export function references<Brand>(
|
|
|
1510
1571
|
);
|
|
1511
1572
|
|
|
1512
1573
|
return {
|
|
1513
|
-
configFile: project.configFile,
|
|
1514
1574
|
references:
|
|
1515
1575
|
consumer === undefined
|
|
1516
1576
|
? found
|
package/src/report.ts
CHANGED
|
@@ -30,14 +30,14 @@ export const ANALYZER_NAME = "deadset-ts";
|
|
|
30
30
|
/** The language this analyzer claims. */
|
|
31
31
|
export const LANGUAGE = "ts";
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface WirePosition {
|
|
34
34
|
readonly path: string;
|
|
35
35
|
readonly line: number;
|
|
36
36
|
readonly column: number;
|
|
37
37
|
readonly end_line: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
interface WireDetails {
|
|
41
41
|
readonly narrower_visibility?: string;
|
|
42
42
|
readonly implementations?: readonly {
|
|
43
43
|
readonly ref: string;
|
|
@@ -54,6 +54,7 @@ export interface WireDetails {
|
|
|
54
54
|
readonly reason?: string;
|
|
55
55
|
};
|
|
56
56
|
readonly overlap?: readonly string[];
|
|
57
|
+
readonly removes_last_use_of?: readonly string[];
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/** One finding as the finding schema spells it. */
|
|
@@ -93,7 +94,7 @@ export interface WireFinding {
|
|
|
93
94
|
readonly details: WireDetails;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
interface WireEdgeEvaluation {
|
|
97
98
|
readonly edge: string;
|
|
98
99
|
readonly side: string;
|
|
99
100
|
readonly symbol: string;
|
|
@@ -178,7 +179,7 @@ interface ReportConsumer {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
/** One consumer the scope declared and the run did not load, with why. */
|
|
181
|
-
|
|
182
|
+
interface UnavailableConsumer {
|
|
182
183
|
readonly id: string;
|
|
183
184
|
readonly reason: string;
|
|
184
185
|
}
|
|
@@ -297,6 +298,9 @@ function wireDetails(details: FindingDetails): WireDetails {
|
|
|
297
298
|
},
|
|
298
299
|
}),
|
|
299
300
|
...(details.overlap === undefined ? {} : { overlap: details.overlap }),
|
|
301
|
+
...(details.removesLastUseOf === undefined
|
|
302
|
+
? {}
|
|
303
|
+
: { removes_last_use_of: details.removesLastUseOf }),
|
|
300
304
|
};
|
|
301
305
|
}
|
|
302
306
|
|
package/src/roots.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
isQualifiedName,
|
|
16
16
|
isTypeQueryNode,
|
|
17
17
|
isTypeReferenceNode,
|
|
18
|
+
isPropertyDeclaration,
|
|
19
|
+
isVariableDeclaration,
|
|
18
20
|
isVariableStatement,
|
|
19
21
|
type Node,
|
|
20
22
|
type SourceFile,
|
|
@@ -31,6 +33,7 @@ import { relativePath, resolvePath } from "./paths.ts";
|
|
|
31
33
|
import { byPosition } from "./position.ts";
|
|
32
34
|
import { UNANSWERED } from "./query.ts";
|
|
33
35
|
import type { ProjectView } from "./session.ts";
|
|
36
|
+
import type { Symbol as TSSymbol, Type } from "@typescript/native/unstable/sync";
|
|
34
37
|
import { sourceFilesOf } from "./source-files.ts";
|
|
35
38
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
36
39
|
|
|
@@ -78,8 +81,6 @@ export interface Root {
|
|
|
78
81
|
|
|
79
82
|
/** One project's roots, and every configured string that named nothing in it. */
|
|
80
83
|
export interface Roots {
|
|
81
|
-
/** The compiler configuration the project was opened from. */
|
|
82
|
-
readonly configFile: string;
|
|
83
84
|
/**
|
|
84
85
|
* Every root of this project, ordered by the site of the declaration it names,
|
|
85
86
|
* then by kind, then by the string that named it. A declaration that is a root for
|
|
@@ -128,6 +129,26 @@ export interface RootOptions {
|
|
|
128
129
|
readonly parse?: ParseFile | undefined;
|
|
129
130
|
/** Whether the target is a library, so a consumer outside it reaches its published API. */
|
|
130
131
|
readonly publishedAPI: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* The directories of the workspace members whose manifest is not private, each a library
|
|
134
|
+
* inside the target whose entries are published API whatever the target's kind.
|
|
135
|
+
*/
|
|
136
|
+
readonly publishedMembers?: ReadonlySet<string> | undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Whether one declaration is written below the directory of a published workspace member. */
|
|
140
|
+
function inPublishedMember(
|
|
141
|
+
held: Inventory,
|
|
142
|
+
id: string,
|
|
143
|
+
root: string,
|
|
144
|
+
members: ReadonlySet<string> | undefined,
|
|
145
|
+
): boolean {
|
|
146
|
+
const path = held.symbols.find((symbol) => symbol.id === id)?.position.path;
|
|
147
|
+
if (path === undefined || members === undefined) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const file = resolvePath(root, path);
|
|
151
|
+
return [...members].some((dir) => file.startsWith(`${dir}/`));
|
|
131
152
|
}
|
|
132
153
|
|
|
133
154
|
/** The kind of root each role of a manifest entry makes. */
|
|
@@ -305,10 +326,16 @@ export function roots<Brand>(
|
|
|
305
326
|
...options.manifest.entries.map((entry) => ({
|
|
306
327
|
entry,
|
|
307
328
|
publishes:
|
|
329
|
+
options.publishedAPI &&
|
|
308
330
|
entry.published &&
|
|
309
331
|
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
310
332
|
})),
|
|
311
|
-
...(options.otherPackages ?? []).map((entry) => ({
|
|
333
|
+
...(options.otherPackages ?? []).map((entry) => ({
|
|
334
|
+
entry,
|
|
335
|
+
publishes:
|
|
336
|
+
entry.published &&
|
|
337
|
+
(options.publishedAPI || options.publishedMembers?.has(entry.dir) === true),
|
|
338
|
+
})),
|
|
312
339
|
];
|
|
313
340
|
const scripted = entries.flatMap(({ entry }) =>
|
|
314
341
|
entry.role === "script" ? files.named(entry.path) : [],
|
|
@@ -324,7 +351,7 @@ export function roots<Brand>(
|
|
|
324
351
|
: readBackByName(options.host, entry, readBackTo);
|
|
325
352
|
};
|
|
326
353
|
for (const { entry, publishes } of entries) {
|
|
327
|
-
if (
|
|
354
|
+
if (publishes) {
|
|
328
355
|
published.push(...entryFiles(entry));
|
|
329
356
|
}
|
|
330
357
|
}
|
|
@@ -380,9 +407,14 @@ export function roots<Brand>(
|
|
|
380
407
|
}
|
|
381
408
|
}
|
|
382
409
|
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
410
|
+
const tagged = releaseTagged(held, files).filter(
|
|
411
|
+
(id) =>
|
|
412
|
+
options.publishedAPI || inPublishedMember(held, id, targetRoot, options.publishedMembers),
|
|
413
|
+
);
|
|
414
|
+
const api =
|
|
415
|
+
published.length > 0 || tagged.length > 0
|
|
416
|
+
? publishedAPI(project, published, exportsOf, held, files.byPath, tagged)
|
|
417
|
+
: { ids: [], guessed: [] };
|
|
386
418
|
for (const id of api.ids) {
|
|
387
419
|
add(id, "published-api", "");
|
|
388
420
|
}
|
|
@@ -398,7 +430,6 @@ export function roots<Brand>(
|
|
|
398
430
|
});
|
|
399
431
|
|
|
400
432
|
return {
|
|
401
|
-
configFile: project.configFile,
|
|
402
433
|
liveUnderReachability: ordered,
|
|
403
434
|
unmatched,
|
|
404
435
|
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
|
@@ -560,6 +591,18 @@ function publishedAPI<Brand>(
|
|
|
560
591
|
const symbol = byId.get(id);
|
|
561
592
|
return symbol === undefined || symbol.kind === "file" ? [] : nodes.typeNamesOf(symbol);
|
|
562
593
|
});
|
|
594
|
+
for (const symbol of inferredTypeSymbols(
|
|
595
|
+
project,
|
|
596
|
+
round.flatMap((id) => {
|
|
597
|
+
const symbol = byId.get(id);
|
|
598
|
+
const node = symbol === undefined ? undefined : nodes.nodeOf(symbol);
|
|
599
|
+
return node === undefined ? [] : [node];
|
|
600
|
+
}),
|
|
601
|
+
)) {
|
|
602
|
+
for (const target of chains.chainOf(symbol)) {
|
|
603
|
+
reach(target.id);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
563
606
|
project.symbolsAt(named.map((node) => project.handle(node))).forEach((symbol, index) => {
|
|
564
607
|
if (symbol === UNANSWERED) {
|
|
565
608
|
for (const id of held.byName.get(named[index]?.getText() ?? "") ?? []) {
|
|
@@ -580,7 +623,10 @@ function publishedAPI<Brand>(
|
|
|
580
623
|
function declarationNodes(
|
|
581
624
|
held: Inventory,
|
|
582
625
|
byPath: ReadonlyMap<string, SourceFile>,
|
|
583
|
-
): {
|
|
626
|
+
): {
|
|
627
|
+
typeNamesOf(symbol: InventorySymbol): readonly Node[];
|
|
628
|
+
nodeOf(symbol: InventorySymbol): Node | undefined;
|
|
629
|
+
} {
|
|
584
630
|
const walked = new Map<string, { byId: Map<string, Node>; nodes: Set<Node> }>();
|
|
585
631
|
const nodesIn = (path: string): { byId: Map<string, Node>; nodes: Set<Node> } => {
|
|
586
632
|
const known = walked.get(path);
|
|
@@ -611,7 +657,81 @@ function declarationNodes(
|
|
|
611
657
|
const declaration = byId.get(symbol.id);
|
|
612
658
|
return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
|
|
613
659
|
},
|
|
660
|
+
nodeOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id),
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** How deep the type arguments and constituents of an inferred type are followed. */
|
|
665
|
+
const INFERRED_DEPTH = 4;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* The declarations of the types the compiler infers where a declaration writes none: the
|
|
669
|
+
* type of a variable or a property with no annotation, and the result of a function or a
|
|
670
|
+
* method with no result annotation, each with the types its type arguments and its union
|
|
671
|
+
* or intersection constituents name, to a fixed depth. A question the checker leaves
|
|
672
|
+
* unanswered names nothing.
|
|
673
|
+
*/
|
|
674
|
+
function inferredTypeSymbols<Brand>(
|
|
675
|
+
project: ProjectView<Brand>,
|
|
676
|
+
declarations: readonly Node[],
|
|
677
|
+
): readonly TSSymbol[] {
|
|
678
|
+
const { queries } = project;
|
|
679
|
+
const valued = declarations.flatMap((node) =>
|
|
680
|
+
(isVariableDeclaration(node) || isPropertyDeclaration(node)) &&
|
|
681
|
+
node.type === undefined &&
|
|
682
|
+
!isFunctionLikeDeclaration(node.initializer ?? node)
|
|
683
|
+
? [node.name]
|
|
684
|
+
: [],
|
|
685
|
+
);
|
|
686
|
+
const types: Type[] = [];
|
|
687
|
+
queries.typesAt(valued).forEach((type) => {
|
|
688
|
+
if (type !== UNANSWERED && type !== undefined) {
|
|
689
|
+
types.push(type);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
for (const node of declarations) {
|
|
693
|
+
const signature = isFunctionLikeDeclaration(node)
|
|
694
|
+
? node
|
|
695
|
+
: isVariableDeclaration(node) &&
|
|
696
|
+
node.type === undefined &&
|
|
697
|
+
node.initializer !== undefined &&
|
|
698
|
+
isFunctionLikeDeclaration(node.initializer)
|
|
699
|
+
? node.initializer
|
|
700
|
+
: undefined;
|
|
701
|
+
if (signature === undefined || signature.type !== undefined || signature.body === undefined) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const typed = queries.signatureOf(signature);
|
|
705
|
+
const result =
|
|
706
|
+
typed === UNANSWERED || typed === undefined ? undefined : queries.returnTypeOf(typed);
|
|
707
|
+
if (result !== UNANSWERED && result !== undefined) {
|
|
708
|
+
types.push(result);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const found = new Map<number, TSSymbol>();
|
|
712
|
+
const seen = new Set<Type>();
|
|
713
|
+
const visit = (type: Type, depth: number): void => {
|
|
714
|
+
if (depth > INFERRED_DEPTH || seen.has(type)) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
seen.add(type);
|
|
718
|
+
const symbol = queries.symbolOfType(type);
|
|
719
|
+
if (symbol !== UNANSWERED && symbol !== undefined) {
|
|
720
|
+
found.set(symbol.id, symbol);
|
|
721
|
+
}
|
|
722
|
+
const parts = queries.constituents(type);
|
|
723
|
+
const args = queries.typeArguments(type);
|
|
724
|
+
for (const next of [
|
|
725
|
+
...(parts === UNANSWERED || (parts.length === 1 && parts[0] === type) ? [] : parts),
|
|
726
|
+
...(args === UNANSWERED ? [] : args),
|
|
727
|
+
]) {
|
|
728
|
+
visit(next, depth + 1);
|
|
729
|
+
}
|
|
614
730
|
};
|
|
731
|
+
types.forEach((type) => {
|
|
732
|
+
visit(type, 0);
|
|
733
|
+
});
|
|
734
|
+
return [...found.values()];
|
|
615
735
|
}
|
|
616
736
|
|
|
617
737
|
/**
|
package/src/run.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConfigError, REPOSITORY_DOCUMENT, type Inputs } from "./config.ts";
|
|
2
2
|
import { DiscoveryError, renderDiagnostic } from "./discover.ts";
|
|
3
|
-
import { EMITTERS } from "./findings/emitters.ts";
|
|
4
3
|
import type { Host } from "./host.ts";
|
|
5
4
|
import { joinPath, resolvePath } from "./paths.ts";
|
|
6
5
|
import { readScope, ScopeError, scopeForDir, type Scope } from "./scope.ts";
|
|
@@ -264,7 +263,6 @@ function invoke(
|
|
|
264
263
|
});
|
|
265
264
|
return opened;
|
|
266
265
|
},
|
|
267
|
-
emitters: EMITTERS,
|
|
268
266
|
});
|
|
269
267
|
} catch (error: unknown) {
|
|
270
268
|
opened?.close();
|
package/src/schema.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { ConfigError } from "./config.ts";
|
|
|
15
15
|
* - `object` is one setting written as an object: its member names are declared
|
|
16
16
|
* and a source supplies the object whole.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
type KeyKind = "leaf" | "section" | "map" | "list" | "object";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* One node of the closed key list. `members` holds a section's declared members,
|
|
@@ -259,7 +259,7 @@ export function malformed(label: string, path: string, detail: string): ConfigEr
|
|
|
259
259
|
* A refusal of a key the closed key list does not declare, naming the nearest key
|
|
260
260
|
* it does.
|
|
261
261
|
*/
|
|
262
|
-
|
|
262
|
+
function unimplementedKey(label: string, path: string): ConfigError {
|
|
263
263
|
const nearest = nearestKey(path);
|
|
264
264
|
let hint = "";
|
|
265
265
|
if (nearest === path) {
|
package/src/scope.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
|
|
|
4
4
|
import { checkDocument, type KeyNode } from "./schema.ts";
|
|
5
5
|
|
|
6
6
|
/** The roles a scope document declares for a module. */
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const ROLE_TARGET = "target";
|
|
8
|
+
const ROLE_CONSUMER = "consumer";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A scope document above this length is refused rather than decoded. It names one
|
package/src/session.ts
CHANGED
|
@@ -347,7 +347,13 @@ function viewOf<Brand>(
|
|
|
347
347
|
if ((symbol.flags & SymbolFlags.Alias) === 0) {
|
|
348
348
|
return undefined;
|
|
349
349
|
}
|
|
350
|
-
|
|
350
|
+
// An alias the checker synthesizes declares nothing, the default a module that assigns
|
|
351
|
+
// its export gets under esModuleInterop, and the immediate step fails on it while the
|
|
352
|
+
// whole resolution answers.
|
|
353
|
+
const held =
|
|
354
|
+
symbol.declarations.length === 0
|
|
355
|
+
? queries.aliased(symbol)
|
|
356
|
+
: queries.immediateAliased(symbol);
|
|
351
357
|
// An alias that resolves to nothing answers with the checker's own unknown
|
|
352
358
|
// symbol, which declares nothing; a symbol that is no alias answers with
|
|
353
359
|
// nothing at all.
|
|
@@ -411,7 +417,6 @@ export function ownedAs<Brand>(
|
|
|
411
417
|
|
|
412
418
|
/** Every diagnostic set a project is refused for carrying an error in. */
|
|
413
419
|
export interface ProjectDiagnostics {
|
|
414
|
-
readonly configFile: string;
|
|
415
420
|
readonly syntactic: readonly Diagnostic[];
|
|
416
421
|
readonly semantic: readonly Diagnostic[];
|
|
417
422
|
readonly configParsing: readonly Diagnostic[];
|
|
@@ -425,7 +430,6 @@ export interface ProjectDiagnostics {
|
|
|
425
430
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
426
431
|
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
427
432
|
return {
|
|
428
|
-
configFile: project.configFile,
|
|
429
433
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
430
434
|
semantic: project.program.getSemanticDiagnostics(),
|
|
431
435
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
package/src/source-files.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface SourceFiles {
|
|
|
38
38
|
* nothing.
|
|
39
39
|
*/
|
|
40
40
|
named(path: string): readonly SourceFile[];
|
|
41
|
+
/** The directories the project writes its output below, absolute: its output and declaration directories. */
|
|
42
|
+
readonly outputDirs: readonly string[];
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -76,6 +78,7 @@ export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: st
|
|
|
76
78
|
return {
|
|
77
79
|
byPath,
|
|
78
80
|
byName,
|
|
81
|
+
outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
|
|
79
82
|
named: (path) => {
|
|
80
83
|
if (!path.includes(SUBPATH_WILDCARD)) {
|
|
81
84
|
for (const candidate of candidates(path)) {
|
package/src/stores.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface Stores {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** What one configuration contributes to the run's stores. */
|
|
40
|
-
|
|
40
|
+
interface ConfigurationStores {
|
|
41
41
|
readonly references: readonly Reference[];
|
|
42
42
|
readonly accessors: readonly string[];
|
|
43
43
|
}
|
package/src/suppress-inline.ts
CHANGED
|
@@ -37,7 +37,7 @@ const NO_REASON = new RegExp(DIRECTIVE_EXPRESSIONS.noReason);
|
|
|
37
37
|
const DIRECTIVE_FORM = "// deadset:ignore DS0000[,DS0000...] -- <reason>";
|
|
38
38
|
|
|
39
39
|
/** What the grammar's decision procedure makes of one comment. */
|
|
40
|
-
|
|
40
|
+
type Directive =
|
|
41
41
|
| { readonly is: "none" }
|
|
42
42
|
| { readonly is: "directive"; readonly codes: readonly string[]; readonly reason: string }
|
|
43
43
|
| { readonly is: "no-reason"; readonly codes: readonly string[] }
|
|
@@ -64,7 +64,7 @@ export function classifyComment(text: string): Directive {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/** One `//` line comment: the offset of its first solidus, and its text to the end of its line. */
|
|
67
|
-
|
|
67
|
+
interface LineComment {
|
|
68
68
|
readonly offset: number;
|
|
69
69
|
readonly text: string;
|
|
70
70
|
}
|
|
@@ -123,7 +123,7 @@ export function tokenStart(text: string, from: number): number {
|
|
|
123
123
|
* syntax tree holds are skipped, so a `//` inside a string, a template or a regular
|
|
124
124
|
* expression starts nothing.
|
|
125
125
|
*/
|
|
126
|
-
|
|
126
|
+
function lineComments(file: SourceFile): readonly LineComment[] {
|
|
127
127
|
const text = file.text;
|
|
128
128
|
const spans: { readonly start: number; readonly end: number }[] = [];
|
|
129
129
|
const visit = (node: Node): void => {
|