@cplieger/deadset-ts 5.2.2 → 6.0.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/analysis.js +28 -5
- package/dist/src/augmentations.js +50 -0
- package/dist/src/components.js +15 -15
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +17 -2
- package/dist/src/conventions.js +82 -1
- package/dist/src/dependencies.js +42 -26
- package/dist/src/entry-points.js +28 -14
- package/dist/src/exempt.js +1 -0
- package/dist/src/findings/dependencies-and-module-machinery.js +2 -2
- package/dist/src/findings/emitters.js +47 -12
- package/dist/src/findings/interfaces.js +4 -3
- package/dist/src/findings/intra-function.js +15 -2
- package/dist/src/findings/unused-declarations.js +13 -1
- package/dist/src/findings-pass.js +1 -0
- package/dist/src/graph.js +6 -6
- package/dist/src/implementations.js +1 -1
- package/dist/src/indexed-access.js +215 -0
- package/dist/src/interface-satisfaction.js +85 -23
- package/dist/src/intra-function-parts.js +139 -30
- package/dist/src/inventory.js +6 -1
- package/dist/src/kinds.js +13 -12
- package/dist/src/matrix.js +34 -9
- package/dist/src/module-declarations.js +180 -0
- package/dist/src/outside-tests.js +17 -15
- package/dist/src/overrides.js +70 -9
- package/dist/src/query.js +7 -0
- package/dist/src/ref.js +1 -0
- package/dist/src/references.js +6 -0
- package/dist/src/report.js +6 -1
- package/dist/src/reporters/sarif.js +3 -1
- package/dist/src/reporters/text.js +23 -3
- package/dist/src/roots.js +50 -10
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +82 -0
- package/jsr.json +1 -1
- package/package.json +8 -2
- package/src/analysis.ts +40 -4
- package/src/augmentations.ts +72 -0
- package/src/components.ts +15 -13
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +25 -3
- package/src/conventions.ts +106 -2
- package/src/dependencies.ts +51 -31
- package/src/entry-points.ts +32 -19
- package/src/exempt.ts +4 -1
- package/src/findings/dependencies-and-module-machinery.ts +2 -2
- package/src/findings/emitters.ts +71 -12
- package/src/findings/interfaces.ts +7 -6
- package/src/findings/intra-function.ts +20 -2
- package/src/findings/unused-declarations.ts +16 -1
- package/src/findings-pass.ts +4 -1
- package/src/graph.ts +6 -6
- package/src/implementations.ts +1 -1
- package/src/indexed-access.ts +295 -0
- package/src/interface-satisfaction.ts +118 -27
- package/src/intra-function-parts.ts +196 -35
- package/src/inventory.ts +8 -0
- package/src/kinds.ts +14 -12
- package/src/matrix.ts +51 -8
- package/src/module-declarations.ts +241 -0
- package/src/outside-tests.ts +18 -16
- package/src/overrides.ts +80 -14
- package/src/query.ts +28 -0
- package/src/ref.ts +1 -0
- package/src/references.ts +6 -0
- package/src/report.ts +11 -1
- package/src/reporters/sarif.ts +4 -1
- package/src/reporters/text.ts +25 -3
- package/src/roots.ts +56 -7
- package/src/sweep.ts +12 -0
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +104 -0
- package/src/workspace-run.ts +3 -4
package/src/roots.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isQualifiedName,
|
|
16
16
|
isTypeQueryNode,
|
|
17
17
|
isTypeReferenceNode,
|
|
18
|
+
isVariableStatement,
|
|
18
19
|
type Node,
|
|
19
20
|
type SourceFile,
|
|
20
21
|
} from "@typescript/native/unstable/ast";
|
|
@@ -297,6 +298,7 @@ export function roots<Brand>(
|
|
|
297
298
|
options.host,
|
|
298
299
|
targetRoot,
|
|
299
300
|
options.parse,
|
|
301
|
+
options.conventions?.selected,
|
|
300
302
|
);
|
|
301
303
|
const published: SourceFile[] = [];
|
|
302
304
|
const entries = [
|
|
@@ -379,7 +381,7 @@ export function roots<Brand>(
|
|
|
379
381
|
}
|
|
380
382
|
|
|
381
383
|
const api = options.publishedAPI
|
|
382
|
-
? publishedAPI(project, published, exportsOf, held, files.byPath)
|
|
384
|
+
? publishedAPI(project, published, exportsOf, held, files.byPath, releaseTagged(held, files))
|
|
383
385
|
: { ids: [], guessed: [] };
|
|
384
386
|
for (const id of api.ids) {
|
|
385
387
|
add(id, "published-api", "");
|
|
@@ -453,16 +455,59 @@ function exportTables<Brand>(
|
|
|
453
455
|
return { tables, answered };
|
|
454
456
|
}
|
|
455
457
|
|
|
458
|
+
/** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
|
|
459
|
+
const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Whether the documentation comment of the statement that starts at `start`, the last
|
|
463
|
+
* `/** *\/` comment before it with only white space between the two, holds a release tag.
|
|
464
|
+
*/
|
|
465
|
+
function releaseTagIn(text: string, from: number, start: number): boolean {
|
|
466
|
+
const leading = text.slice(from, start);
|
|
467
|
+
const open = leading.lastIndexOf("/**");
|
|
468
|
+
const close = open < 0 ? -1 : leading.indexOf("*/", open + 3);
|
|
469
|
+
if (close < 0 || leading.slice(close + 2).trim() !== "") {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return RELEASE_TAG.test(leading.slice(open + 3, close));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** The exported top-level declarations of the program's files a release tag marks. */
|
|
476
|
+
function releaseTagged(
|
|
477
|
+
held: Inventory,
|
|
478
|
+
files: { readonly byPath: ReadonlyMap<string, SourceFile> },
|
|
479
|
+
): readonly string[] {
|
|
480
|
+
const exported = new Set(held.symbols.filter((symbol) => symbol.exported).map((one) => one.id));
|
|
481
|
+
const found: string[] = [];
|
|
482
|
+
for (const file of files.byPath.values()) {
|
|
483
|
+
for (const statement of file.statements) {
|
|
484
|
+
if (!releaseTagIn(file.text, statement.pos, statement.getStart())) {
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const nodes: readonly Node[] = isVariableStatement(statement)
|
|
488
|
+
? statement.declarationList.declarations
|
|
489
|
+
: [statement];
|
|
490
|
+
for (const node of nodes) {
|
|
491
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
492
|
+
if (id !== undefined && exported.has(id)) {
|
|
493
|
+
found.push(id);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return found;
|
|
499
|
+
}
|
|
500
|
+
|
|
456
501
|
/** The kinds of container whose export table, not its members' visibility, says what it publishes. */
|
|
457
502
|
const EXPORTING_CONTAINERS: ReadonlySet<string> = new Set(["file", "namespace"]);
|
|
458
503
|
|
|
459
504
|
/**
|
|
460
|
-
* Every declaration a consumer of the library can name: what the published files export
|
|
461
|
-
* and
|
|
462
|
-
* declaration's public and protected ones
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
505
|
+
* Every declaration a consumer of the library can name: what the published files export,
|
|
506
|
+
* and below each the members a consumer writes, a namespace's exported ones and any other
|
|
507
|
+
* declaration's public and protected ones. A declaration a published one's type names is
|
|
508
|
+
* published too, and so is an exported declaration a release tag marks. A type name the
|
|
509
|
+
* checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
|
|
510
|
+
* published file is not itself published.
|
|
466
511
|
*/
|
|
467
512
|
function publishedAPI<Brand>(
|
|
468
513
|
project: ProjectView<Brand>,
|
|
@@ -470,6 +515,7 @@ function publishedAPI<Brand>(
|
|
|
470
515
|
exportsOf: ReadonlyMap<string, readonly string[]>,
|
|
471
516
|
held: Inventory,
|
|
472
517
|
byPath: ReadonlyMap<string, SourceFile>,
|
|
518
|
+
tagged: readonly string[],
|
|
473
519
|
): { readonly ids: readonly string[]; readonly guessed: readonly string[] } {
|
|
474
520
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
475
521
|
const children = childrenOf(held);
|
|
@@ -484,6 +530,9 @@ function publishedAPI<Brand>(
|
|
|
484
530
|
pending.push(id);
|
|
485
531
|
}
|
|
486
532
|
};
|
|
533
|
+
for (const id of tagged) {
|
|
534
|
+
reach(id);
|
|
535
|
+
}
|
|
487
536
|
for (const file of published) {
|
|
488
537
|
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
489
538
|
if (byId.get(id)?.kind === "file") {
|
package/src/sweep.ts
CHANGED
|
@@ -27,6 +27,18 @@ export interface Exemption {
|
|
|
27
27
|
* record holds only while that file is live.
|
|
28
28
|
*/
|
|
29
29
|
readonly whileLive?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The interface methods the retained member implements, and whether its body is empty.
|
|
32
|
+
* The record holds only while one of them is called through its interface.
|
|
33
|
+
*/
|
|
34
|
+
readonly implementing?: Implementing;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The interface methods one retained class member implements, and whether its body is empty. */
|
|
38
|
+
export interface Implementing {
|
|
39
|
+
/** The declarations of the interface's methods the member answers, by identifier. */
|
|
40
|
+
readonly methods: readonly string[];
|
|
41
|
+
readonly empty: boolean;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
/**
|
package/src/version.ts
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source files a CI workflow's `run:` steps name by their path from the target root.
|
|
3
|
+
* A workflow file is read as text, line by line, with no YAML parsed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SourceFile } from "@typescript/native/unstable/ast";
|
|
7
|
+
import type { Host } from "./host.ts";
|
|
8
|
+
import { scriptTokens } from "./manifest.ts";
|
|
9
|
+
import { joinPath, resolvePath } from "./paths.ts";
|
|
10
|
+
import type { SourceFiles } from "./source-files.ts";
|
|
11
|
+
|
|
12
|
+
/** One file a workflow step names. */
|
|
13
|
+
interface WorkflowEntry {
|
|
14
|
+
readonly file: SourceFile;
|
|
15
|
+
/** The token that named it, as the command writes it. */
|
|
16
|
+
readonly source: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const WORKFLOWS = ".github/workflows";
|
|
20
|
+
|
|
21
|
+
/** A line holding a run command: indentation and one sequence marker before `run:`. */
|
|
22
|
+
const RUN_LINE = /^(\s*(?:- )?)run:(.*)$/u;
|
|
23
|
+
|
|
24
|
+
const BLOCK_SCALAR = /^[|>][+-]?$/u;
|
|
25
|
+
|
|
26
|
+
const QUOTED = /^(["'])(.*)\1$/su;
|
|
27
|
+
|
|
28
|
+
/** A token holding one of these is computed or a pattern, so it names no file. */
|
|
29
|
+
const COMPUTED = /[${*?[]/u;
|
|
30
|
+
|
|
31
|
+
const INDENT = /^\s*/u;
|
|
32
|
+
|
|
33
|
+
function workflowFiles(host: Host, dir: string): readonly string[] {
|
|
34
|
+
let entries;
|
|
35
|
+
try {
|
|
36
|
+
entries = host.readDirectory(dir);
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return entries
|
|
41
|
+
.filter((entry) => !entry.directory && /\.ya?ml$/u.test(entry.name))
|
|
42
|
+
.map((entry) => joinPath(dir, entry.name))
|
|
43
|
+
.sort();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Every command line one workflow file's `run:` steps hold. */
|
|
47
|
+
export function runCommands(text: string): readonly string[] {
|
|
48
|
+
const lines = text.split(/\r?\n/u);
|
|
49
|
+
const found: string[] = [];
|
|
50
|
+
for (let at = 0; at < lines.length; at += 1) {
|
|
51
|
+
const match = RUN_LINE.exec(lines[at] ?? "");
|
|
52
|
+
if (match === null) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const column = (match[1] ?? "").length;
|
|
56
|
+
const value = (match[2] ?? "").trim().replace(QUOTED, "$2");
|
|
57
|
+
if (!BLOCK_SCALAR.test(value)) {
|
|
58
|
+
found.push(value);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
for (let next = at + 1; next < lines.length; next += 1) {
|
|
62
|
+
const line = lines[next] ?? "";
|
|
63
|
+
if (line.trim() !== "" && (INDENT.exec(line)?.[0].length ?? 0) <= column) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
found.push(line);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The own files the `run:` steps of every workflow file in the target root's
|
|
74
|
+
* `.github/workflows` name: a token equal to a file's path below the target root, with or
|
|
75
|
+
* without a leading `./`, whatever directory the step runs in.
|
|
76
|
+
*/
|
|
77
|
+
export function workflowEntries(
|
|
78
|
+
host: Host,
|
|
79
|
+
targetRoot: string,
|
|
80
|
+
files: SourceFiles,
|
|
81
|
+
): readonly WorkflowEntry[] {
|
|
82
|
+
const dir = joinPath(resolvePath(host.workingDirectory(), targetRoot), WORKFLOWS);
|
|
83
|
+
const found: WorkflowEntry[] = [];
|
|
84
|
+
for (const path of workflowFiles(host, dir)) {
|
|
85
|
+
let text: string;
|
|
86
|
+
try {
|
|
87
|
+
text = host.readFile(path);
|
|
88
|
+
} catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
for (const command of runCommands(text)) {
|
|
92
|
+
for (const token of scriptTokens(command)) {
|
|
93
|
+
if (COMPUTED.test(token)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const file = files.byPath.get(token.startsWith("./") ? token.slice(2) : token);
|
|
97
|
+
if (file !== undefined) {
|
|
98
|
+
found.push({ file, source: token });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return found;
|
|
104
|
+
}
|
package/src/workspace-run.ts
CHANGED
|
@@ -41,10 +41,9 @@ export interface WorkspaceRun {
|
|
|
41
41
|
referenceOnly(configFile: string, compiled?: readonly string[]): (file: string) => boolean;
|
|
42
42
|
/**
|
|
43
43
|
* Whether one file a target configuration's program holds is the target's own. A
|
|
44
|
-
* workspace package's file is by its path alone
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* reached it as a library's.
|
|
44
|
+
* workspace package's file is by its path alone: the run reads another package from
|
|
45
|
+
* its source, so the compiler holds that package's files as the importer's own, not
|
|
46
|
+
* as a library's. Any other file is unless the compiler reached it as a library's.
|
|
48
47
|
*/
|
|
49
48
|
ownFile(file: string, fromExternalLibrary: boolean): boolean;
|
|
50
49
|
/** The target's own manifest, its targets read back to source. */
|