@starci/eslint-canon-be 3.0.9 → 3.1.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.
Files changed (151) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/LICENSE +21 -0
  3. package/README.md +2 -1
  4. package/builders.mjs +1 -1
  5. package/comments.mjs +12 -21
  6. package/config-owner.mjs +1 -1
  7. package/idempotency.mjs +54 -5
  8. package/index.mjs +2 -0
  9. package/lib/config.mjs +4 -1
  10. package/lib/doors.mjs +3 -1
  11. package/module-shape.mjs +1 -2
  12. package/package.json +3 -2
  13. package/resilience.mjs +1 -1
  14. package/runtime/config.example.yaml +187 -0
  15. package/runtime/engine/by-code-unit.mjs +12 -0
  16. package/runtime/engine/config.mjs +247 -273
  17. package/runtime/engine/invalid-config.mjs +5 -5
  18. package/runtime/engine/model-config.mjs +90 -0
  19. package/runtime/engine/orca-config.mjs +3 -1
  20. package/runtime/engine/removed-vocabulary.mjs +28 -0
  21. package/runtime/engine/resources-config.mjs +52 -0
  22. package/runtime/engine/runtime-root.mjs +17 -0
  23. package/runtime/engine/secrets.mjs +134 -0
  24. package/runtime/engine/sonar-config.mjs +21 -0
  25. package/runtime/engine/temp-root.mjs +32 -0
  26. package/runtime/knowledge/hfs/canon-pins.yaml +18 -18
  27. package/runtime/knowledge/hfs/rules.yaml +102 -69
  28. package/runtime/knowledge/hfs/slots.yaml +11 -14
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  30. package/runtime/modules/models/registry.yaml +25 -397
  31. package/runtime/modules/models/runtimes.yaml +92 -262
  32. package/runtime/modules/models/tiers.yaml +65 -0
  33. package/runtime/scripts/api/git/lib.mjs +36 -1
  34. package/runtime/scripts/hfs/allows.mjs +3 -3
  35. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  36. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  37. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  38. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  39. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  40. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  41. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  42. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  43. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  44. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  45. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  46. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  47. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  48. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  49. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  50. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  51. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  52. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  53. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  54. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  55. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  56. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  57. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  58. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  59. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  60. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +3 -2
  61. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  62. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  63. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  64. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  65. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  66. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  67. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  68. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  69. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  70. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  71. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  72. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  73. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  74. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  75. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  76. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  77. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  78. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  79. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  80. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  81. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  82. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  83. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  84. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  85. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  86. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  87. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  88. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  89. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  90. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  91. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  92. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  93. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  94. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  95. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  96. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  97. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  98. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  99. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  100. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  101. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  102. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  103. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  104. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  105. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  106. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  107. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  108. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  109. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  110. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  111. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  112. package/runtime/scripts/hfs/project-graph.mjs +4 -1
  113. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  114. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  115. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  116. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  117. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  118. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  119. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  120. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  121. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  122. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  123. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  124. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  125. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  126. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  127. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  128. package/runtime/scripts/hfs/slots.mjs +72 -693
  129. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  130. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  131. package/runtime/scripts/hfs/view.mjs +1 -1
  132. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  133. package/runtime/scripts/lib/git.mjs +2 -2
  134. package/runtime/scripts/lib/i18n.mjs +3 -2
  135. package/runtime/scripts/lib/jsdoc.mjs +35 -0
  136. package/runtime/scripts/lib/language.mjs +3 -3
  137. package/runtime/scripts/lib/list.mjs +8 -1
  138. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  139. package/runtime/scripts/lib/path-key.mjs +42 -9
  140. package/runtime/scripts/lib/regex.mjs +1 -1
  141. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  142. package/runtime/scripts/lib/sonar-syntax-rules.mjs +225 -0
  143. package/runtime/scripts/lib/sops-envelope.mjs +103 -2
  144. package/runtime/scripts/lib/stack-services.mjs +1 -2
  145. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  146. package/runtime/scripts/lib/walk.mjs +6 -1
  147. package/schema-authority.mjs +1 -1
  148. package/service-deps.mjs +1 -1
  149. package/sonar-parity.mjs +19 -0
  150. package/suppression.mjs +2 -6
  151. package/test-world.mjs +2 -10
@@ -0,0 +1,318 @@
1
+ import { relativePath, UNPROVEN_FRAMEWORK, unwrapExpression } from './typescript.mjs';
2
+ import { constructedDecoratorKind, mutableDecoratorKind, nodeDecorators, tracedFrameworkKinds, violation } from './ast-walks.mjs';
3
+ import { byCodeUnit } from '../../lib/list.mjs';
4
+ function locateSource(h, config, context, sourceFile, roots, state) {
5
+ const { featureRoots, moduleRoots, localFiles } = roots;
6
+ const { canonical, frameworkTargets, locatedRoot, sourceRole, exportedDeclarations, SPECIAL_BASENAMES } = h;
7
+ const fileName = canonical(sourceFile.fileName);
8
+ const featureRoot = locatedRoot(featureRoots, fileName);
9
+ const moduleRoot = locatedRoot(moduleRoots, fileName);
10
+ if (!featureRoot && !moduleRoot) return null;
11
+ state.checkedFiles += 1;
12
+ const checker = context.checkerFor(fileName);
13
+ if (!state.frameworkByChecker.has(checker)) state.frameworkByChecker.set(checker, frameworkTargets(config, context, checker, localFiles));
14
+ const framework = state.frameworkByChecker.get(checker);
15
+ const relativeToOwner = relativePath(featureRoot ?? moduleRoot, fileName);
16
+ const parts = relativeToOwner.split('/');
17
+ const directories = parts.slice(0, -1).map(part => part.toLowerCase());
18
+ const applicationIndex = directories.indexOf('application');
19
+ const transportIndex = directories.indexOf('transport');
20
+ const inApplication = applicationIndex >= 0;
21
+ const inTransport = transportIndex >= 0;
22
+ const role = sourceRole(context.ts, sourceFile);
23
+ const relative = relativePath(config.root, fileName);
24
+ const migrationName = /^\d{10,}-[A-Z][A-Za-z0-9]*$/.test(role.base);
25
+ const persistencePath = !role.spec && (migrationName || directories.includes('migrations'));
26
+ const special = SPECIAL_BASENAMES.has(role.base.toLowerCase()) || migrationName
27
+ || ['constants', 'enums', 'errors', 'migrations'].some(folder => directories.includes(folder));
28
+ const publicDeclarations = exportedDeclarations(context.ts, checker, sourceFile);
29
+ return { config, context, sourceFile, fileName, featureRoot, moduleRoot, checker, framework, directories, transportIndex,
30
+ inApplication, inTransport, role, relative, migrationName, persistencePath, special, publicDeclarations };
31
+ }
32
+
33
+ function reportSourceName(h, info, state) {
34
+ const { kebabSourceBase, SOURCE_NAME_RULE_ID } = h;
35
+ const { config, sourceFile, migrationName, role, relative } = info;
36
+ if (!migrationName && !kebabSourceBase(role.base)) state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_NAME_RULE_ID,
37
+ `Source basename ${role.base} must use kebab-case segments plus an explicit role suffix.`));
38
+ if (!role.role && !info.special) state.namingReasons.push(`${relative} has no statically identifiable source role`);
39
+ }
40
+
41
+ function reportFeatureRoleConflicts(h, info, state) {
42
+ const { SOURCE_LAYOUT_RULE_ID, APPLICATION_ROLES, TRANSPORT_ROLES } = h;
43
+ const { config, sourceFile, role, directories, inApplication, inTransport, transportIndex } = info;
44
+ const { violations } = state;
45
+ if (inApplication && inTransport) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
46
+ 'A feature source cannot belong to both application and transport layers.'));
47
+ if (role.role && APPLICATION_ROLES.has(role.role) && !inApplication) violations.push(violation(config, sourceFile, sourceFile,
48
+ SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature application/ layer.`));
49
+ if (role.role && TRANSPORT_ROLES.has(role.role) && !inTransport) violations.push(violation(config, sourceFile, sourceFile,
50
+ SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature transport/<protocol>/ layer.`));
51
+ if (inApplication && role.role && TRANSPORT_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
52
+ SOURCE_LAYOUT_RULE_ID, `Transport role ${role.role} cannot live in application/.`));
53
+ if (inTransport && role.role && APPLICATION_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
54
+ SOURCE_LAYOUT_RULE_ID, `Application role ${role.role} cannot live in transport/.`));
55
+ if (inTransport && transportIndex >= directories.length - 1) violations.push(violation(config, sourceFile, sourceFile,
56
+ SOURCE_LAYOUT_RULE_ID, 'Feature transport source must name a protocol below transport/.'));
57
+ }
58
+
59
+ /** A non-index source whose role is neither one of the layer's own roles nor one the other layer or the schema owns. */
60
+ function unselectedLayerRole(role, layerRoles, otherRoles) {
61
+ return role.base !== 'index' && (!role.role || !layerRoles.has(role.role)) && !(role.role && (otherRoles.has(role.role) || role.role === 'entity'));
62
+ }
63
+
64
+ function reportFeatureRoleSelection(h, info, state) {
65
+ const { APPLICATION_ROLES, TRANSPORT_ROLES, APPLICATION_LAYER_ROLES, TRANSPORT_LAYER_ROLES } = h;
66
+ const { role, relative, directories, inApplication, inTransport, persistencePath } = info;
67
+ const topLevelFeatureFile = directories.length === 1 && (role.base === 'index' || role.role === 'module' || role.spec);
68
+ if (inApplication && unselectedLayerRole(role, APPLICATION_LAYER_ROLES, TRANSPORT_ROLES)) {
69
+ state.layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected application-layer role`);
70
+ }
71
+ if (inTransport && unselectedLayerRole(role, TRANSPORT_LAYER_ROLES, APPLICATION_ROLES)) {
72
+ state.layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected transport-layer role`);
73
+ }
74
+ if (inApplication || inTransport || topLevelFeatureFile) return;
75
+ if (role.role && role.role !== 'entity' && !APPLICATION_ROLES.has(role.role) && !TRANSPORT_ROLES.has(role.role)) {
76
+ state.layoutReasons.push(`${relative} role ${role.role} has no statically selected feature layer`);
77
+ }
78
+ if (!role.role && !persistencePath) state.layoutReasons.push(`${relative} has no statically selected feature layer`);
79
+ }
80
+
81
+ function reportFeatureSchemaPlacement(h, info, state) {
82
+ const { SOURCE_LAYOUT_RULE_ID } = h;
83
+ const { config, sourceFile, role, persistencePath } = info;
84
+ if (persistencePath) state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
85
+ 'Migration source cannot be owned by a feature; place it under the declared persistence module.'));
86
+ if (role.role === 'entity') state.violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
87
+ 'Entity source cannot be owned by a feature; place schema under the declared persistence module.'));
88
+ }
89
+
90
+ function reportFeatureLayout(h, info, state) {
91
+ if (!info.featureRoot) return;
92
+ reportFeatureRoleConflicts(h, info, state);
93
+ reportFeatureRoleSelection(h, info, state);
94
+ reportFeatureSchemaPlacement(h, info, state);
95
+ }
96
+
97
+ function inspectClassPlacement(h, info, declaration, kinds, state) {
98
+ const { implementsFramework, SOURCE_LAYOUT_RULE_ID } = h;
99
+ const { config, context, sourceFile, featureRoot, inTransport, directories, transportIndex, checker, framework } = info;
100
+ const { ts } = context;
101
+ const graphQlDto = kinds.some(kind => kind === 'ArgsType' || kind === 'InputType' || kind === 'ObjectType');
102
+ if (graphQlDto && (!featureRoot || !inTransport || directories[transportIndex + 1] !== 'graphql')) {
103
+ state.violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
104
+ 'GraphQL DTO classes belong under a feature transport/graphql/ boundary.'));
105
+ }
106
+ const persistenceSchema = kinds.includes('Entity') || kinds.includes('ViewEntity')
107
+ || implementsFramework(ts, checker, declaration, framework.bySymbol, 'MigrationInterface');
108
+ if (persistenceSchema && featureRoot) state.violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
109
+ 'TypeORM entities and migrations cannot be owned by a feature; place schema under its declared persistence module.'));
110
+ }
111
+
112
+ function inspectClassName(h, info, declaration, state) {
113
+ const { implementedPortNames, SOURCE_NAME_RULE_ID, CLASS_ROLE_SUFFIX, NON_CLASS_ROLES } = h;
114
+ const { config, sourceFile, role, relative, checker } = info;
115
+ const { ts } = info.context;
116
+ const { publicDeclarations } = info;
117
+ const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
118
+ if (publicDeclarations.has(declaration) && !declaration.name) state.namingReasons.push(`${relative} exports an anonymous class whose role name cannot be proved`);
119
+ else if (publicDeclarations.has(declaration) && declaration.name && classSuffix && !declaration.name.text.endsWith(classSuffix)
120
+ && !implementedPortNames(ts, checker, declaration).some(port => declaration.name.text.endsWith(port))) {
121
+ state.violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
122
+ `Exported class ${declaration.name.text} must end in ${classSuffix} for a ${role.role} source file, or in the name of the port it implements.`));
123
+ } else if (publicDeclarations.has(declaration) && declaration.name && role.role && !NON_CLASS_ROLES.has(role.role) && !classSuffix) {
124
+ state.namingReasons.push(`${relative} declares exported class ${declaration.name.text} with unclassified file role ${role.role}`);
125
+ }
126
+ }
127
+
128
+ function inspectClass(h, info, declaration, state) {
129
+ const { decoratorKind } = h;
130
+ const { ts } = info.context;
131
+ const { checker, framework } = info;
132
+ const declarationDecorators = nodeDecorators(ts, declaration);
133
+ const kinds = declarationDecorators.map(decorator => decoratorKind(ts, checker, decorator, framework.bySymbol)).filter(Boolean);
134
+ inspectClassPlacement(h, info, declaration, kinds, state);
135
+ inspectClassName(h, info, declaration, state);
136
+ }
137
+
138
+ function inspectClasses(h, info, state) {
139
+ const declarations = info.sourceFile.statements.filter(statement => info.context.ts.isClassDeclaration(statement));
140
+ for (const declaration of declarations) inspectClass(h, info, declaration, state);
141
+ }
142
+
143
+ function inspectClassValue(h, info, declaration, state) {
144
+ const { classValueStatus, SOURCE_NAME_RULE_ID, CLASS_ROLE_SUFFIX, NON_CLASS_ROLES } = h;
145
+ const { config, sourceFile, relative, role, checker, publicDeclarations } = info;
146
+ const { ts } = info.context;
147
+ if (!publicDeclarations.has(declaration) || !declaration.initializer) return;
148
+ const classValue = classValueStatus(ts, checker, declaration.initializer);
149
+ if (classValue.status === 'unavailable') {
150
+ state.namingReasons.push(`${relative} exports a class-valued declaration whose role identity is not statically proved`);
151
+ return;
152
+ }
153
+ if (classValue.status !== 'class') return;
154
+ const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
155
+ if (!classSuffix || !ts.isIdentifier(declaration.name)) {
156
+ state.namingReasons.push(`${relative} exports a class-valued declaration without a statically selected class role`);
157
+ return;
158
+ }
159
+ const names = [declaration.name.text, ...classValue.names];
160
+ const invalid = names.find(name => !name.endsWith(classSuffix));
161
+ if (invalid) state.violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
162
+ `Exported class value ${invalid} must end in ${classSuffix} for a ${role.role} source file.`));
163
+ }
164
+
165
+ function inspectClassValues(h, info, state) {
166
+ const { ts } = info.context;
167
+ const { sourceFile } = info;
168
+ for (const statement of sourceFile.statements) if (ts.isVariableStatement(statement)) {
169
+ for (const declaration of statement.declarationList.declarations) inspectClassValue(h, info, declaration, state);
170
+ }
171
+ }
172
+
173
+ function inspectEnumMembers(h, info, statement, state) {
174
+ const { SOURCE_NAME_RULE_ID } = h;
175
+ const { config, sourceFile } = info;
176
+ const { ts } = info.context;
177
+ for (const member of statement.members) {
178
+ const name = ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name) ? member.name.text : null;
179
+ const initializer = member.initializer && unwrapExpression(ts, member.initializer);
180
+ if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name) || !initializer || !ts.isStringLiteralLike(initializer)) {
181
+ state.violations.push(violation(config, sourceFile, member.name, SOURCE_NAME_RULE_ID,
182
+ 'Enum members must use PascalCase names with explicit static string values.'));
183
+ }
184
+ }
185
+ }
186
+
187
+ function inspectObjectContract(h, info, statement, state) {
188
+ const { objectContractStatus, companionInterfaceAllowed, SOURCE_NAME_RULE_ID, TRANSPORT_OBJECT_SUFFIX } = h;
189
+ const { config, sourceFile, role, checker, publicDeclarations } = info;
190
+ const { ts } = info.context;
191
+ const contractStatus = objectContractStatus(ts, checker, statement);
192
+ const transportSuffix = TRANSPORT_OBJECT_SUFFIX.get(role.role);
193
+ if (contractStatus === 'object' && publicDeclarations.has(statement) && transportSuffix && !role.spec
194
+ && !statement.name.text.endsWith(transportSuffix) && !companionInterfaceAllowed(ts, sourceFile, statement.name.text)) {
195
+ state.violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
196
+ `Public object contract ${statement.name.text} in a ${role.role} source file must end in ${transportSuffix}.`));
197
+ }
198
+ }
199
+
200
+ function inspectEnumAndContract(h, info, statement, state) {
201
+ const { hasModifier, SOURCE_NAME_RULE_ID } = h;
202
+ const { config, sourceFile } = info;
203
+ const { ts } = info.context;
204
+ if (ts.isEnumDeclaration(statement)) {
205
+ if (hasModifier(ts, statement, ts.SyntaxKind.ConstKeyword) || !/^[A-Z][A-Za-z0-9]*$/.test(statement.name.text)) {
206
+ state.violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
207
+ 'Enums must be non-const declarations with PascalCase names.'));
208
+ }
209
+ inspectEnumMembers(h, info, statement, state);
210
+ }
211
+ inspectObjectContract(h, info, statement, state);
212
+ }
213
+
214
+ function inspectEnumsAndContracts(h, info, state) {
215
+ for (const statement of info.sourceFile.statements) inspectEnumAndContract(h, info, statement, state);
216
+ }
217
+
218
+ function inspectDynamicDecorator(info, dynamic, state) {
219
+ const detail = `${info.relative} uses a mutable or constructed ${dynamic} decorator identity`;
220
+ if (['Entity', 'ViewEntity', 'ArgsType', 'InputType', 'ObjectType', 'multiple framework', 'unproven framework'].includes(dynamic)) state.layoutReasons.push(detail);
221
+ if (!['Entity', 'ViewEntity'].includes(dynamic)) state.namingReasons.push(detail);
222
+ }
223
+
224
+ function inspectDecorator(h, info, node, decorator, state) {
225
+ const { graphqlField, graphqlArgument, decoratorKind, TRACE_NEW } = h;
226
+ const { ts } = info.context;
227
+ const { checker, framework } = info;
228
+ const kind = decoratorKind(ts, checker, decorator, framework.bySymbol);
229
+ if (!kind) {
230
+ const dynamic = mutableDecoratorKind(ts, checker, decorator, framework.bySymbol)
231
+ ?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol, TRACE_NEW);
232
+ if (dynamic) inspectDynamicDecorator(info, dynamic, state);
233
+ }
234
+ if (kind === 'Mutation' || kind === 'Query') graphqlField({ config: info.config, context: info.context, sourceFile: info.sourceFile,
235
+ node, decorator, kind, namingReasons: state.namingReasons, violations: state.violations });
236
+ if (kind === 'Args') graphqlArgument(info.config, info.context, info.sourceFile, node, decorator, state.namingReasons, state.violations);
237
+ }
238
+
239
+ function inspectNodeDecorators(h, info, node, state) {
240
+ for (const decorator of nodeDecorators(info.context.ts, node)) inspectDecorator(h, info, node, decorator, state);
241
+ }
242
+
243
+ function inspectEnumCalls(h, info, node, state) {
244
+ const { graphqlEnumRegistration, selectedCallKind, callsNamedHelper } = h;
245
+ const { ts } = info.context;
246
+ const { checker, framework } = info;
247
+ if (!ts.isCallExpression(node)) return;
248
+ if (selectedCallKind(ts, checker, node, framework.bySymbol) === 'registerEnumType') {
249
+ graphqlEnumRegistration(info.config, info.context, info.sourceFile, node, state.namingReasons, state.violations);
250
+ } else if (callsNamedHelper(ts, checker, node, 'createEnumType')) {
251
+ state.namingReasons.push(`${info.relative} calls a project GraphQL enum adapter whose emitted type name is not statically proved`);
252
+ }
253
+ }
254
+
255
+ function inspectConstructedSource(h, info, node, state) {
256
+ const { SOURCE_LAYOUT_RULE_ID, TRACE_NEW } = h;
257
+ const { ts } = info.context;
258
+ const { checker, framework, publicDeclarations, featureRoot } = info;
259
+ const exportedFactoryCall = ts.isCallExpression(node) && ts.isVariableDeclaration(node.parent) && publicDeclarations.has(node.parent);
260
+ if ((ts.isNewExpression(node) || exportedFactoryCall) && featureRoot) {
261
+ const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol, TRACE_NEW);
262
+ if (constructed.has('EntitySchema')) state.violations.push(violation(info.config, info.sourceFile, node.expression, SOURCE_LAYOUT_RULE_ID,
263
+ 'TypeORM EntitySchema cannot be owned by a feature; place schema under its declared persistence module.'));
264
+ if (constructed.has(UNPROVEN_FRAMEWORK)) state.layoutReasons.push(`${info.relative} has a constructed provider identity beyond the bounded static trace`);
265
+ }
266
+ }
267
+
268
+ function visitSourceNode(h, info, node, state) {
269
+ inspectNodeDecorators(h, info, node, state);
270
+ inspectEnumCalls(h, info, node, state);
271
+ inspectConstructedSource(h, info, node, state);
272
+ info.context.ts.forEachChild(node, child => visitSourceNode(h, info, child, state));
273
+ }
274
+
275
+ function inspectSourceFile(h, config, context, sourceFile, roots, state) {
276
+ const info = locateSource(h, config, context, sourceFile, roots, state);
277
+ if (!info) return;
278
+ reportSourceName(h, info, state);
279
+ reportFeatureLayout(h, info, state);
280
+ inspectClasses(h, info, state);
281
+ inspectClassValues(h, info, state);
282
+ inspectEnumsAndContracts(h, info, state);
283
+ visitSourceNode(h, info, sourceFile, state);
284
+ }
285
+
286
+ function coverageFor(state) {
287
+ for (const framework of state.frameworkByChecker.values()) {
288
+ state.layoutReasons.push(...framework.reasons);
289
+ state.namingReasons.push(...framework.reasons.filter(reason => reason.includes('@nestjs/graphql')));
290
+ }
291
+ if (state.checkedFiles === 0) {
292
+ state.layoutReasons.push('the checked TypeScript program contains no configured backend feature or module source');
293
+ state.namingReasons.push('the checked TypeScript program contains no configured backend feature or module source');
294
+ }
295
+ return {
296
+ files: state.checkedFiles,
297
+ layout: state.layoutReasons.length
298
+ ? { status: 'unavailable', reason: 'one or more backend source placement relations are not statically proved', details: [...new Set(state.layoutReasons)].sort(byCodeUnit) }
299
+ : { status: 'checked' },
300
+ naming: state.namingReasons.length
301
+ ? { status: 'unavailable', reason: 'one or more backend source naming relations are not statically proved', details: [...new Set(state.namingReasons)].sort(byCodeUnit) }
302
+ : { status: 'checked' },
303
+ };
304
+ }
305
+
306
+ /** Check the adopted domain-first backend source layout without inferring semantic ownership from folder names alone. */
307
+ export function createBackendSourceShapeChecker(helpers) {
308
+ const { absoluteRoots, canonical } = helpers;
309
+ return function checkBackendSourceShape(config, context) {
310
+ const featureRoots = absoluteRoots(config.root, config.backend.features);
311
+ const moduleRoots = absoluteRoots(config.root, config.backend.modules);
312
+ const localFiles = new Set(context.files.map(file => canonical(file.fileName)));
313
+ const state = { frameworkByChecker: new Map(), violations: [], layoutReasons: [], namingReasons: [], checkedFiles: 0 };
314
+ const roots = { featureRoots, moduleRoots, localFiles };
315
+ for (const sourceFile of context.files) inspectSourceFile(helpers, config, context, sourceFile, roots, state);
316
+ return { violations: state.violations, coverage: coverageFor(state) };
317
+ };
318
+ }