@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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The imports that name a `declare module` declaration whose name is a string literal: an
|
|
3
|
+
* augmentation or an ambient module, a wildcard one included. An import names it when its
|
|
4
|
+
* specifier equals the name, matches it with the one `*` standing for any run, or resolves
|
|
5
|
+
* to the same module. One that names it by name or pattern also reads the members it
|
|
6
|
+
* imports, whatever file the specifier resolves to, as a compiler reading no such file does.
|
|
7
|
+
*/
|
|
8
|
+
import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isImportTypeNode, isLiteralTypeNode, isModuleDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
|
+
import { nodeKey } from "./inventory.js";
|
|
10
|
+
import { renderPosition } from "./position.js";
|
|
11
|
+
import { UNANSWERED } from "./query.js";
|
|
12
|
+
import { nameComponent } from "./ref.js";
|
|
13
|
+
/** Whether a node is a specifier's text: a string literal or a template with no substitution. */
|
|
14
|
+
function literalText(node) {
|
|
15
|
+
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
16
|
+
}
|
|
17
|
+
/** The specifier one node writes, where it is an import of any form. */
|
|
18
|
+
function specifierOf(node) {
|
|
19
|
+
if (isImportDeclaration(node) || isExportDeclaration(node)) {
|
|
20
|
+
const specifier = node.moduleSpecifier;
|
|
21
|
+
return literalText(specifier) ? specifier : undefined;
|
|
22
|
+
}
|
|
23
|
+
if (isImportEqualsDeclaration(node)) {
|
|
24
|
+
const reference = node.moduleReference;
|
|
25
|
+
return isExternalModuleReference(reference) && literalText(reference.expression)
|
|
26
|
+
? reference.expression
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
|
|
30
|
+
const [argument] = node.arguments;
|
|
31
|
+
return literalText(argument) ? argument : undefined;
|
|
32
|
+
}
|
|
33
|
+
if (isImportTypeNode(node) && isLiteralTypeNode(node.argument)) {
|
|
34
|
+
const literal = node.argument.literal;
|
|
35
|
+
return literalText(literal) ? literal : undefined;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The export names an import or a re-export reads from the module it names: none for a
|
|
41
|
+
* namespace, a star or a bare form.
|
|
42
|
+
*/
|
|
43
|
+
export function namesImported(node) {
|
|
44
|
+
if (isImportDeclaration(node)) {
|
|
45
|
+
const clause = node.importClause;
|
|
46
|
+
const bindings = clause?.namedBindings;
|
|
47
|
+
return [
|
|
48
|
+
...(clause?.name === undefined ? [] : ["default"]),
|
|
49
|
+
...(bindings === undefined || isNamespaceImport(bindings)
|
|
50
|
+
? []
|
|
51
|
+
: bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
const clause = isExportDeclaration(node) ? node.exportClause : undefined;
|
|
55
|
+
return clause === undefined || isNamespaceExport(clause)
|
|
56
|
+
? []
|
|
57
|
+
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Whether a specifier matches a declaration's name, the name's one `*` standing for any run.
|
|
61
|
+
* A relative name is resolved against its own file, so no specifier names it by its text.
|
|
62
|
+
*/
|
|
63
|
+
function namesModule(specifier, name) {
|
|
64
|
+
if (/^(?:\.{1,2}(?:\/|$)|\/)/u.test(name)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const star = name.indexOf("*");
|
|
68
|
+
if (star < 0 || name.slice(star + 1).includes("*")) {
|
|
69
|
+
return specifier === name;
|
|
70
|
+
}
|
|
71
|
+
const prefix = name.slice(0, star);
|
|
72
|
+
const suffix = name.slice(star + 1);
|
|
73
|
+
return (specifier.length >= prefix.length + suffix.length &&
|
|
74
|
+
specifier.startsWith(prefix) &&
|
|
75
|
+
specifier.endsWith(suffix));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The module declarations of an inventory a specifier names by name or pattern, each with
|
|
79
|
+
* the members that hold the export names given.
|
|
80
|
+
*/
|
|
81
|
+
export function namedBy(held) {
|
|
82
|
+
if (held.modules.size === 0) {
|
|
83
|
+
return () => [];
|
|
84
|
+
}
|
|
85
|
+
const refs = new Map();
|
|
86
|
+
const children = new Map();
|
|
87
|
+
for (const symbol of held.symbols) {
|
|
88
|
+
if (held.modules.has(symbol.id)) {
|
|
89
|
+
refs.set(symbol.id, symbol.ref);
|
|
90
|
+
}
|
|
91
|
+
if (held.modules.has(symbol.parent)) {
|
|
92
|
+
const siblings = children.get(symbol.parent) ?? [];
|
|
93
|
+
siblings.push(symbol);
|
|
94
|
+
children.set(symbol.parent, siblings);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return (specifier, names) => [...held.modules].flatMap(([id, name]) => {
|
|
98
|
+
if (!namesModule(specifier, name)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
// A member is named by its declaration's reference, or an export alias's.
|
|
102
|
+
const ref = refs.get(id) ?? "";
|
|
103
|
+
const scope = ref.slice(0, ref.indexOf("#"));
|
|
104
|
+
const wanted = new Set(names.flatMap((one) => [
|
|
105
|
+
`${ref}.${nameComponent(one)}`,
|
|
106
|
+
`${scope}#${nameComponent(one)}:alias`,
|
|
107
|
+
]));
|
|
108
|
+
const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
|
|
109
|
+
return [{ id, members: members.map((child) => child.id) }];
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The references the imports of one project's own files make to the module declarations
|
|
114
|
+
* they name, each at the import's specifier.
|
|
115
|
+
*/
|
|
116
|
+
export function moduleDeclarationReferences(project, held, root, testFiles) {
|
|
117
|
+
const imports = [];
|
|
118
|
+
const declared = [];
|
|
119
|
+
for (const file of project.ownSourceFiles()) {
|
|
120
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
121
|
+
if (fileId === undefined) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const visit = (node, from) => {
|
|
125
|
+
const own = held.declarations.get(nodeKey(file, node));
|
|
126
|
+
if (isModuleDeclaration(node) && literalText(node.name) && own !== undefined) {
|
|
127
|
+
declared.push({ id: own, name: node.name });
|
|
128
|
+
}
|
|
129
|
+
const specifier = specifierOf(node);
|
|
130
|
+
if (specifier !== undefined) {
|
|
131
|
+
imports.push({ file, from, specifier, names: namesImported(node) });
|
|
132
|
+
}
|
|
133
|
+
node.forEachChild((child) => {
|
|
134
|
+
visit(child, own ?? from);
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
file.forEachChild((child) => {
|
|
138
|
+
visit(child, fileId);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (declared.length === 0 || imports.length === 0) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const asked = [...declared.map((one) => one.name), ...imports.map((one) => one.specifier)];
|
|
145
|
+
const answers = project.symbolsAt(asked.map((node) => project.handle(node)));
|
|
146
|
+
const moduleOf = (at) => {
|
|
147
|
+
const symbol = answers[at];
|
|
148
|
+
return symbol === undefined || symbol === UNANSWERED ? undefined : symbol.id;
|
|
149
|
+
};
|
|
150
|
+
const named = namedBy(held);
|
|
151
|
+
const found = [];
|
|
152
|
+
imports.forEach((one, index) => {
|
|
153
|
+
const resolved = moduleOf(declared.length + index);
|
|
154
|
+
const position = renderPosition(one.file, root, one.specifier.getStart());
|
|
155
|
+
const read = (to, resolution) => {
|
|
156
|
+
found.push({
|
|
157
|
+
from: one.from,
|
|
158
|
+
to,
|
|
159
|
+
position,
|
|
160
|
+
use: "read",
|
|
161
|
+
resolution,
|
|
162
|
+
test: testFiles.has(position.path),
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const same = new Set(declared
|
|
166
|
+
.filter((_, at) => resolved !== undefined && resolved === moduleOf(at))
|
|
167
|
+
.map((declaration) => declaration.id));
|
|
168
|
+
for (const id of same) {
|
|
169
|
+
read(id, "batch");
|
|
170
|
+
}
|
|
171
|
+
for (const declaration of named(one.specifier.text, one.names)) {
|
|
172
|
+
if (!same.has(declaration.id)) {
|
|
173
|
+
for (const to of [declaration.id, ...declaration.members]) {
|
|
174
|
+
read(to, "syntax");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The test files no compiler configuration of a run holds. The runner executes them all
|
|
3
3
|
* the same, so each module one names is a test reference: the own file a relative
|
|
4
|
-
* specifier names, and the declarations that file exports under the names read from it
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* specifier names, and the declarations that file exports under the names read from it,
|
|
5
|
+
* or a module declaration the specifier names by name or pattern and its members of
|
|
6
|
+
* those names. Such a file is read without a program, so what its body references
|
|
7
|
+
* beyond the modules it names is not known.
|
|
7
8
|
*/
|
|
8
9
|
import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
9
10
|
import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
|
|
10
11
|
import { emittedFrom } from "./emit-map.js";
|
|
12
|
+
import { namedBy, namesImported } from "./module-declarations.js";
|
|
11
13
|
import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
|
|
12
14
|
import { renderPosition } from "./position.js";
|
|
13
15
|
import { nameComponent } from "./ref.js";
|
|
@@ -47,17 +49,11 @@ function modulesNamed(file) {
|
|
|
47
49
|
if (isImportDeclaration(statement)) {
|
|
48
50
|
const clause = statement.importClause;
|
|
49
51
|
const bindings = clause?.namedBindings;
|
|
50
|
-
name(statement.moduleSpecifier,
|
|
51
|
-
...(clause?.name === undefined ? [] : ["default"]),
|
|
52
|
-
...(bindings === undefined || isNamespaceImport(bindings)
|
|
53
|
-
? []
|
|
54
|
-
: bindings.elements.map((one) => (one.propertyName ?? one.name).text)),
|
|
55
|
-
], bindings === undefined ? clause?.name === undefined : isNamespaceImport(bindings));
|
|
52
|
+
name(statement.moduleSpecifier, namesImported(statement), bindings === undefined ? clause?.name === undefined : isNamespaceImport(bindings));
|
|
56
53
|
}
|
|
57
54
|
else if (isExportDeclaration(statement) && statement.moduleSpecifier !== undefined) {
|
|
58
55
|
const clause = statement.exportClause;
|
|
59
|
-
|
|
60
|
-
name(statement.moduleSpecifier, star ? [] : clause.elements.map((one) => (one.propertyName ?? one.name).text), star);
|
|
56
|
+
name(statement.moduleSpecifier, namesImported(statement), clause === undefined || isNamespaceExport(clause));
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
const visit = (node) => {
|
|
@@ -106,11 +102,21 @@ export function outsideTestReferences(tests, held, targetRoot) {
|
|
|
106
102
|
exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
|
|
107
103
|
}
|
|
108
104
|
}
|
|
105
|
+
const declared = namedBy(held);
|
|
109
106
|
const found = [];
|
|
110
107
|
for (const { path, file } of tests) {
|
|
111
108
|
const from = `${path}#`;
|
|
112
109
|
const dir = dirnamePath(path) === "." ? "" : dirnamePath(path);
|
|
113
110
|
for (const named of modulesNamed(file)) {
|
|
111
|
+
const position = renderPosition(file, targetRoot, named.specifier.getStart());
|
|
112
|
+
const read = (to, use) => {
|
|
113
|
+
found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
|
|
114
|
+
};
|
|
115
|
+
for (const declaration of declared(named.text, named.names)) {
|
|
116
|
+
for (const to of [declaration.id, ...declaration.members]) {
|
|
117
|
+
read(to, "read");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
114
120
|
const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
|
|
115
121
|
if (written === undefined) {
|
|
116
122
|
continue;
|
|
@@ -121,10 +127,6 @@ export function outsideTestReferences(tests, held, targetRoot) {
|
|
|
121
127
|
if (fileId === undefined) {
|
|
122
128
|
continue;
|
|
123
129
|
}
|
|
124
|
-
const position = renderPosition(file, targetRoot, named.specifier.getStart());
|
|
125
|
-
const read = (to, use) => {
|
|
126
|
-
found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
|
|
127
|
-
};
|
|
128
130
|
if (named.evaluates) {
|
|
129
131
|
read(fileId, "evaluation");
|
|
130
132
|
}
|
package/dist/src/overrides.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The overrides of the target's class members. A call through the base class
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* The overrides of the target's class members. A call through the base class names the
|
|
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 overriding a member of a class
|
|
5
|
+
* declared outside the target, at any depth of its `extends` chain, is called by outside
|
|
6
|
+
* code holding the instance through that class, so its own class references it.
|
|
6
7
|
*/
|
|
7
8
|
import { isClassDeclaration, isClassExpression, isExpressionWithTypeArguments, isPropertyAccessExpression, isQualifiedName, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
8
9
|
import { memberComponent } from "./class-members.js";
|
|
9
10
|
import { nodeKey } from "./inventory.js";
|
|
11
|
+
import { SymbolFlags } from "@typescript/native/unstable/sync";
|
|
10
12
|
import { UNANSWERED } from "./query.js";
|
|
11
13
|
/** The member kinds a subclass overrides. */
|
|
12
14
|
const OVERRIDABLE = new Set(["method", "class-member"]);
|
|
@@ -38,9 +40,10 @@ function extending(project, held) {
|
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
42
|
* The references each base class member makes to its overrides, for the classes of one
|
|
41
|
-
* project that extend another class of the target
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* project that extend another class of the target, and each class makes to its methods
|
|
44
|
+
* that override a member of a class declared outside the target. A member a nearer base
|
|
45
|
+
* already overrides is reached through that override. A private member is not
|
|
46
|
+
* overridden. Each reference is placed at the override and made by its file.
|
|
44
47
|
*/
|
|
45
48
|
export function overrideReferences(project, held, chains, testFiles) {
|
|
46
49
|
const classes = extending(project, held);
|
|
@@ -62,14 +65,25 @@ export function overrideReferences(project, held, chains, testFiles) {
|
|
|
62
65
|
members.set(symbol.parent, own);
|
|
63
66
|
}
|
|
64
67
|
const bases = new Map();
|
|
68
|
+
const outsideBases = new Map();
|
|
69
|
+
const own = project.ownPaths();
|
|
65
70
|
const symbols = project.symbolsAt(classes.map((one) => project.handle(one.base)));
|
|
66
71
|
classes.forEach(({ id }, index) => {
|
|
67
72
|
const symbol = symbols[index];
|
|
68
73
|
if (symbol !== undefined && symbol !== UNANSWERED) {
|
|
69
|
-
|
|
74
|
+
const inTarget = chains
|
|
70
75
|
.chainOf(symbol)
|
|
71
76
|
.map((target) => target.id)
|
|
72
|
-
.filter((target) => byId.get(target)?.kind === "class" && target !== id)
|
|
77
|
+
.filter((target) => byId.get(target)?.kind === "class" && target !== id);
|
|
78
|
+
bases.set(id, inTarget);
|
|
79
|
+
const declared = (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : project.queries.aliased(symbol);
|
|
80
|
+
if (inTarget.length === 0 &&
|
|
81
|
+
declared !== UNANSWERED &&
|
|
82
|
+
(declared.flags & SymbolFlags.Class) !== 0 &&
|
|
83
|
+
declared.declarations.length > 0 &&
|
|
84
|
+
declared.declarations.every((handle) => !own.has(handle.path))) {
|
|
85
|
+
outsideBases.set(id, declared);
|
|
86
|
+
}
|
|
73
87
|
}
|
|
74
88
|
});
|
|
75
89
|
/** The nearest member of one component the class or a class it extends declares. */
|
|
@@ -85,6 +99,21 @@ export function overrideReferences(project, held, chains, testFiles) {
|
|
|
85
99
|
return (bases.get(classId) ?? []).flatMap((base) => declaredAbove(base, component, seen));
|
|
86
100
|
};
|
|
87
101
|
const found = [];
|
|
102
|
+
const outsideNames = outsideMemberNames(project, bases, outsideBases);
|
|
103
|
+
for (const [classId, names] of outsideNames) {
|
|
104
|
+
for (const [component, member] of members.get(classId) ?? []) {
|
|
105
|
+
if (member.kind === "method" && names.has(component)) {
|
|
106
|
+
found.push({
|
|
107
|
+
from: classId,
|
|
108
|
+
to: member.id,
|
|
109
|
+
position: member.position,
|
|
110
|
+
use: "read",
|
|
111
|
+
resolution: "override",
|
|
112
|
+
test: testFiles.has(member.position.path),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
88
117
|
for (const [classId, baseIds] of bases) {
|
|
89
118
|
for (const [component, member] of members.get(classId) ?? []) {
|
|
90
119
|
const seen = new Set([classId]);
|
|
@@ -102,3 +131,35 @@ export function overrideReferences(project, held, chains, testFiles) {
|
|
|
102
131
|
}
|
|
103
132
|
return found;
|
|
104
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Per class of the target with a class declared outside the target in its `extends`
|
|
136
|
+
* chain, the names of the members that outside class's instances carry.
|
|
137
|
+
*/
|
|
138
|
+
function outsideMemberNames(project, bases, outsideBases) {
|
|
139
|
+
const direct = new Map();
|
|
140
|
+
for (const [classId, base] of outsideBases) {
|
|
141
|
+
const type = project.queries.declaredTypeOf(base);
|
|
142
|
+
const properties = type === UNANSWERED ? UNANSWERED : project.queries.propertiesOf(type);
|
|
143
|
+
if (properties !== UNANSWERED) {
|
|
144
|
+
direct.set(classId, new Set(properties.map((property) => property.escapedName)));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const names = new Map();
|
|
148
|
+
const above = (classId, seen) => {
|
|
149
|
+
if (seen.has(classId)) {
|
|
150
|
+
return new Set();
|
|
151
|
+
}
|
|
152
|
+
seen.add(classId);
|
|
153
|
+
return new Set([
|
|
154
|
+
...(direct.get(classId) ?? []),
|
|
155
|
+
...(bases.get(classId) ?? []).flatMap((base) => [...above(base, seen)]),
|
|
156
|
+
]);
|
|
157
|
+
};
|
|
158
|
+
for (const classId of bases.keys()) {
|
|
159
|
+
const found = above(classId, new Set());
|
|
160
|
+
if (found.size > 0) {
|
|
161
|
+
names.set(classId, found);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return names;
|
|
165
|
+
}
|
package/dist/src/query.js
CHANGED
|
@@ -211,10 +211,17 @@ export function queriesOf(checker, configFile, batch, log, ask = ASK_DIRECTLY) {
|
|
|
211
211
|
targetOf: (type) => type.isTypeReference()
|
|
212
212
|
? one("getTarget", () => typeLocation(type), () => type.getTarget())
|
|
213
213
|
: type,
|
|
214
|
+
constraintOf: (type) => type.isTypeParameter()
|
|
215
|
+
? one("getConstraint", () => typeLocation(type), () => type.getConstraint())
|
|
216
|
+
: undefined,
|
|
214
217
|
symbolOfType: (type) => one("getSymbol", () => typeLocation(type), () => type.getSymbol()),
|
|
215
218
|
signaturesOf: (type, kind) => one("getSignaturesOfType", () => typeLocation(type), () => checker.getSignaturesOfType(type, kind)),
|
|
216
219
|
resolvedSignature: (node) => one("getResolvedSignature", () => nodeLocation(node), () => checker.getResolvedSignature(node)),
|
|
217
220
|
parameterType: (signature, index) => one("getParameterType", () => `signature ${String(signature.id)} parameter ${String(index)}`, () => checker.getParameterType(signature, index)),
|
|
221
|
+
genericOf: (signature) => signature.target === undefined
|
|
222
|
+
? undefined
|
|
223
|
+
: one("getTarget", () => `signature ${String(signature.id)}`, () => signature.getTarget()),
|
|
224
|
+
typeAtPosition: (signature, index) => one("getTypeParameterAtPosition", () => `signature ${String(signature.id)} position ${String(index)}`, () => signature.getTypeParameterAtPosition(index)),
|
|
218
225
|
signatureOf: (declaration) => one("getSignatureFromDeclaration", () => nodeLocation(declaration), () => checker.getSignatureFromDeclaration(declaration)),
|
|
219
226
|
returnTypeOf: (signature) => one("getReturnTypeOfSignature", () => `signature ${String(signature.id)}`, () => checker.getReturnTypeOfSignature(signature)),
|
|
220
227
|
typeToString: (type) => one("typeToString", () => typeLocation(type), () => checker.typeToString(type)),
|
package/dist/src/ref.js
CHANGED
|
@@ -52,6 +52,7 @@ export const REF_EXPRESSIONS = [
|
|
|
52
52
|
const REF_FORMS = REF_EXPRESSIONS.map((expression) => new RegExp(expression, "u"));
|
|
53
53
|
const IDENTIFIER = new RegExp(`^${TS_IDENT}$`, "u");
|
|
54
54
|
const PRIVATE_NAME = new RegExp(`^#${TS_IDENT}$`, "u");
|
|
55
|
+
// Stryker disable next-line Regex: its \v to \V mutant is no valid escape under the u flag, so the whole module fails to parse
|
|
55
56
|
const ASCII_WHITESPACE = /[\t\n\v\f\r ]/gu;
|
|
56
57
|
/**
|
|
57
58
|
* The canonical spelling of one declared name as a fragment component: bare when
|
package/dist/src/references.js
CHANGED
|
@@ -6,6 +6,9 @@ import { specifierCandidates } from "./configuration-files.js";
|
|
|
6
6
|
import { namespaceUses } from "./namespace-objects.js";
|
|
7
7
|
import { globExpression } from "./glob.js";
|
|
8
8
|
import { declarationsByName, nodeKey } from "./inventory.js";
|
|
9
|
+
import { augmentationReferences } from "./augmentations.js";
|
|
10
|
+
import { indexedAccessReferences } from "./indexed-access.js";
|
|
11
|
+
import { moduleDeclarationReferences } from "./module-declarations.js";
|
|
9
12
|
import { overrideReferences } from "./overrides.js";
|
|
10
13
|
import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.js";
|
|
11
14
|
import { byPosition, isComponentFile, renderPosition } from "./position.js";
|
|
@@ -1069,7 +1072,10 @@ export function references(project, held, targetRoot, options) {
|
|
|
1069
1072
|
}
|
|
1070
1073
|
if (consumer === undefined) {
|
|
1071
1074
|
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1075
|
+
found.push(...indexedAccessReferences(project, held, chains, root, new Set(tests)));
|
|
1076
|
+
found.push(...moduleDeclarationReferences(project, held, root, new Set(tests)));
|
|
1072
1077
|
}
|
|
1078
|
+
found.push(...augmentationReferences(held, found));
|
|
1073
1079
|
found.sort((a, b) => byPosition(a.position, b.position) ||
|
|
1074
1080
|
compare(a.to, b.to) ||
|
|
1075
1081
|
compare(a.from, b.from) ||
|
package/dist/src/report.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
6
|
/** The version of the report schema this analyzer writes a report to. */
|
|
7
|
-
const SCHEMA_VERSION = "
|
|
7
|
+
const SCHEMA_VERSION = "8.0.0";
|
|
8
8
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
9
9
|
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
10
10
|
/** The name this analyzer writes every document under and mints every identifier with. */
|
|
@@ -186,6 +186,11 @@ function totalsOf(findings, result) {
|
|
|
186
186
|
stale_suppressions: result.staleSuppressions.length,
|
|
187
187
|
pending: result.totals.pending,
|
|
188
188
|
omitted: 0,
|
|
189
|
+
withheld: {
|
|
190
|
+
certain: result.totals.withheld.certain,
|
|
191
|
+
probable: result.totals.withheld.probable,
|
|
192
|
+
possible: result.totals.withheld.possible,
|
|
193
|
+
},
|
|
189
194
|
};
|
|
190
195
|
}
|
|
191
196
|
/** Builds the report of one run, every array in the order the Contract fixes for it. */
|
|
@@ -3,6 +3,7 @@ import { KINDS } from "../kinds.js";
|
|
|
3
3
|
import { lineHashes, symbolFingerprint } from "./fingerprint.js";
|
|
4
4
|
import { RenderError } from "./reporter.js";
|
|
5
5
|
import { utf8Bytes } from "./sha256.js";
|
|
6
|
+
import { withheldLine } from "./text.js";
|
|
6
7
|
const SCHEMA = "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";
|
|
7
8
|
const VERSION = "2.1.0";
|
|
8
9
|
/** The unit a finding's column counts. */
|
|
@@ -216,6 +217,7 @@ export function sarif(report, options) {
|
|
|
216
217
|
...report.stale_suppressions.map((stale) => staleResult(stale, index, hashOf)),
|
|
217
218
|
];
|
|
218
219
|
const languages = [...report.analyzer.languages].sort().join("+");
|
|
220
|
+
const withheld = withheldLine(report.totals);
|
|
219
221
|
const log = {
|
|
220
222
|
$schema: SCHEMA,
|
|
221
223
|
version: VERSION,
|
|
@@ -233,7 +235,7 @@ export function sarif(report, options) {
|
|
|
233
235
|
columnKind: COLUMN_KIND,
|
|
234
236
|
originalUriBaseIds: { [URI_BASE_ID]: { description: { text: ROOT_DESCRIPTION } } },
|
|
235
237
|
results,
|
|
236
|
-
properties: { totals: report.totals },
|
|
238
|
+
properties: withheld === undefined ? { totals: report.totals } : { totals: report.totals, withheld },
|
|
237
239
|
},
|
|
238
240
|
],
|
|
239
241
|
};
|
|
@@ -19,11 +19,27 @@ function summary(totals) {
|
|
|
19
19
|
`${String(totals.omitted)} omitted`,
|
|
20
20
|
].join(", ");
|
|
21
21
|
}
|
|
22
|
+
/** The confidences a withheld line names, from the highest the minimum can withhold. */
|
|
23
|
+
const WITHHELD_ORDER = ["probable", "possible"];
|
|
24
|
+
/**
|
|
25
|
+
* The line naming what the minimum confidence withheld and the setting that shows it all,
|
|
26
|
+
* or undefined where it withheld nothing.
|
|
27
|
+
*/
|
|
28
|
+
export function withheldLine(totals) {
|
|
29
|
+
const named = WITHHELD_ORDER.filter((confidence) => totals.withheld[confidence] > 0);
|
|
30
|
+
const lowest = named.at(-1);
|
|
31
|
+
if (lowest === undefined) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const counts = named.map((confidence) => `${String(totals.withheld[confidence])} ${confidence}`);
|
|
35
|
+
return `withheld by analysis.min_confidence: ${counts.join(", ")}, shown with analysis.min_confidence set to ${lowest}`;
|
|
36
|
+
}
|
|
22
37
|
/**
|
|
23
38
|
* One line per finding in the report's order, then one per stale suppression, then the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
39
|
+
* withheld line where the minimum withheld a finding, then the summary. A finding line is
|
|
40
|
+
* `path:line:col: kind name: message [confidence] (CODE)`; the summary is deliberately not
|
|
41
|
+
* in that shape, so a filter for finding lines yields exactly them. Nothing else is
|
|
42
|
+
* written: no timestamp, duration or host detail.
|
|
27
43
|
*/
|
|
28
44
|
export const text = (report) => coalesced((function* lines() {
|
|
29
45
|
for (const found of report.findings) {
|
|
@@ -32,5 +48,9 @@ export const text = (report) => coalesced((function* lines() {
|
|
|
32
48
|
for (const stale of report.stale_suppressions) {
|
|
33
49
|
yield `${stale.position.path}:${String(stale.position.line)}:${String(stale.position.column)}: ${STALE_KIND} ${stale.symbol}: ${stale.message} [${STALE_CONFIDENCE}] (${stale.code})\n`;
|
|
34
50
|
}
|
|
51
|
+
const withheld = withheldLine(report.totals);
|
|
52
|
+
if (withheld !== undefined) {
|
|
53
|
+
yield `${withheld}\n`;
|
|
54
|
+
}
|
|
35
55
|
yield `${summary(report.totals)}\n`;
|
|
36
56
|
})());
|
package/dist/src/roots.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* decides; the published API only supposes a consumer, so a published export is live
|
|
8
8
|
* under reachability and still a candidate under reference counting.
|
|
9
9
|
*/
|
|
10
|
-
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, } from "@typescript/native/unstable/ast";
|
|
10
|
+
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isVariableStatement, } from "@typescript/native/unstable/ast";
|
|
11
11
|
import { aliasChains } from "./alias-chain.js";
|
|
12
12
|
import { conventionEntries } from "./conventions.js";
|
|
13
13
|
import { entryPoints } from "./entry-points.js";
|
|
@@ -162,7 +162,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
162
162
|
const enter = (file, kind, source, exports) => {
|
|
163
163
|
entered.push({ file, kind, source, exports });
|
|
164
164
|
};
|
|
165
|
-
const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse);
|
|
165
|
+
const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse, options.conventions?.selected);
|
|
166
166
|
const published = [];
|
|
167
167
|
const entries = [
|
|
168
168
|
...options.manifest.entries.map((entry) => ({
|
|
@@ -239,7 +239,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
const api = options.publishedAPI
|
|
242
|
-
? publishedAPI(project, published, exportsOf, held, files.byPath)
|
|
242
|
+
? publishedAPI(project, published, exportsOf, held, files.byPath, releaseTagged(held, files))
|
|
243
243
|
: { ids: [], guessed: [] };
|
|
244
244
|
for (const id of api.ids) {
|
|
245
245
|
add(id, "published-api", "");
|
|
@@ -303,17 +303,54 @@ function exportTables(project, held, wanted) {
|
|
|
303
303
|
});
|
|
304
304
|
return { tables, answered };
|
|
305
305
|
}
|
|
306
|
+
/** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
|
|
307
|
+
const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
|
|
308
|
+
/**
|
|
309
|
+
* Whether the documentation comment of the statement that starts at `start`, the last
|
|
310
|
+
* `/** *\/` comment before it with only white space between the two, holds a release tag.
|
|
311
|
+
*/
|
|
312
|
+
function releaseTagIn(text, from, start) {
|
|
313
|
+
const leading = text.slice(from, start);
|
|
314
|
+
const open = leading.lastIndexOf("/**");
|
|
315
|
+
const close = open < 0 ? -1 : leading.indexOf("*/", open + 3);
|
|
316
|
+
if (close < 0 || leading.slice(close + 2).trim() !== "") {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return RELEASE_TAG.test(leading.slice(open + 3, close));
|
|
320
|
+
}
|
|
321
|
+
/** The exported top-level declarations of the program's files a release tag marks. */
|
|
322
|
+
function releaseTagged(held, files) {
|
|
323
|
+
const exported = new Set(held.symbols.filter((symbol) => symbol.exported).map((one) => one.id));
|
|
324
|
+
const found = [];
|
|
325
|
+
for (const file of files.byPath.values()) {
|
|
326
|
+
for (const statement of file.statements) {
|
|
327
|
+
if (!releaseTagIn(file.text, statement.pos, statement.getStart())) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const nodes = isVariableStatement(statement)
|
|
331
|
+
? statement.declarationList.declarations
|
|
332
|
+
: [statement];
|
|
333
|
+
for (const node of nodes) {
|
|
334
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
335
|
+
if (id !== undefined && exported.has(id)) {
|
|
336
|
+
found.push(id);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return found;
|
|
342
|
+
}
|
|
306
343
|
/** The kinds of container whose export table, not its members' visibility, says what it publishes. */
|
|
307
344
|
const EXPORTING_CONTAINERS = new Set(["file", "namespace"]);
|
|
308
345
|
/**
|
|
309
|
-
* Every declaration a consumer of the library can name: what the published files export
|
|
310
|
-
* and
|
|
311
|
-
* declaration's public and protected ones
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
346
|
+
* Every declaration a consumer of the library can name: what the published files export,
|
|
347
|
+
* and below each the members a consumer writes, a namespace's exported ones and any other
|
|
348
|
+
* declaration's public and protected ones. A declaration a published one's type names is
|
|
349
|
+
* published too, and so is an exported declaration a release tag marks. A type name the
|
|
350
|
+
* checker leaves unanswered publishes every declaration spelled as it is, as `guessed`. A
|
|
351
|
+
* published file is not itself published.
|
|
315
352
|
*/
|
|
316
|
-
function publishedAPI(project, published, exportsOf, held, byPath) {
|
|
353
|
+
function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
317
354
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
318
355
|
const children = childrenOf(held);
|
|
319
356
|
const nodes = declarationNodes(held, byPath);
|
|
@@ -327,6 +364,9 @@ function publishedAPI(project, published, exportsOf, held, byPath) {
|
|
|
327
364
|
pending.push(id);
|
|
328
365
|
}
|
|
329
366
|
};
|
|
367
|
+
for (const id of tagged) {
|
|
368
|
+
reach(id);
|
|
369
|
+
}
|
|
330
370
|
for (const file of published) {
|
|
331
371
|
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
332
372
|
if (byId.get(id)?.kind === "file") {
|
package/dist/src/version.js
CHANGED