Skip to content

Commit

Permalink
Check exported bindings are defined
Browse files Browse the repository at this point in the history
  • Loading branch information
TimothyGu authored and marijnh committed Feb 1, 2019
1 parent 52b8914 commit f8c9a67
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 31 deletions.
12 changes: 12 additions & 0 deletions acorn/src/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pp.declareName = function(name, bindingType, pos) {
const scope = this.currentScope()
redeclared = scope.lexical.indexOf(name) > -1 || scope.functions.indexOf(name) > -1 || scope.var.indexOf(name) > -1
scope.lexical.push(name)
if (this.inModule && (scope.flags & SCOPE_TOP))
delete this.undefinedExports[name];
} else if (bindingType === BIND_SIMPLE_CATCH) {
const scope = this.currentScope()
scope.lexical.push(name)
Expand All @@ -57,12 +59,22 @@ pp.declareName = function(name, bindingType, pos) {
break
}
scope.var.push(name)
if (this.inModule && (scope.flags & SCOPE_TOP))
delete this.undefinedExports[name];
if (scope.flags & SCOPE_VAR) break
}
}
if (redeclared) this.raiseRecoverable(pos, `Identifier '${name}' has already been declared`)
}

pp.checkLocalExport = function(id) {
// scope.functions must be empty as Module code is always strict.
if (this.scopeStack[0].lexical.indexOf(id.name) === -1 &&
this.scopeStack[0].var.indexOf(id.name) === -1) {
this.undefinedExports[id.name] = id;
}
}

pp.currentScope = function() {
return this.scopeStack[this.scopeStack.length - 1]
}
Expand Down
2 changes: 2 additions & 0 deletions acorn/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export class Parser {
this.yieldPos = this.awaitPos = 0
// Labels in scope.
this.labels = []
// Thus-far undefined exports.
this.undefinedExports = {};

// If enabled, skip leading hashbang line.
if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === "#!")
Expand Down
7 changes: 6 additions & 1 deletion acorn/src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pp.parseTopLevel = function(node) {
let stmt = this.parseStatement(null, true, exports)
node.body.push(stmt)
}
if (this.inModule)
for (let name of Object.keys(this.undefinedExports))
this.raiseRecoverable(this.undefinedExports[name].start, `Export '${name}' is not defined`);
this.adaptDirectivePrologue(node.body)
this.next()
if (this.options.ecmaVersion >= 6) {
Expand Down Expand Up @@ -689,9 +692,11 @@ pp.parseExport = function(node, exports) {
if (this.type !== tt.string) this.unexpected()
node.source = this.parseExprAtom()
} else {
// check for keywords used as local names
for (let spec of node.specifiers) {
// check for keywords used as local names
this.checkUnreserved(spec.local)
// check if export is defined
this.checkLocalExport(spec.local)
}

node.source = null
Expand Down
4 changes: 0 additions & 4 deletions bin/test262.whitelist
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,5 @@ language/expressions/object/method-definition/early-errors-object-async-method-d
language/expressions/object/method-definition/early-errors-object-method-duplicate-parameters.js (default)
language/expressions/object/prop-def-invalid-async-prefix.js (default)
language/expressions/object/prop-def-invalid-async-prefix.js (strict mode)
language/module-code/early-export-global.js (default)
language/module-code/early-export-global.js (strict mode)
language/module-code/early-export-unresolvable.js (default)
language/module-code/early-export-unresolvable.js (strict mode)
language/white-space/mongolian-vowel-separator.js (default)
language/white-space/mongolian-vowel-separator.js (strict mode)
209 changes: 183 additions & 26 deletions test/tests-harmony.js
Original file line number Diff line number Diff line change
Expand Up @@ -4499,7 +4499,7 @@ test("export * from \"crypto\"", {
locations: true
});

test("export { encrypt }", {
test("export { encrypt }\nvar encrypt", {
type: "Program",
body: [{
type: "ExportNamedDeclaration",
Expand Down Expand Up @@ -4532,10 +4532,33 @@ test("export { encrypt }", {
start: {line: 1, column: 0},
end: {line: 1, column: 18}
}
}, {
type: "VariableDeclaration",
declarations: [{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "encrypt",
loc: {
start: {line: 2, column: 4},
end: {line: 2, column: 11}
}
},
init: null,
loc: {
start: {line: 2, column: 4},
end: {line: 2, column: 11}
}
}],
kind: "var",
loc: {
start: {line: 2, column: 0},
end: {line: 2, column: 11}
}
}],
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 18}
end: {line: 2, column: 11}
}
}, {
ecmaVersion: 6,
Expand All @@ -4544,9 +4567,55 @@ test("export { encrypt }", {
locations: true
});

test("export { encrypt, decrypt }", {
test("function encrypt() {} let decrypt; export { encrypt, decrypt }", {
type: "Program",
body: [{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "encrypt",
loc: {
start: {line: 1, column: 9},
end: {line: 1, column: 16}
}
},
params: [],
body: {
type: "BlockStatement",
body: [],
loc: {
start: {line: 1, column: 19},
end: {line: 1, column: 21}
}
},
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 21}
}
}, {
type: "VariableDeclaration",
declarations: [{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "decrypt",
loc: {
start: {line: 1, column: 26},
end: {line: 1, column: 33}
}
},
init: null,
loc: {
start: {line: 1, column: 26},
end: {line: 1, column: 33}
}
}],
kind: "let",
loc: {
start: {line: 1, column: 22},
end: {line: 1, column: 34}
}
}, {
type: "ExportNamedDeclaration",
declaration: null,
specifiers: [
Expand All @@ -4556,21 +4625,21 @@ test("export { encrypt, decrypt }", {
type: "Identifier",
name: "encrypt",
loc: {
start: {line: 1, column: 9},
end: {line: 1, column: 16}
start: {line: 1, column: 44},
end: {line: 1, column: 51}
}
},
local: {
type: "Identifier",
name: "encrypt",
loc: {
start: {line: 1, column: 9},
end: {line: 1, column: 16}
start: {line: 1, column: 44},
end: {line: 1, column: 51}
}
},
loc: {
start: {line: 1, column: 9},
end: {line: 1, column: 16}
start: {line: 1, column: 44},
end: {line: 1, column: 51}
}
},
{
Expand All @@ -4579,33 +4648,33 @@ test("export { encrypt, decrypt }", {
type: "Identifier",
name: "decrypt",
loc: {
start: {line: 1, column: 18},
end: {line: 1, column: 25}
start: {line: 1, column: 53},
end: {line: 1, column: 60}
}
},
local: {
type: "Identifier",
name: "decrypt",
loc: {
start: {line: 1, column: 18},
end: {line: 1, column: 25}
start: {line: 1, column: 53},
end: {line: 1, column: 60}
}
},
loc: {
start: {line: 1, column: 18},
end: {line: 1, column: 25}
start: {line: 1, column: 53},
end: {line: 1, column: 60}
}
}
],
source: null,
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 27}
start: {line: 1, column: 35},
end: {line: 1, column: 62}
}
}],
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 27}
end: {line: 1, column: 62}
}
}, {
ecmaVersion: 6,
Expand All @@ -4614,7 +4683,42 @@ test("export { encrypt, decrypt }", {
locations: true
});

test("export { encrypt as default }", {
testFail("export { encrypt }", "Export 'encrypt' is not defined (1:9)", {
ecmaVersion: 6,
sourceType: "module"
});

testFail("export { encrypt, encrypt }", "Duplicate export 'encrypt' (1:18)", {
ecmaVersion: 6,
sourceType: "module"
});

testFail("export { encrypt }; export { encrypt }", "Duplicate export 'encrypt' (1:29)", {
ecmaVersion: 6,
sourceType: "module"
});

testFail("export { decrypt as encrypt }; function encrypt() {}", "Export 'decrypt' is not defined (1:9)", {
ecmaVersion: 6,
sourceType: "module"
});

testFail("export { encrypt }; if (true) function encrypt() {}", "Unexpected token (1:30)", {
ecmaVersion: 6,
sourceType: "module"
});

testFail("{ function encrypt() {} } export { encrypt }", "Export 'encrypt' is not defined (1:35)", {
ecmaVersion: 6,
sourceType: "module"
});

test("{ var encrypt } export { encrypt }", {}, {
ecmaVersion: 6,
sourceType: "module"
});

test("export { encrypt as default }; function* encrypt() {}", {
type: "Program",
body: [{
type: "ExportNamedDeclaration",
Expand Down Expand Up @@ -4645,21 +4749,36 @@ test("export { encrypt as default }", {
source: null,
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 29}
end: {line: 1, column: 30}
}
}, {
type: "FunctionDeclaration",
generator: true,
params: [],
body: {
type: "BlockStatement",
body: [],
loc: {
start: {line: 1, column: 51},
end: {line: 1, column: 53}
}
},
loc: {
start: {line: 1, column: 31},
end: {line: 1, column: 53}
}
}],
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 29}
end: {line: 1, column: 53}
}
}, {
ecmaVersion: 6,
sourceType: "module",
ranges: true,
locations: true
});

test("export { encrypt, decrypt as dec }", {
test("export { encrypt, decrypt as dec }; let encrypt, decrypt", {
type: "Program",
body: [{
type: "ExportNamedDeclaration",
Expand Down Expand Up @@ -4715,12 +4834,50 @@ test("export { encrypt, decrypt as dec }", {
source: null,
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 34}
end: {line: 1, column: 35}
}
}, {
type: "VariableDeclaration",
declarations: [{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "encrypt",
loc: {
start: {line: 1, column: 40},
end: {line: 1, column: 47}
}
},
init: null,
loc: {
start: {line: 1, column: 40},
end: {line: 1, column: 47}
}
}, {
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "decrypt",
loc: {
start: {line: 1, column: 49},
end: {line: 1, column: 56}
}
},
init: null,
loc: {
start: {line: 1, column: 49},
end: {line: 1, column: 56}
}
}],
kind: "let",
loc: {
start: {line: 1, column: 36},
end: {line: 1, column: 56}
}
}],
loc: {
start: {line: 1, column: 0},
end: {line: 1, column: 34}
end: {line: 1, column: 56}
}
}, {
ecmaVersion: 6,
Expand Down Expand Up @@ -16228,7 +16385,7 @@ testFail("class A extends B { constructor() { (super)() } }", "Unexpected token
testFail("class A extends B { foo() { (super).foo } }", "Unexpected token (1:34)", { ecmaVersion: 6 })
test("({super: 1})", {}, { ecmaVersion: 6 })
test("import {super as a} from 'a'", {}, { ecmaVersion: 6, sourceType: "module" })
test("export {a as super}", {}, { ecmaVersion: 6, sourceType: "module" })
test("function a() {} export {a as super}", {}, { ecmaVersion: 6, sourceType: "module" })
test("let instanceof Foo", {
"type": "Program",
"start": 0,
Expand Down

0 comments on commit f8c9a67

Please sign in to comment.