forked from reactjs/react-docgen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgetMemberExpressionValuePath.ts
More file actions
130 lines (106 loc) · 3.18 KB
/
getMemberExpressionValuePath.ts
File metadata and controls
130 lines (106 loc) · 3.18 KB
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
129
130
import type { NodePath } from '@babel/traverse';
import { visitors } from '@babel/traverse';
import type { Expression } from '@babel/types';
import getNameOrValue from './getNameOrValue.js';
import { String as toString } from './expressionTo.js';
import isReactForwardRefCall from './isReactForwardRefCall.js';
function resolveName(path: NodePath): string | undefined {
if (path.isVariableDeclaration()) {
const declarations = path.get('declarations');
if (declarations.length > 1) {
throw new TypeError(
'Got unsupported VariableDeclaration. VariableDeclaration must only ' +
'have a single VariableDeclarator. Got ' +
declarations.length +
' declarations.',
);
}
// VariableDeclarator always has at least one declaration, hence the non-null-assertion
const id = declarations[0]!.get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
if (path.isFunctionDeclaration()) {
const id = path.get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
// When we check ObjectMethod we simply ignore it as assigning
// to it is technicaly possible but rare
if (path.isObjectMethod()) {
return;
}
if (
path.isFunctionExpression() ||
path.isArrowFunctionExpression() ||
path.isTaggedTemplateExpression() ||
path.isCallExpression() ||
isReactForwardRefCall(path)
) {
let currentPath: NodePath = path;
while (currentPath.parentPath) {
if (currentPath.parentPath.isVariableDeclarator()) {
const id = currentPath.parentPath.get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
currentPath = currentPath.parentPath;
}
return;
}
throw new TypeError(
'Attempted to resolveName for an unsupported path. resolveName does not accept ' +
path.node.type +
'".',
);
}
interface TraverseState {
readonly memberName: string;
readonly localName: string;
result: NodePath<Expression> | null;
}
const explodedVisitors = visitors.explode<TraverseState>({
AssignmentExpression: {
enter: function (path, state) {
const memberPath = path.get('left');
if (!memberPath.isMemberExpression()) {
return;
}
const property = memberPath.get('property');
if (
((!memberPath.node.computed && property.isIdentifier()) ||
property.isStringLiteral() ||
property.isNumericLiteral()) &&
getNameOrValue(property) === state.memberName &&
toString(memberPath.get('object')) === state.localName
) {
state.result = path.get('right');
path.stop();
}
},
},
});
export default function getMemberExpressionValuePath(
variableDefinition: NodePath,
memberName: string,
): NodePath<Expression> | null {
const localName = resolveName(variableDefinition);
if (!localName) {
// likely an immediately exported and therefore nameless/anonymous node
// passed in
return null;
}
const state: TraverseState = {
localName,
memberName,
result: null,
};
variableDefinition.hub.file.traverse(explodedVisitors, state);
return state.result;
}