Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Fix: wrong resolution about default parameters #33

Merged
merged 2 commits into from
Nov 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions lib/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,22 +341,32 @@ class Scope {
return this.upper;
}

// To override by function scopes.
// References in default parameters isn't resolved to variables which are in their function body.
__isValidResolution(ref, variable) { // eslint-disable-line class-methods-use-this, no-unused-vars
return true;
}

__resolve(ref) {
const name = ref.identifier.name;

if (this.set.has(name)) {
const variable = this.set.get(name);
if (!this.set.has(name)) {
return false;
}
const variable = this.set.get(name);

variable.references.push(ref);
variable.stack = variable.stack && ref.from.variableScope === this.variableScope;
if (ref.tainted) {
variable.tainted = true;
this.taints.set(variable.name, true);
}
ref.resolved = variable;
return true;
if (!this.__isValidResolution(ref, variable)) {
return false;
}
return false;
variable.references.push(ref);
variable.stack = variable.stack && ref.from.variableScope === this.variableScope;
if (ref.tainted) {
variable.tainted = true;
this.taints.set(variable.name, true);
}
ref.resolved = variable;

return true;
}

__delegateToUpperScope(ref) {
Expand Down Expand Up @@ -690,6 +700,29 @@ class FunctionScope extends Scope {
null);
this.taints.set("arguments", true);
}

// References in default parameters isn't resolved to variables which are in their function body.
// const x = 1
// function f(a = x) { // This `x` is resolved to the `x` in the outer scope.
// const x = 2
// console.log(a)
// }
__isValidResolution(ref, variable) {

// If `options.nodejsScope` is true, `this.block` becomes a Program node.
if (this.block.type === "Program") {
return true;
}

const bodyStart = this.block.body.range[0];

// It's invalid resolution in the following case:
return !(
variable.scope === this &&
ref.identifier.range[0] < bodyStart && // the reference is in the parameter part.
variable.defs.every(d => d.name.range[0] >= bodyStart) // the variable is in the body.
);
}
}

class ForScope extends Scope {
Expand Down
130 changes: 130 additions & 0 deletions tests/es6-default-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,136 @@ describe("ES6 default parameters:", () => {
});
}
});

describe("a default parameter creates a readable reference for references in right. It's resolved to outer scope's even if there is the variable in the function body:", () => {
const patterns = {
FunctionDeclaration: `
let a;
function foo(b = a) { let a; }
`,
FunctionExpression: `
let a;
let foo = function(b = a) { let a; }
`,
ArrowExpression: `
let a;
let foo = (b = a) => { let a; };
`
};

for (const name in patterns) {
const code = patterns[name];

it(name, () => {
const numVars = name === "ArrowExpression" ? 2 : 3;
const ast = espree(code);

const scopeManager = analyze(ast, { ecmaVersion: 6 });

expect(scopeManager.scopes).to.have.length(2); // [global, foo]

const scope = scopeManager.scopes[1];

expect(scope.variables).to.have.length(numVars); // [arguments?, b, a]
expect(scope.references).to.have.length(2); // [b, a]

const reference = scope.references[1];

expect(reference.from).to.equal(scope);
expect(reference.identifier.name).to.equal("a");
expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]);
expect(reference.writeExpr).to.be.undefined;
expect(reference.isWrite()).to.be.false;
expect(reference.isRead()).to.be.true;
});
}
});

describe("a default parameter creates a readable reference for references in right. It's resolved to the parameter:", () => {
const patterns = {
FunctionDeclaration: `
let a;
function foo(b = a, a) { }
`,
FunctionExpression: `
let a;
let foo = function(b = a, a) { }
`,
ArrowExpression: `
let a;
let foo = (b = a, a) => { };
`
};

for (const name in patterns) {
const code = patterns[name];

it(name, () => {
const numVars = name === "ArrowExpression" ? 2 : 3;
const ast = espree(code);

const scopeManager = analyze(ast, { ecmaVersion: 6 });

expect(scopeManager.scopes).to.have.length(2); // [global, foo]

const scope = scopeManager.scopes[1];

expect(scope.variables).to.have.length(numVars); // [arguments?, b, a]
expect(scope.references).to.have.length(2); // [b, a]

const reference = scope.references[1];

expect(reference.from).to.equal(scope);
expect(reference.identifier.name).to.equal("a");
expect(reference.resolved).to.equal(scope.variables[scope.variables.length - 1]);
expect(reference.writeExpr).to.be.undefined;
expect(reference.isWrite()).to.be.false;
expect(reference.isRead()).to.be.true;
});
}
});

describe("a default parameter creates a readable reference for references in right (nested scope). It's resolved to outer scope's even if there is the variable in the function body:", () => {
const patterns = {
FunctionDeclaration: `
let a;
function foo(b = function(){ a }) { let a; }
`,
FunctionExpression: `
let a;
let foo = function(b = function(){ a }) { let a; }
`,
ArrowExpression: `
let a;
let foo = (b = function(){ a }) => { let a; };
`
};

for (const name in patterns) {
const code = patterns[name];

it(name, () => {
const ast = espree(code);

const scopeManager = analyze(ast, { ecmaVersion: 6 });

expect(scopeManager.scopes).to.have.length(3); // [global, foo, anonymous function]

const scope = scopeManager.scopes[2];

expect(scope.references).to.have.length(1); // [a]

const reference = scope.references[0];

expect(reference.from).to.equal(scope);
expect(reference.identifier.name).to.equal("a");
expect(reference.resolved).to.equal(scopeManager.scopes[0].variables[0]);
expect(reference.writeExpr).to.be.undefined;
expect(reference.isWrite()).to.be.false;
expect(reference.isRead()).to.be.true;
});
}
});
});

// vim: set sw=4 ts=4 et tw=80 :
7 changes: 4 additions & 3 deletions tests/util/espree.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@
*/
"use strict";

var espree = require("espree");
const espree = require("espree");

module.exports = function(code, sourceType) {
sourceType = sourceType || "module";

return espree.parse(code, {
// enable es6 features.
sourceType: sourceType
range: true,
ecmaVersion: 7,
sourceType
});
};

Expand Down