@jterrazz/codestyle 1.0.0 → 1.1.1
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 +38 -88
- package/package.json +4 -5
- package/src/codestyle.sh +16 -5
- package/src/oxlint/architectures/hexagonal.json +65 -0
- package/src/oxlint/base.json +66 -22
- package/src/oxlint/expo.json +11 -1
- package/src/oxlint/plugins/architecture-boundaries.js +86 -0
package/README.md
CHANGED
|
@@ -1,125 +1,75 @@
|
|
|
1
|
-
_Hey there - I'm Jean-Baptiste, just another developer doing weird things with code. All my projects live on [jterrazz.com](https://jterrazz.com) - complete with backstories and lessons learned. Feel free to poke around - you might just find something useful!_
|
|
2
|
-
|
|
3
1
|
# @jterrazz/codestyle
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
3
|
+
Fast, opinionated linting and formatting for TypeScript. Powered by Oxlint (2-3x faster than ESLint) and Oxfmt.
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
## Quick Start
|
|
10
6
|
|
|
11
7
|
```bash
|
|
12
8
|
npm install @jterrazz/codestyle --save-dev
|
|
13
9
|
```
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
### Oxlint Configuration
|
|
18
|
-
|
|
19
|
-
Choose the configuration that matches your environment:
|
|
20
|
-
|
|
21
|
-
**For Node.js projects:**
|
|
11
|
+
Create `.oxlintrc.json`:
|
|
22
12
|
|
|
23
13
|
```json
|
|
24
|
-
// oxlint.json
|
|
25
14
|
{
|
|
26
15
|
"extends": ["@jterrazz/codestyle/oxlint/node"]
|
|
27
16
|
}
|
|
28
17
|
```
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
```json
|
|
33
|
-
// oxlint.json
|
|
34
|
-
{
|
|
35
|
-
"extends": ["@jterrazz/codestyle/oxlint/expo"]
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**For Next.js projects:**
|
|
40
|
-
|
|
41
|
-
```json
|
|
42
|
-
// oxlint.json
|
|
43
|
-
{
|
|
44
|
-
"extends": ["@jterrazz/codestyle/oxlint/nextjs"]
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Oxfmt Configuration
|
|
19
|
+
Run:
|
|
49
20
|
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"extends": ["@jterrazz/codestyle/oxfmt"]
|
|
54
|
-
}
|
|
21
|
+
```bash
|
|
22
|
+
npx codestyle # Check everything
|
|
23
|
+
npx codestyle --fix # Fix everything
|
|
55
24
|
```
|
|
56
25
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
### Environment-Specific Configurations
|
|
60
|
-
|
|
61
|
-
**Node.js Configuration:**
|
|
26
|
+
## Configurations
|
|
62
27
|
|
|
63
|
-
|
|
64
|
-
|
|
28
|
+
| Config | Use Case |
|
|
29
|
+
| ----------------------------------- | --------------------------------- |
|
|
30
|
+
| `@jterrazz/codestyle/oxlint/node` | Node.js (requires .js extensions) |
|
|
31
|
+
| `@jterrazz/codestyle/oxlint/expo` | Expo / React Native |
|
|
32
|
+
| `@jterrazz/codestyle/oxlint/nextjs` | Next.js |
|
|
65
33
|
|
|
66
|
-
|
|
34
|
+
## What's Included
|
|
67
35
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
36
|
+
- TypeScript strict mode with `type` imports
|
|
37
|
+
- Import sorting and grouping (perfectionist)
|
|
38
|
+
- Named exports enforcement (no default exports)
|
|
39
|
+
- Performance warnings (spread in loops, etc.)
|
|
40
|
+
- Style consistency (curly braces, comments, naming)
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- No file extensions in imports (Turbopack compatibility, auto-fixed)
|
|
74
|
-
- React and Next.js plugins enabled
|
|
75
|
-
|
|
76
|
-
### Shared Features
|
|
77
|
-
|
|
78
|
-
- **TypeScript**: Strict type checking with consistent type imports
|
|
79
|
-
- **Import Sorting**: Automated import organization with architectural grouping (via perfectionist)
|
|
80
|
-
- **Code Quality**: Perfectionist plugin for consistent code style
|
|
81
|
-
- **Performance**: Oxlint is 50-100x faster than ESLint, Oxfmt is 10x faster than Prettier, tsgo is 10x faster than tsc
|
|
82
|
-
|
|
83
|
-
## CLI Tools
|
|
84
|
-
|
|
85
|
-
This package includes a CLI tool for running quality checks:
|
|
42
|
+
## CLI
|
|
86
43
|
|
|
87
44
|
```bash
|
|
88
|
-
# Run all
|
|
89
|
-
npx codestyle
|
|
90
|
-
|
|
91
|
-
# Automatically fix all fixable issues (Oxlint --fix, Oxfmt format)
|
|
92
|
-
npx codestyle --fix
|
|
93
|
-
|
|
94
|
-
# Run individual checks
|
|
95
|
-
npx codestyle --type # TypeScript type checking only
|
|
96
|
-
npx codestyle --lint # Linting only
|
|
97
|
-
npx codestyle --format # Format checking only
|
|
45
|
+
npx codestyle # Run all checks (type + lint + format)
|
|
46
|
+
npx codestyle --fix # Auto-fix all issues
|
|
98
47
|
|
|
99
|
-
#
|
|
100
|
-
npx codestyle --lint
|
|
101
|
-
npx codestyle --format
|
|
48
|
+
npx codestyle --type # TypeScript only
|
|
49
|
+
npx codestyle --lint # Lint only
|
|
50
|
+
npx codestyle --format # Format only
|
|
102
51
|
```
|
|
103
52
|
|
|
104
|
-
##
|
|
53
|
+
## Architecture Enforcement (Optional)
|
|
105
54
|
|
|
106
|
-
|
|
55
|
+
Enforce hexagonal architecture boundaries:
|
|
107
56
|
|
|
108
57
|
```json
|
|
109
58
|
{
|
|
110
|
-
"
|
|
111
|
-
"codestyle"
|
|
112
|
-
"codestyle
|
|
113
|
-
|
|
59
|
+
"extends": [
|
|
60
|
+
"@jterrazz/codestyle/oxlint/node",
|
|
61
|
+
"@jterrazz/codestyle/oxlint/architectures/hexagonal"
|
|
62
|
+
]
|
|
114
63
|
}
|
|
115
64
|
```
|
|
116
65
|
|
|
117
|
-
|
|
66
|
+
Rules enforced:
|
|
118
67
|
|
|
119
|
-
|
|
68
|
+
- `domain/` cannot import from other layers
|
|
69
|
+
- `application/` cannot import infrastructure
|
|
70
|
+
- `presentation/ui/` cannot import navigation
|
|
71
|
+
- `features/` cannot import other features
|
|
120
72
|
|
|
121
|
-
|
|
122
|
-
- **Environment-specific**: Tailored rules for Node.js, Expo/React Native, and Next.js
|
|
123
|
-
- **Custom plugins**: JS plugins for import extension enforcement
|
|
73
|
+
---
|
|
124
74
|
|
|
125
|
-
|
|
75
|
+
By [Jean-Baptiste Terrazzoni](https://jterrazz.com)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/codestyle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codestyle": "./src/codestyle.sh"
|
|
@@ -15,16 +15,15 @@
|
|
|
15
15
|
"./oxlint/node": "./src/oxlint/node.json",
|
|
16
16
|
"./oxlint/expo": "./src/oxlint/expo.json",
|
|
17
17
|
"./oxlint/nextjs": "./src/oxlint/nextjs.json",
|
|
18
|
+
"./oxlint/architectures/hexagonal": "./src/oxlint/architectures/hexagonal.json",
|
|
18
19
|
"./oxfmt": "./src/oxfmt/index.json"
|
|
19
20
|
},
|
|
20
21
|
"publishConfig": {
|
|
21
22
|
"registry": "https://registry.npmjs.org/"
|
|
22
23
|
},
|
|
23
24
|
"scripts": {
|
|
24
|
-
"lint": "oxlint --ignore-pattern '**/fixtures/**'",
|
|
25
|
-
"lint:fix": "oxlint --fix --ignore-pattern '**/fixtures/**'",
|
|
26
|
-
"format": "oxfmt",
|
|
27
|
-
"format:check": "oxfmt --check",
|
|
25
|
+
"lint": "oxlint --ignore-pattern '**/fixtures/**' && oxfmt --check",
|
|
26
|
+
"lint:fix": "oxlint --fix --ignore-pattern '**/fixtures/**' && oxfmt",
|
|
28
27
|
"test": "vitest --run",
|
|
29
28
|
"test:watch": "vitest",
|
|
30
29
|
"build": "# no build script"
|
package/src/codestyle.sh
CHANGED
|
@@ -63,25 +63,36 @@ type_pid=""
|
|
|
63
63
|
code_pid=""
|
|
64
64
|
style_pid=""
|
|
65
65
|
|
|
66
|
+
# Find the node_modules/.bin directory relative to this script
|
|
67
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
68
|
+
# Find bin directory: installed package or development
|
|
69
|
+
if [ -x "$SCRIPT_DIR/../../.bin/oxlint" ]; then
|
|
70
|
+
BIN_DIR="$SCRIPT_DIR/../../.bin"
|
|
71
|
+
elif [ -x "$SCRIPT_DIR/../node_modules/.bin/oxlint" ]; then
|
|
72
|
+
BIN_DIR="$SCRIPT_DIR/../node_modules/.bin"
|
|
73
|
+
else
|
|
74
|
+
BIN_DIR="$(npm bin 2>/dev/null)"
|
|
75
|
+
fi
|
|
76
|
+
|
|
66
77
|
if [ "$RUN_TYPE" = true ]; then
|
|
67
|
-
tsgo --noEmit "${EXTRA_ARGS[@]}" > "$tmp_dir/type.log" 2>&1 &
|
|
78
|
+
"$BIN_DIR/tsgo" --noEmit "${EXTRA_ARGS[@]}" > "$tmp_dir/type.log" 2>&1 &
|
|
68
79
|
type_pid=$!
|
|
69
80
|
fi
|
|
70
81
|
|
|
71
82
|
if [ "$RUN_LINT" = true ]; then
|
|
72
83
|
if [ "$FIX_MODE" = true ]; then
|
|
73
|
-
oxlint --fix "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
|
|
84
|
+
"$BIN_DIR/oxlint" --fix "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
|
|
74
85
|
else
|
|
75
|
-
oxlint "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
|
|
86
|
+
"$BIN_DIR/oxlint" "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/code.log" 2>&1 &
|
|
76
87
|
fi
|
|
77
88
|
code_pid=$!
|
|
78
89
|
fi
|
|
79
90
|
|
|
80
91
|
if [ "$RUN_FORMAT" = true ]; then
|
|
81
92
|
if [ "$FIX_MODE" = true ]; then
|
|
82
|
-
oxfmt "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
|
|
93
|
+
"$BIN_DIR/oxfmt" "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
|
|
83
94
|
else
|
|
84
|
-
oxfmt --check "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
|
|
95
|
+
"$BIN_DIR/oxfmt" --check "${EXTRA_ARGS[@]:-.}" > "$tmp_dir/style.log" 2>&1 &
|
|
85
96
|
fi
|
|
86
97
|
style_pid=$!
|
|
87
98
|
fi
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
+
"jsPlugins": ["../plugins/architecture-boundaries.js"],
|
|
4
|
+
"rules": {
|
|
5
|
+
// Hexagonal Architecture (Ports & Adapters)
|
|
6
|
+
//
|
|
7
|
+
// Dependency rule: outer layers depend on inner layers, never the reverse
|
|
8
|
+
// All layers CAN import domain (it's the core)
|
|
9
|
+
//
|
|
10
|
+
// ┌─────────────────────────────────────────┐
|
|
11
|
+
// │ infrastructure / presentation (outer) │
|
|
12
|
+
// │ ┌─────────────────────────────────┐ │
|
|
13
|
+
// │ │ application (use-cases, ports) │ │
|
|
14
|
+
// │ │ ┌─────────────────────────┐ │ │
|
|
15
|
+
// │ │ │ domain (core business) │ │ │
|
|
16
|
+
// │ │ └─────────────────────────┘ │ │
|
|
17
|
+
// │ └─────────────────────────────────┘ │
|
|
18
|
+
// └─────────────────────────────────────────┘
|
|
19
|
+
|
|
20
|
+
"architecture-boundaries/architecture-boundaries": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
"rules": [
|
|
24
|
+
{
|
|
25
|
+
"from": "/domain/",
|
|
26
|
+
"disallow": [
|
|
27
|
+
"/application/",
|
|
28
|
+
"/infrastructure/",
|
|
29
|
+
"/presentation/",
|
|
30
|
+
"/di/",
|
|
31
|
+
"/config/",
|
|
32
|
+
"/generated/"
|
|
33
|
+
],
|
|
34
|
+
"message": "Domain layer must be pure - cannot import from other layers"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"from": "/application/use-cases/",
|
|
38
|
+
"disallow": ["/infrastructure/", "/presentation/", "/di/"],
|
|
39
|
+
"message": "Use cases can only depend on domain and ports"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"from": "/application/ports/",
|
|
43
|
+
"disallow": ["/infrastructure/", "/presentation/", "/di/"],
|
|
44
|
+
"message": "Ports are interfaces - cannot depend on implementations"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"from": "/infrastructure/inbound/",
|
|
48
|
+
"disallow": ["/infrastructure/outbound/"],
|
|
49
|
+
"message": "Inbound adapters should not import outbound adapters - use DI"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"from": "/presentation/ui/(atoms|molecules)/",
|
|
53
|
+
"disallow": ["/navigation/"],
|
|
54
|
+
"message": "Atoms and molecules must be pure - no navigation imports"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"from": "/presentation/features/",
|
|
58
|
+
"disallow": ["/presentation/features/(?!common)"],
|
|
59
|
+
"message": "Features should be independent - use shared code in features/common"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/oxlint/base.json
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
-
"plugins": ["typescript", "import", "oxc"],
|
|
3
|
+
"plugins": ["typescript", "import", "oxc", "unicorn"],
|
|
4
4
|
"jsPlugins": ["eslint-plugin-perfectionist"],
|
|
5
5
|
"categories": {
|
|
6
6
|
"correctness": "error",
|
|
7
|
-
"suspicious": "
|
|
7
|
+
"suspicious": "error",
|
|
8
8
|
"perf": "warn",
|
|
9
9
|
"pedantic": "off",
|
|
10
10
|
"style": "warn",
|
|
11
11
|
"nursery": "off"
|
|
12
12
|
},
|
|
13
13
|
"rules": {
|
|
14
|
+
// ============================================
|
|
15
|
+
// ENABLED RULES
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
14
18
|
"no-unused-vars": "error",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"func-style": "off",
|
|
18
|
-
"no-magic-numbers": "off",
|
|
19
|
-
"import/no-named-export": "off",
|
|
20
|
-
"import/no-anonymous-default-export": "off",
|
|
21
|
-
"import/group-exports": "off",
|
|
22
|
-
"no-ternary": "off",
|
|
23
|
-
"init-declarations": "off",
|
|
24
|
-
"capitalized-comments": "off",
|
|
25
|
-
"id-length": "off",
|
|
26
|
-
"typescript/consistent-type-definitions": "off",
|
|
19
|
+
|
|
20
|
+
// -- TypeScript --
|
|
27
21
|
"typescript/consistent-type-imports": [
|
|
28
22
|
"error",
|
|
29
23
|
{
|
|
@@ -40,19 +34,14 @@
|
|
|
40
34
|
"allowTaggedTemplates": true
|
|
41
35
|
}
|
|
42
36
|
],
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
|
|
38
|
+
// -- Perfectionist (sorting/ordering) --
|
|
45
39
|
"perfectionist/sort-heritage-clauses": ["error", { "type": "natural" }],
|
|
46
40
|
"perfectionist/sort-intersection-types": ["error", { "type": "natural" }],
|
|
47
41
|
"perfectionist/sort-jsx-props": ["error", { "type": "natural" }],
|
|
48
42
|
"perfectionist/sort-named-exports": ["error", { "type": "natural" }],
|
|
49
43
|
"perfectionist/sort-named-imports": ["error", { "type": "natural" }],
|
|
50
|
-
"perfectionist/sort-switch-case": ["error", { "type": "natural" }],
|
|
51
44
|
"perfectionist/sort-union-types": ["error", { "type": "natural" }],
|
|
52
|
-
"perfectionist/sort-variable-declarations": [
|
|
53
|
-
"error",
|
|
54
|
-
{ "type": "natural" }
|
|
55
|
-
],
|
|
56
45
|
"perfectionist/sort-imports": [
|
|
57
46
|
"error",
|
|
58
47
|
{
|
|
@@ -68,7 +57,62 @@
|
|
|
68
57
|
"unknown"
|
|
69
58
|
]
|
|
70
59
|
}
|
|
71
|
-
]
|
|
60
|
+
],
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// DISABLED - Conflicts with perfectionist
|
|
64
|
+
// ============================================
|
|
65
|
+
|
|
66
|
+
"sort-keys": "off",
|
|
67
|
+
"sort-imports": "off",
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// DISABLED - Too strict / opinionated
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
"func-style": "off",
|
|
74
|
+
"no-magic-numbers": "off",
|
|
75
|
+
"no-ternary": "off",
|
|
76
|
+
"init-declarations": "off",
|
|
77
|
+
"id-length": "off",
|
|
78
|
+
"max-statements": "off",
|
|
79
|
+
"max-params": "off",
|
|
80
|
+
"no-continue": "off",
|
|
81
|
+
"arrow-body-style": "off",
|
|
82
|
+
"prefer-destructuring": "off",
|
|
83
|
+
"typescript/consistent-type-definitions": "off",
|
|
84
|
+
"typescript/array-type": "off",
|
|
85
|
+
"unicorn/no-null": "off",
|
|
86
|
+
"unicorn/no-array-sort": "off",
|
|
87
|
+
|
|
88
|
+
// ============================================
|
|
89
|
+
// ENABLED - Code style enforcement
|
|
90
|
+
// ============================================
|
|
91
|
+
|
|
92
|
+
"capitalized-comments": "warn",
|
|
93
|
+
"new-cap": "error",
|
|
94
|
+
"curly": "error",
|
|
95
|
+
"no-nested-ternary": "error",
|
|
96
|
+
"unicorn/catch-error-name": "error",
|
|
97
|
+
"unicorn/numeric-separators-style": "warn",
|
|
98
|
+
|
|
99
|
+
// ============================================
|
|
100
|
+
// DISABLED - Conflicts with other rules
|
|
101
|
+
// ============================================
|
|
102
|
+
|
|
103
|
+
"import/no-named-export": "off",
|
|
104
|
+
"import/consistent-type-specifier-style": "off",
|
|
105
|
+
"import/prefer-default-export": "off",
|
|
106
|
+
"import/no-default-export": "off",
|
|
107
|
+
"import/group-exports": "off",
|
|
108
|
+
"import/no-anonymous-default-export": "off",
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// ENABLED - Clean imports
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
"import/first": "error",
|
|
115
|
+
"import/no-namespace": "error"
|
|
72
116
|
},
|
|
73
117
|
"ignorePatterns": ["dist/**", "node_modules/**"]
|
|
74
118
|
}
|
package/src/oxlint/expo.json
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
"allow": ["\\.png$", "\\.jpg$", "\\.jpeg$", "\\.gif$", "\\.webp$"]
|
|
11
11
|
}
|
|
12
12
|
],
|
|
13
|
-
"remove-ts-extensions/remove-ts-extensions": "error"
|
|
13
|
+
"remove-ts-extensions/remove-ts-extensions": "error",
|
|
14
|
+
|
|
15
|
+
// React 17+ doesn't require importing React
|
|
16
|
+
"react/react-in-jsx-scope": "off",
|
|
17
|
+
// Prop spreading is common in React Native
|
|
18
|
+
"react/jsx-props-no-spreading": "off",
|
|
19
|
+
// Style preferences - not worth enforcing
|
|
20
|
+
"react/jsx-boolean-value": "off",
|
|
21
|
+
"react/jsx-handler-names": "off",
|
|
22
|
+
"react/jsx-curly-brace-presence": "off",
|
|
23
|
+
"unicorn/no-nested-ternary": "off"
|
|
14
24
|
}
|
|
15
25
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Custom rule to enforce architecture boundaries
|
|
2
|
+
// Prevents imports between layers (e.g., domain cannot import infra)
|
|
3
|
+
|
|
4
|
+
const architectureBoundariesRule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Enforce architecture layer boundaries",
|
|
9
|
+
category: "Best Practices",
|
|
10
|
+
},
|
|
11
|
+
schema: [
|
|
12
|
+
{
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
rules: {
|
|
16
|
+
type: "array",
|
|
17
|
+
items: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
from: { type: "string" }, // Regex pattern for source file path
|
|
21
|
+
disallow: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: { type: "string" }, // Regex patterns for disallowed imports
|
|
24
|
+
},
|
|
25
|
+
message: { type: "string" },
|
|
26
|
+
},
|
|
27
|
+
required: ["from", "disallow"],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
create(context) {
|
|
35
|
+
const options = context.options[0] || {};
|
|
36
|
+
const rules = options.rules || [];
|
|
37
|
+
const filename = context.getFilename();
|
|
38
|
+
|
|
39
|
+
function checkNode(node) {
|
|
40
|
+
if (!node.source || !node.source.value) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const importPath = node.source.value;
|
|
45
|
+
|
|
46
|
+
for (const rule of rules) {
|
|
47
|
+
const fromPattern = new RegExp(rule.from);
|
|
48
|
+
|
|
49
|
+
// Check if this file matches the "from" pattern
|
|
50
|
+
if (!fromPattern.test(filename)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if the import matches any disallowed pattern
|
|
55
|
+
for (const disallowPattern of rule.disallow) {
|
|
56
|
+
const disallowRegex = new RegExp(disallowPattern);
|
|
57
|
+
|
|
58
|
+
if (disallowRegex.test(importPath)) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: node.source,
|
|
61
|
+
message:
|
|
62
|
+
rule.message || `Import from "${importPath}" violates architecture boundaries`,
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
ImportDeclaration: checkNode,
|
|
72
|
+
ExportNamedDeclaration: checkNode,
|
|
73
|
+
ExportAllDeclaration: checkNode,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
meta: {
|
|
80
|
+
name: "architecture-boundaries",
|
|
81
|
+
version: "1.0.0",
|
|
82
|
+
},
|
|
83
|
+
rules: {
|
|
84
|
+
"architecture-boundaries": architectureBoundariesRule,
|
|
85
|
+
},
|
|
86
|
+
};
|