eslint-plugin-smarthr 6.2.2 → 6.3.0
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 -0
- package/package.json +4 -4
- package/rules/a11y-anchor-has-href-attribute/index.js +2 -2
- package/rules/a11y-heading-in-sectioning-content/index.js +4 -3
- package/rules/best-practice-for-interactive-element/index.js +3 -3
- package/rules/best-practice-for-layouts/index.js +2 -2
- package/rules/best-practice-for-rest-parameters/index.js +3 -3
- package/rules/best-practice-for-tailwind-prohibit-root-margin/index.js +1 -1
- package/rules/best-practice-for-unnesessary-early-return/index.js +2 -2
- package/rules/trim-props/index.js +10 -21
- package/test/a11y-heading-in-sectioning-content.js +3 -2
- package/test/trim-props.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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
|
+
## [6.3.0](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.2.3...eslint-plugin-smarthr-v6.3.0) (2026-02-06)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **a11y-heading-in-sectioning-content:** tag属性がunrecommendedTagにrenameされたためチェック対象を調整 ([#1066](https://github.com/kufu/tamatebako/issues/1066)) ([5b524f3](https://github.com/kufu/tamatebako/commit/5b524f3a712e48953ec6aad18028f3f19d6b86ca))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* selectorでhas(>...)をやめ、直接属性で絞り込むように調整 ([#1069](https://github.com/kufu/tamatebako/issues/1069)) ([540426f](https://github.com/kufu/tamatebako/commit/540426f0ea90ba9b8cd67242067dcc8e4c855b5f))
|
|
16
|
+
|
|
17
|
+
## [6.2.3](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.2.2...eslint-plugin-smarthr-v6.2.3) (2026-02-05)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* eslintのセレクタエンジンのアップデートにより、>の前後のスペースが必須になったため修正 ([#1055](https://github.com/kufu/tamatebako/issues/1055)) ([d885f3c](https://github.com/kufu/tamatebako/commit/d885f3cde328abc119c0e4798c399cbdd1b4907a))
|
|
23
|
+
|
|
5
24
|
## [6.2.2](https://github.com/kufu/tamatebako/compare/eslint-plugin-smarthr-v6.2.1...eslint-plugin-smarthr-v6.2.2) (2026-02-05)
|
|
6
25
|
|
|
7
26
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-smarthr",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"author": "SmartHR",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A sharable ESLint plugin for SmartHR",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=22.
|
|
9
|
+
"node": ">=22.22.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "jest"
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"json5": "^2.2.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"typescript-eslint": "^8.
|
|
29
|
+
"typescript-eslint": "^8.54.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"eslint": "^9"
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
"eslintplugin",
|
|
38
38
|
"smarthr"
|
|
39
39
|
],
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "b68d1a046be224dadd15a4e266df1ef1ccb49b23"
|
|
41
41
|
}
|
|
@@ -46,10 +46,10 @@ const OPTION = (() => {
|
|
|
46
46
|
const ANCHOR_ELEMENT = 'JSXOpeningElement[name.name=/(Anchor|Link|^a)$/]'
|
|
47
47
|
const HREF_ATTRIBUTE = `JSXAttribute[name.name=${OPTION.react_router ? '/^(href|to)$/' : '"href"'}]`
|
|
48
48
|
const NULL_HREF_ATTRIBUTE_VALUES = `${HREF_ATTRIBUTE}:matches(${['#', ''].reduce((prev, v) => {
|
|
49
|
-
return `${prev}
|
|
49
|
+
return `${prev},[value.type="Literal"][value.value="${v}"],[value.type="JSXExpressionContainer"][value.expression.value="${v}"]`
|
|
50
50
|
}, '[value=null]')})`
|
|
51
51
|
const NEXT_LINK_REGEX = /Link$/
|
|
52
|
-
// HINT: next/link で `Link>a` という構造がありえるので直上のJSXElementを調べる
|
|
52
|
+
// HINT: next/link で `Link > a` という構造がありえるので直上のJSXElementを調べる
|
|
53
53
|
const nextCheck = (node) => ((node.parent.parent.openingElement.name.name || '').test(NEXT_LINK_REGEX))
|
|
54
54
|
|
|
55
55
|
const MESSAGE_SUFFIX = ` に href${OPTION.react_router ? '、もしくはto' : ''} 属性を正しく設定してください
|
|
@@ -11,6 +11,7 @@ const noHeadingTagNamesRegex = /^(span|legend)$/
|
|
|
11
11
|
const ignoreHeadingCheckParentTypeRegex = /^(Program|ExportNamedDeclaration)$/
|
|
12
12
|
const headingAttributeRegex = /^(heading|title)$/
|
|
13
13
|
const ariaLabelRegex = /^aria-label(ledby)?$/
|
|
14
|
+
const tagAttrRegex = /^(tag|unrecommendedTag)$/
|
|
14
15
|
|
|
15
16
|
const includeSectioningAsAttr = (a) => asRegex.test(a.name?.name) && bareTagRegex.test(a.value.value)
|
|
16
17
|
|
|
@@ -23,9 +24,9 @@ const pageHeadingMessage = `smarthr-ui/PageHeading が同一ファイル内に
|
|
|
23
24
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
24
25
|
const pageHeadingInSectionMessage = `smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。
|
|
25
26
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
26
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
27
|
+
const noTagAttrMessage = `tag属性、unrecommendedTag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性、unrecommendedTag属性を削除してください。
|
|
27
28
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
28
|
-
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
29
|
+
- tag属性、unrecommendedTag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
29
30
|
|
|
30
31
|
const VariableDeclaratorBareToSHR = (context, node) => {
|
|
31
32
|
if (!node.init) {
|
|
@@ -177,7 +178,7 @@ const forInSearchChildren = (ary) => {
|
|
|
177
178
|
return r
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
const findTagAttr = (a) => a.name?.name
|
|
181
|
+
const findTagAttr = (a) => tagAttrRegex.test(a.name?.name)
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
184
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -70,14 +70,14 @@ module.exports = {
|
|
|
70
70
|
const targetNameProp = `[name.name=${interactiveComponentRegex}]`
|
|
71
71
|
|
|
72
72
|
return {
|
|
73
|
-
[`JSXOpeningElement${targetNameProp}>JSXAttribute[name.name="role"]${NOT_ARROW_ROLE_ATTRIBUTES}`]: (node) => {
|
|
73
|
+
[`JSXOpeningElement${targetNameProp} > JSXAttribute[name.name="role"]${NOT_ARROW_ROLE_ATTRIBUTES}`]: (node) => {
|
|
74
74
|
context.report({
|
|
75
75
|
node: node.parent,
|
|
76
76
|
message: `${node.parent.name.name}にrole属性は指定しないでください。
|
|
77
77
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/best-practice-for-interactive-element`,
|
|
78
78
|
});
|
|
79
79
|
},
|
|
80
|
-
[`JSXOpeningElement
|
|
80
|
+
[`JSXOpeningElement > ${AS_FORM_PART_ATTRIBUTE}`]: (node) => {
|
|
81
81
|
if (node.parent.attributes.some((a) => a.type === 'JSXAttribute' && a.name?.name === 'role')) {
|
|
82
82
|
context.report({
|
|
83
83
|
node: node.parent,
|
|
@@ -86,7 +86,7 @@ module.exports = {
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
-
[`JSXOpeningElement:not(${targetNameProp}):not(:has(${AS_FORM_PART_ATTRIBUTE}))>JSXAttribute[name.name=${INTERACTIVE_ON_REGEX}]:not([value.expression.name=${DELEGATE_REGEX}])`]: (node) => {
|
|
89
|
+
[`JSXOpeningElement:not(${targetNameProp}):not(:has(${AS_FORM_PART_ATTRIBUTE})) > JSXAttribute[name.name=${INTERACTIVE_ON_REGEX}]:not([value.expression.name=${DELEGATE_REGEX}])`]: (node) => {
|
|
90
90
|
switch (node.value.expression.type) {
|
|
91
91
|
case 'MemberExpression':
|
|
92
92
|
if (DELEGATE_REGEX.test(context.sourceCode.getText(node.expression))) {
|
|
@@ -185,7 +185,7 @@ module.exports = {
|
|
|
185
185
|
message: `Fieldsetのlegend属性にアイコンを設定する場合 <Fieldset legend={{ text: 'テキスト', icon: <XxxIcon /> }} /> のようにlegend.icon属性を利用してください${DETAIL_LINK_MESSAGE}`,
|
|
186
186
|
})
|
|
187
187
|
},
|
|
188
|
-
[`JSXElement
|
|
188
|
+
[`JSXElement[openingElement.name.name=/RadioButton(Panel)?$/] ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
189
189
|
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
190
190
|
|
|
191
191
|
context.report({
|
|
@@ -193,7 +193,7 @@ module.exports = {
|
|
|
193
193
|
message: `RadioButton, RadioButtonPanelの子孫に${component}を置く場合、as属性、もしくはforwardedAs属性に \`span\` を指定してください${DETAIL_LINK_MESSAGE}`,
|
|
194
194
|
})
|
|
195
195
|
},
|
|
196
|
-
[`JSXElement
|
|
196
|
+
[`JSXElement[openingElement.name.name=/Checkbox?$/] ${LAYOUT_ELEMENT_NOT_SPAN}`]: (node) => {
|
|
197
197
|
const component = node.name.name.match(LAYOUT_COMPONENT_REGEX)[1]
|
|
198
198
|
|
|
199
199
|
context.report({
|
|
@@ -47,10 +47,10 @@ module.exports = {
|
|
|
47
47
|
message: `残余引数には ${REST_REGEX} とマッチする名称を指定してください${DETAIL_LINK}`,
|
|
48
48
|
})
|
|
49
49
|
},
|
|
50
|
-
[`:not(:matches(RestElement,JSXSpreadAttribute,JSXSpreadAttribute>TSAsExpression,SpreadElement,SpreadElement>TSAsExpression,MemberExpression,VariableDeclarator,ArrayExpression,CallExpression,ObjectPattern>Property,ObjectExpression>Property,ReturnStatement,ArrowFunctionExpression))>Identifier[name=${REST_REGEX}]`]: actionNotRest,
|
|
51
|
-
[`:matches(VariableDeclarator[id.name=${REST_REGEX}],ObjectPattern>Property[value.name=${REST_REGEX}],ObjectExpression>Property[key.name=${REST_REGEX}])`]: actionNotRest,
|
|
50
|
+
[`:not(:matches(RestElement,JSXSpreadAttribute,JSXSpreadAttribute > TSAsExpression,SpreadElement,SpreadElement > TSAsExpression,MemberExpression,VariableDeclarator,ArrayExpression,CallExpression,ObjectPattern > Property,ObjectExpression > Property,ReturnStatement,ArrowFunctionExpression)) > Identifier[name=${REST_REGEX}]`]: actionNotRest,
|
|
51
|
+
[`:matches(VariableDeclarator[id.name=${REST_REGEX}],ObjectPattern > Property[value.name=${REST_REGEX}],ObjectExpression > Property[key.name=${REST_REGEX}])`]: actionNotRest,
|
|
52
52
|
[`MemberExpression[object.name=${REST_REGEX}]`]: actionMemberExpressionName,
|
|
53
|
-
[`ArrowFunctionExpression>Identifier[name=${REST_REGEX}]`]: (node) => {
|
|
53
|
+
[`ArrowFunctionExpression > Identifier[name=${REST_REGEX}]`]: (node) => {
|
|
54
54
|
if (node !== node.parent.body) {
|
|
55
55
|
actionNotRest(node)
|
|
56
56
|
}
|
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
},
|
|
11
11
|
create(context) {
|
|
12
12
|
return {
|
|
13
|
-
':matches(ArrowFunctionExpression,FunctionDeclaration,ReturnStatement)>JSXElement>JSXOpeningElement JSXAttribute[name.name="className"][value.value=/( |^)shr-m[trbl]?-/]': (node) => {
|
|
13
|
+
':matches(ArrowFunctionExpression,FunctionDeclaration,ReturnStatement) > JSXElement > JSXOpeningElement JSXAttribute[name.name="className"][value.value=/( |^)shr-m[trbl]?-/]': (node) => {
|
|
14
14
|
context.report({
|
|
15
15
|
node,
|
|
16
16
|
message: `コンポーネントのルート要素に外側への余白(margin)を設定しないでください。外側の余白は使用する側で制御するべきです。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const SCHEMA = []
|
|
2
2
|
|
|
3
|
-
const EARLY_RETURN_IF_STATEMENT = `:matches(ArrowFunctionExpression,FunctionExpression,FunctionDeclaration)>BlockStatement>IfStatement[alternate=null]:matches([consequent.type='ReturnStatement'],[consequent.body.length=1])
|
|
3
|
+
const EARLY_RETURN_IF_STATEMENT = `:matches(ArrowFunctionExpression,FunctionExpression,FunctionDeclaration) > BlockStatement > IfStatement[alternate=null]:matches([consequent.type='ReturnStatement'],[consequent.body.length=1]) > `
|
|
4
4
|
const NULL_RETURN_STATEMENT = 'ReturnStatement[argument=null]'
|
|
5
5
|
|
|
6
6
|
const FUNCTION_REGEX = /^(Arrow)?Function(Expression|Declaration)$/
|
|
@@ -84,7 +84,7 @@ module.exports = {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
return {
|
|
87
|
-
[`${EARLY_RETURN_IF_STATEMENT}BlockStatement
|
|
87
|
+
[`${EARLY_RETURN_IF_STATEMENT}BlockStatement > ${NULL_RETURN_STATEMENT}`]: action,
|
|
88
88
|
[`${EARLY_RETURN_IF_STATEMENT}${NULL_RETURN_STATEMENT}`]: action,
|
|
89
89
|
}
|
|
90
90
|
},
|
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
const SCHEMA = []
|
|
2
2
|
|
|
3
|
-
const searchBubbleUp = (node) => {
|
|
4
|
-
switch (node.type) {
|
|
5
|
-
case 'Program':
|
|
6
|
-
case 'JSXAttribute':
|
|
7
|
-
return null
|
|
8
|
-
case 'TemplateLiteral':
|
|
9
|
-
return node
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return searchBubbleUp(node.parent)
|
|
13
|
-
}
|
|
14
3
|
|
|
15
4
|
/**
|
|
16
5
|
* @type {import('@typescript-eslint/utils').TSESLint.RuleModule<''>}
|
|
@@ -23,20 +12,20 @@ module.exports = {
|
|
|
23
12
|
},
|
|
24
13
|
create(context) {
|
|
25
14
|
const checker = (node) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
node,
|
|
30
|
-
message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
|
|
15
|
+
return context.report({
|
|
16
|
+
node,
|
|
17
|
+
message: `属性に設定している文字列から先頭、末尾の空白文字を削除してください
|
|
31
18
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/trim-props`,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
19
|
+
fix: (fixer) => fixer.replaceText(node, context.sourceCode.getText(node).replace(/^('|"|`)\s+/, '$1').replace(/\s+('|"|`)$/, '$1')),
|
|
20
|
+
})
|
|
35
21
|
}
|
|
36
22
|
|
|
37
23
|
return {
|
|
38
|
-
'JSXAttribute Literal[value=/(^ | $)/]': checker,
|
|
39
|
-
'JSXAttribute
|
|
24
|
+
'JSXAttribute > Literal[value=/(^ | $)/]': checker,
|
|
25
|
+
'JSXAttribute > JSXExpressionContainer>Literal[value=/(^ | $)/]': checker,
|
|
26
|
+
'JSXAttribute > JSXExpressionContainer > TemplateLiteral > TemplateElement:matches(:first-child[value.raw=/^ /],:last-child[value.raw=/ $/])': (node) => {
|
|
27
|
+
checker(node.parent)
|
|
28
|
+
},
|
|
40
29
|
}
|
|
41
30
|
},
|
|
42
31
|
}
|
|
@@ -20,9 +20,9 @@ const pageMessage = `smarthr-ui/PageHeading が同一ファイル内に複数存
|
|
|
20
20
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
21
21
|
const pageInSectionMessage = `smarthr-ui/PageHeadingはsmarthr-uiのArticle, Aside, Nav, Sectionで囲まないでください。囲んでしまうとページ全体の見出しではなくなってしまいます。
|
|
22
22
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content`
|
|
23
|
-
const noTagAttrMessage = `tag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性を削除してください。
|
|
23
|
+
const noTagAttrMessage = `tag属性、unrecommendedTag属性を指定せず、smarthr-uiのArticle, Aside, Nav, Sectionのいずれかの自動レベル計算に任せるよう、tag属性、unrecommendedTag属性を削除してください。
|
|
24
24
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
25
|
-
- tag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
25
|
+
- tag属性、unrecommendedTag属性を指定することで意図しないレベルに固定されてしまう可能性があります。`
|
|
26
26
|
const notHaveHeadingMessage = (elementName, isNav) => `${elementName} はHeading要素を含んでいません。
|
|
27
27
|
- 詳細: https://github.com/kufu/tamatebako/tree/master/packages/eslint-plugin-smarthr/rules/a11y-heading-in-sectioning-content
|
|
28
28
|
- SectioningContentはHeadingを含むようにマークアップする必要があります
|
|
@@ -69,6 +69,7 @@ ruleTester.run('a11y-heading-in-sectioning-content', rule, {
|
|
|
69
69
|
{ code: '<Section><Heading>hoge</Heading><Heading>fuga</Heading></Section>', errors: [ { message: lowerMessage } ] },
|
|
70
70
|
{ code: '<Section><PageHeading>hoge</PageHeading></Section>', errors: [ { message: pageInSectionMessage } ] },
|
|
71
71
|
{ code: '<Section><Heading tag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
72
|
+
{ code: '<Section><Heading unrecommendedTag="h2">hoge</Heading></Section>', errors: [ { message: noTagAttrMessage } ] },
|
|
72
73
|
{ code: '<Section></Section>', errors: [ { message: notHaveHeadingMessage('Section') } ] },
|
|
73
74
|
{ code: '<Aside><HogeSection></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') }, { message: notHaveHeadingMessage('HogeSection') } ] },
|
|
74
75
|
{ code: '<Aside any="hoge"><HogeSection><Heading /></HogeSection></Aside>', errors: [ { message: notHaveHeadingMessage('Aside') } ] },
|
package/test/trim-props.js
CHANGED
|
@@ -89,12 +89,12 @@ ruleTester.run('trim-props', rule, {
|
|
|
89
89
|
{
|
|
90
90
|
code: '<div data-spec={` a${b} c `}>....</div>',
|
|
91
91
|
output: '<div data-spec={`a${b} c`}>....</div>',
|
|
92
|
-
errors: [{ message: ERROR_MESSAGE }],
|
|
92
|
+
errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
code: '<div data-spec={` a${b ? ` ${c} ` : " "} d `}>....</div>',
|
|
96
96
|
output: '<div data-spec={`a${b ? ` ${c} ` : " "} d`}>....</div>',
|
|
97
|
-
errors: [{ message: ERROR_MESSAGE }],
|
|
97
|
+
errors: [{ message: ERROR_MESSAGE }, { message: ERROR_MESSAGE }],
|
|
98
98
|
},
|
|
99
99
|
],
|
|
100
100
|
})
|