@pjts/eslint-style-rules 0.0.3 → 0.0.5
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/README.md +7 -2
- package/dist/index.js +33 -6
- package/dist/index.js.map +1 -1
- package/dist/lib/wiring.d.ts +23 -0
- package/dist/lib/wiring.js +94 -0
- package/dist/lib/wiring.js.map +1 -0
- package/dist/rules/config-provider-only.d.ts +8 -0
- package/dist/rules/config-provider-only.js +59 -0
- package/dist/rules/config-provider-only.js.map +1 -0
- package/dist/rules/module-service-getters.d.ts +0 -17
- package/dist/rules/module-service-getters.js +41 -5
- package/dist/rules/module-service-getters.js.map +1 -1
- package/dist/rules/module-shape.d.ts +18 -0
- package/dist/rules/module-shape.js +111 -0
- package/dist/rules/module-shape.js.map +1 -0
- package/dist/rules/no-manual-wiring.d.ts +11 -0
- package/dist/rules/no-manual-wiring.js +67 -0
- package/dist/rules/no-manual-wiring.js.map +1 -0
- package/dist/rules/port-has-adapters.d.ts +5 -4
- package/dist/rules/port-has-adapters.js +39 -40
- package/dist/rules/port-has-adapters.js.map +1 -1
- package/dist/rules/subclass-declares-constructor.d.ts +7 -0
- package/dist/rules/subclass-declares-constructor.js +34 -0
- package/dist/rules/subclass-declares-constructor.js.map +1 -0
- package/dist/rules/unique-class-names.d.ts +7 -0
- package/dist/rules/unique-class-names.js +36 -0
- package/dist/rules/unique-class-names.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,15 +106,20 @@ exception is an inline disable with a reason:
|
|
|
106
106
|
| Rule | What it checks |
|
|
107
107
|
|---|---|
|
|
108
108
|
| `module-layout` | only `rootFiles` at a module root (`<Module>Module.ts`, `<Module>MessageHandlers.ts`, `index.ts`); allowed top-level and `app/` subfolders; required entries exist |
|
|
109
|
-
| `module-
|
|
109
|
+
| `module-shape` | `…Module` = `@HttpModule` + `static readonly services`, `…MessageHandlers` = `@MessageHandlers` + `static readonly handlers`, `…Workers` = `@Workers` + `static readonly workers`; entries are classes from the module's own `./app/`; static methods with a return type are config providers; nothing else (no context, fields, getters, setters) |
|
|
110
|
+
| `module-service-getters` | (legacy, not in `configs.ddd`) a hand-wired `…Module`'s public getters return only `new X(...)` of a class from its own `./app/`, named `x` (camelCase) |
|
|
111
|
+
| `unique-class-names` | a class name is declared once under `src/modules/**` + `src/common/**` — class names key the wiring and the route registry |
|
|
112
|
+
| `subclass-declares-constructor` | app and infrastructure classes that `extends` something declare their own constructor (`ignoreBases` for `RepositoryBase` & co.) |
|
|
110
113
|
| `port-file` | ports live in `app/ports/<Name>Port.ts`, one exported interface per file named like it; only types; no `…Port` elsewhere |
|
|
111
|
-
| `port-has-adapters` | every `
|
|
114
|
+
| `port-has-adapters` | every `XPort` in `app/ports/` resolves to exactly one adapter per storage profile the way the container does: `implements` anywhere under `src/modules/**` / `src/common/**` (a prefixed `…XRepository` counts for repository ports), `pg` vs `memory` storage, a composite taking `XPort[]` wins, `@Adapter({ profile })` adapters are left to the profile |
|
|
112
115
|
| `test-location` | `<TestedFile>.<optional-suffix>.test.ts` sits next to `<TestedFile>.ts`; no `__tests__/` |
|
|
113
116
|
|
|
114
117
|
### Cross-cutting
|
|
115
118
|
|
|
116
119
|
| Rule | What it checks |
|
|
117
120
|
|---|---|
|
|
121
|
+
| `no-manual-wiring` | no `new X(...)` of an app class (`…Service/View/Handler` from an `app/` path), an adapter (`infrastructure/` path, `Pg…`, `InMemory…`), a module/group class or an application-provided infra class (`JetStreamMessagePublisher`, …) — outside `domain/`, `testing/`, `deployments/`, tests |
|
|
122
|
+
| `config-provider-only` | `config.pick/pickMaybe/has` only in static config methods of a `…Module`/`…MessageHandlers`/`…Workers` class, under `common/config/` or `deployments/` (`allow`, `moduleStaticMethods`) |
|
|
118
123
|
| `throw-app-error` | only `throw AppError.x(...)` / `throw new AppError(...)` from `@pjts/core`; rethrowing the caught error is allowed |
|
|
119
124
|
| `import-only-in` | a package is imported only under given path fragments (`uuid` → `/domain/`) |
|
|
120
125
|
| `no-duplicate-sdk-types` | frontend: no local type named like one in the generated client's `types.d.ts` |
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,11 @@ const import_only_in_1 = tslib_1.__importDefault(require("./rules/import-only-in
|
|
|
20
20
|
const knex_first_1 = tslib_1.__importDefault(require("./rules/knex-first"));
|
|
21
21
|
const module_layout_1 = tslib_1.__importDefault(require("./rules/module-layout"));
|
|
22
22
|
const module_service_getters_1 = tslib_1.__importDefault(require("./rules/module-service-getters"));
|
|
23
|
+
const module_shape_1 = tslib_1.__importDefault(require("./rules/module-shape"));
|
|
24
|
+
const no_manual_wiring_1 = tslib_1.__importDefault(require("./rules/no-manual-wiring"));
|
|
25
|
+
const config_provider_only_1 = tslib_1.__importDefault(require("./rules/config-provider-only"));
|
|
26
|
+
const unique_class_names_1 = tslib_1.__importDefault(require("./rules/unique-class-names"));
|
|
27
|
+
const subclass_declares_constructor_1 = tslib_1.__importDefault(require("./rules/subclass-declares-constructor"));
|
|
23
28
|
const no_async_in_domain_1 = tslib_1.__importDefault(require("./rules/no-async-in-domain"));
|
|
24
29
|
const no_duplicate_sdk_types_1 = tslib_1.__importDefault(require("./rules/no-duplicate-sdk-types"));
|
|
25
30
|
const no_entity_assembly_in_app_1 = tslib_1.__importDefault(require("./rules/no-entity-assembly-in-app"));
|
|
@@ -53,6 +58,11 @@ const rules = {
|
|
|
53
58
|
'knex-first': knex_first_1.default,
|
|
54
59
|
'module-layout': module_layout_1.default,
|
|
55
60
|
'module-service-getters': module_service_getters_1.default,
|
|
61
|
+
'module-shape': module_shape_1.default,
|
|
62
|
+
'no-manual-wiring': no_manual_wiring_1.default,
|
|
63
|
+
'config-provider-only': config_provider_only_1.default,
|
|
64
|
+
'unique-class-names': unique_class_names_1.default,
|
|
65
|
+
'subclass-declares-constructor': subclass_declares_constructor_1.default,
|
|
56
66
|
'no-async-in-domain': no_async_in_domain_1.default,
|
|
57
67
|
'no-duplicate-sdk-types': no_duplicate_sdk_types_1.default,
|
|
58
68
|
'no-entity-assembly-in-app': no_entity_assembly_in_app_1.default,
|
|
@@ -106,6 +116,10 @@ const createDddConfig = (options = {}) => {
|
|
|
106
116
|
// Only …Pg…Repository classes depend on the client, and only they write through it.
|
|
107
117
|
'pjts/db-client-dependency': 'error',
|
|
108
118
|
'pjts/db-writes-in-repositories': 'error',
|
|
119
|
+
// Environment is read in module static config methods and common/config/ only.
|
|
120
|
+
'pjts/config-provider-only': 'error',
|
|
121
|
+
// Class names key the generated wiring and the route registry.
|
|
122
|
+
'pjts/unique-class-names': 'error',
|
|
109
123
|
// Every thrown error is an AppError (rethrowing a caught error unchanged is fine).
|
|
110
124
|
'pjts/throw-app-error': 'error',
|
|
111
125
|
// Identities are minted by the domain.
|
|
@@ -119,6 +133,12 @@ const createDddConfig = (options = {}) => {
|
|
|
119
133
|
...(authAccess ? { 'pjts/auth-access': ['error', authAccess] } : {}),
|
|
120
134
|
},
|
|
121
135
|
},
|
|
136
|
+
{
|
|
137
|
+
// Nobody builds app classes, adapters or modules by hand: the IoC container does, from the wiring.
|
|
138
|
+
files: [`${src}/**/*.ts`],
|
|
139
|
+
ignores: [...exclude, `${module}/domain/**`, `${module}/testing/**`, `${src}/testing/**`],
|
|
140
|
+
rules: { 'pjts/no-manual-wiring': 'error' },
|
|
141
|
+
},
|
|
122
142
|
// ── Messages: one per file, MessageNames in step ──────────────────────────
|
|
123
143
|
{
|
|
124
144
|
files: [`${messages}/*.ts`],
|
|
@@ -132,17 +152,18 @@ const createDddConfig = (options = {}) => {
|
|
|
132
152
|
folders: ['app', 'domain', 'infrastructure', 'testing'],
|
|
133
153
|
subfolders: { app: ['dto', 'ports'] },
|
|
134
154
|
required: ['app', 'index.ts'],
|
|
135
|
-
// The module's
|
|
136
|
-
rootFiles: ['{Module}Module.ts', '{Module}MessageHandlers.ts', 'index.ts'],
|
|
155
|
+
// The module's handler and worker groups sit next to its HTTP module.
|
|
156
|
+
rootFiles: ['{Module}Module.ts', '{Module}MessageHandlers.ts', '{Module}Workers.ts', 'index.ts'],
|
|
137
157
|
}],
|
|
138
158
|
// Ports: app/ports/<Name>Port.ts, one exported interface named like the file; `…Port` nowhere else.
|
|
139
159
|
'pjts/port-file': 'error',
|
|
140
160
|
},
|
|
141
161
|
},
|
|
142
162
|
{
|
|
143
|
-
// Modules
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
// Modules and groups are declarations: @HttpModule + static services, @MessageHandlers + static
|
|
164
|
+
// handlers, @Workers + static workers, static config methods — no context, getters or setters.
|
|
165
|
+
files: [`${module}/*Module.ts`, `${module}/*MessageHandlers.ts`, `${module}/*Workers.ts`],
|
|
166
|
+
rules: { 'pjts/module-shape': 'error' },
|
|
146
167
|
},
|
|
147
168
|
// ── Domain layer: pure, synchronous, no I/O ───────────────────────────────
|
|
148
169
|
{
|
|
@@ -177,7 +198,8 @@ const createDddConfig = (options = {}) => {
|
|
|
177
198
|
files: [`${module}/app/*.ts`],
|
|
178
199
|
ignores: ['**/index.ts'],
|
|
179
200
|
rules: {
|
|
180
|
-
|
|
201
|
+
// App layer = services (incl. @AppService views), message handlers, workers. Providers live in infrastructure.
|
|
202
|
+
'pjts/app-class-suffix': ['error', { allowed: ['Service', 'Handler', 'Worker', 'View'] }],
|
|
181
203
|
'pjts/return-entities': 'error',
|
|
182
204
|
'pjts/no-entity-assembly-in-app': 'error',
|
|
183
205
|
'pjts/find-one-naming': 'error',
|
|
@@ -192,6 +214,11 @@ const createDddConfig = (options = {}) => {
|
|
|
192
214
|
files: [`${module}/app/ports/*.ts`],
|
|
193
215
|
rules: { 'pjts/port-has-adapters': 'error' },
|
|
194
216
|
},
|
|
217
|
+
{
|
|
218
|
+
// Subclasses declare the dependencies they take; the wiring is derived from constructors.
|
|
219
|
+
files: [`${module}/app/*.ts`, `${module}/infrastructure/**/*.ts`],
|
|
220
|
+
rules: { 'pjts/subclass-declares-constructor': ['error', { ignoreBases: ['RepositoryBase', 'TransactionalRepositoryBase', 'OutboxRepository', 'KnexViewBase'] }] },
|
|
221
|
+
},
|
|
195
222
|
{
|
|
196
223
|
files: [`${module}/app/**/*.ts`, `${module}/infrastructure/**/*.ts`],
|
|
197
224
|
rules: { 'pjts/save-contract': 'error' },
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** `<…>/src/<rest>` → the `src` directory of the file being linted, or null. */
|
|
2
|
+
export declare const srcRoot: (context: any) => string | null;
|
|
3
|
+
export interface ScannedClass {
|
|
4
|
+
name: string;
|
|
5
|
+
file: string;
|
|
6
|
+
implements: Set<string>;
|
|
7
|
+
/** `pg`, `memory`, `pg-nats`, … from the folder, else from the `Pg`/`InMemory` prefix. */
|
|
8
|
+
storage?: string;
|
|
9
|
+
/** `@Adapter({ profile: 'x' })` */
|
|
10
|
+
profile?: string;
|
|
11
|
+
/** Constructor parameter types written as `T[]` / `Array<T>`. */
|
|
12
|
+
arrayParams: Set<string>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Every class declared under `<src>/modules/**` and `<src>/common/**` (tests excluded), read once per
|
|
16
|
+
* lint run with regexes — enough for names, `implements`, storage, profile and array constructor params.
|
|
17
|
+
*/
|
|
18
|
+
export declare const scanClasses: (src: string) => ScannedClass[];
|
|
19
|
+
/** Test hook: forget scanned trees so a test can add files and re-scan. */
|
|
20
|
+
export declare const resetScans: () => void;
|
|
21
|
+
/** Adapters of `port` that the container would consider under `storage` with no active profile. */
|
|
22
|
+
export declare const candidatesOf: (classes: ScannedClass[], port: string, storage: string) => ScannedClass[];
|
|
23
|
+
export declare const isCompositeOf: (cls: ScannedClass, port: string) => boolean;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isCompositeOf = exports.candidatesOf = exports.resetScans = exports.scanClasses = exports.srcRoot = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
6
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
+
const ast_1 = require("./ast");
|
|
8
|
+
/** `<…>/src/<rest>` → the `src` directory of the file being linted, or null. */
|
|
9
|
+
const srcRoot = (context) => {
|
|
10
|
+
const file = (0, ast_1.filePath)(context);
|
|
11
|
+
const index = file.lastIndexOf('/src/');
|
|
12
|
+
return index === -1 ? null : file.slice(0, index + 4);
|
|
13
|
+
};
|
|
14
|
+
exports.srcRoot = srcRoot;
|
|
15
|
+
const STORAGE_FOLDERS = { pg: 'pg', 'pg+nats': 'pg-nats', 'pg-nats': 'pg-nats', 'in-memory': 'memory', memory: 'memory' };
|
|
16
|
+
const storageOf = (file, name) => {
|
|
17
|
+
const segments = file.split('/');
|
|
18
|
+
const infrastructure = segments.indexOf('infrastructure');
|
|
19
|
+
if (infrastructure !== -1) {
|
|
20
|
+
for (const segment of segments.slice(infrastructure + 1, -1)) {
|
|
21
|
+
if (STORAGE_FOLDERS[segment])
|
|
22
|
+
return STORAGE_FOLDERS[segment];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (/^InMemory[A-Z]/.test(name))
|
|
26
|
+
return 'memory';
|
|
27
|
+
if (/^Pg[A-Z]/.test(name))
|
|
28
|
+
return 'pg';
|
|
29
|
+
return undefined;
|
|
30
|
+
};
|
|
31
|
+
const scans = new Map();
|
|
32
|
+
/**
|
|
33
|
+
* Every class declared under `<src>/modules/**` and `<src>/common/**` (tests excluded), read once per
|
|
34
|
+
* lint run with regexes — enough for names, `implements`, storage, profile and array constructor params.
|
|
35
|
+
*/
|
|
36
|
+
const scanClasses = (src) => {
|
|
37
|
+
const cached = scans.get(src);
|
|
38
|
+
if (cached)
|
|
39
|
+
return cached;
|
|
40
|
+
const classes = [];
|
|
41
|
+
const walk = (current) => {
|
|
42
|
+
if (!node_fs_1.default.existsSync(current))
|
|
43
|
+
return;
|
|
44
|
+
for (const entry of node_fs_1.default.readdirSync(current, { withFileTypes: true })) {
|
|
45
|
+
const full = node_path_1.default.join(current, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
if (!/^(__tests?__|node_modules)$/.test(entry.name))
|
|
48
|
+
walk(full);
|
|
49
|
+
}
|
|
50
|
+
else if (/\.ts$/.test(entry.name) && !/\.(test|spec|d)\.ts$/.test(entry.name)) {
|
|
51
|
+
const source = node_fs_1.default.readFileSync(full, 'utf8');
|
|
52
|
+
const file = full.split(node_path_1.default.sep).join('/');
|
|
53
|
+
for (const match of source.matchAll(/(?:@Adapter\(\s*\{[^}]*profile\s*:\s*['"`]([\w-]+)['"`][^}]*\}\s*\)\s*)?(?:export\s+)?(?:abstract\s+)?class\s+(\w+)([^{]*)\{/g)) {
|
|
54
|
+
const [, profile, name, header] = match;
|
|
55
|
+
const implemented = header.match(/\bimplements\s+([\w\s,<>.]+)/)?.[1] ?? '';
|
|
56
|
+
const body = source.slice(match.index + match[0].length);
|
|
57
|
+
const constructorParams = body.match(/constructor\s*\(([^)]*)\)/)?.[1] ?? '';
|
|
58
|
+
const arrayParams = new Set();
|
|
59
|
+
for (const array of constructorParams.matchAll(/:\s*(?:readonly\s+)?(\w+)\[\]|:\s*(?:Readonly)?Array<\s*(\w+)\s*>/g)) {
|
|
60
|
+
arrayParams.add(array[1] ?? array[2]);
|
|
61
|
+
}
|
|
62
|
+
classes.push({
|
|
63
|
+
name,
|
|
64
|
+
file,
|
|
65
|
+
implements: new Set((implemented.match(/\b[A-Z]\w*/g) ?? []).filter((word) => word !== 'implements')),
|
|
66
|
+
storage: storageOf(file, name),
|
|
67
|
+
profile,
|
|
68
|
+
arrayParams,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
walk(node_path_1.default.join(src, 'modules'));
|
|
75
|
+
walk(node_path_1.default.join(src, 'common'));
|
|
76
|
+
scans.set(src, classes);
|
|
77
|
+
return classes;
|
|
78
|
+
};
|
|
79
|
+
exports.scanClasses = scanClasses;
|
|
80
|
+
/** Test hook: forget scanned trees so a test can add files and re-scan. */
|
|
81
|
+
const resetScans = () => scans.clear();
|
|
82
|
+
exports.resetScans = resetScans;
|
|
83
|
+
const isMemory = (storage) => storage === 'memory';
|
|
84
|
+
/** Adapters of `port` that the container would consider under `storage` with no active profile. */
|
|
85
|
+
const candidatesOf = (classes, port, storage) => {
|
|
86
|
+
const base = port.replace(/Port$/, '');
|
|
87
|
+
return classes.filter((cls) => (cls.implements.has(port) || (/RepositoryPort$/.test(port) && cls.name !== base && cls.name.endsWith(base))) &&
|
|
88
|
+
(cls.storage === undefined || isMemory(cls.storage) === isMemory(storage)) &&
|
|
89
|
+
cls.profile === undefined);
|
|
90
|
+
};
|
|
91
|
+
exports.candidatesOf = candidatesOf;
|
|
92
|
+
const isCompositeOf = (cls, port) => cls.implements.has(port) && cls.arrayParams.has(port);
|
|
93
|
+
exports.isCompositeOf = isCompositeOf;
|
|
94
|
+
//# sourceMappingURL=wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiring.js","sourceRoot":"","sources":["../../src/lib/wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment is read in exactly two kinds of places: static config methods of a module class
|
|
3
|
+
* (`static calendarFeedConfig(): CalendarFeedConfig`) and the shared `common/config/` providers (plus
|
|
4
|
+
* the entry file under `deployments/`). Everywhere else a class declares the `…Config` object it needs
|
|
5
|
+
* as a constructor parameter.
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ast_1 = require("../lib/ast");
|
|
4
|
+
const rule_1 = require("../lib/rule");
|
|
5
|
+
/**
|
|
6
|
+
* Environment is read in exactly two kinds of places: static config methods of a module class
|
|
7
|
+
* (`static calendarFeedConfig(): CalendarFeedConfig`) and the shared `common/config/` providers (plus
|
|
8
|
+
* the entry file under `deployments/`). Everywhere else a class declares the `…Config` object it needs
|
|
9
|
+
* as a constructor parameter.
|
|
10
|
+
*/
|
|
11
|
+
exports.default = (0, rule_1.defineRule)({
|
|
12
|
+
meta: {
|
|
13
|
+
type: 'suggestion',
|
|
14
|
+
docs: { description: 'config.pick / pickMaybe / has only in config providers' },
|
|
15
|
+
messages: {
|
|
16
|
+
outside: '`{{object}}.{{method}}(...)` is allowed only in {{where}} — declare the `…Config` object as a constructor dependency instead.',
|
|
17
|
+
},
|
|
18
|
+
schema: [{
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
configNames: { type: 'array', items: { type: 'string' } },
|
|
22
|
+
methods: { type: 'array', items: { type: 'string' } },
|
|
23
|
+
allow: { type: 'array', items: { type: 'string' } },
|
|
24
|
+
moduleStaticMethods: { type: 'boolean' },
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
}],
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
const configNames = context.options[0]?.configNames ?? ['config'];
|
|
31
|
+
const methods = context.options[0]?.methods ?? ['pick', 'pickMaybe', 'has'];
|
|
32
|
+
const allow = context.options[0]?.allow ?? ['/common/config/', '/deployments/'];
|
|
33
|
+
const moduleStaticMethods = context.options[0]?.moduleStaticMethods ?? true;
|
|
34
|
+
const file = (0, ast_1.filePath)(context);
|
|
35
|
+
if (allow.some((fragment) => file.includes(fragment)))
|
|
36
|
+
return {};
|
|
37
|
+
const where = [moduleStaticMethods ? 'static config methods of a module class' : null, ...allow.map((fragment) => fragment.replace(/^\//, ''))]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(', ');
|
|
40
|
+
const inModuleStaticMethod = (node) => {
|
|
41
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
42
|
+
const method = ancestors.find((ancestor) => ancestor.type === 'MethodDefinition');
|
|
43
|
+
const cls = ancestors.find((ancestor) => ancestor.type === 'ClassDeclaration');
|
|
44
|
+
return !!method && method.static && method.kind === 'method' && /(Module|MessageHandlers|Workers)$/.test(cls?.id?.name ?? '');
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
'CallExpression[callee.type="MemberExpression"][callee.object.type="Identifier"]'(node) {
|
|
48
|
+
const object = node.callee.object.name;
|
|
49
|
+
const method = node.callee.property?.name;
|
|
50
|
+
if (!configNames.includes(object) || !methods.includes(method))
|
|
51
|
+
return;
|
|
52
|
+
if (moduleStaticMethods && inModuleStaticMethod(node))
|
|
53
|
+
return;
|
|
54
|
+
context.report({ node, messageId: 'outside', data: { object, method, where } });
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=config-provider-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-provider-only.js","sourceRoot":"","sources":["../../src/rules/config-provider-only.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A module class exposes application-layer classes — and only those — through public getters named
|
|
3
|
-
* after them in camelCase:
|
|
4
|
-
*
|
|
5
|
-
* import { ServiceOfferPublicView } from './app/ServiceOfferPublicView';
|
|
6
|
-
*
|
|
7
|
-
* public get serviceOfferPublicView() {
|
|
8
|
-
* return new ServiceOfferPublicView(this.serviceOfferRepo);
|
|
9
|
-
* }
|
|
10
|
-
*
|
|
11
|
-
* Two directions are checked:
|
|
12
|
-
* - every public getter returns `new X(...)` where X is a value imported from the module's own
|
|
13
|
-
* `./app/…` (not `./app/dto` or `./app/ports`), and is named X with a lower-case first letter;
|
|
14
|
-
* - every `new X(...)` of an app use-case class (suffix `Service` | `View` | `Handler`) sits in a public,
|
|
15
|
-
* non-static getter. Internal app helpers (notifiers, runners) may be built in the constructor for
|
|
16
|
-
* wiring — they just can't be exposed.
|
|
17
|
-
*/
|
|
18
1
|
declare const _default: import("eslint").Rule.RuleModule;
|
|
19
2
|
export default _default;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
5
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
3
6
|
const rule_1 = require("../lib/rule");
|
|
4
7
|
const ast_1 = require("../lib/ast");
|
|
5
8
|
/**
|
|
@@ -15,10 +18,35 @@ const ast_1 = require("../lib/ast");
|
|
|
15
18
|
* Two directions are checked:
|
|
16
19
|
* - every public getter returns `new X(...)` where X is a value imported from the module's own
|
|
17
20
|
* `./app/…` (not `./app/dto` or `./app/ports`), and is named X with a lower-case first letter;
|
|
18
|
-
* - every `new X(...)` of an
|
|
19
|
-
* non-static getter.
|
|
20
|
-
*
|
|
21
|
+
* - every `new X(...)` of an endpoint class — one decorated `@AppService` (`endpointDecorators`) in the
|
|
22
|
+
* module's `app/` — sits in a public, non-static getter. Other app classes (collaborator services,
|
|
23
|
+
* workers) are built privately in the constructor and passed to the endpoints that need them.
|
|
21
24
|
*/
|
|
25
|
+
/** module `app/` dir → names of the classes decorated as endpoints there (per lint run). */
|
|
26
|
+
const endpointClasses = new Map();
|
|
27
|
+
const endpointsIn = (appDir, decorators) => {
|
|
28
|
+
const key = `${appDir}#${decorators.join(',')}`;
|
|
29
|
+
if (!endpointClasses.has(key)) {
|
|
30
|
+
const names = new Set();
|
|
31
|
+
const pattern = new RegExp(`@(?:${decorators.join('|')})(?:\\([^)]*\\))?\\s+export\\s+(?:default\\s+)?class\\s+(\\w+)`, 'g');
|
|
32
|
+
const walk = (dir) => {
|
|
33
|
+
if (!fs_1.default.existsSync(dir))
|
|
34
|
+
return;
|
|
35
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
36
|
+
const full = path_1.default.join(dir, entry.name);
|
|
37
|
+
if (entry.isDirectory())
|
|
38
|
+
walk(full);
|
|
39
|
+
else if (/\.ts$/.test(entry.name) && !/\.test\.ts$/.test(entry.name)) {
|
|
40
|
+
for (const match of fs_1.default.readFileSync(full, 'utf8').matchAll(pattern))
|
|
41
|
+
names.add(match[1]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(appDir);
|
|
46
|
+
endpointClasses.set(key, names);
|
|
47
|
+
}
|
|
48
|
+
return endpointClasses.get(key);
|
|
49
|
+
};
|
|
22
50
|
exports.default = (0, rule_1.defineRule)({
|
|
23
51
|
meta: {
|
|
24
52
|
type: 'suggestion',
|
|
@@ -31,12 +59,17 @@ exports.default = (0, rule_1.defineRule)({
|
|
|
31
59
|
},
|
|
32
60
|
schema: [{
|
|
33
61
|
type: 'object',
|
|
34
|
-
properties: {
|
|
62
|
+
properties: {
|
|
63
|
+
suffixes: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
64
|
+
endpointDecorators: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
65
|
+
},
|
|
35
66
|
additionalProperties: false,
|
|
36
67
|
}],
|
|
37
68
|
},
|
|
38
69
|
create(context) {
|
|
39
70
|
const suffixes = context.options[0]?.suffixes ?? ['Service', 'View', 'Handler'];
|
|
71
|
+
const endpointDecorators = context.options[0]?.endpointDecorators ?? ['AppService'];
|
|
72
|
+
const appDir = path_1.default.join(path_1.default.dirname(context.filename), 'app');
|
|
40
73
|
/** local name → import source, for value imports of the file. */
|
|
41
74
|
const imports = new Map();
|
|
42
75
|
const isAppSource = (source) => /^\.\/app(\/|$)/.test(source) && !/^\.\/app\/(dto|ports)(\/|$)/.test(source);
|
|
@@ -110,11 +143,14 @@ exports.default = (0, rule_1.defineRule)({
|
|
|
110
143
|
}
|
|
111
144
|
}
|
|
112
145
|
},
|
|
113
|
-
//
|
|
146
|
+
// Endpoint classes: only constructed inside public getters.
|
|
114
147
|
'NewExpression[callee.type="Identifier"]'(node) {
|
|
115
148
|
const name = node.callee.name;
|
|
116
149
|
if (!isAppClass(name) || !suffixes.some((suffix) => name.endsWith(suffix)))
|
|
117
150
|
return;
|
|
151
|
+
// Only endpoints must be exposed; collaborator services and workers are private wiring.
|
|
152
|
+
if (!endpointsIn(appDir, endpointDecorators).has(name))
|
|
153
|
+
return;
|
|
118
154
|
const ancestors = context.sourceCode.getAncestors(node);
|
|
119
155
|
const member = (0, ast_1.findLast)(ancestors, (a) => a.type === 'MethodDefinition' || a.type === 'PropertyDefinition');
|
|
120
156
|
if (!member || !isModuleClass(member.parent.parent))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-service-getters.js","sourceRoot":"","sources":["../../src/rules/module-service-getters.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"module-service-getters.js","sourceRoot":"","sources":["../../src/rules/module-service-getters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module, message-handler and worker group classes are declarations, not factories:
|
|
3
|
+
*
|
|
4
|
+
* @HttpModule('/v1')
|
|
5
|
+
* export class CalendarModule {
|
|
6
|
+
* static readonly services = [CalendarEventService, CalendarView]
|
|
7
|
+
* static calendarFeedConfig(): CalendarFeedConfig { … } // config provider (optional)
|
|
8
|
+
* }
|
|
9
|
+
*
|
|
10
|
+
* - `…Module` carries `@HttpModule` and `static readonly services`; `…MessageHandlers` carries
|
|
11
|
+
* `@MessageHandlers` and `static readonly handlers`; `…Workers` carries `@Workers` and `static readonly workers`;
|
|
12
|
+
* - every list entry is a class imported from the module's own `./app/…` (not `./app/dto`, `./app/ports`);
|
|
13
|
+
* - nothing else: no constructor, fields, getters, setters or instance methods (that was manual wiring);
|
|
14
|
+
* static methods with an explicit return type are config providers and are allowed unless
|
|
15
|
+
* `configProviders: false`.
|
|
16
|
+
*/
|
|
17
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ast_1 = require("../lib/ast");
|
|
4
|
+
const rule_1 = require("../lib/rule");
|
|
5
|
+
/**
|
|
6
|
+
* Module, message-handler and worker group classes are declarations, not factories:
|
|
7
|
+
*
|
|
8
|
+
* @HttpModule('/v1')
|
|
9
|
+
* export class CalendarModule {
|
|
10
|
+
* static readonly services = [CalendarEventService, CalendarView]
|
|
11
|
+
* static calendarFeedConfig(): CalendarFeedConfig { … } // config provider (optional)
|
|
12
|
+
* }
|
|
13
|
+
*
|
|
14
|
+
* - `…Module` carries `@HttpModule` and `static readonly services`; `…MessageHandlers` carries
|
|
15
|
+
* `@MessageHandlers` and `static readonly handlers`; `…Workers` carries `@Workers` and `static readonly workers`;
|
|
16
|
+
* - every list entry is a class imported from the module's own `./app/…` (not `./app/dto`, `./app/ports`);
|
|
17
|
+
* - nothing else: no constructor, fields, getters, setters or instance methods (that was manual wiring);
|
|
18
|
+
* static methods with an explicit return type are config providers and are allowed unless
|
|
19
|
+
* `configProviders: false`.
|
|
20
|
+
*/
|
|
21
|
+
exports.default = (0, rule_1.defineRule)({
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'suggestion',
|
|
24
|
+
docs: { description: 'Module and group classes declare their services, handlers or workers statically and nothing else' },
|
|
25
|
+
messages: {
|
|
26
|
+
decorator: '{{name}} must be decorated with @{{decorator}}.',
|
|
27
|
+
list: '{{name}} must declare `static readonly {{key}} = [...]` as an array literal of class names.',
|
|
28
|
+
notApp: '"{{entry}}" in {{name}}.{{key}} must be a class imported from ./app/ — {{what}}.',
|
|
29
|
+
member: '{{name}} may only declare `static readonly {{key}}`{{extra}}; found {{what}}. Wiring is derived by the container.',
|
|
30
|
+
configReturn: 'Static method {{name}}.{{method}}() must declare the config type it returns.',
|
|
31
|
+
},
|
|
32
|
+
schema: [{
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: { configProviders: { type: 'boolean' } },
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
}],
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const configProviders = context.options[0]?.configProviders ?? true;
|
|
40
|
+
const imports = new Map();
|
|
41
|
+
const isAppSource = (source) => /^\.\/app(\/|$)/.test(source) && !/^\.\/app\/(dto|ports)(\/|$)/.test(source);
|
|
42
|
+
const kinds = [
|
|
43
|
+
{ suffix: /Module$/, decorator: 'HttpModule', key: 'services' },
|
|
44
|
+
{ suffix: /MessageHandlers$/, decorator: 'MessageHandlers', key: 'handlers' },
|
|
45
|
+
{ suffix: /Workers$/, decorator: 'Workers', key: 'workers' },
|
|
46
|
+
];
|
|
47
|
+
const describe = (member) => {
|
|
48
|
+
if (member.type === 'MethodDefinition') {
|
|
49
|
+
if (member.kind === 'constructor')
|
|
50
|
+
return 'a constructor';
|
|
51
|
+
if (member.kind === 'get' || member.kind === 'set')
|
|
52
|
+
return `${member.kind}ter \`${(0, ast_1.memberName)(member)}\``;
|
|
53
|
+
return `${member.static ? 'static ' : ''}method \`${(0, ast_1.memberName)(member)}\``;
|
|
54
|
+
}
|
|
55
|
+
return `${member.static ? 'static ' : ''}field \`${(0, ast_1.memberName)(member)}\``;
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
ImportDeclaration(node) {
|
|
59
|
+
if (node.importKind === 'type')
|
|
60
|
+
return;
|
|
61
|
+
for (const specifier of node.specifiers) {
|
|
62
|
+
if (specifier.importKind !== 'type')
|
|
63
|
+
imports.set(specifier.local.name, node.source.value);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
ClassDeclaration(node) {
|
|
67
|
+
const name = node.id?.name;
|
|
68
|
+
const kind = name && kinds.find((candidate) => candidate.suffix.test(name));
|
|
69
|
+
if (!kind)
|
|
70
|
+
return;
|
|
71
|
+
const extra = configProviders ? ' and static config methods' : '';
|
|
72
|
+
if (!(0, ast_1.decoratorNames)(node).includes(kind.decorator)) {
|
|
73
|
+
context.report({ node: node.id, messageId: 'decorator', data: { name, decorator: kind.decorator } });
|
|
74
|
+
}
|
|
75
|
+
const list = node.body.body.find((member) => member.type === 'PropertyDefinition' && member.static && (0, ast_1.memberName)(member) === kind.key);
|
|
76
|
+
if (!list || list.value?.type !== 'ArrayExpression') {
|
|
77
|
+
context.report({ node: list ?? node.id, messageId: 'list', data: { name, key: kind.key } });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
for (const element of list.value.elements) {
|
|
81
|
+
if (element?.type !== 'Identifier') {
|
|
82
|
+
context.report({ node: element ?? list, messageId: 'notApp', data: { entry: element ? context.sourceCode.getText(element) : 'hole', name, key: kind.key, what: 'it is not a class name' } });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const source = imports.get(element.name);
|
|
86
|
+
if (!source || !isAppSource(source)) {
|
|
87
|
+
context.report({
|
|
88
|
+
node: element,
|
|
89
|
+
messageId: 'notApp',
|
|
90
|
+
data: { entry: element.name, name, key: kind.key, what: source ? `it comes from '${source}'` : 'it is not imported from ./app/' },
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const member of node.body.body) {
|
|
96
|
+
if (member === list)
|
|
97
|
+
continue;
|
|
98
|
+
const isStaticMethod = member.type === 'MethodDefinition' && member.static && member.kind === 'method';
|
|
99
|
+
if (configProviders && isStaticMethod) {
|
|
100
|
+
if (!member.value.returnType) {
|
|
101
|
+
context.report({ node: member.key, messageId: 'configReturn', data: { name, method: (0, ast_1.memberName)(member) } });
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
context.report({ node: member.key ?? member, messageId: 'member', data: { name, key: kind.key, extra, what: describe(member) } });
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=module-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-shape.js","sourceRoot":"","sources":["../../src/rules/module-shape.ts"],"names":[],"mappings":";;AAAA,oCAAwD;AACxD,sCAAyC;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,kGAAkG,EAAE;QACzH,QAAQ,EAAE;YACR,SAAS,EAAE,iDAAiD;YAC5D,IAAI,EAAE,6FAA6F;YACnG,MAAM,EAAE,kFAAkF;YAC1F,MAAM,EAAE,mHAAmH;YAC3H,YAAY,EAAE,8EAA8E;SAC7F;QACD,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;gBACpD,oBAAoB,EAAE,KAAK;aAC5B,CAAC;KACH;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,eAAe,IAAI,IAAI,CAAC;QACpE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC1C,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrH,MAAM,KAAK,GAA8D;YACvE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,UAAU,EAAE;YAC/D,EAAE,MAAM,EAAE,kBAAkB,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,EAAE;YAC7E,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE;SAC7D,CAAC;QACF,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE;YAC1B,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;gBACvC,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa;oBAAE,OAAO,eAAe,CAAC;gBAC1D,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;oBAAE,OAAO,GAAG,MAAM,CAAC,IAAI,SAAS,IAAA,gBAAU,EAAC,MAAM,CAAC,IAAI,CAAC;gBACzG,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,IAAA,gBAAU,EAAC,MAAM,CAAC,IAAI,CAAC;YAC7E,CAAC;YACD,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,IAAA,gBAAU,EAAC,MAAM,CAAC,IAAI,CAAC;QAC5E,CAAC,CAAC;QAEF,OAAO;YACL,iBAAiB,CAAC,IAAI;gBACpB,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM;oBAAE,OAAO;gBACvC,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACxC,IAAI,SAAS,CAAC,UAAU,KAAK,MAAM;wBAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC5F,CAAC;YACH,CAAC;YACD,gBAAgB,CAAC,IAAI;gBACnB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC;gBAC3B,MAAM,IAAI,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC5E,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,MAAM,KAAK,GAAG,eAAe,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAA,oBAAc,EAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;oBACnD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACvG,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,oBAAoB,IAAI,MAAM,CAAC,MAAM,IAAI,IAAA,gBAAU,EAAC,MAAM,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC;gBACvI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,iBAAiB,EAAE,CAAC;oBACpD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBAC9F,CAAC;qBAAM,CAAC;oBACN,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;wBAC1C,IAAI,OAAO,EAAE,IAAI,KAAK,YAAY,EAAE,CAAC;4BACnC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,IAAI,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,wBAAwB,EAAE,EAAE,CAAC,CAAC;4BAC7L,SAAS;wBACX,CAAC;wBACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;wBACzC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;4BACpC,OAAO,CAAC,MAAM,CAAC;gCACb,IAAI,EAAE,OAAO;gCACb,SAAS,EAAE,QAAQ;gCACnB,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,kBAAkB,MAAM,GAAG,CAAC,CAAC,CAAC,gCAAgC,EAAE;6BAClI,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;oBACpC,IAAI,MAAM,KAAK,IAAI;wBAAE,SAAS;oBAC9B,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,KAAK,kBAAkB,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC;oBACvG,IAAI,eAAe,IAAI,cAAc,EAAE,CAAC;wBACtC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;4BAC7B,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAA,gBAAU,EAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;wBAC9G,CAAC;wBACD,SAAS;oBACX,CAAC;oBACD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;gBACpI,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nobody constructs application classes, adapters or modules by hand — the IoC container does, from the
|
|
3
|
+
* generated wiring. `new X(...)` is reported when X is:
|
|
4
|
+
* - a value imported from an `app/` path (not `app/dto`, `app/ports`) whose name ends with a use-case suffix;
|
|
5
|
+
* - a value imported from an `infrastructure` path, or named `Pg…` / `InMemory…`;
|
|
6
|
+
* - a class named `…Module`, `…MessageHandlers` or `…Workers`;
|
|
7
|
+
* - one of the infrastructure classes the application itself provides (`JetStreamMessagePublisher`, …).
|
|
8
|
+
* Apply it outside `domain/`, `testing/`, `deployments/` and tests.
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
/**
|
|
5
|
+
* Nobody constructs application classes, adapters or modules by hand — the IoC container does, from the
|
|
6
|
+
* generated wiring. `new X(...)` is reported when X is:
|
|
7
|
+
* - a value imported from an `app/` path (not `app/dto`, `app/ports`) whose name ends with a use-case suffix;
|
|
8
|
+
* - a value imported from an `infrastructure` path, or named `Pg…` / `InMemory…`;
|
|
9
|
+
* - a class named `…Module`, `…MessageHandlers` or `…Workers`;
|
|
10
|
+
* - one of the infrastructure classes the application itself provides (`JetStreamMessagePublisher`, …).
|
|
11
|
+
* Apply it outside `domain/`, `testing/`, `deployments/` and tests.
|
|
12
|
+
*/
|
|
13
|
+
exports.default = (0, rule_1.defineRule)({
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'suggestion',
|
|
16
|
+
docs: { description: 'No hand-built application classes, adapters or modules — the container builds them' },
|
|
17
|
+
messages: {
|
|
18
|
+
wiring: '`new {{name}}(...)` is manual wiring ({{why}}): declare {{name}} as a constructor dependency and let the container build it.',
|
|
19
|
+
},
|
|
20
|
+
schema: [{
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
suffixes: { type: 'array', items: { type: 'string' } },
|
|
24
|
+
infraClasses: { type: 'array', items: { type: 'string' } },
|
|
25
|
+
},
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
}],
|
|
28
|
+
},
|
|
29
|
+
create(context) {
|
|
30
|
+
const suffixes = context.options[0]?.suffixes ?? ['Service', 'View', 'Handler'];
|
|
31
|
+
const infraClasses = context.options[0]?.infraClasses ?? ['JetStreamMessagePublisher', 'JetStreamMessageSubscriber', 'RedisMessageDeduplicator', 'MessageRepeater'];
|
|
32
|
+
const imports = new Map();
|
|
33
|
+
const isApp = (source) => /(^|\/)app(\/|$)/.test(source) && !/(^|\/)app\/(dto|ports)(\/|$)/.test(source);
|
|
34
|
+
const isInfrastructure = (source) => /(^|\/)infrastructure(\/|$)/.test(source);
|
|
35
|
+
const why = (name) => {
|
|
36
|
+
const source = imports.get(name);
|
|
37
|
+
if (/(Module|MessageHandlers|Workers)$/.test(name))
|
|
38
|
+
return 'module and group classes are declarations';
|
|
39
|
+
if (source && isApp(source) && suffixes.some((suffix) => name.endsWith(suffix)))
|
|
40
|
+
return `${name} is an application class from '${source}'`;
|
|
41
|
+
if (source && isInfrastructure(source))
|
|
42
|
+
return `${name} is an adapter from '${source}'`;
|
|
43
|
+
if (/^(Pg|InMemory)[A-Z]/.test(name))
|
|
44
|
+
return `${name} is an adapter`;
|
|
45
|
+
if (infraClasses.includes(name))
|
|
46
|
+
return `${name} is provided by the application`;
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
ImportDeclaration(node) {
|
|
51
|
+
if (node.importKind === 'type')
|
|
52
|
+
return;
|
|
53
|
+
for (const specifier of node.specifiers) {
|
|
54
|
+
if (specifier.importKind !== 'type')
|
|
55
|
+
imports.set(specifier.local.name, node.source.value);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
'NewExpression[callee.type="Identifier"]'(node) {
|
|
59
|
+
const name = node.callee.name;
|
|
60
|
+
const reason = why(name);
|
|
61
|
+
if (reason)
|
|
62
|
+
context.report({ node, messageId: 'wiring', data: { name, why: reason } });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=no-manual-wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-manual-wiring.js","sourceRoot":"","sources":["../../src/rules/no-manual-wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Every `<X>
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Every `<X>Port` declared in `app/ports/` resolves to exactly one adapter per storage profile, the
|
|
3
|
+
* way the IoC container resolves it: a class anywhere under `src/modules/**` or `src/common/**` that
|
|
4
|
+
* `implements` the port (for repository ports a prefixed `…<X>Repository` also counts), filtered by
|
|
5
|
+
* storage (`pg` vs `memory` folders / prefixes), with a composite (`implements T`, takes `T[]`)
|
|
6
|
+
* winning over the adapters it wraps, and adapters behind `@Adapter({ profile })` left to the profile.
|
|
6
7
|
* Reads sibling files, so editors only re-check it when the port file itself is re-linted.
|
|
7
8
|
*/
|
|
8
9
|
declare const _default: import("eslint").Rule.RuleModule;
|
|
@@ -1,60 +1,59 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
5
|
-
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
6
3
|
const ast_1 = require("../lib/ast");
|
|
7
4
|
const rule_1 = require("../lib/rule");
|
|
8
|
-
|
|
9
|
-
const infrastructureClasses = new Map();
|
|
10
|
-
const classesIn = (dir) => {
|
|
11
|
-
if (!infrastructureClasses.has(dir)) {
|
|
12
|
-
const classes = [];
|
|
13
|
-
const walk = (current) => {
|
|
14
|
-
if (!node_fs_1.default.existsSync(current))
|
|
15
|
-
return;
|
|
16
|
-
for (const entry of node_fs_1.default.readdirSync(current, { withFileTypes: true })) {
|
|
17
|
-
const full = node_path_1.default.join(current, entry.name);
|
|
18
|
-
if (entry.isDirectory())
|
|
19
|
-
walk(full);
|
|
20
|
-
else if (/\.ts$/.test(entry.name) && !/\.test\.ts$/.test(entry.name)) {
|
|
21
|
-
for (const match of node_fs_1.default.readFileSync(full, 'utf8').matchAll(/\bclass\s+(\w+)([^{]*)\{/g)) {
|
|
22
|
-
const implemented = match[2].match(/\bimplements\s+([\w\s,<>.]+)/)?.[1] ?? '';
|
|
23
|
-
classes.push({ name: match[1], implements: new Set(implemented.match(/\w+/g) ?? []) });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
walk(dir);
|
|
29
|
-
infrastructureClasses.set(dir, classes);
|
|
30
|
-
}
|
|
31
|
-
return infrastructureClasses.get(dir);
|
|
32
|
-
};
|
|
5
|
+
const wiring_1 = require("../lib/wiring");
|
|
33
6
|
/**
|
|
34
|
-
* Every `<X>
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
7
|
+
* Every `<X>Port` declared in `app/ports/` resolves to exactly one adapter per storage profile, the
|
|
8
|
+
* way the IoC container resolves it: a class anywhere under `src/modules/**` or `src/common/**` that
|
|
9
|
+
* `implements` the port (for repository ports a prefixed `…<X>Repository` also counts), filtered by
|
|
10
|
+
* storage (`pg` vs `memory` folders / prefixes), with a composite (`implements T`, takes `T[]`)
|
|
11
|
+
* winning over the adapters it wraps, and adapters behind `@Adapter({ profile })` left to the profile.
|
|
38
12
|
* Reads sibling files, so editors only re-check it when the port file itself is re-linted.
|
|
39
13
|
*/
|
|
40
14
|
exports.default = (0, rule_1.defineRule)({
|
|
41
15
|
meta: {
|
|
42
16
|
type: 'suggestion',
|
|
43
|
-
docs: { description: 'Require every
|
|
44
|
-
messages: {
|
|
45
|
-
|
|
17
|
+
docs: { description: 'Require every port to have exactly one adapter per storage profile' },
|
|
18
|
+
messages: {
|
|
19
|
+
missing: 'Port "{{port}}" has no adapter under src/modules or src/common (expected e.g. Pg{{base}} implementing it).',
|
|
20
|
+
ambiguous: 'Port "{{port}}" has {{count}} adapters in storage "{{storage}}": {{names}} — keep one, make one a composite taking {{port}}[], or mark them with @Adapter({ profile }).',
|
|
21
|
+
composites: 'Port "{{port}}" has more than one composite adapter: {{names}}.',
|
|
22
|
+
},
|
|
23
|
+
schema: [{
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: { storages: { type: 'array', items: { type: 'string' } } },
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
}],
|
|
46
28
|
},
|
|
47
29
|
create(context) {
|
|
30
|
+
const storages = context.options[0]?.storages ?? ['pg', 'memory'];
|
|
48
31
|
const location = (0, ast_1.moduleLocation)(context);
|
|
49
|
-
|
|
32
|
+
const src = (0, wiring_1.srcRoot)(context);
|
|
33
|
+
if (!location || !src || location.rest[0] !== 'app' || location.rest[1] !== 'ports')
|
|
50
34
|
return {};
|
|
51
35
|
return {
|
|
52
|
-
'TSInterfaceDeclaration[id.name=/
|
|
36
|
+
'TSInterfaceDeclaration[id.name=/Port$/]'(node) {
|
|
53
37
|
const port = node.id.name;
|
|
54
38
|
const base = port.replace(/Port$/, '');
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
const classes = (0, wiring_1.scanClasses)(src);
|
|
40
|
+
const implementers = classes.filter((cls) => cls.implements.has(port) || (/RepositoryPort$/.test(port) && cls.name !== base && cls.name.endsWith(base)));
|
|
41
|
+
if (implementers.length === 0) {
|
|
42
|
+
context.report({ node: node.id, messageId: 'missing', data: { port, base } });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const storage of storages) {
|
|
46
|
+
const candidates = (0, wiring_1.candidatesOf)(classes, port, storage);
|
|
47
|
+
const composites = candidates.filter((cls) => (0, wiring_1.isCompositeOf)(cls, port));
|
|
48
|
+
if (composites.length > 1) {
|
|
49
|
+
context.report({ node: node.id, messageId: 'composites', data: { port, names: composites.map((cls) => cls.name).join(', ') } });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (composites.length === 0 && candidates.length > 1) {
|
|
53
|
+
context.report({ node: node.id, messageId: 'ambiguous', data: { port, storage, count: String(candidates.length), names: candidates.map((cls) => cls.name).join(', ') } });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
58
57
|
},
|
|
59
58
|
};
|
|
60
59
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port-has-adapters.js","sourceRoot":"","sources":["../../src/rules/port-has-adapters.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"port-has-adapters.js","sourceRoot":"","sources":["../../src/rules/port-has-adapters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application services, adapters, handlers and workers that extend a class declare their own
|
|
3
|
+
* constructor: the wiring is derived from constructor signatures, and an inherited signature hides
|
|
4
|
+
* which dependencies the subclass really takes (and lets several subclasses share one ambiguous port).
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
/**
|
|
5
|
+
* Application services, adapters, handlers and workers that extend a class declare their own
|
|
6
|
+
* constructor: the wiring is derived from constructor signatures, and an inherited signature hides
|
|
7
|
+
* which dependencies the subclass really takes (and lets several subclasses share one ambiguous port).
|
|
8
|
+
*/
|
|
9
|
+
exports.default = (0, rule_1.defineRule)({
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: { description: 'Subclasses in app/ and infrastructure/ declare their own constructor' },
|
|
13
|
+
messages: { missing: '{{name}} extends {{base}} but declares no constructor; declare the dependencies it takes (the wiring is derived from them).' },
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: { ignoreBases: { type: 'array', items: { type: 'string' } } },
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
}],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const ignoreBases = context.options[0]?.ignoreBases ?? [];
|
|
22
|
+
return {
|
|
23
|
+
'ClassDeclaration[superClass]'(node) {
|
|
24
|
+
const base = context.sourceCode.getText(node.superClass).replace(/<.*$/, '');
|
|
25
|
+
if (ignoreBases.includes(base) || (node.abstract ?? false))
|
|
26
|
+
return;
|
|
27
|
+
const hasConstructor = node.body.body.some((member) => member.type === 'MethodDefinition' && member.kind === 'constructor');
|
|
28
|
+
if (!hasConstructor)
|
|
29
|
+
context.report({ node: node.id ?? node, messageId: 'missing', data: { name: node.id?.name ?? 'class', base } });
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=subclass-declares-constructor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subclass-declares-constructor.js","sourceRoot":"","sources":["../../src/rules/subclass-declares-constructor.ts"],"names":[],"mappings":";;AAAA,sCAAyC;AAEzC;;;;GAIG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,sEAAsE,EAAE;QAC7F,QAAQ,EAAE,EAAE,OAAO,EAAE,6HAA6H,EAAE;QACpJ,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;gBACzE,oBAAoB,EAAE,KAAK;aAC5B,CAAC;KACH;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,WAAW,GAAa,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,EAAE,CAAC;QACpE,OAAO;YACL,8BAA8B,CAAC,IAAI;gBACjC,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC7E,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;oBAAE,OAAO;gBACnE,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,kBAAkB,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;gBAC5H,IAAI,CAAC,cAAc;oBAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,IAAI,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YACvI,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class names are the keys of the generated wiring and of the HTTP route registry, so they are unique
|
|
3
|
+
* per repository (ubiquitous language). Reads every class declared under `src/modules/**` and
|
|
4
|
+
* `src/common/**` once per lint run.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
5
|
+
const ast_1 = require("../lib/ast");
|
|
6
|
+
const rule_1 = require("../lib/rule");
|
|
7
|
+
const wiring_1 = require("../lib/wiring");
|
|
8
|
+
/**
|
|
9
|
+
* Class names are the keys of the generated wiring and of the HTTP route registry, so they are unique
|
|
10
|
+
* per repository (ubiquitous language). Reads every class declared under `src/modules/**` and
|
|
11
|
+
* `src/common/**` once per lint run.
|
|
12
|
+
*/
|
|
13
|
+
exports.default = (0, rule_1.defineRule)({
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: { description: 'Class names are unique across the repository' },
|
|
17
|
+
messages: { duplicate: 'Class "{{name}}" is also declared in {{other}}; class names must be unique per repository.' },
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const src = (0, wiring_1.srcRoot)(context);
|
|
22
|
+
if (!src)
|
|
23
|
+
return {};
|
|
24
|
+
const file = (0, ast_1.filePath)(context);
|
|
25
|
+
return {
|
|
26
|
+
ClassDeclaration(node) {
|
|
27
|
+
if (!node.id)
|
|
28
|
+
return;
|
|
29
|
+
const other = (0, wiring_1.scanClasses)(src).find((cls) => cls.name === node.id.name && cls.file !== file);
|
|
30
|
+
if (other)
|
|
31
|
+
context.report({ node: node.id, messageId: 'duplicate', data: { name: node.id.name, other: node_path_1.default.relative(src, other.file) } });
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
//# sourceMappingURL=unique-class-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unique-class-names.js","sourceRoot":"","sources":["../../src/rules/unique-class-names.ts"],"names":[],"mappings":";;;AAAA,kEAA6B;AAC7B,oCAAsC;AACtC,sCAAyC;AACzC,0CAAqD;AAErD;;;;GAIG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,EAAE,WAAW,EAAE,8CAA8C,EAAE;QACrE,QAAQ,EAAE,EAAE,SAAS,EAAE,4FAA4F,EAAE;QACrH,MAAM,EAAE,EAAE;KACX;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,GAAG,GAAG,IAAA,gBAAO,EAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,IAAA,cAAQ,EAAC,OAAO,CAAC,CAAC;QAC/B,OAAO;YACL,gBAAgB,CAAC,IAAI;gBACnB,IAAI,CAAC,IAAI,CAAC,EAAE;oBAAE,OAAO;gBACrB,MAAM,KAAK,GAAG,IAAA,oBAAW,EAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;gBAC7F,IAAI,KAAK;oBAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5I,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pjts/eslint-style-rules",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Piotr Józefów DDD utility package — ESLint rules and flat-config preset enforcing the pjts DDD layout and conventions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|