Skip to content

Commit

Permalink
[[FIX]] Detect duplicate exported bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
jugglinmike committed Sep 21, 2020
1 parent 737524d commit 62f448f
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 75 deletions.
101 changes: 57 additions & 44 deletions src/jshint.js
Expand Up @@ -2754,7 +2754,6 @@ var JSHINT = (function() {

var classDeclaration = blockstmt("class", function(context) {
var className, classNameToken;
var inexport = context & prodParams.export;

if (!state.inES6()) {
warning("W104", state.tokens.curr, "class", "6");
Expand All @@ -2781,12 +2780,12 @@ var JSHINT = (function() {
}

if (classNameToken) {
this.name = className;
this.name = classNameToken;
state.funct["(scope)"].initialize(className);
if (inexport) {
state.funct["(scope)"].setExported(className, classNameToken);
}
} else {
this.name = null;
}

state.funct["(scope)"].stack();
classBody(this, context);
return this;
Expand Down Expand Up @@ -2823,13 +2822,15 @@ var JSHINT = (function() {

state.funct["(scope)"].stack();
if (classNameToken) {
this.name = className;
this.name = classNameToken;
state.funct["(scope)"].addbinding(className, {
type: "class",
initialized: true,
token: classNameToken
});
state.funct["(scope)"].block.use(className, classNameToken);
} else {
this.name = null;
}

classBody(this, context);
Expand Down Expand Up @@ -4339,7 +4340,6 @@ var JSHINT = (function() {
// used for both let and const statements

var noin = context & prodParams.noin;
var inexport = context & prodParams.export;
var isLet = type === "let";
var isConst = type === "const";
var tokens, lone, value, letblock;
Expand Down Expand Up @@ -4429,10 +4429,6 @@ var JSHINT = (function() {
if (tokens.hasOwnProperty(t)) {
t = tokens[t];
state.funct["(scope)"].initialize(t.id);

if (inexport) {
state.funct["(scope)"].setExported(t.token.value, t.token);
}
}
}
}
Expand Down Expand Up @@ -4534,7 +4530,6 @@ var JSHINT = (function() {

var varstatement = stmt("var", function(context) {
var noin = context & prodParams.noin;
var inexport = context & prodParams.export;
var tokens, lone, value, id;

this.first = [];
Expand Down Expand Up @@ -4577,9 +4572,6 @@ var JSHINT = (function() {
type: "var",
token: t.token });

if (inexport) {
state.funct["(scope)"].setExported(t.id, t.token);
}
names.push(t.token);
}
}
Expand Down Expand Up @@ -4652,25 +4644,21 @@ var JSHINT = (function() {
if (inblock) {
warning("W082", state.tokens.curr);
}
var nameToken = optionalidentifier(context) ? state.tokens.curr : null;
this.name = optionalidentifier(context) ? state.tokens.curr : null;

if (!nameToken) {
if (!this.name) {
if (!inexport) {
warning("W025");
}
} else {
state.funct["(scope)"].addbinding(nameToken.value, {
state.funct["(scope)"].addbinding(this.name.value, {
type: labelType,
token: state.tokens.curr,
initialized: true });

if (inexport) {
state.funct["(scope)"].setExported(nameToken.value, state.tokens.prev);
}
}

var f = doFunction(context, {
name: nameToken && nameToken.value,
name: this.name && this.name.value,
statement: this,
type: generator ? "generator" : null,
ignoreLoopFunc: inblock // a declaration may already have warned
Expand All @@ -4683,9 +4671,9 @@ var JSHINT = (function() {
// mode (the scope manager will not report an error because a declaration
// does not introduce a binding into the function's environment record).
var enablesStrictMode = f["(isStrict)"] && !state.isStrict();
if (nameToken && (f["(name)"] === "arguments" || f["(name)"] === "eval") &&
if (this.name && (f["(name)"] === "arguments" || f["(name)"] === "eval") &&
enablesStrictMode) {
error("E008", nameToken);
error("E008", this.name);
}

if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) {
Expand All @@ -4712,21 +4700,21 @@ var JSHINT = (function() {

// This context modification restricts the use of `await` as the optional
// BindingIdentifier in async function expressions.
var nameToken = optionalidentifier(isAsync ? context | prodParams.async : context) ?
this.name = optionalidentifier(isAsync ? context | prodParams.async : context) ?
state.tokens.curr : null;

var f = doFunction(context, {
name: nameToken && nameToken.value,
name: this.name && this.name.value,
type: generator ? "generator" : null
});

if (generator && nameToken && nameToken.value === "yield") {
error("E024", nameToken, "yield");
if (generator && this.name && this.name.value === "yield") {
error("E024", this.name, "yield");
}

if (nameToken && (f["(name)"] === "arguments" || f["(name)"] === "eval") &&
if (this.name && (f["(name)"] === "arguments" || f["(name)"] === "eval") &&
f["(isStrict)"]) {
error("E008", nameToken);
error("E008", this.name);
}

return this;
Expand Down Expand Up @@ -5633,6 +5621,7 @@ var JSHINT = (function() {

stmt("export", function(context) {
var ok = true;
var token;
var moduleSpecifier;
context = context | prodParams.export;

Expand Down Expand Up @@ -5665,22 +5654,27 @@ var JSHINT = (function() {
state.nameStack.set(state.tokens.next);

advance("default");
var def = state.tokens.curr;
var exportType = state.tokens.next.id;
if (exportType === "function") {
this.block = true;
advance("function");
state.syntax["function"].fud(context);
token = state.syntax["function"].fud(context);
state.funct["(scope)"].setExported(token.name, def);
} else if (exportType === "async" && peek().id === "function") {
this.block = true;
advance("async");
advance("function");
state.syntax["function"].fud(context | prodParams.preAsync);
token = state.syntax["function"].fud(context | prodParams.preAsync);
state.funct["(scope)"].setExported(token.name, def);
} else if (exportType === "class") {
this.block = true;
advance("class");
state.syntax["class"].fud(context);
token = state.syntax["class"].fud(context);
state.funct["(scope)"].setExported(token.name, def);
} else {
expression(context, 10);
state.funct["(scope)"].setExported(null, def);
}
return this;
}
Expand All @@ -5695,15 +5689,22 @@ var JSHINT = (function() {
}
advance();

exportedTokens.push(state.tokens.curr);

if (state.tokens.next.value === "as") {
advance("as");
if (!state.tokens.next.identifier) {
/* istanbul ignore next */
error("E030", state.tokens.next, state.tokens.next.value);
}
exportedTokens.push({
local: state.tokens.prev,
export: state.tokens.next
});
advance();
} else {
exportedTokens.push({
local: state.tokens.curr,
export: state.tokens.curr
});
}

if (!checkPunctuator(state.tokens.next, "}")) {
Expand All @@ -5717,8 +5718,8 @@ var JSHINT = (function() {
moduleSpecifier = state.tokens.next;
advance("(string)");
} else if (ok) {
exportedTokens.forEach(function(token) {
state.funct["(scope)"].setExported(token.value, token);
exportedTokens.forEach(function(x) {
state.funct["(scope)"].setExported(x.local, x.export);
});
}

Expand All @@ -5734,31 +5735,43 @@ var JSHINT = (function() {
} else if (state.tokens.next.id === "var") {
// ExportDeclaration :: export VariableStatement
advance("var");
state.tokens.curr.fud(context);
token = state.tokens.curr.fud(context);
token.first.forEach(function(binding) {
state.funct["(scope)"].setExported(binding, binding);
});
} else if (state.tokens.next.id === "let") {
// ExportDeclaration :: export VariableStatement
advance("let");
state.tokens.curr.fud(context);
token = state.tokens.curr.fud(context);
token.first.forEach(function(binding) {
state.funct["(scope)"].setExported(binding, binding);
});
} else if (state.tokens.next.id === "const") {
// ExportDeclaration :: export VariableStatement
advance("const");
state.tokens.curr.fud(context);
token = state.tokens.curr.fud(context);
token.first.forEach(function(binding) {
state.funct["(scope)"].setExported(binding, binding);
});
} else if (state.tokens.next.id === "function") {
// ExportDeclaration :: export Declaration
this.block = true;
advance("function");
state.syntax["function"].fud(context);
token = state.syntax["function"].fud(context);
state.funct["(scope)"].setExported(token.name, token.name);
} else if (state.tokens.next.id === "async" && peek().id === "function") {
// ExportDeclaration :: export Declaration
this.block = true;
advance("async");
advance("function");
state.syntax["function"].fud(context | prodParams.preAsync);
token = state.syntax["function"].fud(context | prodParams.preAsync);
state.funct["(scope)"].setExported(token.name, token.name);
} else if (state.tokens.next.id === "class") {
// ExportDeclaration :: export Declaration
this.block = true;
advance("class");
state.syntax["class"].fud(context);
token = state.syntax["class"].fud(context);
state.funct["(scope)"].setExported(token.name, token.name);
} else {
/* istanbul ignore next */
error("E024", state.tokens.next, state.tokens.next.value);
Expand Down
3 changes: 2 additions & 1 deletion src/messages.js
Expand Up @@ -84,7 +84,8 @@ var errors = {
"enable strict mode.",
E066: "Asynchronous iteration is only available with for-of loops.",
E067: "Malformed numeric literal: '{a}'.",
E068: "Decimals with leading zeros are not allowed in strict mode."
E068: "Decimals with leading zeros are not allowed in strict mode.",
E069: "Duplicate exported binding: '{a}'."
};

var warnings = {
Expand Down
15 changes: 13 additions & 2 deletions src/scope-manager.js
Expand Up @@ -64,6 +64,7 @@ var scopeManager = function(state, predefined, exported, declared) {
var usedPredefinedAndGlobals = Object.create(null);
var impliedGlobals = Object.create(null);
var unuseds = [];
var esModuleExports = [];
var emitter = new events.EventEmitter();

function warning(code, token) {
Expand Down Expand Up @@ -694,8 +695,18 @@ var scopeManager = function(state, predefined, exported, declared) {
* @param {string} bindingName - the value of the identifier
* @param {object} token
*/
setExported: function(bindingName, token) {
this.block.use(bindingName, token);
setExported: function(localName, exportName) {
if (exportName) {
if (esModuleExports.indexOf(exportName.value) > -1) {
error("E069", exportName, exportName.value);
}

esModuleExports.push(exportName.value);
}

if (localName) {
this.block.use(localName.value, localName);
}
},

/**
Expand Down
8 changes: 0 additions & 8 deletions tests/test262/expectations.txt
Expand Up @@ -149,14 +149,6 @@ test/language/block-scope/syntax/function-declarations/in-statement-position-if-
test/language/block-scope/syntax/function-declarations/in-statement-position-if-expression-statement.js(strict mode)
test/language/destructuring/binding/syntax/array-elements-with-initializer.js(default)
test/language/destructuring/binding/syntax/array-elements-with-initializer.js(strict mode)
test/language/module-code/early-dup-export-decl.js(default)
test/language/module-code/early-dup-export-decl.js(strict mode)
test/language/module-code/early-dup-export-dflt-id.js(default)
test/language/module-code/early-dup-export-dflt-id.js(strict mode)
test/language/module-code/early-dup-export-id-as.js(default)
test/language/module-code/early-dup-export-id-as.js(strict mode)
test/language/module-code/early-dup-export-id.js(default)
test/language/module-code/early-dup-export-id.js(strict mode)
test/language/module-code/early-export-global.js(default)
test/language/module-code/early-export-global.js(strict mode)
test/language/module-code/early-export-unresolvable.js(default)
Expand Down

0 comments on commit 62f448f

Please sign in to comment.