Skip to content

Commit d8741de

Browse files
Hypnosphiljharb
authored andcommitted
[New] button-has-type: support trivial ternary expressions
1 parent a43f70a commit d8741de

File tree

4 files changed

+160
-9
lines changed

4 files changed

+160
-9
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
55

6+
## Unreleased
7+
8+
### Added
9+
* [`button-has-type`]: support trivial ternary expressions ([#2748][] @Hypnosphi)
10+
11+
[#2748]: https://github.com/yannickcr/eslint-plugin-react/pull/2748
12+
613
## [7.20.6] - 2020.08.12
714

815
### Fixed

docs/rules/button-has-type.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ var Hello = <span type="foo">Hello</span>
2424
var Hello = <button type="button">Hello</button>
2525
var Hello = <button type="submit">Hello</button>
2626
var Hello = <button type="reset">Hello</button>
27+
var Hello = <button type={condition ? "button" : "submit"}>Hello</button>
2728

2829
var Hello = React.createElement('span', {}, 'Hello')
2930
var Hello = React.createElement('span', {type: 'foo'}, 'Hello')
3031
var Hello = React.createElement('button', {type: 'button'}, 'Hello')
3132
var Hello = React.createElement('button', {type: 'submit'}, 'Hello')
3233
var Hello = React.createElement('button', {type: 'reset'}, 'Hello')
34+
var Hello = React.createElement('button', {type: condition ? 'button' : 'submit'}, 'Hello')
3335
```
3436

3537
## Rule Options
@@ -50,8 +52,10 @@ The following patterns are considered errors when using `"react/button-has-type"
5052

5153
```jsx
5254
var Hello = <button type="reset">Hello</button>
55+
var Hello = <button type={condition ? "button" : "reset"}>Hello</button>
5356

5457
var Hello = React.createElement('button', {type: 'reset'}, 'Hello')
58+
var Hello = React.createElement('button', {type: condition ? "button" : "reset"}, 'Hello')
5559
```
5660

5761
## When Not To Use It

lib/rules/button-has-type.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ module.exports = {
7272
});
7373
}
7474

75+
function reportComplex(node) {
76+
context.report({
77+
node,
78+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
79+
});
80+
}
81+
7582
function checkValue(node, value) {
7683
const q = (x) => `"${x}"`;
7784
if (!(value in configuration)) {
@@ -87,6 +94,27 @@ module.exports = {
8794
}
8895
}
8996

97+
function checkExpression(node, expression) {
98+
switch (expression.type) {
99+
case 'Literal':
100+
checkValue(node, expression.value);
101+
return;
102+
case 'TemplateLiteral':
103+
if (expression.expressions.length === 0) {
104+
checkValue(node, expression.quasis[0].value.raw);
105+
} else {
106+
reportComplex(expression);
107+
}
108+
return;
109+
case 'ConditionalExpression':
110+
checkExpression(node, expression.consequent);
111+
checkExpression(node, expression.alternate);
112+
return;
113+
default:
114+
reportComplex(expression);
115+
}
116+
}
117+
90118
return {
91119
JSXElement(node) {
92120
if (node.openingElement.name.name !== 'button') {
@@ -101,10 +129,7 @@ module.exports = {
101129
}
102130

103131
if (typeProp.value.type === 'JSXExpressionContainer') {
104-
context.report({
105-
node: typeProp,
106-
message: 'The button type attribute must be specified by a static string'
107-
});
132+
checkExpression(node, typeProp.value.expression);
108133
return;
109134
}
110135

@@ -128,12 +153,12 @@ module.exports = {
128153
const props = node.arguments[1].properties;
129154
const typeProp = props.find((prop) => prop.key && prop.key.name === 'type');
130155

131-
if (!typeProp || typeProp.value.type !== 'Literal') {
156+
if (!typeProp) {
132157
reportMissing(node);
133158
return;
134159
}
135160

136-
checkValue(node, typeProp.value.value);
161+
checkExpression(node, typeProp.value);
137162
}
138163
};
139164
}

tests/lib/rules/button-has-type.js

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,30 @@ ruleTester.run('button-has-type', rule, {
3232
{code: '<button type="button"/>'},
3333
{code: '<button type="submit"/>'},
3434
{code: '<button type="reset"/>'},
35+
{code: '<button type={"button"}/>'},
36+
{code: '<button type={\'button\'}/>'},
37+
{code: '<button type={`button`}/>'},
38+
{code: '<button type={condition ? "button" : "submit"}/>'},
39+
{code: '<button type={condition ? \'button\' : \'submit\'}/>'},
40+
{code: '<button type={condition ? `button` : `submit`}/>'},
3541
{
3642
code: '<button type="button"/>',
3743
options: [{reset: false}]
3844
},
3945
{code: 'React.createElement("span")'},
4046
{code: 'React.createElement("span", {type: "foo"})'},
4147
{code: 'React.createElement("button", {type: "button"})'},
48+
{code: 'React.createElement("button", {type: \'button\'})'},
49+
{code: 'React.createElement("button", {type: `button`})'},
4250
{code: 'React.createElement("button", {type: "submit"})'},
51+
{code: 'React.createElement("button", {type: \'submit\'})'},
52+
{code: 'React.createElement("button", {type: `submit`})'},
4353
{code: 'React.createElement("button", {type: "reset"})'},
54+
{code: 'React.createElement("button", {type: \'reset\'})'},
55+
{code: 'React.createElement("button", {type: `reset`})'},
56+
{code: 'React.createElement("button", {type: condition ? "button" : "submit"})'},
57+
{code: 'React.createElement("button", {type: condition ? \'button\' : \'submit\'})'},
58+
{code: 'React.createElement("button", {type: condition ? `button` : `submit`})'},
4459
{
4560
code: 'React.createElement("button", {type: "button"})',
4661
options: [{reset: false}]
@@ -73,13 +88,31 @@ ruleTester.run('button-has-type', rule, {
7388
{
7489
code: '<button type={foo}/>',
7590
errors: [{
76-
message: 'The button type attribute must be specified by a static string'
91+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
7792
}]
7893
},
7994
{
8095
code: '<button type={"foo"}/>',
8196
errors: [{
82-
message: 'The button type attribute must be specified by a static string'
97+
message: '"foo" is an invalid value for button type attribute'
98+
}]
99+
},
100+
{
101+
code: '<button type={\'foo\'}/>',
102+
errors: [{
103+
message: '"foo" is an invalid value for button type attribute'
104+
}]
105+
},
106+
{
107+
code: '<button type={`foo`}/>',
108+
errors: [{
109+
message: '"foo" is an invalid value for button type attribute'
110+
}]
111+
},
112+
{
113+
code: '<button type={`button${foo}`}/>',
114+
errors: [{
115+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
83116
}]
84117
},
85118
{
@@ -89,12 +122,56 @@ ruleTester.run('button-has-type', rule, {
89122
message: '"reset" is a forbidden value for button type attribute'
90123
}]
91124
},
125+
{
126+
code: '<button type={condition ? "button" : foo}/>',
127+
errors: [{
128+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
129+
}]
130+
},
131+
{
132+
code: '<button type={condition ? "button" : "foo"}/>',
133+
errors: [{
134+
message: '"foo" is an invalid value for button type attribute'
135+
}]
136+
},
137+
{
138+
code: '<button type={condition ? "button" : "reset"}/>',
139+
options: [{reset: false}],
140+
errors: [{
141+
message: '"reset" is a forbidden value for button type attribute'
142+
}]
143+
},
144+
{
145+
code: '<button type={condition ? foo : "button"}/>',
146+
errors: [{
147+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
148+
}]
149+
},
150+
{
151+
code: '<button type={condition ? "foo" : "button"}/>',
152+
errors: [{
153+
message: '"foo" is an invalid value for button type attribute'
154+
}]
155+
},
156+
{
157+
code: '<button type={condition ? "reset" : "button"}/>',
158+
options: [{reset: false}],
159+
errors: [{
160+
message: '"reset" is a forbidden value for button type attribute'
161+
}]
162+
},
92163
{
93164
code: 'React.createElement("button")',
94165
errors: [{
95166
message: 'Missing an explicit type attribute for button'
96167
}]
97168
},
169+
{
170+
code: 'React.createElement("button", {type: foo})',
171+
errors: [{
172+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
173+
}]
174+
},
98175
{
99176
code: 'React.createElement("button", {type: "foo"})',
100177
errors: [{
@@ -108,6 +185,44 @@ ruleTester.run('button-has-type', rule, {
108185
message: '"reset" is a forbidden value for button type attribute'
109186
}]
110187
},
188+
{
189+
code: 'React.createElement("button", {type: condition ? "button" : foo})',
190+
errors: [{
191+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
192+
}]
193+
},
194+
{
195+
code: 'React.createElement("button", {type: condition ? "button" : "foo"})',
196+
errors: [{
197+
message: '"foo" is an invalid value for button type attribute'
198+
}]
199+
},
200+
{
201+
code: 'React.createElement("button", {type: condition ? "button" : "reset"})',
202+
options: [{reset: false}],
203+
errors: [{
204+
message: '"reset" is a forbidden value for button type attribute'
205+
}]
206+
},
207+
{
208+
code: 'React.createElement("button", {type: condition ? foo : "button"})',
209+
errors: [{
210+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
211+
}]
212+
},
213+
{
214+
code: 'React.createElement("button", {type: condition ? "foo" : "button"})',
215+
errors: [{
216+
message: '"foo" is an invalid value for button type attribute'
217+
}]
218+
},
219+
{
220+
code: 'React.createElement("button", {type: condition ? "reset" : "button"})',
221+
options: [{reset: false}],
222+
errors: [{
223+
message: '"reset" is a forbidden value for button type attribute'
224+
}]
225+
},
111226
{
112227
code: 'Foo.createElement("button")',
113228
errors: [{
@@ -122,7 +237,7 @@ ruleTester.run('button-has-type', rule, {
122237
{
123238
code: 'function Button({ type, ...extraProps }) { const button = type; return <button type={button} {...extraProps} />; }',
124239
errors: [{
125-
message: 'The button type attribute must be specified by a static string'
240+
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
126241
}]
127242
}
128243
]

0 commit comments

Comments
 (0)