@pjts/eslint-style-rules 0.0.0-stage → 0.0.3
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 +138 -2
- package/dist/index.d.ts +38 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/ast.d.ts +27 -0
- package/dist/lib/ast.js +60 -0
- package/dist/lib/ast.js.map +1 -0
- package/dist/lib/db.d.ts +13 -0
- package/dist/lib/db.js +50 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/messages.d.ts +13 -0
- package/dist/lib/messages.js +76 -0
- package/dist/lib/messages.js.map +1 -0
- package/dist/lib/rule.d.ts +9 -0
- package/dist/lib/rule.js +10 -0
- package/dist/lib/rule.js.map +1 -0
- package/dist/rules/app-class-suffix.d.ts +3 -0
- package/dist/rules/app-class-suffix.js +28 -0
- package/dist/rules/app-class-suffix.js.map +1 -0
- package/dist/rules/app-service-endpoints.d.ts +12 -0
- package/dist/rules/app-service-endpoints.js +79 -0
- package/dist/rules/app-service-endpoints.js.map +1 -0
- package/dist/rules/auth-access.d.ts +14 -0
- package/dist/rules/auth-access.js +122 -0
- package/dist/rules/auth-access.js.map +1 -0
- package/dist/rules/db-client-dependency.d.ts +11 -0
- package/dist/rules/db-client-dependency.js +74 -0
- package/dist/rules/db-client-dependency.js.map +1 -0
- package/dist/rules/db-writes-in-repositories.d.ts +12 -0
- package/dist/rules/db-writes-in-repositories.js +105 -0
- package/dist/rules/db-writes-in-repositories.js.map +1 -0
- package/dist/rules/decorated-class-contract.d.ts +6 -0
- package/dist/rules/decorated-class-contract.js +37 -0
- package/dist/rules/decorated-class-contract.js.map +1 -0
- package/dist/rules/entity-constructor-data.d.ts +6 -0
- package/dist/rules/entity-constructor-data.js +35 -0
- package/dist/rules/entity-constructor-data.js.map +1 -0
- package/dist/rules/event-sourced-aggregate.d.ts +15 -0
- package/dist/rules/event-sourced-aggregate.js +134 -0
- package/dist/rules/event-sourced-aggregate.js.map +1 -0
- package/dist/rules/find-one-naming.d.ts +7 -0
- package/dist/rules/find-one-naming.js +38 -0
- package/dist/rules/find-one-naming.js.map +1 -0
- package/dist/rules/import-only-in.d.ts +7 -0
- package/dist/rules/import-only-in.js +52 -0
- package/dist/rules/import-only-in.js.map +1 -0
- package/dist/rules/knex-first.d.ts +3 -0
- package/dist/rules/knex-first.js +28 -0
- package/dist/rules/knex-first.js.map +1 -0
- package/dist/rules/message-file.d.ts +18 -0
- package/dist/rules/message-file.js +118 -0
- package/dist/rules/message-file.js.map +1 -0
- package/dist/rules/message-handler.d.ts +19 -0
- package/dist/rules/message-handler.js +114 -0
- package/dist/rules/message-handler.js.map +1 -0
- package/dist/rules/message-name-reference.d.ts +7 -0
- package/dist/rules/message-name-reference.js +52 -0
- package/dist/rules/message-name-reference.js.map +1 -0
- package/dist/rules/message-names.d.ts +7 -0
- package/dist/rules/message-names.js +80 -0
- package/dist/rules/message-names.js.map +1 -0
- package/dist/rules/module-layout.d.ts +9 -0
- package/dist/rules/module-layout.js +71 -0
- package/dist/rules/module-layout.js.map +1 -0
- package/dist/rules/module-service-getters.d.ts +19 -0
- package/dist/rules/module-service-getters.js +132 -0
- package/dist/rules/module-service-getters.js.map +1 -0
- package/dist/rules/no-async-in-domain.d.ts +2 -0
- package/dist/rules/no-async-in-domain.js +116 -0
- package/dist/rules/no-async-in-domain.js.map +1 -0
- package/dist/rules/no-duplicate-sdk-types.d.ts +6 -0
- package/dist/rules/no-duplicate-sdk-types.js +40 -0
- package/dist/rules/no-duplicate-sdk-types.js.map +1 -0
- package/dist/rules/no-entity-assembly-in-app.d.ts +7 -0
- package/dist/rules/no-entity-assembly-in-app.js +37 -0
- package/dist/rules/no-entity-assembly-in-app.js.map +1 -0
- package/dist/rules/no-entity-mutation-outside-domain.d.ts +11 -0
- package/dist/rules/no-entity-mutation-outside-domain.js +68 -0
- package/dist/rules/no-entity-mutation-outside-domain.js.map +1 -0
- package/dist/rules/no-message-construction-outside-domain.d.ts +15 -0
- package/dist/rules/no-message-construction-outside-domain.js +45 -0
- package/dist/rules/no-message-construction-outside-domain.js.map +1 -0
- package/dist/rules/no-reconstitute.d.ts +3 -0
- package/dist/rules/no-reconstitute.js +22 -0
- package/dist/rules/no-reconstitute.js.map +1 -0
- package/dist/rules/port-file.d.ts +9 -0
- package/dist/rules/port-file.js +94 -0
- package/dist/rules/port-file.js.map +1 -0
- package/dist/rules/port-has-adapters.d.ts +9 -0
- package/dist/rules/port-has-adapters.js +62 -0
- package/dist/rules/port-has-adapters.js.map +1 -0
- package/dist/rules/repository-naming.d.ts +19 -0
- package/dist/rules/repository-naming.js +122 -0
- package/dist/rules/repository-naming.js.map +1 -0
- package/dist/rules/repository-serialization.d.ts +6 -0
- package/dist/rules/repository-serialization.js +50 -0
- package/dist/rules/repository-serialization.js.map +1 -0
- package/dist/rules/return-entities.d.ts +6 -0
- package/dist/rules/return-entities.js +38 -0
- package/dist/rules/return-entities.js.map +1 -0
- package/dist/rules/save-contract.d.ts +7 -0
- package/dist/rules/save-contract.js +41 -0
- package/dist/rules/save-contract.js.map +1 -0
- package/dist/rules/test-location.d.ts +14 -0
- package/dist/rules/test-location.js +94 -0
- package/dist/rules/test-location.js.map +1 -0
- package/dist/rules/throw-app-error.d.ts +12 -0
- package/dist/rules/throw-app-error.js +68 -0
- package/dist/rules/throw-app-error.js.map +1 -0
- package/dist/rules/trackable-repository.d.ts +10 -0
- package/dist/rules/trackable-repository.js +97 -0
- package/dist/rules/trackable-repository.js.map +1 -0
- package/package.json +46 -4
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
const ast_1 = require("../lib/ast");
|
|
5
|
+
const db_1 = require("../lib/db");
|
|
6
|
+
const messages_1 = require("../lib/messages");
|
|
7
|
+
/**
|
|
8
|
+
* A message consumer is an application-layer class:
|
|
9
|
+
*
|
|
10
|
+
* export class SlotCacheInvalidationHandler extends MessageHandlerBase {
|
|
11
|
+
* constructor(private readonly cache: SlotCachePort) {
|
|
12
|
+
* super([MessageNames.CalendarBooked]);
|
|
13
|
+
* }
|
|
14
|
+
* protected async handleMessage(event: CalendarBooked): Promise<void> { … }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* - named `…Handler`, in one of `paths` (default `/app/`);
|
|
18
|
+
* - subscribes with `super([MessageNames.X, …])`; when `handleMessage` switches on the name, every
|
|
19
|
+
* subscribed name has a `case` and every `case` is subscribed;
|
|
20
|
+
* - `handleMessage` is protected and its parameter typed with the message (or a union of them);
|
|
21
|
+
* - depends only on ports (`…Port`) or application classes imported from the app layer — the
|
|
22
|
+
* infrastructure is wired in by the module's `…MessageHandlers` composition root.
|
|
23
|
+
*/
|
|
24
|
+
exports.default = (0, rule_1.defineRule)({
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: { description: 'Enforce the shape and dependencies of message handlers' },
|
|
28
|
+
messages: {
|
|
29
|
+
suffix: 'Message handler "{{name}}" must end with "Handler".',
|
|
30
|
+
location: 'Message handler "{{name}}" belongs in {{paths}}.',
|
|
31
|
+
subscription: '"{{name}}" must subscribe with `super([{{enumName}}.…])`.',
|
|
32
|
+
unhandled: '"{{name}}" subscribes to {{enumName}}.{{message}} but has no `case` for it in handleMessage.',
|
|
33
|
+
notSubscribed: '`case {{enumName}}.{{message}}` is never delivered — add it to super([...]) or remove the case.',
|
|
34
|
+
handleMessage: '"{{name}}" must implement `protected async handleMessage(message: <Message type>)`.',
|
|
35
|
+
untyped: 'Type the handleMessage parameter with the message it handles (or a union of them).',
|
|
36
|
+
dependency: '"{{name}}" may depend on ports (…Port) or application classes only, not "{{type}}" — wire infrastructure in the …MessageHandlers composition root.',
|
|
37
|
+
},
|
|
38
|
+
schema: [{
|
|
39
|
+
type: 'object',
|
|
40
|
+
properties: {
|
|
41
|
+
bases: { type: 'array', items: { type: 'string' } },
|
|
42
|
+
paths: { type: 'array', items: { type: 'string' } },
|
|
43
|
+
enumName: { type: 'string' },
|
|
44
|
+
dependencyPattern: { type: 'string' },
|
|
45
|
+
allowedTypes: { type: 'array', items: { type: 'string' } },
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
}],
|
|
49
|
+
},
|
|
50
|
+
create(context) {
|
|
51
|
+
const { bases = ['MessageHandlerBase'], paths = ['/app/'], enumName = 'MessageNames', dependencyPattern = 'Port$', allowedTypes = [] } = context.options[0] ?? {};
|
|
52
|
+
const file = (0, ast_1.filePath)(context);
|
|
53
|
+
/** local name → import source */
|
|
54
|
+
const imports = new Map();
|
|
55
|
+
/** Relative imports inside the app layer, or any path through an `/app/` folder (`@modules/x/app/Y`). */
|
|
56
|
+
const isAppImport = (source) => !!source && !/infrastructure|\/domain(\/|$)/.test(source) && (source.startsWith('.') || /\/app(\/|$)/.test(source));
|
|
57
|
+
return {
|
|
58
|
+
ImportDeclaration(node) {
|
|
59
|
+
for (const specifier of node.specifiers)
|
|
60
|
+
imports.set(specifier.local.name, node.source.value);
|
|
61
|
+
},
|
|
62
|
+
ClassDeclaration(node) {
|
|
63
|
+
if (!bases.includes(node.superClass?.name))
|
|
64
|
+
return;
|
|
65
|
+
const name = node.id?.name ?? '(anonymous)';
|
|
66
|
+
const at = node.id ?? node;
|
|
67
|
+
if (!name.endsWith('Handler'))
|
|
68
|
+
context.report({ node: at, messageId: 'suffix', data: { name } });
|
|
69
|
+
if (!paths.some((fragment) => file.includes(fragment)))
|
|
70
|
+
context.report({ node: at, messageId: 'location', data: { name, paths: paths.join(' or ') } });
|
|
71
|
+
const members = node.body.body.filter((member) => member.type === 'MethodDefinition');
|
|
72
|
+
const constructor = members.find((member) => member.kind === 'constructor');
|
|
73
|
+
const superCall = constructor?.value.body?.body
|
|
74
|
+
.map((statement) => statement.expression)
|
|
75
|
+
.find((expression) => expression?.type === 'CallExpression' && expression.callee.type === 'Super');
|
|
76
|
+
const list = superCall?.arguments[0];
|
|
77
|
+
const subscribed = list?.type === 'ArrayExpression' ? list.elements.map((element) => (0, messages_1.messageNameOf)(element, enumName)) : [];
|
|
78
|
+
if (!list || list.type !== 'ArrayExpression' || subscribed.length === 0 || subscribed.some((n) => !n)) {
|
|
79
|
+
context.report({ node: superCall ?? constructor?.key ?? at, messageId: 'subscription', data: { name, enumName } });
|
|
80
|
+
}
|
|
81
|
+
const handle = members.find((member) => (member.key.name ?? member.key.value) === 'handleMessage');
|
|
82
|
+
if (!handle || handle.accessibility !== 'protected') {
|
|
83
|
+
context.report({ node: handle?.key ?? at, messageId: 'handleMessage', data: { name } });
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const param = handle.value.params[0] && (0, db_1.paramBinding)(handle.value.params[0]);
|
|
87
|
+
if (!param?.typeAnnotation)
|
|
88
|
+
context.report({ node: param ?? handle.key, messageId: 'untyped' });
|
|
89
|
+
const handled = (0, messages_1.switchCaseNames)(handle.value.body, enumName);
|
|
90
|
+
if (handled.size > 0) {
|
|
91
|
+
for (const message of subscribed.filter(Boolean)) {
|
|
92
|
+
if (!handled.has(message))
|
|
93
|
+
context.report({ node: list, messageId: 'unhandled', data: { name, enumName, message } });
|
|
94
|
+
}
|
|
95
|
+
for (const message of handled) {
|
|
96
|
+
if (!subscribed.includes(message))
|
|
97
|
+
context.report({ node: handle.key, messageId: 'notSubscribed', data: { enumName, message } });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const param of constructor?.value.params ?? []) {
|
|
102
|
+
const type = (0, db_1.paramBinding)(param).typeAnnotation?.typeAnnotation;
|
|
103
|
+
const typeName = type?.type === 'TSTypeReference' && type.typeName.type === 'Identifier' ? type.typeName.name : null;
|
|
104
|
+
if (!typeName)
|
|
105
|
+
continue;
|
|
106
|
+
if (new RegExp(dependencyPattern).test(typeName) || allowedTypes.includes(typeName) || isAppImport(imports.get(typeName)))
|
|
107
|
+
continue;
|
|
108
|
+
context.report({ node: param, messageId: 'dependency', data: { name, type: typeName } });
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=message-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-handler.js","sourceRoot":"","sources":["../../src/rules/message-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message names are referenced through the enum (`MessageNames.CalendarBooked`), never as string
|
|
3
|
+
* literals: in a `name:` property, a `case` label of a `switch (x.name)`, or the names a `MessageHandlerBase` subclass
|
|
4
|
+
* passes to `super([...])`. A literal skips the compiler's check that the message exists.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
/**
|
|
5
|
+
* Message names are referenced through the enum (`MessageNames.CalendarBooked`), never as string
|
|
6
|
+
* literals: in a `name:` property, a `case` label of a `switch (x.name)`, or the names a `MessageHandlerBase` subclass
|
|
7
|
+
* passes to `super([...])`. A literal skips the compiler's check that the message exists.
|
|
8
|
+
*/
|
|
9
|
+
exports.default = (0, rule_1.defineRule)({
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'problem',
|
|
12
|
+
docs: { description: 'Reference message names through the MessageNames enum' },
|
|
13
|
+
messages: { literal: 'Use {{enumName}}.{{suggestion}} instead of the string \'{{value}}\'.' },
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: { enumName: { type: 'string' }, pattern: { type: 'string' }, handlerBases: { type: 'array', items: { type: 'string' } } },
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
}],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const { enumName = 'MessageNames', pattern = '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$', handlerBases = ['MessageHandlerBase'] } = context.options[0] ?? {};
|
|
22
|
+
const isName = (node) => node?.type === 'Literal' && typeof node.value === 'string' && new RegExp(pattern).test(node.value);
|
|
23
|
+
const pascal = (value) => value.toLowerCase().replace(/(^|_)([a-z0-9])/g, (_m, _s, c) => c.toUpperCase());
|
|
24
|
+
const report = (node) => context.report({ node, messageId: 'literal', data: { enumName, value: node.value, suggestion: pascal(node.value) } });
|
|
25
|
+
return {
|
|
26
|
+
'Property[computed=false]'(node) {
|
|
27
|
+
if ((node.key.name ?? node.key.value) === 'name' && isName(node.value))
|
|
28
|
+
report(node.value);
|
|
29
|
+
},
|
|
30
|
+
'TSPropertySignature'(node) {
|
|
31
|
+
// `name: 'CALENDAR_BOOKED'` as a type in a message interface.
|
|
32
|
+
const type = node.typeAnnotation?.typeAnnotation;
|
|
33
|
+
if ((node.key.name ?? node.key.value) === 'name' && type?.type === 'TSLiteralType' && isName(type.literal))
|
|
34
|
+
report(type.literal);
|
|
35
|
+
},
|
|
36
|
+
// Only switches on a message's name — `switch (status) { case 'PART_CAPTURED': … }` is someone else's enum.
|
|
37
|
+
'SwitchStatement[discriminant.type="MemberExpression"][discriminant.property.name="name"] > SwitchCase'(node) {
|
|
38
|
+
if (isName(node.test))
|
|
39
|
+
report(node.test);
|
|
40
|
+
},
|
|
41
|
+
'CallExpression[callee.type="Super"]'(node) {
|
|
42
|
+
const owner = context.sourceCode.getAncestors(node).reverse().find((a) => a.type === 'ClassDeclaration' || a.type === 'ClassExpression');
|
|
43
|
+
if (!handlerBases.includes(owner?.superClass?.name))
|
|
44
|
+
return;
|
|
45
|
+
const list = node.arguments[0];
|
|
46
|
+
if (list?.type === 'ArrayExpression')
|
|
47
|
+
list.elements.filter(isName).forEach(report);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=message-name-reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-name-reference.js","sourceRoot":"","sources":["../../src/rules/message-name-reference.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The messages barrel (`src/common/messages/index.ts`) keeps three things in step with the message
|
|
3
|
+
* files next to it: every `MessageNames` key has a `<Key>.ts`, every message file has a key and a
|
|
4
|
+
* re-export, and every value is the key in SCREAMING_SNAKE_CASE (`CalendarBooked = 'CALENDAR_BOOKED'`).
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
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"));
|
|
6
|
+
const rule_1 = require("../lib/rule");
|
|
7
|
+
const messages_1 = require("../lib/messages");
|
|
8
|
+
/**
|
|
9
|
+
* The messages barrel (`src/common/messages/index.ts`) keeps three things in step with the message
|
|
10
|
+
* files next to it: every `MessageNames` key has a `<Key>.ts`, every message file has a key and a
|
|
11
|
+
* re-export, and every value is the key in SCREAMING_SNAKE_CASE (`CalendarBooked = 'CALENDAR_BOOKED'`).
|
|
12
|
+
*/
|
|
13
|
+
exports.default = (0, rule_1.defineRule)({
|
|
14
|
+
meta: {
|
|
15
|
+
type: 'problem',
|
|
16
|
+
docs: { description: 'Keep MessageNames, message files and re-exports in step' },
|
|
17
|
+
messages: {
|
|
18
|
+
noEnum: 'The messages index must declare `export enum {{enumName}}`.',
|
|
19
|
+
noFile: '{{enumName}}.{{key}} has no message file "{{key}}.ts".',
|
|
20
|
+
value: '{{enumName}}.{{key}} must be \'{{expected}}\'.',
|
|
21
|
+
notInEnum: 'Message file "{{file}}.ts" has no {{enumName}}.{{file}} member.',
|
|
22
|
+
notExported: 'Message "{{file}}" must be re-exported here (`export { {{file}} } from \'./{{file}}\'`).',
|
|
23
|
+
},
|
|
24
|
+
schema: [{
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: { enumName: { type: 'string' }, basePattern: { type: 'string' } },
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
}],
|
|
29
|
+
},
|
|
30
|
+
create(context) {
|
|
31
|
+
const { enumName = 'MessageNames', basePattern = 'Base$' } = context.options[0] ?? {};
|
|
32
|
+
if (path_1.default.basename(context.filename) !== 'index.ts')
|
|
33
|
+
return {};
|
|
34
|
+
const dir = path_1.default.dirname(context.filename);
|
|
35
|
+
return {
|
|
36
|
+
Program(program) {
|
|
37
|
+
const files = fs_1.default.existsSync(dir)
|
|
38
|
+
? fs_1.default.readdirSync(dir)
|
|
39
|
+
.filter((file) => /\.ts$/.test(file) && !/\.(d|test)\.ts$/.test(file) && file !== 'index.ts')
|
|
40
|
+
.map((file) => file.replace(/\.ts$/, ''))
|
|
41
|
+
.filter((name) => !new RegExp(basePattern).test(name))
|
|
42
|
+
: [];
|
|
43
|
+
const enumNode = program.body
|
|
44
|
+
.map((statement) => (statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement))
|
|
45
|
+
.find((declaration) => declaration?.type === 'TSEnumDeclaration' && declaration.id.name === enumName);
|
|
46
|
+
if (!enumNode)
|
|
47
|
+
return context.report({ node: program, loc: { line: 1, column: 0 }, messageId: 'noEnum', data: { enumName } });
|
|
48
|
+
const members = enumNode.body?.members ?? enumNode.members;
|
|
49
|
+
const keys = new Set();
|
|
50
|
+
for (const member of members) {
|
|
51
|
+
const key = member.id.name ?? member.id.value;
|
|
52
|
+
keys.add(key);
|
|
53
|
+
if (!files.includes(key))
|
|
54
|
+
context.report({ node: member.id, messageId: 'noFile', data: { enumName, key } });
|
|
55
|
+
const expected = (0, messages_1.screamingSnake)(key);
|
|
56
|
+
if (member.initializer?.type !== 'Literal' || member.initializer.value !== expected) {
|
|
57
|
+
context.report({ node: member.initializer ?? member, messageId: 'value', data: { enumName, key, expected } });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const reExported = new Set();
|
|
61
|
+
let exportsAll = false;
|
|
62
|
+
for (const statement of program.body) {
|
|
63
|
+
if (statement.type === 'ExportAllDeclaration')
|
|
64
|
+
exportsAll = true;
|
|
65
|
+
if (statement.type === 'ExportNamedDeclaration' && statement.source) {
|
|
66
|
+
for (const specifier of statement.specifiers)
|
|
67
|
+
reExported.add(specifier.exported.name ?? specifier.exported.value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
if (!keys.has(file))
|
|
72
|
+
context.report({ node: enumNode.id, messageId: 'notInEnum', data: { file, enumName } });
|
|
73
|
+
if (!exportsAll && !reExported.has(file))
|
|
74
|
+
context.report({ node: enumNode.id, messageId: 'notExported', data: { file } });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=message-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-names.js","sourceRoot":"","sources":["../../src/rules/message-names.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every file of `src/modules/<module>/` sits in the agreed folder layout:
|
|
3
|
+
* <module>/<Module>Module.ts, <module>/index.ts (+ `rootFiles`, e.g. <Module>MessageHandlers.ts)
|
|
4
|
+
* <module>/{app,domain,infrastructure,testing}/…
|
|
5
|
+
* <module>/app/{dto,ports}/… (app-layer classes sit directly in app/; tests next to them)
|
|
6
|
+
* The `<Module>Module.ts` file additionally checks that the required entries exist on disk.
|
|
7
|
+
*/
|
|
8
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
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
|
+
const ast_1 = require("../lib/ast");
|
|
7
|
+
const rule_1 = require("../lib/rule");
|
|
8
|
+
/**
|
|
9
|
+
* Every file of `src/modules/<module>/` sits in the agreed folder layout:
|
|
10
|
+
* <module>/<Module>Module.ts, <module>/index.ts (+ `rootFiles`, e.g. <Module>MessageHandlers.ts)
|
|
11
|
+
* <module>/{app,domain,infrastructure,testing}/…
|
|
12
|
+
* <module>/app/{dto,ports}/… (app-layer classes sit directly in app/; tests next to them)
|
|
13
|
+
* The `<Module>Module.ts` file additionally checks that the required entries exist on disk.
|
|
14
|
+
*/
|
|
15
|
+
exports.default = (0, rule_1.defineRule)({
|
|
16
|
+
meta: {
|
|
17
|
+
type: 'suggestion',
|
|
18
|
+
docs: { description: 'Enforce the DDD module folder layout' },
|
|
19
|
+
messages: {
|
|
20
|
+
rootFile: '"{{file}}" must not sit in the module root — only {{allowed}} live there.',
|
|
21
|
+
folder: 'Folder "{{folder}}/" is not part of the module layout ({{allowed}}).',
|
|
22
|
+
subfolder: 'Folder "{{layer}}/{{folder}}/" is not allowed ({{allowed}}).',
|
|
23
|
+
missing: 'Module "{{module}}" is missing "{{entry}}".',
|
|
24
|
+
},
|
|
25
|
+
schema: [{
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
folders: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
subfolders: { type: 'object', additionalProperties: { type: 'array', items: { type: 'string' } } },
|
|
30
|
+
required: { type: 'array', items: { type: 'string' } },
|
|
31
|
+
rootFiles: { type: 'array', items: { type: 'string' } },
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: false,
|
|
34
|
+
}],
|
|
35
|
+
},
|
|
36
|
+
create(context) {
|
|
37
|
+
const location = (0, ast_1.moduleLocation)(context);
|
|
38
|
+
if (!location)
|
|
39
|
+
return {};
|
|
40
|
+
const { folders = ['app', 'domain', 'infrastructure', 'testing'], subfolders = { app: ['dto', 'ports'] }, required = ['app', 'index.ts'],
|
|
41
|
+
// `{Module}` → the module folder in PascalCase (`catalog-search` → `CatalogSearch`).
|
|
42
|
+
rootFiles = ['{Module}Module.ts', 'index.ts'], } = context.options[0] ?? {};
|
|
43
|
+
const { module, rest, root } = location;
|
|
44
|
+
const moduleFile = `${(0, ast_1.pascalCase)(module)}Module.ts`;
|
|
45
|
+
return {
|
|
46
|
+
Program(node) {
|
|
47
|
+
const report = (messageId, data) => context.report({ node, loc: { line: 1, column: 0 }, messageId, data });
|
|
48
|
+
if (rest.length === 1) {
|
|
49
|
+
const allowedRoot = rootFiles.map((pattern) => pattern.split('{Module}').join((0, ast_1.pascalCase)(module)));
|
|
50
|
+
if (rest[0] === moduleFile) {
|
|
51
|
+
for (const entry of required) {
|
|
52
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(root, entry)))
|
|
53
|
+
report('missing', { module, entry });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (!allowedRoot.includes(rest[0])) {
|
|
57
|
+
report('rootFile', { file: rest[0], allowed: allowedRoot.join(', ') });
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const [layer, folder] = rest;
|
|
62
|
+
if (!folders.includes(layer))
|
|
63
|
+
return report('folder', { folder: layer, allowed: folders.join(', ') });
|
|
64
|
+
if (rest.length > 2 && subfolders[layer] && !subfolders[layer].includes(folder)) {
|
|
65
|
+
report('subfolder', { layer, folder, allowed: subfolders[layer].join(', ') });
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=module-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-layout.js","sourceRoot":"","sources":["../../src/rules/module-layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
19
|
+
export default _default;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
const ast_1 = require("../lib/ast");
|
|
5
|
+
/**
|
|
6
|
+
* A module class exposes application-layer classes — and only those — through public getters named
|
|
7
|
+
* after them in camelCase:
|
|
8
|
+
*
|
|
9
|
+
* import { ServiceOfferPublicView } from './app/ServiceOfferPublicView';
|
|
10
|
+
*
|
|
11
|
+
* public get serviceOfferPublicView() {
|
|
12
|
+
* return new ServiceOfferPublicView(this.serviceOfferRepo);
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Two directions are checked:
|
|
16
|
+
* - every public getter returns `new X(...)` where X is a value imported from the module's own
|
|
17
|
+
* `./app/…` (not `./app/dto` or `./app/ports`), and is named X with a lower-case first letter;
|
|
18
|
+
* - every `new X(...)` of an app use-case class (suffix `Service` | `View` | `Handler`) sits in a public,
|
|
19
|
+
* non-static getter. Internal app helpers (notifiers, runners) may be built in the constructor for
|
|
20
|
+
* wiring — they just can't be exposed.
|
|
21
|
+
*/
|
|
22
|
+
exports.default = (0, rule_1.defineRule)({
|
|
23
|
+
meta: {
|
|
24
|
+
type: 'suggestion',
|
|
25
|
+
docs: { description: 'Modules expose only their own app-layer classes, through camelCase getters' },
|
|
26
|
+
messages: {
|
|
27
|
+
notGetter: '"{{name}}" must be exposed through `public get {{getter}}()`, not constructed in {{where}}.',
|
|
28
|
+
name: 'Getter returning {{name}} must be named `{{getter}}`, not `{{actual}}`.',
|
|
29
|
+
visibility: 'Getter `{{getter}}` must be public and non-static.',
|
|
30
|
+
notApp: 'Module getter `{{getter}}` may only return `new <AppClass>(...)` of a class imported from ./app/ — {{what}}.',
|
|
31
|
+
},
|
|
32
|
+
schema: [{
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: { suffixes: { type: 'array', items: { type: 'string' }, minItems: 1 } },
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
}],
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const suffixes = context.options[0]?.suffixes ?? ['Service', 'View', 'Handler'];
|
|
40
|
+
/** local name → import source, for value imports of the file. */
|
|
41
|
+
const imports = new Map();
|
|
42
|
+
const isAppSource = (source) => /^\.\/app(\/|$)/.test(source) && !/^\.\/app\/(dto|ports)(\/|$)/.test(source);
|
|
43
|
+
const isAppClass = (name) => isAppSource(imports.get(name) ?? '');
|
|
44
|
+
const camel = (name) => name[0].toLowerCase() + name.slice(1);
|
|
45
|
+
const isModuleClass = (classNode) => /Module$/.test(classNode?.id?.name ?? '');
|
|
46
|
+
const describe = (member) => {
|
|
47
|
+
if (member.type === 'MethodDefinition')
|
|
48
|
+
return member.kind === 'constructor' ? 'the constructor' : `method \`${member.key.name}\``;
|
|
49
|
+
return `field \`${member.key.name}\``;
|
|
50
|
+
};
|
|
51
|
+
/** What a getter returns, in words, when it isn't an app class. */
|
|
52
|
+
const describeReturn = (argument) => {
|
|
53
|
+
if (!argument)
|
|
54
|
+
return 'it returns nothing';
|
|
55
|
+
if (argument.type === 'NewExpression' && argument.callee.type === 'Identifier') {
|
|
56
|
+
const source = imports.get(argument.callee.name);
|
|
57
|
+
return source ? `${argument.callee.name} comes from '${source}'` : `${argument.callee.name} is not imported from ./app/`;
|
|
58
|
+
}
|
|
59
|
+
return `it returns ${context.sourceCode.getText(argument).slice(0, 40)}`;
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
ImportDeclaration(node) {
|
|
63
|
+
if (node.importKind === 'type')
|
|
64
|
+
return;
|
|
65
|
+
for (const specifier of node.specifiers) {
|
|
66
|
+
if (specifier.importKind !== 'type')
|
|
67
|
+
imports.set(specifier.local.name, node.source.value);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
// Getters: only app classes, under the right name.
|
|
71
|
+
'MethodDefinition[kind="get"]'(getterNode) {
|
|
72
|
+
if (!isModuleClass(getterNode.parent.parent))
|
|
73
|
+
return;
|
|
74
|
+
if (getterNode.static || getterNode.accessibility === 'private' || getterNode.accessibility === 'protected')
|
|
75
|
+
return;
|
|
76
|
+
const getter = getterNode.key.name ?? getterNode.key.value;
|
|
77
|
+
const returns = [];
|
|
78
|
+
const collect = (node) => {
|
|
79
|
+
if (!node || typeof node.type !== 'string')
|
|
80
|
+
return;
|
|
81
|
+
if (node.type === 'ReturnStatement') {
|
|
82
|
+
returns.push(node);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Returns inside nested functions belong to those functions.
|
|
86
|
+
if (/Function/.test(node.type))
|
|
87
|
+
return;
|
|
88
|
+
for (const key of Object.keys(node)) {
|
|
89
|
+
if (key === 'parent')
|
|
90
|
+
continue;
|
|
91
|
+
const value = node[key];
|
|
92
|
+
if (Array.isArray(value))
|
|
93
|
+
value.forEach(collect);
|
|
94
|
+
else if (value && typeof value === 'object')
|
|
95
|
+
collect(value);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
getterNode.value.body.body.forEach(collect);
|
|
99
|
+
if (returns.length === 0) {
|
|
100
|
+
context.report({ node: getterNode.key, messageId: 'notApp', data: { getter, what: 'it returns nothing' } });
|
|
101
|
+
}
|
|
102
|
+
for (const statement of returns) {
|
|
103
|
+
const argument = statement.argument;
|
|
104
|
+
const name = argument?.type === 'NewExpression' && argument.callee.type === 'Identifier' ? argument.callee.name : null;
|
|
105
|
+
if (!name || !isAppClass(name)) {
|
|
106
|
+
context.report({ node: statement, messageId: 'notApp', data: { getter, what: describeReturn(argument) } });
|
|
107
|
+
}
|
|
108
|
+
else if (getter !== camel(name)) {
|
|
109
|
+
context.report({ node: getterNode.key, messageId: 'name', data: { name, getter: camel(name), actual: getter } });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
// App use-case classes: only constructed inside public getters.
|
|
114
|
+
'NewExpression[callee.type="Identifier"]'(node) {
|
|
115
|
+
const name = node.callee.name;
|
|
116
|
+
if (!isAppClass(name) || !suffixes.some((suffix) => name.endsWith(suffix)))
|
|
117
|
+
return;
|
|
118
|
+
const ancestors = context.sourceCode.getAncestors(node);
|
|
119
|
+
const member = (0, ast_1.findLast)(ancestors, (a) => a.type === 'MethodDefinition' || a.type === 'PropertyDefinition');
|
|
120
|
+
if (!member || !isModuleClass(member.parent.parent))
|
|
121
|
+
return;
|
|
122
|
+
if (member.type !== 'MethodDefinition' || member.kind !== 'get') {
|
|
123
|
+
context.report({ node, messageId: 'notGetter', data: { name, getter: camel(name), where: describe(member) } });
|
|
124
|
+
}
|
|
125
|
+
else if (member.static || member.accessibility === 'private' || member.accessibility === 'protected') {
|
|
126
|
+
context.report({ node: member.key, messageId: 'visibility', data: { getter: camel(name) } });
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=module-service-getters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-service-getters.js","sourceRoot":"","sources":["../../src/rules/module-service-getters.ts"],"names":[],"mappings":"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"}
|