Show More
Commit Description:
add model solution
Commit Description:
add model solution
References:
File last commit:
Show/Diff file:
Action:
node_modules/scope-analyzer/test/index.js
| 367 lines
| 9.9 KiB
| application/javascript
| JavascriptLexer
|
r789 | var test = require('tape') | |||
var parse = require('acorn').parse | ||||
var ArrayFrom = require('array-from') | ||||
var scan = require('../') | ||||
function crawl (src, opts) { | ||||
var ast = parse(src, opts) | ||||
scan.crawl(ast) | ||||
return ast | ||||
} | ||||
function cloneNode (node) { | ||||
var cloned = {} | ||||
var keys = Object.keys(node) | ||||
for (var i = 0; i < keys.length; i++) { | ||||
cloned[keys[i]] = node[keys[i]] | ||||
} | ||||
return cloned | ||||
} | ||||
test('register variable declarations in scope', function (t) { | ||||
t.plan(5) | ||||
var ast = crawl('var a, b; const c = 0; let d') | ||||
var scope = scan.scope(ast) | ||||
t.ok(scope.has('a'), 'should find var') | ||||
t.ok(scope.has('b'), 'should find second declarator in var statement') | ||||
t.ok(scope.has('c'), 'should find const') | ||||
t.ok(scope.has('d'), 'should find let') | ||||
t.notOk(scope.has('e'), 'nonexistent names should return false') | ||||
}) | ||||
test('register variable declarations in block scope', function (t) { | ||||
t.plan(4) | ||||
var ast = crawl('var a, b; { let b; }') | ||||
var scope = scan.scope(ast) | ||||
t.ok(scope.has('a')) | ||||
t.ok(scope.has('b')) | ||||
scope = scan.scope(ast.body[1]) | ||||
t.ok(scope.has('b'), 'should declare `let` variable in BlockStatement scope') | ||||
t.notOk(scope.has('a'), 'should only return true for names declared here') | ||||
}) | ||||
test('register non variable declarations (function, class, parameter)', function (t) { | ||||
t.plan(4) | ||||
var ast = crawl('function a (b, a) {} class X {}') | ||||
var scope = scan.scope(ast) | ||||
t.ok(scope.has('a'), 'should find function declarations') | ||||
t.ok(scope.has('X'), 'should find class definition') | ||||
scope = scan.scope(ast.body[0]) // function declaration | ||||
t.ok(scope.has('a'), 'should find shadowed parameter') | ||||
t.ok(scope.has('b'), 'should find parameter') | ||||
}) | ||||
test('use the value portion of a shorthand declaration property', function (t) { | ||||
t.plan(2) | ||||
var ast = parse('const { x } = y') | ||||
var property = ast.body[0].declarations[0].id.properties[0] | ||||
property.key = cloneNode(property.value) | ||||
scan.crawl(ast) | ||||
var binding = scan.scope(ast).getBinding('x') | ||||
t.ok(binding.references.has(property.value)) | ||||
t.notOk(binding.references.has(property.key)) | ||||
}) | ||||
test('use the value portion of a shorthand object property', function (t) { | ||||
t.plan(2) | ||||
var ast = parse('({ x })') | ||||
var property = ast.body[0].expression.properties[0] | ||||
property.key = cloneNode(property.value) | ||||
scan.crawl(ast) | ||||
var binding = scan.scope(ast).undeclaredBindings.get('x') | ||||
t.ok(binding.references.has(property.value)) | ||||
t.notOk(binding.references.has(property.key)) | ||||
}) | ||||
test('shadowing', function (t) { | ||||
t.plan(8) | ||||
var ast = crawl(` | ||||
var a | ||||
{ let a } | ||||
function b (b) { | ||||
var a | ||||
} | ||||
`) | ||||
var root = scan.scope(ast) | ||||
var block = scan.scope(ast.body[1]) | ||||
var fn = scan.scope(ast.body[2]) | ||||
t.ok(root.has('a'), 'should find global var') | ||||
t.ok(root.has('b'), 'should find function declaration') | ||||
t.ok(block.has('a'), 'should shadow vars using `let` in block scope') | ||||
t.notEqual(block.getBinding('a'), root.getBinding('a'), 'shadowing should define different bindings') | ||||
t.ok(fn.has('b'), 'should find function parameter') | ||||
t.notEqual(fn.getBinding('b'), root.getBinding('b'), 'shadowing function name with parameter should define different bindings') | ||||
t.ok(fn.has('a'), 'should find local var') | ||||
t.notEqual(fn.getBinding('a'), root.getBinding('a'), 'shadowing vars in function scope should define different bindings') | ||||
}) | ||||
test('references', function (t) { | ||||
t.plan(5) | ||||
var src = ` | ||||
var a = 0 | ||||
a++ | ||||
a++ | ||||
function b (b) { | ||||
console.log(b(a)) | ||||
} | ||||
b(function (b) { return a + b }) | ||||
` | ||||
var ast = crawl(src) | ||||
var root = scan.scope(ast) | ||||
var fn = scan.scope(ast.body[3]) | ||||
var callback = scan.scope(ast.body[4].expression.arguments[0]) | ||||
var a = root.getBinding('a') | ||||
t.equal(a.getReferences().length, 5, 'should collect references in same and nested scopes') | ||||
var b = root.getBinding('b') | ||||
t.equal(b.getReferences().length, 2, 'should collect references to function declaration') | ||||
var b2 = fn.getBinding('b') | ||||
t.equal(b2.getReferences().length, 2, 'should collect references to shadowed function parameter') | ||||
var b3 = callback.getBinding('b') | ||||
t.equal(b3.getReferences().length, 2, 'should collect references to shadowed function parameter') | ||||
// try to rewrite some things | ||||
var result = src.split('') | ||||
a.getReferences().forEach(function (ref) { result[ref.start] = 'x' }) | ||||
b.getReferences().forEach(function (ref) { result[ref.start] = 'y' }) | ||||
b2.getReferences().forEach(function (ref) { result[ref.start] = 'z' }) | ||||
b3.getReferences().forEach(function (ref) { result[ref.start] = 'w' }) | ||||
t.equal(result.join(''), ` | ||||
var x = 0 | ||||
x++ | ||||
x++ | ||||
function y (z) { | ||||
console.log(z(x)) | ||||
} | ||||
y(function (w) { return x + w }) | ||||
`, 'references were associated correctly') | ||||
}) | ||||
test('references that are declared later', function (t) { | ||||
t.plan(4) | ||||
var src = ` | ||||
if (true) { b(function () { c() }) } | ||||
function b () {} | ||||
function c () {} | ||||
` | ||||
var ast = crawl(src) | ||||
var scope = scan.scope(ast) | ||||
var b = scope.getBinding('b') | ||||
t.ok(b, 'should have a binding for function b(){}') | ||||
var c = scope.getBinding('c') | ||||
t.ok(c, 'should have a binding for function c(){}') | ||||
t.equal(b.getReferences().length, 2, 'should find all references for b') | ||||
t.equal(c.getReferences().length, 2, 'should find all references for c') | ||||
}) | ||||
test('shorthand properties', function (t) { | ||||
t.plan(3) | ||||
var src = ` | ||||
var b = 1 | ||||
var a = { b } | ||||
var { c } = a | ||||
console.log({ c, b, a }) | ||||
` | ||||
var ast = crawl(src) | ||||
var body = ast.body | ||||
var scope = scan.scope(ast) | ||||
var a = scope.getBinding('a') | ||||
var b = scope.getBinding('b') | ||||
var c = scope.getBinding('c') | ||||
t.deepEqual(a.getReferences(), [a.definition, body[2].declarations[0].init, body[3].expression.arguments[0].properties[2].value]) | ||||
t.deepEqual(b.getReferences(), [b.definition, body[1].declarations[0].init.properties[0].value, body[3].expression.arguments[0].properties[1].value]) | ||||
t.deepEqual(c.getReferences(), [c.definition, body[3].expression.arguments[0].properties[0].value]) | ||||
}) | ||||
test('do not count object keys and method definitions as references', function (t) { | ||||
t.plan(2) | ||||
var src = ` | ||||
var a | ||||
class B { a () {} } | ||||
class C { get a () {} } | ||||
class D { set a (b) {} } | ||||
var e = { a: null } | ||||
` | ||||
var ast = crawl(src) | ||||
var scope = scan.scope(ast) | ||||
var a = scope.getBinding('a') | ||||
t.equal(a.getReferences().length, 1) | ||||
t.deepEqual(a.getReferences(), [a.definition]) | ||||
}) | ||||
test('do not count renamed imported identifiers as references', function (t) { | ||||
t.plan(2) | ||||
var src = ` | ||||
var a = 0 | ||||
a++ | ||||
a++ | ||||
import { a as b } from "b" | ||||
b() | ||||
` | ||||
var ast = crawl(src, { sourceType: 'module' }) | ||||
var root = scan.scope(ast) | ||||
var a = root.getBinding('a') | ||||
var b = root.getBinding('b') | ||||
t.equal(a.getReferences().length, 3, 'should not have counted renamed `a` import as a reference') | ||||
t.equal(b.getReferences().length, 2, 'should have counted local name of renamed import') | ||||
}) | ||||
test('remove references', function (t) { | ||||
t.plan(6) | ||||
var src = ` | ||||
function a () {} | ||||
a() | ||||
a() | ||||
` | ||||
var ast = crawl(src) | ||||
var root = scan.scope(ast) | ||||
var a = root.getBinding('a') | ||||
t.equal(a.getReferences().length, 3, 'should have 3 references') | ||||
t.ok(a.isReferenced(), 'should be referenced') | ||||
var reference = ast.body[1].expression.callee | ||||
a.remove(reference) | ||||
t.equal(a.getReferences().length, 2, 'should have removed the reference') | ||||
t.ok(a.isReferenced(), 'should still be referenced') | ||||
reference = ast.body[2].expression.callee | ||||
a.remove(reference) | ||||
t.equal(a.getReferences().length, 1, 'should still have the definition reference') | ||||
t.notOk(a.isReferenced(), 'should no longer be referenced') | ||||
}) | ||||
test('collect references to undeclared variables', function (t) { | ||||
t.plan(2) | ||||
var src = ` | ||||
var a = b | ||||
b = a | ||||
a(b) | ||||
function c () { | ||||
return d | ||||
} | ||||
` | ||||
var ast = crawl(src) | ||||
var root = scan.scope(ast) | ||||
var undeclared = ArrayFrom(root.undeclaredBindings.keys()) | ||||
var declared = ArrayFrom(root.bindings.keys()) | ||||
t.deepEqual(undeclared, ['b', 'd']) | ||||
t.deepEqual(declared, ['a', 'c']) | ||||
}) | ||||
test('loop over all available bindings, including declared in parent scope', function (t) { | ||||
t.plan(1) | ||||
var src = ` | ||||
var a = 0 | ||||
var b = 1, c = 2 | ||||
function d() { | ||||
function e() {} | ||||
function f() { | ||||
var b = 3 | ||||
console.log('bindings') | ||||
} | ||||
} | ||||
` | ||||
var ast = crawl(src) | ||||
var scope = scan.scope(ast.body[2].body.body[1]) | ||||
var names = [] | ||||
scope.forEachAvailable(function (binding, name) { | ||||
names.push(name) | ||||
}) | ||||
t.deepEqual(names, ['b', 'e', 'f', 'a', 'c', 'd']) | ||||
}) | ||||
test('always initialise a scope for the root', function (t) { | ||||
t.plan(2) | ||||
var src = ` | ||||
console.log("null") | ||||
` | ||||
var ast = crawl(src) | ||||
var scope = scan.scope(ast) | ||||
t.ok(scope) | ||||
t.deepEqual(scope.getUndeclaredNames(), ['console']) | ||||
}) | ||||
test('initialises a scope for catch clauses', function (t) { | ||||
t.plan(5) | ||||
var ast = crawl(` | ||||
var a = null | ||||
a = 1 | ||||
try { | ||||
} catch (a) { | ||||
a = 2 | ||||
} | ||||
`) | ||||
var scope = scan.scope(ast) | ||||
t.ok(scope.has('a'), 'should find var') | ||||
t.equal(scope.getBinding('a').getReferences().length, 2, 'only counts references to outer `a`') | ||||
var clause = ast.body[2].handler | ||||
var catchScope = scan.scope(clause) | ||||
t.ok(catchScope.has('a'), 'should find param') | ||||
t.notEqual(scope.getBinding('a'), catchScope.getBinding('a'), 'introduced a different binding') | ||||
t.equal(catchScope.getBinding('a').getReferences().length, 2, 'only counts references to inner `a`') | ||||
}) | ||||
test('clear all scope information', function (t) { | ||||
t.plan(6) | ||||
var ast = crawl(` | ||||
function x() { | ||||
var y = z | ||||
} | ||||
var z = x | ||||
`) | ||||
var fn = ast.body[0] | ||||
t.ok(scan.scope(ast)) | ||||
t.ok(scan.scope(fn)) | ||||
t.ok(scan.getBinding(fn.id)) | ||||
scan.clear(ast) | ||||
t.notOk(scan.scope(ast)) | ||||
t.notOk(scan.scope(fn)) | ||||
t.notOk(scan.getBinding(fn.id)) | ||||
}) | ||||
test('clear partial scope information', function (t) { | ||||
t.plan(4) | ||||
var ast = crawl('function x() {}') | ||||
var fn = ast.body[0] | ||||
t.ok(scan.scope(fn)) | ||||
t.ok(scan.getBinding(fn.id)) | ||||
scan.deleteScope(fn) | ||||
t.notOk(scan.scope(fn)) | ||||
t.ok(scan.getBinding(fn.id)) | ||||
}) | ||||