eslint-plugin-smarthr 0.2.13 → 0.2.15
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 +19 -1
- package/README.md +2 -0
- package/package.json +1 -1
- package/rules/no-import-other-domain/README.md +7 -0
- package/rules/no-import-other-domain/index.js +65 -3
- package/rules/prohibit-path-within-template-literal/README.md +42 -0
- package/rules/prohibit-path-within-template-literal/index.js +60 -0
- package/rules/require-barrel-import/README.md +10 -1
- package/rules/require-barrel-import/index.js +77 -3
- package/rules/trim-props/README.md +30 -0
- package/rules/trim-props/index.js +37 -0
- package/test/prohibit-path-within-template-literal.js +30 -0
- package/test/trim-props.js +78 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,30 @@
|
|
|
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.2.15](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.12...v0.2.15) (2022-12-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add prohibit-path-within-template-literal rule. ([#46](https://github.com/kufu/eslint-plugin-smarthr/issues/46)) ([1ea51a9](https://github.com/kufu/eslint-plugin-smarthr/commit/1ea51a95f0720e34729959dbc27b33a91ec2d73e))
|
|
11
|
+
* ignores option for no-import-other-domain and require-barrel-import ([#45](https://github.com/kufu/eslint-plugin-smarthr/issues/45)) ([559fdcf](https://github.com/kufu/eslint-plugin-smarthr/commit/559fdcf9d075e5fe29a48ab2f2d4f1e5d1a33201))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* redundant-nameのarrowedNameなどで利用するファイルパスが削られすぎていたため、指定が行いにくい問題を修正する ([#43](https://github.com/kufu/eslint-plugin-smarthr/issues/43)) ([6de9618](https://github.com/kufu/eslint-plugin-smarthr/commit/6de961831a9f9e0e93eeeebb80e56ecb60d9a2ff))
|
|
17
|
+
|
|
18
|
+
### [0.2.14](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.13...v0.2.14) (2022-12-13)
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
* trim-propsルールを追加 ([#44](https://github.com/kufu/eslint-plugin-smarthr/pull/44))
|
|
22
|
+
|
|
5
23
|
### [0.2.13](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.12...v0.2.13) (2022-12-07)
|
|
6
24
|
|
|
7
25
|
|
|
8
26
|
### Bug Fixes
|
|
9
27
|
|
|
10
|
-
* redundant-nameのarrowedNameなどで利用するファイルパスが削られすぎていたため、指定が行いにくい問題を修正する ([
|
|
28
|
+
* redundant-nameのarrowedNameなどで利用するファイルパスが削られすぎていたため、指定が行いにくい問題を修正する ([#43](https://github.com/kufu/eslint-plugin-smarthr/issues/43)) ([6de9618](https://github.com/kufu/eslint-plugin-smarthr/commit/6de961831a9f9e0e93eeeebb80e56ecb60d9a2ff))
|
|
11
29
|
|
|
12
30
|
### [0.2.12](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.11...v0.2.12) (2022-12-07)
|
|
13
31
|
|
package/README.md
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
- [prohibit-export-array-type](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-export-array-type)
|
|
13
13
|
- [prohibit-file-name](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-file-name)
|
|
14
14
|
- [prohibit-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-import)
|
|
15
|
+
- [prohibit-path-within-template-literal](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/prohibit-path-within-template-literal)
|
|
15
16
|
- [redundant-name](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/redundant-name)
|
|
16
17
|
- [require-barrel-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-barrel-import)
|
|
17
18
|
- [require-declaration](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-declaration)
|
|
18
19
|
- [require-export](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-export)
|
|
19
20
|
- [require-import](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/require-import)
|
|
21
|
+
- [trim-props](https://github.com/kufu/eslint-plugin-smarthr/tree/main/rules/trim-props)
|
package/package.json
CHANGED
|
@@ -56,6 +56,13 @@ const DOMAIN_RULE_ARGS = {
|
|
|
56
56
|
'error', // 'warn', 'off'
|
|
57
57
|
{
|
|
58
58
|
...DOMAIN_RULE_ARGS,
|
|
59
|
+
// ignores: ['\\/test\\/'], // 除外したいファイルの正規表現
|
|
60
|
+
// allowedImports: {
|
|
61
|
+
// '/any/path/': { // 正規表現でチェックするファイルを指定
|
|
62
|
+
// // import制御するファイル (相対パスを指定する場合、.eslintrc.js を基準とする)
|
|
63
|
+
// '@/hoge/fuga': true // ['abc', 'def'] と指定すると個別に指定
|
|
64
|
+
// }
|
|
65
|
+
// },
|
|
59
66
|
// analyticsMode: 'all', // 'same-domain', 'another-domain'
|
|
60
67
|
}
|
|
61
68
|
]
|
|
@@ -8,6 +8,25 @@ const SCHEMA = [
|
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
10
10
|
...BASE_SCHEMA_PROPERTIES,
|
|
11
|
+
allowedImports: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
patternProperties: {
|
|
14
|
+
'.+': {
|
|
15
|
+
type: 'object',
|
|
16
|
+
patternProperties: {
|
|
17
|
+
'.+': {
|
|
18
|
+
type: ['boolean', 'array' ],
|
|
19
|
+
items: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
},
|
|
22
|
+
additionalProperties: false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
additionalProperties: true,
|
|
28
|
+
},
|
|
29
|
+
ignores: { type: 'array', items: { type: 'string' }, default: [] },
|
|
11
30
|
analyticsMode: { type: 'string', default: 'none' }, // 'none' | 'all' | 'same-domain' | 'another-domain'
|
|
12
31
|
},
|
|
13
32
|
additionalProperties: false,
|
|
@@ -27,7 +46,7 @@ module.exports = {
|
|
|
27
46
|
const calcContext = calculateDomainContext(context)
|
|
28
47
|
|
|
29
48
|
// 対象外ファイル
|
|
30
|
-
if (!calcContext.isTarget) {
|
|
49
|
+
if (!calcContext.isTarget || calcContext.option.ignores && calcContext.option.ignores.some((i) => !!calcContext.filename.match(new RegExp(i)))) {
|
|
31
50
|
return {}
|
|
32
51
|
}
|
|
33
52
|
|
|
@@ -36,8 +55,49 @@ module.exports = {
|
|
|
36
55
|
humanizeParentDir,
|
|
37
56
|
} = calcContext
|
|
38
57
|
|
|
58
|
+
const targetPathRegexs = Object.keys(option?.allowedImports || {})
|
|
59
|
+
const targetAllowedImports = targetPathRegexs.filter((regex) => !!calcContext.filename.match(new RegExp(regex)))
|
|
60
|
+
|
|
39
61
|
return {
|
|
40
62
|
ImportDeclaration: (node) => {
|
|
63
|
+
let isDenyPath = false
|
|
64
|
+
let deniedModules = []
|
|
65
|
+
|
|
66
|
+
targetAllowedImports.forEach((allowedKey) => {
|
|
67
|
+
const allowedOption = option.allowedImports[allowedKey]
|
|
68
|
+
const targetModules = Object.keys(allowedOption)
|
|
69
|
+
|
|
70
|
+
targetModules.forEach((targetModule) => {
|
|
71
|
+
const allowedModules = allowedOption[targetModule] || true
|
|
72
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`)
|
|
73
|
+
let sourceValue = node.source.value
|
|
74
|
+
|
|
75
|
+
if (actualTarget[0] === '/') {
|
|
76
|
+
sourceValue = path.resolve(`${calcContext.parentDir}/${sourceValue}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (actualTarget !== sourceValue) {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if (!Array.isArray(allowedModules)) {
|
|
85
|
+
isDenyPath = true
|
|
86
|
+
deniedModules.push(true)
|
|
87
|
+
} else {
|
|
88
|
+
deniedModules.push(node.specifiers.map((s) => s.imported?.name).filter(i => allowedModules.indexOf(i) == -1))
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
if (isDenyPath && deniedModules[0] === true) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
41
101
|
const { importPath, dirs, paths, humanizeImportPath, isGlobalModuleImport, isModuleImport, isDomainImport } = calculateDomainNode(calcContext, node)
|
|
42
102
|
const hit = !isGlobalModuleImport && !isModuleImport && !isDomainImport
|
|
43
103
|
|
|
@@ -58,14 +118,16 @@ module.exports = {
|
|
|
58
118
|
}
|
|
59
119
|
|
|
60
120
|
if (hit) {
|
|
121
|
+
deniedModules = [...new Set(deniedModules.flat())]
|
|
122
|
+
|
|
61
123
|
context.report({
|
|
62
124
|
node,
|
|
63
125
|
messageId: 'no-import-other-domain',
|
|
64
126
|
data: {
|
|
65
|
-
message: `別ドメインから ${importPath} がimportされています。`,
|
|
127
|
+
message: `別ドメインから ${importPath}${deniedModules.length ? ` の ${deniedModules.join(', ')}` : ''} がimportされています。`,
|
|
66
128
|
},
|
|
67
129
|
})
|
|
68
|
-
}
|
|
130
|
+
}
|
|
69
131
|
},
|
|
70
132
|
}
|
|
71
133
|
},
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# smarthr/prohibit-path-within-template-literal
|
|
2
|
+
|
|
3
|
+
- URIを管理するオブジェクト(path, localPath, GlobalPath, PATH, etc...)をtemplate-literalで囲むことを禁止するルールです
|
|
4
|
+
- query-stringの生成やパスの一部などをtemplate-literalで結合することは責務を拡散させることになります
|
|
5
|
+
- それらの責務を指定したオブジェクトに集中させたい場合などに利用出来ます
|
|
6
|
+
- 例
|
|
7
|
+
- NG: `\`${path.xxx}?${queryString}\``
|
|
8
|
+
- pathオブジェクト外でqueryStringが生成されてしまっており、どのようなqueryStringが設定される可能性があるか?という情報が拡散してしまう
|
|
9
|
+
- OK: `path.xxx({ xxxx: 'yyyyy' })`
|
|
10
|
+
- path内でqueryStringを生成するため、URL生成の情報が集約される
|
|
11
|
+
|
|
12
|
+
## rules
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
{
|
|
16
|
+
rules: {
|
|
17
|
+
'smarthr/prohibit-path-within-template-literal': [
|
|
18
|
+
'error', // 'warn', 'off'
|
|
19
|
+
// {
|
|
20
|
+
// pathRegex: '((p|P)ath|PATH)$', // URIを管理するオブジェクトの名称を判定する正規表現
|
|
21
|
+
// },
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## ❌ Incorrect
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
\`${path.any.hoge}\?${queryString}`
|
|
31
|
+
```
|
|
32
|
+
```jsx
|
|
33
|
+
\`${path.any.hoge(ANY)}\${HOGE}`
|
|
34
|
+
```
|
|
35
|
+
```jsx
|
|
36
|
+
\`${path.any.fuga}\`
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## ✅ Correct
|
|
40
|
+
```jsx
|
|
41
|
+
path.any.hoge(queryString)
|
|
42
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const recursiveFetchName = (obj, chained = '') => {
|
|
2
|
+
const o = obj.callee || obj
|
|
3
|
+
const name = o.name || o.property.name || ''
|
|
4
|
+
const nextChained = chained ? `${name}.${chained}` : name
|
|
5
|
+
|
|
6
|
+
if (o.property && o.object) {
|
|
7
|
+
return recursiveFetchName(o.object, nextChained)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return [name, nextChained]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const recursiveFetchRootNameIsPath = (obj, regex) => {
|
|
14
|
+
const [name, chained] = recursiveFetchName(obj, '')
|
|
15
|
+
|
|
16
|
+
return name.match(regex) ? chained : null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SCHEMA = [
|
|
20
|
+
{
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
pathRegex: { type: 'string', default: '((p|P)ath|PATH)$' },
|
|
24
|
+
},
|
|
25
|
+
additionalProperties: false,
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
meta: {
|
|
31
|
+
type: 'suggestion',
|
|
32
|
+
messages: {
|
|
33
|
+
'prohibit-path-within-template-literal': '{{ message }}',
|
|
34
|
+
},
|
|
35
|
+
schema: SCHEMA,
|
|
36
|
+
},
|
|
37
|
+
create(context) {
|
|
38
|
+
const option = context.options[0]
|
|
39
|
+
const nameRegex = new RegExp(option?.pathRegex || SCHEMA[0].properties.pathRegex.default)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
TemplateLiteral: (node) => {
|
|
43
|
+
node.expressions.forEach((exp) => {
|
|
44
|
+
const name = recursiveFetchRootNameIsPath(exp, nameRegex)
|
|
45
|
+
|
|
46
|
+
if (name) {
|
|
47
|
+
context.report({
|
|
48
|
+
node: exp,
|
|
49
|
+
messageId: 'prohibit-path-within-template-literal',
|
|
50
|
+
data: {
|
|
51
|
+
message: `${name}は \`\` で囲まないでください。queryStringを結合するなどのURL生成は ${name} 内で行います。 (例: ${name}({ query: { hoge: 'abc' } })`,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
module.exports.schema = SCHEMA
|
|
@@ -10,7 +10,16 @@
|
|
|
10
10
|
```js
|
|
11
11
|
{
|
|
12
12
|
rules: {
|
|
13
|
-
'smarthr/require-barrel-import':
|
|
13
|
+
'smarthr/require-barrel-import': [
|
|
14
|
+
'error',
|
|
15
|
+
// ignores: ['\\/test\\/'], // 除外したいファイルの正規表現
|
|
16
|
+
// allowedImports: {
|
|
17
|
+
// '/any/path/': { // 正規表現でチェックするファイルを指定
|
|
18
|
+
// // import制御するファイル (相対パスを指定する場合、.eslintrc.js を基準とする)
|
|
19
|
+
// '@/hoge/fuga': true // ['abc', 'def'] と指定すると個別に指定
|
|
20
|
+
// }
|
|
21
|
+
// },
|
|
22
|
+
],
|
|
14
23
|
},
|
|
15
24
|
}
|
|
16
25
|
```
|
|
@@ -44,6 +44,33 @@ const calculateReplacedImportPath = (source) => {
|
|
|
44
44
|
}, source)
|
|
45
45
|
}
|
|
46
46
|
const TARGET_EXTS = ['ts', 'tsx', 'js', 'jsx']
|
|
47
|
+
const SCHEMA = [
|
|
48
|
+
{
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
allowedImports: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
patternProperties: {
|
|
54
|
+
'.+': {
|
|
55
|
+
type: 'object',
|
|
56
|
+
patternProperties: {
|
|
57
|
+
'.+': {
|
|
58
|
+
type: ['boolean', 'array' ],
|
|
59
|
+
items: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
additionalProperties: true,
|
|
68
|
+
},
|
|
69
|
+
ignores: { type: 'array', items: { type: 'string' }, default: [] },
|
|
70
|
+
},
|
|
71
|
+
additionalProperties: false,
|
|
72
|
+
}
|
|
73
|
+
]
|
|
47
74
|
|
|
48
75
|
module.exports = {
|
|
49
76
|
meta: {
|
|
@@ -51,20 +78,65 @@ module.exports = {
|
|
|
51
78
|
messages: {
|
|
52
79
|
'require-barrel-import': '{{ message }}',
|
|
53
80
|
},
|
|
54
|
-
schema:
|
|
81
|
+
schema: SCHEMA,
|
|
55
82
|
},
|
|
56
83
|
create(context) {
|
|
84
|
+
const option = context.options[0] || {}
|
|
57
85
|
const filename = context.getFilename()
|
|
58
86
|
|
|
87
|
+
if ((option.ignores || []).some((i) => !!filename.match(new RegExp(i)))) {
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
const dir = (() => {
|
|
60
92
|
const d = filename.split('/')
|
|
61
93
|
d.pop()
|
|
62
94
|
|
|
63
95
|
return d.join('/')
|
|
64
96
|
})()
|
|
97
|
+
const targetPathRegexs = Object.keys(option?.allowedImports || {})
|
|
98
|
+
const targetAllowedImports = targetPathRegexs.filter((regex) => !!filename.match(new RegExp(regex)))
|
|
65
99
|
|
|
66
100
|
return {
|
|
67
101
|
ImportDeclaration: (node) => {
|
|
102
|
+
let isDenyPath = false
|
|
103
|
+
let deniedModules = []
|
|
104
|
+
|
|
105
|
+
targetAllowedImports.forEach((allowedKey) => {
|
|
106
|
+
const allowedOption = option.allowedImports[allowedKey]
|
|
107
|
+
const targetModules = Object.keys(allowedOption)
|
|
108
|
+
|
|
109
|
+
targetModules.forEach((targetModule) => {
|
|
110
|
+
const allowedModules = allowedOption[targetModule] || true
|
|
111
|
+
const actualTarget = targetModule[0] !== '.' ? targetModule : path.resolve(`${process.cwd()}/${targetModule}`)
|
|
112
|
+
let sourceValue = node.source.value
|
|
113
|
+
|
|
114
|
+
if (actualTarget[0] === '/') {
|
|
115
|
+
sourceValue = path.resolve(`${dir}/${sourceValue}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (actualTarget !== sourceValue) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if (!Array.isArray(allowedModules)) {
|
|
124
|
+
isDenyPath = true
|
|
125
|
+
deniedModules.push(true)
|
|
126
|
+
} else {
|
|
127
|
+
deniedModules.push(node.specifiers.map((s) => s.imported?.name).filter(i => allowedModules.indexOf(i) == -1))
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (isDenyPath && deniedModules[0] === true) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!isDenyPath && deniedModules.length === 1 && deniedModules[0].length === 0) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
68
140
|
let sourceValue = node.source.value
|
|
69
141
|
|
|
70
142
|
if (sourceValue[0] === '.') {
|
|
@@ -106,12 +178,14 @@ module.exports = {
|
|
|
106
178
|
|
|
107
179
|
if (barrel && !barrel.match(new RegExp(`^${rootPath}/index\.`))) {
|
|
108
180
|
barrel = calculateReplacedImportPath(barrel)
|
|
181
|
+
const noExt = barrel.replace(/\/index\.(ts|js)x?$/, '')
|
|
182
|
+
deniedModules = [...new Set(deniedModules.flat())]
|
|
109
183
|
|
|
110
184
|
context.report({
|
|
111
185
|
node,
|
|
112
186
|
messageId: 'require-barrel-import',
|
|
113
187
|
data: {
|
|
114
|
-
message: `${
|
|
188
|
+
message: deniedModules.length ? `${deniedModules.join(', ')} は ${noExt} からimportしてください` : `${noExt} からimportするか、${barrel} のbarrelファイルを削除して直接import可能にしてください`,
|
|
115
189
|
},
|
|
116
190
|
});
|
|
117
191
|
}
|
|
@@ -119,4 +193,4 @@ module.exports = {
|
|
|
119
193
|
}
|
|
120
194
|
},
|
|
121
195
|
}
|
|
122
|
-
module.exports.schema =
|
|
196
|
+
module.exports.schema = SCHEMA
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# smarthr/trim-props
|
|
2
|
+
|
|
3
|
+
- このルールはCLIオプションの`--fix`で自動的に修正可能です
|
|
4
|
+
- 文字列型のpropsについて、先頭末尾に空白文字を含む文字列の設定を禁止させたい場合に利用します
|
|
5
|
+
|
|
6
|
+
## rules
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
{
|
|
10
|
+
rules: {
|
|
11
|
+
'smarthr/trim-props': 'error', // 'warn', 'off',
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## ❌ Incorrect
|
|
17
|
+
|
|
18
|
+
```jsx
|
|
19
|
+
<a href=" https://www.google.com">google</a>
|
|
20
|
+
<img src={"/sample.jpg "} alt={" sample "} />
|
|
21
|
+
<div data-spec=" info-area ">....</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## ✅ Correct
|
|
25
|
+
|
|
26
|
+
```jsx
|
|
27
|
+
<a href="https://www.google.com">google</a>
|
|
28
|
+
<img src={"/sample.jpg"} alt={"sample"} />
|
|
29
|
+
<div data-spec="info-area">....</div>
|
|
30
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'suggestion',
|
|
4
|
+
messages: {
|
|
5
|
+
'trim-props': '{{ message }}',
|
|
6
|
+
},
|
|
7
|
+
schema: [],
|
|
8
|
+
fixable: 'whitespace',
|
|
9
|
+
},
|
|
10
|
+
create(context) {
|
|
11
|
+
return {
|
|
12
|
+
JSXOpeningElement: (node) =>
|
|
13
|
+
node.attributes.reduce((prev, current) => {
|
|
14
|
+
const attribute = current.value?.type === 'JSXExpressionContainer' ? current.value.expression : current.value
|
|
15
|
+
const props = attribute?.value
|
|
16
|
+
|
|
17
|
+
if (typeof props !== 'string') return prev
|
|
18
|
+
|
|
19
|
+
if (props.match(/(^\s+|\s+$)/)) {
|
|
20
|
+
return context.report({
|
|
21
|
+
node,
|
|
22
|
+
loc: current.loc,
|
|
23
|
+
messageId: 'trim-props',
|
|
24
|
+
data: {
|
|
25
|
+
message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください',
|
|
26
|
+
},
|
|
27
|
+
fix(fixer) {
|
|
28
|
+
return fixer.replaceTextRange([attribute.range[0] + 1, attribute.range[1] - 1], props.trim())
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
return prev
|
|
33
|
+
}, []),
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
module.exports.schema = []
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const rule = require('../rules/prohibit-path-within-template-literal')
|
|
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-path-within-template-literal', rule, {
|
|
12
|
+
valid: [
|
|
13
|
+
{ code: 'path.hoge', },
|
|
14
|
+
{ code: 'path.fuga()', },
|
|
15
|
+
{ code: 'localPath.any.aaa("hoge")', },
|
|
16
|
+
{ code: 'PATH.some({ x })', },
|
|
17
|
+
{ code: 'hoge.some({ x })', options: [{ pathRegex: '^hoge$' }] },
|
|
18
|
+
],
|
|
19
|
+
invalid: [
|
|
20
|
+
{
|
|
21
|
+
code: '`${path.hoge}`',
|
|
22
|
+
errors: [{ message: 'path.hogeは `` で囲まないでください。queryStringを結合するなどのURL生成は path.hoge 内で行います。 (例: path.hoge({ query: { hoge: \'abc\' } })' }]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: '`${ABC.hoge()}${hogehoge}`',
|
|
26
|
+
options: [{ pathRegex: '^ABC$' }],
|
|
27
|
+
errors: [{ message: 'ABC.hogeは `` で囲まないでください。queryStringを結合するなどのURL生成は ABC.hoge 内で行います。 (例: ABC.hoge({ query: { hoge: \'abc\' } })' }]
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const rule = require('../rules/trim-props')
|
|
2
|
+
const RuleTester = require('eslint').RuleTester
|
|
3
|
+
|
|
4
|
+
const ruleTester = new RuleTester({
|
|
5
|
+
parserOptions: {
|
|
6
|
+
ecmaFeatures: {
|
|
7
|
+
jsx: true,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
ruleTester.run('trim-props', rule, {
|
|
13
|
+
valid: [
|
|
14
|
+
{ code: '<a href="https://www.google.com">google</a>' },
|
|
15
|
+
{ code: '<a href={"https://www.google.com"}>google</a>' },
|
|
16
|
+
{ code: '<img src="/sample.jpg" alt="sample" />' },
|
|
17
|
+
{ code: '<img src={"/sample.jpg"} alt={"sample"} />' },
|
|
18
|
+
{ code: '<div data-spec="info-area">....</div>' },
|
|
19
|
+
],
|
|
20
|
+
invalid: [
|
|
21
|
+
{
|
|
22
|
+
code: '<a href=" https://www.google.com">google</a>',
|
|
23
|
+
output: '<a href="https://www.google.com">google</a>',
|
|
24
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: '<a href="https://www.google.com ">google</a>',
|
|
28
|
+
output: '<a href="https://www.google.com">google</a>',
|
|
29
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: '<a href=" https://www.google.com ">google</a>',
|
|
33
|
+
output: '<a href="https://www.google.com">google</a>',
|
|
34
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: '<a href={" https://www.google.com"}>google</a>',
|
|
38
|
+
output: '<a href={"https://www.google.com"}>google</a>',
|
|
39
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
code: '<a href={"https://www.google.com "}>google</a>',
|
|
43
|
+
output: '<a href={"https://www.google.com"}>google</a>',
|
|
44
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
code: '<a href={" https://www.google.com "}>google</a>',
|
|
48
|
+
output: '<a href={"https://www.google.com"}>google</a>',
|
|
49
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
code: '<img src=" /sample.jpg" alt="sample " />',
|
|
53
|
+
output: '<img src="/sample.jpg" alt="sample" />',
|
|
54
|
+
errors: [
|
|
55
|
+
{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' },
|
|
56
|
+
{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
code: '<img src={" /sample.jpg"} alt={"sample "} />',
|
|
61
|
+
output: '<img src={"/sample.jpg"} alt={"sample"} />',
|
|
62
|
+
errors: [
|
|
63
|
+
{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' },
|
|
64
|
+
{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: '<div data-spec=" info-area ">....</div>',
|
|
69
|
+
output: '<div data-spec="info-area">....</div>',
|
|
70
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
code: '<div data-spec={" info-area "}>....</div>',
|
|
74
|
+
output: '<div data-spec={"info-area"}>....</div>',
|
|
75
|
+
errors: [{ message: '属性に設定している文字列から先頭、末尾の空白文字を削除してください' }],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
})
|