UNPKG

5.4 kBJavaScriptView Raw
1var test = require('tape')
2var parse = require('acorn').parse
3var scan = require('../')
4
5function crawl (src, opts) {
6 var ast = parse(src, opts)
7 scan.crawl(ast)
8 return ast
9}
10
11test('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
23test('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
34test('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]) // function declaration
41 t.ok(scope.has('a'), 'should find shadowed parameter')
42 t.ok(scope.has('b'), 'should find parameter')
43})
44
45test('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
67test('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 // try to rewrite some things
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
111test('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
131test('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
155test('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})