From 416deff06d90b100f991bfbf86f7484061dca0d2 Mon Sep 17 00:00:00 2001 From: Joshua Stiefer Date: Mon, 20 Feb 2017 21:51:28 -0700 Subject: [PATCH] Update void-dom-elements-no-children This ensures that the rule only checks for a `createElement` call from React. It will also prevent the rule from failing when it hits a `createElement` that only has an element as an argument. --- lib/rules/void-dom-elements-no-children.js | 14 +++-- lib/util/Components.js | 54 ++++++++++++------- .../rules/void-dom-elements-no-children.js | 52 ++++++++++++++++++ 3 files changed, 98 insertions(+), 22 deletions(-) diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js index 323dbed3b2..17ac934a56 100644 --- a/lib/rules/void-dom-elements-no-children.js +++ b/lib/rules/void-dom-elements-no-children.js @@ -8,6 +8,8 @@ var find = require('array.prototype.find'); var has = require('has'); +var Components = require('../util/Components'); + // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ @@ -55,7 +57,7 @@ module.exports = { schema: [] }, - create: function(context) { + create: Components.detect(function(context, components, utils) { return { JSXElement: function(node) { var elementName = node.openingElement.name.name; @@ -93,11 +95,11 @@ module.exports = { }, CallExpression: function(node) { - if (node.callee.type !== 'MemberExpression') { + if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'Identifier') { return; } - if (node.callee.property.name !== 'createElement') { + if (!utils.hasDestructuredReactCreateElement() && !utils.isReactCreateElement(node)) { return; } @@ -109,6 +111,10 @@ module.exports = { return; } + if (args.length < 2) { + return; + } + var firstChild = args[2]; if (firstChild) { // e.g. React.createElement('br', undefined, 'Foo') @@ -137,5 +143,5 @@ module.exports = { } } }; - } + }) }; diff --git a/lib/util/Components.js b/lib/util/Components.js index eb2bad5356..1eacf099dd 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -213,6 +213,40 @@ function componentRule(rule, context) { return false; }, + /** + * Check if createElement is destructured from React import + * + * @returns {Boolean} True if createElement is destructured from React + */ + hasDestructuredReactCreateElement: function() { + var variables = variableUtil.variablesInScope(context); + var variable = variableUtil.getVariable(variables, 'createElement'); + if (variable) { + var map = variable.scope.set; + if (map.has('React')) { + return true; + } + } + return false; + }, + + /** + * Checks to see if node is called within React.createElement + * + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if React.createElement called + */ + isReactCreateElement: function(node) { + return ( + node && + node.callee && + node.callee.object && + node.callee.object.name === 'React' && + node.callee.property && + node.callee.property.name === 'createElement' + ); + }, + /** * Check if the node is returning JSX * @@ -256,25 +290,9 @@ function componentRule(rule, context) { node[property] && node[property].type === 'JSXElement' ; - var destructuredReactCreateElement = function () { - var variables = variableUtil.variablesInScope(context); - var variable = variableUtil.getVariable(variables, 'createElement'); - if (variable) { - var map = variable.scope.set; - if (map.has('React')) { - return true; - } - } - return false; - }; var returnsReactCreateElement = - destructuredReactCreateElement() || - node[property] && - node[property].callee && - node[property].callee.object && - node[property].callee.object.name === 'React' && - node[property].callee.property && - node[property].callee.property.name === 'createElement' + this.hasDestructuredReactCreateElement() || + this.isReactCreateElement(node[property]) ; return Boolean( diff --git a/tests/lib/rules/void-dom-elements-no-children.js b/tests/lib/rules/void-dom-elements-no-children.js index 022d8e877d..97352b5f6c 100644 --- a/tests/lib/rules/void-dom-elements-no-children.js +++ b/tests/lib/rules/void-dom-elements-no-children.js @@ -54,6 +54,28 @@ ruleTester.run('void-dom-elements-no-children', rule, { { code: 'React.createElement("div", { dangerouslySetInnerHTML: { __html: "Foo" } });', parserOptions: parserOptions + }, { + code: 'document.createElement("img")', + parserOptions: parserOptions + }, { + code: 'React.createElement("img");', + parserOptions: parserOptions + }, { + code: [ + 'import React from "react";', + 'const { createElement } = React;', + 'createElement("div")' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions + }, { + code: [ + 'import React from "react";', + 'const { createElement } = React;', + 'createElement("img")' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions } ], invalid: [ @@ -91,6 +113,36 @@ ruleTester.run('void-dom-elements-no-children', rule, { code: 'React.createElement("br", { dangerouslySetInnerHTML: { __html: "Foo" } });', errors: [{message: errorMessage('br')}], parserOptions: parserOptions + }, + { + code: [ + 'import React from "react";', + 'const createElement = React.createElement;', + 'createElement("img", {}, "Foo");' + ].join('\n'), + errors: [{message: errorMessage('img')}], + parser: 'babel-eslint', + parserOptions: parserOptions + }, + { + code: [ + 'import React from "react";', + 'const createElement = React.createElement;', + 'createElement("img", { children: "Foo" });' + ].join('\n'), + errors: [{message: errorMessage('img')}], + parser: 'babel-eslint', + parserOptions: parserOptions + }, + { + code: [ + 'import React from "react";', + 'const createElement = React.createElement;', + 'createElement("img", { dangerouslySetInnerHTML: { __html: "Foo" } });' + ].join('\n'), + errors: [{message: errorMessage('img')}], + parser: 'babel-eslint', + parserOptions: parserOptions } ] });