Skip to content

Commit

Permalink
[New] button-has-type: support trivial ternary expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypnosphi authored and ljharb committed Aug 8, 2020
1 parent 9abc71d commit 877ae08
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/rules/button-has-type.md
Expand Up @@ -24,12 +24,14 @@ var Hello = <span type="foo">Hello</span>
var Hello = <button type="button">Hello</button>
var Hello = <button type="submit">Hello</button>
var Hello = <button type="reset">Hello</button>
var Hello = <button type={condition ? "button" : "submit"}>Hello</button>

var Hello = React.createElement('span', {}, 'Hello')
var Hello = React.createElement('span', {type: 'foo'}, 'Hello')
var Hello = React.createElement('button', {type: 'button'}, 'Hello')
var Hello = React.createElement('button', {type: 'submit'}, 'Hello')
var Hello = React.createElement('button', {type: 'reset'}, 'Hello')
var Hello = React.createElement('button', {type: condition ? 'button' : 'submit'}, 'Hello')
```

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

```jsx
var Hello = <button type="reset">Hello</button>
var Hello = <button type={condition ? "button" : "reset"}>Hello</button>

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

## When Not To Use It
Expand Down
37 changes: 31 additions & 6 deletions lib/rules/button-has-type.js
Expand Up @@ -72,6 +72,13 @@ module.exports = {
});
}

function reportComplex(node) {
context.report({
node,
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
});
}

function checkValue(node, value) {
const q = (x) => `"${x}"`;
if (!(value in configuration)) {
Expand All @@ -87,6 +94,27 @@ module.exports = {
}
}

function checkExpression(node, expression) {
switch (expression.type) {
case 'Literal':
checkValue(node, expression.value);
return;
case 'TemplateLiteral':
if (expression.expressions.length === 0) {
checkValue(node, expression.quasis[0].value.raw);
} else {
reportComplex(expression);
}
return;
case 'ConditionalExpression':
checkExpression(node, expression.consequent);
checkExpression(node, expression.alternate);
return;
default:
reportComplex(expression);
}
}

return {
JSXElement(node) {
if (node.openingElement.name.name !== 'button') {
Expand All @@ -101,10 +129,7 @@ module.exports = {
}

if (typeProp.value.type === 'JSXExpressionContainer') {
context.report({
node: typeProp,
message: 'The button type attribute must be specified by a static string'
});
checkExpression(node, typeProp.value.expression);
return;
}

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

if (!typeProp || typeProp.value.type !== 'Literal') {
if (!typeProp) {
reportMissing(node);
return;
}

checkValue(node, typeProp.value.value);
checkExpression(node, typeProp.value);
}
};
}
Expand Down
121 changes: 118 additions & 3 deletions tests/lib/rules/button-has-type.js
Expand Up @@ -32,15 +32,30 @@ ruleTester.run('button-has-type', rule, {
{code: '<button type="button"/>'},
{code: '<button type="submit"/>'},
{code: '<button type="reset"/>'},
{code: '<button type={"button"}/>'},
{code: '<button type={\'button\'}/>'},
{code: '<button type={`button`}/>'},
{code: '<button type={condition ? "button" : "submit"}/>'},
{code: '<button type={condition ? \'button\' : \'submit\'}/>'},
{code: '<button type={condition ? `button` : `submit`}/>'},
{
code: '<button type="button"/>',
options: [{reset: false}]
},
{code: 'React.createElement("span")'},
{code: 'React.createElement("span", {type: "foo"})'},
{code: 'React.createElement("button", {type: "button"})'},
{code: 'React.createElement("button", {type: \'button\'})'},
{code: 'React.createElement("button", {type: `button`})'},
{code: 'React.createElement("button", {type: "submit"})'},
{code: 'React.createElement("button", {type: \'submit\'})'},
{code: 'React.createElement("button", {type: `submit`})'},
{code: 'React.createElement("button", {type: "reset"})'},
{code: 'React.createElement("button", {type: \'reset\'})'},
{code: 'React.createElement("button", {type: `reset`})'},
{code: 'React.createElement("button", {type: condition ? "button" : "submit"})'},
{code: 'React.createElement("button", {type: condition ? \'button\' : \'submit\'})'},
{code: 'React.createElement("button", {type: condition ? `button` : `submit`})'},
{
code: 'React.createElement("button", {type: "button"})',
options: [{reset: false}]
Expand Down Expand Up @@ -73,13 +88,31 @@ ruleTester.run('button-has-type', rule, {
{
code: '<button type={foo}/>',
errors: [{
message: 'The button type attribute must be specified by a static string'
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: '<button type={"foo"}/>',
errors: [{
message: 'The button type attribute must be specified by a static string'
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: '<button type={\'foo\'}/>',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: '<button type={`foo`}/>',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: '<button type={`button${foo}`}/>',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
Expand All @@ -89,12 +122,56 @@ ruleTester.run('button-has-type', rule, {
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: '<button type={condition ? "button" : foo}/>',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: '<button type={condition ? "button" : "foo"}/>',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: '<button type={condition ? "button" : "reset"}/>',
options: [{reset: false}],
errors: [{
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: '<button type={condition ? foo : "button"}/>',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: '<button type={condition ? "foo" : "button"}/>',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: '<button type={condition ? "reset" : "button"}/>',
options: [{reset: false}],
errors: [{
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: 'React.createElement("button")',
errors: [{
message: 'Missing an explicit type attribute for button'
}]
},
{
code: 'React.createElement("button", {type: foo})',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: 'React.createElement("button", {type: "foo"})',
errors: [{
Expand All @@ -108,6 +185,44 @@ ruleTester.run('button-has-type', rule, {
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: 'React.createElement("button", {type: condition ? "button" : foo})',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: 'React.createElement("button", {type: condition ? "button" : "foo"})',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: 'React.createElement("button", {type: condition ? "button" : "reset"})',
options: [{reset: false}],
errors: [{
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: 'React.createElement("button", {type: condition ? foo : "button"})',
errors: [{
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
},
{
code: 'React.createElement("button", {type: condition ? "foo" : "button"})',
errors: [{
message: '"foo" is an invalid value for button type attribute'
}]
},
{
code: 'React.createElement("button", {type: condition ? "reset" : "button"})',
options: [{reset: false}],
errors: [{
message: '"reset" is a forbidden value for button type attribute'
}]
},
{
code: 'Foo.createElement("button")',
errors: [{
Expand All @@ -122,7 +237,7 @@ ruleTester.run('button-has-type', rule, {
{
code: 'function Button({ type, ...extraProps }) { const button = type; return <button type={button} {...extraProps} />; }',
errors: [{
message: 'The button type attribute must be specified by a static string'
message: 'The button type attribute must be specified by a static string or a trivial ternary expression'
}]
}
]
Expand Down

0 comments on commit 877ae08

Please sign in to comment.