UNPKG

3.78 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent JSX prop spreading
3 * @author Ashish Gambhir
4 */
5
6'use strict';
7
8const docsUrl = require('../util/docsUrl');
9
10// ------------------------------------------------------------------------------
11// Constants
12// ------------------------------------------------------------------------------
13
14const OPTIONS = {ignore: 'ignore', enforce: 'enforce'};
15const DEFAULTS = {
16 html: OPTIONS.enforce,
17 custom: OPTIONS.enforce,
18 explicitSpread: OPTIONS.enforce,
19 exceptions: []
20};
21
22// ------------------------------------------------------------------------------
23// Rule Definition
24// ------------------------------------------------------------------------------
25
26module.exports = {
27 meta: {
28 docs: {
29 description: 'Prevent JSX prop spreading',
30 category: 'Best Practices',
31 recommended: false,
32 url: docsUrl('jsx-props-no-spreading')
33 },
34 schema: [{
35 allOf: [{
36 type: 'object',
37 properties: {
38 html: {
39 enum: [OPTIONS.enforce, OPTIONS.ignore]
40 },
41 custom: {
42 enum: [OPTIONS.enforce, OPTIONS.ignore]
43 },
44 exceptions: {
45 type: 'array',
46 items: {
47 type: 'string',
48 uniqueItems: true
49 }
50 }
51 }
52 }, {
53 not: {
54 type: 'object',
55 required: ['html', 'custom'],
56 properties: {
57 html: {
58 enum: [OPTIONS.ignore]
59 },
60 custom: {
61 enum: [OPTIONS.ignore]
62 },
63 exceptions: {
64 type: 'array',
65 minItems: 0,
66 maxItems: 0
67 }
68 }
69 }
70 }]
71 }]
72 },
73
74 create(context) {
75 const configuration = context.options[0] || {};
76 const ignoreHtmlTags = (configuration.html || DEFAULTS.html) === OPTIONS.ignore;
77 const ignoreCustomTags = (configuration.custom || DEFAULTS.custom) === OPTIONS.ignore;
78 const ignoreExplicitSpread = (configuration.explicitSpread || DEFAULTS.explicitSpread) === OPTIONS.ignore;
79 const exceptions = configuration.exceptions || DEFAULTS.exceptions;
80 const isException = (tag, allExceptions) => allExceptions.indexOf(tag) !== -1;
81 const isProperty = (property) => property.type === 'Property';
82 const getTagNameFromMemberExpression = (node) => `${node.property.parent.object.name}.${node.property.name}`;
83 return {
84 JSXSpreadAttribute(node) {
85 const jsxOpeningElement = node.parent.name;
86 const type = jsxOpeningElement.type;
87
88 let tagName;
89 if (type === 'JSXIdentifier') {
90 tagName = jsxOpeningElement.name;
91 } else if (type === 'JSXMemberExpression') {
92 tagName = getTagNameFromMemberExpression(jsxOpeningElement);
93 } else {
94 tagName = undefined;
95 }
96
97 const isHTMLTag = tagName && tagName[0] !== tagName[0].toUpperCase();
98 const isCustomTag = tagName && (tagName[0] === tagName[0].toUpperCase() || tagName.includes('.'));
99 if (
100 isHTMLTag
101 && ((ignoreHtmlTags && !isException(tagName, exceptions))
102 || (!ignoreHtmlTags && isException(tagName, exceptions)))
103 ) {
104 return;
105 }
106 if (
107 isCustomTag
108 && ((ignoreCustomTags && !isException(tagName, exceptions))
109 || (!ignoreCustomTags && isException(tagName, exceptions)))
110 ) {
111 return;
112 }
113 if (
114 ignoreExplicitSpread
115 && node.argument.type === 'ObjectExpression'
116 && node.argument.properties.every(isProperty)
117 ) {
118 return;
119 }
120 context.report({
121 node,
122 message: 'Prop spreading is forbidden'
123 });
124 }
125 };
126 }
127};