style-prop-object.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. /**
  2. * @fileoverview Enforce style prop value is an object
  3. * @author David Petersen
  4. */
  5. 'use strict';
  6. const variableUtil = require('../util/variable');
  7. const docsUrl = require('../util/docsUrl');
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. module.exports = {
  12. meta: {
  13. docs: {
  14. description: 'Enforce style prop value is an object',
  15. category: 'Possible Errors',
  16. recommended: false,
  17. url: docsUrl('style-prop-object')
  18. },
  19. messages: {
  20. stylePropNotObject: 'Style prop value must be an object'
  21. },
  22. schema: [
  23. {
  24. type: 'object',
  25. properties: {
  26. allow: {
  27. type: 'array',
  28. items: {
  29. type: 'string'
  30. },
  31. additionalItems: false,
  32. uniqueItems: true
  33. }
  34. }
  35. }
  36. ]
  37. },
  38. create(context) {
  39. const allowed = new Set(((context.options.length > 0) && context.options[0].allow) || []);
  40. /**
  41. * @param {ASTNode} expression An Identifier node
  42. * @returns {boolean}
  43. */
  44. function isNonNullaryLiteral(expression) {
  45. return expression.type === 'Literal' && expression.value !== null;
  46. }
  47. /**
  48. * @param {object} node A Identifier node
  49. */
  50. function checkIdentifiers(node) {
  51. const variable = variableUtil.variablesInScope(context).find((item) => item.name === node.name);
  52. if (!variable || !variable.defs[0] || !variable.defs[0].node.init) {
  53. return;
  54. }
  55. if (isNonNullaryLiteral(variable.defs[0].node.init)) {
  56. context.report({
  57. node,
  58. messageId: 'stylePropNotObject'
  59. });
  60. }
  61. }
  62. return {
  63. CallExpression(node) {
  64. if (
  65. node.callee
  66. && node.callee.type === 'MemberExpression'
  67. && node.callee.property.name === 'createElement'
  68. && node.arguments.length > 1
  69. ) {
  70. if (node.arguments[0].name) {
  71. // store name of component
  72. const componentName = node.arguments[0].name;
  73. // allowed list contains the name
  74. if (allowed.has(componentName)) {
  75. // abort operation
  76. return;
  77. }
  78. }
  79. if (node.arguments[1].type === 'ObjectExpression') {
  80. const style = node.arguments[1].properties.find((property) => property.key && property.key.name === 'style' && !property.computed);
  81. if (style) {
  82. if (style.value.type === 'Identifier') {
  83. checkIdentifiers(style.value);
  84. } else if (isNonNullaryLiteral(style.value)) {
  85. context.report({
  86. node: style.value,
  87. messageId: 'stylePropNotObject'
  88. });
  89. }
  90. }
  91. }
  92. }
  93. },
  94. JSXAttribute(node) {
  95. if (!node.value || node.name.name !== 'style') {
  96. return;
  97. }
  98. // store parent element
  99. const parentElement = node.parent;
  100. // parent element is a JSXOpeningElement
  101. if (parentElement && parentElement.type === 'JSXOpeningElement') {
  102. // get the name of the JSX element
  103. const name = parentElement.name && parentElement.name.name;
  104. // allowed list contains the name
  105. if (allowed.has(name)) {
  106. // abort operation
  107. return;
  108. }
  109. }
  110. if (node.value.type !== 'JSXExpressionContainer' || isNonNullaryLiteral(node.value.expression)) {
  111. context.report({
  112. node,
  113. messageId: 'stylePropNotObject'
  114. });
  115. } else if (node.value.expression.type === 'Identifier') {
  116. checkIdentifiers(node.value.expression);
  117. }
  118. }
  119. };
  120. }
  121. };