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 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
@@ -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 {};