@chacki/eslint-plugin-require-extensions 0.0.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/.eslintrc.json +8 -0
- package/.prettierignore +1 -0
- package/.prettierrc +12 -0
- package/README.md +8 -0
- package/index.js +136 -0
- package/package.json +26 -0
package/.eslintrc.json
ADDED
package/.prettierignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
package-lock.json
|
package/.prettierrc
ADDED
package/README.md
ADDED
package/index.js
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
const { existsSync, lstatSync } = require('fs');
|
2
|
+
const { dirname, resolve } = require('path');
|
3
|
+
|
4
|
+
module.exports = {
|
5
|
+
rules: {
|
6
|
+
'require-extensions': {
|
7
|
+
meta: {
|
8
|
+
type: 'problem',
|
9
|
+
docs: {
|
10
|
+
description:
|
11
|
+
'Относительные импорты/экспорты должны заканчиваться на .js (или .ts→.js/.tsx→.js)',
|
12
|
+
category: 'ESM',
|
13
|
+
recommended: true,
|
14
|
+
},
|
15
|
+
fixable: 'code',
|
16
|
+
schema: [
|
17
|
+
{
|
18
|
+
type: 'object',
|
19
|
+
properties: {
|
20
|
+
extensions: {
|
21
|
+
type: 'array',
|
22
|
+
items: { type: 'string' },
|
23
|
+
default: ['.js'],
|
24
|
+
},
|
25
|
+
extMapping: {
|
26
|
+
type: 'object',
|
27
|
+
additionalProperties: { type: 'string' },
|
28
|
+
default: { '.ts': '.js', '.tsx': '.js' },
|
29
|
+
},
|
30
|
+
},
|
31
|
+
additionalProperties: false,
|
32
|
+
},
|
33
|
+
],
|
34
|
+
},
|
35
|
+
create(context) {
|
36
|
+
const opts = context.options[0] || {};
|
37
|
+
const extensions = opts.extensions || ['.js'];
|
38
|
+
const extMapping = opts.extMapping || { '.ts': '.js', '.tsx': '.js' };
|
39
|
+
|
40
|
+
function checkNode(node) {
|
41
|
+
const source = node.source && node.source.value;
|
42
|
+
if (!source) return;
|
43
|
+
const raw = source.replace(/\?.*$/, ''); // убираем query
|
44
|
+
if (!raw.startsWith('.') || extensions.some((e) => raw.endsWith(e))) {
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
|
48
|
+
const absPath = resolve(dirname(context.getFilename()), raw);
|
49
|
+
for (const ext of extensions) {
|
50
|
+
if (existsSync(absPath + ext)) {
|
51
|
+
// найден .js-файл
|
52
|
+
return context.report({
|
53
|
+
node: node.source,
|
54
|
+
message: 'Relative import/ export must end with {{ext}}',
|
55
|
+
data: { ext },
|
56
|
+
fix(fixer) {
|
57
|
+
return fixer.replaceText(node.source, `'${raw + ext}'`);
|
58
|
+
},
|
59
|
+
});
|
60
|
+
}
|
61
|
+
}
|
62
|
+
for (const [from, to] of Object.entries(extMapping)) {
|
63
|
+
if (raw.endsWith(from)) {
|
64
|
+
const without = raw.slice(0, -from.length);
|
65
|
+
const target = without + to;
|
66
|
+
if (existsSync(resolve(dirname(context.getFilename()), target))) {
|
67
|
+
return context.report({
|
68
|
+
node: node.source,
|
69
|
+
message: 'Change extension from {{from}} to {{to}}',
|
70
|
+
data: { from, to },
|
71
|
+
fix(fixer) {
|
72
|
+
return fixer.replaceText(node.source, `'${target}'`);
|
73
|
+
},
|
74
|
+
});
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
return {
|
81
|
+
ImportDeclaration: checkNode,
|
82
|
+
ExportNamedDeclaration: checkNode,
|
83
|
+
ExportAllDeclaration: checkNode,
|
84
|
+
};
|
85
|
+
},
|
86
|
+
},
|
87
|
+
|
88
|
+
'require-index': {
|
89
|
+
meta: {
|
90
|
+
type: 'problem',
|
91
|
+
docs: {
|
92
|
+
description: 'Directory imports must end with /index.js',
|
93
|
+
category: 'ESM',
|
94
|
+
recommended: true,
|
95
|
+
},
|
96
|
+
fixable: 'code',
|
97
|
+
schema: [],
|
98
|
+
},
|
99
|
+
create(context) {
|
100
|
+
function checkNode(node) {
|
101
|
+
const source = node.source && node.source.value;
|
102
|
+
if (!source) return;
|
103
|
+
const raw = source.replace(/\?.*$/, '');
|
104
|
+
if (!raw.startsWith('.')) return;
|
105
|
+
|
106
|
+
const absPath = resolve(dirname(context.getFilename()), raw);
|
107
|
+
if (existsSync(absPath) && lstatSync(absPath).isDirectory()) {
|
108
|
+
return context.report({
|
109
|
+
node: node.source,
|
110
|
+
message: 'Directory imports must end with /index.js',
|
111
|
+
fix(fixer) {
|
112
|
+
return fixer.replaceText(node.source, `'${raw}/index.js'`);
|
113
|
+
},
|
114
|
+
});
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
return {
|
119
|
+
ImportDeclaration: checkNode,
|
120
|
+
ExportNamedDeclaration: checkNode,
|
121
|
+
ExportAllDeclaration: checkNode,
|
122
|
+
};
|
123
|
+
},
|
124
|
+
},
|
125
|
+
},
|
126
|
+
|
127
|
+
configs: {
|
128
|
+
recommended: {
|
129
|
+
plugins: ['@chacki/require-extensions'],
|
130
|
+
rules: {
|
131
|
+
'@chacki/require-extensions/require-extensions': 'error',
|
132
|
+
'@chacki/require-extensions/require-index': 'error',
|
133
|
+
},
|
134
|
+
},
|
135
|
+
},
|
136
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
{
|
2
|
+
"name": "@chacki/eslint-plugin-require-extensions",
|
3
|
+
"version": "0.0.1",
|
4
|
+
"main": "./index.js",
|
5
|
+
"author": "Chacki",
|
6
|
+
"type": "commonjs",
|
7
|
+
"license": "ISC",
|
8
|
+
"keywords": ["eslint", "eslintplugin", "eslint-plugin", "esm", "extensions"],
|
9
|
+
"scripts": {
|
10
|
+
"fmt": "prettier --write '{*,**/*}.{ts,tsx,js,jsx,json}'",
|
11
|
+
"test": "eslint . --report-unused-disable-directives"
|
12
|
+
},
|
13
|
+
"exports": {
|
14
|
+
"require": "./index.js"
|
15
|
+
},
|
16
|
+
"publishConfig": {
|
17
|
+
"access": "public"
|
18
|
+
},
|
19
|
+
"peerDependencies": {
|
20
|
+
"eslint": "*"
|
21
|
+
},
|
22
|
+
"devDependencies": {
|
23
|
+
"eslint": "^8.57.1",
|
24
|
+
"prettier": "^3.5.3"
|
25
|
+
}
|
26
|
+
}
|