0
# Workspace Rule Integration
1
2
The @nx/eslint-plugin provides dynamic loading and integration of custom ESLint rules from the workspace's `tools/eslint-rules` directory, enabling project-specific linting extensions while maintaining proper namespacing and TypeScript support.
3
4
## Capabilities
5
6
### Workspace Rules Loading
7
8
Automatically discovers and loads custom ESLint rules from the workspace tools directory.
9
10
```typescript { .api }
11
/**
12
* Dynamically loaded workspace rules with automatic namespacing
13
*/
14
interface WorkspaceRules {
15
/** Current namespacing format */
16
[key: `workspace-${string}`]: TSESLint.RuleModule<string, unknown[]>;
17
18
/** Legacy namespacing format for backwards compatibility */
19
[key: `workspace/${string}`]: TSESLint.RuleModule<string, unknown[]>;
20
}
21
22
/**
23
* Function that loads workspace rules on plugin initialization
24
*/
25
function loadWorkspaceRules(): WorkspaceRules;
26
```
27
28
### Workspace Plugin Structure
29
30
Expected structure for custom workspace rules.
31
32
```typescript { .api }
33
// tools/eslint-rules/index.ts
34
interface WorkspacePlugin {
35
rules: {
36
[ruleName: string]: TSESLint.RuleModule<string, unknown[]>;
37
};
38
}
39
40
// Example workspace plugin
41
export const rules = {
42
"no-barrel-imports": noBarrelImportsRule,
43
"enforce-naming-convention": enforceNamingConventionRule,
44
"restrict-third-party-libs": restrictThirdPartyLibsRule
45
};
46
```
47
48
### Rule Namespacing
49
50
All workspace rules are automatically namespaced to prevent conflicts with official rules.
51
52
```typescript { .api }
53
interface RuleNamespacing {
54
/** Original rule name in workspace */
55
originalName: string;
56
57
/** Current namespaced format */
58
namespacedName: `workspace-${string}`;
59
60
/** Legacy namespaced format (backwards compatibility) */
61
legacyNamespacedName: `workspace/${string}`;
62
}
63
64
// Example transformations:
65
// "no-barrel-imports" → "workspace-no-barrel-imports"
66
// "no-barrel-imports" → "workspace/no-barrel-imports" (legacy)
67
```
68
69
### Constants and Configuration
70
71
Key constants used for workspace rule integration.
72
73
```typescript { .api }
74
/** Path to workspace rules directory relative to workspace root */
75
const WORKSPACE_RULES_PATH: "tools/eslint-rules";
76
77
/** Full path to workspace plugin directory */
78
const WORKSPACE_PLUGIN_DIR: string; // join(workspaceRoot, WORKSPACE_RULES_PATH)
79
80
/** Namespace prefix for workspace rules */
81
const WORKSPACE_RULE_PREFIX: "workspace";
82
```
83
84
**Usage Examples:**
85
86
```typescript
87
// Creating custom workspace rule
88
// tools/eslint-rules/rules/no-barrel-imports.ts
89
import { ESLintUtils } from '@typescript-eslint/utils';
90
91
export default ESLintUtils.RuleCreator(() => '')({
92
name: 'no-barrel-imports',
93
meta: {
94
type: 'problem',
95
docs: {
96
description: 'Disallow barrel exports that re-export everything'
97
},
98
messages: {
99
noBarrelImports: 'Avoid barrel imports that re-export everything'
100
},
101
schema: []
102
},
103
defaultOptions: [],
104
create(context) {
105
return {
106
ExportAllDeclaration(node) {
107
context.report({
108
node,
109
messageId: 'noBarrelImports'
110
});
111
}
112
};
113
}
114
});
115
116
// tools/eslint-rules/index.ts
117
import noBarrelImports from './rules/no-barrel-imports';
118
119
export const rules = {
120
'no-barrel-imports': noBarrelImports
121
};
122
123
// Using workspace rule in ESLint config
124
module.exports = {
125
rules: {
126
'@nx/workspace-no-barrel-imports': 'error'
127
}
128
};
129
130
// Flat config usage
131
import nxPlugin from '@nx/eslint-plugin';
132
133
export default [
134
{
135
plugins: {
136
'@nx': nxPlugin
137
},
138
rules: {
139
'@nx/workspace-no-barrel-imports': 'error'
140
}
141
}
142
];
143
```
144
145
### TypeScript Integration
146
147
Workspace rules support full TypeScript compilation and type checking.
148
149
```typescript { .api }
150
interface TypeScriptIntegration {
151
/** TypeScript configuration for workspace rules */
152
tsConfigPath: string; // tools/eslint-rules/tsconfig.json
153
154
/** Automatic TypeScript project registration */
155
projectRegistration: () => (() => void) | undefined;
156
157
/** Compilation and loading process */
158
compilationSupport: boolean;
159
}
160
161
// Example tsconfig.json for workspace rules
162
{
163
"extends": "../../tsconfig.base.json",
164
"compilerOptions": {
165
"module": "commonjs",
166
"target": "es2020",
167
"lib": ["es2020"],
168
"declaration": false,
169
"strict": true,
170
"noImplicitReturns": true,
171
"noFallthroughCasesInSwitch": true
172
},
173
"include": ["**/*.ts"],
174
"exclude": ["**/*.spec.ts"]
175
}
176
```
177
178
### Error Handling and Fallbacks
179
180
Robust error handling ensures the plugin continues to work even when workspace rules fail to load.
181
182
```typescript { .api }
183
interface ErrorHandling {
184
/** Fallback when tools/eslint-rules doesn't exist */
185
missingDirectory: () => WorkspaceRules; // Returns {}
186
187
/** Fallback when loading fails */
188
loadingError: (error: Error) => WorkspaceRules; // Logs error, returns {}
189
190
/** Cleanup function for TypeScript registration */
191
cleanup: () => void;
192
}
193
```
194
195
**Usage Examples:**
196
197
```typescript
198
// Complex workspace rule with options
199
// tools/eslint-rules/rules/enforce-project-structure.ts
200
import { ESLintUtils } from '@typescript-eslint/utils';
201
202
interface Options {
203
allowedDirectories: string[];
204
bannedPatterns: string[];
205
}
206
207
export default ESLintUtils.RuleCreator(() => '')({
208
name: 'enforce-project-structure',
209
meta: {
210
type: 'problem',
211
docs: {
212
description: 'Enforce project structure conventions'
213
},
214
schema: [{
215
type: 'object',
216
properties: {
217
allowedDirectories: {
218
type: 'array',
219
items: { type: 'string' }
220
},
221
bannedPatterns: {
222
type: 'array',
223
items: { type: 'string' }
224
}
225
},
226
additionalProperties: false
227
}],
228
messages: {
229
invalidDirectory: 'Files in {{directory}} are not allowed',
230
bannedPattern: 'File matches banned pattern: {{pattern}}'
231
}
232
},
233
defaultOptions: [{
234
allowedDirectories: ['src', 'lib'],
235
bannedPatterns: []
236
}],
237
create(context, [options]) {
238
return {
239
Program(node) {
240
const filename = context.getFilename();
241
// Implementation logic
242
}
243
};
244
}
245
});
246
247
// Using in ESLint config with options
248
module.exports = {
249
rules: {
250
'@nx/workspace-enforce-project-structure': [
251
'error',
252
{
253
allowedDirectories: ['src', 'lib', 'test'],
254
bannedPatterns: ['**/legacy/**', '**/*.old.*']
255
}
256
]
257
}
258
};
259
```
260
261
### Integration with Nx Plugin System
262
263
Workspace rules integrate seamlessly with Nx's broader plugin ecosystem:
264
265
- **Project Graph Access**: Rules can access the Nx project graph for workspace-aware linting
266
- **Configuration Integration**: Rules can read Nx project configurations
267
- **Build System Integration**: Rules can integrate with Nx build targets and caching
268
- **Migration Support**: Rules can participate in Nx migration workflows
269
270
### Best Practices for Workspace Rules
271
272
Guidelines for creating effective workspace-specific rules:
273
274
1. **Namespace Carefully**: Use descriptive names that won't conflict with future official rules
275
2. **TypeScript First**: Write rules in TypeScript for better maintainability
276
3. **Error Handling**: Provide clear error messages and handle edge cases
277
4. **Performance**: Consider performance impact on large workspaces
278
5. **Testing**: Include comprehensive tests for workspace rules
279
6. **Documentation**: Document rule purpose and configuration options
280
281
### Backwards Compatibility
282
283
The plugin maintains backwards compatibility for existing workspace rule configurations:
284
285
```typescript
286
// Both formats work:
287
module.exports = {
288
rules: {
289
'@nx/workspace-my-rule': 'error', // Current format
290
'@nx/workspace/my-rule': 'error' // Legacy format (still supported)
291
}
292
};
293
```