@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 @@
|
|
|
1
|
+
{"version":3,"file":"db-writes-in-repositories.js","sourceRoot":"","sources":["../../src/rules/db-writes-in-repositories.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
* Classes carrying a configured decorator must implement the listed methods.
|
|
7
|
+
* Options: `{ contracts: { StringDomainPrimitive: ['toJSON', 'static fromUnknown'] } }`.
|
|
8
|
+
*/
|
|
9
|
+
exports.default = (0, rule_1.defineRule)({
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'problem',
|
|
12
|
+
docs: { description: 'Require methods on classes that carry a given decorator' },
|
|
13
|
+
messages: { missing: '@{{decorator}} class "{{name}}" must implement `{{method}}`.' },
|
|
14
|
+
schema: [{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: { contracts: { type: 'object', additionalProperties: { type: 'array', items: { type: 'string' } } } },
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
}],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const contracts = context.options[0]?.contracts ?? {};
|
|
22
|
+
return {
|
|
23
|
+
ClassDeclaration(node) {
|
|
24
|
+
for (const decorator of (0, ast_1.decoratorNames)(node)) {
|
|
25
|
+
for (const spec of contracts[decorator] ?? []) {
|
|
26
|
+
const isStatic = spec.startsWith('static ');
|
|
27
|
+
const method = isStatic ? spec.slice('static '.length) : spec;
|
|
28
|
+
if (!(0, ast_1.findMethod)(node, method, isStatic)) {
|
|
29
|
+
context.report({ node: node.id ?? node, messageId: 'missing', data: { decorator, name: node.id?.name, method: spec } });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=decorated-class-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorated-class-contract.js","sourceRoot":"","sources":["../../src/rules/decorated-class-contract.ts"],"names":[],"mappings":";;AAAA,oCAAwD;AACxD,sCAAyC;AAEzC;;;GAGG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,EAAE,WAAW,EAAE,yDAAyD,EAAE;QAChF,QAAQ,EAAE,EAAE,OAAO,EAAE,8DAA8D,EAAE;QACrF,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE;gBACjH,oBAAoB,EAAE,KAAK;aAC5B,CAAC;KACH;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;QACtD,OAAO;YACL,gBAAgB,CAAC,IAAI;gBACnB,KAAK,MAAM,SAAS,IAAI,IAAA,oBAAc,EAAC,IAAI,CAAC,EAAE,CAAC;oBAC7C,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;wBAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;wBAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;wBAC9D,IAAI,CAAC,IAAA,gBAAU,EAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;4BACxC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC1H,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* entity constructors take one `<Entity>Data` object, never positional parameters.
|
|
3
|
+
* Decorated classes (domain primitives) and `…Error` subclasses are not entities and are skipped.
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
* entity constructors take one `<Entity>Data` object, never positional parameters.
|
|
7
|
+
* Decorated classes (domain primitives) and `…Error` subclasses are not entities and are skipped.
|
|
8
|
+
*/
|
|
9
|
+
exports.default = (0, rule_1.defineRule)({
|
|
10
|
+
meta: {
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: { description: 'Require entity constructors to take a single `<Entity>Data` parameter' },
|
|
13
|
+
messages: { data: 'Constructor of "{{name}}" must take a single `data: {{name}}Data` parameter.' },
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
'ClassDeclaration > ClassBody > MethodDefinition[kind="constructor"]'(constructor) {
|
|
19
|
+
const classNode = constructor.parent.parent;
|
|
20
|
+
if (!classNode.id || (0, ast_1.decoratorNames)(classNode).length > 0)
|
|
21
|
+
return;
|
|
22
|
+
if (/Error$/.test(classNode.superClass?.name ?? ''))
|
|
23
|
+
return;
|
|
24
|
+
const params = constructor.value.params.map((param) => (param.type === 'TSParameterProperty' ? param.parameter : param));
|
|
25
|
+
if (params.length === 0)
|
|
26
|
+
return;
|
|
27
|
+
const name = classNode.id.name;
|
|
28
|
+
if (params.length === 1 && (0, ast_1.annotationTypeName)(params[0].typeAnnotation) === `${name}Data`)
|
|
29
|
+
return;
|
|
30
|
+
context.report({ node: constructor.key, messageId: 'data', data: { name } });
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=entity-constructor-data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-constructor-data.js","sourceRoot":"","sources":["../../src/rules/entity-constructor-data.ts"],"names":[],"mappings":";;AAAA,oCAAgE;AAChE,sCAAyC;AAEzC;;;GAGG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,uEAAuE,EAAE;QAC9F,QAAQ,EAAE,EAAE,IAAI,EAAE,8EAA8E,EAAE;QAClG,MAAM,EAAE,EAAE;KACX;IACD,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,qEAAqE,CAAC,WAAW;gBAC/E,MAAM,SAAS,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC;gBAC5C,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,IAAA,oBAAc,EAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO;gBAClE,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC;oBAAE,OAAO;gBAE5D,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,qBAAqB,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;gBACzH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO;gBAChC,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC;gBAC/B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,IAAA,wBAAkB,EAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,GAAG,IAAI,MAAM;oBAAE,OAAO;gBAClG,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAC/E,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-sourced aggregates (`extends EventSourceable<PaymentEvent>`, see altbra `sales/domain/Payment.ts`):
|
|
3
|
+
*
|
|
4
|
+
* - decorated `@trackable` — the repository persists changed properties and the emitted events;
|
|
5
|
+
* - typed with their event union (`EventSourceable<PaymentEvent>`), declared in the messages barrel;
|
|
6
|
+
* - `protected apply(event)` switches on `event.name`, with a `case` for every name the class emits —
|
|
7
|
+
* an emitted event nobody applies changes nothing in memory but is still persisted and published;
|
|
8
|
+
* - `apply` is replay-safe: it runs again for every stored event, so it must not emit, throw, read the
|
|
9
|
+
* clock (`new Date()`, `Date.now()`, `DateTime.now()`), mint ids (`*.create()`, `v4()`) or randomise —
|
|
10
|
+
* time comes from `event.timestamp`, values from the event;
|
|
11
|
+
* - events are emitted with `name: MessageNames.X`.
|
|
12
|
+
* Option `stateChangesInApply` (off by default): `this.x = …` only in the constructor and `apply`.
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
15
|
+
export default _default;
|
|
@@ -0,0 +1,134 @@
|
|
|
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 messages_1 = require("../lib/messages");
|
|
6
|
+
const NON_DETERMINISTIC_CALLS = [
|
|
7
|
+
{ object: 'Date', property: 'now' },
|
|
8
|
+
{ object: 'DateTime', property: 'now' },
|
|
9
|
+
{ object: 'Math', property: 'random' },
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Event-sourced aggregates (`extends EventSourceable<PaymentEvent>`, see altbra `sales/domain/Payment.ts`):
|
|
13
|
+
*
|
|
14
|
+
* - decorated `@trackable` — the repository persists changed properties and the emitted events;
|
|
15
|
+
* - typed with their event union (`EventSourceable<PaymentEvent>`), declared in the messages barrel;
|
|
16
|
+
* - `protected apply(event)` switches on `event.name`, with a `case` for every name the class emits —
|
|
17
|
+
* an emitted event nobody applies changes nothing in memory but is still persisted and published;
|
|
18
|
+
* - `apply` is replay-safe: it runs again for every stored event, so it must not emit, throw, read the
|
|
19
|
+
* clock (`new Date()`, `Date.now()`, `DateTime.now()`), mint ids (`*.create()`, `v4()`) or randomise —
|
|
20
|
+
* time comes from `event.timestamp`, values from the event;
|
|
21
|
+
* - events are emitted with `name: MessageNames.X`.
|
|
22
|
+
* Option `stateChangesInApply` (off by default): `this.x = …` only in the constructor and `apply`.
|
|
23
|
+
*/
|
|
24
|
+
exports.default = (0, rule_1.defineRule)({
|
|
25
|
+
meta: {
|
|
26
|
+
type: 'problem',
|
|
27
|
+
docs: { description: 'Enforce the event-sourced aggregate contract' },
|
|
28
|
+
messages: {
|
|
29
|
+
trackable: 'Event-sourced aggregate "{{name}}" must be decorated @{{decorator}}.',
|
|
30
|
+
eventType: '"{{name}}" must declare its event union: `extends {{base}}<{{name}}Event>`.',
|
|
31
|
+
apply: '"{{name}}" must implement `protected apply(event)` switching on `event.name`.',
|
|
32
|
+
notApplied: '"{{name}}" emits {{enumName}}.{{message}} but apply() has no `case` for it — apply it, or add `case {{enumName}}.{{message}}: break` if it changes no state.',
|
|
33
|
+
emitName: 'Emit events with `name: {{enumName}}.<Message>`.',
|
|
34
|
+
replay: 'apply() runs again when the aggregate is replayed — {{what}} here makes replays differ; derive it from the event.',
|
|
35
|
+
stateOutsideApply: 'Change "{{name}}" state through an emitted event: assign `this.{{property}}` in apply(), not in {{method}}().',
|
|
36
|
+
},
|
|
37
|
+
schema: [{
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
base: { type: 'string' },
|
|
41
|
+
decorator: { type: 'string' },
|
|
42
|
+
enumName: { type: 'string' },
|
|
43
|
+
stateChangesInApply: { type: 'boolean' },
|
|
44
|
+
},
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
}],
|
|
47
|
+
},
|
|
48
|
+
create(context) {
|
|
49
|
+
const { base = 'EventSourceable', decorator = 'trackable', enumName = 'MessageNames', stateChangesInApply = false } = context.options[0] ?? {};
|
|
50
|
+
const isThisCall = (node, method) => node.type === 'CallExpression' && node.callee.type === 'MemberExpression' && node.callee.object.type === 'ThisExpression' && node.callee.property.name === method;
|
|
51
|
+
return {
|
|
52
|
+
ClassDeclaration(node) {
|
|
53
|
+
if (node.superClass?.name !== base)
|
|
54
|
+
return;
|
|
55
|
+
const name = node.id?.name ?? '(anonymous)';
|
|
56
|
+
const at = node.id ?? node;
|
|
57
|
+
if (!(0, ast_1.decoratorNames)(node).includes(decorator))
|
|
58
|
+
context.report({ node: at, messageId: 'trackable', data: { name, decorator } });
|
|
59
|
+
const typeArguments = node.superTypeArguments ?? node.superTypeParameters;
|
|
60
|
+
if (!typeArguments?.params?.length)
|
|
61
|
+
context.report({ node: node.superClass, messageId: 'eventType', data: { name, base } });
|
|
62
|
+
const apply = (0, ast_1.findMethod)(node, 'apply', false);
|
|
63
|
+
const emitted = [];
|
|
64
|
+
for (const member of node.body.body) {
|
|
65
|
+
// Emitting inside apply() is reported as a replay hazard below, not as an unapplied event.
|
|
66
|
+
if (member.type !== 'MethodDefinition' || !member.value.body || member === apply)
|
|
67
|
+
continue;
|
|
68
|
+
(0, messages_1.walkOwnBody)(member.value.body, (inner) => {
|
|
69
|
+
if (!isThisCall(inner, 'emit'))
|
|
70
|
+
return;
|
|
71
|
+
const argument = inner.arguments[0];
|
|
72
|
+
const bodies = argument?.type === 'ArrayExpression' ? argument.elements : [argument];
|
|
73
|
+
for (const body of bodies) {
|
|
74
|
+
const property = (0, messages_1.nameProperty)(body, enumName);
|
|
75
|
+
if (property?.name)
|
|
76
|
+
emitted.push({ message: property.name, node: property.node });
|
|
77
|
+
else if (property)
|
|
78
|
+
context.report({ node: property.node, messageId: 'emitName', data: { enumName } });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (!apply || apply.accessibility !== 'protected') {
|
|
83
|
+
context.report({ node: apply?.key ?? at, messageId: 'apply', data: { name } });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const applied = new Set();
|
|
87
|
+
(0, messages_1.walkOwnBody)(apply.value.body, (inner) => {
|
|
88
|
+
if (inner.type === 'SwitchCase') {
|
|
89
|
+
const message = (0, messages_1.messageNameOf)(inner.test, enumName);
|
|
90
|
+
if (message)
|
|
91
|
+
applied.add(message);
|
|
92
|
+
}
|
|
93
|
+
if (isThisCall(inner, 'emit'))
|
|
94
|
+
context.report({ node: inner, messageId: 'replay', data: { what: 'emitting' } });
|
|
95
|
+
if (inner.type === 'ThrowStatement')
|
|
96
|
+
context.report({ node: inner, messageId: 'replay', data: { what: 'throwing' } });
|
|
97
|
+
if (inner.type === 'NewExpression' && inner.callee.name === 'Date' && inner.arguments.length === 0) {
|
|
98
|
+
context.report({ node: inner, messageId: 'replay', data: { what: 'reading the clock (new Date())' } });
|
|
99
|
+
}
|
|
100
|
+
if (inner.type === 'CallExpression' && inner.callee.type === 'MemberExpression') {
|
|
101
|
+
const object = inner.callee.object.name;
|
|
102
|
+
const property = inner.callee.property.name;
|
|
103
|
+
if (NON_DETERMINISTIC_CALLS.some((call) => call.object === object && call.property === property)) {
|
|
104
|
+
context.report({ node: inner, messageId: 'replay', data: { what: `${object}.${property}()` } });
|
|
105
|
+
}
|
|
106
|
+
else if (property === 'create' && /^[A-Z]/.test(object ?? '')) {
|
|
107
|
+
context.report({ node: inner, messageId: 'replay', data: { what: `minting a value with ${object}.create()` } });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (inner.type === 'CallExpression' && inner.callee.type === 'Identifier' && /^(v4|uuidv4|uuid|randomUUID)$/.test(inner.callee.name)) {
|
|
111
|
+
context.report({ node: inner, messageId: 'replay', data: { what: `${inner.callee.name}()` } });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
for (const { message, node: emitNode } of emitted) {
|
|
115
|
+
if (!applied.has(message))
|
|
116
|
+
context.report({ node: emitNode, messageId: 'notApplied', data: { name, enumName, message } });
|
|
117
|
+
}
|
|
118
|
+
if (stateChangesInApply) {
|
|
119
|
+
for (const member of node.body.body) {
|
|
120
|
+
if (member.type !== 'MethodDefinition' || member.kind === 'constructor' || member === apply || member.static || !member.value.body)
|
|
121
|
+
continue;
|
|
122
|
+
(0, messages_1.walkOwnBody)(member.value.body, (inner) => {
|
|
123
|
+
const target = inner.type === 'AssignmentExpression' ? inner.left : inner.type === 'UpdateExpression' ? inner.argument : null;
|
|
124
|
+
if (target?.type === 'MemberExpression' && target.object.type === 'ThisExpression' && !target.computed) {
|
|
125
|
+
context.report({ node: inner, messageId: 'stateOutsideApply', data: { name, property: target.property.name, method: member.key.name } });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=event-sourced-aggregate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-sourced-aggregate.js","sourceRoot":"","sources":["../../src/rules/event-sourced-aggregate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* the private helper that loads one owner-scoped entity and throws not-found is `findOne`.
|
|
3
|
+
* Detected as: private method, first parameter typed `AuthToken`, returns `Promise<Entity>`,
|
|
4
|
+
* body throws `AppError.notFound(...)`. `findOne<Thing>` is accepted when a class needs several.
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
* the private helper that loads one owner-scoped entity and throws not-found is `findOne`.
|
|
7
|
+
* Detected as: private method, first parameter typed `AuthToken`, returns `Promise<Entity>`,
|
|
8
|
+
* body throws `AppError.notFound(...)`. `findOne<Thing>` is accepted when a class needs several.
|
|
9
|
+
*/
|
|
10
|
+
exports.default = (0, rule_1.defineRule)({
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'suggestion',
|
|
13
|
+
docs: { description: 'Name owner-scoped single-entity fetch helpers `findOne`' },
|
|
14
|
+
messages: { name: 'Owner-scoped fetch helper "{{name}}" must be named `findOne`.' },
|
|
15
|
+
schema: [],
|
|
16
|
+
},
|
|
17
|
+
create(context) {
|
|
18
|
+
const source = context.sourceCode;
|
|
19
|
+
return {
|
|
20
|
+
'MethodDefinition[accessibility="private"][kind="method"]'(method) {
|
|
21
|
+
const name = method.key.name;
|
|
22
|
+
if (!name || /^findOne([A-Z]\w*)?$/.test(name))
|
|
23
|
+
return;
|
|
24
|
+
const fn = method.value;
|
|
25
|
+
const firstParam = fn.params[0]?.type === 'TSParameterProperty' ? fn.params[0].parameter : fn.params[0];
|
|
26
|
+
if ((0, ast_1.annotationTypeName)(firstParam?.typeAnnotation) !== 'AuthToken')
|
|
27
|
+
return;
|
|
28
|
+
const returned = (0, ast_1.promiseTypeArgument)(fn.returnType);
|
|
29
|
+
if (returned?.type !== 'TSTypeReference')
|
|
30
|
+
return;
|
|
31
|
+
if (!/AppError\.notFound\(/.test(source.getText(fn.body)))
|
|
32
|
+
return;
|
|
33
|
+
context.report({ node: method.key, messageId: 'name', data: { name } });
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=find-one-naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-one-naming.js","sourceRoot":"","sources":["../../src/rules/find-one-naming.ts"],"names":[],"mappings":";;AAAA,oCAAqE;AACrE,sCAAyC;AAEzC;;;;GAIG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,yDAAyD,EAAE;QAChF,QAAQ,EAAE,EAAE,IAAI,EAAE,+DAA+D,EAAE;QACnF,MAAM,EAAE,EAAE;KACX;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;QAClC,OAAO;YACL,0DAA0D,CAAC,MAAM;gBAC/D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAC7B,IAAI,CAAC,IAAI,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,OAAO;gBACvD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC;gBACxB,MAAM,UAAU,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,qBAAqB,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACxG,IAAI,IAAA,wBAAkB,EAAC,UAAU,EAAE,cAAc,CAAC,KAAK,WAAW;oBAAE,OAAO;gBAC3E,MAAM,QAAQ,GAAG,IAAA,yBAAmB,EAAC,EAAE,CAAC,UAAU,CAAC,CAAC;gBACpD,IAAI,QAAQ,EAAE,IAAI,KAAK,iBAAiB;oBAAE,OAAO;gBACjD,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;oBAAE,OAAO;gBAClE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;YAC1E,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Some packages belong to one layer only. Options: `{ modules: { uuid: ['/domain/', '/common/values/'] } }`
|
|
3
|
+
* — importing (or requiring) `uuid` from a file whose path contains none of the listed fragments is
|
|
4
|
+
* an error. Sub-paths count (`uuid/v4`).
|
|
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 ast_1 = require("../lib/ast");
|
|
4
|
+
const rule_1 = require("../lib/rule");
|
|
5
|
+
/**
|
|
6
|
+
* Some packages belong to one layer only. Options: `{ modules: { uuid: ['/domain/', '/common/values/'] } }`
|
|
7
|
+
* — importing (or requiring) `uuid` from a file whose path contains none of the listed fragments is
|
|
8
|
+
* an error. Sub-paths count (`uuid/v4`).
|
|
9
|
+
*/
|
|
10
|
+
exports.default = (0, rule_1.defineRule)({
|
|
11
|
+
meta: {
|
|
12
|
+
type: 'problem',
|
|
13
|
+
docs: { description: 'Restrict packages to the layers allowed to use them' },
|
|
14
|
+
messages: { restricted: '"{{module}}" may only be used in {{allowed}} — {{why}}' },
|
|
15
|
+
schema: [{
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
modules: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
additionalProperties: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: { paths: { type: 'array', items: { type: 'string' } }, reason: { type: 'string' } },
|
|
23
|
+
required: ['paths'],
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
additionalProperties: false,
|
|
29
|
+
}],
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const modules = context.options[0]?.modules ?? {};
|
|
33
|
+
const file = (0, ast_1.filePath)(context);
|
|
34
|
+
const check = (node, source) => {
|
|
35
|
+
const module = Object.keys(modules).find((name) => source === name || source.startsWith(`${name}/`));
|
|
36
|
+
if (!module)
|
|
37
|
+
return;
|
|
38
|
+
const { paths, reason = 'move this into that layer.' } = modules[module];
|
|
39
|
+
if (!paths.some((fragment) => file.includes(fragment))) {
|
|
40
|
+
context.report({ node, messageId: 'restricted', data: { module, allowed: paths.join(', '), why: reason } });
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
ImportDeclaration: (node) => check(node, node.source.value),
|
|
45
|
+
ExportAllDeclaration: (node) => check(node, node.source.value),
|
|
46
|
+
'ExportNamedDeclaration[source]': (node) => check(node, node.source.value),
|
|
47
|
+
'CallExpression[callee.name="require"][arguments.0.type="Literal"]': (node) => check(node, String(node.arguments[0].value)),
|
|
48
|
+
ImportExpression: (node) => node.source.type === 'Literal' && check(node, String(node.source.value)),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=import-only-in.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-only-in.js","sourceRoot":"","sources":["../../src/rules/import-only-in.ts"],"names":[],"mappings":";;AAAA,oCAAsC;AACtC,sCAAyC;AAEzC;;;;GAIG;AACH,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,EAAE,WAAW,EAAE,qDAAqD,EAAE;QAC5E,QAAQ,EAAE,EAAE,UAAU,EAAE,wDAAwD,EAAE;QAClF,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,OAAO,EAAE;wBACP,IAAI,EAAE,QAAQ;wBACd,oBAAoB,EAAE;4BACpB,IAAI,EAAE,QAAQ;4BACd,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;4BAC/F,QAAQ,EAAE,CAAC,OAAO,CAAC;4BACnB,oBAAoB,EAAE,KAAK;yBAC5B;qBACF;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B,CAAC;KACH;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,IAAA,cAAQ,EAAC,OAAO,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;YACrG,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,4BAA4B,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YACzE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;gBACvD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;YAC9G,CAAC;QACH,CAAC,CAAC;QACF,OAAO;YACL,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAC3D,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAC9D,gCAAgC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YAC1E,mEAAmE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC3H,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;SACrG,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const rule_1 = require("../lib/rule");
|
|
4
|
+
/** single-record reads end the knex query with `.first()` instead of destructuring an array. */
|
|
5
|
+
exports.default = (0, rule_1.defineRule)({
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'suggestion',
|
|
8
|
+
docs: { description: 'Require knex `.first()` for single-record reads' },
|
|
9
|
+
messages: { first: 'Single-record read: end the query with .first() instead of destructuring the result.' },
|
|
10
|
+
schema: [],
|
|
11
|
+
},
|
|
12
|
+
create(context) {
|
|
13
|
+
const source = context.sourceCode;
|
|
14
|
+
const knexQuery = /^(this\.)?(db|knex|trx)\s*(<[^>]*>)?\(/;
|
|
15
|
+
return {
|
|
16
|
+
'VariableDeclarator[id.type="ArrayPattern"][init.type="AwaitExpression"]'(node) {
|
|
17
|
+
if (node.id.elements.length !== 1)
|
|
18
|
+
return;
|
|
19
|
+
const query = source.getText(node.init.argument);
|
|
20
|
+
// Writes that genuinely return rows (`RETURNING`) are fine to destructure.
|
|
21
|
+
if (knexQuery.test(query) && !/\.(returning|insert|update|del|delete)\(/.test(query)) {
|
|
22
|
+
context.report({ node, messageId: 'first' });
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
//# sourceMappingURL=knex-first.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knex-first.js","sourceRoot":"","sources":["../../src/rules/knex-first.ts"],"names":[],"mappings":";;AAAA,sCAAyC;AACzC,gGAAgG;AAChG,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,iDAAiD,EAAE;QACxE,QAAQ,EAAE,EAAE,KAAK,EAAE,sFAAsF,EAAE;QAC3G,MAAM,EAAE,EAAE;KACX;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;QAClC,MAAM,SAAS,GAAG,wCAAwC,CAAC;QAC3D,OAAO;YACL,yEAAyE,CAAC,IAAI;gBAC5E,IAAI,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO;gBAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACjD,2EAA2E;gBAC3E,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,0CAA0C,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;oBACrF,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One message per file in the messages folder (`src/common/messages/<Name>.ts`):
|
|
3
|
+
*
|
|
4
|
+
* import { Message } from '@pjts/infra'
|
|
5
|
+
* import { MessageNames } from '.'
|
|
6
|
+
*
|
|
7
|
+
* export interface CalendarBooked extends Message {
|
|
8
|
+
* name: MessageNames.CalendarBooked
|
|
9
|
+
* therapistId: string
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Exactly one message interface, exported and named like the file, extending `Message` (or a shared
|
|
13
|
+
* base such as `PaymentEventBase`), whose `name` is `MessageNames.<FileName>`. Helper payload types it
|
|
14
|
+
* uses (`OrderItem`) may sit next to it; another message or runtime code may not. Base files (`basePattern`) only
|
|
15
|
+
* extend `Message` and have no `name`. `index.ts` is checked by `message-names`.
|
|
16
|
+
*/
|
|
17
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
5
|
+
const rule_1 = require("../lib/rule");
|
|
6
|
+
const messages_1 = require("../lib/messages");
|
|
7
|
+
/**
|
|
8
|
+
* One message per file in the messages folder (`src/common/messages/<Name>.ts`):
|
|
9
|
+
*
|
|
10
|
+
* import { Message } from '@pjts/infra'
|
|
11
|
+
* import { MessageNames } from '.'
|
|
12
|
+
*
|
|
13
|
+
* export interface CalendarBooked extends Message {
|
|
14
|
+
* name: MessageNames.CalendarBooked
|
|
15
|
+
* therapistId: string
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Exactly one message interface, exported and named like the file, extending `Message` (or a shared
|
|
19
|
+
* base such as `PaymentEventBase`), whose `name` is `MessageNames.<FileName>`. Helper payload types it
|
|
20
|
+
* uses (`OrderItem`) may sit next to it; another message or runtime code may not. Base files (`basePattern`) only
|
|
21
|
+
* extend `Message` and have no `name`. `index.ts` is checked by `message-names`.
|
|
22
|
+
*/
|
|
23
|
+
exports.default = (0, rule_1.defineRule)({
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'problem',
|
|
26
|
+
docs: { description: 'One message interface per file, named and typed after the file' },
|
|
27
|
+
messages: {
|
|
28
|
+
onePerFile: 'One message per file — move "{{name}}" to its own file (unions go in index.ts; only helper payload types may stay).',
|
|
29
|
+
missing: '"{{file}}.ts" must declare `export interface {{file}} extends Message`.',
|
|
30
|
+
notExported: 'Message "{{file}}" must be exported.',
|
|
31
|
+
extends: 'Message "{{file}}" must extend `Message` from {{modules}} or a shared `…{{base}}` interface.',
|
|
32
|
+
nameMissing: 'Message "{{file}}" must declare `name: {{enumName}}.{{file}}`.',
|
|
33
|
+
nameMismatch: 'Message "{{file}}" has `name: {{enumName}}.{{actual}}` — the enum key, file and interface must share one name.',
|
|
34
|
+
baseName: 'Base interface "{{file}}" must not declare `name` — each message pins its own.',
|
|
35
|
+
},
|
|
36
|
+
schema: [{
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
messageModules: { type: 'array', items: { type: 'string' } },
|
|
40
|
+
basePattern: { type: 'string' },
|
|
41
|
+
enumName: { type: 'string' },
|
|
42
|
+
},
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
}],
|
|
45
|
+
},
|
|
46
|
+
create(context) {
|
|
47
|
+
const { messageModules = ['@pjts/infra', '@pjts/core'], basePattern = 'Base$', enumName = 'MessageNames' } = context.options[0] ?? {};
|
|
48
|
+
const file = path_1.default.basename(context.filename).replace(/\.ts$/, '');
|
|
49
|
+
if (file === 'index' || /\.d$/.test(file))
|
|
50
|
+
return {};
|
|
51
|
+
const isBase = new RegExp(basePattern).test(file);
|
|
52
|
+
return {
|
|
53
|
+
Program(program) {
|
|
54
|
+
const messageImports = new Set();
|
|
55
|
+
for (const statement of program.body) {
|
|
56
|
+
if (statement.type !== 'ImportDeclaration')
|
|
57
|
+
continue;
|
|
58
|
+
for (const specifier of statement.specifiers) {
|
|
59
|
+
if (messageModules.includes(statement.source.value) && (specifier.imported?.name ?? specifier.local.name) === 'Message')
|
|
60
|
+
messageImports.add(specifier.local.name);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const baseRegExp = new RegExp(basePattern);
|
|
64
|
+
/** A declaration that is itself a message (or base): extends Message/…Base, or pins a `name`. */
|
|
65
|
+
const isMessageLike = (declaration) => {
|
|
66
|
+
if (declaration.type !== 'TSInterfaceDeclaration')
|
|
67
|
+
return false;
|
|
68
|
+
const parents = (declaration.extends ?? []).map((heritage) => heritage.expression?.name);
|
|
69
|
+
if (parents.some((parent) => messageImports.has(parent) || baseRegExp.test(parent ?? '')))
|
|
70
|
+
return true;
|
|
71
|
+
return declaration.body.body.some((member) => member.type === 'TSPropertySignature' && (member.key.name ?? member.key.value) === 'name' && (0, messages_1.messageNameOf)(member.typeAnnotation?.typeAnnotation, enumName));
|
|
72
|
+
};
|
|
73
|
+
const isTypeOnly = (declaration) => declaration.type === 'TSInterfaceDeclaration' || declaration.type === 'TSTypeAliasDeclaration';
|
|
74
|
+
let found = null;
|
|
75
|
+
for (const statement of program.body) {
|
|
76
|
+
const declaration = statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement;
|
|
77
|
+
if (!declaration || statement.type === 'ImportDeclaration')
|
|
78
|
+
continue;
|
|
79
|
+
if (statement.type === 'ExportNamedDeclaration' && !statement.declaration)
|
|
80
|
+
continue; // re-export
|
|
81
|
+
const name = declaration.id?.name ?? null;
|
|
82
|
+
if (declaration.type === 'TSInterfaceDeclaration' && name === file) {
|
|
83
|
+
found = { declaration, exported: statement.type === 'ExportNamedDeclaration' };
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// Helper payload types (`OrderItem`) may live with the message that uses them.
|
|
87
|
+
if (isTypeOnly(declaration) && !isMessageLike(declaration))
|
|
88
|
+
continue;
|
|
89
|
+
context.report({ node: declaration.id ?? declaration, messageId: 'onePerFile', data: { name: name ?? context.sourceCode.getText(declaration).slice(0, 30) } });
|
|
90
|
+
}
|
|
91
|
+
if (!found)
|
|
92
|
+
return context.report({ node: program, loc: { line: 1, column: 0 }, messageId: 'missing', data: { file } });
|
|
93
|
+
const { declaration, exported } = found;
|
|
94
|
+
if (!exported)
|
|
95
|
+
context.report({ node: declaration.id, messageId: 'notExported', data: { file } });
|
|
96
|
+
const parents = (declaration.extends ?? []).map((heritage) => heritage.expression?.name);
|
|
97
|
+
const extendsMessage = parents.some((parent) => messageImports.has(parent));
|
|
98
|
+
const extendsBase = parents.some((parent) => baseRegExp.test(parent ?? ''));
|
|
99
|
+
if (isBase ? !extendsMessage : !(extendsMessage || extendsBase)) {
|
|
100
|
+
context.report({ node: declaration.id, messageId: 'extends', data: { file, modules: messageModules.join(' / '), base: basePattern.replace(/\$$/, '') } });
|
|
101
|
+
}
|
|
102
|
+
const nameMember = declaration.body.body.find((member) => member.type === 'TSPropertySignature' && (member.key.name ?? member.key.value) === 'name');
|
|
103
|
+
if (isBase) {
|
|
104
|
+
if (nameMember)
|
|
105
|
+
context.report({ node: nameMember, messageId: 'baseName', data: { file } });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!nameMember)
|
|
109
|
+
return context.report({ node: declaration.id, messageId: 'nameMissing', data: { file, enumName } });
|
|
110
|
+
const actual = (0, messages_1.messageNameOf)(nameMember.typeAnnotation?.typeAnnotation, enumName);
|
|
111
|
+
if (actual !== file) {
|
|
112
|
+
context.report({ node: nameMember, messageId: actual ? 'nameMismatch' : 'nameMissing', data: { file, enumName, actual: actual ?? '' } });
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=message-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-file.js","sourceRoot":"","sources":["../../src/rules/message-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A message consumer is an application-layer class:
|
|
3
|
+
*
|
|
4
|
+
* export class SlotCacheInvalidationHandler extends MessageHandlerBase {
|
|
5
|
+
* constructor(private readonly cache: SlotCachePort) {
|
|
6
|
+
* super([MessageNames.CalendarBooked]);
|
|
7
|
+
* }
|
|
8
|
+
* protected async handleMessage(event: CalendarBooked): Promise<void> { … }
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
* - named `…Handler`, in one of `paths` (default `/app/`);
|
|
12
|
+
* - subscribes with `super([MessageNames.X, …])`; when `handleMessage` switches on the name, every
|
|
13
|
+
* subscribed name has a `case` and every `case` is subscribed;
|
|
14
|
+
* - `handleMessage` is protected and its parameter typed with the message (or a union of them);
|
|
15
|
+
* - depends only on ports (`…Port`) or application classes imported from the app layer — the
|
|
16
|
+
* infrastructure is wired in by the module's `…MessageHandlers` composition root.
|
|
17
|
+
*/
|
|
18
|
+
declare const _default: import("eslint").Rule.RuleModule;
|
|
19
|
+
export default _default;
|