-
-
Notifications
You must be signed in to change notification settings - Fork 296
/
resolveObjectKeysToArray.js
128 lines (113 loc) · 3.61 KB
/
resolveObjectKeysToArray.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import { ASTNode, NodePath, builders, namedTypes as t } from 'ast-types';
import resolveToValue from './resolveToValue';
function isObjectKeysCall(node: ASTNode): boolean {
return (
t.CallExpression.check(node) &&
node.arguments.length === 1 &&
t.MemberExpression.check(node.callee) &&
t.Identifier.check(node.callee.object) &&
node.callee.object.name === 'Object' &&
t.Identifier.check(node.callee.property) &&
node.callee.property.name === 'keys'
);
}
function isWhitelistedObjectProperty(prop) {
return (
(t.Property.check(prop) &&
((t.Identifier.check(prop.key) && !prop.computed) ||
t.Literal.check(prop.key))) ||
t.SpreadElement.check(prop)
);
}
function isWhiteListedObjectTypeProperty(prop) {
return (
t.ObjectTypeProperty.check(prop) ||
t.ObjectTypeSpreadProperty.check(prop) ||
t.TSPropertySignature.check(prop)
);
}
// Resolves an ObjectExpression or an ObjectTypeAnnotation
export function resolveObjectToNameArray(
object: NodePath,
raw: boolean = false,
): ?Array<string> {
if (
(t.ObjectExpression.check(object.value) &&
object.value.properties.every(isWhitelistedObjectProperty)) ||
(t.ObjectTypeAnnotation.check(object.value) &&
object.value.properties.every(isWhiteListedObjectTypeProperty)) ||
(t.TSTypeLiteral.check(object.value) &&
object.value.members.every(isWhiteListedObjectTypeProperty))
) {
let values = [];
let error = false;
const properties = t.TSTypeLiteral.check(object.value)
? object.get('members')
: object.get('properties');
properties.each(propPath => {
if (error) return;
const prop = propPath.value;
if (
t.Property.check(prop) ||
t.ObjectTypeProperty.check(prop) ||
t.TSPropertySignature.check(prop)
) {
// Key is either Identifier or Literal
const name = prop.key.name || (raw ? prop.key.raw : prop.key.value);
values.push(name);
} else if (
t.SpreadElement.check(prop) ||
t.ObjectTypeSpreadProperty.check(prop)
) {
let spreadObject = resolveToValue(propPath.get('argument'));
if (t.GenericTypeAnnotation.check(spreadObject.value)) {
const typeAlias = resolveToValue(spreadObject.get('id'));
if (t.ObjectTypeAnnotation.check(typeAlias.get('right').value)) {
spreadObject = resolveToValue(typeAlias.get('right'));
}
}
const spreadValues = resolveObjectToNameArray(spreadObject);
if (!spreadValues) {
error = true;
return;
}
values = [...values, ...spreadValues];
}
});
if (!error) {
return values;
}
}
return null;
}
/**
* Returns an ArrayExpression which contains all the keys resolved from an object
*
* Ignores setters in objects
*
* Returns null in case of
* unresolvable spreads
* computed identifier keys
*/
export default function resolveObjectKeysToArray(path: NodePath): ?NodePath {
const node = path.node;
if (isObjectKeysCall(node)) {
const objectExpression = resolveToValue(path.get('arguments').get(0));
const values = resolveObjectToNameArray(objectExpression);
if (values) {
const nodes = values
.filter((value, index, array) => array.indexOf(value) === index)
.map(value => builders.literal(value));
return new NodePath(builders.arrayExpression(nodes));
}
}
return null;
}