-
Notifications
You must be signed in to change notification settings - Fork 0
/
functionsVisitor.ts
97 lines (83 loc) · 2.89 KB
/
functionsVisitor.ts
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
import {PluginItem} from '@babel/core';
import {Graph} from './graph/graph';
import {CapturedGlobals, singleFunctionVisitor} from './singleFunctionVisitor';
import {encodeNode} from './utils/node';
import {
isFunctionDeclaration,
isIdentifier,
isVariableDeclaration,
} from './utils/typeGuards';
const isDefined = <T>(v: T | null | undefined): v is T => v != null;
export interface SharedObj {
globalVars: string[];
topLevelFunctions: string[];
callGraph: Graph<string>;
mutationGraph: Graph<string>;
}
export const functionsVisitor = (sharedObj: SharedObj) => (): PluginItem => {
return {
visitor: {
Program(path) {
sharedObj.globalVars = path.node.body
.filter(isVariableDeclaration)
.map((node) => node.declarations[0].id)
.filter(isIdentifier)
.map((id) => id.name);
sharedObj.topLevelFunctions = path.node.body
.filter(isFunctionDeclaration)
.map((node) => node?.id?.name)
.filter(isDefined);
},
FunctionDeclaration(path) {
const functionName = path.node.id?.name;
if (!functionName) {
// TODO: log that there is a function declaration with no name
return;
}
const isTopLevelFunction = path.parent.type === 'Program';
if (!isTopLevelFunction) {
// Only do top level functions.
// Skip functions declared within functions
return;
}
const functionInformation: CapturedGlobals = {
read: [], // global vars written to
write: [], // global vars read from
functions: [], // function calls
functionName, // name of the current function under inspection
topLevelFunctions: new Set(sharedObj.topLevelFunctions),
};
path.traverse(singleFunctionVisitor, {functionInformation});
sharedObj.callGraph.addEdges(
functionName,
intersection(
functionInformation.functions,
sharedObj.topLevelFunctions,
),
);
sharedObj.mutationGraph.addEdges(
encodeNode({type: 'function', value: functionName}),
intersection(functionInformation.write, sharedObj.globalVars).map(
(value) => encodeNode({type: 'variable', value}),
),
);
const readVars = intersection(
functionInformation.read,
sharedObj.globalVars,
);
readVars.forEach((readVar) => {
sharedObj.mutationGraph.addEdge(
encodeNode({type: 'variable', value: readVar}),
encodeNode({type: 'function', value: functionName}),
);
});
},
},
};
};
function difference<T>(arrayA: Array<T>, arrayB: Array<T>) {
return arrayA.filter((item) => !arrayB.includes(item));
}
function intersection<T>(arrayA: Array<T>, arrayB: Array<T>) {
return arrayA.filter((item) => arrayB.includes(item));
}