@starci/hfs 4.0.9 → 4.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.
- package/CHANGELOG.md +1 -1
- package/LICENSE +21 -0
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +82 -8
- package/package.json +3 -2
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +187 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +247 -273
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +134 -0
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +18 -18
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +102 -69
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -1
- package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
- package/runtime/modules/models/registry.yaml +25 -397
- package/runtime/modules/models/runtimes.yaml +92 -262
- package/runtime/modules/models/tiers.yaml +65 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/lib.mjs +6 -0
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +36 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +52 -0
- package/runtime/scripts/api/process/run-program.mjs +8 -0
- package/runtime/scripts/api/sops/decrypt.mjs +9 -16
- package/runtime/scripts/api/sops/lib.mjs +309 -11
- package/runtime/scripts/api/sops/seal.mjs +8 -4
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +3 -2
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +158 -102
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/check.mjs +61 -70
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +5 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +90 -45
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +72 -693
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/fs-kind.mjs +14 -4
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/mutation-fence.mjs +15 -0
- package/runtime/scripts/lib/path-key.mjs +42 -9
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +103 -2
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { isInside } from './config.mjs';
|
|
3
|
-
import { relativePath,
|
|
4
|
-
import { commonJsRequireReasons,
|
|
5
|
-
import {
|
|
3
|
+
import { relativePath, unwrapExpression } from './typescript.mjs';
|
|
4
|
+
import { commonJsRequireReasons, decoratorCallee, moduleExportsOf, nodeDecorators, normalizedSymbol, normalizedSymbolValue, programSourcesOf, returnedExpressions, selectedNode, valueSymbol, violation } from './ast-walks.mjs';
|
|
5
|
+
import { createBackendSourceShapeChecker } from './source-names-shape.mjs';
|
|
6
6
|
|
|
7
7
|
const SOURCE_LAYOUT_RULE_ID = 'BE_FEATURE_LAYOUT_INVALID';
|
|
8
8
|
const SOURCE_NAME_RULE_ID = 'BE_SOURCE_FORM';
|
|
@@ -33,6 +33,7 @@ const PORT_DECLARATION_ROLES = new Set(['contracts', 'port']);
|
|
|
33
33
|
const NON_CLASS_ROLES = new Set(['constants', 'contracts', 'decorators', 'enum', 'providers', 'types']);
|
|
34
34
|
const KNOWN_HYPHEN_ROLES = [...CLASS_ROLE_SUFFIX.keys()].sort((a, b) => b.length - a.length);
|
|
35
35
|
const SPECIAL_BASENAMES = new Set(['config', 'configuration', 'constants', 'decorators', 'env', 'environment', 'index', 'main', 'types']);
|
|
36
|
+
const NO_PROGRAM_CHECKER = 'a TypeScript program checker could not be associated with its source';
|
|
36
37
|
const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/i;
|
|
37
38
|
|
|
38
39
|
function absoluteRoots(root, relatives) {
|
|
@@ -43,20 +44,25 @@ function canonical(file) {
|
|
|
43
44
|
return path.resolve(file);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
function addFrameworkTargets(config, context, checker, sourceFile, statement, bySymbol, reasons) {
|
|
48
|
+
const bound = moduleExportsOf(context.ts, checker, statement);
|
|
49
|
+
if (!bound || !FRAMEWORK_EXPORTS.has(bound.specifier)) return;
|
|
50
|
+
if (!bound.symbol) {
|
|
51
|
+
reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${bound.specifier}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const selected = FRAMEWORK_EXPORTS.get(bound.specifier);
|
|
55
|
+
for (const [name, target] of bound.exports) if (selected.has(name) && target) bySymbol.set(target, name);
|
|
56
|
+
}
|
|
47
57
|
|
|
48
58
|
function frameworkTargets(config, context, checker, localFiles) {
|
|
49
59
|
const reasons = [];
|
|
50
60
|
const bySymbol = new Map();
|
|
51
61
|
const files = programSourcesOf(context, checker, localFiles);
|
|
52
|
-
if (!files) return { bySymbol, reasons: [
|
|
62
|
+
if (!files) return { bySymbol, reasons: [NO_PROGRAM_CHECKER] };
|
|
53
63
|
for (const sourceFile of files) {
|
|
54
64
|
for (const statement of sourceFile.statements) {
|
|
55
|
-
|
|
56
|
-
if (!bound || !FRAMEWORK_EXPORTS.has(bound.specifier)) continue;
|
|
57
|
-
if (!bound.symbol) { reasons.push(`${relativePath(config.root, sourceFile.fileName)} cannot resolve ${bound.specifier}`); continue; }
|
|
58
|
-
const selected = FRAMEWORK_EXPORTS.get(bound.specifier);
|
|
59
|
-
for (const [name, target] of bound.exports) if (selected.has(name) && target) bySymbol.set(target, name);
|
|
65
|
+
addFrameworkTargets(config, context, checker, sourceFile, statement, bySymbol, reasons);
|
|
60
66
|
}
|
|
61
67
|
commonJsRequireReasons(context.ts, checker, sourceFile, (specifier) => FRAMEWORK_EXPORTS.has(specifier),
|
|
62
68
|
reasons, relativePath(config.root, sourceFile.fileName), 'binding whose source role cannot be proved');
|
|
@@ -113,6 +119,33 @@ function combineClassValue(results) {
|
|
|
113
119
|
return { status: 'class', names: [...new Set(classes.flatMap(result => result.names))] };
|
|
114
120
|
}
|
|
115
121
|
|
|
122
|
+
function pushReturnedClassStatuses(ts, checker, results, container, nextSeen, depth) {
|
|
123
|
+
for (const returned of returnedExpressions(ts, container)) results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function classDeclarationResults(ts, checker, expression, declaration, nextSeen, depth) {
|
|
127
|
+
const results = [];
|
|
128
|
+
if (ts.isClassDeclaration(declaration)) results.push({ status: 'class', names: declaration.name ? [declaration.name.text] : [] });
|
|
129
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
130
|
+
if (ts.isCallExpression(expression)
|
|
131
|
+
&& (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
|
|
132
|
+
pushReturnedClassStatuses(ts, checker, results, declaration.initializer, nextSeen, depth);
|
|
133
|
+
} else results.push(classValueStatus(ts, checker, declaration.initializer, nextSeen, depth + 1));
|
|
134
|
+
}
|
|
135
|
+
if (ts.isCallExpression(expression)) pushReturnedClassStatuses(ts, checker, results, declaration, nextSeen, depth);
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function classSymbolStatus(ts, checker, expression, seen, depth) {
|
|
140
|
+
const selected = selectedNode(ts, ts.isCallExpression(expression) ? expression.expression : expression);
|
|
141
|
+
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
142
|
+
if (!symbol || seen.has(symbol)) return { status: 'not-class', names: [] };
|
|
143
|
+
const nextSeen = new Set(seen).add(symbol);
|
|
144
|
+
const results = [];
|
|
145
|
+
for (const declaration of symbol.getDeclarations?.() ?? []) results.push(...classDeclarationResults(ts, checker, expression, declaration, nextSeen, depth));
|
|
146
|
+
return results.length ? combineClassValue(results) : { status: 'not-class', names: [] };
|
|
147
|
+
}
|
|
148
|
+
|
|
116
149
|
function classValueStatus(ts, checker, expression, seen = new Set(), depth = 0) {
|
|
117
150
|
if (!expression) return { status: 'not-class', names: [] };
|
|
118
151
|
if (depth > 10) return { status: 'unavailable', names: [] };
|
|
@@ -122,26 +155,7 @@ function classValueStatus(ts, checker, expression, seen = new Set(), depth = 0)
|
|
|
122
155
|
classValueStatus(ts, checker, expression.whenTrue, seen, depth + 1),
|
|
123
156
|
classValueStatus(ts, checker, expression.whenFalse, seen, depth + 1),
|
|
124
157
|
]);
|
|
125
|
-
|
|
126
|
-
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
127
|
-
if (!symbol || seen.has(symbol)) return { status: 'not-class', names: [] };
|
|
128
|
-
const nextSeen = new Set(seen).add(symbol);
|
|
129
|
-
const results = [];
|
|
130
|
-
for (const declaration of symbol.getDeclarations?.() ?? []) {
|
|
131
|
-
if (ts.isClassDeclaration(declaration)) results.push({ status: 'class', names: declaration.name ? [declaration.name.text] : [] });
|
|
132
|
-
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
133
|
-
if (ts.isCallExpression(expression)
|
|
134
|
-
&& (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
|
|
135
|
-
for (const returned of returnedExpressions(ts, declaration.initializer)) {
|
|
136
|
-
results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
|
|
137
|
-
}
|
|
138
|
-
} else results.push(classValueStatus(ts, checker, declaration.initializer, nextSeen, depth + 1));
|
|
139
|
-
}
|
|
140
|
-
if (ts.isCallExpression(expression)) for (const returned of returnedExpressions(ts, declaration)) {
|
|
141
|
-
results.push(classValueStatus(ts, checker, returned, nextSeen, depth + 1));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return results.length ? combineClassValue(results) : { status: 'not-class', names: [] };
|
|
158
|
+
return classSymbolStatus(ts, checker, expression, seen, depth);
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
function combineObjectStatus(statuses) {
|
|
@@ -150,6 +164,22 @@ function combineObjectStatus(statuses) {
|
|
|
150
164
|
return 'not-object';
|
|
151
165
|
}
|
|
152
166
|
|
|
167
|
+
function declarationObjectStatus(ts, checker, declaration, seen, depth) {
|
|
168
|
+
if (ts.isInterfaceDeclaration(declaration)) return 'object';
|
|
169
|
+
if (ts.isTypeAliasDeclaration(declaration)) return objectTypeStatus(ts, checker, declaration.type, seen, depth + 1);
|
|
170
|
+
if (ts.isTypeParameterDeclaration(declaration) || ts.isClassDeclaration(declaration)) return 'unavailable';
|
|
171
|
+
return 'not-object';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function typeReferenceStatus(ts, checker, node, seen, depth) {
|
|
175
|
+
const symbol = normalizedSymbol(ts, checker, node.typeName);
|
|
176
|
+
if (!symbol || seen.has(symbol)) return 'unavailable';
|
|
177
|
+
const nextSeen = new Set(seen).add(symbol);
|
|
178
|
+
const declarations = symbol.getDeclarations?.() ?? [];
|
|
179
|
+
if (!declarations.length) return 'unavailable';
|
|
180
|
+
return combineObjectStatus(declarations.map(declaration => declarationObjectStatus(ts, checker, declaration, nextSeen, depth)));
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
function objectTypeStatus(ts, checker, node, seen = new Set(), depth = 0) {
|
|
154
184
|
if (!node || depth > 12) return 'unavailable';
|
|
155
185
|
if (ts.isTypeLiteralNode(node) || ts.isMappedTypeNode(node)) return 'object';
|
|
@@ -157,20 +187,7 @@ function objectTypeStatus(ts, checker, node, seen = new Set(), depth = 0) {
|
|
|
157
187
|
if (ts.isIntersectionTypeNode(node) || ts.isUnionTypeNode(node)) {
|
|
158
188
|
return combineObjectStatus(node.types.map(type => objectTypeStatus(ts, checker, type, seen, depth + 1)));
|
|
159
189
|
}
|
|
160
|
-
if (ts.isTypeReferenceNode(node))
|
|
161
|
-
const symbol = normalizedSymbol(ts, checker, node.typeName);
|
|
162
|
-
if (!symbol || seen.has(symbol)) return 'unavailable';
|
|
163
|
-
const nextSeen = new Set(seen).add(symbol);
|
|
164
|
-
const declarations = symbol.getDeclarations?.() ?? [];
|
|
165
|
-
if (!declarations.length) return 'unavailable';
|
|
166
|
-
return combineObjectStatus(declarations.map(declaration => {
|
|
167
|
-
if (ts.isInterfaceDeclaration(declaration)) return 'object';
|
|
168
|
-
if (ts.isTypeAliasDeclaration(declaration)) return objectTypeStatus(ts, checker, declaration.type, nextSeen, depth + 1);
|
|
169
|
-
if (ts.isTypeParameterDeclaration(declaration)) return 'unavailable';
|
|
170
|
-
if (ts.isClassDeclaration(declaration)) return 'unavailable';
|
|
171
|
-
return 'not-object';
|
|
172
|
-
}));
|
|
173
|
-
}
|
|
190
|
+
if (ts.isTypeReferenceNode(node)) return typeReferenceStatus(ts, checker, node, seen, depth);
|
|
174
191
|
if (ts.isConditionalTypeNode(node) || ts.isIndexedAccessTypeNode(node) || ts.isInferTypeNode(node) || ts.isTypeQueryNode(node)) return 'unavailable';
|
|
175
192
|
return 'not-object';
|
|
176
193
|
}
|
|
@@ -206,7 +223,7 @@ function staticProperty(ts, object, name) {
|
|
|
206
223
|
: { status: 'dynamic', node: matching[0].initializer };
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
function graphqlField(config, context, sourceFile, node, decorator, kind, namingReasons, violations) {
|
|
226
|
+
function graphqlField({ config, context, sourceFile, node, decorator, kind, namingReasons, violations }) {
|
|
210
227
|
const call = decorator.expression;
|
|
211
228
|
if (!context.ts.isCallExpression(call)) {
|
|
212
229
|
namingReasons.push(`${relativePath(config.root, sourceFile.fileName)} has a non-call @${kind} decorator`);
|
|
@@ -269,219 +286,37 @@ function implementsFramework(ts, checker, declaration, targets, name) {
|
|
|
269
286
|
}
|
|
270
287
|
|
|
271
288
|
/** Names of the interfaces a class implements that are declared in a *.port.ts or *.contracts.ts file (resolved by the type checker). */
|
|
272
|
-
function
|
|
289
|
+
function portNamesForImplementedType(ts, checker, type) {
|
|
273
290
|
const names = [];
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const declaredRole = path.basename(target.getSourceFile().fileName).replace(SOURCE_EXTENSION, '').split('.').at(-1);
|
|
280
|
-
if (PORT_DECLARATION_ROLES.has(declaredRole)) names.push(target.name.text);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
291
|
+
const symbol = normalizedSymbol(ts, checker, type.expression);
|
|
292
|
+
for (const target of symbol?.getDeclarations?.() ?? []) {
|
|
293
|
+
if (!ts.isInterfaceDeclaration(target) && !ts.isTypeAliasDeclaration(target)) continue;
|
|
294
|
+
const declaredRole = path.basename(target.getSourceFile().fileName).replace(SOURCE_EXTENSION, '').split('.').at(-1);
|
|
295
|
+
if (PORT_DECLARATION_ROLES.has(declaredRole)) names.push(target.name.text);
|
|
283
296
|
}
|
|
284
297
|
return names;
|
|
285
298
|
}
|
|
286
299
|
|
|
300
|
+
function implementedPortNames(ts, checker, declaration) {
|
|
301
|
+
return (declaration.heritageClauses ?? []).filter(clause => clause.token === ts.SyntaxKind.ImplementsKeyword)
|
|
302
|
+
.flatMap(clause => clause.types.flatMap(type => portNamesForImplementedType(ts, checker, type)));
|
|
303
|
+
}
|
|
304
|
+
|
|
287
305
|
function locatedRoot(roots, fileName) {
|
|
288
306
|
return roots.find(root => isInside(root, fileName)) ?? null;
|
|
289
307
|
}
|
|
290
308
|
|
|
291
309
|
/** Check the adopted domain-first backend source layout without inferring semantic ownership from folder names alone. */
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const namingReasons = [];
|
|
301
|
-
let checkedFiles = 0;
|
|
302
|
-
|
|
303
|
-
for (const sourceFile of context.files) {
|
|
304
|
-
const fileName = canonical(sourceFile.fileName);
|
|
305
|
-
const featureRoot = locatedRoot(featureRoots, fileName);
|
|
306
|
-
const moduleRoot = locatedRoot(moduleRoots, fileName);
|
|
307
|
-
if (!featureRoot && !moduleRoot) continue;
|
|
308
|
-
checkedFiles += 1;
|
|
309
|
-
const checker = context.checkerFor(fileName);
|
|
310
|
-
if (!frameworkByChecker.has(checker)) frameworkByChecker.set(checker, frameworkTargets(config, context, checker, localFiles));
|
|
311
|
-
const framework = frameworkByChecker.get(checker);
|
|
312
|
-
const relativeToOwner = relativePath(featureRoot ?? moduleRoot, fileName);
|
|
313
|
-
const parts = relativeToOwner.split('/');
|
|
314
|
-
const directories = parts.slice(0, -1).map(part => part.toLowerCase());
|
|
315
|
-
const applicationIndex = directories.indexOf('application');
|
|
316
|
-
const transportIndex = directories.indexOf('transport');
|
|
317
|
-
const inApplication = applicationIndex >= 0;
|
|
318
|
-
const inTransport = transportIndex >= 0;
|
|
319
|
-
const role = sourceRole(ts, sourceFile);
|
|
320
|
-
const relative = relativePath(config.root, fileName);
|
|
321
|
-
const migrationName = /^\d{10,}-[A-Z][A-Za-z0-9]*$/.test(role.base);
|
|
322
|
-
const persistencePath = !role.spec && (migrationName || directories.includes('migrations'));
|
|
323
|
-
const special = SPECIAL_BASENAMES.has(role.base.toLowerCase()) || migrationName
|
|
324
|
-
|| ['constants', 'enums', 'errors', 'migrations'].some(folder => directories.includes(folder));
|
|
325
|
-
const publicDeclarations = exportedDeclarations(ts, checker, sourceFile);
|
|
326
|
-
|
|
327
|
-
if (!migrationName && !kebabSourceBase(role.base)) violations.push(violation(config, sourceFile, sourceFile, SOURCE_NAME_RULE_ID,
|
|
328
|
-
`Source basename ${role.base} must use kebab-case segments plus an explicit role suffix.`));
|
|
329
|
-
if (!role.role && !special) namingReasons.push(`${relative} has no statically identifiable source role`);
|
|
330
|
-
|
|
331
|
-
if (featureRoot) {
|
|
332
|
-
if (inApplication && inTransport) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
|
|
333
|
-
'A feature source cannot belong to both application and transport layers.'));
|
|
334
|
-
if (role.role && APPLICATION_ROLES.has(role.role) && !inApplication) violations.push(violation(config, sourceFile, sourceFile,
|
|
335
|
-
SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature application/ layer.`));
|
|
336
|
-
if (role.role && TRANSPORT_ROLES.has(role.role) && !inTransport) violations.push(violation(config, sourceFile, sourceFile,
|
|
337
|
-
SOURCE_LAYOUT_RULE_ID, `${role.role} source belongs under the feature transport/<protocol>/ layer.`));
|
|
338
|
-
if (inApplication && role.role && TRANSPORT_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
|
|
339
|
-
SOURCE_LAYOUT_RULE_ID, `Transport role ${role.role} cannot live in application/.`));
|
|
340
|
-
if (inTransport && role.role && APPLICATION_ROLES.has(role.role)) violations.push(violation(config, sourceFile, sourceFile,
|
|
341
|
-
SOURCE_LAYOUT_RULE_ID, `Application role ${role.role} cannot live in transport/.`));
|
|
342
|
-
if (inTransport && transportIndex >= directories.length - 1) violations.push(violation(config, sourceFile, sourceFile,
|
|
343
|
-
SOURCE_LAYOUT_RULE_ID, 'Feature transport source must name a protocol below transport/.'));
|
|
344
|
-
const topLevelFeatureFile = directories.length === 1 && (role.base === 'index' || role.role === 'module' || role.spec);
|
|
345
|
-
if (inApplication && role.base !== 'index' && (!role.role || !APPLICATION_LAYER_ROLES.has(role.role))
|
|
346
|
-
&& !(role.role && (TRANSPORT_ROLES.has(role.role) || role.role === 'entity'))) {
|
|
347
|
-
layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected application-layer role`);
|
|
348
|
-
}
|
|
349
|
-
if (inTransport && role.base !== 'index' && (!role.role || !TRANSPORT_LAYER_ROLES.has(role.role))
|
|
350
|
-
&& !(role.role && (APPLICATION_ROLES.has(role.role) || role.role === 'entity'))) {
|
|
351
|
-
layoutReasons.push(`${relative} role ${role.role ?? '(unclassified)'} is not a selected transport-layer role`);
|
|
352
|
-
}
|
|
353
|
-
if (!inApplication && !inTransport && !topLevelFeatureFile && role.role && role.role !== 'entity'
|
|
354
|
-
&& !APPLICATION_ROLES.has(role.role) && !TRANSPORT_ROLES.has(role.role)) {
|
|
355
|
-
layoutReasons.push(`${relative} role ${role.role} has no statically selected feature layer`);
|
|
356
|
-
}
|
|
357
|
-
if (!inApplication && !inTransport && !topLevelFeatureFile && !role.role && !persistencePath) {
|
|
358
|
-
layoutReasons.push(`${relative} has no statically selected feature layer`);
|
|
359
|
-
}
|
|
360
|
-
if (persistencePath) violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
|
|
361
|
-
'Migration source cannot be owned by a feature; place it under the declared persistence module.'));
|
|
362
|
-
if (role.role === 'entity') violations.push(violation(config, sourceFile, sourceFile, SOURCE_LAYOUT_RULE_ID,
|
|
363
|
-
'Entity source cannot be owned by a feature; place schema under the declared persistence module.'));
|
|
364
|
-
}
|
|
310
|
+
const checkBackendSourceShapeImpl = createBackendSourceShapeChecker({
|
|
311
|
+
absoluteRoots, canonical, frameworkTargets, locatedRoot, sourceRole, kebabSourceBase, exportedDeclarations,
|
|
312
|
+
classValueStatus, objectContractStatus, companionInterfaceAllowed, hasModifier, graphqlField, graphqlArgument,
|
|
313
|
+
graphqlEnumRegistration, selectedCallKind, callsNamedHelper, implementsFramework, implementedPortNames, decoratorKind,
|
|
314
|
+
SOURCE_LAYOUT_RULE_ID, SOURCE_NAME_RULE_ID, APPLICATION_ROLES, TRANSPORT_ROLES, APPLICATION_LAYER_ROLES,
|
|
315
|
+
TRANSPORT_LAYER_ROLES, CLASS_ROLE_SUFFIX, TRANSPORT_OBJECT_SUFFIX, NON_CLASS_ROLES, SPECIAL_BASENAMES,
|
|
316
|
+
TRACE_NEW,
|
|
317
|
+
});
|
|
365
318
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const declarationDecorators = nodeDecorators(ts, declaration);
|
|
369
|
-
const kinds = declarationDecorators.map(decorator => decoratorKind(ts, checker, decorator, framework.bySymbol)).filter(Boolean);
|
|
370
|
-
const graphQlDto = kinds.some(kind => kind === 'ArgsType' || kind === 'InputType' || kind === 'ObjectType');
|
|
371
|
-
if (graphQlDto && (!featureRoot || !inTransport || directories[transportIndex + 1] !== 'graphql')) {
|
|
372
|
-
violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
|
|
373
|
-
'GraphQL DTO classes belong under a feature transport/graphql/ boundary.'));
|
|
374
|
-
}
|
|
375
|
-
const persistenceSchema = kinds.includes('Entity') || kinds.includes('ViewEntity')
|
|
376
|
-
|| implementsFramework(ts, checker, declaration, framework.bySymbol, 'MigrationInterface');
|
|
377
|
-
if (persistenceSchema && featureRoot) violations.push(violation(config, sourceFile, declaration.name ?? declaration, SOURCE_LAYOUT_RULE_ID,
|
|
378
|
-
'TypeORM entities and migrations cannot be owned by a feature; place schema under its declared persistence module.'));
|
|
379
|
-
const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
|
|
380
|
-
if (publicDeclarations.has(declaration) && !declaration.name) namingReasons.push(`${relative} exports an anonymous class whose role name cannot be proved`);
|
|
381
|
-
else if (publicDeclarations.has(declaration) && declaration.name && classSuffix && !declaration.name.text.endsWith(classSuffix)
|
|
382
|
-
&& !implementedPortNames(ts, checker, declaration).some(port => declaration.name.text.endsWith(port))) {
|
|
383
|
-
violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
|
|
384
|
-
`Exported class ${declaration.name.text} must end in ${classSuffix} for a ${role.role} source file, or in the name of the port it implements.`));
|
|
385
|
-
} else if (publicDeclarations.has(declaration) && declaration.name && role.role && !NON_CLASS_ROLES.has(role.role) && !classSuffix) {
|
|
386
|
-
namingReasons.push(`${relative} declares exported class ${declaration.name.text} with unclassified file role ${role.role}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
for (const statement of sourceFile.statements) if (ts.isVariableStatement(statement)) for (const declaration of statement.declarationList.declarations) {
|
|
391
|
-
if (!publicDeclarations.has(declaration) || !declaration.initializer) continue;
|
|
392
|
-
const classValue = classValueStatus(ts, checker, declaration.initializer);
|
|
393
|
-
if (classValue.status === 'unavailable') {
|
|
394
|
-
namingReasons.push(`${relative} exports a class-valued declaration whose role identity is not statically proved`);
|
|
395
|
-
continue;
|
|
396
|
-
}
|
|
397
|
-
if (classValue.status !== 'class') continue;
|
|
398
|
-
const classSuffix = role.role && !NON_CLASS_ROLES.has(role.role) ? CLASS_ROLE_SUFFIX.get(role.role) : null;
|
|
399
|
-
if (!classSuffix || !ts.isIdentifier(declaration.name)) {
|
|
400
|
-
namingReasons.push(`${relative} exports a class-valued declaration without a statically selected class role`);
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
const names = [declaration.name.text, ...classValue.names];
|
|
404
|
-
const invalid = names.find(name => !name.endsWith(classSuffix));
|
|
405
|
-
if (invalid) violations.push(violation(config, sourceFile, declaration.name, SOURCE_NAME_RULE_ID,
|
|
406
|
-
`Exported class value ${invalid} must end in ${classSuffix} for a ${role.role} source file.`));
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
for (const statement of sourceFile.statements) {
|
|
410
|
-
if (ts.isEnumDeclaration(statement)) {
|
|
411
|
-
if (hasModifier(ts, statement, ts.SyntaxKind.ConstKeyword) || !/^[A-Z][A-Za-z0-9]*$/.test(statement.name.text)) {
|
|
412
|
-
violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
|
|
413
|
-
'Enums must be non-const declarations with PascalCase names.'));
|
|
414
|
-
}
|
|
415
|
-
for (const member of statement.members) {
|
|
416
|
-
const name = ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name) ? member.name.text : null;
|
|
417
|
-
const initializer = member.initializer && unwrapExpression(ts, member.initializer);
|
|
418
|
-
if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name) || !initializer || !ts.isStringLiteralLike(initializer)) {
|
|
419
|
-
violations.push(violation(config, sourceFile, member.name, SOURCE_NAME_RULE_ID,
|
|
420
|
-
'Enum members must use PascalCase names with explicit static string values.'));
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const contractStatus = objectContractStatus(ts, checker, statement);
|
|
425
|
-
const transportSuffix = TRANSPORT_OBJECT_SUFFIX.get(role.role);
|
|
426
|
-
if (contractStatus === 'object' && publicDeclarations.has(statement) && transportSuffix && !role.spec
|
|
427
|
-
&& !statement.name.text.endsWith(transportSuffix) && !companionInterfaceAllowed(ts, sourceFile, statement.name.text)) {
|
|
428
|
-
violations.push(violation(config, sourceFile, statement.name, SOURCE_NAME_RULE_ID,
|
|
429
|
-
`Public object contract ${statement.name.text} in a ${role.role} source file must end in ${transportSuffix}.`));
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const visit = node => {
|
|
434
|
-
for (const decorator of nodeDecorators(ts, node)) {
|
|
435
|
-
const kind = decoratorKind(ts, checker, decorator, framework.bySymbol);
|
|
436
|
-
if (!kind) {
|
|
437
|
-
const dynamic = mutableDecoratorKind(ts, checker, decorator, framework.bySymbol)
|
|
438
|
-
?? constructedDecoratorKind(ts, checker, decorator, framework.bySymbol, TRACE_NEW);
|
|
439
|
-
if (dynamic) {
|
|
440
|
-
const detail = `${relative} uses a mutable or constructed ${dynamic} decorator identity`;
|
|
441
|
-
if (['Entity', 'ViewEntity', 'ArgsType', 'InputType', 'ObjectType', 'multiple framework', 'unproven framework'].includes(dynamic)) layoutReasons.push(detail);
|
|
442
|
-
if (!['Entity', 'ViewEntity'].includes(dynamic)) namingReasons.push(detail);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (kind === 'Mutation' || kind === 'Query') graphqlField(config, context, sourceFile, node, decorator, kind, namingReasons, violations);
|
|
446
|
-
if (kind === 'Args') graphqlArgument(config, context, sourceFile, node, decorator, namingReasons, violations);
|
|
447
|
-
}
|
|
448
|
-
if (ts.isCallExpression(node)) {
|
|
449
|
-
if (selectedCallKind(ts, checker, node, framework.bySymbol) === 'registerEnumType') {
|
|
450
|
-
graphqlEnumRegistration(config, context, sourceFile, node, namingReasons, violations);
|
|
451
|
-
} else if (callsNamedHelper(ts, checker, node, 'createEnumType')) {
|
|
452
|
-
namingReasons.push(`${relative} calls a project GraphQL enum adapter whose emitted type name is not statically proved`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
const exportedFactoryCall = ts.isCallExpression(node) && ts.isVariableDeclaration(node.parent) && publicDeclarations.has(node.parent);
|
|
456
|
-
if ((ts.isNewExpression(node) || exportedFactoryCall) && featureRoot) {
|
|
457
|
-
const constructed = tracedFrameworkKinds(ts, checker, node, framework.bySymbol, TRACE_NEW);
|
|
458
|
-
if (constructed.has('EntitySchema')) violations.push(violation(config, sourceFile, node.expression, SOURCE_LAYOUT_RULE_ID,
|
|
459
|
-
'TypeORM EntitySchema cannot be owned by a feature; place schema under its declared persistence module.'));
|
|
460
|
-
if (constructed.has(UNPROVEN_FRAMEWORK)) layoutReasons.push(`${relative} has a constructed provider identity beyond the bounded static trace`);
|
|
461
|
-
}
|
|
462
|
-
ts.forEachChild(node, visit);
|
|
463
|
-
};
|
|
464
|
-
visit(sourceFile);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
for (const framework of frameworkByChecker.values()) {
|
|
468
|
-
layoutReasons.push(...framework.reasons);
|
|
469
|
-
namingReasons.push(...framework.reasons.filter(reason => reason.includes('@nestjs/graphql')));
|
|
470
|
-
}
|
|
471
|
-
if (checkedFiles === 0) {
|
|
472
|
-
layoutReasons.push('the checked TypeScript program contains no configured backend feature or module source');
|
|
473
|
-
namingReasons.push('the checked TypeScript program contains no configured backend feature or module source');
|
|
474
|
-
}
|
|
475
|
-
const coverage = {
|
|
476
|
-
files: checkedFiles,
|
|
477
|
-
layout: layoutReasons.length
|
|
478
|
-
? { status: 'unavailable', reason: 'one or more backend source placement relations are not statically proved', details: [...new Set(layoutReasons)].sort() }
|
|
479
|
-
: { status: 'checked' },
|
|
480
|
-
naming: namingReasons.length
|
|
481
|
-
? { status: 'unavailable', reason: 'one or more backend source naming relations are not statically proved', details: [...new Set(namingReasons)].sort() }
|
|
482
|
-
: { status: 'checked' },
|
|
483
|
-
};
|
|
484
|
-
return { violations, coverage };
|
|
319
|
+
export function checkBackendSourceShape(config, context) {
|
|
320
|
+
return checkBackendSourceShapeImpl(config, context);
|
|
485
321
|
}
|
|
486
|
-
|
|
487
322
|
export { SOURCE_LAYOUT_RULE_ID, SOURCE_NAME_RULE_ID };
|