@nielspeter/eess-ts 0.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/LICENSE +21 -0
- package/README.md +364 -0
- package/dist/builders/call-rule-builder.d.ts +199 -0
- package/dist/builders/call-rule-builder.d.ts.map +1 -0
- package/dist/builders/call-rule-builder.js +254 -0
- package/dist/builders/call-rule-builder.js.map +1 -0
- package/dist/builders/class-rule-builder.d.ts +136 -0
- package/dist/builders/class-rule-builder.d.ts.map +1 -0
- package/dist/builders/class-rule-builder.js +253 -0
- package/dist/builders/class-rule-builder.js.map +1 -0
- package/dist/builders/cross-layer-builder.d.ts +96 -0
- package/dist/builders/cross-layer-builder.d.ts.map +1 -0
- package/dist/builders/cross-layer-builder.js +172 -0
- package/dist/builders/cross-layer-builder.js.map +1 -0
- package/dist/builders/function-rule-builder.d.ts +198 -0
- package/dist/builders/function-rule-builder.d.ts.map +1 -0
- package/dist/builders/function-rule-builder.js +287 -0
- package/dist/builders/function-rule-builder.js.map +1 -0
- package/dist/builders/jsx-rule-builder.d.ts +113 -0
- package/dist/builders/jsx-rule-builder.d.ts.map +1 -0
- package/dist/builders/jsx-rule-builder.js +150 -0
- package/dist/builders/jsx-rule-builder.js.map +1 -0
- package/dist/builders/module-rule-builder.d.ts +159 -0
- package/dist/builders/module-rule-builder.d.ts.map +1 -0
- package/dist/builders/module-rule-builder.js +237 -0
- package/dist/builders/module-rule-builder.js.map +1 -0
- package/dist/builders/scoped-function-rule-builder.d.ts +21 -0
- package/dist/builders/scoped-function-rule-builder.d.ts.map +1 -0
- package/dist/builders/scoped-function-rule-builder.js +26 -0
- package/dist/builders/scoped-function-rule-builder.js.map +1 -0
- package/dist/builders/slice-rule-builder.d.ts +85 -0
- package/dist/builders/slice-rule-builder.d.ts.map +1 -0
- package/dist/builders/slice-rule-builder.js +134 -0
- package/dist/builders/slice-rule-builder.js.map +1 -0
- package/dist/builders/type-rule-builder.d.ts +103 -0
- package/dist/builders/type-rule-builder.d.ts.map +1 -0
- package/dist/builders/type-rule-builder.js +170 -0
- package/dist/builders/type-rule-builder.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/baseline.d.ts +11 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +16 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/check.d.ts +21 -0
- package/dist/cli/commands/check.d.ts.map +1 -0
- package/dist/cli/commands/check.js +94 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +9 -0
- package/dist/cli/commands/explain.d.ts.map +1 -0
- package/dist/cli/commands/explain.js +49 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/config.d.ts +31 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +20 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +21 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +168 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/load-rules.d.ts +20 -0
- package/dist/cli/load-rules.d.ts.map +1 -0
- package/dist/cli/load-rules.js +97 -0
- package/dist/cli/load-rules.js.map +1 -0
- package/dist/cli/resolve-config.d.ts +11 -0
- package/dist/cli/resolve-config.d.ts.map +1 -0
- package/dist/cli/resolve-config.js +61 -0
- package/dist/cli/resolve-config.js.map +1 -0
- package/dist/cli/watch.d.ts +49 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +138 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/conditions/body-analysis-function.d.ts +23 -0
- package/dist/conditions/body-analysis-function.d.ts.map +1 -0
- package/dist/conditions/body-analysis-function.js +103 -0
- package/dist/conditions/body-analysis-function.js.map +1 -0
- package/dist/conditions/body-analysis-module.d.ts +27 -0
- package/dist/conditions/body-analysis-module.d.ts.map +1 -0
- package/dist/conditions/body-analysis-module.js +99 -0
- package/dist/conditions/body-analysis-module.js.map +1 -0
- package/dist/conditions/body-analysis.d.ts +34 -0
- package/dist/conditions/body-analysis.d.ts.map +1 -0
- package/dist/conditions/body-analysis.js +95 -0
- package/dist/conditions/body-analysis.js.map +1 -0
- package/dist/conditions/call.d.ts +62 -0
- package/dist/conditions/call.d.ts.map +1 -0
- package/dist/conditions/call.js +267 -0
- package/dist/conditions/call.js.map +1 -0
- package/dist/conditions/catch-analysis.d.ts +31 -0
- package/dist/conditions/catch-analysis.d.ts.map +1 -0
- package/dist/conditions/catch-analysis.js +78 -0
- package/dist/conditions/catch-analysis.js.map +1 -0
- package/dist/conditions/class.d.ts +33 -0
- package/dist/conditions/class.d.ts.map +1 -0
- package/dist/conditions/class.js +110 -0
- package/dist/conditions/class.js.map +1 -0
- package/dist/conditions/cross-layer.d.ts +28 -0
- package/dist/conditions/cross-layer.d.ts.map +1 -0
- package/dist/conditions/cross-layer.js +105 -0
- package/dist/conditions/cross-layer.js.map +1 -0
- package/dist/conditions/dependency.d.ts +73 -0
- package/dist/conditions/dependency.d.ts.map +1 -0
- package/dist/conditions/dependency.js +177 -0
- package/dist/conditions/dependency.js.map +1 -0
- package/dist/conditions/exports.d.ts +33 -0
- package/dist/conditions/exports.d.ts.map +1 -0
- package/dist/conditions/exports.js +101 -0
- package/dist/conditions/exports.js.map +1 -0
- package/dist/conditions/function.d.ts +79 -0
- package/dist/conditions/function.d.ts.map +1 -0
- package/dist/conditions/function.js +167 -0
- package/dist/conditions/function.js.map +1 -0
- package/dist/conditions/helpers.d.ts +14 -0
- package/dist/conditions/helpers.d.ts.map +1 -0
- package/dist/conditions/helpers.js +26 -0
- package/dist/conditions/helpers.js.map +1 -0
- package/dist/conditions/jsx.d.ts +40 -0
- package/dist/conditions/jsx.d.ts.map +1 -0
- package/dist/conditions/jsx.js +133 -0
- package/dist/conditions/jsx.js.map +1 -0
- package/dist/conditions/members.d.ts +74 -0
- package/dist/conditions/members.d.ts.map +1 -0
- package/dist/conditions/members.js +224 -0
- package/dist/conditions/members.js.map +1 -0
- package/dist/conditions/pattern.d.ts +20 -0
- package/dist/conditions/pattern.d.ts.map +1 -0
- package/dist/conditions/pattern.js +108 -0
- package/dist/conditions/pattern.js.map +1 -0
- package/dist/conditions/reverse-dependency.d.ts +28 -0
- package/dist/conditions/reverse-dependency.d.ts.map +1 -0
- package/dist/conditions/reverse-dependency.js +275 -0
- package/dist/conditions/reverse-dependency.js.map +1 -0
- package/dist/conditions/slice.d.ts +49 -0
- package/dist/conditions/slice.d.ts.map +1 -0
- package/dist/conditions/slice.js +153 -0
- package/dist/conditions/slice.js.map +1 -0
- package/dist/conditions/structural.d.ts +58 -0
- package/dist/conditions/structural.d.ts.map +1 -0
- package/dist/conditions/structural.js +102 -0
- package/dist/conditions/structural.js.map +1 -0
- package/dist/conditions/type-level.d.ts +29 -0
- package/dist/conditions/type-level.d.ts.map +1 -0
- package/dist/conditions/type-level.js +48 -0
- package/dist/conditions/type-level.js.map +1 -0
- package/dist/core/import-options.d.ts +32 -0
- package/dist/core/import-options.d.ts.map +1 -0
- package/dist/core/import-options.js +29 -0
- package/dist/core/import-options.js.map +1 -0
- package/dist/core/module-dependencies.d.ts +28 -0
- package/dist/core/module-dependencies.d.ts.map +1 -0
- package/dist/core/module-dependencies.js +35 -0
- package/dist/core/module-dependencies.js.map +1 -0
- package/dist/core/pair-condition.d.ts +10 -0
- package/dist/core/pair-condition.d.ts.map +1 -0
- package/dist/core/pair-condition.js +2 -0
- package/dist/core/pair-condition.js.map +1 -0
- package/dist/core/project.d.ts +68 -0
- package/dist/core/project.d.ts.map +1 -0
- package/dist/core/project.js +127 -0
- package/dist/core/project.js.map +1 -0
- package/dist/core/type-matcher.d.ts +10 -0
- package/dist/core/type-matcher.d.ts.map +1 -0
- package/dist/core/type-matcher.js +2 -0
- package/dist/core/type-matcher.js.map +1 -0
- package/dist/core/violation.d.ts +38 -0
- package/dist/core/violation.d.ts.map +1 -0
- package/dist/core/violation.js +130 -0
- package/dist/core/violation.js.map +1 -0
- package/dist/graphql/index.d.ts +76 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +108 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/resolver-rule-builder.d.ts +75 -0
- package/dist/graphql/resolver-rule-builder.d.ts.map +1 -0
- package/dist/graphql/resolver-rule-builder.js +146 -0
- package/dist/graphql/resolver-rule-builder.js.map +1 -0
- package/dist/graphql/schema-conditions.d.ts +31 -0
- package/dist/graphql/schema-conditions.d.ts.map +1 -0
- package/dist/graphql/schema-conditions.js +119 -0
- package/dist/graphql/schema-conditions.js.map +1 -0
- package/dist/graphql/schema-loader.d.ts +75 -0
- package/dist/graphql/schema-loader.d.ts.map +1 -0
- package/dist/graphql/schema-loader.js +113 -0
- package/dist/graphql/schema-loader.js.map +1 -0
- package/dist/graphql/schema-predicates.d.ts +42 -0
- package/dist/graphql/schema-predicates.d.ts.map +1 -0
- package/dist/graphql/schema-predicates.js +74 -0
- package/dist/graphql/schema-predicates.js.map +1 -0
- package/dist/graphql/schema-rule-builder.d.ts +74 -0
- package/dist/graphql/schema-rule-builder.d.ts.map +1 -0
- package/dist/graphql/schema-rule-builder.js +188 -0
- package/dist/graphql/schema-rule-builder.js.map +1 -0
- package/dist/helpers/body-traversal.d.ts +58 -0
- package/dist/helpers/body-traversal.d.ts.map +1 -0
- package/dist/helpers/body-traversal.js +180 -0
- package/dist/helpers/body-traversal.js.map +1 -0
- package/dist/helpers/callback-extractor.d.ts +28 -0
- package/dist/helpers/callback-extractor.d.ts.map +1 -0
- package/dist/helpers/callback-extractor.js +159 -0
- package/dist/helpers/callback-extractor.js.map +1 -0
- package/dist/helpers/complexity.d.ts +26 -0
- package/dist/helpers/complexity.d.ts.map +1 -0
- package/dist/helpers/complexity.js +64 -0
- package/dist/helpers/complexity.js.map +1 -0
- package/dist/helpers/matchers.d.ts +206 -0
- package/dist/helpers/matchers.d.ts.map +1 -0
- package/dist/helpers/matchers.js +470 -0
- package/dist/helpers/matchers.js.map +1 -0
- package/dist/helpers/pattern.d.ts +37 -0
- package/dist/helpers/pattern.d.ts.map +1 -0
- package/dist/helpers/pattern.js +22 -0
- package/dist/helpers/pattern.js.map +1 -0
- package/dist/helpers/slice-graph.d.ts +32 -0
- package/dist/helpers/slice-graph.d.ts.map +1 -0
- package/dist/helpers/slice-graph.js +89 -0
- package/dist/helpers/slice-graph.js.map +1 -0
- package/dist/helpers/tarjan.d.ts +21 -0
- package/dist/helpers/tarjan.d.ts.map +1 -0
- package/dist/helpers/tarjan.js +71 -0
- package/dist/helpers/tarjan.js.map +1 -0
- package/dist/helpers/type-matchers.d.ts +67 -0
- package/dist/helpers/type-matchers.d.ts.map +1 -0
- package/dist/helpers/type-matchers.js +108 -0
- package/dist/helpers/type-matchers.js.map +1 -0
- package/dist/helpers/within.d.ts +41 -0
- package/dist/helpers/within.d.ts.map +1 -0
- package/dist/helpers/within.js +32 -0
- package/dist/helpers/within.js.map +1 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/models/arch-call.d.ts +83 -0
- package/dist/models/arch-call.d.ts.map +1 -0
- package/dist/models/arch-call.js +90 -0
- package/dist/models/arch-call.js.map +1 -0
- package/dist/models/arch-function.d.ts +76 -0
- package/dist/models/arch-function.d.ts.map +1 -0
- package/dist/models/arch-function.js +122 -0
- package/dist/models/arch-function.js.map +1 -0
- package/dist/models/arch-jsx-element.d.ts +75 -0
- package/dist/models/arch-jsx-element.d.ts.map +1 -0
- package/dist/models/arch-jsx-element.js +231 -0
- package/dist/models/arch-jsx-element.js.map +1 -0
- package/dist/models/cross-layer.d.ts +15 -0
- package/dist/models/cross-layer.d.ts.map +1 -0
- package/dist/models/cross-layer.js +2 -0
- package/dist/models/cross-layer.js.map +1 -0
- package/dist/models/slice.d.ts +60 -0
- package/dist/models/slice.d.ts.map +1 -0
- package/dist/models/slice.js +89 -0
- package/dist/models/slice.js.map +1 -0
- package/dist/predicates/call.d.ts +49 -0
- package/dist/predicates/call.d.ts.map +1 -0
- package/dist/predicates/call.js +115 -0
- package/dist/predicates/call.js.map +1 -0
- package/dist/predicates/class.d.ts +42 -0
- package/dist/predicates/class.d.ts.map +1 -0
- package/dist/predicates/class.js +83 -0
- package/dist/predicates/class.js.map +1 -0
- package/dist/predicates/function.d.ts +81 -0
- package/dist/predicates/function.d.ts.map +1 -0
- package/dist/predicates/function.js +157 -0
- package/dist/predicates/function.js.map +1 -0
- package/dist/predicates/identity.d.ts +55 -0
- package/dist/predicates/identity.d.ts.map +1 -0
- package/dist/predicates/identity.js +96 -0
- package/dist/predicates/identity.js.map +1 -0
- package/dist/predicates/index.d.ts +4 -0
- package/dist/predicates/index.d.ts.map +1 -0
- package/dist/predicates/index.js +3 -0
- package/dist/predicates/index.js.map +1 -0
- package/dist/predicates/jsx.d.ts +41 -0
- package/dist/predicates/jsx.d.ts.map +1 -0
- package/dist/predicates/jsx.js +84 -0
- package/dist/predicates/jsx.js.map +1 -0
- package/dist/predicates/metrics.d.ts +30 -0
- package/dist/predicates/metrics.d.ts.map +1 -0
- package/dist/predicates/metrics.js +65 -0
- package/dist/predicates/metrics.js.map +1 -0
- package/dist/predicates/module.d.ts +44 -0
- package/dist/predicates/module.d.ts.map +1 -0
- package/dist/predicates/module.js +70 -0
- package/dist/predicates/module.js.map +1 -0
- package/dist/predicates/type.d.ts +48 -0
- package/dist/predicates/type.d.ts.map +1 -0
- package/dist/predicates/type.js +107 -0
- package/dist/predicates/type.js.map +1 -0
- package/dist/presets/boundaries.d.ts +19 -0
- package/dist/presets/boundaries.d.ts.map +1 -0
- package/dist/presets/boundaries.js +93 -0
- package/dist/presets/boundaries.js.map +1 -0
- package/dist/presets/data-layer.d.ts +20 -0
- package/dist/presets/data-layer.d.ts.map +1 -0
- package/dist/presets/data-layer.js +30 -0
- package/dist/presets/data-layer.js.map +1 -0
- package/dist/presets/index.d.ts +9 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +5 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/layered.d.ts +21 -0
- package/dist/presets/layered.d.ts.map +1 -0
- package/dist/presets/layered.js +103 -0
- package/dist/presets/layered.js.map +1 -0
- package/dist/presets/shared.d.ts +3 -0
- package/dist/presets/shared.d.ts.map +1 -0
- package/dist/presets/shared.js +5 -0
- package/dist/presets/shared.js.map +1 -0
- package/dist/rules/architecture.d.ts +10 -0
- package/dist/rules/architecture.d.ts.map +1 -0
- package/dist/rules/architecture.js +14 -0
- package/dist/rules/architecture.js.map +1 -0
- package/dist/rules/code-quality.d.ts +55 -0
- package/dist/rules/code-quality.d.ts.map +1 -0
- package/dist/rules/code-quality.js +116 -0
- package/dist/rules/code-quality.js.map +1 -0
- package/dist/rules/dependencies.d.ts +32 -0
- package/dist/rules/dependencies.d.ts.map +1 -0
- package/dist/rules/dependencies.js +37 -0
- package/dist/rules/dependencies.js.map +1 -0
- package/dist/rules/errors.d.ts +31 -0
- package/dist/rules/errors.d.ts.map +1 -0
- package/dist/rules/errors.js +97 -0
- package/dist/rules/errors.js.map +1 -0
- package/dist/rules/hygiene.d.ts +54 -0
- package/dist/rules/hygiene.d.ts.map +1 -0
- package/dist/rules/hygiene.js +62 -0
- package/dist/rules/hygiene.js.map +1 -0
- package/dist/rules/metrics-function.d.ts +45 -0
- package/dist/rules/metrics-function.d.ts.map +1 -0
- package/dist/rules/metrics-function.js +87 -0
- package/dist/rules/metrics-function.js.map +1 -0
- package/dist/rules/metrics.d.ts +64 -0
- package/dist/rules/metrics.d.ts.map +1 -0
- package/dist/rules/metrics.js +157 -0
- package/dist/rules/metrics.js.map +1 -0
- package/dist/rules/naming.d.ts +24 -0
- package/dist/rules/naming.d.ts.map +1 -0
- package/dist/rules/naming.js +51 -0
- package/dist/rules/naming.js.map +1 -0
- package/dist/rules/security.d.ts +59 -0
- package/dist/rules/security.d.ts.map +1 -0
- package/dist/rules/security.js +92 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/rules/typescript.d.ts +73 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +105 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/smells/duplicate-bodies.d.ts +24 -0
- package/dist/smells/duplicate-bodies.d.ts.map +1 -0
- package/dist/smells/duplicate-bodies.js +131 -0
- package/dist/smells/duplicate-bodies.js.map +1 -0
- package/dist/smells/fingerprint.d.ts +27 -0
- package/dist/smells/fingerprint.d.ts.map +1 -0
- package/dist/smells/fingerprint.js +50 -0
- package/dist/smells/fingerprint.js.map +1 -0
- package/dist/smells/inconsistent-siblings.d.ts +21 -0
- package/dist/smells/inconsistent-siblings.d.ts.map +1 -0
- package/dist/smells/inconsistent-siblings.js +135 -0
- package/dist/smells/inconsistent-siblings.js.map +1 -0
- package/dist/smells/index.d.ts +12 -0
- package/dist/smells/index.d.ts.map +1 -0
- package/dist/smells/index.js +15 -0
- package/dist/smells/index.js.map +1 -0
- package/dist/smells/smell-builder.d.ts +37 -0
- package/dist/smells/smell-builder.d.ts.map +1 -0
- package/dist/smells/smell-builder.js +51 -0
- package/dist/smells/smell-builder.js.map +1 -0
- package/package.json +121 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import picomatch from 'picomatch';
|
|
2
|
+
import nodePath from 'node:path';
|
|
3
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
4
|
+
const graphCache = new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* Build or retrieve the reverse import graph for a project.
|
|
7
|
+
*
|
|
8
|
+
* Maps each file path to the list of files that import it.
|
|
9
|
+
* Cached per ts-morph Project instance — multiple rules in the same
|
|
10
|
+
* test suite share the same graph. Cache is cleared automatically
|
|
11
|
+
* when resetProjectCache() creates new ArchProject instances (WeakMap GC).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Add an edge to the reverse import graph: targetPath is imported by sf.
|
|
15
|
+
* Deduplicates entries when `deduplicate` is true (used for re-exports
|
|
16
|
+
* where the same file may appear via both import and export declarations).
|
|
17
|
+
*/
|
|
18
|
+
function addToGraph(graph, targetPath, sf, deduplicate) {
|
|
19
|
+
const existing = graph.get(targetPath);
|
|
20
|
+
if (existing) {
|
|
21
|
+
if (!deduplicate || !existing.includes(sf)) {
|
|
22
|
+
existing.push(sf);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
graph.set(targetPath, [sf]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a dynamic import specifier to a project source file.
|
|
31
|
+
*
|
|
32
|
+
* Handles relative specifiers with ESM extension mappings (.js→.ts, .jsx→.tsx,
|
|
33
|
+
* .mjs→.mts) and directory index imports. Non-relative specifiers (bare packages)
|
|
34
|
+
* are skipped — they resolve to node_modules, which are outside the project.
|
|
35
|
+
*/
|
|
36
|
+
function resolveDynamicImport(fromFile, specifier) {
|
|
37
|
+
if (!specifier.startsWith('.'))
|
|
38
|
+
return undefined;
|
|
39
|
+
const dirPath = fromFile.getDirectory().getPath();
|
|
40
|
+
const project = fromFile.getProject();
|
|
41
|
+
// Try the specifier as-is, with ESM extension mappings, and as a directory index
|
|
42
|
+
const candidates = [
|
|
43
|
+
specifier,
|
|
44
|
+
specifier.replace(/\.js$/, '.ts'),
|
|
45
|
+
specifier.replace(/\.jsx$/, '.tsx'),
|
|
46
|
+
specifier.replace(/\.mjs$/, '.mts'),
|
|
47
|
+
specifier + '.ts',
|
|
48
|
+
specifier + '.tsx',
|
|
49
|
+
specifier + '/index.ts',
|
|
50
|
+
specifier + '/index.tsx',
|
|
51
|
+
];
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
const absolutePath = nodePath.resolve(dirPath, candidate);
|
|
54
|
+
const resolved = project.getSourceFile(absolutePath);
|
|
55
|
+
if (resolved)
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Index static import declarations from a source file into the reverse graph. */
|
|
61
|
+
function indexStaticImports(graph, sf) {
|
|
62
|
+
for (const decl of sf.getImportDeclarations()) {
|
|
63
|
+
const resolved = decl.getModuleSpecifierSourceFile();
|
|
64
|
+
if (!resolved)
|
|
65
|
+
continue;
|
|
66
|
+
addToGraph(graph, resolved.getFilePath(), sf, false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Index re-export declarations from a source file into the reverse graph. */
|
|
70
|
+
function indexReExports(graph, sf) {
|
|
71
|
+
for (const decl of sf.getExportDeclarations()) {
|
|
72
|
+
const resolved = decl.getModuleSpecifierSourceFile();
|
|
73
|
+
if (!resolved)
|
|
74
|
+
continue;
|
|
75
|
+
addToGraph(graph, resolved.getFilePath(), sf, true);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Index dynamic import() expressions from a source file into the reverse graph. */
|
|
79
|
+
function indexDynamicImports(graph, sf) {
|
|
80
|
+
for (const callExpr of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
81
|
+
if (callExpr.getExpression().getKind() !== SyntaxKind.ImportKeyword)
|
|
82
|
+
continue;
|
|
83
|
+
const args = callExpr.getArguments();
|
|
84
|
+
if (args.length === 0)
|
|
85
|
+
continue;
|
|
86
|
+
const arg = args[0];
|
|
87
|
+
if (!arg)
|
|
88
|
+
continue;
|
|
89
|
+
let specifier;
|
|
90
|
+
if (Node.isStringLiteral(arg) || Node.isNoSubstitutionTemplateLiteral(arg)) {
|
|
91
|
+
specifier = arg.getLiteralValue();
|
|
92
|
+
}
|
|
93
|
+
if (specifier === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
const resolved = resolveDynamicImport(sf, specifier);
|
|
96
|
+
if (resolved) {
|
|
97
|
+
addToGraph(graph, resolved.getFilePath(), sf, true);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function getReverseImportGraph(sourceFiles) {
|
|
102
|
+
if (sourceFiles.length === 0)
|
|
103
|
+
return new Map();
|
|
104
|
+
const firstFile = sourceFiles[0];
|
|
105
|
+
if (!firstFile)
|
|
106
|
+
return new Map();
|
|
107
|
+
const project = firstFile.getProject();
|
|
108
|
+
const cached = graphCache.get(project);
|
|
109
|
+
if (cached)
|
|
110
|
+
return cached;
|
|
111
|
+
const graph = new Map();
|
|
112
|
+
for (const sf of sourceFiles) {
|
|
113
|
+
indexStaticImports(graph, sf);
|
|
114
|
+
indexReExports(graph, sf);
|
|
115
|
+
indexDynamicImports(graph, sf);
|
|
116
|
+
}
|
|
117
|
+
graphCache.set(project, graph);
|
|
118
|
+
return graph;
|
|
119
|
+
}
|
|
120
|
+
// ─── Conditions ──────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Every file that imports this module must match at least one of the globs.
|
|
123
|
+
*
|
|
124
|
+
* Enforces barrel/facade patterns: internal modules should only be
|
|
125
|
+
* imported through their public API (e.g., index.ts).
|
|
126
|
+
*
|
|
127
|
+
* Modules with zero importers pass vacuously. Chain `.andShould().beImported()`
|
|
128
|
+
* if you also want to catch orphaned files.
|
|
129
|
+
*
|
|
130
|
+
* Both static `import` declarations and dynamic `import()` expressions with
|
|
131
|
+
* string-literal specifiers are resolved. `require()` calls are not resolved.
|
|
132
|
+
*/
|
|
133
|
+
export function onlyBeImportedVia(...globs) {
|
|
134
|
+
const matchers = globs.map((g) => picomatch(g));
|
|
135
|
+
const quotedGlobs = globs.map((g) => `"${g}"`).join(', ');
|
|
136
|
+
return {
|
|
137
|
+
description: `only be imported via ${quotedGlobs}`,
|
|
138
|
+
evaluate(elements, context) {
|
|
139
|
+
// Build graph from ALL project files, not just the filtered elements
|
|
140
|
+
const first = elements[0];
|
|
141
|
+
const allFiles = first ? first.getProject().getSourceFiles() : [];
|
|
142
|
+
const graph = getReverseImportGraph(allFiles);
|
|
143
|
+
const violations = [];
|
|
144
|
+
for (const sf of elements) {
|
|
145
|
+
const importers = graph.get(sf.getFilePath()) ?? [];
|
|
146
|
+
for (const importer of importers) {
|
|
147
|
+
const importerPath = importer.getFilePath();
|
|
148
|
+
if (!matchers.some((m) => m(importerPath))) {
|
|
149
|
+
violations.push({
|
|
150
|
+
rule: context.rule,
|
|
151
|
+
element: sf.getBaseName(),
|
|
152
|
+
file: sf.getFilePath(),
|
|
153
|
+
line: 1,
|
|
154
|
+
message: `${sf.getBaseName()} is imported by ${importer.getBaseName()} which does not match [${globs.join(', ')}]`,
|
|
155
|
+
because: context.because,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return violations;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Module must be imported by at least one other file in the project.
|
|
166
|
+
*
|
|
167
|
+
* Detects dead/orphaned modules that nobody references.
|
|
168
|
+
* Use `.excluding('index.ts', 'main.ts')` to skip entry points.
|
|
169
|
+
*
|
|
170
|
+
* Both static `import` declarations and dynamic `import()` expressions with
|
|
171
|
+
* string-literal specifiers are resolved. Modules loaded via `require()` or
|
|
172
|
+
* dynamic imports with computed specifiers will still be falsely reported.
|
|
173
|
+
*/
|
|
174
|
+
export function beImported() {
|
|
175
|
+
return {
|
|
176
|
+
description: 'be imported by at least one other module',
|
|
177
|
+
evaluate(elements, context) {
|
|
178
|
+
const first = elements[0];
|
|
179
|
+
const allFiles = first ? first.getProject().getSourceFiles() : [];
|
|
180
|
+
const graph = getReverseImportGraph(allFiles);
|
|
181
|
+
const violations = [];
|
|
182
|
+
for (const sf of elements) {
|
|
183
|
+
const importers = graph.get(sf.getFilePath()) ?? [];
|
|
184
|
+
if (importers.length === 0) {
|
|
185
|
+
violations.push({
|
|
186
|
+
rule: context.rule,
|
|
187
|
+
element: sf.getBaseName(),
|
|
188
|
+
file: sf.getFilePath(),
|
|
189
|
+
line: 1,
|
|
190
|
+
message: `${sf.getBaseName()} is not imported by any other module`,
|
|
191
|
+
because: context.because,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return violations;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Every named export in the module must be referenced by at least one other file.
|
|
201
|
+
*
|
|
202
|
+
* Uses ts-morph's findReferencesAsNodes() per export symbol.
|
|
203
|
+
* Short-circuits after first external reference is found.
|
|
204
|
+
*
|
|
205
|
+
* More expensive than file-level checks — scope with `.that().resideInFolder()`
|
|
206
|
+
* to limit the search space.
|
|
207
|
+
*
|
|
208
|
+
* **Note:** Default exports are excluded from this check. Use `beImported()` for
|
|
209
|
+
* file-level dead code detection. Only named exports are analyzed.
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* Scan a single source file for unused named exports, returning violations.
|
|
213
|
+
*/
|
|
214
|
+
function findUnusedExportsInFile(sf, context) {
|
|
215
|
+
const violations = [];
|
|
216
|
+
const exportMap = sf.getExportedDeclarations();
|
|
217
|
+
for (const [name, declarations] of exportMap) {
|
|
218
|
+
if (name === 'default')
|
|
219
|
+
continue;
|
|
220
|
+
const firstDecl = declarations[0];
|
|
221
|
+
if (firstDecl === undefined)
|
|
222
|
+
continue;
|
|
223
|
+
if (!hasExternalReference(firstDecl, sf)) {
|
|
224
|
+
const line = Node.isNode(firstDecl) ? firstDecl.getStartLineNumber() : 1;
|
|
225
|
+
violations.push({
|
|
226
|
+
rule: context.rule,
|
|
227
|
+
element: sf.getBaseName(),
|
|
228
|
+
file: sf.getFilePath(),
|
|
229
|
+
line,
|
|
230
|
+
message: `${sf.getBaseName()} exports "${name}" which is not referenced by any other file`,
|
|
231
|
+
because: context.because,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return violations;
|
|
236
|
+
}
|
|
237
|
+
export function haveNoUnusedExports() {
|
|
238
|
+
return {
|
|
239
|
+
description: 'have no unused exports',
|
|
240
|
+
evaluate(elements, context) {
|
|
241
|
+
const violations = [];
|
|
242
|
+
for (const sf of elements) {
|
|
243
|
+
violations.push(...findUnusedExportsInFile(sf, context));
|
|
244
|
+
}
|
|
245
|
+
return violations;
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check if a declaration has at least one reference from a different file.
|
|
251
|
+
* Short-circuits on first external reference found.
|
|
252
|
+
*
|
|
253
|
+
* Uses the project's LanguageService.findReferencesAsNodes() which accepts
|
|
254
|
+
* any Node (unlike the ReferenceFindableNode mixin which only some types have).
|
|
255
|
+
*/
|
|
256
|
+
function hasExternalReference(declaration, sourceFile) {
|
|
257
|
+
const selfPath = sourceFile.getFilePath();
|
|
258
|
+
const languageService = sourceFile.getProject().getLanguageService();
|
|
259
|
+
try {
|
|
260
|
+
const refs = languageService.findReferencesAsNodes(declaration);
|
|
261
|
+
for (const ref of refs) {
|
|
262
|
+
if (ref.getSourceFile().getFilePath() !== selfPath) {
|
|
263
|
+
return true; // short-circuit
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
void err; // deliberate: fall through to the safe answer below
|
|
269
|
+
// Some nodes (e.g., shorthand property assignments in re-exports) may fail
|
|
270
|
+
// Treat as "referenced" to avoid false positives
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=reverse-dependency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reverse-dependency.js","sourceRoot":"","sources":["../../src/conditions/reverse-dependency.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAA;AACjC,OAAO,QAAQ,MAAM,WAAW,CAAA;AAChC,OAAO,EAAiC,IAAI,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQ1E,MAAM,UAAU,GAAG,IAAI,OAAO,EAA+B,CAAA;AAE7D;;;;;;;GAOG;AACH;;;;GAIG;AACH,SAAS,UAAU,CACjB,KAAyB,EACzB,UAAkB,EAClB,EAAc,EACd,WAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;IACtC,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACnB,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,QAAoB,EAAE,SAAiB;IACnE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IAEhD,MAAM,OAAO,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,CAAA;IACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAA;IAErC,iFAAiF;IACjF,MAAM,UAAU,GAAG;QACjB,SAAS;QACT,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;QACjC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC;QACnC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC;QACnC,SAAS,GAAG,KAAK;QACjB,SAAS,GAAG,MAAM;QAClB,SAAS,GAAG,WAAW;QACvB,SAAS,GAAG,YAAY;KACzB,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,YAAY,CAAC,CAAA;QACpD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;IAC/B,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,kFAAkF;AAClF,SAAS,kBAAkB,CAAC,KAAyB,EAAE,EAAc;IACnE,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,4BAA4B,EAAE,CAAA;QACpD,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACvB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;IACtD,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,cAAc,CAAC,KAAyB,EAAE,EAAc;IAC/D,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,qBAAqB,EAAE,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,4BAA4B,EAAE,CAAA;QACpD,IAAI,CAAC,QAAQ;YAAE,SAAQ;QACvB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;IACrD,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,mBAAmB,CAAC,KAAyB,EAAE,EAAc;IACpE,KAAK,MAAM,QAAQ,IAAI,EAAE,CAAC,oBAAoB,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAC1E,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,KAAK,UAAU,CAAC,aAAa;YAAE,SAAQ;QAC7E,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAA;QACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACnB,IAAI,CAAC,GAAG;YAAE,SAAQ;QAClB,IAAI,SAA6B,CAAA;QACjC,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,+BAA+B,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3E,SAAS,GAAG,GAAG,CAAC,eAAe,EAAE,CAAA;QACnC,CAAC;QAED,IAAI,SAAS,KAAK,SAAS;YAAE,SAAQ;QAErC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;QACrD,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,WAAyB;IACtD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,EAAE,CAAA;IAE9C,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;IAChC,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,GAAG,EAAE,CAAA;IAChC,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,EAAE,CAAA;IACtC,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACtC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IAEzB,MAAM,KAAK,GAAuB,IAAI,GAAG,EAAE,CAAA;IAE3C,KAAK,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;QAC7B,kBAAkB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAC7B,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QACzB,mBAAmB,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAChC,CAAC;IAED,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC9B,OAAO,KAAK,CAAA;AACd,CAAC;AAED,oEAAoE;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,KAAe;IAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzD,OAAO;QACL,WAAW,EAAE,wBAAwB,WAAW,EAAE;QAClD,QAAQ,CAAC,QAAsB,EAAE,OAAyB;YACxD,qEAAqE;YACrE,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACjE,MAAM,KAAK,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAA;YAC7C,MAAM,UAAU,GAAoB,EAAE,CAAA;YAEtC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;gBACnD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;oBACjC,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAA;oBAC3C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;wBAC3C,UAAU,CAAC,IAAI,CAAC;4BACd,IAAI,EAAE,OAAO,CAAC,IAAI;4BAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EAAE;4BACzB,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE;4BACtB,IAAI,EAAE,CAAC;4BACP,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,mBAAmB,QAAQ,CAAC,WAAW,EAAE,0BAA0B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;4BAClH,OAAO,EAAE,OAAO,CAAC,OAAO;yBACzB,CAAC,CAAA;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,UAAU,CAAA;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,WAAW,EAAE,0CAA0C;QACvD,QAAQ,CAAC,QAAsB,EAAE,OAAyB;YACxD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACjE,MAAM,KAAK,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAA;YAC7C,MAAM,UAAU,GAAoB,EAAE,CAAA;YAEtC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;gBACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC3B,UAAU,CAAC,IAAI,CAAC;wBACd,IAAI,EAAE,OAAO,CAAC,IAAI;wBAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EAAE;wBACzB,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE;wBACtB,IAAI,EAAE,CAAC;wBACP,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,sCAAsC;wBAClE,OAAO,EAAE,OAAO,CAAC,OAAO;qBACzB,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;YAED,OAAO,UAAU,CAAA;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH;;GAEG;AACH,SAAS,uBAAuB,CAAC,EAAc,EAAE,OAAyB;IACxE,MAAM,UAAU,GAAoB,EAAE,CAAA;IACtC,MAAM,SAAS,GAAG,EAAE,CAAC,uBAAuB,EAAE,CAAA;IAE9C,KAAK,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,SAAS,EAAE,CAAC;QAC7C,IAAI,IAAI,KAAK,SAAS;YAAE,SAAQ;QAEhC,MAAM,SAAS,GAAG,YAAY,CAAC,CAAC,CAAC,CAAA;QACjC,IAAI,SAAS,KAAK,SAAS;YAAE,SAAQ;QACrC,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YACxE,UAAU,CAAC,IAAI,CAAC;gBACd,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,EAAE,CAAC,WAAW,EAAE;gBACzB,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE;gBACtB,IAAI;gBACJ,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,aAAa,IAAI,6CAA6C;gBAC1F,OAAO,EAAE,OAAO,CAAC,OAAO;aACzB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,WAAW,EAAE,wBAAwB;QACrC,QAAQ,CAAC,QAAsB,EAAE,OAAyB;YACxD,MAAM,UAAU,GAAoB,EAAE,CAAA;YACtC,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,UAAU,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAA;YAC1D,CAAC;YACD,OAAO,UAAU,CAAA;QACnB,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,WAAiB,EAAE,UAAsB;IACrE,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,EAAE,CAAA;IACzC,MAAM,eAAe,GAAG,UAAU,CAAC,UAAU,EAAE,CAAC,kBAAkB,EAAE,CAAA;IAEpE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,eAAe,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAA;QAC/D,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACnD,OAAO,IAAI,CAAA,CAAC,gBAAgB;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,GAAG,CAAA,CAAC,oDAAoD;QAC7D,2EAA2E;QAC3E,iDAAiD;QACjD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Condition } from '@nielspeter/eess';
|
|
2
|
+
import type { Slice } from '../models/slice.js';
|
|
3
|
+
/**
|
|
4
|
+
* Assert that no circular dependencies exist between slices.
|
|
5
|
+
*
|
|
6
|
+
* Builds a directed dependency graph from import declarations,
|
|
7
|
+
* then runs Tarjan's SCC algorithm to detect cycles.
|
|
8
|
+
* Each cycle produces a violation listing the cycle path.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* slices(project)
|
|
12
|
+
* .matching('src/features/*\/')
|
|
13
|
+
* .should().beFreeOfCycles()
|
|
14
|
+
* .check()
|
|
15
|
+
*/
|
|
16
|
+
export declare function beFreeOfCycles(): Condition<Slice>;
|
|
17
|
+
/**
|
|
18
|
+
* Assert that slices respect a layered dependency order.
|
|
19
|
+
*
|
|
20
|
+
* Given layers ['presentation', 'application', 'persistence', 'domain'],
|
|
21
|
+
* layer N may depend on layers N+1, N+2, ... but NOT on layers N-1, N-2, ...
|
|
22
|
+
* That is, dependencies must flow downward (toward higher indices) only.
|
|
23
|
+
*
|
|
24
|
+
* A layer not present in the slice set is silently skipped.
|
|
25
|
+
*
|
|
26
|
+
* @param layers - Ordered layer names, from highest (e.g., UI) to lowest (e.g., domain)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* slices(project)
|
|
30
|
+
* .assignedFrom(layers)
|
|
31
|
+
* .should().respectLayerOrder('presentation', 'application', 'persistence', 'domain')
|
|
32
|
+
* .check()
|
|
33
|
+
*/
|
|
34
|
+
export declare function respectLayerOrder(...layers: string[]): Condition<Slice>;
|
|
35
|
+
/**
|
|
36
|
+
* Assert that no slice depends on any of the listed slices.
|
|
37
|
+
*
|
|
38
|
+
* Use for explicit isolation rules, e.g., "no slice may depend on legacy".
|
|
39
|
+
*
|
|
40
|
+
* @param forbiddenSlices - Names of slices that must not be depended upon
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* slices(project)
|
|
44
|
+
* .matching('src/features/*\/')
|
|
45
|
+
* .should().notDependOn('legacy', 'deprecated')
|
|
46
|
+
* .check()
|
|
47
|
+
*/
|
|
48
|
+
export declare function notDependOn(...forbiddenSlices: string[]): Condition<Slice>;
|
|
49
|
+
//# sourceMappingURL=slice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slice.d.ts","sourceRoot":"","sources":["../../src/conditions/slice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAA;AAEnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAQ/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,IAAI,SAAS,CAAC,KAAK,CAAC,CAmDjD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAsCvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,GAAG,eAAe,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CA6B1E"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { buildSliceDependencyGraph, buildFileToSliceMap, findSliceDependencyDetails, } from '../helpers/slice-graph.js';
|
|
2
|
+
import { tarjanSCC } from '../helpers/tarjan.js';
|
|
3
|
+
/**
|
|
4
|
+
* Assert that no circular dependencies exist between slices.
|
|
5
|
+
*
|
|
6
|
+
* Builds a directed dependency graph from import declarations,
|
|
7
|
+
* then runs Tarjan's SCC algorithm to detect cycles.
|
|
8
|
+
* Each cycle produces a violation listing the cycle path.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* slices(project)
|
|
12
|
+
* .matching('src/features/*\/')
|
|
13
|
+
* .should().beFreeOfCycles()
|
|
14
|
+
* .check()
|
|
15
|
+
*/
|
|
16
|
+
export function beFreeOfCycles() {
|
|
17
|
+
return {
|
|
18
|
+
description: 'be free of cycles',
|
|
19
|
+
evaluate(slices, context) {
|
|
20
|
+
const fileToSlice = buildFileToSliceMap(slices);
|
|
21
|
+
const edges = buildSliceDependencyGraph(slices, fileToSlice);
|
|
22
|
+
// Map slice names to indices for Tarjan's
|
|
23
|
+
const sliceNames = slices.map((s) => s.name);
|
|
24
|
+
const nameToIndex = new Map(sliceNames.map((name, i) => [name, i]));
|
|
25
|
+
const adjacency = new Map();
|
|
26
|
+
for (const edge of edges) {
|
|
27
|
+
const fromIdx = nameToIndex.get(edge.from);
|
|
28
|
+
const toIdx = nameToIndex.get(edge.to);
|
|
29
|
+
if (fromIdx === undefined || toIdx === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const existing = adjacency.get(fromIdx);
|
|
32
|
+
if (existing) {
|
|
33
|
+
existing.push(toIdx);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
adjacency.set(fromIdx, [toIdx]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const sccs = tarjanSCC(slices.length, adjacency);
|
|
40
|
+
const violations = [];
|
|
41
|
+
for (const scc of sccs) {
|
|
42
|
+
const cycleNames = scc.map((i) => sliceNames[i]);
|
|
43
|
+
const cyclePath = [...cycleNames, cycleNames[0]].join(' -> ');
|
|
44
|
+
// Find one concrete file causing the cycle for the violation location
|
|
45
|
+
const fromSlice = cycleNames[0] ?? '';
|
|
46
|
+
const toSlice = cycleNames[1] ?? fromSlice;
|
|
47
|
+
const details = findSliceDependencyDetails(slices, fromSlice, toSlice, fileToSlice);
|
|
48
|
+
const firstDetail = details[0];
|
|
49
|
+
violations.push({
|
|
50
|
+
rule: context.rule,
|
|
51
|
+
element: `[${cycleNames.join(', ')}]`,
|
|
52
|
+
file: firstDetail ? firstDetail.sourceFile.getFilePath() : 'unknown',
|
|
53
|
+
line: firstDetail ? firstDetail.importLine : 0,
|
|
54
|
+
message: `Cycle detected: ${cyclePath}`,
|
|
55
|
+
because: context.because,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return violations;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Assert that slices respect a layered dependency order.
|
|
64
|
+
*
|
|
65
|
+
* Given layers ['presentation', 'application', 'persistence', 'domain'],
|
|
66
|
+
* layer N may depend on layers N+1, N+2, ... but NOT on layers N-1, N-2, ...
|
|
67
|
+
* That is, dependencies must flow downward (toward higher indices) only.
|
|
68
|
+
*
|
|
69
|
+
* A layer not present in the slice set is silently skipped.
|
|
70
|
+
*
|
|
71
|
+
* @param layers - Ordered layer names, from highest (e.g., UI) to lowest (e.g., domain)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* slices(project)
|
|
75
|
+
* .assignedFrom(layers)
|
|
76
|
+
* .should().respectLayerOrder('presentation', 'application', 'persistence', 'domain')
|
|
77
|
+
* .check()
|
|
78
|
+
*/
|
|
79
|
+
export function respectLayerOrder(...layers) {
|
|
80
|
+
return {
|
|
81
|
+
description: `respect layer order [${layers.join(' -> ')}]`,
|
|
82
|
+
evaluate(slices, context) {
|
|
83
|
+
const fileToSlice = buildFileToSliceMap(slices);
|
|
84
|
+
const edges = buildSliceDependencyGraph(slices, fileToSlice);
|
|
85
|
+
// Map layer names to their position (lower index = higher layer)
|
|
86
|
+
const layerIndex = new Map(layers.map((name, i) => [name, i]));
|
|
87
|
+
const violations = [];
|
|
88
|
+
for (const edge of edges) {
|
|
89
|
+
const fromIdx = layerIndex.get(edge.from);
|
|
90
|
+
const toIdx = layerIndex.get(edge.to);
|
|
91
|
+
// Skip edges involving non-layer slices
|
|
92
|
+
if (fromIdx === undefined || toIdx === undefined)
|
|
93
|
+
continue;
|
|
94
|
+
// Violation: depending on a higher layer (lower index)
|
|
95
|
+
if (toIdx < fromIdx) {
|
|
96
|
+
const details = findSliceDependencyDetails(slices, edge.from, edge.to, fileToSlice);
|
|
97
|
+
for (const detail of details) {
|
|
98
|
+
violations.push({
|
|
99
|
+
rule: context.rule,
|
|
100
|
+
element: detail.sourceFile.getBaseName(),
|
|
101
|
+
file: detail.sourceFile.getFilePath(),
|
|
102
|
+
line: detail.importLine,
|
|
103
|
+
message: `Layer "${edge.from}" depends on higher layer "${edge.to}" (allowed: ${layers.slice(fromIdx + 1).join(', ') || 'none'})`,
|
|
104
|
+
because: context.because,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return violations;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Assert that no slice depends on any of the listed slices.
|
|
115
|
+
*
|
|
116
|
+
* Use for explicit isolation rules, e.g., "no slice may depend on legacy".
|
|
117
|
+
*
|
|
118
|
+
* @param forbiddenSlices - Names of slices that must not be depended upon
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* slices(project)
|
|
122
|
+
* .matching('src/features/*\/')
|
|
123
|
+
* .should().notDependOn('legacy', 'deprecated')
|
|
124
|
+
* .check()
|
|
125
|
+
*/
|
|
126
|
+
export function notDependOn(...forbiddenSlices) {
|
|
127
|
+
const forbiddenSet = new Set(forbiddenSlices);
|
|
128
|
+
return {
|
|
129
|
+
description: `not depend on [${forbiddenSlices.join(', ')}]`,
|
|
130
|
+
evaluate(slices, context) {
|
|
131
|
+
const fileToSlice = buildFileToSliceMap(slices);
|
|
132
|
+
const edges = buildSliceDependencyGraph(slices, fileToSlice);
|
|
133
|
+
const violations = [];
|
|
134
|
+
for (const edge of edges) {
|
|
135
|
+
if (forbiddenSet.has(edge.to)) {
|
|
136
|
+
const details = findSliceDependencyDetails(slices, edge.from, edge.to, fileToSlice);
|
|
137
|
+
for (const detail of details) {
|
|
138
|
+
violations.push({
|
|
139
|
+
rule: context.rule,
|
|
140
|
+
element: detail.sourceFile.getBaseName(),
|
|
141
|
+
file: detail.sourceFile.getFilePath(),
|
|
142
|
+
line: detail.importLine,
|
|
143
|
+
message: `Slice "${edge.from}" depends on forbidden slice "${edge.to}"`,
|
|
144
|
+
because: context.because,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return violations;
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=slice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slice.js","sourceRoot":"","sources":["../../src/conditions/slice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
2
|
+
import type { Condition } from '@nielspeter/eess';
|
|
3
|
+
/**
|
|
4
|
+
* Elements must reside in a file matching the glob pattern.
|
|
5
|
+
*
|
|
6
|
+
* Uses picomatch for glob matching against the absolute file path.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Assert all matched elements are in repository files
|
|
10
|
+
* .should(resideInFile('** /repositories/*.ts'))
|
|
11
|
+
*/
|
|
12
|
+
export declare function resideInFile<T extends Node>(glob: string): Condition<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Elements must reside in a folder matching the glob pattern.
|
|
15
|
+
*
|
|
16
|
+
* Matches against the directory portion of the file path (everything
|
|
17
|
+
* before the last path separator).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Assert all matched elements are in the services folder
|
|
21
|
+
* .should(resideInFolder('** /services'))
|
|
22
|
+
*/
|
|
23
|
+
export declare function resideInFolder<T extends Node>(glob: string): Condition<T>;
|
|
24
|
+
/**
|
|
25
|
+
* Elements must have a name matching the regex pattern.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Assert all matched elements follow the naming convention
|
|
29
|
+
* .should(haveNameMatching(/Service$/))
|
|
30
|
+
*/
|
|
31
|
+
export declare function haveNameMatching<T extends Node>(regex: RegExp): Condition<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Elements must be exported from their module.
|
|
34
|
+
*
|
|
35
|
+
* Checks for the `export` keyword on the node. For variable declarations,
|
|
36
|
+
* checks the parent variable statement.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Assert all matched services are exported
|
|
40
|
+
* .should(beExported())
|
|
41
|
+
*/
|
|
42
|
+
export declare function beExported<T extends Node>(): Condition<T>;
|
|
43
|
+
/**
|
|
44
|
+
* The predicate set must be empty — no elements should match.
|
|
45
|
+
*
|
|
46
|
+
* This is a set-level condition, not an element-level condition.
|
|
47
|
+
* If ANY elements exist after predicate filtering, each one becomes
|
|
48
|
+
* a violation with the message "X should not exist".
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Assert no parse*Order functions exist
|
|
52
|
+
* functions(project)
|
|
53
|
+
* .that(haveNameMatching(/^parse\w+Order$/))
|
|
54
|
+
* .should(notExist())
|
|
55
|
+
* .because('use shared parseOrder() utility instead')
|
|
56
|
+
*/
|
|
57
|
+
export declare function notExist<T extends Node>(): Condition<T>;
|
|
58
|
+
//# sourceMappingURL=structural.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structural.d.ts","sourceRoot":"","sources":["../../src/conditions/structural.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAE/B,OAAO,KAAK,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAA;AAKnE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAQvE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAezE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAM5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAyBzD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CASvD"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
2
|
+
import picomatch from 'picomatch';
|
|
3
|
+
import { createViolation, getElementFile, getElementName } from '../core/violation.js';
|
|
4
|
+
import { elementCondition } from './helpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Elements must reside in a file matching the glob pattern.
|
|
7
|
+
*
|
|
8
|
+
* Uses picomatch for glob matching against the absolute file path.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* // Assert all matched elements are in repository files
|
|
12
|
+
* .should(resideInFile('** /repositories/*.ts'))
|
|
13
|
+
*/
|
|
14
|
+
export function resideInFile(glob) {
|
|
15
|
+
const isMatch = picomatch(glob);
|
|
16
|
+
return elementCondition(`reside in file matching '${glob}'`, (element) => isMatch(getElementFile(element)), (element) => `${getElementName(element)} resides in '${getElementFile(element)}' which does not match '${glob}'`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Elements must reside in a folder matching the glob pattern.
|
|
20
|
+
*
|
|
21
|
+
* Matches against the directory portion of the file path (everything
|
|
22
|
+
* before the last path separator).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Assert all matched elements are in the services folder
|
|
26
|
+
* .should(resideInFolder('** /services'))
|
|
27
|
+
*/
|
|
28
|
+
export function resideInFolder(glob) {
|
|
29
|
+
const isMatch = picomatch(glob);
|
|
30
|
+
return elementCondition(`reside in folder matching '${glob}'`, (element) => {
|
|
31
|
+
const filePath = getElementFile(element);
|
|
32
|
+
const folder = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
33
|
+
return isMatch(folder);
|
|
34
|
+
}, (element) => {
|
|
35
|
+
const filePath = getElementFile(element);
|
|
36
|
+
const folder = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
37
|
+
return `${getElementName(element)} resides in folder '${folder}' which does not match '${glob}'`;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Elements must have a name matching the regex pattern.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Assert all matched elements follow the naming convention
|
|
45
|
+
* .should(haveNameMatching(/Service$/))
|
|
46
|
+
*/
|
|
47
|
+
export function haveNameMatching(regex) {
|
|
48
|
+
return elementCondition(`have name matching ${String(regex)}`, (element) => regex.test(getElementName(element)), (element) => `${getElementName(element)} does not have a name matching ${String(regex)}`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Elements must be exported from their module.
|
|
52
|
+
*
|
|
53
|
+
* Checks for the `export` keyword on the node. For variable declarations,
|
|
54
|
+
* checks the parent variable statement.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Assert all matched services are exported
|
|
58
|
+
* .should(beExported())
|
|
59
|
+
*/
|
|
60
|
+
export function beExported() {
|
|
61
|
+
return elementCondition('be exported', (element) => {
|
|
62
|
+
// ts-morph type guards for nodes with isExported()
|
|
63
|
+
if (Node.isClassDeclaration(element) ||
|
|
64
|
+
Node.isFunctionDeclaration(element) ||
|
|
65
|
+
Node.isInterfaceDeclaration(element) ||
|
|
66
|
+
Node.isTypeAliasDeclaration(element) ||
|
|
67
|
+
Node.isEnumDeclaration(element)) {
|
|
68
|
+
return element.isExported();
|
|
69
|
+
}
|
|
70
|
+
// VariableDeclaration — check parent VariableStatement
|
|
71
|
+
if (Node.isVariableDeclaration(element)) {
|
|
72
|
+
const varStatement = element.getVariableStatement();
|
|
73
|
+
if (varStatement) {
|
|
74
|
+
return varStatement.isExported();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}, (element) => `${getElementName(element)} is not exported`);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The predicate set must be empty — no elements should match.
|
|
82
|
+
*
|
|
83
|
+
* This is a set-level condition, not an element-level condition.
|
|
84
|
+
* If ANY elements exist after predicate filtering, each one becomes
|
|
85
|
+
* a violation with the message "X should not exist".
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // Assert no parse*Order functions exist
|
|
89
|
+
* functions(project)
|
|
90
|
+
* .that(haveNameMatching(/^parse\w+Order$/))
|
|
91
|
+
* .should(notExist())
|
|
92
|
+
* .because('use shared parseOrder() utility instead')
|
|
93
|
+
*/
|
|
94
|
+
export function notExist() {
|
|
95
|
+
return {
|
|
96
|
+
description: 'not exist',
|
|
97
|
+
evaluate(elements, context) {
|
|
98
|
+
return elements.map((element) => createViolation(element, `${getElementName(element)} should not exist`, context));
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=structural.js.map
|