@yasainet/eslint 0.0.72 → 0.0.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +17 -60
  2. package/package.json +1 -1
  3. package/src/common/{constants.mjs → _internal/constants.mjs} +0 -18
  4. package/src/common/_internal/import-patterns.mjs +16 -0
  5. package/src/common/{plugins.mjs → _internal/plugins.mjs} +0 -1
  6. package/src/common/_internal/selectors.mjs +10 -0
  7. package/src/common/{rules.mjs → base/typescript.mjs} +2 -29
  8. package/src/common/{entry-points.mjs → boundaries/entry-point.mjs} +0 -1
  9. package/src/common/cross-cutting/ban-alias.mjs +22 -0
  10. package/src/common/cross-cutting/feature-default-imports.mjs +26 -0
  11. package/src/common/cross-cutting/feature-name.mjs +15 -0
  12. package/src/common/cross-cutting/features-ts-only.mjs +20 -0
  13. package/src/common/cross-cutting/form-state.mjs +16 -0
  14. package/src/common/{jsdoc.mjs → cross-cutting/jsdoc.mjs} +2 -3
  15. package/src/common/cross-cutting/logger.mjs +21 -0
  16. package/src/common/cross-cutting/namespace-import.mjs +23 -0
  17. package/src/common/cross-cutting/no-any-return.mjs +18 -0
  18. package/src/common/cross-cutting/supabase-columns-satisfies.mjs +18 -0
  19. package/src/common/index.mjs +42 -24
  20. package/src/common/layers/constants.mjs +36 -0
  21. package/src/common/layers/entries.mjs +168 -0
  22. package/src/common/layers/lib.mjs +18 -0
  23. package/src/common/layers/queries.mjs +183 -0
  24. package/src/common/layers/schemas.mjs +45 -0
  25. package/src/common/layers/services.mjs +115 -0
  26. package/src/common/layers/top-level-utils.mjs +18 -0
  27. package/src/common/layers/types.mjs +44 -0
  28. package/src/common/layers/utils.mjs +50 -0
  29. package/src/common/local-plugins/entry-single-service-call.mjs +0 -30
  30. package/src/common/local-plugins/entry-template.mjs +33 -72
  31. package/src/common/local-plugins/feature-name.mjs +8 -22
  32. package/src/common/local-plugins/form-state-naming.mjs +0 -10
  33. package/src/common/local-plugins/form-state-shape.mjs +0 -34
  34. package/src/common/local-plugins/import-path-style.mjs +0 -7
  35. package/src/common/local-plugins/index.mjs +0 -1
  36. package/src/common/local-plugins/layout-main-structural-only.mjs +0 -21
  37. package/src/common/local-plugins/namespace-import-name.mjs +0 -26
  38. package/src/common/local-plugins/no-any-return.mjs +0 -8
  39. package/src/common/local-plugins/queries-export.mjs +0 -8
  40. package/src/common/local-plugins/queries-namespace-import.mjs +0 -10
  41. package/src/common/local-plugins/schema-naming.mjs +0 -6
  42. package/src/common/local-plugins/supabase-columns-satisfies.mjs +0 -24
  43. package/src/common/local-plugins/supabase-select-typed-columns.mjs +0 -32
  44. package/src/deno/boundaries/entry-point.mjs +44 -0
  45. package/src/deno/boundaries/lib.mjs +28 -0
  46. package/src/deno/boundaries/utils.mjs +25 -0
  47. package/src/deno/index.mjs +9 -13
  48. package/src/deno/local-plugins/flat-entry-point.mjs +0 -5
  49. package/src/deno/local-plugins/index.mjs +0 -1
  50. package/src/next/boundaries/components.mjs +36 -0
  51. package/src/next/boundaries/hooks.mjs +36 -0
  52. package/src/next/boundaries/lib.mjs +23 -0
  53. package/src/next/boundaries/page.mjs +36 -0
  54. package/src/next/boundaries/route.mjs +36 -0
  55. package/src/next/boundaries/sitemap.mjs +36 -0
  56. package/src/next/directives.mjs +0 -1
  57. package/src/next/imports.mjs +0 -1
  58. package/src/next/index.mjs +12 -15
  59. package/src/next/layers/components.mjs +30 -0
  60. package/src/next/layers/hooks.mjs +31 -0
  61. package/src/next/layers/layouts.mjs +12 -0
  62. package/src/next/tailwindcss.mjs +0 -21
  63. package/src/node/index.mjs +1 -2
  64. package/src/common/imports.mjs +0 -457
  65. package/src/common/layers.mjs +0 -158
  66. package/src/common/naming.mjs +0 -347
  67. package/src/deno/imports.mjs +0 -90
  68. package/src/next/layouts.mjs +0 -18
  69. package/src/next/naming.mjs +0 -58
@@ -0,0 +1,168 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import {
3
+ LIB_BOUNDARY_PATTERNS,
4
+ MAPPING_PATTERNS,
5
+ } from "../_internal/import-patterns.mjs";
6
+ import { checkFile } from "../_internal/plugins.mjs";
7
+ import {
8
+ aliasDynamicImportMessage,
9
+ aliasDynamicImportSelector,
10
+ } from "../_internal/selectors.mjs";
11
+ import { localPlugin } from "../local-plugins/index.mjs";
12
+
13
+ const LAYER_PATTERNS = [
14
+ {
15
+ group: ["**/queries/*", "**/queries"],
16
+ message: "entries cannot import queries (layer violation)",
17
+ },
18
+ {
19
+ group: ["**/hooks/*", "**/hooks"],
20
+ message: "entries cannot import hooks (layer violation)",
21
+ },
22
+ ];
23
+
24
+ const LATERAL_PATTERNS = [
25
+ {
26
+ group: ["@/features/*/entries/*", "@/features/*/entries"],
27
+ message:
28
+ "entries cannot import other feature's entries (lateral violation)",
29
+ },
30
+ {
31
+ group: [
32
+ "@/features/*/services/*",
33
+ "@/features/*/services",
34
+ "!@/features/shared/services/*",
35
+ "!@/features/shared/services",
36
+ ],
37
+ message:
38
+ "entries cannot import other feature's services. Use the same feature's service (1:1) or move orchestration into the service layer. `shared/services/*` is exempt for cross-cutting side effects (notifications etc.).",
39
+ },
40
+ ];
41
+
42
+ const CARDINALITY_PATTERNS = {
43
+ server: [
44
+ {
45
+ group: ["**/services/client", "**/services/admin"],
46
+ message:
47
+ "server entry can only import server service (cardinality violation)",
48
+ },
49
+ ],
50
+ client: [
51
+ {
52
+ group: ["**/services/server", "**/services/admin"],
53
+ message:
54
+ "client entry can only import client service (cardinality violation)",
55
+ },
56
+ ],
57
+ admin: [
58
+ {
59
+ group: ["**/services/server", "**/services/client"],
60
+ message:
61
+ "admin entry can only import admin service (cardinality violation)",
62
+ },
63
+ ],
64
+ };
65
+
66
+ export function createEntriesConfigs({ featureRoot, prefixLibMapping }) {
67
+ const prefixes = Object.keys(prefixLibMapping);
68
+ const hasPrefixes = prefixes.length > 0;
69
+ const prefixPattern = hasPrefixes ? `@(${prefixes.join("|")})` : "*";
70
+ const sharedPrefixPattern = hasPrefixes
71
+ ? `@(shared|${prefixes.join("|")})`
72
+ : "shared";
73
+
74
+ const configs = [
75
+ {
76
+ name: "naming/entries",
77
+ files: featuresGlob(featureRoot, "**/entries/*.ts"),
78
+ ignores: featuresGlob(featureRoot, "shared/entries/*.ts"),
79
+ plugins: { "check-file": checkFile },
80
+ rules: {
81
+ "check-file/filename-naming-convention": [
82
+ "error",
83
+ { "**/*.ts": prefixPattern },
84
+ ],
85
+ },
86
+ },
87
+ {
88
+ name: "naming/entries-shared",
89
+ files: featuresGlob(featureRoot, "shared/entries/*.ts"),
90
+ plugins: { "check-file": checkFile },
91
+ rules: {
92
+ "check-file/filename-naming-convention": [
93
+ "error",
94
+ { "**/*.ts": sharedPrefixPattern },
95
+ ],
96
+ },
97
+ },
98
+ {
99
+ name: "naming/entry-template",
100
+ files: featuresGlob(featureRoot, "**/entries/*.ts"),
101
+ plugins: { local: localPlugin },
102
+ rules: {
103
+ "local/entry-template": "error",
104
+ },
105
+ },
106
+ {
107
+ name: "naming/entry-single-service-call",
108
+ files: featuresGlob(featureRoot, "**/entries/*.ts"),
109
+ plugins: { local: localPlugin },
110
+ rules: {
111
+ "local/entry-single-service-call": "error",
112
+ },
113
+ },
114
+ {
115
+ name: "layers/entries",
116
+ files: [`${featureRoot}/**/entries/*.ts`],
117
+ rules: {
118
+ "no-restricted-syntax": [
119
+ "error",
120
+ {
121
+ selector: aliasDynamicImportSelector,
122
+ message: aliasDynamicImportMessage,
123
+ },
124
+ ],
125
+ },
126
+ },
127
+ {
128
+ name: "imports/entries",
129
+ files: [`${featureRoot}/**/entries/*.ts`],
130
+ rules: {
131
+ "no-restricted-imports": [
132
+ "error",
133
+ {
134
+ patterns: [
135
+ ...LAYER_PATTERNS,
136
+ ...LATERAL_PATTERNS,
137
+ ...LIB_BOUNDARY_PATTERNS,
138
+ ...MAPPING_PATTERNS,
139
+ ],
140
+ },
141
+ ],
142
+ },
143
+ },
144
+ ];
145
+
146
+ for (const prefix of ["server", "client", "admin"]) {
147
+ configs.push({
148
+ name: `imports/entries/${prefix}`,
149
+ files: [`${featureRoot}/**/entries/${prefix}.ts`],
150
+ rules: {
151
+ "no-restricted-imports": [
152
+ "error",
153
+ {
154
+ patterns: [
155
+ ...LAYER_PATTERNS,
156
+ ...LATERAL_PATTERNS,
157
+ ...CARDINALITY_PATTERNS[prefix],
158
+ ...LIB_BOUNDARY_PATTERNS,
159
+ ...MAPPING_PATTERNS,
160
+ ],
161
+ },
162
+ ],
163
+ },
164
+ });
165
+ }
166
+
167
+ return configs;
168
+ }
@@ -0,0 +1,18 @@
1
+ import { checkFile } from "../_internal/plugins.mjs";
2
+
3
+ export function createLibLayerConfigs({ featureRoot }) {
4
+ const libRoot = featureRoot.replace(/features$/, "lib");
5
+ return [
6
+ {
7
+ name: "naming/lib",
8
+ files: [`${libRoot}/**/*.ts`],
9
+ plugins: { "check-file": checkFile },
10
+ rules: {
11
+ "check-file/filename-naming-convention": [
12
+ "error",
13
+ { "**/*.ts": "*" },
14
+ ],
15
+ },
16
+ },
17
+ ];
18
+ }
@@ -0,0 +1,183 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import { MAPPING_PATTERNS } from "../_internal/import-patterns.mjs";
3
+ import { checkFile } from "../_internal/plugins.mjs";
4
+ import {
5
+ aliasDynamicImportMessage,
6
+ aliasDynamicImportSelector,
7
+ loggerMessage,
8
+ loggerSelector,
9
+ } from "../_internal/selectors.mjs";
10
+ import { localPlugin } from "../local-plugins/index.mjs";
11
+
12
+ const LAYER_PATTERNS = [
13
+ {
14
+ group: ["**/services/*", "**/services"],
15
+ message: "queries cannot import services (layer violation)",
16
+ },
17
+ {
18
+ group: ["**/entries/*", "**/entries"],
19
+ message: "queries cannot import entries (layer violation)",
20
+ },
21
+ {
22
+ group: ["**/hooks/*", "**/hooks"],
23
+ message: "queries cannot import hooks (layer violation)",
24
+ },
25
+ ];
26
+
27
+ const LATERAL_PATTERNS = [
28
+ {
29
+ group: ["@/features/*/queries/*", "@/features/*/queries"],
30
+ message:
31
+ "queries cannot import other feature's queries (lateral violation)",
32
+ },
33
+ ];
34
+
35
+ function prefixLibPatterns(prefix, mapping) {
36
+ const prefixes = Object.keys(mapping);
37
+ const allowedLib = mapping[prefix];
38
+ return prefixes
39
+ .filter((p) => p !== prefix)
40
+ .map((p) => ({
41
+ group: [`**/lib/${mapping[p]}`, `**/lib/${mapping[p]}/*`],
42
+ message: `queries/${prefix}.ts can only import from lib/${allowedLib}. Use the correct query file for this lib.`,
43
+ }));
44
+ }
45
+
46
+ export function createQueriesConfigs({ featureRoot, prefixLibMapping }) {
47
+ const prefixes = Object.keys(prefixLibMapping);
48
+ const hasPrefixes = prefixes.length > 0;
49
+ const prefixPattern = hasPrefixes ? `@(${prefixes.join("|")})` : "*";
50
+ const sharedPrefixPattern = hasPrefixes
51
+ ? `@(shared|${prefixes.join("|")})`
52
+ : "shared";
53
+
54
+ const configs = [
55
+ {
56
+ name: "naming/queries",
57
+ files: featuresGlob(featureRoot, "**/queries/*.ts"),
58
+ ignores: featuresGlob(featureRoot, "shared/queries/*.ts"),
59
+ plugins: { "check-file": checkFile },
60
+ rules: {
61
+ "check-file/filename-naming-convention": [
62
+ "error",
63
+ { "**/*.ts": prefixPattern },
64
+ ],
65
+ },
66
+ },
67
+ {
68
+ name: "naming/queries-shared",
69
+ files: featuresGlob(featureRoot, "shared/queries/*.ts"),
70
+ plugins: { "check-file": checkFile },
71
+ rules: {
72
+ "check-file/filename-naming-convention": [
73
+ "error",
74
+ { "**/*.ts": sharedPrefixPattern },
75
+ ],
76
+ },
77
+ },
78
+ {
79
+ name: "naming/queries-export",
80
+ files: featuresGlob(featureRoot, "**/queries/*.ts"),
81
+ plugins: { local: localPlugin },
82
+ rules: {
83
+ "local/queries-export": "error",
84
+ },
85
+ },
86
+ {
87
+ name: "naming/supabase-select",
88
+ files: featuresGlob(featureRoot, "**/queries/*.ts"),
89
+ plugins: { local: localPlugin },
90
+ rules: {
91
+ "local/supabase-select-typed-columns": "error",
92
+ },
93
+ },
94
+ {
95
+ name: "layers/queries",
96
+ files: [`${featureRoot}/**/queries/*.ts`],
97
+ rules: {
98
+ "no-restricted-syntax": [
99
+ "error",
100
+ {
101
+ selector: "TryStatement",
102
+ message:
103
+ "try-catch is not allowed in queries. Error handling belongs in entries.",
104
+ },
105
+ {
106
+ selector: "IfStatement",
107
+ message:
108
+ "if statements are not allowed in queries. Conditional logic belongs in services.",
109
+ },
110
+ {
111
+ selector: "ForStatement",
112
+ message:
113
+ "Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
114
+ },
115
+ {
116
+ selector: "ForOfStatement",
117
+ message:
118
+ "Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
119
+ },
120
+ {
121
+ selector: "ForInStatement",
122
+ message:
123
+ "Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
124
+ },
125
+ {
126
+ selector: "WhileStatement",
127
+ message:
128
+ "Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
129
+ },
130
+ {
131
+ selector: "DoWhileStatement",
132
+ message:
133
+ "Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
134
+ },
135
+ {
136
+ selector: "ThrowStatement",
137
+ message:
138
+ "throw is not allowed in queries. Queries must return Supabase's { data, error } shape as-is. Error handling belongs in entries.",
139
+ },
140
+ { selector: loggerSelector, message: loggerMessage },
141
+ {
142
+ selector: aliasDynamicImportSelector,
143
+ message: aliasDynamicImportMessage,
144
+ },
145
+ ],
146
+ },
147
+ },
148
+ {
149
+ name: "imports/queries",
150
+ files: [`${featureRoot}/**/queries/*.ts`],
151
+ rules: {
152
+ "no-restricted-imports": [
153
+ "error",
154
+ { patterns: [...LAYER_PATTERNS, ...LATERAL_PATTERNS, ...MAPPING_PATTERNS] },
155
+ ],
156
+ },
157
+ },
158
+ ];
159
+
160
+ for (const prefix of prefixes) {
161
+ const patterns = prefixLibPatterns(prefix, prefixLibMapping);
162
+ if (patterns.length === 0) continue;
163
+ configs.push({
164
+ name: `imports/queries/${prefix}`,
165
+ files: [`${featureRoot}/**/queries/${prefix}.ts`],
166
+ rules: {
167
+ "no-restricted-imports": [
168
+ "error",
169
+ {
170
+ patterns: [
171
+ ...LAYER_PATTERNS,
172
+ ...LATERAL_PATTERNS,
173
+ ...patterns,
174
+ ...MAPPING_PATTERNS,
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ });
180
+ }
181
+
182
+ return configs;
183
+ }
@@ -0,0 +1,45 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import { checkFile } from "../_internal/plugins.mjs";
3
+ import { localPlugin } from "../local-plugins/index.mjs";
4
+
5
+ export function createSchemasConfigs({ featureRoot, prefixLibMapping }) {
6
+ const prefixes = Object.keys(prefixLibMapping);
7
+ const hasPrefixes = prefixes.length > 0;
8
+ const sharedPrefixPattern = hasPrefixes
9
+ ? `@(shared|${prefixes.join("|")})`
10
+ : "shared";
11
+
12
+ return [
13
+ {
14
+ name: "naming/schemas",
15
+ files: featuresGlob(featureRoot, "*/schemas/*.ts"),
16
+ ignores: featuresGlob(featureRoot, "shared/schemas/*.ts"),
17
+ plugins: { "check-file": checkFile },
18
+ rules: {
19
+ "check-file/filename-naming-convention": [
20
+ "error",
21
+ { "**/*/schemas/*.ts": "<1>" },
22
+ ],
23
+ },
24
+ },
25
+ {
26
+ name: "naming/schemas-shared",
27
+ files: featuresGlob(featureRoot, "shared/schemas/*.ts"),
28
+ plugins: { "check-file": checkFile },
29
+ rules: {
30
+ "check-file/filename-naming-convention": [
31
+ "error",
32
+ { "**/*.ts": sharedPrefixPattern },
33
+ ],
34
+ },
35
+ },
36
+ {
37
+ name: "naming/schema-naming",
38
+ files: featuresGlob(featureRoot, "**/schemas/*.ts"),
39
+ plugins: { local: localPlugin },
40
+ rules: {
41
+ "local/schema-naming": "error",
42
+ },
43
+ },
44
+ ];
45
+ }
@@ -0,0 +1,115 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import { LIB_BOUNDARY_PATTERNS } from "../_internal/import-patterns.mjs";
3
+ import { checkFile } from "../_internal/plugins.mjs";
4
+ import {
5
+ aliasDynamicImportMessage,
6
+ aliasDynamicImportSelector,
7
+ loggerMessage,
8
+ loggerSelector,
9
+ } from "../_internal/selectors.mjs";
10
+
11
+ const LAYER_PATTERNS = [
12
+ {
13
+ group: ["**/entries/*", "**/entries"],
14
+ message: "services cannot import entries (layer violation)",
15
+ },
16
+ {
17
+ group: ["**/hooks/*", "**/hooks"],
18
+ message: "services cannot import hooks (layer violation)",
19
+ },
20
+ ];
21
+
22
+ const LATERAL_PATTERNS = [
23
+ {
24
+ group: ["@/features/*/services/*", "@/features/*/services"],
25
+ message:
26
+ "services cannot import other feature's services (lateral violation)",
27
+ },
28
+ ];
29
+
30
+ export function createServicesConfigs({ featureRoot, prefixLibMapping }) {
31
+ const prefixes = Object.keys(prefixLibMapping);
32
+ const hasPrefixes = prefixes.length > 0;
33
+ const prefixPattern = hasPrefixes ? `@(${prefixes.join("|")})` : "*";
34
+ const sharedPrefixPattern = hasPrefixes
35
+ ? `@(shared|${prefixes.join("|")})`
36
+ : "shared";
37
+
38
+ return [
39
+ {
40
+ name: "naming/services",
41
+ files: featuresGlob(featureRoot, "**/services/*.ts"),
42
+ ignores: featuresGlob(featureRoot, "shared/services/*.ts"),
43
+ plugins: { "check-file": checkFile },
44
+ rules: {
45
+ "check-file/filename-naming-convention": [
46
+ "error",
47
+ { "**/*.ts": prefixPattern },
48
+ ],
49
+ },
50
+ },
51
+ {
52
+ name: "naming/services-shared",
53
+ files: featuresGlob(featureRoot, "shared/services/*.ts"),
54
+ plugins: { "check-file": checkFile },
55
+ rules: {
56
+ "check-file/filename-naming-convention": [
57
+ "error",
58
+ { "**/*.ts": sharedPrefixPattern },
59
+ ],
60
+ },
61
+ },
62
+ {
63
+ name: "layers/services",
64
+ files: [`${featureRoot}/**/services/*.ts`],
65
+ rules: {
66
+ "no-restricted-syntax": [
67
+ "error",
68
+ {
69
+ selector: "TryStatement",
70
+ message:
71
+ "try-catch is not allowed in services. Error handling belongs in entries.",
72
+ },
73
+ {
74
+ selector: "ThrowStatement",
75
+ message:
76
+ "throw is not allowed in services. Communicate failures via T | null / { data, error } / empty default. Native exceptions from libs auto-propagate to entry's catch.",
77
+ },
78
+ { selector: loggerSelector, message: loggerMessage },
79
+ {
80
+ selector:
81
+ "LogicalExpression[operator='??'][left.type='ChainExpression'][left.expression.property.name='message'][right.type='Literal']",
82
+ message:
83
+ "Dead fallback for error message. If you reached this branch the error is known — return the error directly. Unhandled exceptions belong in entries.",
84
+ },
85
+ {
86
+ selector:
87
+ "LogicalExpression[operator='??'][left.type='MemberExpression'][left.property.name='error'][right.type='ObjectExpression']",
88
+ message:
89
+ "Dead fallback for nullable error. Check `if (error)` and return the error directly. Unhandled exceptions belong in entries.",
90
+ },
91
+ {
92
+ selector: aliasDynamicImportSelector,
93
+ message: aliasDynamicImportMessage,
94
+ },
95
+ ],
96
+ },
97
+ },
98
+ {
99
+ name: "imports/services",
100
+ files: [`${featureRoot}/**/services/*.ts`],
101
+ rules: {
102
+ "no-restricted-imports": [
103
+ "error",
104
+ {
105
+ patterns: [
106
+ ...LAYER_PATTERNS,
107
+ ...LATERAL_PATTERNS,
108
+ ...LIB_BOUNDARY_PATTERNS,
109
+ ],
110
+ },
111
+ ],
112
+ },
113
+ },
114
+ ];
115
+ }
@@ -0,0 +1,18 @@
1
+ import { checkFile } from "../_internal/plugins.mjs";
2
+
3
+ export function createTopLevelUtilsConfigs({ featureRoot }) {
4
+ const utilsRoot = featureRoot.replace(/features$/, "utils");
5
+ return [
6
+ {
7
+ name: "naming/top-level-utils",
8
+ files: [`${utilsRoot}/**/*.ts`],
9
+ plugins: { "check-file": checkFile },
10
+ rules: {
11
+ "check-file/filename-naming-convention": [
12
+ "error",
13
+ { "**/*.ts": "*" },
14
+ ],
15
+ },
16
+ },
17
+ ];
18
+ }
@@ -0,0 +1,44 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import { MAPPING_PATTERNS } from "../_internal/import-patterns.mjs";
3
+ import { checkFile } from "../_internal/plugins.mjs";
4
+
5
+ export function createTypesConfigs({ featureRoot, prefixLibMapping }) {
6
+ const prefixes = Object.keys(prefixLibMapping);
7
+ const hasPrefixes = prefixes.length > 0;
8
+ const sharedPrefixPattern = hasPrefixes
9
+ ? `@(shared|${prefixes.join("|")})`
10
+ : "shared";
11
+
12
+ return [
13
+ {
14
+ name: "naming/types",
15
+ files: featuresGlob(featureRoot, "*/types/*.ts"),
16
+ ignores: featuresGlob(featureRoot, "shared/types/*.ts"),
17
+ plugins: { "check-file": checkFile },
18
+ rules: {
19
+ "check-file/filename-naming-convention": [
20
+ "error",
21
+ { "**/*/types/*.ts": "<1>" },
22
+ ],
23
+ },
24
+ },
25
+ {
26
+ name: "naming/types-shared",
27
+ files: featuresGlob(featureRoot, "shared/types/*.ts"),
28
+ plugins: { "check-file": checkFile },
29
+ rules: {
30
+ "check-file/filename-naming-convention": [
31
+ "error",
32
+ { "**/*.ts": sharedPrefixPattern },
33
+ ],
34
+ },
35
+ },
36
+ {
37
+ name: "imports/feature-types",
38
+ files: [`${featureRoot}/**/types/*.ts`],
39
+ rules: {
40
+ "no-restricted-imports": ["error", { patterns: MAPPING_PATTERNS }],
41
+ },
42
+ },
43
+ ];
44
+ }
@@ -0,0 +1,50 @@
1
+ import { featuresGlob } from "../_internal/constants.mjs";
2
+ import {
3
+ LIB_BOUNDARY_PATTERNS,
4
+ MAPPING_PATTERNS,
5
+ } from "../_internal/import-patterns.mjs";
6
+ import { checkFile } from "../_internal/plugins.mjs";
7
+
8
+ export function createUtilsConfigs({ featureRoot, prefixLibMapping }) {
9
+ const prefixes = Object.keys(prefixLibMapping);
10
+ const hasPrefixes = prefixes.length > 0;
11
+ const sharedPrefixPattern = hasPrefixes
12
+ ? `@(shared|${prefixes.join("|")})`
13
+ : "shared";
14
+
15
+ return [
16
+ {
17
+ name: "naming/utils",
18
+ files: featuresGlob(featureRoot, "*/utils/*.ts"),
19
+ ignores: featuresGlob(featureRoot, "shared/utils/*.ts"),
20
+ plugins: { "check-file": checkFile },
21
+ rules: {
22
+ "check-file/filename-naming-convention": [
23
+ "error",
24
+ { "**/*/utils/*.ts": "<1>" },
25
+ ],
26
+ },
27
+ },
28
+ {
29
+ name: "naming/utils-shared",
30
+ files: featuresGlob(featureRoot, "shared/utils/*.ts"),
31
+ plugins: { "check-file": checkFile },
32
+ rules: {
33
+ "check-file/filename-naming-convention": [
34
+ "error",
35
+ { "**/*.ts": sharedPrefixPattern },
36
+ ],
37
+ },
38
+ },
39
+ {
40
+ name: "imports/utils",
41
+ files: [`${featureRoot}/**/utils/*.ts`],
42
+ rules: {
43
+ "no-restricted-imports": [
44
+ "error",
45
+ { patterns: [...LIB_BOUNDARY_PATTERNS, ...MAPPING_PATTERNS] },
46
+ ],
47
+ },
48
+ },
49
+ ];
50
+ }
@@ -1,33 +1,3 @@
1
- /**
2
- * Enforce 1:1 entry-to-service mapping for `**\/entries/*.ts` exports.
3
- *
4
- * Why: services are the orchestration layer (they may combine multiple queries
5
- * and other features' queries). entries should be a thin wrapper that calls a
6
- * single service function and normalizes the return shape into
7
- * `{ data, error }`. If an entry calls more than one service, orchestration is
8
- * leaking up into the entry layer.
9
- *
10
- * Detection rule:
11
- *
12
- * - For every exported async `FunctionDeclaration` in an entries file, count
13
- * `CallExpression`s whose callee is a `MemberExpression` of the form
14
- * `<binding>.<method>(...)` where `<binding>` matches the namespace import
15
- * naming convention `*Service` (e.g. `articlesServerService`,
16
- * `usersClientService`).
17
- * - More than one such call inside the same exported function is an error.
18
- *
19
- * Exception (C-3):
20
- *
21
- * - Bindings starting with `shared` (e.g. `sharedDiscordService`,
22
- * `sharedResendService`) are EXCLUDED from the count. These represent
23
- * cross-cutting side-effect abstractions (Discord / Resend / Slack
24
- * notifications) that don't fit the entry-service 1:1 model and are allowed
25
- * to be invoked from entries directly.
26
- *
27
- * The rule reports the 2nd and later violations (the 1st call is permitted),
28
- * so the fix surface is the redundant calls.
29
- */
30
-
31
1
  const SERVICE_BINDING_REGEX = /Service$/;
32
2
 
33
3
  function isServiceCall(node) {