eslint-plugin-smarthr 0.2.0 → 0.2.3
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 -0
- package/README.md +15 -574
- package/index.js +3 -6
- package/libs/format_styled_components.js +47 -30
- package/package.json +1 -1
- package/rules/a11y-clickable-element-has-text/README.md +61 -0
- package/rules/{a11y-clickable-element-has-text.js → a11y-clickable-element-has-text/index.js} +1 -1
- package/rules/a11y-image-has-alt-attribute/README.md +55 -0
- package/rules/{a11y-image-has-alt-attribute.js → a11y-image-has-alt-attribute/index.js} +1 -1
- package/rules/a11y-trigger-has-button/README.md +57 -0
- package/rules/{a11y-trigger-has-button.js → a11y-trigger-has-button/index.js} +1 -1
- 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.js → format-translate-component/index.js} +0 -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} +10 -2
- 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/best-practice-for-date.js +31 -0
- package/test/prohibit-file-name.js +45 -0
- package/test/require-export.js +83 -0
|
@@ -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
|
|
|
@@ -203,7 +203,7 @@ const handleReportBetterName = ({
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
const generateTypeRedundant = (args) => {
|
|
206
|
-
const { context } = args
|
|
206
|
+
const { context, filename } = args
|
|
207
207
|
const key = 'type'
|
|
208
208
|
const redundantKeywords = generateRedundantKeywords({ args, key })
|
|
209
209
|
const option = args.option[key]
|
|
@@ -211,6 +211,14 @@ const generateTypeRedundant = (args) => {
|
|
|
211
211
|
|
|
212
212
|
return (node) => {
|
|
213
213
|
const typeName = node.id.name
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
option.allowedNames &&
|
|
217
|
+
Object.entries(option.allowedNames).find(([regex, calcs]) => filename.match(new RegExp(regex)) && calcs.find((c) => c === typeName))
|
|
218
|
+
) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
214
222
|
const suffix = option.suffix || defaultConfig.SUFFIX
|
|
215
223
|
|
|
216
224
|
let SuffixedName = typeName
|
|
@@ -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
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const rule = require('../rules/best-practice-for-date')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaVersion: 2018,
|
|
7
|
+
ecmaFeatures: {
|
|
8
|
+
experimentalObjectRestSpread: true,
|
|
9
|
+
jsx: true,
|
|
10
|
+
},
|
|
11
|
+
sourceType: 'module',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const errorNewDate = "'new Date(arg)' のように引数一つのみの指定方は実行環境により結果が変わる可能性があるため 'new Date(2022, 12 - 1, 31)' のようにparseするなど他の方法を検討してください。"
|
|
16
|
+
const errorDateParse = 'Date.parse は日付形式の解釈がブラウザによって異なるため、他の手段を検討してください'
|
|
17
|
+
|
|
18
|
+
ruleTester.run('best-practice-for-date', rule, {
|
|
19
|
+
valid: [
|
|
20
|
+
{ code: `new Date()` },
|
|
21
|
+
{ code: `new Date(2022, 11, 31)` },
|
|
22
|
+
{ code: `new Date('2022', '11', '31')` },
|
|
23
|
+
{ code: `const year = 2022; const month = 11; const date = 31; new Date(year, month, date)` },
|
|
24
|
+
],
|
|
25
|
+
invalid: [
|
|
26
|
+
{ code: 'new Date("2022/12/31")', errors: [ { message: errorNewDate } ] },
|
|
27
|
+
{ code: 'const arg = "2022/12/31"; new Date(arg)', errors: [ { message: errorNewDate } ] },
|
|
28
|
+
{ code: 'Date.parse("2022/12/31")', errors: [ { message: errorDateParse } ] },
|
|
29
|
+
{ code: 'const arg = "2022/12/31"; Date.parse(arg)', errors: [ { message: errorDateParse } ] },
|
|
30
|
+
]
|
|
31
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const rule = require('../rules/prohibit-file-name')
|
|
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('prohibit-file-name', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
{
|
|
14
|
+
code: 'const any = "code"',
|
|
15
|
+
filename: 'hoge.js',
|
|
16
|
+
options: [
|
|
17
|
+
{
|
|
18
|
+
'fuga\.js': 'any message.',
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
invalid: [
|
|
24
|
+
{
|
|
25
|
+
code: 'const any = "code"',
|
|
26
|
+
filename: 'hoge.js',
|
|
27
|
+
options: [
|
|
28
|
+
{
|
|
29
|
+
'hoge\.js': 'any message.',
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
errors: [{ message: 'any message.' }]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: 'const any = "code"',
|
|
36
|
+
filename: 'hoge.js',
|
|
37
|
+
options: [
|
|
38
|
+
{
|
|
39
|
+
'(hoge|fuga)\.js': '$1.jsは作成しないで!',
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
errors: [{ message: 'hoge.jsは作成しないで!' }]
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
})
|