ESLint plugin for Nx monorepos with boundary enforcement and dependency management rules.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Automatically discovers and loads custom ESLint rules from the workspace tools directory.
/**
* Dynamically loaded workspace rules with automatic namespacing
*/
interface WorkspaceRules {
/** Current namespacing format */
[key: `workspace-${string}`]: TSESLint.RuleModule<string, unknown[]>;
/** Legacy namespacing format for backwards compatibility */
[key: `workspace/${string}`]: TSESLint.RuleModule<string, unknown[]>;
}
/**
* Function that loads workspace rules on plugin initialization
*/
function loadWorkspaceRules(): WorkspaceRules;Expected structure for custom workspace rules.
// tools/eslint-rules/index.ts
interface WorkspacePlugin {
rules: {
[ruleName: string]: TSESLint.RuleModule<string, unknown[]>;
};
}
// Example workspace plugin
export const rules = {
"no-barrel-imports": noBarrelImportsRule,
"enforce-naming-convention": enforceNamingConventionRule,
"restrict-third-party-libs": restrictThirdPartyLibsRule
};All workspace rules are automatically namespaced to prevent conflicts with official rules.
interface RuleNamespacing {
/** Original rule name in workspace */
originalName: string;
/** Current namespaced format */
namespacedName: `workspace-${string}`;
/** Legacy namespaced format (backwards compatibility) */
legacyNamespacedName: `workspace/${string}`;
}
// Example transformations:
// "no-barrel-imports" → "workspace-no-barrel-imports"
// "no-barrel-imports" → "workspace/no-barrel-imports" (legacy)Key constants used for workspace rule integration.
/** Path to workspace rules directory relative to workspace root */
const WORKSPACE_RULES_PATH: "tools/eslint-rules";
/** Full path to workspace plugin directory */
const WORKSPACE_PLUGIN_DIR: string; // join(workspaceRoot, WORKSPACE_RULES_PATH)
/** Namespace prefix for workspace rules */
const WORKSPACE_RULE_PREFIX: "workspace";Usage Examples:
// Creating custom workspace rule
// tools/eslint-rules/rules/no-barrel-imports.ts
import { ESLintUtils } from '@typescript-eslint/utils';
export default ESLintUtils.RuleCreator(() => '')({
name: 'no-barrel-imports',
meta: {
type: 'problem',
docs: {
description: 'Disallow barrel exports that re-export everything'
},
messages: {
noBarrelImports: 'Avoid barrel imports that re-export everything'
},
schema: []
},
defaultOptions: [],
create(context) {
return {
ExportAllDeclaration(node) {
context.report({
node,
messageId: 'noBarrelImports'
});
}
};
}
});
// tools/eslint-rules/index.ts
import noBarrelImports from './rules/no-barrel-imports';
export const rules = {
'no-barrel-imports': noBarrelImports
};
// Using workspace rule in ESLint config
module.exports = {
rules: {
'@nx/workspace-no-barrel-imports': 'error'
}
};
// Flat config usage
import nxPlugin from '@nx/eslint-plugin';
export default [
{
plugins: {
'@nx': nxPlugin
},
rules: {
'@nx/workspace-no-barrel-imports': 'error'
}
}
];Workspace rules support full TypeScript compilation and type checking.
interface TypeScriptIntegration {
/** TypeScript configuration for workspace rules */
tsConfigPath: string; // tools/eslint-rules/tsconfig.json
/** Automatic TypeScript project registration */
projectRegistration: () => (() => void) | undefined;
/** Compilation and loading process */
compilationSupport: boolean;
}
// Example tsconfig.json for workspace rules
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"target": "es2020",
"lib": ["es2020"],
"declaration": false,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts"],
"exclude": ["**/*.spec.ts"]
}Robust error handling ensures the plugin continues to work even when workspace rules fail to load.
interface ErrorHandling {
/** Fallback when tools/eslint-rules doesn't exist */
missingDirectory: () => WorkspaceRules; // Returns {}
/** Fallback when loading fails */
loadingError: (error: Error) => WorkspaceRules; // Logs error, returns {}
/** Cleanup function for TypeScript registration */
cleanup: () => void;
}Usage Examples:
// Complex workspace rule with options
// tools/eslint-rules/rules/enforce-project-structure.ts
import { ESLintUtils } from '@typescript-eslint/utils';
interface Options {
allowedDirectories: string[];
bannedPatterns: string[];
}
export default ESLintUtils.RuleCreator(() => '')({
name: 'enforce-project-structure',
meta: {
type: 'problem',
docs: {
description: 'Enforce project structure conventions'
},
schema: [{
type: 'object',
properties: {
allowedDirectories: {
type: 'array',
items: { type: 'string' }
},
bannedPatterns: {
type: 'array',
items: { type: 'string' }
}
},
additionalProperties: false
}],
messages: {
invalidDirectory: 'Files in {{directory}} are not allowed',
bannedPattern: 'File matches banned pattern: {{pattern}}'
}
},
defaultOptions: [{
allowedDirectories: ['src', 'lib'],
bannedPatterns: []
}],
create(context, [options]) {
return {
Program(node) {
const filename = context.getFilename();
// Implementation logic
}
};
}
});
// Using in ESLint config with options
module.exports = {
rules: {
'@nx/workspace-enforce-project-structure': [
'error',
{
allowedDirectories: ['src', 'lib', 'test'],
bannedPatterns: ['**/legacy/**', '**/*.old.*']
}
]
}
};Workspace rules integrate seamlessly with Nx's broader plugin ecosystem:
Guidelines for creating effective workspace-specific rules:
The plugin maintains backwards compatibility for existing workspace rule configurations:
// Both formats work:
module.exports = {
rules: {
'@nx/workspace-my-rule': 'error', // Current format
'@nx/workspace/my-rule': 'error' // Legacy format (still supported)
}
};Install with Tessl CLI
npx tessl i tessl/npm-nx--eslint-plugin