2 * @fileoverview Utility functions for React components detection
3 * @author Yannick Croissant
7 var util = require('util');
9 var DEFAULT_COMPONENT_NAME = 'eslintReactComponent';
12 * Detect if we are in a React Component
13 * A React component is defined has an object/class with a property "render"
14 * that return a JSXElement or null/false
15 * @param {Object} context The current rule context.
16 * @param {ASTNode} node The AST node being checked.
17 * @returns {Boolean} True if we are in a React Component, false if not.
19 function isReactComponent(context, node) {
20 if (node.type !== 'ReturnStatement') {
21 throw new Error('React Component detection must be done from a ReturnStatement ASTNode');
24 var scope = context.getScope();
25 var isComponentRender =
27 node.argument.type === 'JSXElement' &&
28 scope.block.parent.key && scope.block.parent.key.name === 'render'
30 var isEmptyComponentRender =
32 node.argument.type === 'Literal' && (node.argument.value === null || node.argument.value === false) &&
33 scope.block.parent.key && scope.block.parent.key.name === 'render'
36 return Boolean(isEmptyComponentRender || isComponentRender);
40 * Detect if the node is a component definition
41 * @param {ASTNode} node The AST node being checked.
42 * @returns {Boolean} True the node is a component definition, false if not.
44 function isComponentDefinition(node) {
45 var isES6Component = node.type === 'ClassDeclaration';
46 var isES5Component = Boolean(
47 node.type === 'ObjectExpression' &&
50 node.parent.callee.object &&
51 node.parent.callee.property &&
52 node.parent.callee.object.name === 'React' &&
53 node.parent.callee.property.name === 'createClass'
55 return isES5Component || isES6Component;
59 * Get the React component ASTNode from any child ASTNode
60 * @param {Object} context The current rule context.
61 * @param {ASTNode} node The AST node being checked.
62 * @returns {ASTNode} The ASTNode of the React component.
64 function getNode(context, node) {
65 var componentNode = null;
66 var ancestors = context.getAncestors().reverse();
68 ancestors.unshift(node);
70 for (var i = 0, j = ancestors.length; i < j; i++) {
71 if (isComponentDefinition(ancestors[i])) {
72 componentNode = ancestors[i];
81 * Get the identifiers of a React component ASTNode
82 * @param {ASTNode} node The React component ASTNode being checked.
83 * @returns {Object} The component identifiers.
85 function getIdentifiers(node) {
86 var name = node.id && node.id.name || DEFAULT_COMPONENT_NAME;
87 var id = name + ':' + node.loc.start.line + ':' + node.loc.start.column;
96 * Store a React component list
105 * Find a component in the list by his node or one of his child node
106 * @param {Object} context The current rule context.
107 * @param {ASTNode} node The node to find.
108 * @returns {Object|null} The component if it is found, null if not.
110 List.prototype.getByNode = function(context, node) {
111 var componentNode = getNode(context, node);
112 if (!componentNode) {
115 var identifiers = getIdentifiers(componentNode);
117 return this._list[identifiers.id] || null;
121 * Find a component in the list by his name
122 * @param {String} name Name of the component to find.
123 * @returns {Object|null} The component if it is found, null if not.
125 List.prototype.getByName = function(name) {
126 for (var component in this._list) {
127 if (this._list.hasOwnProperty(component) && this._list[component].name === name) {
128 return this._list[component];
135 * Return the component list
136 * @returns {Object} The component list.
138 List.prototype.getList = function() {
143 * Add/update a component in the list
144 * @param {Object} context The current rule context.
145 * @param {ASTNode} node The node to add.
146 * @param {Object} customProperties Additional properties to add to the component.
147 * @returns {Object} The added component.
149 List.prototype.set = function(context, node, customProperties) {
150 var componentNode = getNode(context, node);
151 if (!componentNode) {
154 var identifiers = getIdentifiers(componentNode);
156 var component = util._extend({
157 name: identifiers.name,
159 }, customProperties || {});
161 if (!this._list[identifiers.id]) {
165 this._list[identifiers.id] = util._extend(this._list[identifiers.id] || {}, component);
171 * Remove a component from the list
172 * @param {Object} context The current rule context.
173 * @param {ASTNode} node The node to remove.
175 List.prototype.remove = function(context, node) {
176 var componentNode = getNode(context, node);
177 if (!componentNode) {
180 var identifiers = getIdentifiers(componentNode);
182 if (!this._list[identifiers.id]) {
186 delete this._list[identifiers.id];
193 * Return the component list length
194 * @returns {Number} The component list length.
196 List.prototype.count = function() {
201 DEFAULT_COMPONENT_NAME: DEFAULT_COMPONENT_NAME,
202 isReactComponent: isReactComponent,
204 isComponentDefinition: isComponentDefinition,
205 getIdentifiers: getIdentifiers,