eslint-plugin-smarthr 0.1.2 → 0.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/.github/CODEOWNERS +3 -0
- package/CHANGELOG.md +27 -0
- package/README.md +15 -489
- package/github/CODEOWNERS +3 -0
- package/index.js +3 -6
- package/libs/format_styled_components.js +57 -0
- package/package.json +1 -1
- package/rules/a11y-clickable-element-has-text/README.md +61 -0
- package/rules/a11y-clickable-element-has-text/index.js +71 -0
- package/rules/a11y-image-has-alt-attribute/README.md +55 -0
- package/rules/a11y-image-has-alt-attribute/index.js +48 -0
- package/rules/a11y-trigger-has-button/README.md +57 -0
- package/rules/a11y-trigger-has-button/index.js +74 -0
- package/rules/best-practice-for-date/README.md +40 -0
- package/rules/best-practice-for-date/index.js +42 -0
- package/rules/format-import-path/README.md +99 -0
- package/rules/{format-import-path.js → format-import-path/index.js} +2 -2
- package/rules/format-translate-component/README.md +58 -0
- package/rules/format-translate-component/index.js +97 -0
- package/rules/jsx-start-with-spread-attributes/README.md +31 -0
- package/rules/{jsx-start-with-spread-attributes.js → jsx-start-with-spread-attributes/index.js} +0 -0
- package/rules/no-import-other-domain/README.md +85 -0
- package/rules/{no-import-other-domain.js → no-import-other-domain/index.js} +2 -2
- package/rules/prohibit-export-array-type/README.md +28 -0
- package/rules/prohibit-export-array-type/index.js +28 -0
- package/rules/prohibit-file-name/README.md +35 -0
- package/rules/prohibit-file-name/index.js +61 -0
- package/rules/prohibit-import/README.md +44 -0
- package/rules/{prohibit-import.js → prohibit-import/index.js} +0 -0
- package/rules/redundant-name/README.md +94 -0
- package/rules/{redundant-name.js → redundant-name/index.js} +15 -5
- package/rules/require-barrel-import/README.md +39 -0
- package/rules/{require-barrel-import.js → require-barrel-import/index.js} +1 -1
- package/rules/require-export/README.md +43 -0
- package/rules/require-export/index.js +90 -0
- package/rules/require-import/README.md +51 -0
- package/rules/{require-import.js → require-import/index.js} +0 -0
- package/test/a11y-clickable-element-has-text.js +142 -0
- package/test/a11y-image-has-alt-attribute.js +44 -0
- package/test/a11y-trigger-has-button.js +50 -0
- package/test/best-practice-for-date.js +31 -0
- package/test/format-translate-component.js +37 -0
- package/test/prohibit-file-name.js +45 -0
- package/test/require-export.js +83 -0
- package/rules/a11y-icon-button-has-name.js +0 -56
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# smarthr/no-import-other-domain
|
|
2
|
+
|
|
3
|
+
- ドメイン外からのimportを防ぐruleです
|
|
4
|
+
- 例: crews/index 以下からのimportはOK, crews/index から crews/show 以下をimportするとNG
|
|
5
|
+
- ディレクトリ構造からドメインを識別して判定することが出来ます
|
|
6
|
+
|
|
7
|
+
## config
|
|
8
|
+
|
|
9
|
+
- tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
|
|
10
|
+
- ドメインを識別するために以下の設定を記述する必要があります
|
|
11
|
+
- globalModuleDir
|
|
12
|
+
- 全体で利用するファイルを収めているディレクトリを相対パスで指定します
|
|
13
|
+
- domainModuleDir:
|
|
14
|
+
- ドメイン内で共通のファイルを収めているディレクトリ名を指定します
|
|
15
|
+
- domainConstituteDir
|
|
16
|
+
- ドメインを構築するディレクトリ名を指定します
|
|
17
|
+
|
|
18
|
+
### ディレクトリ例
|
|
19
|
+
```
|
|
20
|
+
/ constants
|
|
21
|
+
/ modules // 全体共通ディレクトリ
|
|
22
|
+
/ crews
|
|
23
|
+
/ modules // 共通ディレクトリ
|
|
24
|
+
/ views
|
|
25
|
+
/ parts
|
|
26
|
+
/ index
|
|
27
|
+
/ adapters
|
|
28
|
+
/ index.ts
|
|
29
|
+
/ hoge.ts
|
|
30
|
+
/ slices
|
|
31
|
+
/ index.ts
|
|
32
|
+
/ views
|
|
33
|
+
/ index.ts
|
|
34
|
+
/ parts
|
|
35
|
+
/ Abc.ts
|
|
36
|
+
/ show
|
|
37
|
+
/ views
|
|
38
|
+
/ parts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 指定例
|
|
42
|
+
```
|
|
43
|
+
const DOMAIN_RULE_ARGS = {
|
|
44
|
+
globalModuleDir: [ './constants', './modules' ],
|
|
45
|
+
domainModuleDir: [ 'modules' ],
|
|
46
|
+
domainConstituteDir: [ 'adapters', 'slices', 'views', 'parts' ],
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## rules
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
{
|
|
54
|
+
rules: {
|
|
55
|
+
'smarthr/no-import-other-domain': [
|
|
56
|
+
'error', // 'warn', 'off'
|
|
57
|
+
{
|
|
58
|
+
...DOMAIN_RULE_ARGS,
|
|
59
|
+
// analyticsMode: 'all', // 'same-domain', 'another-domain'
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## ❌ Incorrect
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
// crews/index/views/index.js
|
|
70
|
+
|
|
71
|
+
import showPart1 from '@/crews/show/views/parts'
|
|
72
|
+
import showPart2 from '../../show/views/parts'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## ✅ Correct
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
// crews/index/views/index.js
|
|
79
|
+
|
|
80
|
+
import slice from '../slice'
|
|
81
|
+
import hoge from '../adapter/hoge'
|
|
82
|
+
import Abc from './parts/Abc'
|
|
83
|
+
import modulePart from '../../modules/views/parts'
|
|
84
|
+
import globalModulePart from '@/modules/views/parts'
|
|
85
|
+
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const fs = require('fs')
|
|
3
|
-
const { replacePaths, rootPath } = require('
|
|
4
|
-
const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('
|
|
3
|
+
const { replacePaths, rootPath } = require('../../libs/common')
|
|
4
|
+
const { BASE_SCHEMA_PROPERTIES, calculateDomainContext, calculateDomainNode } = require('../../libs/common_domain')
|
|
5
5
|
|
|
6
6
|
const SCHEMA = [
|
|
7
7
|
{
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# smarthr/prohibit-export-array-type
|
|
2
|
+
|
|
3
|
+
- 配列の型をexport出来ないように制御するルールです
|
|
4
|
+
- 利用するファイルで `ItemProps[]` のように配列指定を強制する目的などで利用できます
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/prohibit-export-array-type': 'error', // 'warn', 'off'
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## ❌ Incorrect
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
type Item = { attr: string }
|
|
20
|
+
export type Items = Item[]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ✅ Correct
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
export type Item = { attr: string }
|
|
28
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
messages: {
|
|
5
|
+
'prohibit-export-array-type': '{{ message }}',
|
|
6
|
+
},
|
|
7
|
+
schema: [],
|
|
8
|
+
},
|
|
9
|
+
create(context) {
|
|
10
|
+
const checker = (node) => {
|
|
11
|
+
if (node.declaration?.typeAnnotation?.type === 'TSArrayType') {
|
|
12
|
+
context.report({
|
|
13
|
+
node,
|
|
14
|
+
messageId: 'prohibit-export-array-type',
|
|
15
|
+
data: {
|
|
16
|
+
message: '利用する際、配列かどうかわかりにくいため、配列ではない状態でexportしてください',
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
ExportDefaultDeclaration: checker,
|
|
24
|
+
ExportNamedDeclaration: checker,
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
module.exports.schema = []
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# smarthr/prohibit-file-name
|
|
2
|
+
|
|
3
|
+
- 正規表現に合致するファイル、ディレクトリの作成を阻害するルールです
|
|
4
|
+
|
|
5
|
+
## rules
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'smarthr/prohibit-file-name': [
|
|
11
|
+
'error', // 'warn', 'off'
|
|
12
|
+
{
|
|
13
|
+
'\/(actions|reducers)\/$': 'slicesディレクトリ内でcreateSliceを利用してください',
|
|
14
|
+
'\/views\/(page|template)\.(ts(x)?)$': 'index.$2、もしくはTemplate.$2にリネームしてください',
|
|
15
|
+
'\/modules\/(adapters|entities|repositories|slices)\/index\.ts(x)?$': '利用目的が推測出来ない為、リネームしてください(例: new, edit用ならform.tsなど)',
|
|
16
|
+
},
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ❌ Incorrect
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
// src/pages/actions/index.ts
|
|
26
|
+
// src/modules/entities/index.ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ✅ Correct
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// src/pages/slices/index.ts
|
|
34
|
+
// src/modules/entities/item.ts
|
|
35
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const SCHEMA = [{
|
|
2
|
+
type: 'object',
|
|
3
|
+
patternProperties: {
|
|
4
|
+
'.+': {
|
|
5
|
+
type: 'string',
|
|
6
|
+
},
|
|
7
|
+
},
|
|
8
|
+
additionalProperties: true,
|
|
9
|
+
}]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
messages: {
|
|
16
|
+
'prohibit-file-name': '{{ message }}',
|
|
17
|
+
},
|
|
18
|
+
schema: SCHEMA,
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const options = context.options[0]
|
|
22
|
+
const filename = context.getFilename()
|
|
23
|
+
const targetPaths = Object.keys(options).filter((regex) => !!filename.match(new RegExp(regex)))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if (targetPaths.length === 0) {
|
|
27
|
+
return {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const messages = []
|
|
31
|
+
|
|
32
|
+
targetPaths.forEach((path) => {
|
|
33
|
+
const message = options[path]
|
|
34
|
+
|
|
35
|
+
matcher = filename.match(new RegExp(path))
|
|
36
|
+
|
|
37
|
+
if (matcher) {
|
|
38
|
+
messages.push([...matcher].reduce(((prev, k, index) => prev.replaceAll(`\$${index}`, k)), message))
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (messages.length === 0) {
|
|
43
|
+
return {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
Program: (node) => {
|
|
48
|
+
messages.forEach((message) => {
|
|
49
|
+
context.report({
|
|
50
|
+
node,
|
|
51
|
+
messageId: 'prohibit-file-name',
|
|
52
|
+
data: {
|
|
53
|
+
message,
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
module.exports.schema = SCHEMA
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# smarthr/prohibit-import
|
|
2
|
+
|
|
3
|
+
- 例えば特定の module にバグが発見されたなど、importさせたくない場合に利用するルールです
|
|
4
|
+
|
|
5
|
+
## rules
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'smarthr/prohibit-import': [
|
|
11
|
+
'error', // 'warn', 'off'
|
|
12
|
+
{
|
|
13
|
+
'^.+$': {
|
|
14
|
+
'smarthr-ui': {
|
|
15
|
+
imported: ['SecondaryButtonAnchor'],
|
|
16
|
+
reportMessage: `{{module}}/{{export}} はXxxxxxなので利用せず yyyy/zzzz を利用してください`
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
'\/pages\/views\/': {
|
|
20
|
+
'query-string': {
|
|
21
|
+
imported: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## ❌ Incorrect
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// src/pages/views/Page.tsx
|
|
34
|
+
import queryString from 'query-string'
|
|
35
|
+
import { SecondaryButtonAnchor } from 'smarthr-ui'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## ✅ Correct
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// src/pages/views/Page.tsx
|
|
43
|
+
import { PrimaryButton, SecondaryButton } from 'smarthr-ui'
|
|
44
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# smarthr/redundant-name
|
|
2
|
+
|
|
3
|
+
- ファイル、コードの冗長な部分を取り除くことを提案するruleです
|
|
4
|
+
- ファイルが設置されているディレクトリ構造からキーワードを生成し、取り除く文字列を生成します
|
|
5
|
+
|
|
6
|
+
## config
|
|
7
|
+
|
|
8
|
+
- tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
|
|
9
|
+
- 以下の設定を行えます。全て省略可能です。
|
|
10
|
+
- ignoreKeywords
|
|
11
|
+
- ディレクトリ名から生成されるキーワードに含めたくない文字列を指定します
|
|
12
|
+
- betterNames
|
|
13
|
+
- 対象の名前を修正する候補を指定します
|
|
14
|
+
- allowedNames
|
|
15
|
+
- 許可する名前を指定します
|
|
16
|
+
- suffix:
|
|
17
|
+
- type のみ指定出来ます
|
|
18
|
+
- type のsuffixを指定します
|
|
19
|
+
|
|
20
|
+
### ファイル例
|
|
21
|
+
- `@/crews/index/views/page.tsx` の場合
|
|
22
|
+
- 生成されるキーワードは `['crews', 'crew', 'index', 'page']`
|
|
23
|
+
- `@/crews/index/views/parts/Abc.tsx` の場合
|
|
24
|
+
- 生成されるキーワードは `['crews', 'crew', 'index', 'Abc']`
|
|
25
|
+
- `@/crews/index/repositories/index.ts` の場合
|
|
26
|
+
- 生成されるキーワードは `['crews', 'crew', 'index', 'repositories', 'repository']`
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## rules
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const ignorekeywords = ['views', 'parts']
|
|
33
|
+
const betterNames = {
|
|
34
|
+
'\/repositories\/': {
|
|
35
|
+
operator: '-',
|
|
36
|
+
names: ['repository', 'Repository'],
|
|
37
|
+
},
|
|
38
|
+
'\/entities\/': {
|
|
39
|
+
operator: '+',
|
|
40
|
+
names: ['entity'],
|
|
41
|
+
},
|
|
42
|
+
'\/slices\/': {
|
|
43
|
+
operator: '=',
|
|
44
|
+
names: ['index'],
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
// const allowedNames = {
|
|
48
|
+
// '\/views\/crews\/histories\/': ['crewId'],
|
|
49
|
+
// }
|
|
50
|
+
|
|
51
|
+
{
|
|
52
|
+
rules: {
|
|
53
|
+
'smarthr/redundant-name': [
|
|
54
|
+
'error', // 'warn', 'off'
|
|
55
|
+
{
|
|
56
|
+
type: { ignorekeywords, suffix: ['Props', 'Type'] },
|
|
57
|
+
file: { ignorekeywords, betternames },
|
|
58
|
+
// property: { ignorekeywords, allowedNames },
|
|
59
|
+
// function: { ignorekeywords },
|
|
60
|
+
// variable: { ignorekeywords },
|
|
61
|
+
// class: { ignorekeywords },
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## ❌ Incorrect
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
// @/crews/index/views/page.tsx
|
|
72
|
+
|
|
73
|
+
type CrewIndexPage = { hoge: string }
|
|
74
|
+
type CrewsView = { hoge: string }
|
|
75
|
+
```
|
|
76
|
+
```js
|
|
77
|
+
// @/crews/show/repositories/index.tsx
|
|
78
|
+
|
|
79
|
+
type CrewIndexRepository = { hoge: () => any }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## ✅ Correct
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
// @/crews/index/views/page.tsx
|
|
86
|
+
|
|
87
|
+
type ItemProps = { hoge: string }
|
|
88
|
+
```
|
|
89
|
+
```js
|
|
90
|
+
// @/crews/show/repositories/index.tsx
|
|
91
|
+
|
|
92
|
+
type IndexProps = { hoge: () => any }
|
|
93
|
+
type ResponseType = { hoge: () => any }
|
|
94
|
+
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const Inflector = require('inflected')
|
|
3
3
|
|
|
4
|
-
const { rootPath } = require('
|
|
4
|
+
const { rootPath } = require('../../libs/common')
|
|
5
5
|
|
|
6
6
|
const uniq = (array) => array.filter((elem, index, self) => self.indexOf(elem) === index)
|
|
7
7
|
|
|
@@ -48,6 +48,10 @@ const DEFAULT_SCHEMA_PROPERTY = {
|
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
|
+
allowedNames: {
|
|
52
|
+
type: 'array',
|
|
53
|
+
items: 'string',
|
|
54
|
+
},
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
const SCHEMA = [
|
|
@@ -91,9 +95,11 @@ const generateRedundantKeywords = ({ args, key, terminalImportName }) => {
|
|
|
91
95
|
return prev
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
return [...prev, ...uniq([
|
|
99
|
+
Inflector.pluralize(keyword),
|
|
100
|
+
keyword,
|
|
101
|
+
Inflector.singularize(keyword),
|
|
102
|
+
])]
|
|
97
103
|
}, [])
|
|
98
104
|
}
|
|
99
105
|
const handleReportBetterName = ({
|
|
@@ -113,7 +119,11 @@ const handleReportBetterName = ({
|
|
|
113
119
|
return (node) => {
|
|
114
120
|
const name = fetchName(node)
|
|
115
121
|
|
|
116
|
-
if (
|
|
122
|
+
if (
|
|
123
|
+
!name ||
|
|
124
|
+
option.allowedNames &&
|
|
125
|
+
Object.entries(option.allowedNames).find(([regex, calcs]) => filename.match(new RegExp(regex)) && calcs.find((c) => c === name))
|
|
126
|
+
) {
|
|
117
127
|
return
|
|
118
128
|
}
|
|
119
129
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# smarthr/require-barrel-import
|
|
2
|
+
|
|
3
|
+
- tsconfig.json の compilerOptions.pathsに '@/*' としてroot path を指定する必要があります
|
|
4
|
+
- importした対象が本来exportされているべきであるbarrel(index.tsなど)が有る場合、import pathの変更を促します
|
|
5
|
+
- 例: Page/parts/Menu/Item の import は Page/parts/Menu から行わせたい
|
|
6
|
+
- ディレクトリ内のindexファイルを捜査し、対象を決定します
|
|
7
|
+
|
|
8
|
+
## rules
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
{
|
|
12
|
+
rules: {
|
|
13
|
+
'smarthr/require-barrel-import': 'error',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## ❌ Incorrect
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
// client/src/views/Page/parts/Menu/index.ts
|
|
22
|
+
export { Menu } from './Menu'
|
|
23
|
+
export { Item } from './Item'
|
|
24
|
+
|
|
25
|
+
// client/src/App.tsx
|
|
26
|
+
import { Item } from './Page/parts/Menu/Item'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## ✅ Correct
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// client/src/views/Page/parts/Menu/index.ts
|
|
34
|
+
export { Menu } from './Menu'
|
|
35
|
+
export { Item } from './Item'
|
|
36
|
+
|
|
37
|
+
// client/src/App.tsx
|
|
38
|
+
import { Item } from './Page/parts/Menu'
|
|
39
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const path = require('path')
|
|
2
2
|
const fs = require('fs')
|
|
3
|
-
const { replacePaths, rootPath } = require('
|
|
3
|
+
const { replacePaths, rootPath } = require('../../libs/common')
|
|
4
4
|
const calculateAbsoluteImportPath = (source) => {
|
|
5
5
|
if (source[0] === '/') {
|
|
6
6
|
return source
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# smarthr/require-export
|
|
2
|
+
|
|
3
|
+
- 対象ファイルにexportを強制させたい場合に利用します
|
|
4
|
+
- 例: Page.tsx ではページタイトルを設定させたいので useTitle を必ずexportさせたい
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/require-export': [
|
|
12
|
+
'error',
|
|
13
|
+
{
|
|
14
|
+
'adapter\/.+\.ts': ['Props', 'generator'],
|
|
15
|
+
// slice以下のファイルかつmodulesディレクトリに属さずファイル名にmockを含まないもの
|
|
16
|
+
'^(?=.*\/slices\/[a-zA-Z0-9]+\.ts)(?!.*(\/modules\/|mock\.)).*$': [ 'default' ],
|
|
17
|
+
},
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ❌ Incorrect
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// adapter/index.ts
|
|
27
|
+
export type Type = { abc: string }
|
|
28
|
+
|
|
29
|
+
// slice/index.ts
|
|
30
|
+
export const slice = { method: () => 'any action' }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## ✅ Correct
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
// adapter/index.ts
|
|
38
|
+
export type Props = { abc: string }
|
|
39
|
+
|
|
40
|
+
// slice/index.ts
|
|
41
|
+
const slice = { method: () => 'any action' }
|
|
42
|
+
export default slice
|
|
43
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const SCHEMA = [{
|
|
2
|
+
type: 'object',
|
|
3
|
+
patternProperties: {
|
|
4
|
+
'.+': {
|
|
5
|
+
type: 'array',
|
|
6
|
+
items: { type: 'string' },
|
|
7
|
+
additionalProperties: true,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
additionalProperties: true,
|
|
11
|
+
}]
|
|
12
|
+
|
|
13
|
+
const fetchEdgeDeclaration = (node) => {
|
|
14
|
+
const { declaration } = node
|
|
15
|
+
|
|
16
|
+
if (!declaration) {
|
|
17
|
+
return node
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return fetchEdgeDeclaration(declaration)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
meta: {
|
|
25
|
+
type: 'suggestion',
|
|
26
|
+
messages: {
|
|
27
|
+
'require-export': '{{ message }}',
|
|
28
|
+
},
|
|
29
|
+
schema: SCHEMA,
|
|
30
|
+
},
|
|
31
|
+
create(context) {
|
|
32
|
+
const options = context.options[0]
|
|
33
|
+
const filename = context.getFilename()
|
|
34
|
+
const targetPathRegexs = Object.keys(options)
|
|
35
|
+
const targetRequires = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
|
|
36
|
+
|
|
37
|
+
if (targetRequires.length === 0) {
|
|
38
|
+
return {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
Program: (node) => {
|
|
43
|
+
targetRequires.forEach((requireKey) => {
|
|
44
|
+
const option = options[requireKey]
|
|
45
|
+
let existDefault = false
|
|
46
|
+
const exports =
|
|
47
|
+
node.body
|
|
48
|
+
.filter((i) => {
|
|
49
|
+
if (i.type == 'ExportDefaultDeclaration') {
|
|
50
|
+
existDefault = true
|
|
51
|
+
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return i.type == 'ExportNamedDeclaration'
|
|
56
|
+
})
|
|
57
|
+
.map((i) => {
|
|
58
|
+
const declaration = fetchEdgeDeclaration(i)
|
|
59
|
+
|
|
60
|
+
if (declaration.id) {
|
|
61
|
+
return declaration.id.name
|
|
62
|
+
}
|
|
63
|
+
if (declaration.specifiers) {
|
|
64
|
+
return declaration.specifiers.map((s) => s.exported.name)
|
|
65
|
+
}
|
|
66
|
+
if (declaration.declarations) {
|
|
67
|
+
return declaration.declarations.map((d) => d.id.name || d.id.properties.map((p) => p.key.name))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return declaration
|
|
71
|
+
})
|
|
72
|
+
.flat(2)
|
|
73
|
+
|
|
74
|
+
let notExistsExports = [...(!existDefault && option.includes('default') ? ['default'] : []), ...option.filter((o) => o !== 'default' && !exports.includes(o))]
|
|
75
|
+
|
|
76
|
+
if (notExistsExports.length) {
|
|
77
|
+
context.report({
|
|
78
|
+
node,
|
|
79
|
+
messageId: 'require-export',
|
|
80
|
+
data: {
|
|
81
|
+
message: `${notExistsExports.join(', ')} をexportしてください`,
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
module.exports.schema = SCHEMA
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# smarthr/require-import
|
|
2
|
+
|
|
3
|
+
- 対象ファイルにimportを強制させたい場合に利用します
|
|
4
|
+
- 例: Page.tsx ではページタイトルを設定させたいので useTitle を必ずimportさせたい
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/require-import': [
|
|
12
|
+
'error',
|
|
13
|
+
{
|
|
14
|
+
'Buttons\/.+\.tsx': {
|
|
15
|
+
'smarthr-ui': {
|
|
16
|
+
imported: ['SecondaryButton'],
|
|
17
|
+
reportMessage: 'Buttons以下のコンポーネントでは {{module}}/{{export}} を拡張するようにしてください',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
'Page.tsx$': {
|
|
21
|
+
'./client/src/hooks/useTitle': {
|
|
22
|
+
imported: true,
|
|
23
|
+
reportMessage: '{{module}} を利用してください(ページタイトルを設定するため必要です)',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## ❌ Incorrect
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
// client/src/Buttons/SecondaryButton.tsx
|
|
36
|
+
import { SecondaryButtonAnchor } from 'smarthr-ui'
|
|
37
|
+
|
|
38
|
+
// client/src/Page.tsx
|
|
39
|
+
import { SecondaryButton } from 'smarthr-ui'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## ✅ Correct
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// client/src/Buttons/SecondaryButton.tsx
|
|
47
|
+
import { SecondaryButton } from 'smarthr-ui'
|
|
48
|
+
|
|
49
|
+
// client/src/Page.tsx
|
|
50
|
+
import useTitle from '.hooks/useTitle'
|
|
51
|
+
```
|
|
File without changes
|