@starci/eslint-canon-fe 9.5.23 → 9.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/button-intent.md +1 -1
  3. package/package.json +1 -1
  4. package/runtime/bundle.json +192 -0
  5. package/runtime/engine/by-code-unit.mjs +6 -6
  6. package/runtime/engine/model-config.mjs +98 -76
  7. package/runtime/engine/orca-config.mjs +22 -17
  8. package/runtime/engine/release-config.mjs +22 -18
  9. package/runtime/engine/removed-vocabulary.mjs +21 -20
  10. package/runtime/engine/resources-config.mjs +42 -35
  11. package/runtime/engine/runtime-root.mjs +11 -13
  12. package/runtime/engine/secrets.mjs +170 -102
  13. package/runtime/engine/sonar-config.mjs +16 -13
  14. package/runtime/engine/temp-root.mjs +3 -9
  15. package/runtime/knowledge/hfs/canon-pins.yaml +10 -10
  16. package/runtime/knowledge/hfs/slots.yaml +2 -2
  17. package/runtime/knowledge/sonar-gate.yaml +1 -1
  18. package/runtime/packages/hfs/templates/app/package-scripts/package.json +1 -1
  19. package/runtime/scripts/api/git/cat-file.mjs +0 -1
  20. package/runtime/scripts/api/git/config-get.mjs +1 -2
  21. package/runtime/scripts/api/git/diff.mjs +0 -1
  22. package/runtime/scripts/api/git/lib.mjs +32 -32
  23. package/runtime/scripts/api/git/ls-files.mjs +0 -1
  24. package/runtime/scripts/api/git/ls-tree.mjs +0 -1
  25. package/runtime/scripts/api/git/merge-base.mjs +2 -3
  26. package/runtime/scripts/api/git/remote.mjs +0 -1
  27. package/runtime/scripts/api/git/rev-parse-query.mjs +0 -1
  28. package/runtime/scripts/api/git/show.mjs +0 -1
  29. package/runtime/scripts/hfs/allows.mjs +37 -38
  30. package/runtime/scripts/hfs/architecture/ast-walks.mjs +180 -163
  31. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +80 -73
  32. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +88 -77
  33. package/runtime/scripts/hfs/architecture/backend.mjs +378 -357
  34. package/runtime/scripts/hfs/architecture/background-unowned.mjs +201 -187
  35. package/runtime/scripts/hfs/architecture/canon-clones.mjs +260 -209
  36. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +149 -116
  37. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +72 -68
  38. package/runtime/scripts/hfs/architecture/clones.mjs +212 -217
  39. package/runtime/scripts/hfs/architecture/config-unread.mjs +7 -9
  40. package/runtime/scripts/hfs/architecture/config.mjs +302 -268
  41. package/runtime/scripts/hfs/architecture/connection-map.mjs +258 -225
  42. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +80 -71
  43. package/runtime/scripts/hfs/architecture/context-coupling.mjs +82 -77
  44. package/runtime/scripts/hfs/architecture/context-map.mjs +267 -234
  45. package/runtime/scripts/hfs/architecture/context-owner.mjs +73 -61
  46. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +47 -43
  47. package/runtime/scripts/hfs/architecture/context-transaction.mjs +54 -52
  48. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +113 -104
  49. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +288 -270
  50. package/runtime/scripts/hfs/architecture/contracts.mjs +408 -393
  51. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +50 -51
  52. package/runtime/scripts/hfs/architecture/dead-exports-bindings.mjs +143 -119
  53. package/runtime/scripts/hfs/architecture/dead-exports.mjs +286 -228
  54. package/runtime/scripts/hfs/architecture/default-deny.mjs +129 -110
  55. package/runtime/scripts/hfs/architecture/doc-language.mjs +24 -19
  56. package/runtime/scripts/hfs/architecture/entity-registry.mjs +20 -22
  57. package/runtime/scripts/hfs/architecture/entrypoint.mjs +47 -45
  58. package/runtime/scripts/hfs/architecture/error-codes.mjs +37 -35
  59. package/runtime/scripts/hfs/architecture/error-masked.mjs +55 -59
  60. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +24 -23
  61. package/runtime/scripts/hfs/architecture/feature-shape.mjs +40 -38
  62. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +21 -21
  63. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +106 -98
  64. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +153 -150
  65. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +161 -149
  66. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +343 -305
  67. package/runtime/scripts/hfs/architecture/frontend-world.mjs +340 -294
  68. package/runtime/scripts/hfs/architecture/frontend.mjs +174 -184
  69. package/runtime/scripts/hfs/architecture/function-spec-providers.mjs +124 -106
  70. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +52 -48
  71. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +81 -71
  72. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +155 -141
  73. package/runtime/scripts/hfs/architecture/identity.mjs +141 -125
  74. package/runtime/scripts/hfs/architecture/index.mjs +272 -272
  75. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +45 -43
  76. package/runtime/scripts/hfs/architecture/jwt-ast.mjs +195 -176
  77. package/runtime/scripts/hfs/architecture/machine-ast.mjs +163 -155
  78. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +19 -20
  79. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +177 -172
  80. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +100 -78
  81. package/runtime/scripts/hfs/architecture/next-data-inference.mjs +170 -154
  82. package/runtime/scripts/hfs/architecture/next-data-key.mjs +311 -275
  83. package/runtime/scripts/hfs/architecture/next-data-symbols.mjs +17 -17
  84. package/runtime/scripts/hfs/architecture/next-data.mjs +351 -334
  85. package/runtime/scripts/hfs/architecture/operation-contract-roots.mjs +39 -37
  86. package/runtime/scripts/hfs/architecture/owners.mjs +97 -98
  87. package/runtime/scripts/hfs/architecture/package-shape.mjs +56 -55
  88. package/runtime/scripts/hfs/architecture/presentation.mjs +138 -130
  89. package/runtime/scripts/hfs/architecture/reachability.mjs +258 -241
  90. package/runtime/scripts/hfs/architecture/register-once.mjs +152 -139
  91. package/runtime/scripts/hfs/architecture/registration.mjs +301 -267
  92. package/runtime/scripts/hfs/architecture/required-files.mjs +172 -167
  93. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +85 -77
  94. package/runtime/scripts/hfs/architecture/schema-owner.mjs +206 -181
  95. package/runtime/scripts/hfs/architecture/server-action.mjs +48 -48
  96. package/runtime/scripts/hfs/architecture/side-boundary.mjs +2 -5
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +261 -259
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +304 -278
  99. package/runtime/scripts/hfs/architecture/sql-keys.mjs +277 -194
  100. package/runtime/scripts/hfs/architecture/sql-owner.mjs +236 -194
  101. package/runtime/scripts/hfs/architecture/sql-returning.mjs +46 -45
  102. package/runtime/scripts/hfs/architecture/sql-token-forms.mjs +86 -74
  103. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +456 -341
  104. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +35 -30
  105. package/runtime/scripts/hfs/architecture/surface.mjs +64 -74
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +209 -216
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +165 -155
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +161 -154
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +155 -149
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +255 -250
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +358 -318
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +110 -92
  113. package/runtime/scripts/hfs/declaration-shape.mjs +140 -103
  114. package/runtime/scripts/hfs/declaration-slots.mjs +64 -61
  115. package/runtime/scripts/hfs/edition-slots.mjs +43 -40
  116. package/runtime/scripts/hfs/linear-text.mjs +20 -20
  117. package/runtime/scripts/hfs/param-names.mjs +6 -11
  118. package/runtime/scripts/hfs/path-findings.mjs +120 -100
  119. package/runtime/scripts/hfs/repo-identity.mjs +55 -42
  120. package/runtime/scripts/hfs/rule-catalog.mjs +353 -257
  121. package/runtime/scripts/hfs/rule-params-shape.mjs +60 -47
  122. package/runtime/scripts/hfs/rules/database-migrations.mjs +428 -388
  123. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +37 -36
  124. package/runtime/scripts/hfs/rules/read.mjs +19 -15
  125. package/runtime/scripts/hfs/rules/secrets.mjs +1 -1
  126. package/runtime/scripts/hfs/rules/spec-placement.mjs +12 -13
  127. package/runtime/scripts/hfs/slot-app-view.mjs +69 -74
  128. package/runtime/scripts/hfs/slot-classify.mjs +42 -38
  129. package/runtime/scripts/hfs/slot-errors.mjs +8 -7
  130. package/runtime/scripts/hfs/slot-imports.mjs +70 -63
  131. package/runtime/scripts/hfs/slot-manifest-shape.mjs +74 -52
  132. package/runtime/scripts/hfs/slot-match.mjs +122 -111
  133. package/runtime/scripts/hfs/slot-path.mjs +0 -2
  134. package/runtime/scripts/hfs/slot-required.mjs +65 -59
  135. package/runtime/scripts/hfs/slot-semantic-problems.mjs +138 -112
  136. package/runtime/scripts/hfs/slot-side-problems.mjs +23 -19
  137. package/runtime/scripts/hfs/slots.mjs +175 -152
  138. package/runtime/scripts/hfs/test-world-slot.mjs +0 -1
  139. package/runtime/scripts/hfs/trailing-slashes.mjs +4 -4
  140. package/runtime/scripts/hfs/typescript-programs.mjs +66 -69
  141. package/runtime/scripts/hfs/view.mjs +38 -40
  142. package/runtime/scripts/lib/dockerfile.mjs +131 -114
  143. package/runtime/scripts/lib/env.mjs +3 -6
  144. package/runtime/scripts/lib/event-contract.mjs +73 -78
  145. package/runtime/scripts/lib/git.mjs +15 -16
  146. package/runtime/scripts/lib/glob.mjs +23 -14
  147. package/runtime/scripts/lib/grammar-lint.mjs +33 -37
  148. package/runtime/scripts/lib/i18n.mjs +32 -35
  149. package/runtime/scripts/lib/in-order.mjs +57 -38
  150. package/runtime/scripts/lib/json-clone.mjs +45 -36
  151. package/runtime/scripts/lib/json.mjs +26 -14
  152. package/runtime/scripts/lib/language.mjs +133 -129
  153. package/runtime/scripts/lib/list.mjs +9 -14
  154. package/runtime/scripts/lib/mutation-fence.mjs +10 -5
  155. package/runtime/scripts/lib/path-key.mjs +53 -38
  156. package/runtime/scripts/lib/read-text.mjs +30 -17
  157. package/runtime/scripts/lib/regex.mjs +1 -2
  158. package/runtime/scripts/lib/same-text.mjs +0 -1
  159. package/runtime/scripts/lib/sops-envelope.mjs +148 -120
  160. package/runtime/scripts/lib/stack-services.mjs +128 -129
  161. package/runtime/scripts/lib/ts-analysis.mjs +28 -26
  162. package/runtime/scripts/lib/ts-ast.mjs +36 -42
  163. package/runtime/scripts/lib/walk.mjs +33 -27
  164. package/runtime/scripts/lib/yaml-cached.mjs +4 -4
@@ -1,233 +1,250 @@
1
- // ast-walks.mjs - AST walks the architecture checkers share (moved out of typescript.mjs, which keeps the program loading and module resolution).
2
1
  import { canonical, isInside } from './config.mjs';
3
2
  import { hasAnyFlag, isConstVariable, sourceLocation } from '../../lib/ts-ast.mjs';
4
3
  import { UNPROVEN_FRAMEWORK, isUnshadowedCommonJsRequire, relativePath, unwrapExpression } from './typescript.mjs';
5
-
6
4
  /** `expression` unwrapped by the caller's own predicate set (a predicate absent on an older ts reads as never). */
7
- export function unwrapEach(ts, expression, predicates) {
8
- let current = expression;
9
- while (current && predicates.some((predicate) => predicate?.(current))) current = current.expression;
10
- return current;
5
+ export function unwrapEach(_ts, expression, predicates) {
6
+ let current = expression;
7
+ // The predicates the caller passes select wrapper nodes, which all carry an `expression` child.
8
+ while (current && predicates.some((predicate) => predicate?.(current)))
9
+ current = current.expression;
10
+ return current;
11
11
  }
12
-
13
12
  /** `value` with every TS alias hop resolved (a re-exported import reads as its target symbol). */
14
13
  export function normalizedSymbolValue(ts, checker, value = null) {
15
- let symbol = value;
16
- const seen = new Set();
17
- while (symbol && hasAnyFlag(symbol.flags, ts.SymbolFlags.Alias) && !seen.has(symbol)) {
18
- seen.add(symbol);
19
- const target = checker.getAliasedSymbol(symbol);
20
- if (!target || target === symbol) break;
21
- symbol = target;
22
- }
23
- return symbol;
14
+ let symbol = value;
15
+ const seen = new Set();
16
+ while (symbol && hasAnyFlag(symbol.flags, ts.SymbolFlags.Alias) && !seen.has(symbol)) {
17
+ seen.add(symbol);
18
+ const target = checker.getAliasedSymbol(symbol);
19
+ if (!target || target === symbol)
20
+ break;
21
+ symbol = target;
22
+ }
23
+ return symbol;
24
24
  }
25
-
26
25
  /** The symbol `node` names, aliases resolved. */
27
26
  export function normalizedSymbol(ts, checker, node) {
28
- return normalizedSymbolValue(ts, checker, checker?.getSymbolAtLocation(node) ?? null);
27
+ return normalizedSymbolValue(ts, checker, checker?.getSymbolAtLocation(node) ?? null);
29
28
  }
30
-
31
29
  /** The identifier or member-name node an expression selects through unwrapExpression, or null. */
32
30
  export function selectedNode(ts, expression) {
33
- expression = unwrapExpression(ts, expression);
34
- if (ts.isIdentifier(expression)) return expression;
35
- if (ts.isPropertyAccessExpression(expression)) return expression.name;
36
- if (ts.isElementAccessExpression(expression) && ts.isStringLiteralLike(expression.argumentExpression)) return expression.argumentExpression;
37
- return null;
31
+ const unwrapped = unwrapExpression(ts, expression);
32
+ if (ts.isIdentifier(unwrapped))
33
+ return unwrapped;
34
+ if (ts.isPropertyAccessExpression(unwrapped))
35
+ return unwrapped.name;
36
+ if (ts.isElementAccessExpression(unwrapped) && ts.isStringLiteralLike(unwrapped.argumentExpression))
37
+ return unwrapped.argumentExpression;
38
+ return null;
38
39
  }
39
-
40
40
  /**
41
41
  * The symbol `node` ultimately evaluates to: its own symbol, followed through a single `const` variable
42
42
  * initializer (and so on). `unselected: 'null'` answers null when `node` selects no name at all.
43
43
  */
44
44
  export function valueSymbol(ts, checker, node, seen = new Set(), { unselected = 'node' } = {}) {
45
- const selected = selectedNode(ts, node);
46
- if (!selected && unselected === 'null') return null;
47
- const symbol = normalizedSymbol(ts, checker, selected ?? node);
48
- if (!symbol || seen.has(symbol)) return symbol;
49
- seen.add(symbol);
50
- const declarations = symbol.getDeclarations?.() ?? [];
51
- if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
52
- && isConstVariable(ts, declarations[0])) {
53
- return valueSymbol(ts, checker, declarations[0].initializer, seen, { unselected });
54
- }
55
- return symbol;
56
- }
57
-
45
+ const selected = selectedNode(ts, node);
46
+ if (!selected && unselected === 'null')
47
+ return null;
48
+ const symbol = normalizedSymbol(ts, checker, selected ?? node);
49
+ if (!symbol || seen.has(symbol))
50
+ return symbol;
51
+ seen.add(symbol);
52
+ const declarations = symbol.getDeclarations?.() ?? [];
53
+ const [only] = declarations;
54
+ if (declarations.length === 1 && only && ts.isVariableDeclaration(only) && only.initializer
55
+ && isConstVariable(ts, only)) {
56
+ return valueSymbol(ts, checker, only.initializer, seen, { unselected });
57
+ }
58
+ return symbol;
59
+ }
58
60
  /**
59
61
  * The expressions `declaration` returns: its body when it is an expression-bodied arrow/function, else every
60
62
  * `return`'s argument without descending into nested functions. `functionLike` overrides which node kinds bound
61
63
  * that descent (default the four callable declaration kinds; pass `ts.isFunctionLike` for the wider set).
62
64
  */
63
65
  export function returnedExpressions(ts, declaration, { functionLike = null } = {}) {
64
- const boundary = functionLike ?? ((node) => ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)
65
- || ts.isArrowFunction(node) || ts.isFunctionExpression(node));
66
- if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && !ts.isBlock(declaration.body)) return [declaration.body];
67
- const body = boundary(declaration) ? declaration.body : null;
68
- if (!body || !ts.isBlock(body)) return [];
69
- const returned = [];
70
- const visit = (node) => {
71
- if (node !== body && boundary(node)) return;
72
- if (ts.isReturnStatement(node) && node.expression) returned.push(node.expression);
73
- else ts.forEachChild(node, visit);
74
- };
75
- visit(body);
76
- return returned;
77
- }
78
-
66
+ const boundary = functionLike ?? ((node) => ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)
67
+ || ts.isArrowFunction(node) || ts.isFunctionExpression(node));
68
+ if ((ts.isArrowFunction(declaration) || ts.isFunctionExpression(declaration)) && !ts.isBlock(declaration.body))
69
+ return [declaration.body];
70
+ // A node the boundary predicate accepts is a function-like, which carries a `body`.
71
+ const body = boundary(declaration) ? declaration.body : null;
72
+ if (!body || !ts.isBlock(body))
73
+ return [];
74
+ const returned = [];
75
+ const visit = (node) => {
76
+ if (node !== body && boundary(node))
77
+ return;
78
+ if (ts.isReturnStatement(node) && node.expression)
79
+ returned.push(node.expression);
80
+ else
81
+ ts.forEachChild(node, visit);
82
+ };
83
+ visit(body);
84
+ return returned;
85
+ }
79
86
  /** A node's decorators across TS versions (ts.getDecorators, else the legacy `decorators` property). */
80
87
  export function nodeDecorators(ts, node) {
81
- return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : node.decorators ?? [];
88
+ return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : node.decorators ?? [];
82
89
  }
83
-
84
90
  /** One finding row: rule id, root-relative path, node position and message, plus rule extras. */
85
91
  export function violation(config, sourceFile, node, ruleId, message, extra = {}) {
86
- return { ruleId, path: relativePath(config.root, sourceFile.fileName), ...sourceLocation(sourceFile, node), message, ...extra };
92
+ return { ruleId, path: relativePath(config.root, sourceFile.fileName), ...sourceLocation(sourceFile, node), message, ...extra };
87
93
  }
88
-
89
94
  /** Whether `match` holds for `node` or a descendant (the walk stops at the first match). */
90
95
  export function anyDescendant(ts, node, match) {
91
- let found = false;
92
- const visit = (current) => {
93
- if (found) return;
94
- if (match(current)) { found = true; return; }
95
- ts.forEachChild(current, visit);
96
- };
97
- visit(node);
98
- return found;
99
- }
100
-
96
+ let found = false;
97
+ const visit = (current) => {
98
+ if (found)
99
+ return;
100
+ if (match(current)) {
101
+ found = true;
102
+ return;
103
+ }
104
+ ts.forEachChild(current, visit);
105
+ };
106
+ visit(node);
107
+ return found;
108
+ }
101
109
  /**
102
110
  * The framework kinds `expression` can produce: `directOf(ts, checker, {symbol, selected, targets})` maps a
103
111
  * selected node's symbols to a kind (default `targets.get(symbol)`); `shorthandOf` (when set) also resolves
104
112
  * shorthand-property declarations and descends property assignments; `newExpression` descends `new X()` callees.
105
113
  */
106
- const addKinds = (target, source) => { for (const kind of source) target.add(kind); };
107
-
114
+ const addKinds = (target, source) => { for (const kind of source)
115
+ target.add(kind); };
108
116
  const kindsFromReturns = (ts, checker, declaration, targets, options) => {
109
- const kinds = new Set();
110
- for (const returned of returnedExpressions(ts, declaration)) addKinds(kinds, tracedFrameworkKinds(ts, checker, returned, targets, options));
111
- return kinds;
117
+ const kinds = new Set();
118
+ for (const returned of returnedExpressions(ts, declaration))
119
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, returned, targets, options));
120
+ return kinds;
112
121
  };
113
-
114
122
  const kindsFromDeclaration = (ts, checker, declaration, targets, options, shorthandOf) => {
115
- const kinds = new Set();
116
- if (ts.isVariableDeclaration(declaration) && declaration.initializer)
117
- addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
118
- if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
119
- const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
120
- const kind = shorthandOf(ts, checker, target, targets);
121
- if (kind) kinds.add(kind);
122
- }
123
- if (shorthandOf && ts.isPropertyAssignment(declaration))
124
- addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
125
- addKinds(kinds, kindsFromReturns(ts, checker, declaration, targets, options));
126
- return kinds;
123
+ const kinds = new Set();
124
+ if (ts.isVariableDeclaration(declaration) && declaration.initializer)
125
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
126
+ if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
127
+ const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
128
+ const kind = shorthandOf(ts, checker, target, targets);
129
+ if (kind)
130
+ kinds.add(kind);
131
+ }
132
+ if (shorthandOf && ts.isPropertyAssignment(declaration))
133
+ addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
134
+ addKinds(kinds, kindsFromReturns(ts, checker, declaration, targets, options));
135
+ return kinds;
127
136
  };
128
-
129
137
  const kindsFromSymbol = (ts, checker, symbol, targets, seen, options) => {
130
- const nextSeen = new Set(seen).add(symbol);
131
- const declarationOptions = { ...options, seen: nextSeen };
132
- const kinds = new Set();
133
- for (const declaration of symbol.getDeclarations?.() ?? [])
134
- addKinds(kinds, kindsFromDeclaration(ts, checker, declaration, targets, declarationOptions, options.shorthandOf));
135
- return kinds;
138
+ const nextSeen = new Set(seen).add(symbol);
139
+ const declarationOptions = { ...options, seen: nextSeen };
140
+ const kinds = new Set();
141
+ for (const declaration of symbol.getDeclarations?.() ?? [])
142
+ addKinds(kinds, kindsFromDeclaration(ts, checker, declaration, targets, declarationOptions, options.shorthandOf));
143
+ return kinds;
136
144
  };
137
-
138
- const defaultDirectOf = (ts, checker, { symbol, targets }) => targets.get(symbol) ?? null;
139
-
140
- export function tracedFrameworkKinds(ts, checker, expression, targets,
141
- { seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
142
- if (!expression) return new Set();
143
- if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
144
- const unwrapped = unwrapExpression(ts, expression);
145
- const selected = selectedNode(ts, unwrapped);
146
- const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
147
- const direct = symbol ? (directOf ?? defaultDirectOf)(ts, checker, { symbol, selected, targets }) : null;
148
- if (direct) return new Set([direct]);
149
- const options = { depth: depth + 1, directOf, shorthandOf, newExpression };
150
- if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) return kindsFromReturns(ts, checker, unwrapped, targets, { ...options, seen });
151
- if (ts.isCallExpression(unwrapped)) {
152
- const kinds = tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
153
- if (kinds.size) return kinds;
154
- }
155
- if (newExpression && ts.isNewExpression(unwrapped))
156
- return tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
157
- if (!symbol || seen.has(symbol)) return new Set();
158
- return kindsFromSymbol(ts, checker, symbol, targets, seen, options);
159
- }
160
-
145
+ const defaultDirectOf = (_ts, _checker, { symbol, targets }) => targets.get(symbol) ?? null;
146
+ export function tracedFrameworkKinds(ts, checker, expression, targets, { seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
147
+ if (!expression)
148
+ return new Set();
149
+ if (depth > 10)
150
+ return new Set([UNPROVEN_FRAMEWORK]);
151
+ const unwrapped = unwrapExpression(ts, expression);
152
+ const selected = selectedNode(ts, unwrapped);
153
+ const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
154
+ const direct = symbol && selected ? (directOf ?? defaultDirectOf)(ts, checker, { symbol, selected, targets }) : null;
155
+ if (direct)
156
+ return new Set([direct]);
157
+ const options = { depth: depth + 1, directOf, shorthandOf, newExpression };
158
+ if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped))
159
+ return kindsFromReturns(ts, checker, unwrapped, targets, { ...options, seen });
160
+ if (ts.isCallExpression(unwrapped)) {
161
+ const kinds = tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
162
+ if (kinds.size)
163
+ return kinds;
164
+ }
165
+ if (newExpression && ts.isNewExpression(unwrapped))
166
+ return tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
167
+ if (!symbol || seen.has(symbol))
168
+ return new Set();
169
+ return kindsFromSymbol(ts, checker, symbol, targets, seen, options);
170
+ }
161
171
  /** 'unproven framework' | 'multiple framework' | the one kind | null, for a decorator whose expression constructs. */
162
172
  export function constructedDecoratorKind(ts, checker, decorator, targets, traceOpts = {}) {
163
- const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
164
- const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
165
- if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
166
- if (kinds.size === 1) return [...kinds][0];
167
- return kinds.size ? 'multiple framework' : null;
173
+ const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
174
+ const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
175
+ if (kinds.has(UNPROVEN_FRAMEWORK))
176
+ return 'unproven framework';
177
+ if (kinds.size === 1)
178
+ return [...kinds][0] ?? null;
179
+ return kinds.size ? 'multiple framework' : null;
168
180
  }
169
-
170
181
  /** The target a decorator resolves to through a `let`/`var` alias's single initializer, or null. */
171
182
  export function mutableDecoratorKind(ts, checker, decorator, targets) {
172
- const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
173
- const selected = selectedNode(ts, callee);
174
- const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
175
- const declarations = symbol?.getDeclarations?.() ?? [];
176
- if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
177
- || isConstVariable(ts, declarations[0])) return null;
178
- return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
179
- }
180
-
183
+ const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
184
+ const selected = selectedNode(ts, callee);
185
+ const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
186
+ const declarations = symbol?.getDeclarations?.() ?? [];
187
+ const [only] = declarations;
188
+ if (declarations.length !== 1 || !only || !ts.isVariableDeclaration(only) || !only.initializer
189
+ || isConstVariable(ts, only))
190
+ return null;
191
+ const resolved = valueSymbol(ts, checker, only.initializer);
192
+ return (resolved ? targets.get(resolved) : undefined) ?? null;
193
+ }
181
194
  /** The callee expression of a decorator (the called expression of `@x(...)`, else the expression itself). */
182
195
  export function decoratorCallee(ts, decorator) {
183
- return ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
196
+ return ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
184
197
  }
185
-
186
198
  /**
187
199
  * The module `statement` (import, export-from or import-equals) statically binds: its specifier text, the
188
200
  * module's symbol and its exported symbols by name (aliases resolved); `symbol` is null when the module does
189
201
  * not resolve. Null when the statement names no static specifier.
190
202
  */
191
203
  export function moduleExportsOf(ts, checker, statement) {
192
- let moduleSpecifier = null;
193
- if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
194
- && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) moduleSpecifier = statement.moduleSpecifier;
195
- let importEquals = null;
196
- if (ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
197
- && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)) importEquals = statement.moduleReference.expression;
198
- const specifierNode = moduleSpecifier ?? importEquals;
199
- const specifier = specifierNode?.text;
200
- if (specifier == null) return null;
201
- const symbol = checker.getSymbolAtLocation(specifierNode) ?? null;
202
- const exports = new Map((symbol ? checker.getExportsOfModule(symbol) : []).map(item => [item.getName(), normalizedSymbolValue(ts, checker, item)]));
203
- return { specifierNode, specifier, symbol, exports };
204
- }
205
-
204
+ let moduleSpecifier = null;
205
+ if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
206
+ && statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier))
207
+ moduleSpecifier = statement.moduleSpecifier;
208
+ let importEquals = null;
209
+ if (ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
210
+ && statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression))
211
+ importEquals = statement.moduleReference.expression;
212
+ const specifierNode = moduleSpecifier ?? importEquals;
213
+ if (!specifierNode)
214
+ return null;
215
+ const specifier = specifierNode.text;
216
+ const symbol = checker.getSymbolAtLocation(specifierNode) ?? null;
217
+ const exports = new Map((symbol ? checker.getExportsOfModule(symbol) : [])
218
+ .map((item) => [item.getName(), normalizedSymbolValue(ts, checker, item) ?? null]));
219
+ return { specifierNode, specifier, symbol, exports };
220
+ }
206
221
  /**
207
222
  * The program source files a framework-target scan of `checker` judges: every file in `localFiles`, plus (when
208
223
  * `root` is given) declaration files inside the root but outside node_modules. Null when no program of the
209
224
  * context owns the checker.
210
225
  */
211
226
  export function programSourcesOf(context, checker, localFiles, { root = null } = {}) {
212
- const program = context.programs.find(candidate => candidate.getTypeChecker() === checker) ?? null;
213
- if (!program) return null;
214
- return program.getSourceFiles().filter(sourceFile => localFiles.has(canonical(sourceFile.fileName))
215
- || (root && sourceFile.isDeclarationFile && isInside(root, sourceFile.fileName)
216
- && !sourceFile.fileName.replaceAll('\\', '/').includes('/node_modules/')));
227
+ const program = context.programs.find(candidate => candidate.getTypeChecker() === checker) ?? null;
228
+ if (!program)
229
+ return null;
230
+ return program.getSourceFiles().filter(sourceFile => localFiles.has(canonical(sourceFile.fileName))
231
+ || (root && sourceFile.isDeclarationFile && isInside(root, sourceFile.fileName)
232
+ && !sourceFile.fileName.replaceAll('\\', '/').includes('/node_modules/')));
217
233
  }
218
-
219
234
  /**
220
235
  * The CommonJS require() caveat every framework scan reports: each `require('<specifier>')` `accepted` pushes
221
236
  * `${filePath} uses a CommonJS <label ?? specifier> <detail>` (the import-shape reasoning cannot prove that binding).
222
237
  */
223
- export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail) {
224
- const label = arguments[7] ?? null;
225
- const visit = node => {
226
- if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)
227
- && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]) && accepted(node.arguments[0].text)) {
228
- reasons.push(`${filePath} uses a CommonJS ${label ?? node.arguments[0].text} ${detail}`);
229
- }
230
- ts.forEachChild(node, visit);
231
- };
232
- visit(sourceFile);
238
+ export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail, labelArgument) {
239
+ const label = labelArgument ?? null;
240
+ const visit = (node) => {
241
+ if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression) && node.arguments.length === 1) {
242
+ const [argument] = node.arguments;
243
+ if (argument && ts.isStringLiteralLike(argument) && accepted(argument.text)) {
244
+ reasons.push(`${filePath} uses a CommonJS ${label ?? argument.text} ${detail}`);
245
+ }
246
+ }
247
+ ts.forEachChild(node, visit);
248
+ };
249
+ visit(sourceFile);
233
250
  }
@@ -1,96 +1,103 @@
1
1
  import { readTextFile } from '../../lib/read-text.mjs';
2
-
3
2
  // Owner ruling 2026-09-29 (layout 2026-09-30): integration, e2e and contract run MANUALLY only. No hook, default typecheck,
4
3
  // coverage run or automatic CI trigger may include those trees or run those projects. Linting the e2e files is not running them: ESLint reads them
5
4
  // as syntax in the one repository-wide lint run (the factory's e2e block), so no `lint:e2e` command exists to judge.
6
5
  const E2E_COMMAND = new RegExp([
7
- String.raw`\btest:(?:e2e|integration|contract)\b|`,
8
- String.raw`\btypecheck:tests\b|`,
9
- String.raw`--selectProjects\s+(?:e2e|integration|contract)\b|`,
10
- String.raw`src\/tests\/(?:world|integration|e2e|contract)\b|`,
11
- String.raw`jest[^\n|&;]*(?:e2e|integration|contract)`,
6
+ String.raw `\btest:(?:e2e|integration|contract)\b|`,
7
+ String.raw `\btypecheck:tests\b|`,
8
+ String.raw `--selectProjects\s+(?:e2e|integration|contract)\b|`,
9
+ String.raw `src\/tests\/(?:world|integration|e2e|contract)\b|`,
10
+ String.raw `jest[^\n|&;]*(?:e2e|integration|contract)`,
12
11
  ].join(''), 'u');
13
12
  const UNIT_RUN_SCRIPTS = ['test', 'test:unit', 'test:ci', 'test:coverage', 'test:cov'];
14
-
15
- const runsE2e = text => E2E_COMMAND.test(String(text).replace(/--ignore-pattern[= ]+(?:"[^"]*"|'[^']*'|\S+)/gu, ''));
16
- const withoutComments = text => text.split('\n').filter(line => !/^\s*#/u.test(line)).join('\n');
17
-
13
+ const runsE2e = (text) => E2E_COMMAND.test(String(text).replace(/--ignore-pattern[= ]+(?:"[^"]*"|'[^']*'|\S+)/gu, ''));
14
+ const withoutComments = (text) => text.split('\n').filter(line => !/^\s*#/u.test(line)).join('\n');
18
15
  function inspectHooks(state) {
19
- for (const hook of ['.husky/pre-commit', '.husky/pre-push']) {
20
- const text = readTextFile(state.root, hook);
21
- if (text === null) continue;
22
- const body = withoutComments(text);
23
- if (runsE2e(body)) state.finding(state.rule, hook, `${hook} runs integration, e2e or contract. They are manual only; hooks run unit, lint and typecheck.`);
24
- for (const match of body.matchAll(/npm\s+run\s+([\w:.-]+)/gu)) state.pending.push(match[1]);
25
- }
16
+ for (const hook of ['.husky/pre-commit', '.husky/pre-push']) {
17
+ const text = readTextFile(state.root, hook);
18
+ if (text === null)
19
+ continue;
20
+ const body = withoutComments(text);
21
+ if (runsE2e(body))
22
+ state.finding(state.rule, hook, `${hook} runs integration, e2e or contract. They are manual only; hooks run unit, lint and typecheck.`);
23
+ for (const match of body.matchAll(/npm\s+run\s+([\w:.-]+)/gu))
24
+ state.pending.push(match[1]);
25
+ }
26
26
  }
27
-
28
27
  function inspectLintStaged(state) {
29
- const lintStaged = state.pkg?.['lint-staged'];
30
- if (lintStaged && runsE2e(Object.values(lintStaged).flat().join('\n')))
31
- state.finding(state.rule, 'package.json', 'lint-staged runs an integration, e2e or contract command. They are manual only.');
28
+ const lintStaged = state.pkg?.['lint-staged'];
29
+ if (lintStaged && runsE2e(Object.values(lintStaged).flat().join('\n')))
30
+ state.finding(state.rule, 'package.json', 'lint-staged runs an integration, e2e or contract command. They are manual only.');
32
31
  }
33
-
34
32
  function inspectHookScripts(state) {
35
- for (const name of state.pending) {
36
- if (state.called.has(name)) continue;
37
- state.called.add(name);
38
- const command = state.scripts[name];
39
- if (typeof command !== 'string') continue;
40
- if (runsE2e(command)) state.finding(state.rule, 'package.json', `Script ${name} is run by a husky hook and touches integration, e2e or contract. They are manual only.`);
41
- for (const match of command.matchAll(/npm\s+run\s+([\w:.-]+)/gu)) state.pending.push(match[1]);
42
- }
33
+ for (const name of state.pending) {
34
+ if (state.called.has(name))
35
+ continue;
36
+ state.called.add(name);
37
+ const command = state.scripts[name];
38
+ if (typeof command !== 'string')
39
+ continue;
40
+ if (runsE2e(command))
41
+ state.finding(state.rule, 'package.json', `Script ${name} is run by a husky hook and touches integration, e2e or contract. They are manual only.`);
42
+ for (const match of command.matchAll(/npm\s+run\s+([\w:.-]+)/gu))
43
+ state.pending.push(match[1]);
44
+ }
43
45
  }
44
-
45
46
  function inspectUnitGates(state) {
46
- if (!state.tree.hasFile(state.jestConfig)) return;
47
- for (const name of UNIT_RUN_SCRIPTS) {
48
- const command = state.scripts[name];
49
- if (typeof command === 'string' && /\bjest\b/u.test(command) && !/--selectProjects\s+unit\b/u.test(command))
50
- state.finding(state.rule, 'package.json', `Script ${name} runs jest without --selectProjects unit and would run the integration, e2e or contract project.`);
51
- }
52
- const jestText = readTextFile(state.root, state.jestConfig) ?? '';
53
- if (/collectCoverageFrom/u.test(jestText) && !/!src\/tests\/(?:\*\*|e2e)/u.test(jestText))
54
- state.finding(state.rule, state.jestConfig, 'collectCoverageFrom must exclude src/tests/** so no integration, e2e or contract file counts toward coverage.');
47
+ if (state.jestConfig === null || !state.tree.hasFile(state.jestConfig))
48
+ return;
49
+ for (const name of UNIT_RUN_SCRIPTS) {
50
+ const command = state.scripts[name];
51
+ if (typeof command === 'string' && /\bjest\b/u.test(command) && !/--selectProjects\s+unit\b/u.test(command))
52
+ state.finding(state.rule, 'package.json', `Script ${name} runs jest without --selectProjects unit and would run the integration, e2e or contract project.`);
53
+ }
54
+ const jestText = readTextFile(state.root, state.jestConfig) ?? '';
55
+ if (/collectCoverageFrom/u.test(jestText) && !/!src\/tests\/(?:\*\*|e2e)/u.test(jestText))
56
+ state.finding(state.rule, state.jestConfig, 'collectCoverageFrom must exclude src/tests/** so no integration, e2e or contract file counts toward coverage.');
55
57
  }
56
-
57
58
  function inspectWorkflows(state) {
58
- for (const file of state.tree.files().filter(entry => /^\.github\/workflows\/[^/]+\.ya?ml$/u.test(entry))) {
59
- const text = readTextFile(state.root, file);
60
- if (text === null) continue;
61
- const trigger = /^on:.*(?:\n(?:[ \t].*)?$)*/mu.exec(text)?.[0] ?? '';
62
- if (!/\b(?:push|pull_request)\b/u.test(trigger)) continue;
63
- if (runsE2e(withoutComments(text)))
64
- state.finding(state.rule, file, `${file} runs e2e on push or pull_request. Move the e2e job to its own workflow with on: workflow_dispatch only.`);
65
- }
59
+ for (const file of state.tree.files().filter((entry) => /^\.github\/workflows\/[^/]+\.ya?ml$/u.test(entry))) {
60
+ const text = readTextFile(state.root, file);
61
+ if (text === null)
62
+ continue;
63
+ const trigger = /^on:.*(?:\n(?:[ \t].*)?$)*/mu.exec(text)?.[0] ?? '';
64
+ if (!/\b(?:push|pull_request)\b/u.test(trigger))
65
+ continue;
66
+ if (runsE2e(withoutComments(text)))
67
+ state.finding(state.rule, file, `${file} runs e2e on push or pull_request. Move the e2e job to its own workflow with on: workflow_dispatch only.`);
68
+ }
66
69
  }
67
-
68
70
  /** Check hooks, transitive hook scripts, unit scripts, coverage and automatic workflows. */
69
71
  export function e2eInAutomaticGate({ root, tree, jestConfig, finding }) {
70
- const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
71
- let pkg = null;
72
- try { pkg = JSON.parse(readTextFile(root, 'package.json') ?? ''); } catch { /* the package checks own invalid JSON */ }
73
- const state = { root, tree, jestConfig, finding, rule, pkg, scripts: pkg?.scripts ?? {}, pending: [], called: new Set() };
74
- inspectHooks(state);
75
- inspectLintStaged(state);
76
- inspectHookScripts(state);
77
- inspectUnitGates(state);
78
- inspectWorkflows(state);
72
+ const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
73
+ let pkg = null;
74
+ try {
75
+ pkg = JSON.parse(readTextFile(root, 'package.json') ?? '');
76
+ }
77
+ catch { /* the package checks own invalid JSON */ }
78
+ const state = { root, tree, jestConfig, finding, rule, pkg, scripts: pkg?.scripts ?? {}, pending: [], called: new Set() };
79
+ inspectHooks(state);
80
+ inspectLintStaged(state);
81
+ inspectHookScripts(state);
82
+ inspectUnitGates(state);
83
+ inspectWorkflows(state);
79
84
  }
80
-
81
85
  /** The be side's default tsconfig excludes the world, integration, e2e and contract trees (they are checked by src/tests/tsconfig.json). */
82
86
  export function testTreesOutOfDefaultProgram({ root, tree, finding }) {
83
- const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
84
- const tsconfigText = readTextFile(root, 'tsconfig.json');
85
- let tsconfig = null;
86
- try { tsconfig = tsconfigText === null ? null : JSON.parse(tsconfigText); } catch { /* the typecheck itself owns parsing */ }
87
- if (tsconfig) {
88
- const excludedText = JSON.stringify(tsconfig.exclude ?? []);
89
- const excludesTestTrees = ['world', 'integration', 'e2e', 'contract'].every(tree => excludedText.includes(`src/tests/${tree}`));
90
- const defaultAll = tsconfig.include === undefined && tsconfig.files === undefined;
91
- const files = tree.files();
92
- if (files.some(file => /^src\/tests\/(?:world|integration|e2e|contract)\/.+\.[cm]?tsx?$/u.test(file)) && !excludesTestTrees &&
93
- (defaultAll || JSON.stringify(tsconfig.include ?? []).includes('src')))
94
- finding(rule, 'tsconfig.json', 'The default tsconfig includes src/tests/{world,integration,e2e,contract}/**. Exclude those trees and check them with src/tests/tsconfig.json (typecheck:tests).');
95
- }
87
+ const rule = 'HFS_E2E_IN_AUTOMATIC_GATE';
88
+ const tsconfigText = readTextFile(root, 'tsconfig.json');
89
+ let tsconfig = null;
90
+ try {
91
+ tsconfig = tsconfigText === null ? null : JSON.parse(tsconfigText);
92
+ }
93
+ catch { /* the typecheck itself owns parsing */ }
94
+ if (tsconfig) {
95
+ const excludedText = JSON.stringify(tsconfig.exclude ?? []);
96
+ const excludesTestTrees = ['world', 'integration', 'e2e', 'contract'].every(tree => excludedText.includes(`src/tests/${tree}`));
97
+ const defaultAll = tsconfig.include === undefined && tsconfig.files === undefined;
98
+ const files = tree.files();
99
+ if (files.some((file) => /^src\/tests\/(?:world|integration|e2e|contract)\/.+\.[cm]?tsx?$/u.test(file)) && !excludesTestTrees &&
100
+ (defaultAll || JSON.stringify(tsconfig.include ?? []).includes('src')))
101
+ finding(rule, 'tsconfig.json', 'The default tsconfig includes src/tests/{world,integration,e2e,contract}/**. Exclude those trees and check them with src/tests/tsconfig.json (typecheck:tests).');
102
+ }
96
103
  }