-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
150 lines (115 loc) · 3.54 KB
/
index.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
const DIRECTIVE = 'use fc'
const LEFTPIPE = '>>'
const RIGHTPIPE = '<<'
const TRIPLEPIPE = '>>>'
export default function ({ types: t }) {
const id = name => t.identifier(name)
function pipe (fnName, ...args) {
return t.callExpression(id(fnName), args)
}
function importAs (name, as, from) {
return t.importDeclaration([
t.importSpecifier(t.identifier(name), t.identifier(as))
], t.stringLiteral(from))
}
let fileHasDirective = false
const visitor = {
Program: {
enter (path, { opts }) {
fileHasDirective = false
},
exit (path, opts) {
const { name = 'pipe', as = 'pipe', from } = opts || {}
if (fileHasDirective && from && !path.scope.hasBinding(as)) {
path.unshiftContainer('body', importAs(name, as, from))
}
}
},
CallExpression: {
/* transform multiple pipe calls into as few pipe calls as possible */
exit (path, { opts }) {
if (!hasDirective(path)) return
fileHasDirective = true
const { as = 'pipe' } = opts || {}
if (isPipeCall(as, path.node)) {
const [left, right] = path.node.arguments
if (!left || !right) return
if (isPipeCall(as, left)) {
path.replaceWith(pipe(as, ...left.arguments, right))
}
}
}
},
BinaryExpression: {
/* transform >> and << into calls to pipe() */
enter (path, { opts }) {
if (!hasDirective(path)) return
fileHasDirective = true
const { as = 'pipe' } = opts || {}
if (isPipe(path)) {
const [left, right] = side(path)
if (!left.node || !right.node) return
path.replaceWith(pipe(as, left.node, right.node))
}
if (isAntiPipe(path)) {
const [left, right] = side(path)
if (!left.node || !right.node) return
path.replaceWith(pipe(as, right.node, left.node))
}
if (isTriplePipe(path)) {
const [ left, right ] = side(path)
if (!left.node || !right.node) return
if (t.isIdentifier(right)) {
path.replaceWith(pipe(right.node.name, left.node))
}
if (t.isCallExpression(right)) {
path.replaceWith(pipe(right.node.callee.name,
...right.node.arguments, left.node))
}
}
}
}
}
return { visitor }
}
/* check to see if scope has directive "use pipe" */
function hasDirective (path) {
let matched = false
if (!path || typeof path.findParent !== 'function') return matched
path.findParent((parent) => {
parent.node.directives && parent.node.directives.some(({ value }) => {
matched = value.value === DIRECTIVE
if (!matched) {
return hasDirective(parent)
}
})
})
return matched
}
/* check if an ast node is a function call pipe */
function isPipeCall (as, node) {
return node.callee && node.callee.name === as
}
/* checks if an BinaryExpression is an pipe call */
function isPipe (path) {
return path.node.operator === LEFTPIPE
}
/* checks if an BinaryExpression is a right pipe call */
function isAntiPipe (path) {
return path.node.operator === RIGHTPIPE
}
function isTriplePipe (path) {
return path.node.operator === TRIPLEPIPE
}
/* gets the nodes to the left and right of a path */
function side (path) {
return [ leftOf(path), rightOf(path) ]
}
/* gets the path left of a given path */
function leftOf (path) {
return path.get('left')
}
/* get the path right of a given path */
function rightOf (path) {
return path.get('right')
}