Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

3 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /**
  2. * @fileoverview Prevent missing displayName in a React component definition
  3. * @author Yannick Croissant
  4. */
  5. 'use strict';
  6. const has = require('has');
  7. const Components = require('../util/Components');
  8. const astUtil = require('../util/ast');
  9. const docsUrl = require('../util/docsUrl');
  10. // ------------------------------------------------------------------------------
  11. // Rule Definition
  12. // ------------------------------------------------------------------------------
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: 'Prevent missing displayName in a React component definition',
  17. category: 'Best Practices',
  18. recommended: true,
  19. url: docsUrl('display-name')
  20. },
  21. schema: [{
  22. type: 'object',
  23. properties: {
  24. ignoreTranspilerName: {
  25. type: 'boolean'
  26. }
  27. },
  28. additionalProperties: false
  29. }]
  30. },
  31. create: Components.detect((context, components, utils) => {
  32. const config = context.options[0] || {};
  33. const ignoreTranspilerName = config.ignoreTranspilerName || false;
  34. const MISSING_MESSAGE = 'Component definition is missing display name';
  35. /**
  36. * Checks if we are declaring a display name
  37. * @param {ASTNode} node The AST node being checked.
  38. * @returns {Boolean} True if we are declaring a display name, false if not.
  39. */
  40. function isDisplayNameDeclaration(node) {
  41. switch (node.type) {
  42. case 'ClassProperty':
  43. return node.key && node.key.name === 'displayName';
  44. case 'Identifier':
  45. return node.name === 'displayName';
  46. case 'Literal':
  47. return node.value === 'displayName';
  48. default:
  49. return false;
  50. }
  51. }
  52. /**
  53. * Mark a prop type as declared
  54. * @param {ASTNode} node The AST node being checked.
  55. */
  56. function markDisplayNameAsDeclared(node) {
  57. components.set(node, {
  58. hasDisplayName: true
  59. });
  60. }
  61. /**
  62. * Reports missing display name for a given component
  63. * @param {Object} component The component to process
  64. */
  65. function reportMissingDisplayName(component) {
  66. context.report({
  67. node: component.node,
  68. message: MISSING_MESSAGE,
  69. data: {
  70. component: component.name
  71. }
  72. });
  73. }
  74. /**
  75. * Checks if the component have a name set by the transpiler
  76. * @param {ASTNode} node The AST node being checked.
  77. * @returns {Boolean} True if component has a name, false if not.
  78. */
  79. function hasTranspilerName(node) {
  80. const namedObjectAssignment = (
  81. node.type === 'ObjectExpression' &&
  82. node.parent &&
  83. node.parent.parent &&
  84. node.parent.parent.type === 'AssignmentExpression' &&
  85. (
  86. !node.parent.parent.left.object ||
  87. node.parent.parent.left.object.name !== 'module' ||
  88. node.parent.parent.left.property.name !== 'exports'
  89. )
  90. );
  91. const namedObjectDeclaration = (
  92. node.type === 'ObjectExpression' &&
  93. node.parent &&
  94. node.parent.parent &&
  95. node.parent.parent.type === 'VariableDeclarator'
  96. );
  97. const namedClass = (
  98. (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') &&
  99. node.id &&
  100. node.id.name
  101. );
  102. const namedFunctionDeclaration = (
  103. (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
  104. node.id &&
  105. node.id.name
  106. );
  107. const namedFunctionExpression = (
  108. astUtil.isFunctionLikeExpression(node) &&
  109. node.parent &&
  110. (node.parent.type === 'VariableDeclarator' || node.parent.method === true) &&
  111. (!node.parent.parent || !utils.isES5Component(node.parent.parent))
  112. );
  113. if (
  114. namedObjectAssignment || namedObjectDeclaration ||
  115. namedClass ||
  116. namedFunctionDeclaration || namedFunctionExpression
  117. ) {
  118. return true;
  119. }
  120. return false;
  121. }
  122. // --------------------------------------------------------------------------
  123. // Public
  124. // --------------------------------------------------------------------------
  125. return {
  126. ClassProperty: function(node) {
  127. if (!isDisplayNameDeclaration(node)) {
  128. return;
  129. }
  130. markDisplayNameAsDeclared(node);
  131. },
  132. MemberExpression: function(node) {
  133. if (!isDisplayNameDeclaration(node.property)) {
  134. return;
  135. }
  136. const component = utils.getRelatedComponent(node);
  137. if (!component) {
  138. return;
  139. }
  140. markDisplayNameAsDeclared(component.node);
  141. },
  142. FunctionExpression: function(node) {
  143. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  144. return;
  145. }
  146. markDisplayNameAsDeclared(node);
  147. },
  148. FunctionDeclaration: function(node) {
  149. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  150. return;
  151. }
  152. markDisplayNameAsDeclared(node);
  153. },
  154. ArrowFunctionExpression: function(node) {
  155. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  156. return;
  157. }
  158. markDisplayNameAsDeclared(node);
  159. },
  160. MethodDefinition: function(node) {
  161. if (!isDisplayNameDeclaration(node.key)) {
  162. return;
  163. }
  164. markDisplayNameAsDeclared(node);
  165. },
  166. ClassExpression: function(node) {
  167. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  168. return;
  169. }
  170. markDisplayNameAsDeclared(node);
  171. },
  172. ClassDeclaration: function(node) {
  173. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  174. return;
  175. }
  176. markDisplayNameAsDeclared(node);
  177. },
  178. ObjectExpression: function(node) {
  179. if (ignoreTranspilerName || !hasTranspilerName(node)) {
  180. // Search for the displayName declaration
  181. node.properties.forEach(property => {
  182. if (!property.key || !isDisplayNameDeclaration(property.key)) {
  183. return;
  184. }
  185. markDisplayNameAsDeclared(node);
  186. });
  187. return;
  188. }
  189. markDisplayNameAsDeclared(node);
  190. },
  191. 'Program:exit': function() {
  192. const list = components.list();
  193. // Report missing display name for all components
  194. for (const component in list) {
  195. if (!has(list, component) || list[component].hasDisplayName) {
  196. continue;
  197. }
  198. reportMissingDisplayName(list[component]);
  199. }
  200. }
  201. };
  202. })
  203. };