diff --git a/src/Statement.js b/src/Statement.js index 790c3a1..3e18560 100644 --- a/src/Statement.js +++ b/src/Statement.js @@ -3,6 +3,11 @@ import getLocation from './utils/getLocation'; import walk from './ast/walk'; import Scope from './ast/Scope'; +const blockDeclarations = { + 'const': true, + 'let': true +}; + function isIife ( node, parent ) { return parent && parent.type === 'CallExpression' && node === parent.callee; } @@ -73,7 +78,7 @@ export default class Statement { switch ( node.type ) { case 'FunctionDeclaration': - scope.addDeclaration( node.id.name, node, false ); + scope.addDeclaration( node, false, false ); case 'BlockStatement': if ( parent && /Function/.test( parent.type ) ) { @@ -86,7 +91,7 @@ export default class Statement { // named function expressions - the name is considered // part of the function's scope if ( parent.type === 'FunctionExpression' && parent.id ) { - newScope.addDeclaration( parent.id.name, parent, false ); + newScope.addDeclaration( parent, false, false ); } } else { newScope = new Scope({ @@ -108,12 +113,13 @@ export default class Statement { case 'VariableDeclaration': node.declarations.forEach( declarator => { - scope.addDeclaration( declarator.id.name, node, true ); + const isBlockDeclaration = node.type === 'VariableDeclaration' && blockDeclarations[ node.kind ]; + scope.addDeclaration( declarator, isBlockDeclaration, true ); }); break; case 'ClassDeclaration': - scope.addDeclaration( node.id.name, node, false ); + scope.addDeclaration( node, false, false ); break; } diff --git a/src/ast/Scope.js b/src/ast/Scope.js index aeb9da7..7bd79bf 100644 --- a/src/ast/Scope.js +++ b/src/ast/Scope.js @@ -1,10 +1,38 @@ import { blank } from '../utils/object'; -const blockDeclarations = { - 'const': true, - 'let': true +const extractors = { + Identifier ( names, param ) { + names.push( param.name ); + }, + + ObjectPattern ( names, param ) { + param.properties.forEach( prop => { + extractors[ prop.key.type ]( names, prop.key ); + }); + }, + + ArrayPattern ( names, param ) { + param.elements.forEach( element => { + if ( element ) extractors[ element.type ]( names, element ); + }); + }, + + RestElement ( names, param ) { + extractors[ param.argument.type ]( names, param.argument ); + }, + + AssignmentPattern ( names, param ) { + return extractors[ param.left.type ]( names, param.left ); + } }; +function extractNames ( param ) { + let names = []; + + extractors[ param.type ]( names, param ); + return names; +} + export default class Scope { constructor ( options ) { options = options || {}; @@ -18,26 +46,29 @@ export default class Scope { if ( options.params ) { options.params.forEach( param => { - this.declarations[ param.name ] = param; + extractNames( param ).forEach( name => { + this.declarations[ name ] = true; + }); }); } } - addDeclaration ( name, declaration, isVar ) { - const isBlockDeclaration = declaration.type === 'VariableDeclaration' && blockDeclarations[ declaration.kind ]; - + addDeclaration ( declaration, isBlockDeclaration, isVar ) { if ( !isBlockDeclaration && this.isBlockScope ) { - // it's a `var` or function declaration, and this + // it's a `var` or function node, and this // is a block scope, so we need to go up - this.parent.addDeclaration( name, declaration, isVar ); + this.parent.addDeclaration( declaration, isBlockDeclaration, isVar ); } else { - this.declarations[ name ] = declaration; - if ( isVar ) this.varDeclarations.push( name ); + extractNames( declaration.id ).forEach( name => { + this.declarations[ name ] = true; + if ( isVar ) this.varDeclarations.push( name ); + }); } } contains ( name ) { - return !!this.getDeclaration( name ); + return this.declarations[ name ] || + ( this.parent ? this.parent.contains( name ) : false ); } findDefiningScope ( name ) { @@ -51,9 +82,4 @@ export default class Scope { return null; } - - getDeclaration ( name ) { - return this.declarations[ name ] || - this.parent && this.parent.getDeclaration( name ); - } } diff --git a/test/function/assignment-patterns/_config.js b/test/function/assignment-patterns/_config.js new file mode 100644 index 0000000..d4afdc3 --- /dev/null +++ b/test/function/assignment-patterns/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'allows reassigments to default parameters that shadow imports', + babel: true +}; diff --git a/test/function/assignment-patterns/main.js b/test/function/assignment-patterns/main.js new file mode 100644 index 0000000..844c908 --- /dev/null +++ b/test/function/assignment-patterns/main.js @@ -0,0 +1,21 @@ +import { bar, baz, x, items, p, q, r, s } from './other'; + +function foo ( bar = 1, { baz } = { baz: 2 }, [[[,x = 3] = []] = []] = [], ...items ) { + bar += 1; + baz += 1; + x += 1; + + let { p, q } = { p: 4, q: 5 }; + let [ r, s ] = [ 6, 7 ]; + + p++; + q += 1; + r = 7; + s = 6; + + return bar + baz + x + items.length + p + q + r + s; +} + +assert.equal( foo(), 33 ); +assert.equal( foo( 2 ), 34 ); +assert.equal( foo( 2, { baz: 3 }, [[[99,10]]], 'a', 'b', 'c' ), 45 ); diff --git a/test/function/assignment-patterns/other.js b/test/function/assignment-patterns/other.js new file mode 100644 index 0000000..840584c --- /dev/null +++ b/test/function/assignment-patterns/other.js @@ -0,0 +1,8 @@ +export const bar = 'bar'; +export const baz = 'baz'; +export const x = 'x'; +export const items = 'items'; +export const p = 'p'; +export const q = 'q'; +export const r = 'r'; +export const s = 's';