8b55b69fea889f39ea57c94cb989ef950bfa081e
[platform/framework/web/crosswalk-tizen.git] /
1 /**
2  * @fileoverview Utility functions for React components detection
3  * @author Yannick Croissant
4  */
5 'use strict';
6
7 var util = require('util');
8
9 var DEFAULT_COMPONENT_NAME = 'eslintReactComponent';
10
11 /**
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.
18  */
19 function isReactComponent(context, node) {
20   if (node.type !== 'ReturnStatement') {
21     throw new Error('React Component detection must be done from a ReturnStatement ASTNode');
22   }
23
24   var scope = context.getScope();
25   var isComponentRender =
26     node.argument &&
27     node.argument.type === 'JSXElement' &&
28     scope.block.parent.key && scope.block.parent.key.name === 'render'
29   ;
30   var isEmptyComponentRender =
31     node.argument &&
32     node.argument.type === 'Literal' && (node.argument.value === null || node.argument.value === false) &&
33     scope.block.parent.key && scope.block.parent.key.name === 'render'
34   ;
35
36   return Boolean(isEmptyComponentRender || isComponentRender);
37 }
38
39 /**
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.
43  */
44 function isComponentDefinition(node) {
45   var isES6Component = node.type === 'ClassDeclaration';
46   var isES5Component = Boolean(
47     node.type === 'ObjectExpression' &&
48     node.parent &&
49     node.parent.callee &&
50     node.parent.callee.object &&
51     node.parent.callee.property &&
52     node.parent.callee.object.name === 'React' &&
53     node.parent.callee.property.name === 'createClass'
54   );
55   return isES5Component || isES6Component;
56 }
57
58 /**
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.
63  */
64 function getNode(context, node) {
65   var componentNode = null;
66   var ancestors = context.getAncestors().reverse();
67
68   ancestors.unshift(node);
69
70   for (var i = 0, j = ancestors.length; i < j; i++) {
71     if (isComponentDefinition(ancestors[i])) {
72       componentNode = ancestors[i];
73       break;
74     }
75   }
76
77   return componentNode;
78 }
79
80 /**
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.
84  */
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;
88
89   return {
90     id: id,
91     name: name
92   };
93 }
94
95 /**
96  * Store a React component list
97  * @constructor
98  */
99 function List() {
100   this._list = {};
101   this._length = 0;
102 }
103
104 /**
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.
109  */
110 List.prototype.getByNode = function(context, node) {
111   var componentNode = getNode(context, node);
112   if (!componentNode) {
113     return null;
114   }
115   var identifiers = getIdentifiers(componentNode);
116
117   return this._list[identifiers.id] || null;
118 };
119
120 /**
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.
124  */
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];
129     }
130   }
131   return null;
132 };
133
134 /**
135  * Return the component list
136  * @returns {Object} The component list.
137  */
138 List.prototype.getList = function() {
139   return this._list;
140 };
141
142 /**
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.
148  */
149 List.prototype.set = function(context, node, customProperties) {
150   var componentNode = getNode(context, node);
151   if (!componentNode) {
152     return null;
153   }
154   var identifiers = getIdentifiers(componentNode);
155
156   var component = util._extend({
157     name: identifiers.name,
158     node: componentNode
159   }, customProperties || {});
160
161   if (!this._list[identifiers.id]) {
162     this._length++;
163   }
164
165   this._list[identifiers.id] = util._extend(this._list[identifiers.id] || {}, component);
166
167   return component;
168 };
169
170 /**
171  * Remove a component from the list
172  * @param {Object} context The current rule context.
173  * @param {ASTNode} node The node to remove.
174  */
175 List.prototype.remove = function(context, node) {
176   var componentNode = getNode(context, node);
177   if (!componentNode) {
178     return null;
179   }
180   var identifiers = getIdentifiers(componentNode);
181
182   if (!this._list[identifiers.id]) {
183     return null;
184   }
185
186   delete this._list[identifiers.id];
187   this._length--;
188
189   return null;
190 };
191
192 /**
193  * Return the component list length
194  * @returns {Number} The component list length.
195  */
196 List.prototype.count = function() {
197   return this._length;
198 };
199
200 module.exports = {
201   DEFAULT_COMPONENT_NAME: DEFAULT_COMPONENT_NAME,
202   isReactComponent: isReactComponent,
203   getNode: getNode,
204   isComponentDefinition: isComponentDefinition,
205   getIdentifiers: getIdentifiers,
206   List: List
207 };