resolveObjectKeysToArray.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import resolveToValue from './resolveToValue.js';
  2. function isObjectKeysCall(path) {
  3. if (!path.isCallExpression() || path.get('arguments').length !== 1) {
  4. return false;
  5. }
  6. const callee = path.get('callee');
  7. if (!callee.isMemberExpression()) {
  8. return false;
  9. }
  10. const object = callee.get('object');
  11. const property = callee.get('property');
  12. return (object.isIdentifier({ name: 'Object' }) &&
  13. property.isIdentifier({ name: 'keys' }));
  14. }
  15. function isWhitelistedObjectProperty(path) {
  16. if (path.isSpreadElement())
  17. return true;
  18. if (path.isObjectProperty() ||
  19. (path.isObjectMethod() &&
  20. (path.node.kind === 'get' || path.node.kind === 'set'))) {
  21. const key = path.get('key');
  22. return ((key.isIdentifier() && !path.node.computed) ||
  23. key.isStringLiteral() ||
  24. key.isNumericLiteral());
  25. }
  26. return false;
  27. }
  28. function isWhiteListedObjectTypeProperty(path) {
  29. return (path.isObjectTypeProperty() ||
  30. path.isObjectTypeSpreadProperty() ||
  31. path.isTSPropertySignature());
  32. }
  33. // Resolves an ObjectExpression or an ObjectTypeAnnotation
  34. export function resolveObjectToNameArray(objectPath, raw = false) {
  35. if ((objectPath.isObjectExpression() &&
  36. objectPath.get('properties').every(isWhitelistedObjectProperty)) ||
  37. (objectPath.isObjectTypeAnnotation() &&
  38. objectPath.get('properties').every(isWhiteListedObjectTypeProperty)) ||
  39. (objectPath.isTSTypeLiteral() &&
  40. objectPath.get('members').every(isWhiteListedObjectTypeProperty))) {
  41. let values = [];
  42. let error = false;
  43. const properties = objectPath.isTSTypeLiteral()
  44. ? objectPath.get('members')
  45. : objectPath.get('properties');
  46. properties.forEach((propPath) => {
  47. if (error)
  48. return;
  49. if (propPath.isObjectProperty() ||
  50. propPath.isObjectMethod() ||
  51. propPath.isObjectTypeProperty() ||
  52. propPath.isTSPropertySignature()) {
  53. const key = propPath.get('key');
  54. // Key is either Identifier or Literal
  55. const name = key.isIdentifier()
  56. ? key.node.name
  57. : raw
  58. ? key.node.extra?.raw
  59. : `${key.node.value}`;
  60. values.push(name);
  61. }
  62. else if (propPath.isSpreadElement() ||
  63. propPath.isObjectTypeSpreadProperty()) {
  64. let spreadObject = resolveToValue(propPath.get('argument'));
  65. if (spreadObject.isGenericTypeAnnotation()) {
  66. const typeAliasRight = resolveToValue(spreadObject.get('id')).get('right');
  67. if (typeAliasRight.isObjectTypeAnnotation()) {
  68. spreadObject = resolveToValue(typeAliasRight);
  69. }
  70. }
  71. const spreadValues = resolveObjectToNameArray(spreadObject);
  72. if (!spreadValues) {
  73. error = true;
  74. return;
  75. }
  76. values = [...values, ...spreadValues];
  77. }
  78. });
  79. if (!error) {
  80. return values;
  81. }
  82. }
  83. return null;
  84. }
  85. /**
  86. * Returns an ArrayExpression which contains all the keys resolved from an object
  87. *
  88. * Ignores setters in objects
  89. *
  90. * Returns null in case of
  91. * unresolvable spreads
  92. * computed identifier keys
  93. */
  94. export default function resolveObjectKeysToArray(path) {
  95. if (isObjectKeysCall(path)) {
  96. const argument = path.get('arguments')[0];
  97. const objectExpression = resolveToValue(
  98. // isObjectKeysCall already asserts that there is at least one argument, hence the non-null-assertion
  99. argument);
  100. const values = resolveObjectToNameArray(objectExpression);
  101. if (values) {
  102. const nodes = values
  103. //filter duplicates
  104. .filter((value, index, array) => array.indexOf(value) === index)
  105. .map((value) => `"${value}"`);
  106. return nodes;
  107. }
  108. }
  109. return null;
  110. }