UNPKG

10.1 kBJavaScriptView Raw
1var test = require('tape')
2var parse = require('acorn').parse
3var ArrayFrom = require('array-from')
4var scan = require('../')
5
6function crawl (src, opts) {
7 var ast = parse(src, opts)
8 scan.crawl(ast)
9 return ast
10}
11
12function cloneNode (node) {
13 var cloned = {}
14 var keys = Object.keys(node)
15 for (var i = 0; i < keys.length; i++) {
16 cloned[keys[i]] = node[keys[i]]
17 }
18 return cloned
19}
20
21test('register variable declarations in scope', function (t) {
22 t.plan(5)
23 var ast = crawl('var a, b; const c = 0; let d')
24
25 var scope = scan.scope(ast)
26 t.ok(scope.has('a'), 'should find var')
27 t.ok(scope.has('b'), 'should find second declarator in var statement')
28 t.ok(scope.has('c'), 'should find const')
29 t.ok(scope.has('d'), 'should find let')
30 t.notOk(scope.has('e'), 'nonexistent names should return false')
31})
32
33test('register variable declarations in block scope', function (t) {
34 t.plan(4)
35 var ast = crawl('var a, b; { let b; }')
36 var scope = scan.scope(ast)
37 t.ok(scope.has('a'))
38 t.ok(scope.has('b'))
39 scope = scan.scope(ast.body[1])
40 t.ok(scope.has('b'), 'should declare `let` variable in BlockStatement scope')
41 t.notOk(scope.has('a'), 'should only return true for names declared here')
42})
43
44test('register non variable declarations (function, class, parameter)', function (t) {
45 t.plan(4)
46 var ast = crawl('function a (b, a) {} class X {}')
47 var scope = scan.scope(ast)
48 t.ok(scope.has('a'), 'should find function declarations')
49 t.ok(scope.has('X'), 'should find class definition')
50 scope = scan.scope(ast.body[0]) // function declaration
51 t.ok(scope.has('a'), 'should find shadowed parameter')
52 t.ok(scope.has('b'), 'should find parameter')
53})
54
55test('use the value portion of a shorthand declaration property', function (t) {
56 t.plan(2)
57
58 var ast = parse('const { x } = y')
59 var property = ast.body[0].declarations[0].id.properties[0]
60 property.key = cloneNode(property.value)
61 scan.crawl(ast)
62
63 var binding = scan.scope(ast).getBinding('x')
64
65 t.ok(binding.references.has(property.value))
66 t.notOk(binding.references.has(property.key))
67})
68
69test('use the value portion of a shorthand object property', function (t) {
70 t.plan(2)
71
72 var ast = parse('({ x })')
73 var property = ast.body[0].expression.properties[0]
74 property.key = cloneNode(property.value)
75 scan.crawl(ast)
76
77 var binding = scan.scope(ast).undeclaredBindings.get('x')
78
79 t.ok(binding.references.has(property.value))
80 t.notOk(binding.references.has(property.key))
81})
82
83test('shadowing', function (t) {
84 t.plan(8)
85 var ast = crawl(`
86 var a
87 { let a }
88 function b (b) {
89 var a
90 }
91 `)
92 var root = scan.scope(ast)
93 var block = scan.scope(ast.body[1])
94 var fn = scan.scope(ast.body[2])
95 t.ok(root.has('a'), 'should find global var')
96 t.ok(root.has('b'), 'should find function declaration')
97 t.ok(block.has('a'), 'should shadow vars using `let` in block scope')
98 t.notEqual(block.getBinding('a'), root.getBinding('a'), 'shadowing should define different bindings')
99 t.ok(fn.has('b'), 'should find function parameter')
100 t.notEqual(fn.getBinding('b'), root.getBinding('b'), 'shadowing function name with parameter should define different bindings')
101 t.ok(fn.has('a'), 'should find local var')
102 t.notEqual(fn.getBinding('a'), root.getBinding('a'), 'shadowing vars in function scope should define different bindings')
103})
104
105test('references', function (t) {
106 t.plan(5)
107
108 var src = `
109 var a = 0
110 a++
111 a++
112 function b (b) {
113 console.log(b(a))
114 }
115 b(function (b) { return a + b })
116 `
117 var ast = crawl(src)
118
119 var root = scan.scope(ast)
120 var fn = scan.scope(ast.body[3])
121 var callback = scan.scope(ast.body[4].expression.arguments[0])
122
123 var a = root.getBinding('a')
124 t.equal(a.getReferences().length, 5, 'should collect references in same and nested scopes')
125 var b = root.getBinding('b')
126 t.equal(b.getReferences().length, 2, 'should collect references to function declaration')
127 var b2 = fn.getBinding('b')
128 t.equal(b2.getReferences().length, 2, 'should collect references to shadowed function parameter')
129 var b3 = callback.getBinding('b')
130 t.equal(b3.getReferences().length, 2, 'should collect references to shadowed function parameter')
131
132 // try to rewrite some things
133 var result = src.split('')
134 a.getReferences().forEach(function (ref) { result[ref.start] = 'x' })
135 b.getReferences().forEach(function (ref) { result[ref.start] = 'y' })
136 b2.getReferences().forEach(function (ref) { result[ref.start] = 'z' })
137 b3.getReferences().forEach(function (ref) { result[ref.start] = 'w' })
138 t.equal(result.join(''), `
139 var x = 0
140 x++
141 x++
142 function y (z) {
143 console.log(z(x))
144 }
145 y(function (w) { return x + w })
146 `, 'references were associated correctly')
147})
148
149test('references that are declared later', function (t) {
150 t.plan(4)
151
152 var src = `
153 if (true) { b(function () { c() }) }
154 function b () {}
155 function c () {}
156 `
157 var ast = crawl(src)
158
159 var scope = scan.scope(ast)
160 var b = scope.getBinding('b')
161 t.ok(b, 'should have a binding for function b(){}')
162 var c = scope.getBinding('c')
163 t.ok(c, 'should have a binding for function c(){}')
164 t.equal(b.getReferences().length, 2, 'should find all references for b')
165 t.equal(c.getReferences().length, 2, 'should find all references for c')
166})
167
168test('shorthand properties', function (t) {
169 t.plan(3)
170
171 var src = `
172 var b = 1
173 var a = { b }
174 var { c } = a
175 console.log({ c, b, a })
176 `
177 var ast = crawl(src)
178 var body = ast.body
179
180 var scope = scan.scope(ast)
181 var a = scope.getBinding('a')
182 var b = scope.getBinding('b')
183 var c = scope.getBinding('c')
184 t.deepEqual(a.getReferences(), [a.definition, body[2].declarations[0].init, body[3].expression.arguments[0].properties[2].value])
185 t.deepEqual(b.getReferences(), [b.definition, body[1].declarations[0].init.properties[0].value, body[3].expression.arguments[0].properties[1].value])
186 t.deepEqual(c.getReferences(), [c.definition, body[3].expression.arguments[0].properties[0].value])
187})
188
189test('do not count object keys and method definitions as references', function (t) {
190 t.plan(2)
191
192 var src = `
193 var a
194 class B { a () {} }
195 class C { get a () {} }
196 class D { set a (b) {} }
197 var e = { a: null }
198 `
199 var ast = crawl(src)
200
201 var scope = scan.scope(ast)
202 var a = scope.getBinding('a')
203 t.equal(a.getReferences().length, 1)
204 t.deepEqual(a.getReferences(), [a.definition])
205})
206
207test('do not count renamed imported identifiers as references', function (t) {
208 t.plan(2)
209
210 var src = `
211 var a = 0
212 a++
213 a++
214 import { a as b } from "b"
215 b()
216 `
217 var ast = crawl(src, { sourceType: 'module' })
218
219 var root = scan.scope(ast)
220
221 var a = root.getBinding('a')
222 var b = root.getBinding('b')
223 t.equal(a.getReferences().length, 3, 'should not have counted renamed `a` import as a reference')
224 t.equal(b.getReferences().length, 2, 'should have counted local name of renamed import')
225})
226
227test('remove references', function (t) {
228 t.plan(6)
229
230 var src = `
231 function a () {}
232 a()
233 a()
234 `
235 var ast = crawl(src)
236
237 var root = scan.scope(ast)
238 var a = root.getBinding('a')
239 t.equal(a.getReferences().length, 3, 'should have 3 references')
240 t.ok(a.isReferenced(), 'should be referenced')
241 var reference = ast.body[1].expression.callee
242 a.remove(reference)
243 t.equal(a.getReferences().length, 2, 'should have removed the reference')
244 t.ok(a.isReferenced(), 'should still be referenced')
245 reference = ast.body[2].expression.callee
246 a.remove(reference)
247 t.equal(a.getReferences().length, 1, 'should still have the definition reference')
248 t.notOk(a.isReferenced(), 'should no longer be referenced')
249})
250
251test('collect references to undeclared variables', function (t) {
252 t.plan(2)
253
254 var src = `
255 var a = b
256 b = a
257 a(b)
258 function c () {
259 return d
260 }
261 `
262 var ast = crawl(src)
263
264 var root = scan.scope(ast)
265 var undeclared = ArrayFrom(root.undeclaredBindings.keys())
266 var declared = ArrayFrom(root.bindings.keys())
267 t.deepEqual(undeclared, ['b', 'd'])
268 t.deepEqual(declared, ['a', 'c'])
269})
270
271test('loop over all available bindings, including declared in parent scope', function (t) {
272 t.plan(1)
273
274 var src = `
275 var a = 0
276 var b = 1, c = 2
277 function d() {
278 function e() {}
279 function f() {
280 var b = 3
281 console.log('bindings')
282 }
283 }
284 `
285
286 var ast = crawl(src)
287 var scope = scan.scope(ast.body[2].body.body[1])
288 var names = []
289 scope.forEachAvailable(function (binding, name) {
290 names.push(name)
291 })
292 t.deepEqual(names, ['b', 'e', 'f', 'a', 'c', 'd'])
293})
294
295test('always initialise a scope for the root', function (t) {
296 t.plan(2)
297
298 var src = `
299 console.log("null")
300 `
301
302 var ast = crawl(src)
303 var scope = scan.scope(ast)
304
305 t.ok(scope)
306 t.deepEqual(scope.getUndeclaredNames(), ['console'])
307})
308
309test('initialises a scope for catch clauses', function (t) {
310 t.plan(5)
311 var ast = crawl(`
312 var a = null
313 a = 1
314 try {
315 } catch (a) {
316 a = 2
317 }
318 `)
319
320 var scope = scan.scope(ast)
321 t.ok(scope.has('a'), 'should find var')
322 t.equal(scope.getBinding('a').getReferences().length, 2, 'only counts references to outer `a`')
323 var clause = ast.body[2].handler
324 var catchScope = scan.scope(clause)
325 t.ok(catchScope.has('a'), 'should find param')
326 t.notEqual(scope.getBinding('a'), catchScope.getBinding('a'), 'introduced a different binding')
327 t.equal(catchScope.getBinding('a').getReferences().length, 2, 'only counts references to inner `a`')
328})
329
330test('clear all scope information', function (t) {
331 t.plan(6)
332
333 var ast = crawl(`
334 function x() {
335 var y = z
336 }
337 var z = x
338 `)
339
340 var fn = ast.body[0]
341
342 t.ok(scan.scope(ast))
343 t.ok(scan.scope(fn))
344 t.ok(scan.getBinding(fn.id))
345
346 scan.clear(ast)
347
348 t.notOk(scan.scope(ast))
349 t.notOk(scan.scope(fn))
350 t.notOk(scan.getBinding(fn.id))
351})
352
353test('clear partial scope information', function (t) {
354 t.plan(4)
355
356 var ast = crawl('function x() {}')
357
358 var fn = ast.body[0]
359
360 t.ok(scan.scope(fn))
361 t.ok(scan.getBinding(fn.id))
362
363 scan.deleteScope(fn)
364
365 t.notOk(scan.scope(fn))
366 t.ok(scan.getBinding(fn.id))
367})