UNPKG

3.75 kBJavaScriptView Raw
1/**
2 * @fileoverview Forbid "button" element without an explicit "type" attribute
3 * @author Filipp Riabchun
4 */
5
6'use strict';
7
8const getProp = require('jsx-ast-utils/getProp');
9const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
10const docsUrl = require('../util/docsUrl');
11const pragmaUtil = require('../util/pragma');
12
13// ------------------------------------------------------------------------------
14// Helpers
15// ------------------------------------------------------------------------------
16
17function isCreateElement(node, context) {
18 const pragma = pragmaUtil.getFromContext(context);
19 return node.callee
20 && node.callee.type === 'MemberExpression'
21 && node.callee.property.name === 'createElement'
22 && node.callee.object
23 && node.callee.object.name === pragma
24 && node.arguments.length > 0;
25}
26
27// ------------------------------------------------------------------------------
28// Rule Definition
29// ------------------------------------------------------------------------------
30
31const optionDefaults = {
32 button: true,
33 submit: true,
34 reset: true
35};
36
37module.exports = {
38 meta: {
39 docs: {
40 description: 'Forbid "button" element without an explicit "type" attribute',
41 category: 'Possible Errors',
42 recommended: false,
43 url: docsUrl('button-has-type')
44 },
45 schema: [{
46 type: 'object',
47 properties: {
48 button: {
49 default: optionDefaults.button,
50 type: 'boolean'
51 },
52 submit: {
53 default: optionDefaults.submit,
54 type: 'boolean'
55 },
56 reset: {
57 default: optionDefaults.reset,
58 type: 'boolean'
59 }
60 },
61 additionalProperties: false
62 }]
63 },
64
65 create(context) {
66 const configuration = Object.assign({}, optionDefaults, context.options[0]);
67
68 function reportMissing(node) {
69 context.report({
70 node,
71 message: 'Missing an explicit type attribute for button'
72 });
73 }
74
75 function checkValue(node, value) {
76 const q = (x) => `"${x}"`;
77 if (!(value in configuration)) {
78 context.report({
79 node,
80 message: `${q(value)} is an invalid value for button type attribute`
81 });
82 } else if (!configuration[value]) {
83 context.report({
84 node,
85 message: `${q(value)} is a forbidden value for button type attribute`
86 });
87 }
88 }
89
90 return {
91 JSXElement(node) {
92 if (node.openingElement.name.name !== 'button') {
93 return;
94 }
95
96 const typeProp = getProp(node.openingElement.attributes, 'type');
97
98 if (!typeProp) {
99 reportMissing(node);
100 return;
101 }
102
103 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 });
108 return;
109 }
110
111 const propValue = getLiteralPropValue(typeProp);
112 checkValue(node, propValue);
113 },
114 CallExpression(node) {
115 if (!isCreateElement(node, context)) {
116 return;
117 }
118
119 if (node.arguments[0].type !== 'Literal' || node.arguments[0].value !== 'button') {
120 return;
121 }
122
123 if (!node.arguments[1] || node.arguments[1].type !== 'ObjectExpression') {
124 reportMissing(node);
125 return;
126 }
127
128 const props = node.arguments[1].properties;
129 const typeProp = props.find((prop) => prop.key && prop.key.name === 'type');
130
131 if (!typeProp || typeProp.value.type !== 'Literal') {
132 reportMissing(node);
133 return;
134 }
135
136 checkValue(node, typeProp.value.value);
137 }
138 };
139 }
140};