@jterrazz/codestyle 1.1.2 → 1.2.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/package.json +4 -2
- package/src/oxfmt/index.json +1 -1
- package/src/oxlint/architectures/hexagonal.json +1 -17
- package/src/oxlint/base.json +1 -1
- package/src/oxlint/expo.json +1 -6
- package/src/oxlint/nextjs.json +1 -2
- package/src/oxlint/node.json +1 -2
- package/src/oxlint/plugins/codestyle.js +197 -0
- package/src/postinstall.js +106 -0
- package/src/oxlint/plugins/architecture-boundaries.js +0 -86
- package/src/oxlint/plugins/remove-ts-extensions.js +0 -58
- package/src/oxlint/plugins/require-js-extensions.js +0 -65
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/codestyle",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codestyle": "./src/codestyle.sh"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"./oxlint/expo": "./src/oxlint/expo.json",
|
|
17
17
|
"./oxlint/nextjs": "./src/oxlint/nextjs.json",
|
|
18
18
|
"./oxlint/architectures/hexagonal": "./src/oxlint/architectures/hexagonal.json",
|
|
19
|
+
"./oxlint/plugins/codestyle": "./src/oxlint/plugins/codestyle.js",
|
|
19
20
|
"./oxfmt": "./src/oxfmt/index.json"
|
|
20
21
|
},
|
|
21
22
|
"publishConfig": {
|
|
@@ -26,7 +27,8 @@
|
|
|
26
27
|
"lint:fix": "oxlint --fix --ignore-pattern '**/fixtures/**' && oxfmt",
|
|
27
28
|
"test": "vitest --run",
|
|
28
29
|
"test:watch": "vitest",
|
|
29
|
-
"build": "# no build script"
|
|
30
|
+
"build": "# no build script",
|
|
31
|
+
"postinstall": "node src/postinstall.js"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
32
34
|
"@typescript/native-preview": "^7.0.0-dev",
|
package/src/oxfmt/index.json
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
|
-
"jsPlugins": ["../plugins/architecture-boundaries.js"],
|
|
4
3
|
"rules": {
|
|
5
|
-
|
|
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": [
|
|
4
|
+
"codestyle/arch-hexagonal": [
|
|
21
5
|
"error",
|
|
22
6
|
{
|
|
23
7
|
"rules": [
|
package/src/oxlint/base.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
3
|
"plugins": ["typescript", "import", "oxc", "unicorn"],
|
|
4
|
-
"jsPlugins": ["eslint-plugin-perfectionist"],
|
|
4
|
+
"jsPlugins": ["eslint-plugin-perfectionist", "./plugins/codestyle.js"],
|
|
5
5
|
"categories": {
|
|
6
6
|
"correctness": "error",
|
|
7
7
|
"suspicious": "error",
|
package/src/oxlint/expo.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
3
|
"extends": ["./base.json"],
|
|
4
4
|
"plugins": ["typescript", "import", "react"],
|
|
5
|
-
"jsPlugins": ["./plugins/remove-ts-extensions.js"],
|
|
6
5
|
"rules": {
|
|
7
6
|
"typescript/no-require-imports": [
|
|
8
7
|
"error",
|
|
@@ -10,13 +9,9 @@
|
|
|
10
9
|
"allow": ["\\.png$", "\\.jpg$", "\\.jpeg$", "\\.gif$", "\\.webp$"]
|
|
11
10
|
}
|
|
12
11
|
],
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
// React 17+ doesn't require importing React
|
|
12
|
+
"codestyle/imports-without-ext": "error",
|
|
16
13
|
"react/react-in-jsx-scope": "off",
|
|
17
|
-
// Prop spreading is common in React Native
|
|
18
14
|
"react/jsx-props-no-spreading": "off",
|
|
19
|
-
// Style preferences - not worth enforcing
|
|
20
15
|
"react/jsx-boolean-value": "off",
|
|
21
16
|
"react/jsx-handler-names": "off",
|
|
22
17
|
"react/jsx-curly-brace-presence": "off",
|
package/src/oxlint/nextjs.json
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
3
|
"extends": ["./base.json"],
|
|
4
4
|
"plugins": ["typescript", "import", "react", "nextjs"],
|
|
5
|
-
"jsPlugins": ["./plugins/remove-ts-extensions.js"],
|
|
6
5
|
"ignorePatterns": ["dist/**", "node_modules/**", ".next/**", "next-env.d.ts"],
|
|
7
6
|
"rules": {
|
|
8
|
-
"
|
|
7
|
+
"codestyle/imports-without-ext": "error"
|
|
9
8
|
}
|
|
10
9
|
}
|
package/src/oxlint/node.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
|
|
3
3
|
"extends": ["./base.json"],
|
|
4
|
-
"jsPlugins": ["./plugins/require-js-extensions.js"],
|
|
5
4
|
"rules": {
|
|
6
|
-
"
|
|
5
|
+
"codestyle/imports-with-ext": "error"
|
|
7
6
|
}
|
|
8
7
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Codestyle plugin - custom rules for code quality
|
|
2
|
+
// Contains: architecture boundaries, import extensions
|
|
3
|
+
|
|
4
|
+
import hexagonalConfig from "../architectures/hexagonal.json" with { type: "json" };
|
|
5
|
+
|
|
6
|
+
// ============================================
|
|
7
|
+
// Hexagonal - Architecture boundary rules
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
function createHexagonalRule() {
|
|
11
|
+
const ruleConfig = hexagonalConfig.rules["codestyle/arch-hexagonal"];
|
|
12
|
+
const defaultRules = Array.isArray(ruleConfig) ? ruleConfig[1]?.rules || [] : [];
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
meta: {
|
|
16
|
+
type: "problem",
|
|
17
|
+
docs: {
|
|
18
|
+
description: "Enforce hexagonal architecture layer boundaries",
|
|
19
|
+
category: "Best Practices",
|
|
20
|
+
},
|
|
21
|
+
schema: [
|
|
22
|
+
{
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
rules: {
|
|
26
|
+
type: "array",
|
|
27
|
+
items: {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
from: { type: "string" },
|
|
31
|
+
disallow: { type: "array", items: { type: "string" } },
|
|
32
|
+
message: { type: "string" },
|
|
33
|
+
},
|
|
34
|
+
required: ["from", "disallow"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
create(context) {
|
|
42
|
+
const options = context.options[0];
|
|
43
|
+
const rules = options && options.rules ? options.rules : defaultRules;
|
|
44
|
+
const filename = context.getFilename();
|
|
45
|
+
|
|
46
|
+
function checkNode(node) {
|
|
47
|
+
if (!node.source || !node.source.value) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const importPath = node.source.value;
|
|
52
|
+
|
|
53
|
+
for (const rule of rules) {
|
|
54
|
+
const fromPattern = new RegExp(rule.from);
|
|
55
|
+
if (!fromPattern.test(filename)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const disallowPattern of rule.disallow) {
|
|
60
|
+
if (new RegExp(disallowPattern).test(importPath)) {
|
|
61
|
+
context.report({
|
|
62
|
+
node: node.source,
|
|
63
|
+
message: rule.message || "Import violates architecture boundaries",
|
|
64
|
+
});
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
ImportDeclaration: checkNode,
|
|
73
|
+
ExportNamedDeclaration: checkNode,
|
|
74
|
+
ExportAllDeclaration: checkNode,
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================
|
|
81
|
+
// Imports-with-ext - Require .js extensions
|
|
82
|
+
// ============================================
|
|
83
|
+
|
|
84
|
+
const importsWithExtRule = {
|
|
85
|
+
meta: {
|
|
86
|
+
type: "problem",
|
|
87
|
+
docs: {
|
|
88
|
+
description: "Require .js extension in imports for Node.js ESM compatibility",
|
|
89
|
+
category: "Best Practices",
|
|
90
|
+
},
|
|
91
|
+
fixable: "code",
|
|
92
|
+
schema: [],
|
|
93
|
+
},
|
|
94
|
+
create(context) {
|
|
95
|
+
const hasExtension = /\.[a-zA-Z0-9]+$/;
|
|
96
|
+
|
|
97
|
+
function checkNode(node) {
|
|
98
|
+
if (!node.source || !node.source.value) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const importPath = node.source.value;
|
|
103
|
+
|
|
104
|
+
// Only check relative imports
|
|
105
|
+
if (!importPath.startsWith(".")) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Skip if it already has an extension
|
|
110
|
+
if (hasExtension.test(importPath)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Skip type-only imports (they are erased at runtime)
|
|
115
|
+
if (node.importKind === "type") {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
context.report({
|
|
120
|
+
node: node.source,
|
|
121
|
+
message: "Missing .js extension in import (required for Node.js ESM)",
|
|
122
|
+
fix(fixer) {
|
|
123
|
+
const newPath = `${importPath}.js`;
|
|
124
|
+
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
ImportDeclaration: checkNode,
|
|
131
|
+
ExportNamedDeclaration: checkNode,
|
|
132
|
+
ExportAllDeclaration: checkNode,
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// ============================================
|
|
138
|
+
// Imports-without-ext - Remove extensions
|
|
139
|
+
// ============================================
|
|
140
|
+
|
|
141
|
+
const importsWithoutExtRule = {
|
|
142
|
+
meta: {
|
|
143
|
+
type: "problem",
|
|
144
|
+
docs: {
|
|
145
|
+
description: "Remove .js, .jsx, .ts, .tsx extensions from imports",
|
|
146
|
+
category: "Best Practices",
|
|
147
|
+
},
|
|
148
|
+
fixable: "code",
|
|
149
|
+
schema: [],
|
|
150
|
+
},
|
|
151
|
+
create(context) {
|
|
152
|
+
const extensionsToRemove = /\.(js|jsx|ts|tsx)$/;
|
|
153
|
+
|
|
154
|
+
function checkNode(node) {
|
|
155
|
+
if (!node.source || !node.source.value) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const importPath = node.source.value;
|
|
160
|
+
|
|
161
|
+
// Check both relative imports and path alias imports
|
|
162
|
+
if (!importPath.startsWith(".") && !importPath.startsWith("@/")) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (extensionsToRemove.test(importPath)) {
|
|
167
|
+
const match = importPath.match(extensionsToRemove);
|
|
168
|
+
context.report({
|
|
169
|
+
node: node.source,
|
|
170
|
+
message: `Remove "${match[0]}" extension from import`,
|
|
171
|
+
fix(fixer) {
|
|
172
|
+
const newPath = importPath.replace(extensionsToRemove, "");
|
|
173
|
+
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
ImportDeclaration: checkNode,
|
|
181
|
+
ExportNamedDeclaration: checkNode,
|
|
182
|
+
ExportAllDeclaration: checkNode,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export default {
|
|
188
|
+
meta: {
|
|
189
|
+
name: "codestyle",
|
|
190
|
+
version: "1.0.0",
|
|
191
|
+
},
|
|
192
|
+
rules: {
|
|
193
|
+
"arch-hexagonal": createHexagonalRule(),
|
|
194
|
+
"imports-with-ext": importsWithExtRule,
|
|
195
|
+
"imports-without-ext": importsWithoutExtRule,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
function findProjectRoot() {
|
|
10
|
+
let dir = process.cwd();
|
|
11
|
+
if (dir.includes("node_modules")) {
|
|
12
|
+
dir = dir.split("node_modules")[0];
|
|
13
|
+
}
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Rule mappings based on extended configs
|
|
18
|
+
const ruleMappings = [
|
|
19
|
+
{
|
|
20
|
+
pattern: "architectures/hexagonal",
|
|
21
|
+
rule: ["codestyle/arch-hexagonal", "error"],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pattern: "oxlint/node",
|
|
25
|
+
rule: ["codestyle/imports-with-ext", "error"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: "oxlint/expo",
|
|
29
|
+
rule: ["codestyle/imports-without-ext", "error"],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
pattern: "oxlint/nextjs",
|
|
33
|
+
rule: ["codestyle/imports-without-ext", "error"],
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
function run() {
|
|
38
|
+
const projectRoot = findProjectRoot();
|
|
39
|
+
const configPath = path.join(projectRoot, ".oxlintrc.json");
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(configPath)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let config;
|
|
46
|
+
try {
|
|
47
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
48
|
+
} catch {
|
|
49
|
+
console.log("[@jterrazz/codestyle] Could not parse .oxlintrc.json");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const configStr = JSON.stringify(config);
|
|
54
|
+
const neededRules = new Map();
|
|
55
|
+
let needsPlugin = false;
|
|
56
|
+
|
|
57
|
+
for (const mapping of ruleMappings) {
|
|
58
|
+
if (configStr.includes(mapping.pattern)) {
|
|
59
|
+
neededRules.set(mapping.rule[0], mapping.rule[1]);
|
|
60
|
+
needsPlugin = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!needsPlugin) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let updated = false;
|
|
69
|
+
const updates = [];
|
|
70
|
+
|
|
71
|
+
// Add plugin
|
|
72
|
+
const pluginPath = "./node_modules/@jterrazz/codestyle/src/oxlint/plugins/codestyle.js";
|
|
73
|
+
config.jsPlugins = config.jsPlugins || [];
|
|
74
|
+
if (!config.jsPlugins.includes(pluginPath)) {
|
|
75
|
+
config.jsPlugins.push(pluginPath);
|
|
76
|
+
updated = true;
|
|
77
|
+
updates.push(" + jsPlugins: codestyle.js");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Add rules
|
|
81
|
+
config.rules = config.rules || {};
|
|
82
|
+
for (const [ruleName, ruleLevel] of neededRules) {
|
|
83
|
+
if (!config.rules[ruleName]) {
|
|
84
|
+
config.rules[ruleName] = ruleLevel;
|
|
85
|
+
updated = true;
|
|
86
|
+
updates.push(` + rule: ${ruleName}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!updated) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fs.writeFileSync(
|
|
95
|
+
configPath,
|
|
96
|
+
`${JSON.stringify(config, null, 2)}
|
|
97
|
+
`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.log("[@jterrazz/codestyle] Updated .oxlintrc.json:");
|
|
101
|
+
for (const update of updates) {
|
|
102
|
+
console.log(update);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
run();
|
|
@@ -1,86 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// Custom rule to remove TS/JS extensions from imports
|
|
2
|
-
// Compatible with both ESLint and Oxlint JS plugin API
|
|
3
|
-
|
|
4
|
-
const removeTsExtensionsRule = {
|
|
5
|
-
meta: {
|
|
6
|
-
type: "problem",
|
|
7
|
-
docs: {
|
|
8
|
-
description: "Remove .js, .jsx, .ts, .tsx extensions from imports",
|
|
9
|
-
category: "Best Practices",
|
|
10
|
-
},
|
|
11
|
-
fixable: "code",
|
|
12
|
-
schema: [],
|
|
13
|
-
},
|
|
14
|
-
create(context) {
|
|
15
|
-
const extensionsToRemove = /\.(js|jsx|ts|tsx)$/;
|
|
16
|
-
|
|
17
|
-
function checkNode(node) {
|
|
18
|
-
if (!node.source || !node.source.value) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const importPath = node.source.value;
|
|
23
|
-
|
|
24
|
-
// Check both relative imports (starting with . or ..) AND path alias imports (starting with @/)
|
|
25
|
-
if (!importPath.startsWith(".") && !importPath.startsWith("@/")) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (extensionsToRemove.test(importPath)) {
|
|
30
|
-
const match = importPath.match(extensionsToRemove);
|
|
31
|
-
context.report({
|
|
32
|
-
node: node.source,
|
|
33
|
-
message: `Remove "${match[0]}" extension from import`,
|
|
34
|
-
fix(fixer) {
|
|
35
|
-
const newPath = importPath.replace(extensionsToRemove, "");
|
|
36
|
-
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
ImportDeclaration: checkNode,
|
|
44
|
-
ExportNamedDeclaration: checkNode,
|
|
45
|
-
ExportAllDeclaration: checkNode,
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export default {
|
|
51
|
-
meta: {
|
|
52
|
-
name: "remove-ts-extensions",
|
|
53
|
-
version: "1.0.0",
|
|
54
|
-
},
|
|
55
|
-
rules: {
|
|
56
|
-
"remove-ts-extensions": removeTsExtensionsRule,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Custom rule to require .js extensions in imports (for Node.js ESM)
|
|
2
|
-
// Compatible with both ESLint and Oxlint JS plugin API
|
|
3
|
-
|
|
4
|
-
const requireJsExtensionsRule = {
|
|
5
|
-
meta: {
|
|
6
|
-
type: "problem",
|
|
7
|
-
docs: {
|
|
8
|
-
description: "Require .js extension in imports for Node.js ESM compatibility",
|
|
9
|
-
category: "Best Practices",
|
|
10
|
-
},
|
|
11
|
-
fixable: "code",
|
|
12
|
-
schema: [],
|
|
13
|
-
},
|
|
14
|
-
create(context) {
|
|
15
|
-
const hasExtension = /\.[a-zA-Z0-9]+$/;
|
|
16
|
-
|
|
17
|
-
function checkNode(node) {
|
|
18
|
-
if (!node.source || !node.source.value) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const importPath = node.source.value;
|
|
23
|
-
|
|
24
|
-
// Only check relative imports
|
|
25
|
-
if (!importPath.startsWith(".")) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Skip if it already has an extension
|
|
30
|
-
if (hasExtension.test(importPath)) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Skip type-only imports (they are erased at runtime)
|
|
35
|
-
if (node.importKind === "type") {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
context.report({
|
|
40
|
-
node: node.source,
|
|
41
|
-
message: "Missing .js extension in import (required for Node.js ESM)",
|
|
42
|
-
fix(fixer) {
|
|
43
|
-
const newPath = `${importPath}.js`;
|
|
44
|
-
return fixer.replaceText(node.source, `'${newPath}'`);
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
ImportDeclaration: checkNode,
|
|
51
|
-
ExportNamedDeclaration: checkNode,
|
|
52
|
-
ExportAllDeclaration: checkNode,
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export default {
|
|
58
|
-
meta: {
|
|
59
|
-
name: "require-js-extensions",
|
|
60
|
-
version: "1.0.0",
|
|
61
|
-
},
|
|
62
|
-
rules: {
|
|
63
|
-
"require-js-extensions": requireJsExtensionsRule,
|
|
64
|
-
},
|
|
65
|
-
};
|