ESLint Configuration
This guide covers all ESLint configuration options for Stricture, from simple zero-config setups to advanced patterns with defineConfig() and extends.
Quick Reference
Choose your configuration style:
| Style | Best For | Example |
|---|---|---|
| Zero-Config | Using a preset without customization | stricture.configs.hexagonal() |
| Inline Customization | Adding a few custom rules | stricture.configs.hexagonal({ rules: [...] }) |
| Separate Config File | Complex configs (10+ custom rules) | stricture.configs.recommended() + .stricture/config.json |
| Per-File Configs | Different rules for different file types | Using defineConfig() with extends |
Modern Setup (Flat Config)
Level 1: Zero-Config with Preset
The simplest approach - just use a preset:
import stricture from '@stricture/eslint-plugin'
export default [ stricture.configs.hexagonal()]Available presets:
stricture.configs.hexagonal()- Ports & Adaptersstricture.configs.layered()- N-tier Architecturestricture.configs.clean()- Clean Architecturestricture.configs.modular()- Module-basedstricture.configs.nextjs()- Next.js App Routerstricture.configs.nestjs()- NestJS framework
Level 2: Inline Customization
Add overrides directly in your ESLint config:
import stricture from '@stricture/eslint-plugin'
export default [ stricture.configs.hexagonal({ // Ignore test files ignorePatterns: ['**/*.test.ts', '**/*.spec.ts'],
// Add custom rules rules: [ { id: 'no-legacy-imports', from: { pattern: 'src/**', exclude: ['src/legacy/**'] }, to: { pattern: 'src/legacy/**' }, allowed: false, message: 'No new code should depend on legacy modules' } ],
// Add custom boundaries boundaries: [ { name: 'shared', pattern: 'src/shared/**', mode: 'folder', tags: ['shared'] } ] })]Level 3: Separate Config File
For complex configurations (10+ custom boundaries/rules):
import stricture from '@stricture/eslint-plugin'
export default [ stricture.configs.recommended() // Reads .stricture/config.json]{ "preset": "@stricture/hexagonal", "boundaries": [ { "name": "domain", "pattern": "src/core/domain/**", "mode": "file", "tags": ["domain"] }, { "name": "infrastructure", "pattern": "src/infrastructure/**", "mode": "file", "tags": ["infrastructure"] } ], "rules": [ { "id": "domain-isolation", "name": "Domain Isolation", "severity": "error", "from": { "tag": "domain" }, "to": { "tag": "infrastructure" }, "allowed": false, "message": "Domain cannot depend on infrastructure" } ]}See the Configuration Files Guide for details on .stricture/config.json.
Advanced: Per-File Configurations
Using defineConfig() and extends
Requires: ESLint 9.17+ or @eslint/config-helpers
Apply different configurations to different file sets:
import { defineConfig } from 'eslint/config'import stricture from '@stricture/eslint-plugin'
export default defineConfig([ // Configuration for source files { files: ['src/**/*.ts'], extends: [stricture.configs.hexagonal()] },
// Relaxed rules for test files { files: ['tests/**/*.ts', '**/*.test.ts'], extends: [stricture.configs.hexagonal({ rules: [ { id: 'allow-test-imports', from: { pattern: 'tests/**' }, to: { pattern: 'src/**' }, allowed: true } ] })] },
// Different preset for scripts { files: ['scripts/**/*.ts'], extends: [stricture.configs.modular()] }])Benefits of defineConfig()
- Type safety - Full TypeScript IntelliSense for config
- Automatic flattening - No need for spread operators (
...) - Clearer structure - Easier to see which rules apply where
Using with globalIgnores()
import { defineConfig, globalIgnores } from 'eslint/config'import stricture from '@stricture/eslint-plugin'
export default defineConfig([ // Global ignores (never linted) globalIgnores(['dist', 'build', 'node_modules', '.next']),
// Main configuration { files: ['**/*.ts', '**/*.tsx'], extends: [stricture.configs.nextjs()] }])Combining Multiple Configs
import { defineConfig } from 'eslint/config'import stricture from '@stricture/eslint-plugin'import tseslint from 'typescript-eslint'
export default defineConfig([ ...tseslint.configs.recommended, { files: ['src/**/*.ts'], extends: [stricture.configs.hexagonal()], rules: { '@typescript-eslint/no-explicit-any': 'warn', '@stricture/enforce-boundaries': 'error' } }])Legacy ESLint Config
Using .eslintrc.js
const stricture = require('@stricture/eslint-plugin')
module.exports = { extends: ['plugin:@stricture/recommended'], rules: { '@stricture/enforce-boundaries': ['error', { inlineConfig: require('@stricture/hexagonal').default }] }}Using .eslintrc.json
{ "plugins": ["@stricture"], "rules": { "@stricture/enforce-boundaries": "error" }}You’ll need .stricture/config.json for this approach.
Migration to Flat Config
We recommend migrating to flat config for:
- Better TypeScript support
- Simpler configuration
- Inline preset configuration
- Modern ESLint features
Migration steps:
-
Create
eslint.config.js:import stricture from '@stricture/eslint-plugin'export default [stricture.configs.hexagonal()] -
Delete
.eslintrc.*files -
Update
package.jsonscripts if needed:{"scripts": {"lint": "eslint ."}}
See ESLint’s migration guide for details.
Configuration Priority
When multiple configuration methods are present, Stricture uses this priority:
-
Inline config (highest priority)
stricture.configs.hexagonal({ rules: [...] }) -
File config
stricture.configs.recommended() // reads .stricture/config.json -
No config found → Error
Common Patterns
Pattern 1: Framework + Architecture
Combine framework-specific and architecture presets:
import { defineConfig } from 'eslint/config'import stricture from '@stricture/eslint-plugin'
export default defineConfig([ { files: ['app/**/*.tsx', 'app/**/*.ts'], extends: [stricture.configs.nextjs()] }, { files: ['src/**/*.ts'], extends: [stricture.configs.hexagonal()] }])Pattern 2: Monorepo with Different Presets
import stricture from '@stricture/eslint-plugin'export default [stricture.configs.hexagonal()]import stricture from '@stricture/eslint-plugin'export default [stricture.configs.nextjs()]Pattern 3: Gradual Adoption
Start with warnings, not errors:
import stricture from '@stricture/eslint-plugin'
export default [ { plugins: { '@stricture': stricture }, rules: { '@stricture/enforce-boundaries': 'warn' // Warn first } }]Then use .stricture/config.json to set severity per-rule:
{ "preset": "@stricture/hexagonal", "rules": [ { "id": "domain-isolation", "severity": "error", // Critical rules as errors "from": { "tag": "domain" }, "to": { "pattern": "**" }, "allowed": false }, { "id": "adapter-ports", "severity": "warn", // Less critical as warnings "from": { "tag": "adapters" }, "to": { "tag": "domain" }, "allowed": false } ]}Pattern 4: TypeScript Project References
For projects using TypeScript project references:
import { defineConfig } from 'eslint/config'import stricture from '@stricture/eslint-plugin'
export default defineConfig([ { files: ['packages/core/**/*.ts'], extends: [stricture.configs.hexagonal()], languageOptions: { parserOptions: { project: './packages/core/tsconfig.json' } } }, { files: ['packages/ui/**/*.tsx'], extends: [stricture.configs.modular()], languageOptions: { parserOptions: { project: './packages/ui/tsconfig.json' } } }])Rule Options
The @stricture/enforce-boundaries rule accepts these options:
{ rules: { '@stricture/enforce-boundaries': ['error', { // Inline configuration (takes priority over config file) inlineConfig?: StrictureConfig,
// Path to config file (default: '.stricture/config.json') configPath?: string,
// Base URL for path resolution (default: './') baseUrl?: string,
// Check dynamic imports (default: true) checkDynamicImports?: boolean }] }}Example with options:
import stricture from '@stricture/eslint-plugin'
export default [ { plugins: { '@stricture': stricture }, rules: { '@stricture/enforce-boundaries': ['error', { configPath: './architecture/boundaries.json', baseUrl: './src', checkDynamicImports: true }] } }]Troubleshooting
Config not loading
Problem: Stricture doesn’t seem to read my configuration.
Solution:
- Check config file location (should be in project root)
- Verify JSON syntax is valid
- Ensure you’re calling the config function:
stricture.configs.hexagonal()notstricture.configs.hexagonal - Run with
--debugflag:npx eslint --debug .
Types not working with defineConfig
Problem: No TypeScript IntelliSense for ESLint config.
Solution:
- Upgrade to ESLint 9.17+ or install
@eslint/config-helpers - Use
eslint.config.tsinstead ofeslint.config.js - Import from correct location:
import { defineConfig } from 'eslint/config' // ESLint 9.17+// orimport { defineConfig } from '@eslint/config-helpers' // Older versions
Next Steps
- Configuration Files - Deep dive into
.stricture/config.json - Boundaries - Define architectural boundaries
- Rules - Create boundary enforcement rules
- Presets - Explore available architecture presets
For more help, see the Troubleshooting Guide.