eslint-plugin-modularity 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/package.json +54 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +59 -0
- package/src/index.js.map +1 -0
- package/src/lib/eslint-plugin-modularity.d.ts +1 -0
- package/src/lib/eslint-plugin-modularity.js +7 -0
- package/src/lib/eslint-plugin-modularity.js.map +1 -0
- package/src/rules/ddd-anemic-domain-model.d.ts +27 -0
- package/src/rules/ddd-anemic-domain-model.js +285 -0
- package/src/rules/ddd-anemic-domain-model.js.map +1 -0
- package/src/rules/ddd-value-object-immutability.d.ts +27 -0
- package/src/rules/ddd-value-object-immutability.js +197 -0
- package/src/rules/ddd-value-object-immutability.js.map +1 -0
- package/src/rules/enforce-naming.d.ts +30 -0
- package/src/rules/enforce-naming.js +212 -0
- package/src/rules/enforce-naming.js.map +1 -0
- package/src/rules/enforce-rest-conventions.d.ts +27 -0
- package/src/rules/enforce-rest-conventions.js +162 -0
- package/src/rules/enforce-rest-conventions.js.map +1 -0
- package/src/rules/no-external-api-calls-in-utils.d.ts +23 -0
- package/src/rules/no-external-api-calls-in-utils.js +151 -0
- package/src/rules/no-external-api-calls-in-utils.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://eslint.interlace.tools" target="blank"><img src="https://eslint.interlace.tools/eslint-interlace-logo-light.svg" alt="ESLint Interlace Logo" width="120" /></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
Architecture rules for DDD patterns, module isolation, and clean design.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/eslint-plugin-modularity" target="_blank"><img src="https://img.shields.io/npm/v/eslint-plugin-modularity.svg" alt="NPM Version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/eslint-plugin-modularity" target="_blank"><img src="https://img.shields.io/npm/dm/eslint-plugin-modularity.svg" alt="NPM Downloads" /></a>
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT" target="_blank"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="Package License" /></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## Description
|
|
16
|
+
|
|
17
|
+
This plugin enforces Domain-Driven Design (DDD) patterns, module isolation, and architectural best practices. It helps teams maintain clean, layered architectures by detecting anemic domain models, mutable value objects, and architectural boundary violations.
|
|
18
|
+
|
|
19
|
+
## Philosophy
|
|
20
|
+
|
|
21
|
+
**Interlace** fosters **strength through integration**. Good architecture isn't just documentation — it should be enforced. These rules encode architectural decisions as code, preventing drift and maintaining design integrity over time.
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
- To check out the [guide](https://eslint.interlace.tools/docs/modularity), visit [eslint.interlace.tools](https://eslint.interlace.tools). 📚
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install eslint-plugin-modularity --save-dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## ⚙️ Configuration Presets
|
|
32
|
+
|
|
33
|
+
| Preset | Description |
|
|
34
|
+
| :------------ | :----------------------------------------- |
|
|
35
|
+
| `recommended` | Balanced DDD and architecture enforcement |
|
|
36
|
+
| `strict` | All rules as errors for strict enforcement |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🏢 Usage Example
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// eslint.config.js
|
|
44
|
+
import modularity from 'eslint-plugin-modularity';
|
|
45
|
+
|
|
46
|
+
export default [
|
|
47
|
+
modularity.configs.recommended,
|
|
48
|
+
|
|
49
|
+
// Apply strict DDD enforcement to domain layer
|
|
50
|
+
{
|
|
51
|
+
files: ['src/domain/**/*.ts'],
|
|
52
|
+
...modularity.configs.strict,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
|
|
61
|
+
| Rule | Description | 💼 | ⚠️ |
|
|
62
|
+
| :------------------------------------------------------------------------------- | :--------------------------------------------- | :-: | :-: |
|
|
63
|
+
| [ddd-anemic-domain-model](./docs/rules/ddd-anemic-domain-model.md) | Detect anemic domain models lacking behavior | 💼 | ⚠️ |
|
|
64
|
+
| [ddd-value-object-immutability](./docs/rules/ddd-value-object-immutability.md) | Enforce immutability in value objects | 💼 | |
|
|
65
|
+
| [enforce-naming](./docs/rules/enforce-naming.md) | Enforce consistent naming conventions by layer | 💼 | ⚠️ |
|
|
66
|
+
| [enforce-rest-conventions](./docs/rules/enforce-rest-conventions.md) | Enforce RESTful naming in API controllers | 💼 | |
|
|
67
|
+
| [no-external-api-calls-in-utils](./docs/rules/no-external-api-calls-in-utils.md) | Prevent external API calls in utility modules | 💼 | |
|
|
68
|
+
|
|
69
|
+
**Legend**: 💼 Recommended | ⚠️ Warns (not error)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Why These Rules?
|
|
74
|
+
|
|
75
|
+
### `ddd-anemic-domain-model`
|
|
76
|
+
|
|
77
|
+
Detects domain entities that are just data containers without behavior — a common anti-pattern.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// ❌ Bad: Anemic model, no behavior
|
|
81
|
+
class Order {
|
|
82
|
+
id: string;
|
|
83
|
+
items: OrderItem[];
|
|
84
|
+
status: OrderStatus;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ✅ Good: Rich domain model with behavior
|
|
88
|
+
class Order {
|
|
89
|
+
id: string;
|
|
90
|
+
private items: OrderItem[];
|
|
91
|
+
private status: OrderStatus;
|
|
92
|
+
|
|
93
|
+
addItem(item: OrderItem): void {
|
|
94
|
+
/* ... */
|
|
95
|
+
}
|
|
96
|
+
submit(): void {
|
|
97
|
+
/* ... */
|
|
98
|
+
}
|
|
99
|
+
cancel(reason: string): void {
|
|
100
|
+
/* ... */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `ddd-value-object-immutability`
|
|
106
|
+
|
|
107
|
+
Value objects should be immutable. This rule catches mutable value objects.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// ❌ Bad: Mutable value object
|
|
111
|
+
class Money {
|
|
112
|
+
amount: number; // Can be mutated!
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ✅ Good: Immutable value object
|
|
116
|
+
class Money {
|
|
117
|
+
readonly amount: number;
|
|
118
|
+
readonly currency: string;
|
|
119
|
+
|
|
120
|
+
add(other: Money): Money {
|
|
121
|
+
return new Money(this.amount + other.amount, this.currency);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `no-external-api-calls-in-utils`
|
|
127
|
+
|
|
128
|
+
Utility modules should be pure functions without side effects like API calls.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// ❌ Bad: Utils with external dependencies
|
|
132
|
+
// src/utils/formatters.ts
|
|
133
|
+
import axios from 'axios';
|
|
134
|
+
|
|
135
|
+
export async function fetchAndFormat(id: string) {
|
|
136
|
+
const data = await axios.get(`/api/${id}`); // External API call!
|
|
137
|
+
return format(data);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ✅ Good: Pure utility function
|
|
141
|
+
export function format(data: Data): FormattedData {
|
|
142
|
+
return {
|
|
143
|
+
/* pure transformation */
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🔗 Related ESLint Plugins
|
|
151
|
+
|
|
152
|
+
Part of the **Interlace ESLint Ecosystem** — AI-native quality plugins with LLM-optimized error messages:
|
|
153
|
+
|
|
154
|
+
| Plugin | Description |
|
|
155
|
+
| :------------------------------------------------------------------------------------------------------------------- | :---------------------------------------- |
|
|
156
|
+
| [`eslint-plugin-import-next`](https://www.npmjs.com/package/eslint-plugin-import-next) | Import ordering & dependency architecture |
|
|
157
|
+
| [`@interlace/eslint-plugin-maintainability`](https://www.npmjs.com/package/@interlace/eslint-plugin-maintainability) | Cognitive complexity & code quality |
|
|
158
|
+
|
|
159
|
+
## 📄 License
|
|
160
|
+
|
|
161
|
+
MIT © [Ofri Peretz](https://github.com/ofri-peretz)
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-modularity",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "ESLint rules for architecture, DDD patterns, and module isolation with AI-parseable guidance.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"author": "Ofri Peretz <ofriperetzdev@gmail.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/ofri-peretz/eslint/tree/main/packages/eslint-plugin-modularity#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/ofri-peretz/eslint",
|
|
20
|
+
"directory": "packages/eslint-plugin-modularity"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/ofri-peretz/eslint/issues"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src/",
|
|
30
|
+
"dist/",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"eslint",
|
|
36
|
+
"eslint-plugin",
|
|
37
|
+
"eslintplugin",
|
|
38
|
+
"interlace-quality",
|
|
39
|
+
"modularity",
|
|
40
|
+
"ddd",
|
|
41
|
+
"domain-driven-design",
|
|
42
|
+
"architecture",
|
|
43
|
+
"llm-optimized",
|
|
44
|
+
"ai-assistant",
|
|
45
|
+
"typescript"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"tslib": "^2.3.0",
|
|
52
|
+
"@interlace/eslint-devkit": "*"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
import { TSESLint } from '@interlace/eslint-devkit';
|
|
7
|
+
/**
|
|
8
|
+
* Collection of all modularity and design pattern ESLint rules
|
|
9
|
+
*/
|
|
10
|
+
export declare const rules: Record<string, TSESLint.RuleModule<string, readonly unknown[]>>;
|
|
11
|
+
/**
|
|
12
|
+
* ESLint Plugin object
|
|
13
|
+
*/
|
|
14
|
+
export declare const plugin: TSESLint.FlatConfig.Plugin;
|
|
15
|
+
export declare const configs: Record<string, TSESLint.FlatConfig.Config>;
|
|
16
|
+
export default plugin;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.configs = exports.plugin = exports.rules = void 0;
|
|
9
|
+
const ddd_anemic_domain_model_1 = require("./rules/ddd-anemic-domain-model");
|
|
10
|
+
const ddd_value_object_immutability_1 = require("./rules/ddd-value-object-immutability");
|
|
11
|
+
const enforce_naming_1 = require("./rules/enforce-naming");
|
|
12
|
+
const enforce_rest_conventions_1 = require("./rules/enforce-rest-conventions");
|
|
13
|
+
const no_external_api_calls_in_utils_1 = require("./rules/no-external-api-calls-in-utils");
|
|
14
|
+
/**
|
|
15
|
+
* Collection of all modularity and design pattern ESLint rules
|
|
16
|
+
*/
|
|
17
|
+
exports.rules = {
|
|
18
|
+
'ddd-anemic-domain-model': ddd_anemic_domain_model_1.dddAnemicDomainModel,
|
|
19
|
+
'ddd-value-object-immutability': ddd_value_object_immutability_1.dddValueObjectImmutability,
|
|
20
|
+
'enforce-naming': enforce_naming_1.enforceNaming,
|
|
21
|
+
'enforce-rest-conventions': enforce_rest_conventions_1.enforceRestConventions,
|
|
22
|
+
'no-external-api-calls-in-utils': no_external_api_calls_in_utils_1.noExternalApiCallsInUtils,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* ESLint Plugin object
|
|
26
|
+
*/
|
|
27
|
+
exports.plugin = {
|
|
28
|
+
meta: {
|
|
29
|
+
name: 'eslint-plugin-modularity',
|
|
30
|
+
version: '1.0.0',
|
|
31
|
+
},
|
|
32
|
+
rules: exports.rules,
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Preset configurations for modularity rules
|
|
36
|
+
*/
|
|
37
|
+
const recommendedRules = {
|
|
38
|
+
'modularity/ddd-anemic-domain-model': 'warn',
|
|
39
|
+
'modularity/ddd-value-object-immutability': 'error',
|
|
40
|
+
'modularity/enforce-naming': 'warn',
|
|
41
|
+
'modularity/enforce-rest-conventions': 'error',
|
|
42
|
+
'modularity/no-external-api-calls-in-utils': 'error',
|
|
43
|
+
};
|
|
44
|
+
exports.configs = {
|
|
45
|
+
recommended: {
|
|
46
|
+
plugins: {
|
|
47
|
+
'modularity': exports.plugin,
|
|
48
|
+
},
|
|
49
|
+
rules: recommendedRules,
|
|
50
|
+
},
|
|
51
|
+
strict: {
|
|
52
|
+
plugins: {
|
|
53
|
+
'modularity': exports.plugin,
|
|
54
|
+
},
|
|
55
|
+
rules: Object.fromEntries(Object.keys(exports.rules).map(ruleName => [`modularity/${ruleName}`, 'error'])),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
exports.default = exports.plugin;
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/eslint-plugin-modularity/src/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,6EAAuE;AACvE,yFAAmF;AACnF,2DAAuD;AACvD,+EAA0E;AAC1E,2FAAmF;AAInF;;GAEG;AACU,QAAA,KAAK,GAAoE;IACpF,yBAAyB,EAAE,8CAAoB;IAC/C,+BAA+B,EAAE,0DAA0B;IAC3D,gBAAgB,EAAE,8BAAa;IAC/B,0BAA0B,EAAE,iDAAsB;IAClD,gCAAgC,EAAE,0DAAyB;CACc,CAAC;AAE5E;;GAEG;AACU,QAAA,MAAM,GAA+B;IAChD,IAAI,EAAE;QACJ,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,OAAO;KACjB;IACD,KAAK,EAAL,aAAK;CAC+B,CAAC;AAEvC;;GAEG;AACH,MAAM,gBAAgB,GAAkD;IACtE,oCAAoC,EAAE,MAAM;IAC5C,0CAA0C,EAAE,OAAO;IACnD,2BAA2B,EAAE,MAAM;IACnC,qCAAqC,EAAE,OAAO;IAC9C,2CAA2C,EAAE,OAAO;CACrD,CAAC;AAEW,QAAA,OAAO,GAA+C;IACjE,WAAW,EAAE;QACX,OAAO,EAAE;YACP,YAAY,EAAE,cAAM;SACrB;QACD,KAAK,EAAE,gBAAgB;KACa;IAEtC,MAAM,EAAE;QACN,OAAO,EAAE;YACP,YAAY,EAAE,cAAM;SACrB;QACD,KAAK,EAAE,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,IAAI,CAAC,aAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,cAAc,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAC,CACxE;KACmC;CACvC,CAAC;AAEF,kBAAe,cAAM,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function eslintPluginModularity(): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.eslintPluginModularity = eslintPluginModularity;
|
|
4
|
+
function eslintPluginModularity() {
|
|
5
|
+
return 'eslint-plugin-modularity';
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=eslint-plugin-modularity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eslint-plugin-modularity.js","sourceRoot":"","sources":["../../../../../packages/eslint-plugin-modularity/src/lib/eslint-plugin-modularity.ts"],"names":[],"mappings":";;AAAA,wDAEC;AAFD,SAAgB,sBAAsB;IACpC,OAAO,0BAA0B,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: ddd-anemic-domain-model
|
|
8
|
+
* Detects entities with only getters/setters and no business logic
|
|
9
|
+
* Priority 1: Domain-Driven Design
|
|
10
|
+
*
|
|
11
|
+
* @see https://martinfowler.com/bliki/AnemicDomainModel.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'anemicDomainModel' | 'addBusinessLogic' | 'migrateToRichModel' | 'identifyAggregateRoot';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Minimum methods required to not be considered anemic. Default: 1 */
|
|
17
|
+
minBusinessMethods?: number;
|
|
18
|
+
/** Ignore DTOs and data transfer objects. Default: true */
|
|
19
|
+
ignoreDtos?: boolean;
|
|
20
|
+
/** Patterns for DTO identification. Default: ['DTO', 'Dto', 'Data', 'Request', 'Response'] */
|
|
21
|
+
dtoPatterns?: string[];
|
|
22
|
+
}
|
|
23
|
+
type RuleOptions = [Options?];
|
|
24
|
+
export declare const dddAnemicDomainModel: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
25
|
+
name: string;
|
|
26
|
+
};
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.dddAnemicDomainModel = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
/**
|
|
12
|
+
* Check if a class is likely a DTO
|
|
13
|
+
*/
|
|
14
|
+
function isDto(className, dtoPatterns) {
|
|
15
|
+
return dtoPatterns.some(pattern => className.includes(pattern));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if a method is a simple getter/setter in disguise
|
|
19
|
+
*/
|
|
20
|
+
function isSimpleGetterSetter(member) {
|
|
21
|
+
if (member.value.type !== 'FunctionExpression') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const methodName = member.key.type === 'Identifier' ? member.key.name : '';
|
|
25
|
+
const body = member.value.body;
|
|
26
|
+
if (body.type !== 'BlockStatement' || body.body.length === 0) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Check if it's a simple getter (getName() { return this.name; })
|
|
30
|
+
if (methodName.startsWith('get') && body.body.length === 1) {
|
|
31
|
+
const statement = body.body[0];
|
|
32
|
+
if (statement.type === 'ReturnStatement' && statement.argument) {
|
|
33
|
+
// Simple return statement
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Check if it's a simple setter (setName(name) { this.name = name; })
|
|
38
|
+
if (methodName.startsWith('set') && body.body.length === 1) {
|
|
39
|
+
const statement = body.body[0];
|
|
40
|
+
if (statement.type === 'ExpressionStatement' &&
|
|
41
|
+
statement.expression.type === 'AssignmentExpression') {
|
|
42
|
+
// Simple assignment
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a method is pure delegation (just calls another method and returns)
|
|
50
|
+
* e.g., save() { return this.repository.save(this); }
|
|
51
|
+
* Does NOT count calls to array methods, built-ins, or methods on this
|
|
52
|
+
*/
|
|
53
|
+
function isPureDelegation(member) {
|
|
54
|
+
if (member.value.type !== 'FunctionExpression') {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const body = member.value.body;
|
|
58
|
+
if (body.type !== 'BlockStatement' || body.body.length !== 1) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const statement = body.body[0];
|
|
62
|
+
// Array/collection methods that are business logic, not delegation
|
|
63
|
+
const builtInMethods = new Set([
|
|
64
|
+
'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
|
|
65
|
+
'includes', 'indexOf', 'slice', 'concat', 'join', 'sort', 'reverse',
|
|
66
|
+
'push', 'pop', 'shift', 'unshift', 'splice', 'flat', 'flatMap',
|
|
67
|
+
'toString', 'valueOf', 'toJSON'
|
|
68
|
+
]);
|
|
69
|
+
// Check: return someService.method(...) or return this.service.method(...)
|
|
70
|
+
if (statement.type === 'ReturnStatement' && statement.argument) {
|
|
71
|
+
const arg = statement.argument;
|
|
72
|
+
if (arg.type === 'CallExpression' && arg.callee.type === 'MemberExpression') {
|
|
73
|
+
const callee = arg.callee;
|
|
74
|
+
const methodName = callee.property.type === 'Identifier' ? callee.property.name : '';
|
|
75
|
+
// Skip if it's a built-in method (not delegation)
|
|
76
|
+
if (builtInMethods.has(methodName)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
// It's a delegation call, check if it's to an external service
|
|
80
|
+
const object = callee.object;
|
|
81
|
+
if (object.type === 'MemberExpression' &&
|
|
82
|
+
object.object.type === 'ThisExpression') {
|
|
83
|
+
// this.service.method() pattern - but only if not a built-in
|
|
84
|
+
const propName = object.property.type === 'Identifier' ? object.property.name : '';
|
|
85
|
+
// Common service patterns: repository, service, api, client, dao
|
|
86
|
+
if (propName.includes('repository') || propName.includes('service') ||
|
|
87
|
+
propName.includes('api') || propName.includes('client') ||
|
|
88
|
+
propName.includes('dao') || propName.includes('Repository') ||
|
|
89
|
+
propName.includes('Service') || propName.includes('Api')) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false; // this.items.method() is business logic
|
|
93
|
+
}
|
|
94
|
+
if (object.type === 'Identifier') {
|
|
95
|
+
// externalService.method() pattern - definitely delegation
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Check: this.service.method(...) without return (void delegation)
|
|
101
|
+
if (statement.type === 'ExpressionStatement') {
|
|
102
|
+
const expr = statement.expression;
|
|
103
|
+
if (expr.type === 'CallExpression' && expr.callee.type === 'MemberExpression') {
|
|
104
|
+
const callee = expr.callee;
|
|
105
|
+
const methodName = callee.property.type === 'Identifier' ? callee.property.name : '';
|
|
106
|
+
// Skip built-in methods
|
|
107
|
+
if (builtInMethods.has(methodName)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const object = callee.object;
|
|
111
|
+
if (object.type === 'MemberExpression' &&
|
|
112
|
+
object.object.type === 'ThisExpression') {
|
|
113
|
+
const propName = object.property.type === 'Identifier' ? object.property.name : '';
|
|
114
|
+
if (propName.includes('repository') || propName.includes('service') ||
|
|
115
|
+
propName.includes('api') || propName.includes('client') ||
|
|
116
|
+
propName.includes('dao') || propName.includes('Repository') ||
|
|
117
|
+
propName.includes('Service') || propName.includes('Api')) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Count business logic methods (non-getter/setter/delegation)
|
|
128
|
+
*/
|
|
129
|
+
function countBusinessMethods(node) {
|
|
130
|
+
let count = 0;
|
|
131
|
+
for (const member of node.body.body) {
|
|
132
|
+
if (member.type === 'MethodDefinition') {
|
|
133
|
+
const methodName = member.key.type === 'Identifier' ? member.key.name : '';
|
|
134
|
+
// Skip getters and setters
|
|
135
|
+
if (member.kind === 'get' || member.kind === 'set') {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Skip constructor
|
|
139
|
+
if (methodName === 'constructor') {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Skip simple getter/setter methods in disguise
|
|
143
|
+
if (isSimpleGetterSetter(member)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Skip pure delegation methods (just calls external service)
|
|
147
|
+
if (isPureDelegation(member)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// Count methods (any method that's not a getter/setter/constructor/delegation is business logic)
|
|
151
|
+
const valueType = member.value.type;
|
|
152
|
+
if (valueType === 'FunctionExpression' || valueType === 'ArrowFunctionExpression') {
|
|
153
|
+
count++;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (member.type === 'PropertyDefinition') {
|
|
157
|
+
// Property definitions are not business logic
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return count;
|
|
162
|
+
}
|
|
163
|
+
exports.dddAnemicDomainModel = (0, eslint_devkit_2.createRule)({
|
|
164
|
+
name: 'ddd-anemic-domain-model',
|
|
165
|
+
meta: {
|
|
166
|
+
type: 'suggestion',
|
|
167
|
+
docs: {
|
|
168
|
+
description: 'Detects entities with only getters/setters and no business logic',
|
|
169
|
+
},
|
|
170
|
+
messages: {
|
|
171
|
+
anemicDomainModel: (0, eslint_devkit_1.formatLLMMessage)({
|
|
172
|
+
icon: eslint_devkit_1.MessageIcons.ARCHITECTURE,
|
|
173
|
+
issueName: 'Anemic domain model',
|
|
174
|
+
description: 'Class {{className}} has no business logic (only getters/setters)',
|
|
175
|
+
severity: 'MEDIUM',
|
|
176
|
+
fix: 'Add business logic methods to create a rich domain model',
|
|
177
|
+
documentationLink: 'https://martinfowler.com/bliki/AnemicDomainModel.html',
|
|
178
|
+
}),
|
|
179
|
+
addBusinessLogic: (0, eslint_devkit_1.formatLLMMessage)({
|
|
180
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
181
|
+
issueName: 'Add Business Logic',
|
|
182
|
+
description: 'Add business logic methods',
|
|
183
|
+
severity: 'LOW',
|
|
184
|
+
fix: 'Add domain behavior methods to {{className}}',
|
|
185
|
+
documentationLink: 'https://martinfowler.com/bliki/AnemicDomainModel.html',
|
|
186
|
+
}),
|
|
187
|
+
migrateToRichModel: (0, eslint_devkit_1.formatLLMMessage)({
|
|
188
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
189
|
+
issueName: 'Migrate to Rich Model',
|
|
190
|
+
description: 'Migrate to rich domain model',
|
|
191
|
+
severity: 'LOW',
|
|
192
|
+
fix: 'Encapsulate behavior with data',
|
|
193
|
+
documentationLink: 'https://martinfowler.com/bliki/AnemicDomainModel.html',
|
|
194
|
+
}),
|
|
195
|
+
identifyAggregateRoot: (0, eslint_devkit_1.formatLLMMessage)({
|
|
196
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
197
|
+
issueName: 'Identify Aggregate Root',
|
|
198
|
+
description: 'Identify aggregate root and enforce invariants',
|
|
199
|
+
severity: 'LOW',
|
|
200
|
+
fix: 'Define aggregate boundaries and enforce invariants',
|
|
201
|
+
documentationLink: 'https://martinfowler.com/bliki/DDD_Aggregate.html',
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
204
|
+
schema: [
|
|
205
|
+
{
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
minBusinessMethods: {
|
|
209
|
+
type: 'number',
|
|
210
|
+
default: 1,
|
|
211
|
+
minimum: 0,
|
|
212
|
+
description: 'Minimum business methods required',
|
|
213
|
+
},
|
|
214
|
+
ignoreDtos: {
|
|
215
|
+
type: 'boolean',
|
|
216
|
+
default: true,
|
|
217
|
+
description: 'Ignore DTOs and data transfer objects',
|
|
218
|
+
},
|
|
219
|
+
dtoPatterns: {
|
|
220
|
+
type: 'array',
|
|
221
|
+
items: { type: 'string' },
|
|
222
|
+
default: ['DTO', 'Dto', 'Data', 'Request', 'Response', 'Payload'],
|
|
223
|
+
description: 'Patterns for DTO identification',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
additionalProperties: false,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
defaultOptions: [
|
|
231
|
+
{
|
|
232
|
+
minBusinessMethods: 1,
|
|
233
|
+
ignoreDtos: true,
|
|
234
|
+
dtoPatterns: ['DTO', 'Dto', 'Data', 'Request', 'Response', 'Payload'],
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
create(context, [options = {}]) {
|
|
238
|
+
const { minBusinessMethods = 1, ignoreDtos = true, dtoPatterns = ['DTO', 'Dto', 'Data', 'Request', 'Response', 'Payload'], } = options || {};
|
|
239
|
+
/**
|
|
240
|
+
* Check class declarations
|
|
241
|
+
*/
|
|
242
|
+
function checkClass(node) {
|
|
243
|
+
if (!node.id) {
|
|
244
|
+
return; // Anonymous class
|
|
245
|
+
}
|
|
246
|
+
const className = node.id.name;
|
|
247
|
+
// Check if it's a DTO
|
|
248
|
+
if (ignoreDtos && isDto(className, dtoPatterns)) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const businessMethodCount = countBusinessMethods(node);
|
|
252
|
+
if (businessMethodCount < minBusinessMethods) {
|
|
253
|
+
context.report({
|
|
254
|
+
node,
|
|
255
|
+
messageId: 'anemicDomainModel',
|
|
256
|
+
data: {
|
|
257
|
+
className,
|
|
258
|
+
},
|
|
259
|
+
suggest: [
|
|
260
|
+
{
|
|
261
|
+
messageId: 'addBusinessLogic',
|
|
262
|
+
fix: () => null,
|
|
263
|
+
data: {
|
|
264
|
+
className,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
messageId: 'migrateToRichModel',
|
|
269
|
+
fix: () => null,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
messageId: 'identifyAggregateRoot',
|
|
273
|
+
fix: () => null,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
ClassDeclaration: checkClass,
|
|
281
|
+
ClassExpression: checkClass,
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
//# sourceMappingURL=ddd-anemic-domain-model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ddd-anemic-domain-model.js","sourceRoot":"","sources":["../../../../../packages/eslint-plugin-modularity/src/rules/ddd-anemic-domain-model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: ddd-value-object-immutability
|
|
8
|
+
* Validates value objects are properly immutable
|
|
9
|
+
* Priority 1: Domain-Driven Design
|
|
10
|
+
*
|
|
11
|
+
* @see https://martinfowler.com/bliki/ValueObject.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'mutableValueObject' | 'mutableNestedType' | 'addReadonly' | 'useObjectFreeze' | 'makeImmutable';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Patterns to identify value objects. Default: ['Value', 'VO', 'ValueObject'] */
|
|
17
|
+
valueObjectPatterns?: string[];
|
|
18
|
+
/** Require readonly modifiers. Default: true */
|
|
19
|
+
requireReadonly?: boolean;
|
|
20
|
+
/** Check for Object.freeze usage. Default: true */
|
|
21
|
+
checkObjectFreeze?: boolean;
|
|
22
|
+
}
|
|
23
|
+
type RuleOptions = [Options?];
|
|
24
|
+
export declare const dddValueObjectImmutability: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
25
|
+
name: string;
|
|
26
|
+
};
|
|
27
|
+
export {};
|