eslint-plugin-smarthr 0.0.1 → 0.1.2
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/CHANGELOG.md +21 -3
- package/README.md +109 -6
- package/package.json +1 -1
- package/rules/jsx-start-with-spread-attributes.js +44 -10
- package/rules/prohibit-import.js +73 -42
- package/rules/require-barrel-import.js +122 -0
- package/rules/require-import.js +113 -0
- package/test/prohibit-import.js +114 -29
- package/test/require-import.js +184 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [0.1.2](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.1.1...v0.1.2) (2022-03-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* require-barrel-import修正(barrelファイルが複数存在する場合、一番親に当たるファイルを検知する) ([#14](https://github.com/kufu/eslint-plugin-smarthr/issues/14)) ([87a6724](https://github.com/kufu/eslint-plugin-smarthr/commit/87a67240f31c9408faad6784741bbf6a2f7ef47b))
|
|
11
|
+
|
|
12
|
+
### [0.1.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.1.0...v0.1.1) (2022-03-08)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* add require-barrel-import rule ([#13](https://github.com/kufu/eslint-plugin-smarthr/issues/13)) ([79ee88d](https://github.com/kufu/eslint-plugin-smarthr/commit/79ee88d355e01bb8344dc95bd65157e2fbcf916e))
|
|
18
|
+
|
|
19
|
+
## [0.1.0](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.0.1...v0.1.0) (2022-02-09)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### ⚠ BREAKING CHANGES
|
|
23
|
+
|
|
24
|
+
* BREAKING CHANGE: add require-import & update prohibit-import (#12) ([e6c5c44](https://github.com/kufu/eslint-plugin-smarthr/commit/e6c5c445a21620d4b796ded00a685e5da367c7bb)), closes [#12](https://github.com/kufu/eslint-plugin-smarthr/issues/12)
|
|
25
|
+
|
|
5
26
|
### [0.0.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.0.0...v0.0.1) (2022-02-08)
|
|
6
27
|
|
|
7
28
|
|
|
@@ -10,9 +31,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
10
31
|
* add type property function params redundant ([758df90](https://github.com/kufu/eslint-plugin-smarthr/commit/758df90f89bd27dd589aeeb55165e27c8e072b08))
|
|
11
32
|
* redundant-name の修正候補を操作できるように改修 ([20991e8](https://github.com/kufu/eslint-plugin-smarthr/commit/20991e874890556e84e7c682e789e4b2650a85b0))
|
|
12
33
|
|
|
13
|
-
### [0.0.2](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.0.1...v0.0.2) (2022-01-26)
|
|
14
|
-
|
|
15
|
-
### [0.0.1](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.0.0...v0.0.1) (2022-01-26)
|
|
16
34
|
|
|
17
35
|
## 0.0.0 (2022-01-25)
|
|
18
36
|
|
package/README.md
CHANGED
|
@@ -145,13 +145,19 @@ import globalModulePart from '@/modules/views/parts'
|
|
|
145
145
|
## smarthr/jsx-start-with-spread-attributes
|
|
146
146
|
|
|
147
147
|
- jsxを記述する際、意図しない属性の上書きを防ぐため、spread-attributesを先に指定するように強制するruleです
|
|
148
|
+
- eslint を `--fix` オプション付きで実行する際、 fix option を true にすると自動修正します
|
|
148
149
|
|
|
149
150
|
### rules
|
|
150
151
|
|
|
151
152
|
```js
|
|
152
153
|
{
|
|
153
154
|
rules: {
|
|
154
|
-
'smarthr/jsx-start-with-spread-attributes':
|
|
155
|
+
'smarthr/jsx-start-with-spread-attributes': [
|
|
156
|
+
'error', // 'warn', 'off'
|
|
157
|
+
{
|
|
158
|
+
fix: false, // true
|
|
159
|
+
},
|
|
160
|
+
]
|
|
155
161
|
},
|
|
156
162
|
}
|
|
157
163
|
```
|
|
@@ -268,12 +274,17 @@ import globalModulePart from '@/modules/views/parts'
|
|
|
268
274
|
'smarthr/prohibit-import': [
|
|
269
275
|
'error', // 'warn', 'off'
|
|
270
276
|
{
|
|
271
|
-
|
|
272
|
-
'
|
|
273
|
-
|
|
277
|
+
'^.+$': {
|
|
278
|
+
'smarthr-ui': {
|
|
279
|
+
imported: ['SecondaryButtonAnchor'],
|
|
280
|
+
reportMessage: `{{module}}/{{export}} はXxxxxxなので利用せず yyyy/zzzz を利用してください`
|
|
281
|
+
},
|
|
282
|
+
}
|
|
283
|
+
'\/pages\/views\/': {
|
|
284
|
+
'query-string': {
|
|
285
|
+
imported: true,
|
|
286
|
+
},
|
|
274
287
|
},
|
|
275
|
-
// generateReportMessage: (source, imported) =>
|
|
276
|
-
// `${source}${imported && `/${imported}`} はXxxxxxなので利用せず yyyy/zzzz を利用してください`
|
|
277
288
|
}
|
|
278
289
|
]
|
|
279
290
|
},
|
|
@@ -283,6 +294,7 @@ import globalModulePart from '@/modules/views/parts'
|
|
|
283
294
|
### ❌ Incorrect
|
|
284
295
|
|
|
285
296
|
```js
|
|
297
|
+
// src/pages/views/Page.tsx
|
|
286
298
|
import queryString from 'query-string'
|
|
287
299
|
import { SecondaryButtonAnchor } from 'smarthr-ui'
|
|
288
300
|
```
|
|
@@ -291,10 +303,101 @@ import { SecondaryButtonAnchor } from 'smarthr-ui'
|
|
|
291
303
|
|
|
292
304
|
|
|
293
305
|
```js
|
|
306
|
+
// src/pages/views/Page.tsx
|
|
294
307
|
import { PrimaryButton, SecondaryButton } from 'smarthr-ui'
|
|
295
308
|
```
|
|
296
309
|
|
|
310
|
+
## smarthr/require-import
|
|
311
|
+
|
|
312
|
+
- 対象ファイルにimportを強制させたい場合に利用します
|
|
313
|
+
- 例: Page.tsx ではページタイトルを設定させたいので useTitle を必ずimportさせたい
|
|
314
|
+
|
|
315
|
+
### rules
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
{
|
|
319
|
+
rules: {
|
|
320
|
+
'smarthr/require-import': [
|
|
321
|
+
'error',
|
|
322
|
+
{
|
|
323
|
+
'Buttons\/.+\.tsx': {
|
|
324
|
+
'smarthr-ui': {
|
|
325
|
+
imported: ['SecondaryButton'],
|
|
326
|
+
reportMessage: 'Buttons以下のコンポーネントでは {{module}}/{{export}} を拡張するようにしてください',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
'Page.tsx$': {
|
|
330
|
+
'./client/src/hooks/useTitle': {
|
|
331
|
+
imported: true,
|
|
332
|
+
reportMessage: '{{module}} を利用してください(ページタイトルを設定するため必要です)',
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
]
|
|
337
|
+
},
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### ❌ Incorrect
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
// client/src/Buttons/SecondaryButton.tsx
|
|
345
|
+
import { SecondaryButtonAnchor } from 'smarthr-ui'
|
|
346
|
+
|
|
347
|
+
// client/src/Page.tsx
|
|
348
|
+
import { SecondaryButton } from 'smarthr-ui'
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### ✅ Correct
|
|
352
|
+
|
|
297
353
|
|
|
354
|
+
```js
|
|
355
|
+
// client/src/Buttons/SecondaryButton.tsx
|
|
356
|
+
import { SecondaryButton } from 'smarthr-ui'
|
|
357
|
+
|
|
358
|
+
// client/src/Page.tsx
|
|
359
|
+
import useTitle from '.hooks/useTitle'
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## smarthr/require-barrel-import
|
|
363
|
+
|
|
364
|
+
- tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
|
|
365
|
+
- importした対象が本来exportされているべきであるbarrel(index.tsなど)が有る場合、import pathの変更を促します
|
|
366
|
+
- 例: Page/parts/Menu/Item の import は Page/parts/Menu から行わせたい
|
|
367
|
+
- ディレクトリ内のindexファイルを捜査し、対象を決定します
|
|
368
|
+
|
|
369
|
+
### rules
|
|
370
|
+
|
|
371
|
+
```js
|
|
372
|
+
{
|
|
373
|
+
rules: {
|
|
374
|
+
'smarthr/require-barrel-import': 'error',
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### ❌ Incorrect
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
// client/src/views/Page/parts/Menu/index.ts
|
|
383
|
+
export { Menu } from './Menu'
|
|
384
|
+
export { Item } from './Item'
|
|
385
|
+
|
|
386
|
+
// client/src/App.tsx
|
|
387
|
+
import { Item } from './Page/parts/Menu/Item'
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### ✅ Correct
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
// client/src/views/Page/parts/Menu/index.ts
|
|
395
|
+
export { Menu } from './Menu'
|
|
396
|
+
export { Item } from './Item'
|
|
397
|
+
|
|
398
|
+
// client/src/App.tsx
|
|
399
|
+
import { Item } from './Page/parts/Menu'
|
|
400
|
+
```
|
|
298
401
|
|
|
299
402
|
|
|
300
403
|
## smarthr/redundant-name
|
package/package.json
CHANGED
|
@@ -1,38 +1,72 @@
|
|
|
1
|
+
const SCHEMA = [
|
|
2
|
+
{
|
|
3
|
+
type: 'object',
|
|
4
|
+
properties: {
|
|
5
|
+
fix: { type: 'boolean', default: false },
|
|
6
|
+
},
|
|
7
|
+
additionalProperties: false,
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
|
|
1
11
|
module.exports = {
|
|
2
12
|
meta: {
|
|
3
13
|
type: 'suggestion',
|
|
4
14
|
messages: {
|
|
5
15
|
'jsx-start-with-spread-attributes': '{{ message }}',
|
|
6
16
|
},
|
|
7
|
-
|
|
17
|
+
fixable: 'code',
|
|
18
|
+
schema: SCHEMA,
|
|
8
19
|
},
|
|
9
20
|
create(context) {
|
|
10
21
|
return {
|
|
11
22
|
JSXSpreadAttribute: (node) => {
|
|
12
|
-
// HINT:
|
|
13
|
-
const
|
|
14
|
-
if (h ===
|
|
23
|
+
// HINT: -2: 計算中 -1: 見つからなかった >= 0: 見つかった
|
|
24
|
+
const insertIndex = node.parent.attributes.reduce((h, a, i) => {
|
|
25
|
+
if (h === -2) {
|
|
15
26
|
if (a === node) {
|
|
16
|
-
return 1
|
|
27
|
+
return -1
|
|
17
28
|
}
|
|
18
29
|
|
|
19
|
-
return a.type !== 'JSXSpreadAttribute' ?
|
|
30
|
+
return a.type !== 'JSXSpreadAttribute' ? i : h
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
return h
|
|
23
|
-
},
|
|
34
|
+
}, -2)
|
|
35
|
+
|
|
36
|
+
if (insertIndex >= 0) {
|
|
37
|
+
const option = context.options[0]
|
|
38
|
+
const sourceCode = context.getSourceCode()
|
|
39
|
+
const attributeCode = sourceCode.getText(node)
|
|
24
40
|
|
|
25
|
-
if (hit === 2) {
|
|
26
41
|
context.report({
|
|
27
42
|
node,
|
|
28
43
|
messageId: 'jsx-start-with-spread-attributes',
|
|
29
44
|
data: {
|
|
30
|
-
message: `"${
|
|
45
|
+
message: `"${attributeCode}" は意図しない上書きを防ぐため、spread attributesでない属性より先に記述してください`,
|
|
31
46
|
},
|
|
47
|
+
fix: option?.fix ? (fixer) => {
|
|
48
|
+
const elementNode = node.parent
|
|
49
|
+
const sortedAttributes = [...elementNode.attributes].reduce((p, a, i) => {
|
|
50
|
+
if (insertIndex === i) {
|
|
51
|
+
p = [attributeCode, ...p]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (a !== node) {
|
|
55
|
+
p = [...p, sourceCode.getText(a)]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return p
|
|
59
|
+
}, [])
|
|
60
|
+
|
|
61
|
+
return fixer.replaceText(
|
|
62
|
+
elementNode,
|
|
63
|
+
`<${elementNode.name.name} ${sortedAttributes.join(' ')}${elementNode.selfClosing ? '/' : ''}>`
|
|
64
|
+
)
|
|
65
|
+
} : null
|
|
32
66
|
});
|
|
33
67
|
}
|
|
34
68
|
},
|
|
35
69
|
}
|
|
36
70
|
},
|
|
37
71
|
}
|
|
38
|
-
module.exports.schema =
|
|
72
|
+
module.exports.schema = SCHEMA
|
package/rules/prohibit-import.js
CHANGED
|
@@ -1,28 +1,34 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
1
3
|
const SCHEMA = [{
|
|
2
|
-
type:
|
|
4
|
+
type: 'object',
|
|
3
5
|
patternProperties: {
|
|
4
|
-
|
|
5
|
-
type:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
'.+': {
|
|
7
|
+
type: 'object',
|
|
8
|
+
patternProperties: {
|
|
9
|
+
'.+': {
|
|
10
|
+
type: 'object',
|
|
11
|
+
required: [
|
|
12
|
+
'imported',
|
|
13
|
+
],
|
|
14
|
+
properties: {
|
|
15
|
+
imported: {
|
|
16
|
+
type: ['boolean', 'array'],
|
|
17
|
+
items: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
reportMessage: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false
|
|
18
26
|
}
|
|
19
|
-
},
|
|
20
|
-
additionalProperties: false
|
|
21
27
|
}
|
|
22
28
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
]
|
|
29
|
+
},
|
|
30
|
+
additionalProperties: true,
|
|
31
|
+
}]
|
|
26
32
|
|
|
27
33
|
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} は利用しないでください`
|
|
28
34
|
|
|
@@ -36,34 +42,59 @@ module.exports = {
|
|
|
36
42
|
},
|
|
37
43
|
create(context) {
|
|
38
44
|
const options = context.options[0]
|
|
39
|
-
const
|
|
45
|
+
const filename = context.getFilename()
|
|
46
|
+
const parentDir = (() => {
|
|
47
|
+
const dir = filename.match(/^(.+?)\..+?$/)[1].split('/')
|
|
48
|
+
dir.pop()
|
|
49
|
+
|
|
50
|
+
return dir.join('/')
|
|
51
|
+
})()
|
|
52
|
+
const targetPathRegexs = Object.keys(options)
|
|
53
|
+
const targetProhibits = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
|
|
54
|
+
|
|
55
|
+
if (targetProhibits.length === 0) {
|
|
56
|
+
return {}
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
return {
|
|
41
60
|
ImportDeclaration: (node) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
61
|
+
targetProhibits.forEach((prohibitKey) => {
|
|
62
|
+
const option = options[prohibitKey]
|
|
63
|
+
const targetModules = Object.keys(option)
|
|
64
|
+
|
|
65
|
+
targetModules.forEach((targetModule) => {
|
|
66
|
+
const { imported, reportMessage } = Object.assign({imported: true}, option[targetModule])
|
|
67
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`)
|
|
68
|
+
let sourceValue = node.source.value
|
|
69
|
+
|
|
70
|
+
if (actualTarget[0] === '/') {
|
|
71
|
+
sourceValue = path.resolve(`${parentDir}/${sourceValue}`)
|
|
51
72
|
}
|
|
52
73
|
|
|
53
|
-
|
|
74
|
+
if (actualTarget !== sourceValue) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const useImported = (() => {
|
|
79
|
+
if (!Array.isArray(imported)) {
|
|
80
|
+
return !!imported
|
|
81
|
+
}
|
|
54
82
|
|
|
55
|
-
|
|
56
|
-
})()
|
|
83
|
+
const specifier = node.specifiers.find((s) => s.imported && imported.includes(s.imported.name))
|
|
57
84
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
return specifier ? specifier.imported.name : false
|
|
86
|
+
})()
|
|
87
|
+
|
|
88
|
+
if (useImported) {
|
|
89
|
+
context.report({
|
|
90
|
+
node,
|
|
91
|
+
messageId: 'prohibit_import',
|
|
92
|
+
data: {
|
|
93
|
+
message: reportMessage ? reportMessage.replace('{{module}}', node.source.value).replace('{{export}}', useImported) : defaultReportMessage(node.source.value, useImported)
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
})
|
|
67
98
|
})
|
|
68
99
|
},
|
|
69
100
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const { replacePaths, rootPath } = require('../libs/common')
|
|
4
|
+
const calculateAbsoluteImportPath = (source) => {
|
|
5
|
+
if (source[0] === '/') {
|
|
6
|
+
return source
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return Object.entries(replacePaths).reduce((prev, [key, values]) => {
|
|
10
|
+
if (source === prev) {
|
|
11
|
+
return values.reduce((p, v) => {
|
|
12
|
+
if (prev === p) {
|
|
13
|
+
const regexp = new RegExp(`^${key}(.+)$`)
|
|
14
|
+
|
|
15
|
+
if (prev.match(regexp)) {
|
|
16
|
+
return p.replace(regexp, `${path.resolve(`${process.cwd()}/${v}`)}/$1`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return p
|
|
21
|
+
}, prev)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return prev
|
|
25
|
+
}, source)
|
|
26
|
+
}
|
|
27
|
+
const calculateReplacedImportPath = (source) => {
|
|
28
|
+
return Object.entries(replacePaths).reduce((prev, [key, values]) => {
|
|
29
|
+
if (source === prev) {
|
|
30
|
+
return values.reduce((p, v) => {
|
|
31
|
+
if (prev === p) {
|
|
32
|
+
const regexp = new RegExp(`^${path.resolve(`${process.cwd()}/${v}`)}(.+)$`)
|
|
33
|
+
|
|
34
|
+
if (prev.match(regexp)) {
|
|
35
|
+
return p.replace(regexp, `${key}/$1`).replace(/(\/)+/g, '/')
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return p
|
|
40
|
+
}, prev)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return prev
|
|
44
|
+
}, source)
|
|
45
|
+
}
|
|
46
|
+
const TARGET_EXTS = ['ts', 'tsx', 'js', 'jsx']
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
meta: {
|
|
50
|
+
type: 'suggestion',
|
|
51
|
+
messages: {
|
|
52
|
+
'require-barrel-import': '{{ message }}',
|
|
53
|
+
},
|
|
54
|
+
schema: [],
|
|
55
|
+
},
|
|
56
|
+
create(context) {
|
|
57
|
+
const filename = context.getFilename()
|
|
58
|
+
|
|
59
|
+
const dir = (() => {
|
|
60
|
+
const d = filename.split('/')
|
|
61
|
+
d.pop()
|
|
62
|
+
|
|
63
|
+
return d.join('/')
|
|
64
|
+
})()
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
ImportDeclaration: (node) => {
|
|
68
|
+
let sourceValue = node.source.value
|
|
69
|
+
|
|
70
|
+
if (sourceValue[0] === '.') {
|
|
71
|
+
sourceValue = path.resolve(`${dir}/${sourceValue}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sourceValue = calculateAbsoluteImportPath(sourceValue)
|
|
75
|
+
|
|
76
|
+
if (sourceValue[0] !== '/') {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const sources = sourceValue.split('/')
|
|
81
|
+
|
|
82
|
+
// HINT: directoryの場合、indexファイルからimportしていることは自明であるため、一階層上からチェックする
|
|
83
|
+
if (fs.existsSync(sourceValue) && fs.statSync(sourceValue).isDirectory()) {
|
|
84
|
+
sources.pop()
|
|
85
|
+
sourceValue = sources.join('/')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let barrel = undefined
|
|
89
|
+
|
|
90
|
+
while (sources.length > 0) {
|
|
91
|
+
// HINT: 以下の場合は即終了
|
|
92
|
+
// - import元以下のimportだった場合
|
|
93
|
+
// - rootまで捜索した場合
|
|
94
|
+
if (
|
|
95
|
+
dir === rootPath ||
|
|
96
|
+
dir.match(new RegExp(`^${sourceValue}`))
|
|
97
|
+
) {
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
barrel = TARGET_EXTS.map((e) => `${sourceValue}/index.${e}`).find((p) => fs.existsSync(p)) || barrel
|
|
102
|
+
|
|
103
|
+
sources.pop()
|
|
104
|
+
sourceValue = sources.join('/')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (barrel) {
|
|
108
|
+
barrel = calculateReplacedImportPath(barrel)
|
|
109
|
+
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
messageId: 'require-barrel-import',
|
|
113
|
+
data: {
|
|
114
|
+
message: `${barrel.replace(/\/index\.(ts|js)x?$/, '')} からimportするか、${barrel} を削除してください`,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
module.exports.schema = []
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const SCHEMA = [{
|
|
3
|
+
type: 'object',
|
|
4
|
+
patternProperties: {
|
|
5
|
+
'.+': {
|
|
6
|
+
type: 'object',
|
|
7
|
+
patternProperties: {
|
|
8
|
+
'.+': {
|
|
9
|
+
type: 'object',
|
|
10
|
+
required: [
|
|
11
|
+
'imported',
|
|
12
|
+
],
|
|
13
|
+
properties: {
|
|
14
|
+
imported: {
|
|
15
|
+
type: ['boolean', 'array'],
|
|
16
|
+
items: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
reportMessage: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
additionalProperties: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
additionalProperties: true,
|
|
31
|
+
}]
|
|
32
|
+
|
|
33
|
+
const defaultReportMessage = (moduleName, exportName) => `${moduleName}${typeof exportName == 'string' ? `/${exportName}`: ''} をimportしてください`
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
meta: {
|
|
37
|
+
type: 'suggestion',
|
|
38
|
+
messages: {
|
|
39
|
+
'require_import': '{{ message }}',
|
|
40
|
+
},
|
|
41
|
+
schema: SCHEMA,
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
const options = context.options[0]
|
|
45
|
+
const filename = context.getFilename()
|
|
46
|
+
const targetPathRegexs = Object.keys(options)
|
|
47
|
+
const targetRequires = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
|
|
48
|
+
|
|
49
|
+
if (targetRequires.length === 0) {
|
|
50
|
+
return {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
Program: (node) => {
|
|
55
|
+
const importDeclarations = node.body.filter((item) => item.type === 'ImportDeclaration')
|
|
56
|
+
const parentDir = (() => {
|
|
57
|
+
const dir = filename.match(/^(.+?)\..+?$/)[1].split('/')
|
|
58
|
+
dir.pop()
|
|
59
|
+
|
|
60
|
+
return dir.join('/')
|
|
61
|
+
})()
|
|
62
|
+
|
|
63
|
+
targetRequires.forEach((requireKey) => {
|
|
64
|
+
const option = options[requireKey]
|
|
65
|
+
|
|
66
|
+
Object.keys(option).forEach((targetModule) => {
|
|
67
|
+
const { imported, reportMessage, targetRegex } = Object.assign({imported: true}, option[targetModule])
|
|
68
|
+
|
|
69
|
+
if (targetRegex && !filename.match(new RegExp(targetRegex))) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`)
|
|
74
|
+
const importDeclaration = importDeclarations.find(
|
|
75
|
+
actualTarget[0] !== '/' ? (
|
|
76
|
+
(id) => id.source.value === actualTarget
|
|
77
|
+
) : (
|
|
78
|
+
(id) => path.resolve(`${parentDir}/${id.source.value}`) === actualTarget
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
const reporter = (item) => {
|
|
82
|
+
context.report({
|
|
83
|
+
node,
|
|
84
|
+
messageId: 'require_import',
|
|
85
|
+
data: {
|
|
86
|
+
message: reportMessage ? reportMessage.replace('{{module}}', actualTarget).replace('{{export}}', item) : defaultReportMessage(actualTarget, item)
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!importDeclaration) {
|
|
92
|
+
if (Array.isArray(imported)) {
|
|
93
|
+
imported.forEach((i) => {
|
|
94
|
+
reporter(i)
|
|
95
|
+
})
|
|
96
|
+
} else if (imported) {
|
|
97
|
+
reporter()
|
|
98
|
+
}
|
|
99
|
+
} else if (Array.isArray(imported)) {
|
|
100
|
+
imported.forEach((i) => {
|
|
101
|
+
if (!importDeclaration.specifiers.find((s) => s.imported && s.imported.name === i)) {
|
|
102
|
+
reporter(i)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports.schema = SCHEMA
|
package/test/prohibit-import.js
CHANGED
|
@@ -1,63 +1,104 @@
|
|
|
1
|
-
const rule = require(
|
|
2
|
-
const RuleTester = require(
|
|
1
|
+
const rule = require('../rules/prohibit-import')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
3
|
|
|
4
4
|
const ruleTester = new RuleTester({
|
|
5
5
|
parserOptions: {
|
|
6
|
-
sourceType:
|
|
6
|
+
sourceType: 'module',
|
|
7
7
|
ecmaVersion: 2015
|
|
8
8
|
},
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
ruleTester.run(
|
|
11
|
+
ruleTester.run('prohibit-import', rule, {
|
|
12
12
|
valid: [
|
|
13
13
|
{
|
|
14
14
|
code: `import _ from 'lodash-es'`,
|
|
15
|
+
filename: 'hoge.js',
|
|
15
16
|
options: [
|
|
16
17
|
{
|
|
17
|
-
'
|
|
18
|
-
|
|
18
|
+
'^.+$': {
|
|
19
|
+
'lodash': {
|
|
20
|
+
imported: true,
|
|
21
|
+
},
|
|
19
22
|
},
|
|
20
23
|
}
|
|
21
24
|
]
|
|
22
25
|
},
|
|
23
26
|
{
|
|
24
27
|
code: `import { isEqual } from 'lodash-es'`,
|
|
28
|
+
filename: 'hoge.js',
|
|
25
29
|
options: [
|
|
26
30
|
{
|
|
27
|
-
'
|
|
28
|
-
|
|
31
|
+
'^.+$': {
|
|
32
|
+
'lodash': {
|
|
33
|
+
imported: ['isEqual']
|
|
34
|
+
},
|
|
29
35
|
},
|
|
30
36
|
}
|
|
31
37
|
]
|
|
32
38
|
},
|
|
33
39
|
{
|
|
34
40
|
code: `import { isEqaul } from 'lodash'`,
|
|
41
|
+
filename: 'hoge.js',
|
|
35
42
|
options: [
|
|
36
43
|
{
|
|
37
|
-
'
|
|
38
|
-
|
|
44
|
+
'^.+$': {
|
|
45
|
+
'lodash': {
|
|
46
|
+
imported: ['isEqual']
|
|
47
|
+
},
|
|
39
48
|
},
|
|
40
49
|
}
|
|
41
50
|
]
|
|
42
51
|
},
|
|
43
52
|
{
|
|
44
53
|
code: `import _ from 'lodash'`,
|
|
54
|
+
filename: 'hoge.js',
|
|
45
55
|
options: [
|
|
46
56
|
{
|
|
47
|
-
'
|
|
48
|
-
|
|
57
|
+
'^.+$': {
|
|
58
|
+
'lodash': {
|
|
59
|
+
imported: ['isEqual']
|
|
60
|
+
},
|
|
49
61
|
},
|
|
50
62
|
}
|
|
51
63
|
]
|
|
52
|
-
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: `import _ from 'lodash'`,
|
|
67
|
+
filename: 'hoge.js',
|
|
68
|
+
options: [
|
|
69
|
+
{
|
|
70
|
+
'^fuga.js$': {
|
|
71
|
+
'lodash': {
|
|
72
|
+
imported: true
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
code: `import { isEqual } from './module/validator'`,
|
|
80
|
+
filename: 'page/hoge.js',
|
|
81
|
+
options: [
|
|
82
|
+
{
|
|
83
|
+
'^.+$': {
|
|
84
|
+
'./module/validator': {
|
|
85
|
+
imported: ['isEqual'],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
},
|
|
53
91
|
],
|
|
54
92
|
invalid: [
|
|
55
93
|
{
|
|
56
94
|
code: `import _ from 'lodash'`,
|
|
95
|
+
filename: 'hoge.js',
|
|
57
96
|
options: [
|
|
58
97
|
{
|
|
59
|
-
'
|
|
60
|
-
|
|
98
|
+
'^.+$': {
|
|
99
|
+
'lodash': {
|
|
100
|
+
imported: true
|
|
101
|
+
},
|
|
61
102
|
},
|
|
62
103
|
}
|
|
63
104
|
],
|
|
@@ -65,10 +106,13 @@ ruleTester.run("prohibit-import-lodash", rule, {
|
|
|
65
106
|
},
|
|
66
107
|
{
|
|
67
108
|
code: `import { isEqual } from 'lodash'`,
|
|
109
|
+
filename: 'hoge.js',
|
|
68
110
|
options: [
|
|
69
111
|
{
|
|
70
|
-
'
|
|
71
|
-
|
|
112
|
+
'^.+$': {
|
|
113
|
+
'lodash': {
|
|
114
|
+
imported: true
|
|
115
|
+
},
|
|
72
116
|
},
|
|
73
117
|
}
|
|
74
118
|
],
|
|
@@ -76,10 +120,13 @@ ruleTester.run("prohibit-import-lodash", rule, {
|
|
|
76
120
|
},
|
|
77
121
|
{
|
|
78
122
|
code: `import { isEqual } from 'lodash'`,
|
|
123
|
+
filename: 'hoge.js',
|
|
79
124
|
options: [
|
|
80
125
|
{
|
|
81
|
-
'
|
|
82
|
-
|
|
126
|
+
'^.+$': {
|
|
127
|
+
'lodash': {
|
|
128
|
+
imported: ['isEqual']
|
|
129
|
+
},
|
|
83
130
|
},
|
|
84
131
|
}
|
|
85
132
|
],
|
|
@@ -87,11 +134,14 @@ ruleTester.run("prohibit-import-lodash", rule, {
|
|
|
87
134
|
},
|
|
88
135
|
{
|
|
89
136
|
code: `import { isEqual } from 'lodash'`,
|
|
137
|
+
filename: 'hoge.js',
|
|
90
138
|
options: [
|
|
91
139
|
{
|
|
92
|
-
'
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
'^.+$': {
|
|
141
|
+
'lodash': {
|
|
142
|
+
imported: ['isEqual'],
|
|
143
|
+
"reportMessage": "must not use {{module}}/{{export}}"
|
|
144
|
+
},
|
|
95
145
|
},
|
|
96
146
|
}
|
|
97
147
|
],
|
|
@@ -99,18 +149,53 @@ ruleTester.run("prohibit-import-lodash", rule, {
|
|
|
99
149
|
},
|
|
100
150
|
{
|
|
101
151
|
code: `import { isEqual } from 'lodash'`,
|
|
152
|
+
filename: 'hoge.js',
|
|
102
153
|
options: [
|
|
103
154
|
{
|
|
104
|
-
'
|
|
105
|
-
|
|
155
|
+
'^.+$': {
|
|
156
|
+
'example': {
|
|
157
|
+
imported: true,
|
|
158
|
+
},
|
|
159
|
+
'lodash': {
|
|
160
|
+
imported: ['isEqual'],
|
|
161
|
+
reportMessage: "must not use {{module}}/{{export}}",
|
|
162
|
+
},
|
|
106
163
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
errors: [{message: 'must not use lodash/isEqual'}]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
code: `import { isEqual } from 'lodash'`,
|
|
170
|
+
filename: 'hoge.js',
|
|
171
|
+
options: [
|
|
172
|
+
{
|
|
173
|
+
'^hoge.js$': {
|
|
174
|
+
'example': {
|
|
175
|
+
imported: true,
|
|
176
|
+
},
|
|
177
|
+
'lodash': {
|
|
178
|
+
imported: ['isEqual'],
|
|
179
|
+
reportMessage: "must not use {{module}}/{{export}}",
|
|
180
|
+
},
|
|
110
181
|
},
|
|
111
182
|
}
|
|
112
183
|
],
|
|
113
184
|
errors: [{message: 'must not use lodash/isEqual'}]
|
|
114
|
-
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
code: `import { isEqual } from './module/validator'`,
|
|
188
|
+
filename: 'page/hoge.js',
|
|
189
|
+
options: [
|
|
190
|
+
{
|
|
191
|
+
'^.+$': {
|
|
192
|
+
'./page/module/validator': {
|
|
193
|
+
imported: ['isEqual'],
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
errors: [{ message: './module/validator/isEqual は利用しないでください' }]
|
|
199
|
+
},
|
|
115
200
|
]
|
|
116
|
-
})
|
|
201
|
+
})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const rule = require('../rules/require-import')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
sourceType: 'module',
|
|
7
|
+
ecmaVersion: 2015
|
|
8
|
+
},
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
ruleTester.run('require-import', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
{
|
|
14
|
+
code: `import _ from 'lodash'`,
|
|
15
|
+
filename: 'hoge.js',
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
'^.+$': {
|
|
19
|
+
'lodash': {
|
|
20
|
+
imported: true,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: ``,
|
|
28
|
+
filename: 'hoge.js',
|
|
29
|
+
options: [
|
|
30
|
+
{
|
|
31
|
+
'^fuga.js$': {
|
|
32
|
+
'lodash': {
|
|
33
|
+
imported: true,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
code: `import _ from 'lodash'`,
|
|
41
|
+
filename: 'hoge.js',
|
|
42
|
+
options: [
|
|
43
|
+
{
|
|
44
|
+
'^hoge.js$': {
|
|
45
|
+
'lodash': {
|
|
46
|
+
imported: true,
|
|
47
|
+
reportMessage: '{{module}} を絶対使ってください'
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
code: `import { isEqual } from 'lodash'`,
|
|
55
|
+
filename: 'hoge.js',
|
|
56
|
+
options: [
|
|
57
|
+
{
|
|
58
|
+
'^hoge.js$': {
|
|
59
|
+
'lodash': {
|
|
60
|
+
imported: ['isEqual'],
|
|
61
|
+
reportMessage: '{{module}}/{{export}} を絶対使ってください'
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
errors: [{ message: 'lodash/isEqual を絶対使ってください' }],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
code: `import { chunk } from 'lodash'`,
|
|
70
|
+
filename: 'hoge.js',
|
|
71
|
+
options: [
|
|
72
|
+
{
|
|
73
|
+
'^hoge.js$': {
|
|
74
|
+
'lodash': {
|
|
75
|
+
imported: true,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
code: `import { isEqual } from './module/validator'`,
|
|
83
|
+
filename: 'page/hoge.js',
|
|
84
|
+
options: [
|
|
85
|
+
{
|
|
86
|
+
'^.+$': {
|
|
87
|
+
'./page/module/validator': {
|
|
88
|
+
imported: ['isEqual'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
invalid: [
|
|
96
|
+
{
|
|
97
|
+
code: ``,
|
|
98
|
+
filename: 'hoge.js',
|
|
99
|
+
options: [
|
|
100
|
+
{
|
|
101
|
+
'^.+$': {
|
|
102
|
+
'lodash': {
|
|
103
|
+
imported: true,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
errors: [{ message: 'lodash をimportしてください' }],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: ``,
|
|
112
|
+
filename: 'hoge.js',
|
|
113
|
+
options: [
|
|
114
|
+
{
|
|
115
|
+
'^hoge.js$': {
|
|
116
|
+
'lodash': {
|
|
117
|
+
imported: true,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
],
|
|
122
|
+
errors: [{ message: 'lodash をimportしてください' }],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
code: ``,
|
|
126
|
+
filename: 'hoge.js',
|
|
127
|
+
options: [
|
|
128
|
+
{
|
|
129
|
+
'^hoge.js$': {
|
|
130
|
+
'lodash': {
|
|
131
|
+
imported: true,
|
|
132
|
+
reportMessage: '{{module}} を絶対使ってください'
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
],
|
|
137
|
+
errors: [{ message: 'lodash を絶対使ってください' }],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
code: ``,
|
|
141
|
+
filename: 'hoge.js',
|
|
142
|
+
options: [
|
|
143
|
+
{
|
|
144
|
+
'^hoge.js$': {
|
|
145
|
+
'lodash': {
|
|
146
|
+
imported: ['isEqual'],
|
|
147
|
+
reportMessage: '{{module}}/{{export}} を絶対使ってください'
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
errors: [{ message: 'lodash/isEqual を絶対使ってください' }],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
code: `import { chunk } from 'lodash'`,
|
|
156
|
+
filename: 'hoge.js',
|
|
157
|
+
options: [
|
|
158
|
+
{
|
|
159
|
+
'^hoge.js$': {
|
|
160
|
+
'lodash': {
|
|
161
|
+
imported: ['isEqual'],
|
|
162
|
+
reportMessage: '{{module}}/{{export}} を絶対使ってください'
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
errors: [{ message: 'lodash/isEqual を絶対使ってください' }],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
code: `import { isEqual } from './module/validator'`,
|
|
171
|
+
filename: 'page/hoge.js',
|
|
172
|
+
options: [
|
|
173
|
+
{
|
|
174
|
+
'^.+$': {
|
|
175
|
+
'./module/validator': {
|
|
176
|
+
imported: ['isEqual'],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
errors: [{ message: /module\/validator\/isEqual をimportしてください$/ }],
|
|
182
|
+
},
|
|
183
|
+
]
|
|
184
|
+
})
|