@cplieger/deadset-ts 3.3.1 → 3.5.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.
@@ -0,0 +1,232 @@
1
+ /**
2
+ * The declarations the configuration's entries name, resolved in one project, and
3
+ * which of them a symbol is. Declarations are compared rather than symbols, because a
4
+ * member read through an instantiation of a generic type is a symbol of its own that
5
+ * keeps the declarations of the member it instantiates.
6
+ */
7
+ import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImportDeclaration, isImportEqualsDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
8
+ import { SymbolFlags, } from "@typescript/native/unstable/sync";
9
+ import { resolvedTargets } from "./calls.js";
10
+ import { nameComponent } from "./ref.js";
11
+ /** The meanings a name of the global scope is looked up under: any of them. */
12
+ const ANY_MEANING = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace;
13
+ /** The suffix that marks a static member at the end of a path. */
14
+ const STATIC = ":static";
15
+ /** The key one declaration handle is compared by: its file and its index in that file. */
16
+ function handleKey(handle) {
17
+ return `${handle.path}:${String(handle.index)}`;
18
+ }
19
+ /**
20
+ * The components of one declaration path and whether its last one is a static member.
21
+ * A path is split at the dots outside a quoted or a bracketed component, and each
22
+ * component keeps the spelling a reference gives it.
23
+ */
24
+ function componentsOf(path) {
25
+ const isStatic = path.endsWith(STATIC);
26
+ const body = isStatic ? path.slice(0, -STATIC.length) : path;
27
+ const components = [];
28
+ let current = "";
29
+ let closing = "";
30
+ for (let at = 0; at < body.length; at += 1) {
31
+ const ch = body.charAt(at);
32
+ if (closing === "'" && ch === "\\") {
33
+ current += ch + body.charAt(at + 1);
34
+ at += 1;
35
+ continue;
36
+ }
37
+ if (closing !== "") {
38
+ closing = ch === closing ? "" : closing;
39
+ }
40
+ else if (ch === "'" || ch === "[") {
41
+ closing = ch === "'" ? "'" : "]";
42
+ }
43
+ else if (ch === ".") {
44
+ components.push(current);
45
+ current = "";
46
+ continue;
47
+ }
48
+ current += ch;
49
+ }
50
+ components.push(current);
51
+ return { components, static: isStatic };
52
+ }
53
+ /** What a symbol is once every alias it stands for is followed. */
54
+ function unaliased(project, symbol) {
55
+ if ((symbol.flags & SymbolFlags.Alias) === 0) {
56
+ return symbol;
57
+ }
58
+ const target = project.checker.getAliasedSymbol(symbol);
59
+ return target.declarations.length === 0 ? undefined : target;
60
+ }
61
+ /** Whether one symbol is a member a class declares: a property, a method or an accessor. */
62
+ function isClassMember(symbol) {
63
+ return (symbol.flags & SymbolFlags.ClassMember) !== 0;
64
+ }
65
+ /**
66
+ * The members of one container a path component may name. A class keeps its static
67
+ * members among its exports beside what a merged namespace exports, so a component
68
+ * with the static suffix names one of the first and a component without it names an
69
+ * instance member or one of the second.
70
+ */
71
+ function candidatesOf(container, isStatic) {
72
+ const exported = [...container.getExports().values()];
73
+ if (isStatic) {
74
+ return exported.filter(isClassMember);
75
+ }
76
+ const isClass = (container.flags & SymbolFlags.Class) !== 0;
77
+ return [
78
+ ...container.getMembers().values(),
79
+ ...exported.filter((member) => !isClass || !isClassMember(member)),
80
+ ];
81
+ }
82
+ /** The member of one container a path component spells, followed through any alias. */
83
+ function memberOf(project, container, component, isStatic) {
84
+ const member = candidatesOf(container, isStatic).find((candidate) => nameComponent(candidate.name) === component);
85
+ return member === undefined ? undefined : unaliased(project, member);
86
+ }
87
+ /** The declaration a path names from one starting symbol, component by component. */
88
+ function walkPath(project, start, rest, isStatic) {
89
+ let at = start;
90
+ rest.forEach((component, index) => {
91
+ if (at !== undefined) {
92
+ at = memberOf(project, at, component, isStatic && index === rest.length - 1);
93
+ }
94
+ });
95
+ return at;
96
+ }
97
+ /** Whether one node is literal text: a string literal or a template with no substitution. */
98
+ function isLiteralText(node) {
99
+ return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
100
+ }
101
+ /** The module specifier one node writes, for each form that imports or re-exports a module. */
102
+ function specifierOf(node) {
103
+ if (isImportDeclaration(node) || isExportDeclaration(node)) {
104
+ return node.moduleSpecifier;
105
+ }
106
+ if (isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference)) {
107
+ return node.moduleReference.expression;
108
+ }
109
+ if (isCallExpression(node) && node.expression.kind === SyntaxKind.ImportKeyword) {
110
+ return node.arguments[0];
111
+ }
112
+ return undefined;
113
+ }
114
+ /**
115
+ * The module each specifier the entries name resolves to in one project, read from a
116
+ * specifier the project's own files write, so the module is the one the target's own
117
+ * import resolves. A specifier no file writes resolves to no module.
118
+ */
119
+ function modulesNamed(project, specifiers) {
120
+ const written = new Map();
121
+ for (const file of project.ownSourceFiles()) {
122
+ const visit = (node) => {
123
+ const specifier = specifierOf(node);
124
+ if (isLiteralText(specifier) &&
125
+ specifiers.has(specifier.text) &&
126
+ !written.has(specifier.text)) {
127
+ written.set(specifier.text, specifier);
128
+ }
129
+ node.forEachChild(visit);
130
+ };
131
+ file.forEachChild(visit);
132
+ }
133
+ const specified = [...written];
134
+ const symbols = project.symbolsAt(specified.map(([, node]) => project.handle(node)));
135
+ const modules = new Map();
136
+ specified.forEach(([name], index) => {
137
+ const symbol = symbols[index];
138
+ if (symbol !== undefined) {
139
+ modules.set(name, symbol);
140
+ }
141
+ });
142
+ return modules;
143
+ }
144
+ /**
145
+ * The declaration each entry names in one project. A symbol entry names the
146
+ * inventory's declaration whose reference it spells exactly; a module entry names the
147
+ * declaration its path reaches from the exports of the module its specifier resolves
148
+ * to; a global entry names the declaration its path reaches from the global scope.
149
+ * Every alias along a path is followed to the declaration it stands for.
150
+ */
151
+ export function resolveEntries(project, held, entries) {
152
+ if (entries.length === 0) {
153
+ return [];
154
+ }
155
+ const byRef = new Map(held.symbols.map((symbol) => [symbol.ref, symbol.id]));
156
+ const modules = modulesNamed(project, new Set(entries.flatMap((entry) => (entry.shape === "module" ? [entry.module] : []))));
157
+ return entries.map((entry) => {
158
+ if (entry.shape === "symbol") {
159
+ const id = byRef.get(entry.symbol);
160
+ return { entry, handles: new Set(), ids: new Set(id === undefined ? [] : [id]) };
161
+ }
162
+ const { components, static: isStatic } = componentsOf(entry.shape === "module" ? entry.name : entry.global);
163
+ const [first = "", ...rest] = components;
164
+ let start;
165
+ if (isStatic && rest.length === 0) {
166
+ // Only a member is static, and a path of one component names no member.
167
+ start = undefined;
168
+ }
169
+ else if (entry.shape === "module") {
170
+ const module = modules.get(entry.module);
171
+ start = module === undefined ? undefined : memberOf(project, module, first, false);
172
+ }
173
+ else if (nameComponent(first) === first) {
174
+ const global = project.checker.resolveName(first, ANY_MEANING, undefined, false);
175
+ start = global === undefined ? undefined : unaliased(project, global);
176
+ }
177
+ const declared = walkPath(project, start, rest, isStatic);
178
+ return {
179
+ entry,
180
+ handles: new Set(declared?.declarations.map(handleKey) ?? []),
181
+ ids: new Set(),
182
+ };
183
+ });
184
+ }
185
+ /**
186
+ * Whether one symbol is the declaration a resolved entry names: it shares one of the
187
+ * entry's declarations, or, for an entry naming a declaration of the program, it is
188
+ * declared there. `chains` reads the symbol's declarations against the inventory the
189
+ * entries were resolved with.
190
+ */
191
+ export function names(resolved, symbol, chains) {
192
+ if (symbol.declarations.some((handle) => resolved.handles.has(handleKey(handle)))) {
193
+ return true;
194
+ }
195
+ return resolved.ids.size > 0 && chains.declarationsOf(symbol).some((id) => resolved.ids.has(id));
196
+ }
197
+ /** Whether a resolved entry names a declaration, so that some symbol can be it. */
198
+ export function namesADeclaration(resolved) {
199
+ return resolved.handles.size > 0 || resolved.ids.size > 0;
200
+ }
201
+ /**
202
+ * The entries each of a batch of name nodes is: what the node resolves to, every alias
203
+ * followed, is the declaration each of them names. A node that is none is absent.
204
+ */
205
+ export function entriesAt(project, chains, nodes, resolved) {
206
+ const naming = resolved.filter(namesADeclaration);
207
+ const found = new Map();
208
+ if (naming.length === 0) {
209
+ return found;
210
+ }
211
+ for (const [node, target] of resolvedTargets(project, nodes)) {
212
+ const entries = naming.filter((entry) => names(entry, target, chains));
213
+ if (entries.length > 0) {
214
+ found.set(node, entries);
215
+ }
216
+ }
217
+ return found;
218
+ }
219
+ /**
220
+ * How one entry is spelled in a record's detail: its path, or for a declaration of the
221
+ * program the display name the inventory gives it.
222
+ */
223
+ export function spelledEntry(entry, held) {
224
+ switch (entry.shape) {
225
+ case "symbol":
226
+ return held.symbols.find((symbol) => symbol.ref === entry.symbol)?.name ?? entry.symbol;
227
+ case "module":
228
+ return entry.name;
229
+ case "global":
230
+ return entry.global;
231
+ }
232
+ }
@@ -10,17 +10,11 @@
10
10
  * declaration is held back, and the member set of a decorated class is every member
11
11
  * the inventory holds for the class, static members included.
12
12
  */
13
- import { isCallExpression, isDecorator, } from "@typescript/native/unstable/ast";
13
+ import { decoratorsOf, spelledDecorator } from "./calls.js";
14
14
  import { nodeKey } from "./inventory.js";
15
15
  import { renderPosition } from "./position.js";
16
16
  /** The kinds of declaration a decorator is attached to and holds back. */
17
17
  const DECORATED = new Set(["class", "method", "class-member"]);
18
- /** How one decorator is spelled in a record: its expression, without a call's arguments. */
19
- function spelled(decorator) {
20
- const expression = decorator.expression;
21
- const named = isCallExpression(expression) ? expression.expression : expression;
22
- return `@${named.getText().replace(/\s+/gu, "")}`;
23
- }
24
18
  /**
25
19
  * The decorator detector: each decorated member, and each member of a decorated
26
20
  * class, recorded at the decorator.
@@ -46,9 +40,8 @@ export function decorator(input) {
46
40
  const id = held.declarations.get(nodeKey(file, node));
47
41
  const symbol = id === undefined ? undefined : byId.get(id);
48
42
  if (symbol !== undefined && DECORATED.has(symbol.kind)) {
49
- const modifiers = node.modifiers ?? [];
50
- for (const attached of modifiers.filter(isDecorator)) {
51
- const detail = `decorated by ${spelled(attached)}`;
43
+ for (const attached of decoratorsOf(node)) {
44
+ const detail = `decorated by ${spelledDecorator(attached)}`;
52
45
  const site = renderPosition(file, targetRoot, attached.getStart());
53
46
  const reached = symbol.kind === "class" ? (membersOf.get(symbol.id) ?? []) : [symbol];
54
47
  for (const member of reached) {
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The framework-lifecycle class: a member a `ts.lifecycle_contracts` entry lists is held
3
+ * back on each class that is the entry's component, being a class a decorator or a call
4
+ * the entry's `components` name is attached to or passes, or one extending, directly or
5
+ * through a chain, a class its `bases` name. A component's members are its type's, so a
6
+ * member a base class declares answers for every subclass.
7
+ */
8
+ import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, } from "@typescript/native/unstable/ast";
9
+ import { aliasChains } from "./alias-chain.js";
10
+ import { calleeName, callsOf, classesPassed, decoratedClasses, decoratorCallee } from "./calls.js";
11
+ import { classMembers, memberComponent, typesAt } from "./class-members.js";
12
+ import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
13
+ import { nodeKey } from "./inventory.js";
14
+ import { renderPosition } from "./position.js";
15
+ /** Every contract's components and bases, resolved in one pass over the entries. */
16
+ function resolveContracts(project, held, contracts) {
17
+ const entries = contracts.flatMap((contract) => [
18
+ ...contract.components,
19
+ ...contract.bases,
20
+ ]);
21
+ const resolved = resolveEntries(project, held, entries);
22
+ let next = 0;
23
+ const take = (count) => {
24
+ const taken = resolved.slice(next, next + count).filter(namesADeclaration);
25
+ next += count;
26
+ return taken;
27
+ };
28
+ return contracts.map((contract) => ({
29
+ contract,
30
+ components: take(contract.components.length),
31
+ bases: take(contract.bases.length),
32
+ }));
33
+ }
34
+ /** The classes a decorator or a call the contracts' components name makes a component. */
35
+ function byComponents(project, held, chains, contracts) {
36
+ const routed = contracts.filter((one) => one.components.length > 0);
37
+ if (routed.length === 0) {
38
+ return [];
39
+ }
40
+ const contractOf = new Map(routed.flatMap((one) => one.components.map((entry) => [entry, one.contract])));
41
+ const calls = callsOf(project);
42
+ const decorated = decoratedClasses(project, held);
43
+ const named = entriesAt(project, chains, [
44
+ ...calls.flatMap((call) => calleeName(call.expression) ?? []),
45
+ ...decorated.flatMap((one) => one.decorators.flatMap((decorator) => decoratorCallee(decorator) ?? [])),
46
+ ], [...contractOf.keys()]);
47
+ const naming = (node) => (node === undefined ? [] : (named.get(node) ?? [])).flatMap((entry) => {
48
+ const contract = contractOf.get(entry);
49
+ return contract === undefined ? [] : [{ contract, detail: spelledEntry(entry.entry, held) }];
50
+ });
51
+ const found = [];
52
+ for (const one of decorated) {
53
+ for (const decorator of one.decorators) {
54
+ for (const { contract, detail } of naming(decoratorCallee(decorator))) {
55
+ found.push({ contract, id: one.id, site: decorator, detail: `decorated by @${detail}` });
56
+ }
57
+ }
58
+ }
59
+ const matched = new Map();
60
+ for (const call of calls) {
61
+ const routes = naming(calleeName(call.expression));
62
+ if (routes.length > 0) {
63
+ matched.set(call, routes);
64
+ }
65
+ }
66
+ for (const [call, uses] of classesPassed(project, held, chains, [...matched.keys()])) {
67
+ for (const route of matched.get(call) ?? []) {
68
+ for (const use of uses) {
69
+ found.push({
70
+ contract: route.contract,
71
+ id: use.id,
72
+ site: use.site,
73
+ detail: `registered by ${route.detail}`,
74
+ });
75
+ }
76
+ }
77
+ }
78
+ return found;
79
+ }
80
+ /** The class declarations of the target that extend another class. */
81
+ function extendingClasses(project, held) {
82
+ const found = [];
83
+ for (const file of project.ownSourceFiles()) {
84
+ const visit = (node) => {
85
+ if ((isClassDeclaration(node) || isClassExpression(node)) && node.name !== undefined) {
86
+ const id = held.declarations.get(nodeKey(file, node));
87
+ const clause = (node.heritageClauses ?? []).find((one) => isHeritageClause(one) && one.token === SyntaxKind.ExtendsKeyword);
88
+ const extended = clause !== undefined && isHeritageClause(clause) ? clause.types[0] : undefined;
89
+ if (id !== undefined && extended !== undefined) {
90
+ found.push({ id, name: node.name, extended });
91
+ }
92
+ }
93
+ node.forEachChild(visit);
94
+ };
95
+ file.forEachChild(visit);
96
+ }
97
+ return found;
98
+ }
99
+ /** The classes that extend, directly or through a chain, a class the contracts' bases name. */
100
+ function byBases(project, held, chains, contracts) {
101
+ const based = contracts.filter((one) => one.bases.length > 0);
102
+ if (based.length === 0) {
103
+ return [];
104
+ }
105
+ const classes = extendingClasses(project, held);
106
+ if (classes.length === 0) {
107
+ return [];
108
+ }
109
+ const checker = project.checker;
110
+ const ancestry = new Map();
111
+ const ancestorsOf = (type, seen) => {
112
+ const known = ancestry.get(type.id);
113
+ if (known !== undefined) {
114
+ return known;
115
+ }
116
+ const declared = type.isClassOrInterface()
117
+ ? type
118
+ : type.isTypeReference()
119
+ ? type.getTarget()
120
+ : undefined;
121
+ if (declared === undefined || !declared.isClassOrInterface() || seen.has(declared.id)) {
122
+ return [];
123
+ }
124
+ seen.add(declared.id);
125
+ const found = checker
126
+ .getBaseTypes(declared)
127
+ .flatMap((base) => [base, ...ancestorsOf(base, seen)]);
128
+ ancestry.set(type.id, found);
129
+ return found;
130
+ };
131
+ const types = typesAt(project, classes.map((one) => one.name));
132
+ const found = [];
133
+ classes.forEach((one, index) => {
134
+ const type = types[index];
135
+ if (type === undefined) {
136
+ return;
137
+ }
138
+ const ancestors = ancestorsOf(type, new Set()).flatMap((base) => base.getSymbol() ?? []);
139
+ for (const contract of based) {
140
+ for (const entry of contract.bases) {
141
+ if (ancestors.some((symbol) => names(entry, symbol, chains))) {
142
+ found.push({
143
+ contract: contract.contract,
144
+ id: one.id,
145
+ site: one.extended,
146
+ detail: `extends ${spelledEntry(entry.entry, held)}`,
147
+ });
148
+ }
149
+ }
150
+ }
151
+ });
152
+ return found;
153
+ }
154
+ /**
155
+ * The framework-lifecycle detector: each member a contract lists, on each class that
156
+ * is a component of that contract's framework, recorded where the class was found to
157
+ * be one.
158
+ */
159
+ export function frameworkLifecycle(input) {
160
+ const { project, held, targetRoot } = input;
161
+ if (input.ts.lifecycleContracts.length === 0) {
162
+ return [];
163
+ }
164
+ const chains = aliasChains(project, held);
165
+ const contracts = resolveContracts(project, held, input.ts.lifecycleContracts);
166
+ const components = [
167
+ ...byComponents(project, held, chains, contracts),
168
+ ...byBases(project, held, chains, contracts),
169
+ ];
170
+ if (components.length === 0) {
171
+ return [];
172
+ }
173
+ const ids = new Set(components.map((one) => one.id));
174
+ const sides = input.ts.lifecycleContracts.some((contract) => contract.members.some((name) => name.endsWith(":static")))
175
+ ? ["instance", "static"]
176
+ : ["instance"];
177
+ const members = sides.map((side) => classMembers(project, held, ids, side));
178
+ const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
179
+ const found = [];
180
+ for (const component of components) {
181
+ const listed = new Set(component.contract.members);
182
+ const site = renderPosition(component.site.getSourceFile(), targetRoot, component.site.getStart());
183
+ for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
184
+ const symbol = byId.get(member.id);
185
+ const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
186
+ if (spelled !== undefined && listed.has(spelled)) {
187
+ found.push({ id: member.id, detail: component.detail, site });
188
+ }
189
+ }
190
+ }
191
+ return found;
192
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The injection-container class: a container constructs a class passed to a call
3
+ * `ts.injection_registrations` names, or one a parameter of whose constructor carries a
4
+ * decorator, and populates each of its properties and accessors that carries one, a base
5
+ * class's included. A constructor and its parameters are no declarations of the
6
+ * inventory, so the class stands for them and the registration referencing it keeps it.
7
+ */
8
+ import { isClassDeclaration, isClassExpression, isConstructorDeclaration, isGetAccessorDeclaration, isPropertyDeclaration, isSetAccessorDeclaration, } from "@typescript/native/unstable/ast";
9
+ import { aliasChains } from "./alias-chain.js";
10
+ import { calleeName, callsOf, classesPassed, decoratorsOf, spelledDecorator } from "./calls.js";
11
+ import { classMembers } from "./class-members.js";
12
+ import { entriesAt, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
13
+ import { nodeKey } from "./inventory.js";
14
+ import { renderPosition } from "./position.js";
15
+ /** Whether one member declaration is a property or an accessor carrying a decorator. */
16
+ function isInjected(node) {
17
+ return ((isPropertyDeclaration(node) ||
18
+ isGetAccessorDeclaration(node) ||
19
+ isSetAccessorDeclaration(node)) &&
20
+ decoratorsOf(node).length > 0);
21
+ }
22
+ /** The classes of the target a call whose callee an entry names passes, with that call's entry. */
23
+ function registrations(input) {
24
+ const { project, held } = input;
25
+ const resolved = resolveEntries(project, held, input.ts.injectionRegistrations);
26
+ if (!resolved.some(namesADeclaration)) {
27
+ return [];
28
+ }
29
+ const chains = aliasChains(project, held);
30
+ const calls = callsOf(project);
31
+ const named = entriesAt(project, chains, calls.flatMap((call) => calleeName(call.expression) ?? []), resolved);
32
+ const matched = new Map();
33
+ for (const call of calls) {
34
+ const name = calleeName(call.expression);
35
+ const [entry] = name === undefined ? [] : (named.get(name) ?? []);
36
+ if (entry !== undefined) {
37
+ matched.set(call, entry);
38
+ }
39
+ }
40
+ return [...classesPassed(project, held, chains, [...matched.keys()])].flatMap(([call, uses]) => {
41
+ const entry = matched.get(call);
42
+ const detail = entry === undefined ? "" : `registered by ${spelledEntry(entry.entry, held)}`;
43
+ return uses.map((use) => ({ id: use.id, site: use.site, detail }));
44
+ });
45
+ }
46
+ /** The classes of the target a parameter of whose constructor carries a decorator. */
47
+ function constructedForParameters(input) {
48
+ const { project, held } = input;
49
+ const found = new Set();
50
+ for (const file of project.ownSourceFiles()) {
51
+ const visit = (node) => {
52
+ if (isClassDeclaration(node) || isClassExpression(node)) {
53
+ const id = held.declarations.get(nodeKey(file, node));
54
+ const asks = node.members.some((member) => isConstructorDeclaration(member) &&
55
+ member.parameters.some((parameter) => decoratorsOf(parameter).length > 0));
56
+ if (id !== undefined && asks) {
57
+ found.add(id);
58
+ }
59
+ }
60
+ node.forEachChild(visit);
61
+ };
62
+ file.forEachChild(visit);
63
+ }
64
+ return found;
65
+ }
66
+ /**
67
+ * The injection-container detector: each decorated property or accessor of a class a
68
+ * container constructs, recorded at the registration that names the class, or, for a
69
+ * class whose constructor asks for its parameters, at the member's own decorator.
70
+ */
71
+ export function injectionContainer(input) {
72
+ const { project, held, targetRoot } = input;
73
+ const registered = registrations(input);
74
+ const constructed = constructedForParameters(input);
75
+ const classes = new Set([...registered.map((one) => one.id), ...constructed]);
76
+ if (classes.size === 0) {
77
+ return [];
78
+ }
79
+ const members = classMembers(project, held, classes, "instance");
80
+ const injected = (id) => (members.get(id) ?? []).filter((member) => isInjected(member.node));
81
+ const at = (node) => renderPosition(node.getSourceFile(), targetRoot, node.getStart());
82
+ const found = [];
83
+ for (const registration of registered) {
84
+ for (const member of injected(registration.id)) {
85
+ found.push({ id: member.id, detail: registration.detail, site: at(registration.site) });
86
+ }
87
+ }
88
+ for (const id of constructed) {
89
+ for (const member of injected(id)) {
90
+ const [decorator] = decoratorsOf(member.node);
91
+ if (decorator !== undefined) {
92
+ found.push({
93
+ id: member.id,
94
+ detail: `injected by ${spelledDecorator(decorator)}`,
95
+ site: at(decorator),
96
+ });
97
+ }
98
+ }
99
+ }
100
+ return found;
101
+ }
package/dist/src/kinds.js CHANGED
@@ -10,3 +10,43 @@
10
10
  * code the Contract fixes arrives here as a failing test.
11
11
  */
12
12
  export const FIXED_SEVERITY_CODES = ["DS1703", "DS1704"];
13
+ /**
14
+ * The code of every live issue kind, in ascending order. A severity key names one of
15
+ * them or a family prefix whose range holds one; a key naming a retired code, an
16
+ * unassigned code or a range holding no live kind is an unimplemented key, so every
17
+ * severity a configuration sets is one some analyzer reports under. A test compares
18
+ * this list against the Contract release the analyzer is written against.
19
+ */
20
+ export const LIVE_CODES = [
21
+ "DS1001",
22
+ "DS1002",
23
+ "DS1003",
24
+ "DS1004",
25
+ "DS1005",
26
+ "DS1006",
27
+ "DS1101",
28
+ "DS1102",
29
+ "DS1103",
30
+ "DS1104",
31
+ "DS1201",
32
+ "DS1203",
33
+ "DS1204",
34
+ "DS1301",
35
+ "DS1302",
36
+ "DS1303",
37
+ "DS1501",
38
+ "DS1502",
39
+ "DS1601",
40
+ "DS1605",
41
+ "DS1701",
42
+ "DS1702",
43
+ "DS1703",
44
+ "DS1704",
45
+ "DS1705",
46
+ "DS1801",
47
+ "DS1802",
48
+ "DS1803",
49
+ "DS1805",
50
+ "DS1807",
51
+ "DS1809",
52
+ ];
package/dist/src/print.js CHANGED
@@ -1,4 +1,23 @@
1
- import { renderOrigin } from "./config.js";
1
+ import { renderOrigin, } from "./config.js";
2
+ /** One declaration entry as a document writes it: the members of its shape alone. */
3
+ function declarationOf(entry) {
4
+ switch (entry.shape) {
5
+ case "symbol":
6
+ return { symbol: entry.symbol };
7
+ case "module":
8
+ return { module: entry.module, name: entry.name };
9
+ case "global":
10
+ return { global: entry.global };
11
+ }
12
+ }
13
+ /** One provider entry as a document writes it: the members of its shape alone. */
14
+ function providerOf(entry) {
15
+ const installed = { name: entry.name, languages: entry.languages, command: entry.command };
16
+ if (entry.shape === "installed") {
17
+ return installed;
18
+ }
19
+ return { ...installed, source: entry.source, version: entry.version, digest: entry.digest };
20
+ }
2
21
  /**
3
22
  * The resolved configuration and its provenance as one JSON object, indented,
4
23
  * ending in a newline.
@@ -40,10 +59,18 @@ export function printConfig(config, provenance) {
40
59
  max_findings: config.reporters.maxFindings,
41
60
  fail_on: config.reporters.failOn,
42
61
  },
62
+ providers: { analyzers: config.providers.analyzers.map(providerOf) },
43
63
  go: {},
44
64
  ts: {
45
65
  test_files: config.ts.testFiles,
46
66
  entry_files: config.ts.entryFiles,
67
+ injection_registrations: config.ts.injectionRegistrations.map(declarationOf),
68
+ lifecycle_contracts: config.ts.lifecycleContracts.map((entry) => ({
69
+ components: entry.components.map(declarationOf),
70
+ bases: entry.bases.map(declarationOf),
71
+ members: entry.members,
72
+ })),
73
+ serializers: config.ts.serializers.map(declarationOf),
47
74
  },
48
75
  provenance: Object.fromEntries([...provenance]
49
76
  .sort(([a], [b]) => (a < b ? -1 : 1))