@cplieger/deadset-ts 8.0.0-dev.20 → 8.0.0-dev.21

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.
Files changed (68) hide show
  1. package/dist/src/alias-chain.js +33 -2
  2. package/dist/src/analysis.js +18 -77
  3. package/dist/src/conformance-record.js +2 -2
  4. package/dist/src/contract/contract.generated.js +4 -3
  5. package/dist/src/dependencies.js +1 -1
  6. package/dist/src/exemptions/argument-places.js +131 -0
  7. package/dist/src/{calls.js → exemptions/calls.js} +126 -179
  8. package/dist/src/{exempt-classes.js → exemptions/classes.js} +1 -1
  9. package/dist/src/{decorator.js → exemptions/decorator.js} +19 -30
  10. package/dist/src/exemptions/detectors.js +48 -0
  11. package/dist/src/{enum-group.js → exemptions/enum-group.js} +7 -7
  12. package/dist/src/{erased-stores.js → exemptions/erased-stores.js} +4 -6
  13. package/dist/src/{framework-lifecycle.js → exemptions/framework-lifecycle.js} +31 -36
  14. package/dist/src/{generated-file.js → exemptions/generated-file.js} +4 -4
  15. package/dist/src/{injection-container.js → exemptions/injection-container.js} +19 -25
  16. package/dist/src/exemptions/installed-globals.js +173 -0
  17. package/dist/src/{interface-satisfaction.js → exemptions/interface-satisfaction.js} +52 -121
  18. package/dist/src/exemptions/lifecycle.js +150 -0
  19. package/dist/src/{reflective-lookup.js → exemptions/reflective-lookup.js} +3 -3
  20. package/dist/src/{serialization-contract.js → exemptions/serialization-contract.js} +415 -329
  21. package/dist/src/exemptions/sites.js +182 -0
  22. package/dist/src/{template-field.js → exemptions/template-field.js} +7 -7
  23. package/dist/src/{value-reach.js → exemptions/value-reach.js} +14 -7
  24. package/dist/src/index/sites.js +1 -1
  25. package/dist/src/index/values.js +124 -0
  26. package/dist/src/liveness/sweep.js +2 -2
  27. package/dist/src/query.js +48 -0
  28. package/dist/src/roots/configured-declarations.js +1 -1
  29. package/dist/src/verbs/print-retained.js +23 -1
  30. package/jsr.json +1 -1
  31. package/package.json +1 -1
  32. package/src/alias-chain.ts +38 -2
  33. package/src/analysis.ts +19 -100
  34. package/src/bindings.ts +1 -1
  35. package/src/conformance-record.ts +2 -2
  36. package/src/contract/contract.generated.ts +4 -3
  37. package/src/dependencies.ts +1 -1
  38. package/src/exemptions/argument-places.ts +198 -0
  39. package/src/{calls.ts → exemptions/calls.ts} +190 -224
  40. package/src/{exempt-classes.ts → exemptions/classes.ts} +1 -1
  41. package/src/exemptions/decorator.ts +56 -0
  42. package/src/exemptions/detectors.ts +75 -0
  43. package/src/{enum-group.ts → exemptions/enum-group.ts} +9 -9
  44. package/src/{erased-stores.ts → exemptions/erased-stores.ts} +5 -8
  45. package/src/{framework-lifecycle.ts → exemptions/framework-lifecycle.ts} +39 -48
  46. package/src/{generated-file.ts → exemptions/generated-file.ts} +6 -6
  47. package/src/{injection-container.ts → exemptions/injection-container.ts} +28 -33
  48. package/src/exemptions/installed-globals.ts +255 -0
  49. package/src/{interface-satisfaction.ts → exemptions/interface-satisfaction.ts} +69 -160
  50. package/src/exemptions/lifecycle.ts +286 -0
  51. package/src/{reflective-lookup.ts → exemptions/reflective-lookup.ts} +5 -5
  52. package/src/{serialization-contract.ts → exemptions/serialization-contract.ts} +563 -429
  53. package/src/exemptions/sites.ts +293 -0
  54. package/src/{template-field.ts → exemptions/template-field.ts} +11 -11
  55. package/src/{value-reach.ts → exemptions/value-reach.ts} +16 -8
  56. package/src/facts.ts +1 -1
  57. package/src/index/sites.ts +1 -1
  58. package/src/index/values.ts +170 -0
  59. package/src/liveness/sweep.ts +2 -2
  60. package/src/query.ts +109 -10
  61. package/src/roots/configured-declarations.ts +1 -2
  62. package/src/stores.ts +1 -1
  63. package/src/verbs/print-retained.ts +29 -1
  64. package/dist/src/exempt.js +0 -164
  65. package/dist/src/value-flow.js +0 -74
  66. package/src/decorator.ts +0 -78
  67. package/src/exempt.ts +0 -295
  68. package/src/value-flow.ts +0 -104
package/src/query.ts CHANGED
@@ -8,16 +8,17 @@
8
8
  */
9
9
 
10
10
  import { isIdentifier, type Expression, type Node } from "@typescript/native/ast";
11
- import type {
12
- APIRequestGenerator,
13
- Checker,
14
- IndexInfo,
15
- JSDocTagInfo,
16
- Signature,
17
- SignatureKind,
18
- SymbolFlags,
19
- Symbol as TSSymbol,
20
- Type,
11
+ import {
12
+ IndexKind,
13
+ type APIRequestGenerator,
14
+ type Checker,
15
+ type IndexInfo,
16
+ type JSDocTagInfo,
17
+ type Signature,
18
+ type SignatureKind,
19
+ type SymbolFlags,
20
+ type Symbol as TSSymbol,
21
+ type Type,
21
22
  } from "@typescript/native/sync";
22
23
 
23
24
  /**
@@ -205,6 +206,12 @@ export interface Queries {
205
206
  targetOf(type: Type): Answer<Type>;
206
207
  /** The constraint of a type parameter, where it declares one; none for another type. */
207
208
  constraintOf(type: Type): Answer<Type | undefined>;
209
+ /**
210
+ * The type a type the checker leaves generic is at most, its constraints resolved, such
211
+ * as a type parameter, an indexed access, a generic tuple or a union of them; none for
212
+ * an object type that is not generic.
213
+ */
214
+ baseConstraintOf(type: Type): Answer<Type | undefined>;
208
215
  /** The object and the index of an indexed-access type; none for another type. */
209
216
  indexedAccessOf(type: Type): Answer<{ readonly object: Type; readonly index: Type } | undefined>;
210
217
  /**
@@ -232,6 +239,24 @@ export interface Queries {
232
239
  genericOf(signature: Signature): Answer<Signature | undefined>;
233
240
  /** The type of the parameter an argument at one position binds, a rest element's included. */
234
241
  typeAtPosition(signature: Signature, index: number): Answer<Type>;
242
+ /**
243
+ * The type of a signature's rest parameter: an array, a tuple, or another type such as
244
+ * a type parameter or a union of tuples; none where it has none.
245
+ */
246
+ restTypeOf(signature: Signature): Answer<Type | undefined>;
247
+ /**
248
+ * The type a number index of a type reads, which holds every element type of an array,
249
+ * a tuple or an array-like interface; none where the type has no number index.
250
+ */
251
+ numberIndexOf(type: Type): Answer<Type | undefined>;
252
+ /**
253
+ * The type parameters a mapped type is over: every one its alias's arguments, its key
254
+ * set, its `as` clause or its template names, through the types they are spelled from,
255
+ * each mapped type's own key parameter aside; none for another type.
256
+ */
257
+ mappedParametersOf(type: Type): Answer<readonly Type[]>;
258
+ /** The type of one named property of a type, its apparent members included; none where it has none. */
259
+ propertyTypeOf(type: Type, name: string): Answer<Type | undefined>;
235
260
  signatureOf(declaration: Node): Answer<Signature | undefined>;
236
261
  returnTypeOf(signature: Signature): Answer<Type | undefined>;
237
262
  typeToString(type: Type): Answer<string>;
@@ -268,6 +293,43 @@ function partQuestion(type: Type): (() => readonly (Type | undefined)[]) | undef
268
293
  return undefined;
269
294
  }
270
295
 
296
+ /** How deep {@link mappedParameters} reads, as a recursive mapped type makes a new one at each level. */
297
+ const MAPPED_DEPTH = 4;
298
+
299
+ /** The type parameters a mapped type is over, as {@link Queries.mappedParametersOf} names them. */
300
+ function mappedParameters(checker: Checker, mapped: Type): Type[] {
301
+ const own = new Set<number>();
302
+ const found = new Map<number, Type>();
303
+ const seen = new Set<number>();
304
+ const visit = (type: Type, depth: number): void => {
305
+ if (depth > MAPPED_DEPTH || seen.has(type.id)) {
306
+ return;
307
+ }
308
+ seen.add(type.id);
309
+ if (type.isMappedType()) {
310
+ own.add(type.getTypeParameter().id);
311
+ }
312
+ if (type.isTypeParameter() && !type.isThisType) {
313
+ found.set(type.id, type);
314
+ }
315
+ const aliased = (type as { readonly aliasTypeArguments?: readonly number[] })
316
+ .aliasTypeArguments;
317
+ const inner = [
318
+ ...(type.isUnionType() || type.isIntersectionType() ? type.getTypes() : []),
319
+ ...(type.isTypeReference() ? checker.getTypeArguments(type) : []),
320
+ ...(aliased === undefined || aliased.length === 0 ? [] : type.getAliasTypeArguments()),
321
+ ...(partQuestion(type)?.() ?? []),
322
+ ];
323
+ for (const part of inner) {
324
+ if (part !== undefined) {
325
+ visit(part, depth + 1);
326
+ }
327
+ }
328
+ };
329
+ visit(mapped, 0);
330
+ return [...found.values()].filter((parameter) => !own.has(parameter.id));
331
+ }
332
+
271
333
  /**
272
334
  * The guarded questions over one project's checker. `configFile` names the project
273
335
  * in the log, `batch` sends many questions in one round trip, `log` is the run's,
@@ -565,6 +627,12 @@ export function queriesOf(
565
627
  () => type.getConstraint(),
566
628
  )
567
629
  : undefined,
630
+ baseConstraintOf: (type) =>
631
+ one(
632
+ "getBaseConstraintOfType",
633
+ () => typeLocation(type),
634
+ () => checker.getBaseConstraintOfType(type),
635
+ ),
568
636
  indexedAccessOf: (type) =>
569
637
  type.isIndexedAccessType()
570
638
  ? one(
@@ -656,6 +724,37 @@ export function queriesOf(
656
724
  () => `signature ${String(signature.id)} position ${String(index)}`,
657
725
  () => signature.getTypeParameterAtPosition(index),
658
726
  ),
727
+ restTypeOf: (signature) =>
728
+ signature.hasRestParameter
729
+ ? one(
730
+ "getTypeOfSymbol",
731
+ () => `signature ${String(signature.id)} rest parameter`,
732
+ () => {
733
+ const rest = signature.getParameters().at(-1);
734
+ return rest === undefined ? undefined : checker.getTypeOfSymbol(rest);
735
+ },
736
+ )
737
+ : undefined,
738
+ numberIndexOf: (type) =>
739
+ one(
740
+ "getIndexTypeOfType",
741
+ () => typeLocation(type),
742
+ () => checker.getIndexTypeOfType(type, IndexKind.Number),
743
+ ),
744
+ mappedParametersOf: (type) =>
745
+ type.isMappedType()
746
+ ? one(
747
+ "getConstraintType",
748
+ () => typeLocation(type),
749
+ () => mappedParameters(checker, type),
750
+ )
751
+ : [],
752
+ propertyTypeOf: (type, name) =>
753
+ one(
754
+ "getTypeOfPropertyOfType",
755
+ () => `${typeLocation(type)} property ${name}`,
756
+ () => checker.getTypeOfPropertyOfType(type, name),
757
+ ),
659
758
  signatureOf: (declaration) =>
660
759
  one(
661
760
  "getSignatureFromDeclaration",
@@ -17,8 +17,7 @@ import {
17
17
  type Node,
18
18
  } from "@typescript/native/ast";
19
19
  import { SymbolFlags, type NodeHandle, type Symbol as TSSymbol } from "@typescript/native/sync";
20
- import type { AliasChains } from "../alias-chain.ts";
21
- import { resolvedTargets } from "../calls.ts";
20
+ import { resolvedTargets, type AliasChains } from "../alias-chain.ts";
22
21
  import type { DeclarationEntry, ResolvedConfig } from "../config.ts";
23
22
  import { declaredComponent, type Inventory } from "../inventory.ts";
24
23
  import { must, Unanswerable } from "../query.ts";
package/src/stores.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  import { nodeKey, type Inventory } from "./inventory.ts";
13
13
  import type { Reference } from "./references.ts";
14
14
  import type { ProjectView } from "./session.ts";
15
- import type { Exemption } from "./exempt.ts";
15
+ import type { Exemption } from "./exemptions/lifecycle.ts";
16
16
  import type { Mode } from "./tests/file-class.ts";
17
17
  import { classedAsTest, madeByTestCode, withheldBy } from "./tests/file-class.ts";
18
18
 
@@ -1,12 +1,40 @@
1
1
  import { EXIT_CLEAN } from "../contract/contract.generated.ts";
2
2
  import { productionMode, runSweep } from "../analysis.ts";
3
- import { retainedLines } from "../exempt.ts";
3
+ import type { Exemption } from "../exemptions/lifecycle.ts";
4
+ import type { InventorySymbol } from "../inventory.ts";
4
5
  import { partialNotes } from "../partial.ts";
6
+ import { positionKey } from "../position.ts";
5
7
  import { resolvePath } from "../paths.ts";
6
8
  import { resolve } from "../resolve.ts";
7
9
  import { type Verb } from "./verb.ts";
8
10
  import { diagnose } from "../product.ts";
9
11
 
12
+ /**
13
+ * One line per held-back declaration, in site order: its reference and position, then
14
+ * each record's class, site and detail, all separated by tabs.
15
+ */
16
+ function retainedLines(
17
+ symbols: readonly InventorySymbol[],
18
+ retained: readonly Exemption[],
19
+ ): readonly string[] {
20
+ const held = new Map<string, Exemption[]>();
21
+ for (const record of retained) {
22
+ held.set(record.id, [...(held.get(record.id) ?? []), record]);
23
+ }
24
+ return symbols.flatMap((symbol) => {
25
+ const records = held.get(symbol.id);
26
+ if (records === undefined) {
27
+ return [];
28
+ }
29
+ const fields = records.flatMap((record) => [
30
+ record.class,
31
+ positionKey(record.site),
32
+ record.detail,
33
+ ]);
34
+ return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
35
+ });
36
+ }
37
+
10
38
  /**
11
39
  * Writes every declaration an exemption held back in the production sweep a report is
12
40
  * built from, one per line, and nothing where no exemption held one back. One fact
@@ -1,164 +0,0 @@
1
- /**
2
- * The exemption framework: the classes of reason for which a declaration the reference
3
- * graph alone would report is live all the same. A class is policy over what one
4
- * project's checker answers, and its detector names the declarations it holds back;
5
- * this module runs the detectors, decides which of their records stand, and answers
6
- * which records held something back. It names no class's detector: the composition
7
- * hands it the table of the classes it has.
8
- */
9
- import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
10
- import { TS_EXEMPTION_CLASSES, typescriptVisibilityOf, } from "./exempt-classes.js";
11
- import { byPosition, positionKey } from "./position.js";
12
- import { Unanswerable } from "./query.js";
13
- import { bytewise } from "./text/compare.js";
14
- /**
15
- * The classes `exemptions.disabled` switches off. A class that runs on another language
16
- * only switches nothing off here, so one configuration serves every analyzer.
17
- */
18
- export function disabledClasses(config) {
19
- return new Set(config.exemptions.disabled);
20
- }
21
- /**
22
- * Whether a class whose row states `allowed` may retain a member of one visibility. A
23
- * declaration the inventory does not hold has no visibility to refuse it by.
24
- */
25
- function retainable(allowed, visibility) {
26
- if (visibility === "private") {
27
- return allowed.private;
28
- }
29
- if (visibility === "private-name") {
30
- return allowed.privateName;
31
- }
32
- return true;
33
- }
34
- /** The kinds of declaration each class can hold back, which a detector that stops holds back whole. */
35
- const RETAINABLE = new Map(EXEMPTION_CLASSES.flatMap((row) => "ts" in row.retainsKinds ? [[row.class, new Set(row.retainsKinds.ts)]] : []));
36
- /** The detail of a record a detector that could not finish holds. */
37
- const UNANSWERED_DETAIL = "kept live by a question the checker did not answer";
38
- /** Whether one record is held by a detector that could not finish. */
39
- export function isUnansweredRecord(record) {
40
- return record.detail === UNANSWERED_DETAIL;
41
- }
42
- /**
43
- * What one detector holds back over one project. A detector that stops because the
44
- * checker left a question it needs unanswered holds back every declaration of the
45
- * project its class could hold back, each recorded at its own position, so the gap
46
- * never yields a finding.
47
- */
48
- function detected(exemptionClass, detect, input) {
49
- try {
50
- return detect(input);
51
- }
52
- catch (error) {
53
- if (!(error instanceof Unanswerable)) {
54
- throw error;
55
- }
56
- const kinds = RETAINABLE.get(exemptionClass);
57
- return input.held.symbols
58
- .filter((symbol) => kinds?.has(symbol.kind) === true)
59
- .map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
60
- }
61
- }
62
- /**
63
- * Every record one project's detectors found, one per piece of evidence, before the
64
- * mode decides which of them hold: what {@link heldRecords} reads. Classes run in the
65
- * vocabulary's order and a switched-off class runs not at all; evidence on a private
66
- * member the class's row says it cannot retain is dropped, whatever the detector found.
67
- */
68
- export function evidenceRecords(input, detectors, disabled) {
69
- const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
70
- const found = [];
71
- for (const exemptionClass of TS_EXEMPTION_CLASSES) {
72
- const detect = detectors.get(exemptionClass);
73
- if (detect === undefined || disabled.has(exemptionClass)) {
74
- continue;
75
- }
76
- const allowed = typescriptVisibilityOf(exemptionClass);
77
- for (const evidence of detected(exemptionClass, detect, input)) {
78
- if (!retainable(allowed, visibility.get(evidence.id))) {
79
- continue;
80
- }
81
- found.push({
82
- id: evidence.id,
83
- class: exemptionClass,
84
- detail: evidence.detail,
85
- site: evidence.site,
86
- ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
87
- ...(evidence.implementing === undefined ? {} : { implementing: evidence.implementing }),
88
- ...(evidence.whileWritten === undefined ? {} : { whileWritten: evidence.whileWritten }),
89
- });
90
- }
91
- }
92
- return found;
93
- }
94
- /**
95
- * The records that hold given the declarations a write the mode counts stores into: one
96
- * that holds only while written holds where its declaration is among them.
97
- */
98
- export function writtenRecords(records, written) {
99
- return records.flatMap(({ whileWritten, ...record }) => whileWritten === undefined || written.has(record.id) ? [record] : []);
100
- }
101
- /** The records that hold under one mode, beside the evidence a production mode found in a test file. */
102
- export function heldRecords(records, holding) {
103
- const found = [];
104
- const inTestFiles = [];
105
- for (const record of records) {
106
- if (holding.mode.production && holding.testFiles.has(record.site.path)) {
107
- inTestFiles.push(record);
108
- }
109
- else {
110
- found.push(record);
111
- }
112
- }
113
- return { records: exemptionsOf(found), inTestFiles };
114
- }
115
- /**
116
- * Records as a run holds them: ordered by site, then class, then declaration, and one
117
- * per declaration, class, detail and file it holds while live, the one at the first
118
- * site. A fact found at many sites, or by several projects, is one record.
119
- */
120
- export function exemptionsOf(found) {
121
- const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || bytewise(a.class, b.class) || bytewise(a.id, b.id));
122
- const seen = new Set();
123
- return ordered.filter((record) => {
124
- const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
125
- if (seen.has(fact)) {
126
- return false;
127
- }
128
- seen.add(fact);
129
- return true;
130
- });
131
- }
132
- /** The records per declaration, each declaration's in the order given. */
133
- function byDeclaration(records) {
134
- const grouped = new Map();
135
- for (const record of records) {
136
- const held = grouped.get(record.id);
137
- if (held === undefined) {
138
- grouped.set(record.id, [record]);
139
- }
140
- else {
141
- held.push(record);
142
- }
143
- }
144
- return grouped;
145
- }
146
- /**
147
- * One line per held-back declaration, in site order: its reference and position, then
148
- * each record's class, site and detail, all separated by tabs.
149
- */
150
- export function retainedLines(symbols, retained) {
151
- const held = byDeclaration(retained);
152
- return symbols.flatMap((symbol) => {
153
- const records = held.get(symbol.id);
154
- if (records === undefined) {
155
- return [];
156
- }
157
- const fields = records.flatMap((record) => [
158
- record.class,
159
- positionKey(record.site),
160
- record.detail,
161
- ]);
162
- return [[symbol.ref, positionKey(symbol.position), ...fields].join("\t")];
163
- });
164
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * The values a program hands to a position the checker types from its context: the
3
- * initializer of a declaration whose type is written, an assignment's right side, an
4
- * argument, a returned value, an arrow function's expression body, an array element, an
5
- * object property, and the operand of `as` or `satisfies`.
6
- */
7
- import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isBinaryExpression, isCallExpression, isConditionalExpression, isIdentifier, isNewExpression, isParameterDeclaration, isParenthesizedExpression, isPropertyAssignment, isPropertyDeclaration, isReturnStatement, isSatisfiesExpression, isShorthandPropertyAssignment, isSpreadElement, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
8
- /** The operators of an assignment whose right side is stored into the left side's type. */
9
- const STORES = new Set([
10
- SyntaxKind.EqualsToken,
11
- SyntaxKind.QuestionQuestionEqualsToken,
12
- SyntaxKind.BarBarEqualsToken,
13
- SyntaxKind.AmpersandAmpersandEqualsToken,
14
- ]);
15
- /** Whether a declaration writes its type, so its initializer is read in that type's context. */
16
- function writesType(node) {
17
- return node.type !== undefined;
18
- }
19
- /**
20
- * The values one written expression hands on: the expression itself, or what it
21
- * evaluates to through parentheses, a conditional and the operators whose result is
22
- * one of their operands.
23
- */
24
- export function valuesOf(expression) {
25
- if (isParenthesizedExpression(expression)) {
26
- return valuesOf(expression.expression);
27
- }
28
- if (isConditionalExpression(expression)) {
29
- return [...valuesOf(expression.whenTrue), ...valuesOf(expression.whenFalse)];
30
- }
31
- if (isBinaryExpression(expression)) {
32
- const operator = expression.operatorToken.kind;
33
- if (operator === SyntaxKind.QuestionQuestionToken || operator === SyntaxKind.BarBarToken) {
34
- return [...valuesOf(expression.left), ...valuesOf(expression.right)];
35
- }
36
- if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.CommaToken) {
37
- return valuesOf(expression.right);
38
- }
39
- }
40
- return [expression];
41
- }
42
- /** The expressions one node hands to a position typed by its context. */
43
- export function handedOn(node) {
44
- if ((isVariableDeclaration(node) || isPropertyDeclaration(node) || isParameterDeclaration(node)) &&
45
- node.initializer !== undefined &&
46
- writesType(node)) {
47
- return [node.initializer];
48
- }
49
- if (isBinaryExpression(node) && STORES.has(node.operatorToken.kind)) {
50
- return [node.right];
51
- }
52
- if (isCallExpression(node) || isNewExpression(node)) {
53
- return (node.arguments ?? []).filter((argument) => !isSpreadElement(argument));
54
- }
55
- if (isReturnStatement(node)) {
56
- return node.expression === undefined ? [] : [node.expression];
57
- }
58
- if (isArrowFunction(node)) {
59
- return node.body.kind === SyntaxKind.Block ? [] : [node.body];
60
- }
61
- if (isArrayLiteralExpression(node)) {
62
- return node.elements.filter((element) => !isSpreadElement(element) && element.kind !== SyntaxKind.OmittedExpression);
63
- }
64
- if (isPropertyAssignment(node)) {
65
- return [node.initializer];
66
- }
67
- if (isShorthandPropertyAssignment(node)) {
68
- return isIdentifier(node.name) ? [node.name] : [];
69
- }
70
- if (isAsExpression(node) || isSatisfiesExpression(node)) {
71
- return [node.expression];
72
- }
73
- return [];
74
- }
package/src/decorator.ts DELETED
@@ -1,78 +0,0 @@
1
- /**
2
- * The decorator class: a member a decorator expression is attached to, and every member
3
- * of a class one is attached to but the member a parameter property declares, is held
4
- * back, because the decorator may reach the member by its name. The rule is read from
5
- * the tree alone. The decorator's own use of what it decorates is the reference pass's
6
- * `decorator` reference; this class records only that the declaration is held back,
7
- * static members of a decorated class included.
8
- */
9
-
10
- import type { Node } from "@typescript/native/ast";
11
- import { decoratorsOf, spelledDecorator } from "./calls.ts";
12
- import type { DetectorInput, Evidence } from "./exempt.ts";
13
- import {
14
- isParameterProperty,
15
- nodeKey,
16
- type InventorySymbol,
17
- type SymbolKind,
18
- } from "./inventory.ts";
19
- import { renderPosition } from "./position.ts";
20
-
21
- /** The kinds of declaration a decorator is attached to and holds back. */
22
- const DECORATED: ReadonlySet<SymbolKind> = new Set(["class", "method", "class-member"]);
23
-
24
- /**
25
- * The decorator detector: each decorated member, and each member of a decorated
26
- * class, recorded at the decorator.
27
- */
28
- export function decorator<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
29
- const { project, held, targetRoot } = input;
30
- const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
31
- const membersOf = new Map<string, InventorySymbol[]>();
32
- for (const symbol of held.symbols) {
33
- if (symbol.kind === "method" || symbol.kind === "class-member") {
34
- const members = membersOf.get(symbol.parent);
35
- if (members === undefined) {
36
- membersOf.set(symbol.parent, [symbol]);
37
- } else {
38
- members.push(symbol);
39
- }
40
- }
41
- }
42
-
43
- const decorated: {
44
- readonly symbol: InventorySymbol;
45
- readonly detail: string;
46
- readonly site: Evidence["site"];
47
- }[] = [];
48
- // A decorated class's container passes each constructor argument, which keeps the
49
- // parameter, not the member a parameter property stores it in.
50
- const parameterMembers = new Set<string>();
51
- for (const file of project.ownSourceFiles()) {
52
- const visit = (node: Node): void => {
53
- const id = held.declarations.get(nodeKey(file, node));
54
- const symbol = id === undefined ? undefined : byId.get(id);
55
- if (id !== undefined && isParameterProperty(node)) {
56
- parameterMembers.add(id);
57
- }
58
- // A parameter property's decorator is attached to its parameter, and to no member.
59
- if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
60
- for (const attached of decoratorsOf(node)) {
61
- decorated.push({
62
- symbol,
63
- detail: `decorated by ${spelledDecorator(attached)}`,
64
- site: renderPosition(file, targetRoot, attached.getStart()),
65
- });
66
- }
67
- }
68
- node.forEachChild(visit);
69
- };
70
- file.forEachChild(visit);
71
- }
72
- return decorated.flatMap(({ symbol, detail, site }) =>
73
- (symbol.kind === "class"
74
- ? (membersOf.get(symbol.id) ?? []).filter((member) => !parameterMembers.has(member.id))
75
- : [symbol]
76
- ).map((member) => ({ id: member.id, detail, site })),
77
- );
78
- }