1 | var test = require('tape')
|
2 | var parse = require('acorn').parse
|
3 | var scan = require('../')
|
4 |
|
5 | function crawl (src, opts) {
|
6 | var ast = parse(src, opts)
|
7 | scan.crawl(ast)
|
8 | return ast
|
9 | }
|
10 |
|
11 | test('register variable declarations in scope', function (t) {
|
12 | t.plan(5)
|
13 | var ast = crawl('var a, b; const c = 0; let d')
|
14 |
|
15 | var scope = scan.scope(ast)
|
16 | t.ok(scope.has('a'), 'should find var')
|
17 | t.ok(scope.has('b'), 'should find second declarator in var statement')
|
18 | t.ok(scope.has('c'), 'should find const')
|
19 | t.ok(scope.has('d'), 'should find let')
|
20 | t.notOk(scope.has('e'), 'nonexistent names should return false')
|
21 | })
|
22 |
|
23 | test('register variable declarations in block scope', function (t) {
|
24 | t.plan(4)
|
25 | var ast = crawl('var a, b; { let b; }')
|
26 | var scope = scan.scope(ast)
|
27 | t.ok(scope.has('a'))
|
28 | t.ok(scope.has('b'))
|
29 | scope = scan.scope(ast.body[1])
|
30 | t.ok(scope.has('b'), 'should declare `let` variable in BlockStatement scope')
|
31 | t.notOk(scope.has('a'), 'should only return true for names declared here')
|
32 | })
|
33 |
|
34 | test('register non variable declarations (function, class, parameter)', function (t) {
|
35 | t.plan(4)
|
36 | var ast = crawl('function a (b, a) {} class X {}')
|
37 | var scope = scan.scope(ast)
|
38 | t.ok(scope.has('a'), 'should find function declarations')
|
39 | t.ok(scope.has('X'), 'should find class definition')
|
40 | scope = scan.scope(ast.body[0])
|
41 | t.ok(scope.has('a'), 'should find shadowed parameter')
|
42 | t.ok(scope.has('b'), 'should find parameter')
|
43 | })
|
44 |
|
45 | test('shadowing', function (t) {
|
46 | t.plan(8)
|
47 | var ast = crawl(`
|
48 | var a
|
49 | { let a }
|
50 | function b (b) {
|
51 | var a
|
52 | }
|
53 | `)
|
54 | var root = scan.scope(ast)
|
55 | var block = scan.scope(ast.body[1])
|
56 | var fn = scan.scope(ast.body[2])
|
57 | t.ok(root.has('a'), 'should find global var')
|
58 | t.ok(root.has('b'), 'should find function declaration')
|
59 | t.ok(block.has('a'), 'should shadow vars using `let` in block scope')
|
60 | t.notEqual(block.getBinding('a'), root.getBinding('a'), 'shadowing should define different bindings')
|
61 | t.ok(fn.has('b'), 'should find function parameter')
|
62 | t.notEqual(fn.getBinding('b'), root.getBinding('b'), 'shadowing function name with parameter should define different bindings')
|
63 | t.ok(fn.has('a'), 'should find local var')
|
64 | t.notEqual(fn.getBinding('a'), root.getBinding('a'), 'shadowing vars in function scope should define different bindings')
|
65 | })
|
66 |
|
67 | test('references', function (t) {
|
68 | t.plan(5)
|
69 |
|
70 | var src = `
|
71 | var a = 0
|
72 | a++
|
73 | a++
|
74 | function b (b) {
|
75 | console.log(b(a))
|
76 | }
|
77 | b(function (b) { return a + b })
|
78 | `
|
79 | var ast = crawl(src)
|
80 |
|
81 | var root = scan.scope(ast)
|
82 | var fn = scan.scope(ast.body[3])
|
83 | var callback = scan.scope(ast.body[4].expression.arguments[0])
|
84 |
|
85 | var a = root.getBinding('a')
|
86 | t.equal(a.getReferences().length, 5, 'should collect references in same and nested scopes')
|
87 | var b = root.getBinding('b')
|
88 | t.equal(b.getReferences().length, 2, 'should collect references to function declaration')
|
89 | var b2 = fn.getBinding('b')
|
90 | t.equal(b2.getReferences().length, 2, 'should collect references to shadowed function parameter')
|
91 | var b3 = callback.getBinding('b')
|
92 | t.equal(b3.getReferences().length, 2, 'should collect references to shadowed function parameter')
|
93 |
|
94 |
|
95 | var result = src.split('')
|
96 | a.getReferences().forEach(function (ref) { result[ref.start] = 'x' })
|
97 | b.getReferences().forEach(function (ref) { result[ref.start] = 'y' })
|
98 | b2.getReferences().forEach(function (ref) { result[ref.start] = 'z' })
|
99 | b3.getReferences().forEach(function (ref) { result[ref.start] = 'w' })
|
100 | t.equal(result.join(''), `
|
101 | var x = 0
|
102 | x++
|
103 | x++
|
104 | function y (z) {
|
105 | console.log(z(x))
|
106 | }
|
107 | y(function (w) { return x + w })
|
108 | `, 'references were associated correctly')
|
109 | })
|
110 |
|
111 | test('do not count renamed imported identifiers as references', function (t) {
|
112 | t.plan(2)
|
113 |
|
114 | var src = `
|
115 | var a = 0
|
116 | a++
|
117 | a++
|
118 | import { a as b } from "b"
|
119 | b()
|
120 | `
|
121 | var ast = crawl(src, { sourceType: 'module' })
|
122 |
|
123 | var root = scan.scope(ast)
|
124 |
|
125 | var a = root.getBinding('a')
|
126 | var b = root.getBinding('b')
|
127 | t.equal(a.getReferences().length, 3, 'should not have counted renamed `a` import as a reference')
|
128 | t.equal(b.getReferences().length, 2, 'should have counted local name of renamed import')
|
129 | })
|
130 |
|
131 | test('remove references', function (t) {
|
132 | t.plan(6)
|
133 |
|
134 | var src = `
|
135 | function a () {}
|
136 | a()
|
137 | a()
|
138 | `
|
139 | var ast = crawl(src)
|
140 |
|
141 | var root = scan.scope(ast)
|
142 | var a = root.getBinding('a')
|
143 | t.equal(a.getReferences().length, 3, 'should have 3 references')
|
144 | t.ok(a.isReferenced(), 'should be referenced')
|
145 | var reference = ast.body[1].expression.callee
|
146 | a.remove(reference)
|
147 | t.equal(a.getReferences().length, 2, 'should have removed the reference')
|
148 | t.ok(a.isReferenced(), 'should still be referenced')
|
149 | var reference = ast.body[2].expression.callee
|
150 | a.remove(reference)
|
151 | t.equal(a.getReferences().length, 1, 'should still have the definition reference')
|
152 | t.notOk(a.isReferenced(), 'should no longer be referenced')
|
153 | })
|
154 |
|
155 | test('collect references to undeclared variables', function (t) {
|
156 | t.plan(2)
|
157 |
|
158 | var src = `
|
159 | var a = b
|
160 | b = a
|
161 | a(b)
|
162 | function c () {
|
163 | return d
|
164 | }
|
165 | `
|
166 | var ast = crawl(src)
|
167 |
|
168 | var root = scan.scope(ast)
|
169 | var undeclared = Array.from(root.undeclaredBindings.keys())
|
170 | var declared = Array.from(root.bindings.keys())
|
171 | t.deepEqual(undeclared, ['b', 'd'])
|
172 | t.deepEqual(declared, ['a', 'c'])
|
173 | })
|