eslint-plugin-smarthr 0.2.14 → 0.2.16

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 CHANGED
@@ -2,6 +2,32 @@
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.16](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.15...v0.2.16) (2022-12-21)
6
+
7
+
8
+ ### Features
9
+
10
+ * ラジオボタンにnameを強制するルールを追加 ([97792cf](https://github.com/kufu/eslint-plugin-smarthr/commit/97792cf276e3400cb6e01a01ef4cf104de5db190))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * no-import-other-domain、require-barrel-import でignoresオプションを指定するとエラーが発生する問題を修正する ([#49](https://github.com/kufu/eslint-plugin-smarthr/issues/49)) ([8526236](https://github.com/kufu/eslint-plugin-smarthr/commit/85262365d105c1afa4fd53662825c4c51cb84531))
16
+ * prohibit-path-within-template-literalのpathオブジェクト名の解析の際、エラーになるパターンがあったため修正する ([#48](https://github.com/kufu/eslint-plugin-smarthr/issues/48)) ([715e485](https://github.com/kufu/eslint-plugin-smarthr/commit/715e485f3679e4ca2eb9675b67b8f452f9707096))
17
+
18
+ ### [0.2.15](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.12...v0.2.15) (2022-12-15)
19
+
20
+
21
+ ### Features
22
+
23
+ * 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))
24
+ * 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))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * redundant-nameのarrowedNameなどで利用するファイルパスが削られすぎていたため、指定が行いにくい問題を修正する ([#43](https://github.com/kufu/eslint-plugin-smarthr/issues/43)) ([6de9618](https://github.com/kufu/eslint-plugin-smarthr/commit/6de961831a9f9e0e93eeeebb80e56ecb60d9a2ff))
30
+
5
31
  ### [0.2.14](https://github.com/kufu/eslint-plugin-smarthr/compare/v0.2.13...v0.2.14) (2022-12-13)
6
32
 
7
33
  ### Features
package/README.md CHANGED
@@ -12,6 +12,7 @@
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)
@@ -13,8 +13,9 @@ const calculateDomainContext = (context) => {
13
13
  throw new Error('tsconfig.json の compilerOptions.paths に `"@/*": ["any_path/*"]` 形式でフロントエンドのroot dir を指定してください')
14
14
  }
15
15
 
16
+ const filename = context.getFilename()
16
17
  const parentDir = (() => {
17
- const dir = context.getFilename().split('/')
18
+ const dir = filename.split('/')
18
19
  dir.pop()
19
20
  return dir.join('/')
20
21
  })()
@@ -23,6 +24,7 @@ const calculateDomainContext = (context) => {
23
24
  return {
24
25
  option: context.options[0],
25
26
  parentDir,
27
+ filename,
26
28
  humanizeParentDir,
27
29
  isTarget: humanizeParentDir !== parentDir,
28
30
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -0,0 +1,58 @@
1
+ # smarthr/a11y-radio-has-bane-attribute
2
+
3
+ - ラジオボタンに name 属性を設定することを強制するルールです。
4
+ - name を適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。
5
+
6
+ ## rules
7
+
8
+ ```js
9
+ {
10
+ rules: {
11
+ 'smarthr/a11y-radio-has-name-attribute': 'error', // 'warn', 'off'
12
+ },
13
+ }
14
+ ```
15
+
16
+ ## ❌ Incorrect
17
+
18
+ ```jsx
19
+ <RadioButton />
20
+ ```
21
+
22
+ ```jsx
23
+ <Input type="radio" />
24
+ ```
25
+
26
+ ```jsx
27
+ <input type="radio" />
28
+ ```
29
+
30
+ ```jsx
31
+ import styled from 'styled-components';
32
+
33
+ const StyledHoge = styled.input``;
34
+ const StyledFuga = styled(Input)``;
35
+ const StyledPiyo = styled(RadioButton)``;
36
+ ```
37
+
38
+ ## ✅ Correct
39
+
40
+ ```jsx
41
+ <RadioButton name="hoge" />
42
+ ```
43
+
44
+ ```jsx
45
+ <Input type="radio" name="hoge" />
46
+ ```
47
+
48
+ ```jsx
49
+ <input type="radio" name="hoge" />
50
+ ```
51
+
52
+ ```jsx
53
+ import styled from 'styled-components';
54
+
55
+ const StyledInput = styled.input``;
56
+ const StyledInput = styled(Input)``;
57
+ const StyledRadioButton = styled(RadioButton)``;
58
+ ```
@@ -0,0 +1,49 @@
1
+ const { generateTagFormatter } = require('../../libs/format_styled_components');
2
+
3
+ const EXPECTED_NAMES = {
4
+ RadioButton$: 'RadioButton$',
5
+ '(i|I)nput$': 'Input$',
6
+ };
7
+
8
+ module.exports = {
9
+ meta: {
10
+ type: 'problem',
11
+ messages: {
12
+ 'format-styled-components': '{{ message }}',
13
+ 'a11y-radio-has-name-attribute': '{{ message }}',
14
+ },
15
+ schema: [],
16
+ },
17
+ create(context) {
18
+ return {
19
+ ...generateTagFormatter({ context, EXPECTED_NAMES }),
20
+ JSXOpeningElement: (node) => {
21
+ const name = node.name.name || '';
22
+ const isRadio =
23
+ (name.match(/(i|I)nput$/) &&
24
+ node.attributes.some(
25
+ (a) => a.name?.name === 'type' && a.value.value === 'radio'
26
+ )) ||
27
+ name.match(/RadioButton$/);
28
+
29
+ if (!isRadio) return;
30
+
31
+ const hasName = node.attributes.some(
32
+ (attribute) => attribute?.name?.name === 'name'
33
+ );
34
+
35
+ if (!hasName) {
36
+ context.report({
37
+ node,
38
+ messageId: 'a11y-radio-has-name-attribute',
39
+ data: {
40
+ message:
41
+ 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。',
42
+ },
43
+ });
44
+ }
45
+ },
46
+ };
47
+ },
48
+ };
49
+ module.exports.schema = [];
@@ -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': 'error',
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: `${barrel.replace(/\/index\.(ts|js)x?$/, '')} からimportするか、${barrel} を削除してください`,
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,36 @@
1
+ const rule = require('../rules/a11y-radio-has-name-attribute');
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
+ ruleTester.run('a11y-radio-has-name-attribute', rule, {
16
+ valid: [
17
+ { code: `import styled from 'styled-components'` },
18
+ { code: `import styled, { css } from 'styled-components'` },
19
+ { code: `import { css } from 'styled-components'` },
20
+ { code: 'const HogeInput = styled.input``' },
21
+ { code: 'const HogeInput = styled(Input)``' },
22
+ { code: 'const HogeRadioButton = styled(RadioButton)``' },
23
+ { code: '<input type="radio" name="hoge" />' },
24
+ { code: '<HogeInput type="radio" name="hoge" />' },
25
+ { code: '<HogeRadioButton name="hoge" />' },
26
+ ],
27
+ invalid: [
28
+ { code: `import hoge from 'styled-components'`, errors: [ { message: "styled-components をimportする際は、名称が`styled` となるようにしてください。例: `import styled from 'styled-components'`" } ] },
29
+ { code: 'const Hoge = styled.input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
30
+ { code: 'const Hoge = styled.Input``', errors: [ { message: `Hogeを正規表現 "/Input$/" がmatchする名称に変更してください` } ] },
31
+ { code: 'const Hoge = styled(RadioButton)``', errors: [ { message: `Hogeを正規表現 "/RadioButton$/" がmatchする名称に変更してください` } ] },
32
+ { code: '<input type="radio" />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
33
+ { code: '<HogeInput type="radio" />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
34
+ { code: '<HogeRadioButton />', errors: [ { message: 'ラジオボタンにはname属性を指定してください。nameを適切に設定することでラジオグループが確立され、キーボード操作しやすくなる等のメリットがあります。' } ] },
35
+ ],
36
+ });
@@ -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
+ })