eslint-plugin-smarthr 1.7.0 → 1.8.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/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
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
+ ## [1.8.1](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.8.0...eslint-plugin-smarthr-v1.8.1) (2025-06-12)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * a11y-anchor-has-href-attribute での特殊分岐条件を react-router-dom から react-router に変更 ([#678](https://github.com/kufu/tamatebako/issues/678)) ([5941384](https://github.com/kufu/tamatebako/commit/59413842b5b7e5ca6ee88a511792ae6a81be988b))
11
+ * a11y-delegate-element-has-role-presentationでインタラクティブな要素を判定する際、コンポーネント名が複数形でも許容する ([#679](https://github.com/kufu/tamatebako/issues/679)) ([67567bd](https://github.com/kufu/tamatebako/commit/67567bde757624ed46649fb0401e24dc904865ba))
12
+ * a11y-heading-in-sectioning-contentでNavにaria-labelが設定されている場合を許容する ([#680](https://github.com/kufu/tamatebako/issues/680)) ([12f26be](https://github.com/kufu/tamatebako/commit/12f26be9d461d1580711263ae974ed948c74fc1d))
13
+
14
+ ## [1.8.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.7.0...eslint-plugin-smarthr-v1.8.0) (2025-06-09)
15
+
16
+
17
+ ### Features
18
+
19
+ * best-practice-for-layoutsで <any>{hoge}</any> など子要素が変数のみの場合を許容する ([#668](https://github.com/kufu/tamatebako/issues/668)) ([7d03ff1](https://github.com/kufu/tamatebako/commit/7d03ff150833f673b9c2afe8e86adfdaee150d72))
20
+ * format-translate-component rule で RangeSeparator を許容する ([#667](https://github.com/kufu/tamatebako/issues/667)) ([951bee2](https://github.com/kufu/tamatebako/commit/951bee2f376ddf48c5e9a0b13eb50b2daeeec7f9))
21
+
5
22
  ## [1.7.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v1.6.0...eslint-plugin-smarthr-v1.7.0) (2025-06-02)
6
23
 
7
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-smarthr",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "author": "SmartHR",
5
5
  "license": "MIT",
6
6
  "description": "A sharable ESLint plugin for SmartHR",
@@ -26,7 +26,7 @@
26
26
  "json5": "^2.2.3"
27
27
  },
28
28
  "devDependencies": {
29
- "typescript-eslint": "^8.33.0"
29
+ "typescript-eslint": "^8.33.1"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "eslint": "^9"
@@ -37,5 +37,5 @@
37
37
  "eslintplugin",
38
38
  "smarthr"
39
39
  ],
40
- "gitHead": "12babfba8f3ff1aa9bc9d159136ef15ffce24fe0"
40
+ "gitHead": "18fc771c7e65e784d224990f2ce857c150cd69e2"
41
41
  }
@@ -31,7 +31,7 @@ const OPTION = (() => {
31
31
  }
32
32
 
33
33
  break
34
- case 'react-router-dom':
34
+ case 'react-router':
35
35
  react_router = true
36
36
 
37
37
  if (nextjs) {
@@ -1,27 +1,27 @@
1
1
  const INTERACTIVE_COMPONENT_NAMES = `(${[
2
- '(B|b)utton',
3
- '(Check|Combo)(B|b)ox',
4
- '(Date(timeLocal)?|Time|Month|Wareki)Picker',
5
- '(I|i)nput(File)?',
6
- '(S|s)elect',
7
- '(T|t)extarea',
8
- '(ActionDialogWith|RemoteDialog)Trigger',
9
- 'AccordionPanel',
2
+ '(B|b)utton(s)?',
3
+ '(Check|Combo)(B|b)ox(es|s)?',
4
+ '(Date(timeLocal)?|Time|Month|Wareki)Picker(s)?',
5
+ '(I|i)nput(File)?(s)?',
6
+ '(S|s)elect(s)?',
7
+ '(T|t)extarea(s)?',
8
+ '(ActionDialogWith|RemoteDialog)Trigger(s)?',
9
+ 'AccordionPanel(s)?',
10
10
  '^a',
11
11
  'Anchor',
12
- 'Link',
13
- 'DropZone',
14
- 'Field(S|s)et',
15
- 'FilterDropdown',
16
- '(F|f)orm(Control|Group|Dialog)?',
17
- 'Pagination',
18
- 'RadioButton(Panel)?',
19
- 'RemoteTrigger(.+)Dialog',
20
- 'RightFixedNote',
21
- 'SegmentedControl',
22
- 'SideNav',
23
- 'Switch',
24
- 'TabItem',
12
+ 'Link(s)?',
13
+ 'DropZone(s)?',
14
+ 'Field(S|s)et(s)?',
15
+ 'FilterDropdown(s)?',
16
+ '(F|f)orm(Control|Group|Dialog)?(s)?',
17
+ 'Pagination(s)?',
18
+ 'RadioButton(Panel)?(s)?',
19
+ 'RemoteTrigger(.+)Dialog(s)?',
20
+ 'RightFixedNote(s)?',
21
+ 'SegmentedControl(s)?',
22
+ 'SideNav(s)?',
23
+ 'Switch(s)?',
24
+ 'TabItem(s)?',
25
25
  ].join('|')})$`
26
26
  const INTERACTIVE_ON_REGEX = /^on(Change|Input|Focus|Blur|(Double)?Click|Key(Down|Up|Press)|Mouse(Enter|Over|Down|Up|Leave)|Select|Submit)$/
27
27
  const MEANED_ROLE_REGEX = /^(combobox|group|slider|toolbar)$/
@@ -10,9 +10,9 @@ const ignoreCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
10
10
  const noHeadingTagNamesRegex = /^(span|legend)$/
11
11
  const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
12
12
  const headingAttributeRegex = /^(heading|title)$/
13
+ const ariaLabelRegex = /^aria-label(ledby)?$/
13
14
 
14
15
  const includeSectioningAsAttr = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
15
- const findHeadingAttribute = (a) => headingAttributeRegex.test(a.name?.name || '')
16
16
 
17
17
  const headingMessage = `smarthr-ui/Headingと紐づく内容の範囲(アウトライン)が曖昧になっています。
18
18
  - smarthr-uiのArticle, Aside, Nav, SectionのいずれかでHeadingコンポーネントと内容をラップしてHeadingに対応する範囲を明確に指定してください。`
@@ -250,23 +250,41 @@ module.exports = {
250
250
  }
251
251
  }
252
252
  } else if (!node.selfClosing) {
253
- const isSection = sectioningRegex.test(elementName)
254
-
255
- // HINT: SectioningContent系コンポーネントの拡張の場合、title, heading属性などにHeadingのテキストが仕込まれている場合がある
256
- // 対象属性を持っている場合はcorrectとして扱う
257
- if (isSection && node.attributes.some(findHeadingAttribute)) {
258
- return
253
+ const isSection = elementName.match(sectioningRegex)
254
+ let isNav = false
255
+
256
+ if (isSection) {
257
+ isNav = isSection[1] === 'Nav'
258
+
259
+ for (let i = 0; i < node.attributes.length; i++) {
260
+ const attrName = node.attributes[i].name?.name || ''
261
+
262
+ if (
263
+ // HINT: SectioningContent系コンポーネントの拡張の場合、title, heading属性などにHeadingのテキストが仕込まれている場合がある
264
+ // 対象属性を持っている場合はcorrectとして扱う
265
+ headingAttributeRegex.test(attrName) ||
266
+ // HINT: Navかつaria-labelを持っている場合も許容する
267
+ isNav && ariaLabelRegex.test(attrName)
268
+ ) {
269
+ return
270
+ }
271
+ }
259
272
  }
260
273
 
261
274
  const layoutSectionAsAttr = !isSection && layoutComponentRegex.test(elementName) ? node.attributes.find(includeSectioningAsAttr) : null
262
275
 
263
- if ((isSection || layoutSectionAsAttr) && !searchBubbleUpSections(node.parent.parent) && !forInSearchChildren(node.parent.children)) {
276
+ if (
277
+ (isSection || layoutSectionAsAttr) &&
278
+ !searchBubbleUpSections(node.parent.parent) &&
279
+ !forInSearchChildren(node.parent.children)
280
+ ) {
264
281
  context.report({
265
282
  node,
266
283
  message: `${isSection ? elementName : `<${elementName} ${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}">`} はHeading要素を含んでいません。
267
284
  - SectioningContentはHeadingを含むようにマークアップする必要があります
268
285
  - ${elementName}に設定しているいずれかの属性がHeading,もしくはHeadingのテキストに該当する場合、その属性の名称を ${headingAttributeRegex.toString()} にマッチする名称に変更してください
269
- - Headingにするべき適切な文字列が存在しない場合、 ${isSection ? `${elementName} は削除するか、SectioningContentではない要素に差し替えてください` : `${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}"を削除、もしくは別の要素に変更してください`}`,
286
+ - Headingにするべき適切な文字列が存在しない場合、 ${isSection ? `${elementName} は削除するか、SectioningContentではない要素に差し替えてください` : `${layoutSectionAsAttr.name.name}="${layoutSectionAsAttr.value.value}"を削除、もしくは別の要素に変更してください`}${isNav ? `
287
+ - nav要素の場合、aria-label、もしくはaria-labelledby属性を設定し、どんなナビゲーションなのかがわかる名称を設定してください` : ''}`,
270
288
  })
271
289
  }
272
290
  }
@@ -24,6 +24,11 @@ const searchChildren = (node) => {
24
24
  }
25
25
 
26
26
  switch(node.type) {
27
+ case 'MemberExpression':
28
+ return searchChildren(node.object)
29
+ // {hoge} や {hoge.fuga} の場合、許容する
30
+ case 'Identifier':
31
+ return false
27
32
  case 'JSXExpressionContainer':
28
33
  case 'ChainExpression':
29
34
  return searchChildren(node.expression)
@@ -101,7 +106,6 @@ module.exports = {
101
106
  return
102
107
  }
103
108
 
104
-
105
109
  if (searchChildren(children[0])) {
106
110
  context.report({
107
111
  node,
@@ -14,6 +14,10 @@ const SCHEMA = [
14
14
  ]
15
15
 
16
16
  const NOOP = () => {}
17
+ const findDangerouslySetInnerHTMLAttr = (a) => a.name.name === 'dangerouslySetInnerHTML'
18
+
19
+ const WHITESPACE_REGEX = /(\s|\n)+/g
20
+ const ALLOW_ELM_REGEX = /^(br|RangeSeparator)$/
17
21
 
18
22
  /**
19
23
  * @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
@@ -46,43 +50,39 @@ module.exports = {
46
50
  // HINT: 翻訳コンポーネントはテキストとbrのみ許容する
47
51
  if (node.name.name === componentName) {
48
52
  let existValidChild = false
49
- let existNotBrElement = false
50
53
 
51
- node.parent.children.forEach((c) => {
54
+ for (let i = 0; i < node.parent.children.length; i++) {
55
+ const c = node.parent.children[i]
56
+
52
57
  switch (c.type) {
53
58
  case 'JSXText':
54
59
  // HINT: 空白と改行のみの場合はテキストが存在する扱いにはしない
55
- if (c.value.replace(/(\s|\n)+/g, '')) {
56
- existValidChild = true
60
+ if (existValidChild || !c.value.replace(WHITESPACE_REGEX, '')) {
61
+ break
57
62
  }
58
63
 
59
- break
64
+ existValidChild = true
60
65
  case 'JSXExpressionContainer':
61
66
  // TODO 変数がstringのみか判定できるなら対応したい
62
67
  existValidChild = true
63
68
 
64
69
  break
65
70
  case 'JSXElement':
66
- if (c.openingElement.name.name !== 'br') {
67
- existNotBrElement = true
71
+ if (ALLOW_ELM_REGEX.test(c.openingElement.name.name)) {
72
+ break
68
73
  }
69
74
 
70
- break
71
- }
72
- })
73
-
74
- const message = (() => {
75
- if (existNotBrElement) {
76
- return `${componentName} 内では <br /> 以外のタグは使えません`
77
- } else if (!existValidChild && !node.attributes.some((a) => a.name.name === 'dangerouslySetInnerHTML')) {
78
- return `${componentName} 内には必ずテキストを設置してください`
75
+ return context.report({
76
+ node,
77
+ message: `${componentName} 内では <br />, <RangeSeparator /> 以外のタグは使えません`,
78
+ });
79
79
  }
80
- })()
80
+ }
81
81
 
82
- if (message) {
82
+ if (!existValidChild && !node.attributes.some(findDangerouslySetInnerHTMLAttr)) {
83
83
  context.report({
84
84
  node,
85
- message,
85
+ message: `${componentName} 内には必ずテキストを設置してください`,
86
86
  });
87
87
  }
88
88
  }
@@ -11,7 +11,7 @@ const ruleTester = new RuleTester({
11
11
  },
12
12
  })
13
13
 
14
- const defaultInteractiveMessage = '((B|b)utton|(Check|Combo)(B|b)ox|(Date(timeLocal)?|Time|Month|Wareki)Picker|(I|i)nput(File)?|(S|s)elect|(T|t)extarea|(ActionDialogWith|RemoteDialog)Trigger|AccordionPanel|^a|Anchor|Link|DropZone|Field(S|s)et|FilterDropdown|(F|f)orm(Control|Group|Dialog)?|Pagination|RadioButton(Panel)?|RemoteTrigger(.+)Dialog|RightFixedNote|SegmentedControl|SideNav|Switch|TabItem)$'
14
+ const defaultInteractiveMessage = '((B|b)utton(s)?|(Check|Combo)(B|b)ox(es|s)?|(Date(timeLocal)?|Time|Month|Wareki)Picker(s)?|(I|i)nput(File)?(s)?|(S|s)elect(s)?|(T|t)extarea(s)?|(ActionDialogWith|RemoteDialog)Trigger(s)?|AccordionPanel(s)?|^a|Anchor|Link(s)?|DropZone(s)?|Field(S|s)et(s)?|FilterDropdown(s)?|(F|f)orm(Control|Group|Dialog)?(s)?|Pagination(s)?|RadioButton(Panel)?(s)?|RemoteTrigger(.+)Dialog(s)?|RightFixedNote(s)?|SegmentedControl(s)?|SideNav(s)?|Switch(s)?|TabItem(s)?)$'
15
15
  const defaultInteractiveRegex = `/(${defaultInteractiveMessage})/`
16
16
  const messageNonInteractiveEventHandler = (nodeName, onAttrs, interactiveComponentRegex = defaultInteractiveRegex) => {
17
17
  const onAttrsText = onAttrs.join(', ')
@@ -50,7 +50,7 @@ ruleTester.run('smarthr/a11y-delegate-element-has-role-presentation', rule, {
50
50
  { code: '<Wrapper onClick={any} role="presentation"><Hoge /></Wrapper>', options: [{ additionalInteractiveComponentRegex: ['^Hoge$'] }] },
51
51
  { code: '<Wrapper onClick={any} role="presentation"><any><Link /></any></Wrapper>' },
52
52
  { code: '<Wrapper onClick={any} role="presentation">{any && <AnyButton />}</Wrapper>' },
53
- { code: '<Wrapper onClick={any} role="presentation">{any || <AnyButton />}</Wrapper>' },
53
+ { code: '<Wrapper onClick={any} role="presentation">{any || <AnyButtons />}</Wrapper>' },
54
54
  { code: '<Wrapper onClick={any} role="presentation">{any1 && (any2 || any3) && <AnyButton />}</Wrapper>' },
55
55
  { code: '<Wrapper onClick={any} role="presentation">{any ? <AnyButton /> : null}</Wrapper>' },
56
56
  { code: '<Wrapper onClick={any} role="presentation">{any1 ? (any2 ? <HogeLink /> : null) : null}</Wrapper>' },
@@ -19,10 +19,11 @@ const pageMessage = 'smarthr-ui/PageHeading が同一ファイル内に複数存
19
19
  const pageInSectionMessage = 'smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。'
20
20
  const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
21
21
  - tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
22
- const notHaveHeadingMessage = (elementName) => `${elementName} はHeading要素を含んでいません。
22
+ const notHaveHeadingMessage = (elementName, isNav) => `${elementName} はHeading要素を含んでいません。
23
23
  - SectioningContentはHeadingを含むようにマークアップする必要があります
24
24
  - ${elementName}に設定しているいずれかの属性がHeading,もしくはHeadingのテキストに該当する場合、その属性の名称を /^(heading|title)$/ にマッチする名称に変更してください
25
- - Headingにするべき適切な文字列が存在しない場合、 ${elementName} は削除するか、SectioningContentではない要素に差し替えてください`
25
+ - Headingにするべき適切な文字列が存在しない場合、 ${elementName} は削除するか、SectioningContentではない要素に差し替えてください${isNav ? `
26
+ - nav要素の場合、aria-label、もしくはaria-labelledby属性を設定し、どんなナビゲーションなのかがわかる名称を設定してください` : ''}`
26
27
 
27
28
  ruleTester.run('a11y-heading-in-sectioning-content', rule, {
28
29
  valid: [
@@ -48,6 +49,8 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
48
49
  { code: '<HogeStack forwardedAs="section"><div><Heading>hoge</Heading></div></HogeStack>' },
49
50
  { code: '<HogeBase as="aside"><Heading>hoge</Heading></HogeBase>' },
50
51
  { code: '<HogeBaseColumn forwardedAs="nav"><Heading>hoge</Heading></HogeBaseColumn>' },
52
+ { code: '<HogeNav aria-label="any"><Any /></HogeNav>' },
53
+ { code: '<HogeNav aria-labelledby="any"><Any /></HogeNav>' },
51
54
  ],
52
55
  invalid: [
53
56
  { code: 'const StyledArticle = styled.article``', errors: [ { message: `"article"を利用せず、smarthr-ui/Articleを拡張してください。Headingのレベルが自動計算されるようになります。(例: "styled.article" -> "styled(Article)")` } ] },
@@ -64,5 +67,7 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
64
67
  { code: '<Section></Section>', errors: [ { message: notHaveHeadingMessage('Section') } ] },
65
68
  { code: '<Aside><HogeSection></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') }, { message: notHaveHeadingMessage('HogeSection') } ] },
66
69
  { code: '<Aside any="hoge"><HogeSection><Heading /></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') } ] },
70
+ { code: '<HogeNav><Any /></HogeNav>', errors: [ { message: notHaveHeadingMessage('HogeNav', true) } ] },
71
+ { code: '<HogeNav aria-diabled="true"><Any /></HogeNav>', errors: [ { message: notHaveHeadingMessage('HogeNav', true) } ] },
67
72
  ],
68
73
  });
@@ -55,10 +55,12 @@ ruleTester.run('best-practice-for-button-element', rule, {
55
55
  { code: `<HogeCluster justify="end" gap={0}>{a}</HogeCluster>` },
56
56
  { code: `<Stack align="flex-end">{a}</Stack>` },
57
57
  { code: `<HogeStack align="end">{a}</HogeStack>` },
58
+ { code: `<Cluster>{a}</Cluster>` },
59
+ { code: `<Cluster>{a.b}</Cluster>` },
60
+ { code: `<Cluster>{a ? b : c}</Cluster>` },
58
61
  ],
59
62
  invalid: [
60
63
  { code: `<Stack><Hoge /></Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
61
- { code: `<Stack>{a}</Stack>`, errors: [ { message: errorMessage('Stack', 'Stack') } ] },
62
64
  { code: `<AnyStack>{a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
63
65
  { code: `<AnyStack>{a && <><Hoge /></>}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
64
66
  { code: `<AnyStack>{a && a.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
@@ -70,7 +72,6 @@ ruleTester.run('best-practice-for-button-element', rule, {
70
72
  { code: `<AnyStack>{a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
71
73
  { code: `<AnyStack>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyStack>`, errors: [ { message: errorMessage('Stack', 'AnyStack') } ] },
72
74
  { code: `<Cluster><Hoge /></Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
73
- { code: `<Cluster>{a}</Cluster>`, errors: [ { message: errorMessage('Cluster', 'Cluster') } ] },
74
75
  { code: `<AnyCluster>{a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
75
76
  { code: `<AnyCluster>{a && <><Hoge /></>}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
76
77
  { code: `<AnyCluster>{a && a.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
@@ -81,8 +82,6 @@ ruleTester.run('best-practice-for-button-element', rule, {
81
82
  { code: `<AnyCluster>{a ? a.b.hoge(action) : <Hoge />}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
82
83
  { code: `<AnyCluster>{a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
83
84
  { code: `<AnyCluster>{a ? <Hoge /> : a ? <Hoge /> : a.b.hoge(action)}</AnyCluster>`, errors: [ { message: errorMessage('Cluster', 'AnyCluster') } ] },
84
- { code: `<HogeCluster justify="center">{a}</HogeCluster>`, errors: [ { message: 'HogeCluster は smarthr-ui/Cluster ではなく smarthr-ui/Center でマークアップしてください' } ] },
85
- { code: `<HogeStack align="center">{a}</HogeStack>`, errors: [ { message: 'HogeStack は smarthr-ui/Stack ではなく smarthr-ui/Center でマークアップしてください' } ] },
86
85
  { code: `<HogeStack gap={0}>{a}{b}</HogeStack>`, errors: [ { message: `HogeStack に "gap={0}" が指定されており、smarthr-ui/Stack の利用方法として誤っている可能性があります。以下の修正方法を検討してください。
87
86
  - 方法1: 子要素を一つにまとめられないか検討してください
88
87
  - 例: "<Stack gap={0}><p>hoge</p><p>fuga</p></Stack>" を "<p>hoge<br />fuga</p>" にするなど
@@ -23,13 +23,14 @@ ruleTester.run('format-translate-component', rule, {
23
23
  { code: '<Any data-wovn-enable="true">ほげ</Any>', options },
24
24
  { code: '<Translate>ほげ</Translate>', options },
25
25
  { code: '<Translate>ほげ<br />ふが</Translate>', options },
26
+ { code: '<Translate>1<RangeSeparator />100</Translate>', options },
26
27
  { code: '<Translate>{any}</Translate>', options },
27
28
  { code: '<Translate dangerouslySetInnerHTML={{ __html: "ほげ" }} />', options },
28
29
  ],
29
30
  invalid: [
30
31
  { code: '<Any data-translate="true">ほげ</Any>', options, errors: [ { message: 'data-translate 属性は使用せず、 @/any/path/Translate コンポーネントを利用してください' } ] },
31
- { code: '<Translate><Any>ほげ</Any></Translate>', options, errors: [ { message: 'Translate 内では <br /> 以外のタグは使えません' } ] },
32
- { code: '<Translate><Any /></Translate>', options, errors: [ { message: 'Translate 内では <br /> 以外のタグは使えません' } ] },
32
+ { code: '<Translate><Any>ほげ</Any></Translate>', options, errors: [ { message: 'Translate 内では <br />, <RangeSeparator /> 以外のタグは使えません' } ] },
33
+ { code: '<Translate><Any /></Translate>', options, errors: [ { message: 'Translate 内では <br />, <RangeSeparator /> 以外のタグは使えません' } ] },
33
34
  { code: '<Translate></Translate>', options, errors: [ { message: 'Translate 内には必ずテキストを設置してください' } ] },
34
35
  ]
35
36
  })