style-prop-object.js 3.8 KB

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