diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index d189d53ad4f..c49863501ae 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -169,6 +169,13 @@ This method returns the scope which has the following types:
**※2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
**※3** The scope of the closest ancestor node which has own scope. If the closest ancestor node has multiple scopes then it chooses the innermost scope (E.g., the `Program` node has a `global` scope and a `module` scope if `Program#sourceType` is `"module"`. The innermost scope is the `module` scope.).
+The returned value is a [`Scope` object](scope-manager-interface.md) defined by the `eslint-scope` package. The `Variable` objects of global variables have some additional properties.
+
+* `variable.writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only.
+* `variable.eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file.
+* `variable.eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments.
+* `variable.eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments.
+
### context.report()
The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties:
diff --git a/docs/rules/no-redeclare.md b/docs/rules/no-redeclare.md
index d0d71e5dee5..c3b8422cf3f 100644
--- a/docs/rules/no-redeclare.md
+++ b/docs/rules/no-redeclare.md
@@ -27,7 +27,7 @@ a = 10;
## Options
-This rule takes one optional argument, an object with a boolean property `"builtinGlobals"`. It defaults to `false`.
+This rule takes one optional argument, an object with a boolean property `"builtinGlobals"`. It defaults to `true`.
If set to `true`, this rule also checks redeclaration of built-in globals, such as `Object`, `Array`, `Number`...
### builtinGlobals
diff --git a/lib/config/config-ops.js b/lib/config/config-ops.js
index 48a8302d91f..beca8ca2c31 100644
--- a/lib/config/config-ops.js
+++ b/lib/config/config-ops.js
@@ -385,14 +385,14 @@ module.exports = {
case "true":
case "writeable":
case "writable":
- return "writeable";
+ return "writable";
case null:
case false:
case "false":
case "readable":
case "readonly":
- return "readable";
+ return "readonly";
default:
throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
diff --git a/lib/linter.js b/lib/linter.js
index bd3d8b23553..f2297529bb8 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -70,39 +70,41 @@ const commentParser = new ConfigCommentParser();
* @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration
* @returns {void}
*/
-function addDeclaredGlobals(globalScope, configGlobals, commentDirectives) {
- const mergedGlobalsInfo = Object.assign(
- {},
+function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) {
+
+ // Define configured global variables.
+ for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) {
/*
* `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would
* typically be caught when validating a config anyway (validity for inline global comments is checked separately).
*/
- lodash.mapValues(configGlobals, value => ({ sourceComment: null, value: ConfigOps.normalizeConfigGlobal(value) })),
- lodash.mapValues(commentDirectives.enabledGlobals, ({ comment, value }) => ({ sourceComment: comment, value }))
- );
+ const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]);
+ const commentValue = enabledGlobals[id] && enabledGlobals[id].value;
+ const value = commentValue || configValue;
+ const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments;
- Object.keys(mergedGlobalsInfo)
- .filter(name => mergedGlobalsInfo[name].value !== "off")
- .forEach(name => {
- let variable = globalScope.set.get(name);
-
- if (!variable) {
- variable = new eslintScope.Variable(name, globalScope);
- if (mergedGlobalsInfo[name].sourceComment === null) {
- variable.eslintExplicitGlobal = false;
- } else {
- variable.eslintExplicitGlobal = true;
- variable.eslintExplicitGlobalComment = mergedGlobalsInfo[name].sourceComment;
- }
- globalScope.variables.push(variable);
- globalScope.set.set(name, variable);
- }
- variable.writeable = (mergedGlobalsInfo[name].value === "writeable");
- });
+ if (value === "off") {
+ continue;
+ }
+
+ let variable = globalScope.set.get(id);
+
+ if (!variable) {
+ variable = new eslintScope.Variable(id, globalScope);
+
+ globalScope.variables.push(variable);
+ globalScope.set.set(id, variable);
+ }
+
+ variable.eslintImplicitGlobalSetting = configValue;
+ variable.eslintExplicitGlobal = sourceComments !== void 0;
+ variable.eslintExplicitGlobalComments = sourceComments;
+ variable.writeable = (value === "writable");
+ }
// mark all exported variables as such
- Object.keys(commentDirectives.exportedVariables).forEach(name => {
+ Object.keys(exportedVariables).forEach(name => {
const variable = globalScope.set.get(name);
if (variable) {
@@ -157,7 +159,7 @@ function createDisableDirectives(type, loc, value) {
* @param {string} filename The file being checked.
* @param {ASTNode} ast The top node of the AST.
* @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules
- * @returns {{configuredRules: Object, enabledGlobals: Object, exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
+ * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: Problem[], disableDirectives: DisableDirective[]}}
* A collection of the directive comments that were found, along with any problems that occurred when parsing
*/
function getDirectiveComments(filename, ast, ruleMapper) {
@@ -201,11 +203,8 @@ function getDirectiveComments(filename, ast, ruleMapper) {
break;
case "globals":
- case "global": {
- const updatedGlobals = commentParser.parseStringConfig(directiveValue, comment);
-
- Object.keys(updatedGlobals).forEach(globalName => {
- const { value } = updatedGlobals[globalName];
+ case "global":
+ for (const [id, { value }] of Object.entries(commentParser.parseStringConfig(directiveValue, comment))) {
let normalizedValue;
try {
@@ -221,13 +220,21 @@ function getDirectiveComments(filename, ast, ruleMapper) {
endColumn: comment.loc.end.column + 1,
nodeType: null
});
- return;
+ continue;
}
- enabledGlobals[globalName] = { comment, value: normalizedValue };
- });
+ if (enabledGlobals[id]) {
+ enabledGlobals[id].comments.push(comment);
+ enabledGlobals[id].value = normalizedValue;
+ } else {
+ enabledGlobals[id] = {
+ comments: [comment],
+ value: normalizedValue
+ };
+ }
+ }
break;
- }
+
case "eslint-disable":
disableDirectives.push(...createDisableDirectives("disable", comment.loc.start, directiveValue));
break;
diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js
index 4d689cc6138..6adde0f97a5 100644
--- a/lib/rules/no-redeclare.js
+++ b/lib/rules/no-redeclare.js
@@ -5,6 +5,12 @@
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("../util/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -20,11 +26,17 @@ module.exports = {
url: "https://eslint.org/docs/rules/no-redeclare"
},
+ messages: {
+ redeclared: "'{{id}}' is already defined.",
+ redeclaredAsBuiltin: "'{{id}}' is already defined as a built-in global variable.",
+ redeclaredBySyntax: "'{{id}}' is already defined by a variable declaration."
+ },
+
schema: [
{
type: "object",
properties: {
- builtinGlobals: { type: "boolean", default: false }
+ builtinGlobals: { type: "boolean", default: true }
},
additionalProperties: false
}
@@ -33,72 +45,128 @@ module.exports = {
create(context) {
const options = {
- builtinGlobals: context.options[0] && context.options[0].builtinGlobals
+ builtinGlobals: Boolean(
+ context.options.length === 0 ||
+ context.options[0].builtinGlobals
+ )
};
+ const sourceCode = context.getSourceCode();
/**
- * Find variables in a given scope and flag redeclared ones.
- * @param {Scope} scope - An eslint-scope scope object.
- * @returns {void}
- * @private
+ * Iterate declarations of a given variable.
+ * @param {escope.variable} variable The variable object to iterate declarations.
+ * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations.
*/
- function findVariablesInScope(scope) {
- scope.variables.forEach(variable => {
- const hasBuiltin = options.builtinGlobals && "writeable" in variable;
- const count = (hasBuiltin ? 1 : 0) + variable.identifiers.length;
+ function *iterateDeclarations(variable) {
+ if (options.builtinGlobals && (
+ variable.eslintImplicitGlobalSetting === "readonly" ||
+ variable.eslintImplicitGlobalSetting === "writable"
+ )) {
+ yield { type: "builtin" };
+ }
- if (count >= 2) {
- variable.identifiers.sort((a, b) => a.range[1] - b.range[1]);
+ for (const id of variable.identifiers) {
+ yield { type: "syntax", node: id, loc: id.loc };
+ }
- for (let i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) {
- context.report({ node: variable.identifiers[i], message: "'{{a}}' is already defined.", data: { a: variable.name } });
- }
+ if (variable.eslintExplicitGlobalComments) {
+ for (const comment of variable.eslintExplicitGlobalComments) {
+ yield {
+ type: "comment",
+ node: comment,
+ loc: astUtils.getNameLocationInGlobalDirectiveComment(
+ sourceCode,
+ comment,
+ variable.name
+ )
+ };
}
- });
-
+ }
}
/**
- * Find variables in the current scope.
- * @param {ASTNode} node - The Program node.
+ * Find variables in a given scope and flag redeclared ones.
+ * @param {Scope} scope - An eslint-scope scope object.
* @returns {void}
* @private
*/
- function checkForGlobal(node) {
- const scope = context.getScope(),
- parserOptions = context.parserOptions,
- ecmaFeatures = parserOptions.ecmaFeatures || {};
-
- // Nodejs env or modules has a special scope.
- if (ecmaFeatures.globalReturn || node.sourceType === "module") {
- findVariablesInScope(scope.childScopes[0]);
- } else {
- findVariablesInScope(scope);
+ function findVariablesInScope(scope) {
+ for (const variable of scope.variables) {
+ const [
+ declaration,
+ ...extraDeclarations
+ ] = iterateDeclarations(variable);
+
+ if (extraDeclarations.length === 0) {
+ continue;
+ }
+
+ /*
+ * If the type of a declaration is different from the type of
+ * the first declaration, it shows the location of the first
+ * declaration.
+ */
+ const detailMessageId = declaration.type === "builtin"
+ ? "redeclaredAsBuiltin"
+ : "redeclaredBySyntax";
+ const data = { id: variable.name };
+
+ // Report extra declarations.
+ for (const { type, node, loc } of extraDeclarations) {
+ const messageId = type === declaration.type
+ ? "redeclared"
+ : detailMessageId;
+
+ context.report({ node, loc, messageId, data });
+ }
}
}
/**
* Find variables in the current scope.
+ * @param {ASTNode} node The node of the current scope.
* @returns {void}
* @private
*/
- function checkForBlock() {
- findVariablesInScope(context.getScope());
- }
+ function checkForBlock(node) {
+ const scope = context.getScope();
- if (context.parserOptions.ecmaVersion >= 6) {
- return {
- Program: checkForGlobal,
- BlockStatement: checkForBlock,
- SwitchStatement: checkForBlock
- };
+ /*
+ * In ES5, some node type such as `BlockStatement` doesn't have that scope.
+ * `scope.block` is a different node in such a case.
+ */
+ if (scope.block === node) {
+ findVariablesInScope(scope);
+ }
}
+
return {
- Program: checkForGlobal,
+ Program() {
+ const scope = context.getScope();
+
+ findVariablesInScope(scope);
+
+ // Node.js or ES modules has a special scope.
+ if (
+ scope.type === "global" &&
+ scope.childScopes[0] &&
+
+ // The special scope's block is the Program node.
+ scope.block === scope.childScopes[0].block
+ ) {
+ findVariablesInScope(scope.childScopes[0]);
+ }
+ },
+
FunctionDeclaration: checkForBlock,
FunctionExpression: checkForBlock,
- ArrowFunctionExpression: checkForBlock
- };
+ ArrowFunctionExpression: checkForBlock,
+ BlockStatement: checkForBlock,
+ ForStatement: checkForBlock,
+ ForInStatement: checkForBlock,
+ ForOfStatement: checkForBlock,
+ SwitchStatement: checkForBlock
+ };
}
};
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 1482a0238ca..8e32951bdae 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -9,7 +9,6 @@
// Requirements
//------------------------------------------------------------------------------
-const lodash = require("lodash");
const astUtils = require("../util/ast-utils");
//------------------------------------------------------------------------------
@@ -588,39 +587,6 @@ module.exports = {
return unusedVars;
}
- /**
- * Gets the index of a given variable name in a given comment.
- * @param {eslint-scope.Variable} variable - A variable to get.
- * @param {ASTNode} comment - A comment node which includes the variable name.
- * @returns {number} The index of the variable name's location.
- * @private
- */
- function getColumnInComment(variable, comment) {
- const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(variable.name)}(?:$|[\\s,:])`, "gu");
-
- // To ignore the first text "global".
- namePattern.lastIndex = comment.value.indexOf("global") + 6;
-
- // Search a given variable name.
- const match = namePattern.exec(comment.value);
-
- return match ? match.index + 1 : 0;
- }
-
- /**
- * Creates the correct location of a given variables.
- * The location is at its name string in a `/*global` comment.
- *
- * @param {eslint-scope.Variable} variable - A variable to get its location.
- * @returns {{line: number, column: number}} The location object for the variable.
- * @private
- */
- function getLocation(variable) {
- const comment = variable.eslintExplicitGlobalComment;
-
- return sourceCode.getLocFromIndex(comment.range[0] + 2 + getColumnInComment(variable, comment));
- }
-
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
@@ -632,14 +598,8 @@ module.exports = {
for (let i = 0, l = unusedVars.length; i < l; ++i) {
const unusedVar = unusedVars[i];
- if (unusedVar.eslintExplicitGlobal) {
- context.report({
- node: programNode,
- loc: getLocation(unusedVar),
- message: getDefinedMessage(unusedVar),
- data: unusedVar
- });
- } else if (unusedVar.defs.length > 0) {
+ // Report the first declaration.
+ if (unusedVar.defs.length > 0) {
context.report({
node: unusedVar.identifiers[0],
message: unusedVar.references.some(ref => ref.isWrite())
@@ -647,6 +607,17 @@ module.exports = {
: getDefinedMessage(unusedVar),
data: unusedVar
});
+
+ // If there are no regular declaration, report the first `/*globals*/` comment directive.
+ } else if (unusedVar.eslintExplicitGlobalComments) {
+ const directiveComment = unusedVar.eslintExplicitGlobalComments[0];
+
+ context.report({
+ node: programNode,
+ loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name),
+ message: getDefinedMessage(unusedVar),
+ data: unusedVar
+ });
}
}
}
diff --git a/lib/util/ast-utils.js b/lib/util/ast-utils.js
index 85205a75d24..0f9ba290a9d 100644
--- a/lib/util/ast-utils.js
+++ b/lib/util/ast-utils.js
@@ -11,6 +11,7 @@
const esutils = require("esutils");
const espree = require("espree");
+const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
@@ -1342,5 +1343,29 @@ module.exports = {
}
return false;
+ },
+
+ /**
+ * Get the `loc` object of a given name in a `/*globals` directive comment.
+ * @param {SourceCode} sourceCode The source code to convert index to loc.
+ * @param {Comment} comment The `/*globals` directive comment which include the name.
+ * @param {string} name The name to find.
+ * @returns {SourceLocation} The `loc` object.
+ */
+ getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
+ const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
+
+ // To ignore the first text "global".
+ namePattern.lastIndex = comment.value.indexOf("global") + 6;
+
+ // Search a given variable name.
+ const match = namePattern.exec(comment.value);
+
+ // Convert the index to loc.
+ return sourceCode.getLocFromIndex(
+ comment.range[0] +
+ "/*".length +
+ (match ? match.index + 1 : 0)
+ );
}
};
diff --git a/package.json b/package.json
index d2ea8a004cd..c3b47d47d9d 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"@babel/core": "^7.4.3",
"@babel/polyfill": "^7.4.3",
"@babel/preset-env": "^7.4.3",
+ "acorn": "^6.1.1",
"babel-loader": "^8.0.5",
"beefy": "^2.1.8",
"brfs": "^2.0.2",
diff --git a/tests/lib/config/config-initializer.js b/tests/lib/config/config-initializer.js
index d306db1b1c3..377633ec675 100644
--- a/tests/lib/config/config-initializer.js
+++ b/tests/lib/config/config-initializer.js
@@ -70,7 +70,7 @@ describe("configInitializer", () => {
// copy into clean area so as not to get "infected" by this project's .eslintrc files
before(() => {
- fixtureDir = `${os.tmpdir()}/eslint/fixtures/config-initializer`;
+ fixtureDir = path.join(os.tmpdir(), "eslint/fixtures/config-initializer");
sh.mkdir("-p", fixtureDir);
sh.cp("-r", "./tests/fixtures/config-initializer/.", fixtureDir);
fixtureDir = fs.realpathSync(fixtureDir);
diff --git a/tests/lib/config/config-ops.js b/tests/lib/config/config-ops.js
index ffa65660d58..6a1f0b219d7 100644
--- a/tests/lib/config/config-ops.js
+++ b/tests/lib/config/config-ops.js
@@ -902,16 +902,16 @@ describe("ConfigOps", () => {
describe("normalizeConfigGlobal", () => {
[
["off", "off"],
- [true, "writeable"],
- ["true", "writeable"],
- [false, "readable"],
- ["false", "readable"],
- [null, "readable"],
- ["writeable", "writeable"],
- ["writable", "writeable"],
- ["readable", "readable"],
- ["readonly", "readable"],
- ["writable", "writeable"]
+ [true, "writable"],
+ ["true", "writable"],
+ [false, "readonly"],
+ ["false", "readonly"],
+ [null, "readonly"],
+ ["writeable", "writable"],
+ ["writable", "writable"],
+ ["readable", "readonly"],
+ ["readonly", "readonly"],
+ ["writable", "writable"]
].forEach(([input, output]) => {
it(util.inspect(input), () => {
assert.strictEqual(ConfigOps.normalizeConfigGlobal(input), output);
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index bf8812c7bea..3df16711105 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -3469,18 +3469,18 @@ describe("Linter", () => {
const foo = getVariable(scope, "foo");
- assert.strictEqual(true, foo.eslintExplicitGlobal);
- assert.strictEqual(comments[0], foo.eslintExplicitGlobalComment);
+ assert.strictEqual(foo.eslintExplicitGlobal, true);
+ assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]);
const bar = getVariable(scope, "bar");
- assert.strictEqual(true, bar.eslintExplicitGlobal);
- assert.strictEqual(comments[1], bar.eslintExplicitGlobalComment);
+ assert.strictEqual(bar.eslintExplicitGlobal, true);
+ assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]);
const baz = getVariable(scope, "baz");
- assert.strictEqual(true, baz.eslintExplicitGlobal);
- assert.strictEqual(comments[1], baz.eslintExplicitGlobalComment);
+ assert.strictEqual(baz.eslintExplicitGlobal, true);
+ assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]);
ok = true;
}
diff --git a/tests/lib/rules/no-redeclare.js b/tests/lib/rules/no-redeclare.js
index 23a45f02185..15d8275d02b 100644
--- a/tests/lib/rules/no-redeclare.js
+++ b/tests/lib/rules/no-redeclare.js
@@ -9,13 +9,15 @@
// Requirements
//------------------------------------------------------------------------------
-const rule = require("../../../lib/rules/no-redeclare"),
- RuleTester = require("../../../lib/testers/rule-tester");
+const path = require("path");
+const rule = require("../../../lib/rules/no-redeclare");
+const RuleTester = require("../../../lib/testers/rule-tester");
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
+const looseParserPath = path.resolve(__dirname, "../../tools/loose-parser.js");
const ruleTester = new RuleTester();
ruleTester.run("no-redeclare", rule, {
@@ -28,11 +30,9 @@ ruleTester.run("no-redeclare", rule, {
ecmaVersion: 6
}
},
- "var Object = 0;",
{ code: "var Object = 0;", options: [{ builtinGlobals: false }] },
{ code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { sourceType: "module" } },
{ code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaFeatures: { globalReturn: true } } },
- { code: "var top = 0;", env: { browser: true } },
{ code: "var top = 0;", options: [{ builtinGlobals: true }] },
{ code: "var top = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaFeatures: { globalReturn: true } }, env: { browser: true } },
{ code: "var top = 0;", options: [{ builtinGlobals: true }], parserOptions: { sourceType: "module" }, env: { browser: true } },
@@ -40,6 +40,32 @@ ruleTester.run("no-redeclare", rule, {
code: "var self = 1",
options: [{ builtinGlobals: true }],
env: { browser: false }
+ },
+
+ // Comments and built-ins.
+ {
+ code: "/*globals Array */",
+ options: [{ builtinGlobals: false }]
+ },
+ {
+ code: "/*globals a */",
+ options: [{ builtinGlobals: false }],
+ globals: { a: "readonly" }
+ },
+ {
+ code: "/*globals a */",
+ options: [{ builtinGlobals: false }],
+ globals: { a: "writable" }
+ },
+ {
+ code: "/*globals a:off */",
+ options: [{ builtinGlobals: true }],
+ globals: { a: "readonly" }
+ },
+ {
+ code: "/*globals a */",
+ options: [{ builtinGlobals: true }],
+ globals: { a: "off" }
}
],
invalid: [
@@ -57,13 +83,13 @@ ruleTester.run("no-redeclare", rule, {
{
code: "var Object = 0;",
options: [{ builtinGlobals: true }],
- errors: [{ message: "'Object' is already defined.", type: "Identifier" }]
+ errors: [{ message: "'Object' is already defined as a built-in global variable.", type: "Identifier" }]
},
{
code: "var top = 0;",
options: [{ builtinGlobals: true }],
env: { browser: true },
- errors: [{ message: "'top' is already defined.", type: "Identifier" }]
+ errors: [{ message: "'top' is already defined as a built-in global variable.", type: "Identifier" }]
},
{
code: "var a; var {a = 0, b: Object = 0} = {};",
@@ -71,7 +97,7 @@ ruleTester.run("no-redeclare", rule, {
parserOptions: { ecmaVersion: 6 },
errors: [
{ message: "'a' is already defined.", type: "Identifier" },
- { message: "'Object' is already defined.", type: "Identifier" }
+ { message: "'Object' is already defined as a built-in global variable.", type: "Identifier" }
]
},
{
@@ -98,13 +124,203 @@ ruleTester.run("no-redeclare", rule, {
{ message: "'a' is already defined.", type: "Identifier" }
]
},
-
- // Notifications of readonly are moved from no-undef: https://github.com/eslint/eslint/issues/4504
{
code: "/*global b:false*/ var b = 1;",
options: [{ builtinGlobals: true }],
errors: [
- { message: "'b' is already defined.", type: "Identifier" }
+ { message: "'b' is already defined by a variable declaration.", type: "Block" }
+ ]
+ },
+ {
+ code: "/*global b:true*/ var b = 1;",
+ options: [{ builtinGlobals: true }],
+ errors: [
+ { message: "'b' is already defined by a variable declaration.", type: "Block" }
+ ]
+ },
+ {
+ code: "function f() { var a; var a; }",
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "function f(a) { var a; }",
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "function f() { var a; if (test) { var a; } }",
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "for (var a, a;;);",
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+
+ {
+ code: "var Object = 0;",
+ errors: [
+ { message: "'Object' is already defined as a built-in global variable.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "var top = 0;",
+ env: { browser: true },
+ errors: [
+ { message: "'top' is already defined as a built-in global variable.", type: "Identifier" }
+ ]
+ },
+
+ // let/const
+ {
+ code: "let a; let a;",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "let a; let a;",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015, sourceType: "module" },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "let a; let a;",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015, ecmaFeatures: { globalReturn: true } },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "let a; const a = 0;",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "const a = 0; const a = 0;",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "if (test) { let a; let a; }",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "switch (test) { case 0: let a; let a; }",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "for (let a, a;;);",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "for (let [a, a] in xs);",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "for (let [a, a] of xs);",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "function f() { let a; let a; }",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "function f(a) { let a; }",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "function f() { if (test) { let a; let a; } }",
+ parser: looseParserPath,
+ parserOptions: { ecmaVersion: 2015 },
+ errors: [
+ { message: "'a' is already defined.", type: "Identifier" }
+ ]
+ },
+
+ // Comments and built-ins.
+ {
+ code: "/*globals Array */",
+ options: [{ builtinGlobals: true }],
+ errors: [
+ { message: "'Array' is already defined as a built-in global variable.", type: "Block" }
+ ]
+ },
+ {
+ code: "/*globals a */",
+ options: [{ builtinGlobals: true }],
+ globals: { a: "readonly" },
+ errors: [
+ { message: "'a' is already defined as a built-in global variable.", type: "Block" }
+ ]
+ },
+ {
+ code: "/*globals a */",
+ options: [{ builtinGlobals: true }],
+ globals: { a: "writable" },
+ errors: [
+ { message: "'a' is already defined as a built-in global variable.", type: "Block" }
+ ]
+ },
+ {
+ code: "/*globals a */ /*globals a */",
+ errors: [
+ { message: "'a' is already defined.", type: "Block", column: 26 }
+ ]
+ },
+ {
+ code: "/*globals a */ /*globals a */ var a = 0",
+ options: [{ builtinGlobals: true }],
+ globals: { a: "writable" },
+ errors: [
+ { message: "'a' is already defined as a built-in global variable.", type: "Block", column: 11 },
+ { message: "'a' is already defined as a built-in global variable.", type: "Block", column: 26 },
+ { message: "'a' is already defined as a built-in global variable.", type: "Identifier", column: 35 }
]
}
]
diff --git a/tests/tools/loose-parser.js b/tests/tools/loose-parser.js
new file mode 100644
index 00000000000..abb3aeb5bd0
--- /dev/null
+++ b/tests/tools/loose-parser.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Define a custom parser to ignore recoverable syntax errors.
+ * @author Toru Nagashima
+ *
+ * no-redeclare rule uses this parser to check redeclarations.
+ */
+"use strict";
+
+const acorn = require("acorn");
+const espree = require("espree/lib/espree");
+
+// eslint-disable-next-line valid-jsdoc
+/**
+ * Define the parser which ignores recoverable errors.
+ * @returns {(parser:acorn.Parser) => acorn.Parser} The function that defines loose parser.
+ */
+function loose() {
+ return Parser => class LooseParser extends Parser {
+ raiseRecoverable() { // eslint-disable-line class-methods-use-this
+ // ignore
+ }
+ };
+}
+
+const LooseEspree = acorn.Parser.extend(espree(), loose());
+
+module.exports = {
+ parse(code, options) {
+ return new LooseEspree(options, code).parse();
+ }
+};