mirror of
https://git.proxmox.com/git/pve-eslint
synced 2025-10-05 09:49:11 +00:00
5403 lines
203 KiB
JavaScript
5403 lines
203 KiB
JavaScript
/**
|
|
* @fileoverview Tests for eslint object.
|
|
* @author Nicholas C. Zakas
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helper
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* To make sure this works in both browsers and Node.js
|
|
* @param {string} name Name of the module to require
|
|
* @param {Object} windowName name of the window
|
|
* @returns {Object} Required object
|
|
* @private
|
|
*/
|
|
function compatRequire(name, windowName) {
|
|
if (typeof window === "object") { // eslint-disable-line no-undef
|
|
return window[windowName || name]; // eslint-disable-line no-undef
|
|
}
|
|
if (typeof require === "function") {
|
|
return require(name);
|
|
}
|
|
throw new Error(`Cannot find object '${name}'.`);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Requirements
|
|
//------------------------------------------------------------------------------
|
|
|
|
const assert = require("chai").assert,
|
|
sinon = require("sinon"),
|
|
esprima = require("esprima"),
|
|
testParsers = require("../../fixtures/parsers/linter-test-parsers");
|
|
|
|
const { Linter } = compatRequire("../../../lib/linter", "eslint");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Constants
|
|
//------------------------------------------------------------------------------
|
|
|
|
const TEST_CODE = "var answer = 6 * 7;",
|
|
BROKEN_TEST_CODE = "var;";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helpers
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Get variables in the current scope
|
|
* @param {Object} scope current scope
|
|
* @param {string} name name of the variable to look for
|
|
* @returns {ASTNode|null} The variable object
|
|
* @private
|
|
*/
|
|
function getVariable(scope, name) {
|
|
return scope.variables.find(v => v.name === name) || null;
|
|
}
|
|
|
|
/**
|
|
* `eslint-env` comments are processed by doing a full source text match before parsing.
|
|
* As a result, if this source file contains `eslint- env` followed by an environment in a string,
|
|
* it will actually enable the given envs for this source file. This variable is used to avoid having a string
|
|
* like that appear in the code.
|
|
*/
|
|
const ESLINT_ENV = "eslint-env";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Tests
|
|
//------------------------------------------------------------------------------
|
|
|
|
describe("Linter", () => {
|
|
const filename = "filename.js";
|
|
|
|
/** @type {InstanceType<import("../../../lib/linter/linter.js")["Linter"]>} */
|
|
let linter;
|
|
|
|
beforeEach(() => {
|
|
linter = new Linter();
|
|
});
|
|
|
|
afterEach(() => {
|
|
sinon.verifyAndRestore();
|
|
});
|
|
|
|
describe("Static Members", () => {
|
|
describe("version", () => {
|
|
it("should return same version as instance property", () => {
|
|
assert.strictEqual(Linter.version, linter.version);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when using events", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("an error should be thrown when an error occurs inside of an event handler", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
|
|
linter.defineRule("checker", () => ({
|
|
Program() {
|
|
throw new Error("Intentional error.");
|
|
}
|
|
}));
|
|
|
|
assert.throws(() => {
|
|
linter.verify(code, config, filename);
|
|
}, `Intentional error.\nOccurred while linting ${filename}:1`);
|
|
});
|
|
|
|
it("does not call rule listeners with a `this` value", () => {
|
|
const spy = sinon.spy();
|
|
|
|
linter.defineRule("checker", () => ({ Program: spy }));
|
|
linter.verify("foo", { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
assert.strictEqual(spy.firstCall.thisValue, void 0);
|
|
});
|
|
|
|
it("does not allow listeners to use special EventEmitter values", () => {
|
|
const spy = sinon.spy();
|
|
|
|
linter.defineRule("checker", () => ({ newListener: spy }));
|
|
linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } });
|
|
assert(spy.notCalled);
|
|
});
|
|
|
|
it("has all the `parent` properties on nodes when the rule listeners are created", () => {
|
|
const spy = sinon.spy(context => {
|
|
const ast = context.getSourceCode().ast;
|
|
|
|
assert.strictEqual(ast.body[0].parent, ast);
|
|
assert.strictEqual(ast.body[0].expression.parent, ast.body[0]);
|
|
assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression);
|
|
assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression);
|
|
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
|
|
linter.verify("foo + bar", { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("context.getSourceLines()", () => {
|
|
|
|
it("should get proper lines when using \\n as a line break", () => {
|
|
const code = "a;\nb;";
|
|
const spy = sinon.spy(context => {
|
|
assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should get proper lines when using \\r\\n as a line break", () => {
|
|
const code = "a;\r\nb;";
|
|
const spy = sinon.spy(context => {
|
|
assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should get proper lines when using \\r as a line break", () => {
|
|
const code = "a;\rb;";
|
|
const spy = sinon.spy(context => {
|
|
assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should get proper lines when using \\u2028 as a line break", () => {
|
|
const code = "a;\u2028b;";
|
|
const spy = sinon.spy(context => {
|
|
assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should get proper lines when using \\u2029 as a line break", () => {
|
|
const code = "a;\u2029b;";
|
|
const spy = sinon.spy(context => {
|
|
assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
|
|
});
|
|
|
|
describe("getSourceCode()", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("should retrieve SourceCode object after reset", () => {
|
|
linter.verify(code, {}, filename, true);
|
|
|
|
const sourceCode = linter.getSourceCode();
|
|
|
|
assert.isObject(sourceCode);
|
|
assert.strictEqual(sourceCode.text, code);
|
|
assert.isObject(sourceCode.ast);
|
|
});
|
|
|
|
it("should retrieve SourceCode object without reset", () => {
|
|
linter.verify(code, {}, filename);
|
|
|
|
const sourceCode = linter.getSourceCode();
|
|
|
|
assert.isObject(sourceCode);
|
|
assert.strictEqual(sourceCode.text, code);
|
|
assert.isObject(sourceCode.ast);
|
|
});
|
|
|
|
});
|
|
|
|
describe("context.getSource()", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("should retrieve all text when used without parameters", () => {
|
|
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
assert.strictEqual(context.getSource(), TEST_CODE);
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve all text for root node", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node), TEST_CODE);
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should clamp to valid range when retrieving characters before start of source", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE);
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve all text for binary expression", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node), "6 * 7");
|
|
});
|
|
return { BinaryExpression: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve all text plus two characters before for binary expression", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node, 2), "= 6 * 7");
|
|
});
|
|
return { BinaryExpression: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve all text plus one character after for binary expression", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;");
|
|
});
|
|
return { BinaryExpression: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve all text plus two characters before and one character after for binary expression", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;");
|
|
});
|
|
return { BinaryExpression: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
});
|
|
|
|
describe("when calling context.getAncestors", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("should retrieve all ancestors when used", () => {
|
|
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const ancestors = context.getAncestors();
|
|
|
|
assert.strictEqual(ancestors.length, 3);
|
|
});
|
|
return { BinaryExpression: spy };
|
|
});
|
|
|
|
linter.verify(code, config, filename, true);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve empty ancestors for root node", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const ancestors = context.getAncestors();
|
|
|
|
assert.strictEqual(ancestors.length, 0);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when calling context.getNodeByRangeIndex", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("should retrieve a node starting at the given index", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve a node containing the given index", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve a node that is exactly the given index", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
const node = context.getNodeByRangeIndex(13);
|
|
|
|
assert.strictEqual(node.type, "Literal");
|
|
assert.strictEqual(node.value, 6);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve a node ending with the given index", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the deepest node containing the given index", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
const node1 = context.getNodeByRangeIndex(14);
|
|
|
|
assert.strictEqual(node1.type, "BinaryExpression");
|
|
|
|
const node2 = context.getNodeByRangeIndex(3);
|
|
|
|
assert.strictEqual(node2.type, "VariableDeclaration");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
it("should return null if the index is outside the range of any node", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
const node1 = context.getNodeByRangeIndex(-1);
|
|
|
|
assert.isNull(node1);
|
|
|
|
const node2 = context.getNodeByRangeIndex(-99);
|
|
|
|
assert.isNull(node2);
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
|
|
describe("when calling context.getScope", () => {
|
|
const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });";
|
|
|
|
it("should retrieve the global scope correctly from a Program", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "global");
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from a FunctionDeclaration", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
});
|
|
return { FunctionDeclaration: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledTwice);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from a LabeledStatement", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block.id.name, "foo");
|
|
});
|
|
return { LabeledStatement: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block.type, "ArrowFunctionExpression");
|
|
});
|
|
|
|
return { ReturnStatement: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within an SwitchStatement", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "switch");
|
|
assert.strictEqual(scope.block.type, "SwitchStatement");
|
|
});
|
|
|
|
return { SwitchStatement: spy };
|
|
});
|
|
|
|
linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within a BlockStatement", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.block.type, "BlockStatement");
|
|
});
|
|
|
|
return { BlockStatement: spy };
|
|
});
|
|
|
|
linter.verify("var x; {let y = 1}", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within a nested block statement", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.block.type, "BlockStatement");
|
|
});
|
|
|
|
return { BlockStatement: spy };
|
|
});
|
|
|
|
linter.verify("if (true) { let x = 1 }", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within a FunctionDeclaration", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block.type, "FunctionDeclaration");
|
|
});
|
|
|
|
return { FunctionDeclaration: spy };
|
|
});
|
|
|
|
linter.verify("function foo() {}", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the function scope correctly from within a FunctionExpression", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block.type, "FunctionExpression");
|
|
});
|
|
|
|
return { FunctionExpression: spy };
|
|
});
|
|
|
|
linter.verify("(function foo() {})();", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve the catch scope correctly from within a CatchClause", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "catch");
|
|
assert.strictEqual(scope.block.type, "CatchClause");
|
|
});
|
|
|
|
return { CatchClause: spy };
|
|
});
|
|
|
|
linter.verify("try {} catch (err) {}", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve module scope correctly from an ES6 module", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "module");
|
|
});
|
|
|
|
return { AssignmentExpression: spy };
|
|
});
|
|
|
|
linter.verify("var foo = {}; foo.bar = 1;", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should retrieve function scope correctly when globalReturn is true", () => {
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
});
|
|
|
|
return { AssignmentExpression: spy };
|
|
});
|
|
|
|
linter.verify("var foo = {}; foo.bar = 1;", config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("marking variables as used", () => {
|
|
it("should mark variables in current scope as used", () => {
|
|
const code = "var a = 1, b = 2;";
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
assert.isTrue(context.markVariableAsUsed("a"));
|
|
|
|
const scope = context.getScope();
|
|
|
|
assert.isTrue(getVariable(scope, "a").eslintUsed);
|
|
assert.notOk(getVariable(scope, "b").eslintUsed);
|
|
});
|
|
|
|
return { "Program:exit": spy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
it("should mark variables in function args as used", () => {
|
|
const code = "function abc(a, b) { return 1; }";
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
assert.isTrue(context.markVariableAsUsed("a"));
|
|
|
|
const scope = context.getScope();
|
|
|
|
assert.isTrue(getVariable(scope, "a").eslintUsed);
|
|
assert.notOk(getVariable(scope, "b").eslintUsed);
|
|
});
|
|
|
|
return { ReturnStatement: spy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
it("should mark variables in higher scopes as used", () => {
|
|
const code = "var a, b; function abc() { return 1; }";
|
|
let returnSpy, exitSpy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
returnSpy = sinon.spy(() => {
|
|
assert.isTrue(context.markVariableAsUsed("a"));
|
|
});
|
|
exitSpy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.isTrue(getVariable(scope, "a").eslintUsed);
|
|
assert.notOk(getVariable(scope, "b").eslintUsed);
|
|
});
|
|
|
|
return { ReturnStatement: returnSpy, "Program:exit": exitSpy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(returnSpy && returnSpy.calledOnce);
|
|
assert(exitSpy && exitSpy.calledOnce);
|
|
});
|
|
|
|
it("should mark variables in Node.js environment as used", () => {
|
|
const code = "var a = 1, b = 2;";
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const globalScope = context.getScope(),
|
|
childScope = globalScope.childScopes[0];
|
|
|
|
assert.isTrue(context.markVariableAsUsed("a"));
|
|
|
|
assert.isTrue(getVariable(childScope, "a").eslintUsed);
|
|
assert.isUndefined(getVariable(childScope, "b").eslintUsed);
|
|
});
|
|
|
|
return { "Program:exit": spy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" }, env: { node: true } });
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should mark variables in modules as used", () => {
|
|
const code = "var a = 1, b = 2;";
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const globalScope = context.getScope(),
|
|
childScope = globalScope.childScopes[0];
|
|
|
|
assert.isTrue(context.markVariableAsUsed("a"));
|
|
|
|
assert.isTrue(getVariable(childScope, "a").eslintUsed);
|
|
assert.isUndefined(getVariable(childScope, "b").eslintUsed);
|
|
});
|
|
|
|
return { "Program:exit": spy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should return false if the given variable is not found", () => {
|
|
const code = "var a = 1, b = 2;";
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
assert.isFalse(context.markVariableAsUsed("c"));
|
|
});
|
|
|
|
return { "Program:exit": spy };
|
|
});
|
|
|
|
linter.verify(code, { rules: { checker: "error" } });
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code", () => {
|
|
const code = TEST_CODE;
|
|
|
|
it("events for each node type should fire", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
|
|
// spies for various AST node types
|
|
const spyLiteral = sinon.spy(),
|
|
spyVariableDeclarator = sinon.spy(),
|
|
spyVariableDeclaration = sinon.spy(),
|
|
spyIdentifier = sinon.spy(),
|
|
spyBinaryExpression = sinon.spy();
|
|
|
|
linter.defineRule("checker", () => ({
|
|
Literal: spyLiteral,
|
|
VariableDeclarator: spyVariableDeclarator,
|
|
VariableDeclaration: spyVariableDeclaration,
|
|
Identifier: spyIdentifier,
|
|
BinaryExpression: spyBinaryExpression
|
|
}));
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
sinon.assert.calledOnce(spyVariableDeclaration);
|
|
sinon.assert.calledOnce(spyVariableDeclarator);
|
|
sinon.assert.calledOnce(spyIdentifier);
|
|
sinon.assert.calledTwice(spyLiteral);
|
|
sinon.assert.calledOnce(spyBinaryExpression);
|
|
});
|
|
|
|
it("should throw an error if a rule reports a problem without a message", () => {
|
|
linter.defineRule("invalid-report", context => ({
|
|
Program(node) {
|
|
context.report({ node });
|
|
}
|
|
}));
|
|
|
|
assert.throws(
|
|
() => linter.verify("foo", { rules: { "invalid-report": "error" } }),
|
|
TypeError,
|
|
"Missing `message` property in report() call; add a message that describes the linting problem."
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when config has shared settings for rules", () => {
|
|
const code = "test-rule";
|
|
|
|
it("should pass settings to all rules", () => {
|
|
linter.defineRule(code, context => ({
|
|
Literal(node) {
|
|
context.report(node, context.settings.info);
|
|
}
|
|
}));
|
|
|
|
const config = { rules: {}, settings: { info: "Hello" } };
|
|
|
|
config.rules[code] = 1;
|
|
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Hello");
|
|
});
|
|
|
|
it("should not have any settings if they were not passed in", () => {
|
|
linter.defineRule(code, context => ({
|
|
Literal(node) {
|
|
if (Object.getOwnPropertyNames(context.settings).length !== 0) {
|
|
context.report(node, "Settings should be empty");
|
|
}
|
|
}
|
|
}));
|
|
|
|
const config = { rules: {} };
|
|
|
|
config.rules[code] = 1;
|
|
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when config has parseOptions", () => {
|
|
|
|
it("should pass ecmaFeatures to all rules when provided on config", () => {
|
|
|
|
const parserOptions = {
|
|
ecmaFeatures: {
|
|
jsx: true,
|
|
globalReturn: true
|
|
}
|
|
};
|
|
|
|
linter.defineRule("test-rule", sinon.mock().withArgs(
|
|
sinon.match({ parserOptions })
|
|
).returns({}));
|
|
|
|
const config = { rules: { "test-rule": 2 }, parserOptions };
|
|
|
|
linter.verify("0", config, filename);
|
|
});
|
|
|
|
it("should pass parserOptions to all rules when default parserOptions is used", () => {
|
|
|
|
const parserOptions = {};
|
|
|
|
linter.defineRule("test-rule", sinon.mock().withArgs(
|
|
sinon.match({ parserOptions })
|
|
).returns({}));
|
|
|
|
const config = { rules: { "test-rule": 2 } };
|
|
|
|
linter.verify("0", config, filename);
|
|
});
|
|
|
|
});
|
|
|
|
describe("when a custom parser is defined using defineParser", () => {
|
|
|
|
it("should be able to define a custom parser", () => {
|
|
const parser = {
|
|
parseForESLint: function parse(code, options) {
|
|
return {
|
|
ast: esprima.parse(code, options),
|
|
services: {
|
|
test: {
|
|
getMessage() {
|
|
return "Hi!";
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
linter.defineParser("test-parser", parser);
|
|
const config = { rules: {}, parser: "test-parser" };
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
});
|
|
|
|
describe("when config has parser", () => {
|
|
|
|
it("should pass parser as parserPath to all rules when provided on config", () => {
|
|
|
|
const alternateParser = "esprima";
|
|
|
|
linter.defineParser("esprima", esprima);
|
|
linter.defineRule("test-rule", sinon.mock().withArgs(
|
|
sinon.match({ parserPath: alternateParser })
|
|
).returns({}));
|
|
|
|
const config = { rules: { "test-rule": 2 }, parser: alternateParser };
|
|
|
|
linter.verify("0", config, filename);
|
|
});
|
|
|
|
it("should use parseForESLint() in custom parser when custom parser is specified", () => {
|
|
const config = { rules: {}, parser: "enhanced-parser" };
|
|
|
|
linter.defineParser("enhanced-parser", testParsers.enhancedParser);
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should expose parser services when using parseForESLint() and services are specified", () => {
|
|
linter.defineParser("enhanced-parser", testParsers.enhancedParser);
|
|
linter.defineRule("test-service-rule", context => ({
|
|
Literal(node) {
|
|
context.report({
|
|
node,
|
|
message: context.parserServices.test.getMessage()
|
|
});
|
|
}
|
|
}));
|
|
|
|
const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Hi!");
|
|
});
|
|
|
|
it("should use the same parserServices if source code object is reused", () => {
|
|
linter.defineParser("enhanced-parser", testParsers.enhancedParser);
|
|
linter.defineRule("test-service-rule", context => ({
|
|
Literal(node) {
|
|
context.report({
|
|
node,
|
|
message: context.parserServices.test.getMessage()
|
|
});
|
|
}
|
|
}));
|
|
|
|
const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" };
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Hi!");
|
|
|
|
const messages2 = linter.verify(linter.getSourceCode(), config, filename);
|
|
|
|
assert.strictEqual(messages2.length, 1);
|
|
assert.strictEqual(messages2[0].message, "Hi!");
|
|
});
|
|
|
|
it("should pass parser as parserPath to all rules when default parser is used", () => {
|
|
linter.defineRule("test-rule", sinon.mock().withArgs(
|
|
sinon.match({ parserPath: "espree" })
|
|
).returns({}));
|
|
|
|
const config = { rules: { "test-rule": 2 } };
|
|
|
|
linter.verify("0", config, filename);
|
|
});
|
|
|
|
});
|
|
|
|
|
|
describe("when passing in configuration values for rules", () => {
|
|
const code = "var answer = 6 * 7";
|
|
|
|
it("should be configurable by only setting the integer value", () => {
|
|
const rule = "semi",
|
|
config = { rules: {} };
|
|
|
|
config.rules[rule] = 1;
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, rule);
|
|
});
|
|
|
|
it("should be configurable by only setting the string value", () => {
|
|
const rule = "semi",
|
|
config = { rules: {} };
|
|
|
|
config.rules[rule] = "warn";
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 1);
|
|
assert.strictEqual(messages[0].ruleId, rule);
|
|
});
|
|
|
|
it("should be configurable by passing in values as an array", () => {
|
|
const rule = "semi",
|
|
config = { rules: {} };
|
|
|
|
config.rules[rule] = [1];
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, rule);
|
|
});
|
|
|
|
it("should be configurable by passing in string value as an array", () => {
|
|
const rule = "semi",
|
|
config = { rules: {} };
|
|
|
|
config.rules[rule] = ["warn"];
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 1);
|
|
assert.strictEqual(messages[0].ruleId, rule);
|
|
});
|
|
|
|
it("should not be configurable by setting other value", () => {
|
|
const rule = "semi",
|
|
config = { rules: {} };
|
|
|
|
config.rules[rule] = "1";
|
|
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should process empty config", () => {
|
|
const config = {};
|
|
const messages = linter.verify(code, config, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing /*global */ and /*globals */ blocks", () => {
|
|
|
|
it("variables should be available in global scope", () => {
|
|
const config = { rules: { checker: "error" }, globals: { Array: "off", ConfigGlobal: "writeable" } };
|
|
const code = `
|
|
/*global a b:true c:false d:readable e:writeable Math:off */
|
|
function foo() {}
|
|
/*globals f:true*/
|
|
/* global ConfigGlobal : readable */
|
|
`;
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
const a = getVariable(scope, "a"),
|
|
b = getVariable(scope, "b"),
|
|
c = getVariable(scope, "c"),
|
|
d = getVariable(scope, "d"),
|
|
e = getVariable(scope, "e"),
|
|
f = getVariable(scope, "f"),
|
|
mathGlobal = getVariable(scope, "Math"),
|
|
arrayGlobal = getVariable(scope, "Array"),
|
|
configGlobal = getVariable(scope, "ConfigGlobal");
|
|
|
|
assert.strictEqual(a.name, "a");
|
|
assert.strictEqual(a.writeable, false);
|
|
assert.strictEqual(b.name, "b");
|
|
assert.strictEqual(b.writeable, true);
|
|
assert.strictEqual(c.name, "c");
|
|
assert.strictEqual(c.writeable, false);
|
|
assert.strictEqual(d.name, "d");
|
|
assert.strictEqual(d.writeable, false);
|
|
assert.strictEqual(e.name, "e");
|
|
assert.strictEqual(e.writeable, true);
|
|
assert.strictEqual(f.name, "f");
|
|
assert.strictEqual(f.writeable, true);
|
|
assert.strictEqual(mathGlobal, null);
|
|
assert.strictEqual(arrayGlobal, null);
|
|
assert.strictEqual(configGlobal.name, "ConfigGlobal");
|
|
assert.strictEqual(configGlobal.writeable, false);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => {
|
|
const code = "/* global a b : true c: false*/";
|
|
|
|
it("variables should be available in global scope", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
a = getVariable(scope, "a"),
|
|
b = getVariable(scope, "b"),
|
|
c = getVariable(scope, "c");
|
|
|
|
assert.strictEqual(a.name, "a");
|
|
assert.strictEqual(a.writeable, false);
|
|
assert.strictEqual(b.name, "b");
|
|
assert.strictEqual(b.writeable, true);
|
|
assert.strictEqual(c.name, "c");
|
|
assert.strictEqual(c.writeable, false);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing a /*global */ block with specific variables", () => {
|
|
const code = "/* global toString hasOwnProperty valueOf: true */";
|
|
|
|
it("should not throw an error if comment block has global variables which are Object.prototype contains", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
|
|
linter.verify(code, config);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing /*eslint-env */ block", () => {
|
|
it("variables should be available in global scope", () => {
|
|
const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`;
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
exports = getVariable(scope, "exports"),
|
|
window = getVariable(scope, "window");
|
|
|
|
assert.strictEqual(exports.writeable, true);
|
|
assert.strictEqual(window.writeable, false);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => {
|
|
const code = `/* ${ESLINT_ENV} ,, node , no-browser ,, */`;
|
|
|
|
it("variables should be available in global scope", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
exports = getVariable(scope, "exports"),
|
|
window = getVariable(scope, "window");
|
|
|
|
assert.strictEqual(exports.writeable, true);
|
|
assert.strictEqual(window, null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing /*exported */ block", () => {
|
|
|
|
it("we should behave nicely when no matching variable is found", () => {
|
|
const code = "/* exported horse */";
|
|
const config = { rules: {} };
|
|
|
|
linter.verify(code, config, filename, true);
|
|
});
|
|
|
|
it("variables should be exported", () => {
|
|
const code = "/* exported horse */\n\nvar horse = 'circus'";
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
horse = getVariable(scope, "horse");
|
|
|
|
assert.strictEqual(horse.eslintUsed, true);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("undefined variables should not be exported", () => {
|
|
const code = "/* exported horse */\n\nhorse = 'circus'";
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
horse = getVariable(scope, "horse");
|
|
|
|
assert.strictEqual(horse, null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("variables should be exported in strict mode", () => {
|
|
const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'";
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
horse = getVariable(scope, "horse");
|
|
|
|
assert.strictEqual(horse.eslintUsed, true);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("variables should not be exported in the es6 module environment", () => {
|
|
const code = "/* exported horse */\nvar horse = 'circus'";
|
|
const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
horse = getVariable(scope, "horse");
|
|
|
|
assert.strictEqual(horse, null); // there is no global scope at all
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("variables should not be exported when in the node environment", () => {
|
|
const code = "/* exported horse */\nvar horse = 'circus'";
|
|
const config = { rules: { checker: "error" }, env: { node: true } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope(),
|
|
horse = getVariable(scope, "horse");
|
|
|
|
assert.strictEqual(horse, null); // there is no global scope at all
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing a line comment", () => {
|
|
const code = "//global a \n function f() {}";
|
|
|
|
it("should not introduce a global variable", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(getVariable(scope, "a"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code containing normal block comments", () => {
|
|
const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/";
|
|
|
|
it("should not introduce a global variable", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(getVariable(scope, "a"), null);
|
|
assert.strictEqual(getVariable(scope, "b"), null);
|
|
assert.strictEqual(getVariable(scope, "foo"), null);
|
|
assert.strictEqual(getVariable(scope, "c"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating any code", () => {
|
|
const code = "x";
|
|
|
|
it("builtin global variables should be available in the global scope", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.notStrictEqual(getVariable(scope, "Object"), null);
|
|
assert.notStrictEqual(getVariable(scope, "Array"), null);
|
|
assert.notStrictEqual(getVariable(scope, "undefined"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("ES6 global variables should not be available by default", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(getVariable(scope, "Promise"), null);
|
|
assert.strictEqual(getVariable(scope, "Symbol"), null);
|
|
assert.strictEqual(getVariable(scope, "WeakMap"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("ES6 global variables should be available in the es6 environment", () => {
|
|
const config = { rules: { checker: "error" }, env: { es6: true } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.notStrictEqual(getVariable(scope, "Promise"), null);
|
|
assert.notStrictEqual(getVariable(scope, "Symbol"), null);
|
|
assert.notStrictEqual(getVariable(scope, "WeakMap"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("ES6 global variables can be disabled when the es6 environment is enabled", () => {
|
|
const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
const scope = context.getScope();
|
|
|
|
assert.strictEqual(getVariable(scope, "Promise"), null);
|
|
assert.strictEqual(getVariable(scope, "Symbol"), null);
|
|
assert.strictEqual(getVariable(scope, "WeakMap"), null);
|
|
});
|
|
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("at any time", () => {
|
|
const code = "new-rule";
|
|
|
|
it("can add a rule dynamically", () => {
|
|
linter.defineRule(code, context => ({
|
|
Literal(node) {
|
|
context.report(node, "message");
|
|
}
|
|
}));
|
|
|
|
const config = { rules: {} };
|
|
|
|
config.rules[code] = 1;
|
|
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, code);
|
|
assert.strictEqual(messages[0].nodeType, "Literal");
|
|
});
|
|
});
|
|
|
|
describe("at any time", () => {
|
|
const code = ["new-rule-0", "new-rule-1"];
|
|
|
|
it("can add multiple rules dynamically", () => {
|
|
const config = { rules: {} };
|
|
const newRules = {};
|
|
|
|
code.forEach(item => {
|
|
config.rules[item] = 1;
|
|
newRules[item] = function(context) {
|
|
return {
|
|
Literal(node) {
|
|
context.report(node, "message");
|
|
}
|
|
};
|
|
};
|
|
});
|
|
linter.defineRules(newRules);
|
|
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages.length, code.length);
|
|
code.forEach(item => {
|
|
assert.ok(messages.some(message => message.ruleId === item));
|
|
});
|
|
messages.forEach(message => {
|
|
assert.strictEqual(message.nodeType, "Literal");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("at any time", () => {
|
|
const code = "filename-rule";
|
|
|
|
it("has access to the filename", () => {
|
|
linter.defineRule(code, context => ({
|
|
Literal(node) {
|
|
context.report(node, context.getFilename());
|
|
}
|
|
}));
|
|
|
|
const config = { rules: {} };
|
|
|
|
config.rules[code] = 1;
|
|
|
|
const messages = linter.verify("0", config, filename);
|
|
|
|
assert.strictEqual(messages[0].message, filename);
|
|
});
|
|
|
|
it("defaults filename to '<input>'", () => {
|
|
linter.defineRule(code, context => ({
|
|
Literal(node) {
|
|
context.report(node, context.getFilename());
|
|
}
|
|
}));
|
|
|
|
const config = { rules: {} };
|
|
|
|
config.rules[code] = 1;
|
|
|
|
const messages = linter.verify("0", config);
|
|
|
|
assert.strictEqual(messages[0].message, "<input>");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable rules", () => {
|
|
|
|
it("should report a violation", () => {
|
|
const code = "/*eslint no-alert:1*/ alert('test');";
|
|
const config = { rules: {} };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].message, "Unexpected alert.");
|
|
assert.include(messages[0].nodeType, "CallExpression");
|
|
});
|
|
|
|
it("rules should not change initial config", () => {
|
|
const config = { rules: { strict: 2 } };
|
|
const codeA = "/*eslint strict: 0*/ function bar() { return 2; }";
|
|
const codeB = "function foo() { return 1; }";
|
|
let messages = linter.verify(codeA, config, filename, false);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
|
|
messages = linter.verify(codeB, config, filename, false);
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
it("rules should not change initial config", () => {
|
|
const config = { rules: { quotes: [2, "double"] } };
|
|
const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }";
|
|
const codeB = "function foo() { return '1'; }";
|
|
let messages = linter.verify(codeA, config, filename, false);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
|
|
messages = linter.verify(codeB, config, filename, false);
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
it("rules should not change initial config", () => {
|
|
const config = { rules: { quotes: [2, "double"] } };
|
|
const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }";
|
|
const codeB = "function foo() { return '1'; }";
|
|
let messages = linter.verify(codeA, config, filename, false);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
|
|
messages = linter.verify(codeB, config, filename, false);
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
it("rules should not change initial config", () => {
|
|
const config = { rules: { "no-unused-vars": [2, { vars: "all" }] } };
|
|
const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;";
|
|
const codeB = "var b = 55;";
|
|
let messages = linter.verify(codeA, config, filename, false);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
|
|
messages = linter.verify(codeB, config, filename, false);
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with invalid comments to enable rules", () => {
|
|
it("should report a violation when the config is not a valid rule configuration", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/*eslint no-alert:true*/ alert('test');", {}),
|
|
[
|
|
{
|
|
severity: 2,
|
|
ruleId: "no-alert",
|
|
message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 25,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
it("should report a violation when the config violates a rule's schema", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}),
|
|
[
|
|
{
|
|
severity: 2,
|
|
ruleId: "no-alert",
|
|
message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 63,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to disable rules", () => {
|
|
const code = "/*eslint no-alert:0*/ alert('test');";
|
|
|
|
it("should not report a violation", () => {
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to disable rules", () => {
|
|
let code, messages;
|
|
|
|
it("should report an error when disabling a non-existent rule in inline comment", () => {
|
|
code = "/*eslint foo:0*/ ;";
|
|
messages = linter.verify(code, {}, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
|
|
code = "/*eslint-disable foo*/ ;";
|
|
messages = linter.verify(code, {}, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
|
|
code = "/*eslint-disable-line foo*/ ;";
|
|
messages = linter.verify(code, {}, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
|
|
code = "/*eslint-disable-next-line foo*/ ;";
|
|
messages = linter.verify(code, {}, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
});
|
|
|
|
it("should not report an error, when disabling a non-existent rule in config", () => {
|
|
messages = linter.verify("", { rules: { foo: 0 } }, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report an error, when config a non-existent rule in config", () => {
|
|
messages = linter.verify("", { rules: { foo: 1 } }, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
|
|
messages = linter.verify("", { rules: { foo: 2 } }, filename);
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found.");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable multiple rules", () => {
|
|
const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');";
|
|
|
|
it("should report a violation", () => {
|
|
const config = { rules: {} };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].message, "Unexpected alert.");
|
|
assert.include(messages[0].nodeType, "CallExpression");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable and disable multiple rules", () => {
|
|
const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');";
|
|
|
|
it("should report a violation", () => {
|
|
const config = { rules: { "no-console": 1, "no-alert": 0 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].message, "Unexpected alert.");
|
|
assert.include(messages[0].nodeType, "CallExpression");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => {
|
|
|
|
beforeEach(() => {
|
|
linter.defineRule("test-plugin/test-rule", context => ({
|
|
Literal(node) {
|
|
if (node.value === "trigger violation") {
|
|
context.report(node, "Reporting violation.");
|
|
}
|
|
}
|
|
}));
|
|
});
|
|
|
|
it("should not report a violation when inline comment enables plugin rule and there's no violation", () => {
|
|
const config = { rules: {} };
|
|
const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";";
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation when inline comment disables plugin rule", () => {
|
|
const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\"";
|
|
const config = { rules: { "test-plugin/test-rule": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report a violation when the report is right before the comment", () => {
|
|
const code = " /* eslint-disable */ ";
|
|
|
|
linter.defineRule("checker", context => ({
|
|
Program() {
|
|
context.report({ loc: { line: 1, column: 0 }, message: "foo" });
|
|
}
|
|
}));
|
|
const problems = linter.verify(code, { rules: { checker: "error" } });
|
|
|
|
assert.strictEqual(problems.length, 1);
|
|
assert.strictEqual(problems[0].message, "foo");
|
|
});
|
|
|
|
it("should not report a violation when the report is right at the start of the comment", () => {
|
|
const code = " /* eslint-disable */ ";
|
|
|
|
linter.defineRule("checker", context => ({
|
|
Program() {
|
|
context.report({ loc: { line: 1, column: 1 }, message: "foo" });
|
|
}
|
|
}));
|
|
const problems = linter.verify(code, { rules: { checker: "error" } });
|
|
|
|
assert.strictEqual(problems.length, 0);
|
|
});
|
|
|
|
it("rules should not change initial config", () => {
|
|
const config = { rules: { "test-plugin/test-rule": 2 } };
|
|
const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";";
|
|
const codeB = "var a = \"trigger violation\";";
|
|
let messages = linter.verify(codeA, config, filename, false);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
|
|
messages = linter.verify(codeB, config, filename, false);
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable and disable all reporting", () => {
|
|
it("should report a violation", () => {
|
|
|
|
const code = [
|
|
"/*eslint-disable */",
|
|
"alert('test');",
|
|
"/*eslint-enable */",
|
|
"alert('test');"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].message, "Unexpected alert.");
|
|
assert.include(messages[0].nodeType, "CallExpression");
|
|
assert.strictEqual(messages[0].line, 4);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable */",
|
|
"alert('test');",
|
|
"alert('test');"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
" alert('test1');/*eslint-disable */\n",
|
|
"alert('test');",
|
|
" alert('test');\n",
|
|
"/*eslint-enable */alert('test2');"
|
|
].join("");
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].column, 21);
|
|
assert.strictEqual(messages[1].column, 19);
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
|
|
const code = [
|
|
"/*eslint-disable */",
|
|
"alert('test');",
|
|
"/*eslint-disable */",
|
|
"alert('test');",
|
|
"/*eslint-enable*/",
|
|
"alert('test');",
|
|
"/*eslint-enable*/"
|
|
].join("\n");
|
|
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable */",
|
|
"(function(){ var b = 44;})()",
|
|
"/*eslint-enable */;any();"
|
|
].join("\n");
|
|
|
|
const config = { rules: { "no-unused-vars": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"(function(){ /*eslint-disable */ var b = 44;})()",
|
|
"/*eslint-enable */;any();"
|
|
].join("\n");
|
|
|
|
const config = { rules: { "no-unused-vars": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to ignore reporting on specific rules on a specific line", () => {
|
|
|
|
describe("eslint-disable-line", () => {
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert",
|
|
"console.log('test');" // here
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert",
|
|
"console.log('test'); // eslint-disable-line no-console",
|
|
"alert('test');" // here
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
});
|
|
|
|
it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => {
|
|
const code = [
|
|
"/* eslint-disable-line",
|
|
"*",
|
|
"*/ console.log('test');" // here
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => {
|
|
const code = [
|
|
"alert('test'); /* eslint-disable-line ",
|
|
"no-alert */"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config);
|
|
|
|
assert.deepStrictEqual(messages, [
|
|
{
|
|
ruleId: "no-alert",
|
|
severity: 1,
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 14,
|
|
message: "Unexpected alert.",
|
|
messageId: "unexpected",
|
|
nodeType: "CallExpression"
|
|
},
|
|
{
|
|
ruleId: null,
|
|
severity: 2,
|
|
message: "eslint-disable-line comment should not span multiple lines.",
|
|
line: 1,
|
|
column: 16,
|
|
endLine: 2,
|
|
endColumn: 12,
|
|
nodeType: null
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should not report a violation for eslint-disable-line in block comment", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert",
|
|
"alert('test'); /*eslint-disable-line no-alert*/"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert",
|
|
"console.log('test'); // eslint-disable-line no-console"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"alert('test') // eslint-disable-line no-alert, quotes, semi",
|
|
"console.log('test'); // eslint-disable-line"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "double"],
|
|
semi: [1, "always"],
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"alert('test') /* eslint-disable-line no-alert, quotes, semi */",
|
|
"console.log('test'); /* eslint-disable-line */"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "double"],
|
|
semi: [1, "always"],
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should ignore violations of multiple rules when specified in mixed comments", () => {
|
|
const code = [
|
|
" alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "single"]
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("eslint-disable-next-line", () => {
|
|
it("should ignore violation of specified rule on next line", () => {
|
|
const code = [
|
|
"// eslint-disable-next-line no-alert",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
|
|
const code = [
|
|
"/* eslint-disable-next-line no-alert */",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => {
|
|
const code = [
|
|
"/* eslint-disable-next-line no-alert */",
|
|
"alert('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not ignore violation if block comment is not on a single line", () => {
|
|
const code = [
|
|
"/* eslint-disable-next-line",
|
|
"no-alert */alert('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
});
|
|
|
|
it("should ignore violations only of specified rule", () => {
|
|
const code = [
|
|
"// eslint-disable-next-line no-console",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore violations of multiple rules when specified", () => {
|
|
const code = [
|
|
"// eslint-disable-next-line no-alert, quotes",
|
|
"alert(\"test\");",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "single"],
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore violations of multiple rules when specified in mixed comments", () => {
|
|
const code = [
|
|
"/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes",
|
|
"alert(\"test\");"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "single"]
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should ignore violations of only the specified rule on next line", () => {
|
|
const code = [
|
|
"// eslint-disable-next-line quotes",
|
|
"alert(\"test\");",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
quotes: [1, "single"],
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore violations of specified rule on next line only", () => {
|
|
const code = [
|
|
"alert('test');",
|
|
"// eslint-disable-next-line no-alert",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore all rule violations on next line if none specified", () => {
|
|
const code = [
|
|
"// eslint-disable-next-line",
|
|
"alert(\"test\");",
|
|
"console.log('test')"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
semi: [1, "never"],
|
|
quotes: [1, "single"],
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
it("should ignore violations if eslint-disable-next-line is a block comment", () => {
|
|
const code = [
|
|
"alert('test');",
|
|
"/* eslint-disable-next-line no-alert */",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/* eslint-disable-next-line",
|
|
"*",
|
|
"*/",
|
|
"console.log('test');" // here
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
|
|
it("should not ignore violations if comment is of the type Shebang", () => {
|
|
const code = [
|
|
"#! eslint-disable-next-line no-alert",
|
|
"alert('test');",
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1,
|
|
"no-console": 1
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable and disable reporting of specific rules", () => {
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert */",
|
|
"alert('test');",
|
|
"console.log('test');" // here
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
it("should report no violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-unused-vars */",
|
|
"var foo; // eslint-disable-line no-unused-vars",
|
|
"var bar;",
|
|
"/* eslint-enable no-unused-vars */" // here
|
|
].join("\n");
|
|
const config = { rules: { "no-unused-vars": 2 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report no violation", () => {
|
|
const code = [
|
|
"var foo1; // eslint-disable-line no-unused-vars",
|
|
"var foo2; // eslint-disable-line no-unused-vars",
|
|
"var foo3; // eslint-disable-line no-unused-vars",
|
|
"var foo4; // eslint-disable-line no-unused-vars",
|
|
"var foo5; // eslint-disable-line no-unused-vars"
|
|
].join("\n");
|
|
const config = { rules: { "no-unused-vars": 2 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report no violation", () => {
|
|
const code = [
|
|
"/* eslint-disable quotes */",
|
|
"console.log(\"foo\");",
|
|
"/* eslint-enable quotes */"
|
|
].join("\n");
|
|
const config = { rules: { quotes: 2 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert, no-console */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
"/*eslint-enable*/",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');" // here
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].line, 5);
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
assert.strictEqual(messages[1].line, 6);
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
"/*eslint-enable no-console */",
|
|
|
|
"alert('test');" // here
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-console");
|
|
});
|
|
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert, no-console */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
"/*eslint-enable no-alert*/",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].line, 5);
|
|
});
|
|
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert */",
|
|
|
|
"/*eslint-disable no-console */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
"/*eslint-enable */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');", // here
|
|
|
|
"/*eslint-enable */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');", // here
|
|
|
|
"/*eslint-enable*/"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 4);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].line, 6);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-console");
|
|
assert.strictEqual(messages[1].line, 7);
|
|
|
|
assert.strictEqual(messages[2].ruleId, "no-alert");
|
|
assert.strictEqual(messages[2].line, 9);
|
|
|
|
assert.strictEqual(messages[3].ruleId, "no-console");
|
|
assert.strictEqual(messages[3].line, 10);
|
|
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert, no-console */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
|
|
"/*eslint-enable no-alert */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');",
|
|
|
|
"/*eslint-enable no-console */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');", // here
|
|
"/*eslint-enable no-console */"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": 1, "no-console": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 3);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].line, 5);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].line, 8);
|
|
|
|
assert.strictEqual(messages[2].ruleId, "no-console");
|
|
assert.strictEqual(messages[2].line, 9);
|
|
|
|
});
|
|
|
|
it("should report a violation when severity is warn", () => {
|
|
const code = [
|
|
"/*eslint-disable no-alert, no-console */",
|
|
"alert('test');",
|
|
"console.log('test');",
|
|
|
|
"/*eslint-enable no-alert */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');",
|
|
|
|
"/*eslint-enable no-console */",
|
|
|
|
"alert('test');", // here
|
|
"console.log('test');", // here
|
|
"/*eslint-enable no-console */"
|
|
].join("\n");
|
|
const config = { rules: { "no-alert": "warn", "no-console": "warn" } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 3);
|
|
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].line, 5);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].line, 8);
|
|
|
|
assert.strictEqual(messages[2].ruleId, "no-console");
|
|
assert.strictEqual(messages[2].line, 9);
|
|
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => {
|
|
const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');";
|
|
|
|
it("should report a violation", () => {
|
|
const config = { rules: { "no-console": 1, "no-alert": 0 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
assert.strictEqual(messages[0].message, "Unexpected alert.");
|
|
assert.include(messages[0].nodeType, "CallExpression");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable configurable rule", () => {
|
|
const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');";
|
|
|
|
it("should report a violation", () => {
|
|
const config = { rules: { quotes: [2, "single"] } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "quotes");
|
|
assert.strictEqual(messages[0].message, "Strings must use doublequote.");
|
|
assert.include(messages[0].nodeType, "Literal");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to enable configurable rule using string severity", () => {
|
|
const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');";
|
|
|
|
it("should report a violation", () => {
|
|
const config = { rules: { quotes: [2, "single"] } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "quotes");
|
|
assert.strictEqual(messages[0].message, "Strings must use doublequote.");
|
|
assert.include(messages[0].nodeType, "Literal");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with incorrectly formatted comments to disable rule", () => {
|
|
it("should report a violation", () => {
|
|
const code = "/*eslint no-alert:'1'*/ alert('test');";
|
|
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
/*
|
|
* Incorrectly formatted comment threw error;
|
|
* message from caught exception
|
|
* may differ amongst UAs, so verifying
|
|
* first part only as defined in the
|
|
* parseJsonConfig function in lib/eslint.js
|
|
*/
|
|
assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 1);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].message, "Unexpected alert.");
|
|
assert.include(messages[1].nodeType, "CallExpression");
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = "/*eslint no-alert:abc*/ alert('test');";
|
|
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
/*
|
|
* Incorrectly formatted comment threw error;
|
|
* message from caught exception
|
|
* may differ amongst UAs, so verifying
|
|
* first part only as defined in the
|
|
* parseJsonConfig function in lib/eslint.js
|
|
*/
|
|
assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 1);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].message, "Unexpected alert.");
|
|
assert.include(messages[1].nodeType, "CallExpression");
|
|
});
|
|
|
|
it("should report a violation", () => {
|
|
const code = "/*eslint no-alert:0 2*/ alert('test');";
|
|
|
|
const config = { rules: { "no-alert": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
|
|
/*
|
|
* Incorrectly formatted comment threw error;
|
|
* message from caught exception
|
|
* may differ amongst UAs, so verifying
|
|
* first part only as defined in the
|
|
* parseJsonConfig function in lib/eslint.js
|
|
*/
|
|
assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 1);
|
|
|
|
assert.strictEqual(messages[1].ruleId, "no-alert");
|
|
assert.strictEqual(messages[1].message, "Unexpected alert.");
|
|
assert.include(messages[1].nodeType, "CallExpression");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments which have colon in its value", () => {
|
|
const code = String.raw`
|
|
/* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */
|
|
alert('test');
|
|
`;
|
|
|
|
it("should not parse errors, should report a violation", () => {
|
|
const messages = linter.verify(code, {}, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "max-len");
|
|
assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100.");
|
|
assert.include(messages[0].nodeType, "Program");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments that contain escape sequences", () => {
|
|
const code = String.raw`
|
|
/* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */
|
|
console.log("test");
|
|
consolexlog("test2");
|
|
var a = "test2";
|
|
`;
|
|
|
|
it("should validate correctly", () => {
|
|
const config = { rules: {} };
|
|
const messages = linter.verify(code, config, filename);
|
|
const [message1, message2] = messages;
|
|
|
|
assert.strictEqual(messages.length, 2);
|
|
assert.strictEqual(message1.ruleId, "max-len");
|
|
assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1.");
|
|
assert.strictEqual(message1.line, 4);
|
|
assert.strictEqual(message1.column, 1);
|
|
assert.include(message1.nodeType, "Program");
|
|
assert.strictEqual(message2.ruleId, "max-len");
|
|
assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1.");
|
|
assert.strictEqual(message2.line, 5);
|
|
assert.strictEqual(message2.column, 1);
|
|
assert.include(message2.nodeType, "Program");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating a file with a shebang", () => {
|
|
const code = "#!bin/program\n\nvar foo;;";
|
|
|
|
it("should preserve line numbers", () => {
|
|
const config = { rules: { "no-extra-semi": 1 } };
|
|
const messages = linter.verify(code, config);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-extra-semi");
|
|
assert.strictEqual(messages[0].nodeType, "EmptyStatement");
|
|
assert.strictEqual(messages[0].line, 3);
|
|
});
|
|
|
|
it("should have a comment with the shebang in it", () => {
|
|
const config = { rules: { checker: "error" } };
|
|
const spy = sinon.spy(context => {
|
|
const comments = context.getAllComments();
|
|
|
|
assert.strictEqual(comments.length, 1);
|
|
assert.strictEqual(comments[0].type, "Shebang");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", spy);
|
|
linter.verify(code, config);
|
|
assert(spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating broken code", () => {
|
|
const code = BROKEN_TEST_CODE;
|
|
|
|
it("should report a violation with a useful parse error prefix", () => {
|
|
const messages = linter.verify(code);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.isNull(messages[0].ruleId);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 4);
|
|
assert.isTrue(messages[0].fatal);
|
|
assert.match(messages[0].message, /^Parsing error:/u);
|
|
});
|
|
|
|
it("should report source code where the issue is present", () => {
|
|
const inValidCode = [
|
|
"var x = 20;",
|
|
"if (x ==4 {",
|
|
" x++;",
|
|
"}"
|
|
];
|
|
const messages = linter.verify(inValidCode.join("\n"));
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.isTrue(messages[0].fatal);
|
|
assert.match(messages[0].message, /^Parsing error:/u);
|
|
});
|
|
});
|
|
|
|
describe("when using an invalid (undefined) rule", () => {
|
|
linter = new Linter();
|
|
|
|
const code = TEST_CODE;
|
|
const results = linter.verify(code, { rules: { foobar: 2 } });
|
|
const result = results[0];
|
|
const warningResult = linter.verify(code, { rules: { foobar: 1 } })[0];
|
|
const arrayOptionResults = linter.verify(code, { rules: { foobar: [2, "always"] } });
|
|
const objectOptionResults = linter.verify(code, { rules: { foobar: [1, { bar: false }] } });
|
|
const resultsMultiple = linter.verify(code, { rules: { foobar: 2, barfoo: 1 } });
|
|
|
|
it("should report a problem", () => {
|
|
assert.isNotNull(result);
|
|
assert.isArray(results);
|
|
assert.isObject(result);
|
|
assert.property(result, "ruleId");
|
|
assert.strictEqual(result.ruleId, "foobar");
|
|
});
|
|
|
|
it("should report that the rule does not exist", () => {
|
|
assert.property(result, "message");
|
|
assert.strictEqual(result.message, "Definition for rule 'foobar' was not found.");
|
|
});
|
|
|
|
it("should report at the correct severity", () => {
|
|
assert.property(result, "severity");
|
|
assert.strictEqual(result.severity, 2);
|
|
assert.strictEqual(warningResult.severity, 2); // this is 2, since the rulename is very likely to be wrong
|
|
});
|
|
|
|
it("should accept any valid rule configuration", () => {
|
|
assert.isObject(arrayOptionResults[0]);
|
|
assert.isObject(objectOptionResults[0]);
|
|
});
|
|
|
|
it("should report multiple missing rules", () => {
|
|
assert.isArray(resultsMultiple);
|
|
|
|
assert.deepStrictEqual(
|
|
resultsMultiple[1],
|
|
{
|
|
ruleId: "barfoo",
|
|
message: "Definition for rule 'barfoo' was not found.",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 2,
|
|
severity: 2,
|
|
nodeType: null
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when using a rule which has been replaced", () => {
|
|
const code = TEST_CODE;
|
|
const results = linter.verify(code, { rules: { "no-comma-dangle": 2 } });
|
|
|
|
it("should report the new rule", () => {
|
|
assert.strictEqual(results[0].ruleId, "no-comma-dangle");
|
|
assert.strictEqual(results[0].message, "Rule 'no-comma-dangle' was removed and replaced by: comma-dangle");
|
|
});
|
|
});
|
|
|
|
describe("when calling getRules", () => {
|
|
it("should return all loaded rules", () => {
|
|
const rules = linter.getRules();
|
|
|
|
assert.isAbove(rules.size, 230);
|
|
assert.isObject(rules.get("no-alert"));
|
|
});
|
|
});
|
|
|
|
describe("when calling version", () => {
|
|
it("should return current version number", () => {
|
|
const version = linter.version;
|
|
|
|
assert.isString(version);
|
|
assert.isTrue(parseInt(version[0], 10) >= 3);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating an empty string", () => {
|
|
it("runs rules", () => {
|
|
linter.defineRule("no-programs", context => ({
|
|
Program(node) {
|
|
context.report({ node, message: "No programs allowed." });
|
|
}
|
|
}));
|
|
|
|
assert.strictEqual(
|
|
linter.verify("", { rules: { "no-programs": "error" } }).length,
|
|
1
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code without comments to environment", () => {
|
|
it("should report a violation when using typed array", () => {
|
|
const code = "var array = new Uint8Array();";
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
it("should report a violation when using Promise", () => {
|
|
const code = "new Promise();";
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to environment", () => {
|
|
it("should not support legacy config", () => {
|
|
const code = "/*jshint mocha:true */ describe();";
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-undef");
|
|
assert.strictEqual(messages[0].nodeType, "Identifier");
|
|
assert.strictEqual(messages[0].line, 1);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = "/*eslint-env es6 */ new Promise();";
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`;
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = "/*eslint-env mocha */ suite();test();";
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*${ESLINT_ENV} amd */ define();require();`;
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`;
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`;
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*${ESLINT_ENV} node */ process.exit();`;
|
|
|
|
const config = { rules: {} };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report a violation", () => {
|
|
const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`;
|
|
|
|
const config = { rules: { "no-undef": 1 } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => {
|
|
it("should report a violation for disabling rules", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, {
|
|
filename,
|
|
allowInlineConfig: false
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
});
|
|
|
|
it("should report a violation for global variable declarations", () => {
|
|
const code = [
|
|
"/* global foo */"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
test: 2
|
|
}
|
|
};
|
|
let ok = false;
|
|
|
|
linter.defineRules({
|
|
test(context) {
|
|
return {
|
|
Program() {
|
|
const scope = context.getScope();
|
|
const sourceCode = context.getSourceCode();
|
|
const comments = sourceCode.getAllComments();
|
|
|
|
assert.strictEqual(1, comments.length);
|
|
|
|
const foo = getVariable(scope, "foo");
|
|
|
|
assert.notOk(foo);
|
|
|
|
ok = true;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
linter.verify(code, config, { allowInlineConfig: false });
|
|
assert(ok);
|
|
});
|
|
|
|
it("should report a violation for eslint-disable", () => {
|
|
const code = [
|
|
"/* eslint-disable */",
|
|
"alert('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, {
|
|
filename,
|
|
allowInlineConfig: false
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
});
|
|
|
|
it("should not report a violation for rule changes", () => {
|
|
const code = [
|
|
"/*eslint no-alert:2*/",
|
|
"alert('test');"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 0
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, {
|
|
filename,
|
|
allowInlineConfig: false
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report a violation for disable-line", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 2
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, {
|
|
filename,
|
|
allowInlineConfig: false
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-alert");
|
|
});
|
|
|
|
it("should report a violation for env changes", () => {
|
|
const code = [
|
|
`/*${ESLINT_ENV} browser*/ window`
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-undef": 2
|
|
}
|
|
};
|
|
const messages = linter.verify(code, config, { allowInlineConfig: false });
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].ruleId, "no-undef");
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with 'noInlineComment'", () => {
|
|
for (const directive of [
|
|
"globals foo",
|
|
"global foo",
|
|
"exported foo",
|
|
"eslint eqeqeq: error",
|
|
"eslint-disable eqeqeq",
|
|
"eslint-disable-line eqeqeq",
|
|
"eslint-disable-next-line eqeqeq",
|
|
"eslint-enable eqeqeq",
|
|
"eslint-env es6"
|
|
]) {
|
|
// eslint-disable-next-line no-loop-func
|
|
it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => {
|
|
const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true });
|
|
|
|
assert.deepStrictEqual(messages.length, 1);
|
|
assert.deepStrictEqual(messages[0].fatal, void 0);
|
|
assert.deepStrictEqual(messages[0].ruleId, null);
|
|
assert.deepStrictEqual(messages[0].severity, 1);
|
|
assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`);
|
|
});
|
|
}
|
|
|
|
for (const directive of [
|
|
"eslint-disable-line eqeqeq",
|
|
"eslint-disable-next-line eqeqeq"
|
|
]) {
|
|
// eslint-disable-next-line no-loop-func
|
|
it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => {
|
|
const messages = linter.verify(`// ${directive}`, { noInlineConfig: true });
|
|
|
|
assert.deepStrictEqual(messages.length, 1);
|
|
assert.deepStrictEqual(messages[0].fatal, void 0);
|
|
assert.deepStrictEqual(messages[0].ruleId, null);
|
|
assert.deepStrictEqual(messages[0].severity, 1);
|
|
assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`);
|
|
});
|
|
}
|
|
|
|
it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => {
|
|
const messages = linter.verify("/* globals foo */", { noInlineConfig: true }, { allowInlineConfig: false });
|
|
|
|
assert.deepStrictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when receiving cwd in options during instantiation", () => {
|
|
const code = "a;\nb;";
|
|
const config = { rules: { checker: "error" } };
|
|
|
|
it("should get cwd correctly in the context", () => {
|
|
const cwd = "cwd";
|
|
const linterWithOption = new Linter({ cwd });
|
|
let spy;
|
|
|
|
linterWithOption.defineRule("checker", context => {
|
|
spy = sinon.spy(() => {
|
|
assert.strictEqual(context.getCwd(), cwd);
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linterWithOption.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should assign process.cwd() to it if cwd is undefined", () => {
|
|
let spy;
|
|
const linterWithOption = new Linter({ });
|
|
|
|
linterWithOption.defineRule("checker", context => {
|
|
|
|
spy = sinon.spy(() => {
|
|
assert.strictEqual(context.getCwd(), process.cwd());
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linterWithOption.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
|
|
it("should assign process.cwd() to it if the option is undefined", () => {
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
|
|
spy = sinon.spy(() => {
|
|
assert.strictEqual(context.getCwd(), process.cwd());
|
|
});
|
|
return { Program: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("reportUnusedDisable option", () => {
|
|
it("reports problems for unused eslint-disable comments", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }),
|
|
[
|
|
{
|
|
ruleId: null,
|
|
message: "Unused eslint-disable directive (no problems were reported).",
|
|
line: 1,
|
|
column: 1,
|
|
severity: 2,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
it("reports problems for unused eslint-disable comments (error)", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }),
|
|
[
|
|
{
|
|
ruleId: null,
|
|
message: "Unused eslint-disable directive (no problems were reported).",
|
|
line: 1,
|
|
column: 1,
|
|
severity: 2,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
it("reports problems for unused eslint-disable comments (warn)", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }),
|
|
[
|
|
{
|
|
ruleId: null,
|
|
message: "Unused eslint-disable directive (no problems were reported).",
|
|
line: 1,
|
|
column: 1,
|
|
severity: 1,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
|
|
it("reports problems for unused eslint-disable comments (in config)", () => {
|
|
assert.deepStrictEqual(
|
|
linter.verify("/* eslint-disable */", { reportUnusedDisableDirectives: true }),
|
|
[
|
|
{
|
|
ruleId: null,
|
|
message: "Unused eslint-disable directive (no problems were reported).",
|
|
line: 1,
|
|
column: 1,
|
|
severity: 1,
|
|
nodeType: null
|
|
}
|
|
]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => {
|
|
it("should not report a violation", () => {
|
|
const code = [
|
|
"alert('test'); // eslint-disable-line no-alert"
|
|
].join("\n");
|
|
const config = {
|
|
rules: {
|
|
"no-alert": 1
|
|
}
|
|
};
|
|
|
|
const messages = linter.verify(code, config, {
|
|
filename,
|
|
allowInlineConfig: true
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
describe("when evaluating code with hashbang", () => {
|
|
it("should comment hashbang without breaking offset", () => {
|
|
const code = "#!/usr/bin/env node\n'123';";
|
|
const config = { rules: { checker: "error" } };
|
|
let spy;
|
|
|
|
linter.defineRule("checker", context => {
|
|
spy = sinon.spy(node => {
|
|
assert.strictEqual(context.getSource(node), "'123';");
|
|
});
|
|
return { ExpressionStatement: spy };
|
|
});
|
|
|
|
linter.verify(code, config);
|
|
assert(spy && spy.calledOnce);
|
|
});
|
|
});
|
|
|
|
describe("verify()", () => {
|
|
describe("filenames", () => {
|
|
it("should allow filename to be passed on options object", () => {
|
|
const filenameChecker = sinon.spy(context => {
|
|
assert.strictEqual(context.getFilename(), "foo.js");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", filenameChecker);
|
|
linter.defineRule("checker", filenameChecker);
|
|
linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
|
|
assert(filenameChecker.calledOnce);
|
|
});
|
|
|
|
it("should allow filename to be passed as third argument", () => {
|
|
const filenameChecker = sinon.spy(context => {
|
|
assert.strictEqual(context.getFilename(), "bar.js");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", filenameChecker);
|
|
linter.verify("foo;", { rules: { checker: "error" } }, "bar.js");
|
|
assert(filenameChecker.calledOnce);
|
|
});
|
|
|
|
it("should default filename to <input> when options object doesn't have filename", () => {
|
|
const filenameChecker = sinon.spy(context => {
|
|
assert.strictEqual(context.getFilename(), "<input>");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", filenameChecker);
|
|
linter.verify("foo;", { rules: { checker: "error" } }, {});
|
|
assert(filenameChecker.calledOnce);
|
|
});
|
|
|
|
it("should default filename to <input> when only two arguments are passed", () => {
|
|
const filenameChecker = sinon.spy(context => {
|
|
assert.strictEqual(context.getFilename(), "<input>");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("checker", filenameChecker);
|
|
linter.verify("foo;", { rules: { checker: "error" } });
|
|
assert(filenameChecker.calledOnce);
|
|
});
|
|
});
|
|
|
|
it("should report warnings in order by line and column when called", () => {
|
|
|
|
const code = "foo()\n alert('test')";
|
|
const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } };
|
|
|
|
const messages = linter.verify(code, config, filename);
|
|
|
|
assert.strictEqual(messages.length, 3);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 6);
|
|
assert.strictEqual(messages[1].line, 2);
|
|
assert.strictEqual(messages[1].column, 18);
|
|
assert.strictEqual(messages[2].line, 2);
|
|
assert.strictEqual(messages[2].column, 18);
|
|
});
|
|
|
|
describe("ecmaVersion", () => {
|
|
describe("it should properly parse let declaration when", () => {
|
|
it("the ECMAScript version number is 6", () => {
|
|
const messages = linter.verify("let x = 5;", {
|
|
parserOptions: {
|
|
ecmaVersion: 6
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("the ECMAScript version number is 2015", () => {
|
|
const messages = linter.verify("let x = 5;", {
|
|
parserOptions: {
|
|
ecmaVersion: 2015
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
|
|
it("should fail to parse exponentiation operator when the ECMAScript version number is 2015", () => {
|
|
const messages = linter.verify("x ** y;", {
|
|
parserOptions: {
|
|
ecmaVersion: 2015
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
});
|
|
|
|
describe("should properly parse exponentiation operator when", () => {
|
|
it("the ECMAScript version number is 7", () => {
|
|
const messages = linter.verify("x ** y;", {
|
|
parserOptions: {
|
|
ecmaVersion: 7
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("the ECMAScript version number is 2016", () => {
|
|
const messages = linter.verify("x ** y;", {
|
|
parserOptions: {
|
|
ecmaVersion: 2016
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
it("should properly parse object spread when ecmaVersion is 2018", () => {
|
|
|
|
const messages = linter.verify("var x = { ...y };", {
|
|
parserOptions: {
|
|
ecmaVersion: 2018
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should properly parse global return when passed ecmaFeatures", () => {
|
|
|
|
const messages = linter.verify("return;", {
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
globalReturn: true
|
|
}
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should properly parse global return when in Node.js environment", () => {
|
|
|
|
const messages = linter.verify("return;", {
|
|
env: {
|
|
node: true
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not parse global return when in Node.js environment with globalReturn explicitly off", () => {
|
|
|
|
const messages = linter.verify("return;", {
|
|
env: {
|
|
node: true
|
|
},
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
globalReturn: false
|
|
}
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function");
|
|
});
|
|
|
|
it("should not parse global return when Node.js environment is false", () => {
|
|
|
|
const messages = linter.verify("return;", {}, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function");
|
|
});
|
|
|
|
it("should properly parse sloppy-mode code when impliedStrict is false", () => {
|
|
|
|
const messages = linter.verify("var private;", {}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not parse sloppy-mode code when impliedStrict is true", () => {
|
|
|
|
const messages = linter.verify("var private;", {
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
impliedStrict: true
|
|
}
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved");
|
|
});
|
|
|
|
it("should properly parse valid code when impliedStrict is true", () => {
|
|
|
|
const messages = linter.verify("var foo;", {
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
impliedStrict: true
|
|
}
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should properly parse JSX when passed ecmaFeatures", () => {
|
|
|
|
const messages = linter.verify("var x = <div/>;", {
|
|
parserOptions: {
|
|
ecmaFeatures: {
|
|
jsx: true
|
|
}
|
|
}
|
|
}, filename);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should report an error when JSX code is encountered and JSX is not enabled", () => {
|
|
const code = "var myDivElement = <div className=\"foo\" />;";
|
|
const messages = linter.verify(code, {}, "filename");
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].line, 1);
|
|
assert.strictEqual(messages[0].column, 20);
|
|
assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <");
|
|
});
|
|
|
|
it("should not report an error when JSX code is encountered and JSX is enabled", () => {
|
|
const code = "var myDivElement = <div className=\"foo\" />;";
|
|
const messages = linter.verify(code, { parserOptions: { ecmaFeatures: { jsx: true } } }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
|
|
const code = "var myDivElement = <div {...this.props} />;";
|
|
const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should be able to use es6 features if there is a comment which has \"eslint-env es6\"", () => {
|
|
const code = [
|
|
"/* eslint-env es6 */",
|
|
"var arrow = () => 0;",
|
|
"var binary = 0b1010;",
|
|
"{ let a = 0; const b = 1; }",
|
|
"class A {}",
|
|
"function defaultParams(a = 0) {}",
|
|
"var {a = 1, b = 2} = {};",
|
|
"for (var a of []) {}",
|
|
"function* generator() { yield 0; }",
|
|
"var computed = {[a]: 0};",
|
|
"var duplicate = {dup: 0, dup: 1};",
|
|
"var method = {foo() {}};",
|
|
"var property = {a, b};",
|
|
"var octal = 0o755;",
|
|
"var u = /^.$/u.test('𠮷');",
|
|
"var y = /hello/y.test('hello');",
|
|
"function restParam(a, ...rest) {}",
|
|
"class B { superInFunc() { super.foo(); } }",
|
|
"var template = `hello, ${a}`;",
|
|
"var unicode = '\\u{20BB7}';"
|
|
].join("\n");
|
|
|
|
const messages = linter.verify(code, null, "eslint-env es6");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should be able to return in global if there is a comment which enables the node environment with a comment", () => {
|
|
const messages = linter.verify(`/* ${ESLINT_ENV} node */ return;`, null, "node environment");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should attach a \"/*global\" comment node to declared variables", () => {
|
|
const code = "/* global foo */\n/* global bar, baz */";
|
|
let ok = false;
|
|
|
|
linter.defineRules({
|
|
test(context) {
|
|
return {
|
|
Program() {
|
|
const scope = context.getScope();
|
|
const sourceCode = context.getSourceCode();
|
|
const comments = sourceCode.getAllComments();
|
|
|
|
assert.strictEqual(2, comments.length);
|
|
|
|
const foo = getVariable(scope, "foo");
|
|
|
|
assert.strictEqual(foo.eslintExplicitGlobal, true);
|
|
assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]);
|
|
|
|
const bar = getVariable(scope, "bar");
|
|
|
|
assert.strictEqual(bar.eslintExplicitGlobal, true);
|
|
assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]);
|
|
|
|
const baz = getVariable(scope, "baz");
|
|
|
|
assert.strictEqual(baz.eslintExplicitGlobal, true);
|
|
assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]);
|
|
|
|
ok = true;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
linter.verify(code, { rules: { test: 2 } });
|
|
assert(ok);
|
|
});
|
|
|
|
it("should report a linting error when a global is set to an invalid value", () => {
|
|
const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } });
|
|
|
|
assert.deepStrictEqual(results, [
|
|
{
|
|
ruleId: null,
|
|
severity: 2,
|
|
message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')",
|
|
line: 1,
|
|
column: 1,
|
|
endLine: 1,
|
|
endColumn: 39,
|
|
nodeType: null
|
|
},
|
|
{
|
|
ruleId: "no-undef",
|
|
messageId: "undef",
|
|
severity: 2,
|
|
message: "'foo' is not defined.",
|
|
line: 2,
|
|
column: 1,
|
|
endLine: 2,
|
|
endColumn: 4,
|
|
nodeType: "Identifier"
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should not crash when we reuse the SourceCode object", () => {
|
|
linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
|
|
linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
|
|
});
|
|
|
|
it("should reuse the SourceCode object", () => {
|
|
let ast1 = null,
|
|
ast2 = null;
|
|
|
|
linter.defineRule("save-ast1", () => ({
|
|
Program(node) {
|
|
ast1 = node;
|
|
}
|
|
}));
|
|
linter.defineRule("save-ast2", () => ({
|
|
Program(node) {
|
|
ast2 = node;
|
|
}
|
|
}));
|
|
|
|
linter.verify("function render() { return <div className='test'>{hello}</div> }", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } });
|
|
linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } });
|
|
|
|
assert(ast1 !== null);
|
|
assert(ast2 !== null);
|
|
assert(ast1 === ast2);
|
|
});
|
|
|
|
it("should allow 'await' as a property name in modules", () => {
|
|
const result = linter.verify(
|
|
"obj.await",
|
|
{ parserOptions: { ecmaVersion: 6, sourceType: "module" } }
|
|
);
|
|
|
|
assert(result.length === 0);
|
|
});
|
|
|
|
|
|
it("should not modify config object passed as argument", () => {
|
|
const config = {};
|
|
|
|
Object.freeze(config);
|
|
linter.verify("var", config);
|
|
});
|
|
|
|
it("should pass 'id' to rule contexts with the rule id", () => {
|
|
const spy = sinon.spy(context => {
|
|
assert.strictEqual(context.id, "foo-bar-baz");
|
|
return {};
|
|
});
|
|
|
|
linter.defineRule("foo-bar-baz", spy);
|
|
linter.verify("x", { rules: { "foo-bar-baz": "error" } });
|
|
assert(spy.calledOnce);
|
|
});
|
|
|
|
describe("descriptions in directive comments", () => {
|
|
it("should ignore the part preceded by '--' in '/*eslint*/'.", () => {
|
|
const aaa = sinon.stub().returns({});
|
|
const bbb = sinon.stub().returns({});
|
|
|
|
linter.defineRule("aaa", { create: aaa });
|
|
linter.defineRule("bbb", { create: bbb });
|
|
const messages = linter.verify(`
|
|
/*eslint aaa:error -- bbb:error */
|
|
console.log("hello")
|
|
`, {});
|
|
|
|
// Don't include syntax error of the comment.
|
|
assert.deepStrictEqual(messages, []);
|
|
|
|
// Use only `aaa`.
|
|
assert.strictEqual(aaa.callCount, 1);
|
|
assert.strictEqual(bbb.callCount, 0);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*eslint-env*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*eslint-env es2015 -- es2017 */
|
|
var Promise = {}
|
|
var Atomics = {}
|
|
`, { rules: { "no-redeclare": "error" } });
|
|
|
|
// Don't include `Atomics`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endColumn: 32,
|
|
endLine: 3,
|
|
line: 3,
|
|
message: "'Promise' is already defined as a built-in global variable.",
|
|
messageId: "redeclaredAsBuiltin",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-redeclare",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*global*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*global aaa -- bbb */
|
|
var aaa = {}
|
|
var bbb = {}
|
|
`, { rules: { "no-redeclare": "error" } });
|
|
|
|
// Don't include `bbb`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 30,
|
|
endColumn: 33,
|
|
line: 2,
|
|
endLine: 2,
|
|
message: "'aaa' is already defined by a variable declaration.",
|
|
messageId: "redeclaredBySyntax",
|
|
nodeType: "Block",
|
|
ruleId: "no-redeclare",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*globals*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*globals aaa -- bbb */
|
|
var aaa = {}
|
|
var bbb = {}
|
|
`, { rules: { "no-redeclare": "error" } });
|
|
|
|
// Don't include `bbb`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 31,
|
|
endColumn: 34,
|
|
line: 2,
|
|
endLine: 2,
|
|
message: "'aaa' is already defined by a variable declaration.",
|
|
messageId: "redeclaredBySyntax",
|
|
nodeType: "Block",
|
|
ruleId: "no-redeclare",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*exported*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*exported aaa -- bbb */
|
|
var aaa = {}
|
|
var bbb = {}
|
|
`, { rules: { "no-unused-vars": "error" } });
|
|
|
|
// Don't include `aaa`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endColumn: 28,
|
|
endLine: 4,
|
|
line: 4,
|
|
message: "'bbb' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*eslint-disable no-redeclare -- no-unused-vars */
|
|
var aaa = {}
|
|
var aaa = {}
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-unused-vars` but not `no-redeclare`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 4,
|
|
endColumn: 28,
|
|
line: 4,
|
|
message: "'aaa' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*eslint-disable no-redeclare, no-unused-vars */
|
|
/*eslint-enable no-redeclare -- no-unused-vars */
|
|
var aaa = {}
|
|
var aaa = {}
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-redeclare` but not `no-unused-vars`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 5,
|
|
endColumn: 28,
|
|
line: 5,
|
|
message: "'aaa' is already defined.",
|
|
messageId: "redeclared",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-redeclare",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => {
|
|
const messages = linter.verify(`
|
|
var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
|
|
var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-unused-vars` but not `no-redeclare`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 3,
|
|
endColumn: 28,
|
|
line: 3,
|
|
message: "'aaa' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => {
|
|
const messages = linter.verify(`
|
|
var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
|
|
var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-unused-vars` but not `no-redeclare`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 3,
|
|
endColumn: 28,
|
|
line: 3,
|
|
message: "'aaa' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => {
|
|
const messages = linter.verify(`
|
|
//eslint-disable-next-line no-redeclare -- no-unused-vars
|
|
var aaa = {}
|
|
//eslint-disable-next-line no-redeclare -- no-unused-vars
|
|
var aaa = {}
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-unused-vars` but not `no-redeclare`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 5,
|
|
endColumn: 28,
|
|
line: 5,
|
|
message: "'aaa' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => {
|
|
const messages = linter.verify(`
|
|
/*eslint-disable-next-line no-redeclare -- no-unused-vars */
|
|
var aaa = {}
|
|
/*eslint-disable-next-line no-redeclare -- no-unused-vars */
|
|
var aaa = {}
|
|
`, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } });
|
|
|
|
// Do include `no-unused-vars` but not `no-redeclare`
|
|
assert.deepStrictEqual(
|
|
messages,
|
|
[{
|
|
column: 25,
|
|
endLine: 5,
|
|
endColumn: 28,
|
|
line: 5,
|
|
message: "'aaa' is assigned a value but never used.",
|
|
messageId: "unusedVar",
|
|
nodeType: "Identifier",
|
|
ruleId: "no-unused-vars",
|
|
severity: 2
|
|
}]
|
|
);
|
|
});
|
|
|
|
it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => {
|
|
const rule = sinon.stub().returns({});
|
|
|
|
linter.defineRule("a--rule", { create: rule });
|
|
const messages = linter.verify(`
|
|
/*eslint a--rule:error */
|
|
console.log("hello")
|
|
`, {});
|
|
|
|
// Don't include syntax error of the comment.
|
|
assert.deepStrictEqual(messages, []);
|
|
|
|
// Use `a--rule`.
|
|
assert.strictEqual(rule.callCount, 1);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => {
|
|
const aaa = sinon.stub().returns({});
|
|
const bbb = sinon.stub().returns({});
|
|
|
|
linter.defineRule("aaa", { create: aaa });
|
|
linter.defineRule("bbb", { create: bbb });
|
|
const messages = linter.verify(`
|
|
/*eslint aaa:error -------- bbb:error */
|
|
console.log("hello")
|
|
`, {});
|
|
|
|
// Don't include syntax error of the comment.
|
|
assert.deepStrictEqual(messages, []);
|
|
|
|
// Use only `aaa`.
|
|
assert.strictEqual(aaa.callCount, 1);
|
|
assert.strictEqual(bbb.callCount, 0);
|
|
});
|
|
|
|
it("should ignore the part preceded by '--' with line breaks.", () => {
|
|
const aaa = sinon.stub().returns({});
|
|
const bbb = sinon.stub().returns({});
|
|
|
|
linter.defineRule("aaa", { create: aaa });
|
|
linter.defineRule("bbb", { create: bbb });
|
|
const messages = linter.verify(`
|
|
/*eslint aaa:error
|
|
--------
|
|
bbb:error */
|
|
console.log("hello")
|
|
`, {});
|
|
|
|
// Don't include syntax error of the comment.
|
|
assert.deepStrictEqual(messages, []);
|
|
|
|
// Use only `aaa`.
|
|
assert.strictEqual(aaa.callCount, 1);
|
|
assert.strictEqual(bbb.callCount, 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("context.getScope()", () => {
|
|
|
|
/**
|
|
* Get the scope on the node `astSelector` specified.
|
|
* @param {string} code The source code to verify.
|
|
* @param {string} astSelector The AST selector to get scope.
|
|
* @param {number} [ecmaVersion=5] The ECMAScript version.
|
|
* @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope.
|
|
*/
|
|
function getScope(code, astSelector, ecmaVersion = 5) {
|
|
let node, scope;
|
|
|
|
linter.defineRule("get-scope", context => ({
|
|
[astSelector](node0) {
|
|
node = node0;
|
|
scope = context.getScope();
|
|
}
|
|
}));
|
|
linter.verify(
|
|
code,
|
|
{
|
|
parserOptions: { ecmaVersion },
|
|
rules: { "get-scope": 2 }
|
|
}
|
|
);
|
|
|
|
return { node, scope };
|
|
}
|
|
|
|
it("should return 'function' scope on FunctionDeclaration (ES5)", () => {
|
|
const { node, scope } = getScope("function f() {}", "FunctionDeclaration");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node);
|
|
});
|
|
|
|
it("should return 'function' scope on FunctionExpression (ES5)", () => {
|
|
const { node, scope } = getScope("!function f() {}", "FunctionExpression");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node);
|
|
});
|
|
|
|
it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => {
|
|
const { node, scope } = getScope("function f() {}", "BlockStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent);
|
|
});
|
|
|
|
it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() {}", "BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent);
|
|
});
|
|
|
|
it("should return 'function' scope on BlockStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
|
|
});
|
|
|
|
it("should return 'block' scope on BlockStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.upper.type, "function");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
|
|
assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]);
|
|
});
|
|
|
|
it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.upper.type, "block");
|
|
assert.strictEqual(scope.upper.upper.type, "function");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
|
|
assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]);
|
|
assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]);
|
|
});
|
|
|
|
it("should return 'function' scope on SwitchStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
|
|
});
|
|
|
|
it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "switch");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
|
|
});
|
|
|
|
it("should return 'function' scope on SwitchCase in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]);
|
|
});
|
|
|
|
it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015);
|
|
|
|
assert.strictEqual(scope.type, "switch");
|
|
assert.strictEqual(scope.block, node.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]);
|
|
});
|
|
|
|
it("should return 'catch' scope on CatchClause in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause");
|
|
|
|
assert.strictEqual(scope.type, "catch");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
|
|
});
|
|
|
|
it("should return 'catch' scope on CatchClause in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015);
|
|
|
|
assert.strictEqual(scope.type, "catch");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
|
|
});
|
|
|
|
it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement");
|
|
|
|
assert.strictEqual(scope.type, "catch");
|
|
assert.strictEqual(scope.block, node.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]);
|
|
});
|
|
|
|
it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]);
|
|
});
|
|
|
|
it("should return 'function' scope on ForStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
|
|
});
|
|
|
|
it("should return 'for' scope on ForStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]);
|
|
});
|
|
|
|
it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]);
|
|
});
|
|
|
|
it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.upper.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), []);
|
|
assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]);
|
|
});
|
|
|
|
it("should return 'function' scope on ForInStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
|
|
});
|
|
|
|
it("should return 'for' scope on ForInStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]);
|
|
});
|
|
|
|
it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => {
|
|
const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement");
|
|
|
|
assert.strictEqual(scope.type, "function");
|
|
assert.strictEqual(scope.block, node.parent.parent.parent);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]);
|
|
});
|
|
|
|
it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.upper.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), []);
|
|
assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]);
|
|
});
|
|
|
|
it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]);
|
|
});
|
|
|
|
it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => {
|
|
const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "block");
|
|
assert.strictEqual(scope.upper.type, "for");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.deepStrictEqual(scope.variables.map(v => v.name), []);
|
|
assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]);
|
|
});
|
|
|
|
it("should shadow the same name variable by the iteration variable.", () => {
|
|
const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015);
|
|
|
|
assert.strictEqual(scope.type, "for");
|
|
assert.strictEqual(scope.upper.type, "global");
|
|
assert.strictEqual(scope.block, node);
|
|
assert.strictEqual(scope.upper.variables[0].references.length, 0);
|
|
assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id);
|
|
assert.strictEqual(scope.references[1].identifier, node.right);
|
|
assert.strictEqual(scope.references[1].resolved, scope.variables[0]);
|
|
});
|
|
});
|
|
|
|
describe("Variables and references", () => {
|
|
const code = [
|
|
"a;",
|
|
"function foo() { b; }",
|
|
"Object;",
|
|
"foo;",
|
|
"var c;",
|
|
"c;",
|
|
"/* global d */",
|
|
"d;",
|
|
"e;",
|
|
"f;"
|
|
].join("\n");
|
|
let scope = null;
|
|
|
|
beforeEach(() => {
|
|
let ok = false;
|
|
|
|
linter.defineRules({
|
|
test(context) {
|
|
return {
|
|
Program() {
|
|
scope = context.getScope();
|
|
ok = true;
|
|
}
|
|
};
|
|
}
|
|
});
|
|
linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } });
|
|
assert(ok);
|
|
});
|
|
|
|
afterEach(() => {
|
|
scope = null;
|
|
});
|
|
|
|
it("Scope#through should contain references of undefined variables", () => {
|
|
assert.strictEqual(scope.through.length, 2);
|
|
assert.strictEqual(scope.through[0].identifier.name, "a");
|
|
assert.strictEqual(scope.through[0].identifier.loc.start.line, 1);
|
|
assert.strictEqual(scope.through[0].resolved, null);
|
|
assert.strictEqual(scope.through[1].identifier.name, "b");
|
|
assert.strictEqual(scope.through[1].identifier.loc.start.line, 2);
|
|
assert.strictEqual(scope.through[1].resolved, null);
|
|
});
|
|
|
|
it("Scope#variables should contain global variables", () => {
|
|
assert(scope.variables.some(v => v.name === "Object"));
|
|
assert(scope.variables.some(v => v.name === "foo"));
|
|
assert(scope.variables.some(v => v.name === "c"));
|
|
assert(scope.variables.some(v => v.name === "d"));
|
|
assert(scope.variables.some(v => v.name === "e"));
|
|
assert(scope.variables.some(v => v.name === "f"));
|
|
});
|
|
|
|
it("Scope#set should contain global variables", () => {
|
|
assert(scope.set.get("Object"));
|
|
assert(scope.set.get("foo"));
|
|
assert(scope.set.get("c"));
|
|
assert(scope.set.get("d"));
|
|
assert(scope.set.get("e"));
|
|
assert(scope.set.get("f"));
|
|
});
|
|
|
|
it("Variables#references should contain their references", () => {
|
|
assert.strictEqual(scope.set.get("Object").references.length, 1);
|
|
assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object");
|
|
assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3);
|
|
assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
|
|
assert.strictEqual(scope.set.get("foo").references.length, 1);
|
|
assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo");
|
|
assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4);
|
|
assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
|
|
assert.strictEqual(scope.set.get("c").references.length, 1);
|
|
assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c");
|
|
assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6);
|
|
assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
|
|
assert.strictEqual(scope.set.get("d").references.length, 1);
|
|
assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d");
|
|
assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8);
|
|
assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
|
|
assert.strictEqual(scope.set.get("e").references.length, 1);
|
|
assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e");
|
|
assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9);
|
|
assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
|
|
assert.strictEqual(scope.set.get("f").references.length, 1);
|
|
assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f");
|
|
assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10);
|
|
assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
|
|
});
|
|
|
|
it("Reference#resolved should be their variable", () => {
|
|
assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object"));
|
|
assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo"));
|
|
assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c"));
|
|
assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d"));
|
|
assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e"));
|
|
assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f"));
|
|
});
|
|
});
|
|
|
|
describe("context.getDeclaredVariables(node)", () => {
|
|
|
|
/**
|
|
* Assert `context.getDeclaredVariables(node)` is valid.
|
|
* @param {string} code A code to check.
|
|
* @param {string} type A type string of ASTNode. This method checks variables on the node of the type.
|
|
* @param {Array<Array<string>>} expectedNamesList An array of expected variable names. The expected variable names is an array of string.
|
|
* @returns {void}
|
|
*/
|
|
function verify(code, type, expectedNamesList) {
|
|
linter.defineRules({
|
|
test(context) {
|
|
|
|
/**
|
|
* Assert `context.getDeclaredVariables(node)` is empty.
|
|
* @param {ASTNode} node A node to check.
|
|
* @returns {void}
|
|
*/
|
|
function checkEmpty(node) {
|
|
assert.strictEqual(0, context.getDeclaredVariables(node).length);
|
|
}
|
|
const rule = {
|
|
Program: checkEmpty,
|
|
EmptyStatement: checkEmpty,
|
|
BlockStatement: checkEmpty,
|
|
ExpressionStatement: checkEmpty,
|
|
LabeledStatement: checkEmpty,
|
|
BreakStatement: checkEmpty,
|
|
ContinueStatement: checkEmpty,
|
|
WithStatement: checkEmpty,
|
|
SwitchStatement: checkEmpty,
|
|
ReturnStatement: checkEmpty,
|
|
ThrowStatement: checkEmpty,
|
|
TryStatement: checkEmpty,
|
|
WhileStatement: checkEmpty,
|
|
DoWhileStatement: checkEmpty,
|
|
ForStatement: checkEmpty,
|
|
ForInStatement: checkEmpty,
|
|
DebuggerStatement: checkEmpty,
|
|
ThisExpression: checkEmpty,
|
|
ArrayExpression: checkEmpty,
|
|
ObjectExpression: checkEmpty,
|
|
Property: checkEmpty,
|
|
SequenceExpression: checkEmpty,
|
|
UnaryExpression: checkEmpty,
|
|
BinaryExpression: checkEmpty,
|
|
AssignmentExpression: checkEmpty,
|
|
UpdateExpression: checkEmpty,
|
|
LogicalExpression: checkEmpty,
|
|
ConditionalExpression: checkEmpty,
|
|
CallExpression: checkEmpty,
|
|
NewExpression: checkEmpty,
|
|
MemberExpression: checkEmpty,
|
|
SwitchCase: checkEmpty,
|
|
Identifier: checkEmpty,
|
|
Literal: checkEmpty,
|
|
ForOfStatement: checkEmpty,
|
|
ArrowFunctionExpression: checkEmpty,
|
|
YieldExpression: checkEmpty,
|
|
TemplateLiteral: checkEmpty,
|
|
TaggedTemplateExpression: checkEmpty,
|
|
TemplateElement: checkEmpty,
|
|
ObjectPattern: checkEmpty,
|
|
ArrayPattern: checkEmpty,
|
|
RestElement: checkEmpty,
|
|
AssignmentPattern: checkEmpty,
|
|
ClassBody: checkEmpty,
|
|
MethodDefinition: checkEmpty,
|
|
MetaProperty: checkEmpty
|
|
};
|
|
|
|
rule[type] = function(node) {
|
|
const expectedNames = expectedNamesList.shift();
|
|
const variables = context.getDeclaredVariables(node);
|
|
|
|
assert(Array.isArray(expectedNames));
|
|
assert(Array.isArray(variables));
|
|
assert.strictEqual(expectedNames.length, variables.length);
|
|
for (let i = variables.length - 1; i >= 0; i--) {
|
|
assert.strictEqual(expectedNames[i], variables[i].name);
|
|
}
|
|
};
|
|
return rule;
|
|
}
|
|
});
|
|
linter.verify(code, {
|
|
rules: { test: 2 },
|
|
parserOptions: {
|
|
ecmaVersion: 6,
|
|
sourceType: "module"
|
|
}
|
|
});
|
|
|
|
// Check all expected names are asserted.
|
|
assert.strictEqual(0, expectedNamesList.length);
|
|
}
|
|
|
|
it("VariableDeclaration", () => {
|
|
const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n ";
|
|
const namesList = [
|
|
["a", "b", "c"],
|
|
["d", "e", "f"],
|
|
["g", "h", "i", "j", "k"],
|
|
["l"]
|
|
];
|
|
|
|
verify(code, "VariableDeclaration", namesList);
|
|
});
|
|
|
|
it("VariableDeclaration (on for-in/of loop)", () => {
|
|
|
|
// TDZ scope is created here, so tests to exclude those.
|
|
const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n ";
|
|
const namesList = [
|
|
["a", "b", "c"],
|
|
["g"],
|
|
["d", "e", "f"],
|
|
["h"]
|
|
];
|
|
|
|
verify(code, "VariableDeclaration", namesList);
|
|
});
|
|
|
|
it("VariableDeclarator", () => {
|
|
|
|
// TDZ scope is created here, so tests to exclude those.
|
|
const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n ";
|
|
const namesList = [
|
|
["a", "b", "c"],
|
|
["d", "e", "f"],
|
|
["g", "h", "i"],
|
|
["j", "k"],
|
|
["l"]
|
|
];
|
|
|
|
verify(code, "VariableDeclarator", namesList);
|
|
});
|
|
|
|
it("FunctionDeclaration", () => {
|
|
const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n ";
|
|
const namesList = [
|
|
["foo", "a", "b", "c", "d", "e"],
|
|
["bar", "f", "g", "h", "i", "j"]
|
|
];
|
|
|
|
verify(code, "FunctionDeclaration", namesList);
|
|
});
|
|
|
|
it("FunctionExpression", () => {
|
|
const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n ";
|
|
const namesList = [
|
|
["foo", "a", "b", "c", "d", "e"],
|
|
["bar", "f", "g", "h", "i", "j"],
|
|
["q"]
|
|
];
|
|
|
|
verify(code, "FunctionExpression", namesList);
|
|
});
|
|
|
|
it("ArrowFunctionExpression", () => {
|
|
const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n ";
|
|
const namesList = [
|
|
["a", "b", "c", "d", "e"],
|
|
["f", "g", "h", "i", "j"]
|
|
];
|
|
|
|
verify(code, "ArrowFunctionExpression", namesList);
|
|
});
|
|
|
|
it("ClassDeclaration", () => {
|
|
const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n ";
|
|
const namesList = [
|
|
["A", "A"], // outer scope's and inner scope's.
|
|
["B", "B"]
|
|
];
|
|
|
|
verify(code, "ClassDeclaration", namesList);
|
|
});
|
|
|
|
it("ClassExpression", () => {
|
|
const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n ";
|
|
const namesList = [
|
|
["A"],
|
|
["B"]
|
|
];
|
|
|
|
verify(code, "ClassExpression", namesList);
|
|
});
|
|
|
|
it("CatchClause", () => {
|
|
const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n ";
|
|
const namesList = [
|
|
["a", "b"],
|
|
["c", "d"]
|
|
];
|
|
|
|
verify(code, "CatchClause", namesList);
|
|
});
|
|
|
|
it("ImportDeclaration", () => {
|
|
const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
|
|
const namesList = [
|
|
[],
|
|
["a"],
|
|
["b", "c", "d"]
|
|
];
|
|
|
|
verify(code, "ImportDeclaration", namesList);
|
|
});
|
|
|
|
it("ImportSpecifier", () => {
|
|
const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
|
|
const namesList = [
|
|
["c"],
|
|
["d"]
|
|
];
|
|
|
|
verify(code, "ImportSpecifier", namesList);
|
|
});
|
|
|
|
it("ImportDefaultSpecifier", () => {
|
|
const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
|
|
const namesList = [
|
|
["b"]
|
|
];
|
|
|
|
verify(code, "ImportDefaultSpecifier", namesList);
|
|
});
|
|
|
|
it("ImportNamespaceSpecifier", () => {
|
|
const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n ";
|
|
const namesList = [
|
|
["a"]
|
|
];
|
|
|
|
verify(code, "ImportNamespaceSpecifier", namesList);
|
|
});
|
|
});
|
|
|
|
describe("suggestions", () => {
|
|
it("provides suggestion information for tools to use", () => {
|
|
linter.defineRule("rule-with-suggestions", context => ({
|
|
Program(node) {
|
|
context.report({
|
|
node,
|
|
message: "Incorrect spacing",
|
|
suggest: [{
|
|
desc: "Insert space at the beginning",
|
|
fix: fixer => fixer.insertTextBefore(node, " ")
|
|
}, {
|
|
desc: "Insert space at the end",
|
|
fix: fixer => fixer.insertTextAfter(node, " ")
|
|
}]
|
|
});
|
|
}
|
|
}));
|
|
|
|
const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } });
|
|
|
|
assert.deepStrictEqual(messages[0].suggestions, [{
|
|
desc: "Insert space at the beginning",
|
|
fix: {
|
|
range: [0, 0],
|
|
text: " "
|
|
}
|
|
}, {
|
|
desc: "Insert space at the end",
|
|
fix: {
|
|
range: [10, 10],
|
|
text: " "
|
|
}
|
|
}]);
|
|
});
|
|
|
|
it("supports messageIds for suggestions", () => {
|
|
linter.defineRule("rule-with-suggestions", {
|
|
meta: {
|
|
messages: {
|
|
suggestion1: "Insert space at the beginning",
|
|
suggestion2: "Insert space at the end"
|
|
}
|
|
},
|
|
create: context => ({
|
|
Program(node) {
|
|
context.report({
|
|
node,
|
|
message: "Incorrect spacing",
|
|
suggest: [{
|
|
messageId: "suggestion1",
|
|
fix: fixer => fixer.insertTextBefore(node, " ")
|
|
}, {
|
|
messageId: "suggestion2",
|
|
fix: fixer => fixer.insertTextAfter(node, " ")
|
|
}]
|
|
});
|
|
}
|
|
})
|
|
});
|
|
|
|
const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } });
|
|
|
|
assert.deepStrictEqual(messages[0].suggestions, [{
|
|
messageId: "suggestion1",
|
|
desc: "Insert space at the beginning",
|
|
fix: {
|
|
range: [0, 0],
|
|
text: " "
|
|
}
|
|
}, {
|
|
messageId: "suggestion2",
|
|
desc: "Insert space at the end",
|
|
fix: {
|
|
range: [10, 10],
|
|
text: " "
|
|
}
|
|
}]);
|
|
});
|
|
});
|
|
|
|
describe("mutability", () => {
|
|
let linter1 = null;
|
|
let linter2 = null;
|
|
|
|
beforeEach(() => {
|
|
linter1 = new Linter();
|
|
linter2 = new Linter();
|
|
});
|
|
|
|
describe("rules", () => {
|
|
it("with no changes, same rules are loaded", () => {
|
|
assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys()));
|
|
});
|
|
|
|
it("loading rule in one doesn't change the other", () => {
|
|
linter1.defineRule("mock-rule", () => ({}));
|
|
|
|
assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present");
|
|
assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("processors", () => {
|
|
let receivedFilenames = [];
|
|
|
|
beforeEach(() => {
|
|
receivedFilenames = [];
|
|
|
|
// A rule that always reports the AST with a message equal to the source text
|
|
linter.defineRule("report-original-text", context => ({
|
|
Program(ast) {
|
|
receivedFilenames.push(context.getFilename());
|
|
context.report({ node: ast, message: context.getSourceCode().text });
|
|
}
|
|
}));
|
|
});
|
|
|
|
describe("preprocessors", () => {
|
|
it("should receive text and filename.", () => {
|
|
const code = "foo bar baz";
|
|
const preprocess = sinon.spy(text => text.split(" "));
|
|
|
|
linter.verify(code, {}, { filename, preprocess });
|
|
|
|
assert.strictEqual(preprocess.calledOnce, true);
|
|
assert.deepStrictEqual(preprocess.args[0], [code, filename]);
|
|
});
|
|
|
|
it("should apply a preprocessor to the code, and lint each code sample separately", () => {
|
|
const code = "foo bar baz";
|
|
const problems = linter.verify(
|
|
code,
|
|
{ rules: { "report-original-text": "error" } },
|
|
{
|
|
|
|
// Apply a preprocessor that splits the source text into spaces and lints each word individually
|
|
preprocess(input) {
|
|
return input.split(" ");
|
|
}
|
|
}
|
|
);
|
|
|
|
assert.strictEqual(problems.length, 3);
|
|
assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
|
|
});
|
|
|
|
it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => {
|
|
const code = "foo bar baz";
|
|
const problems = linter.verify(
|
|
code,
|
|
{ rules: { "report-original-text": "error" } },
|
|
{
|
|
filename,
|
|
|
|
// Apply a preprocessor that splits the source text into spaces and lints each word individually
|
|
preprocess(input) {
|
|
return input.split(" ").map(text => ({
|
|
filename: "block.js",
|
|
text
|
|
}));
|
|
}
|
|
}
|
|
);
|
|
|
|
assert.strictEqual(problems.length, 3);
|
|
assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
|
|
assert.strictEqual(receivedFilenames.length, 3);
|
|
assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]));
|
|
assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]));
|
|
assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]));
|
|
});
|
|
|
|
it("should receive text even if a SourceCode object was given.", () => {
|
|
const code = "foo";
|
|
const preprocess = sinon.spy(text => text.split(" "));
|
|
|
|
linter.verify(code, {});
|
|
const sourceCode = linter.getSourceCode();
|
|
|
|
linter.verify(sourceCode, {}, { filename, preprocess });
|
|
|
|
assert.strictEqual(preprocess.calledOnce, true);
|
|
assert.deepStrictEqual(preprocess.args[0], [code, filename]);
|
|
});
|
|
|
|
it("should receive text even if a SourceCode object was given (with BOM).", () => {
|
|
const code = "\uFEFFfoo";
|
|
const preprocess = sinon.spy(text => text.split(" "));
|
|
|
|
linter.verify(code, {});
|
|
const sourceCode = linter.getSourceCode();
|
|
|
|
linter.verify(sourceCode, {}, { filename, preprocess });
|
|
|
|
assert.strictEqual(preprocess.calledOnce, true);
|
|
assert.deepStrictEqual(preprocess.args[0], [code, filename]);
|
|
});
|
|
});
|
|
|
|
describe("postprocessors", () => {
|
|
it("should receive result and filename.", () => {
|
|
const code = "foo bar baz";
|
|
const preprocess = sinon.spy(text => text.split(" "));
|
|
const postprocess = sinon.spy(text => [text]);
|
|
|
|
linter.verify(code, {}, { filename, postprocess, preprocess });
|
|
|
|
assert.strictEqual(postprocess.calledOnce, true);
|
|
assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]);
|
|
});
|
|
|
|
it("should apply a postprocessor to the reported messages", () => {
|
|
const code = "foo bar baz";
|
|
|
|
const problems = linter.verify(
|
|
code,
|
|
{ rules: { "report-original-text": "error" } },
|
|
{
|
|
preprocess: input => input.split(" "),
|
|
|
|
/*
|
|
* Apply a postprocessor that updates the locations of the reported problems
|
|
* to make sure they correspond to the locations in the original text.
|
|
*/
|
|
postprocess(problemLists) {
|
|
problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1));
|
|
return problemLists.reduce(
|
|
(combinedList, problemList, index) =>
|
|
combinedList.concat(
|
|
problemList.map(
|
|
problem =>
|
|
Object.assign(
|
|
{},
|
|
problem,
|
|
{
|
|
message: problem.message.toUpperCase(),
|
|
column: problem.column + index * 4
|
|
}
|
|
)
|
|
)
|
|
),
|
|
[]
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
assert.strictEqual(problems.length, 3);
|
|
assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]);
|
|
assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]);
|
|
});
|
|
|
|
it("should use postprocessed problem ranges when applying autofixes", () => {
|
|
const code = "foo bar baz";
|
|
|
|
linter.defineRule("capitalize-identifiers", context => ({
|
|
Identifier(node) {
|
|
if (node.name !== node.name.toUpperCase()) {
|
|
context.report({
|
|
node,
|
|
message: "Capitalize this identifier",
|
|
fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
|
|
});
|
|
}
|
|
}
|
|
}));
|
|
|
|
const fixResult = linter.verifyAndFix(
|
|
code,
|
|
{ rules: { "capitalize-identifiers": "error" } },
|
|
{
|
|
|
|
/*
|
|
* Apply a postprocessor that updates the locations of autofixes
|
|
* to make sure they correspond to locations in the original text.
|
|
*/
|
|
preprocess: input => input.split(" "),
|
|
postprocess(problemLists) {
|
|
return problemLists.reduce(
|
|
(combinedProblems, problemList, blockIndex) =>
|
|
combinedProblems.concat(
|
|
problemList.map(problem =>
|
|
Object.assign(problem, {
|
|
fix: {
|
|
text: problem.fix.text,
|
|
range: problem.fix.range.map(
|
|
rangeIndex => rangeIndex + blockIndex * 4
|
|
)
|
|
}
|
|
}))
|
|
),
|
|
[]
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
assert.strictEqual(fixResult.fixed, true);
|
|
assert.strictEqual(fixResult.messages.length, 0);
|
|
assert.strictEqual(fixResult.output, "FOO BAR BAZ");
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("verifyAndFix", () => {
|
|
it("Fixes the code", () => {
|
|
const messages = linter.verifyAndFix("var a", {
|
|
rules: {
|
|
semi: 2
|
|
}
|
|
}, { filename: "test.js" });
|
|
|
|
assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly");
|
|
assert.isTrue(messages.fixed);
|
|
});
|
|
|
|
it("does not require a third argument", () => {
|
|
const fixResult = linter.verifyAndFix("var a", {
|
|
rules: {
|
|
semi: 2
|
|
}
|
|
});
|
|
|
|
assert.deepStrictEqual(fixResult, {
|
|
fixed: true,
|
|
messages: [],
|
|
output: "var a;"
|
|
});
|
|
});
|
|
|
|
it("does not include suggestions in autofix results", () => {
|
|
const fixResult = linter.verifyAndFix("var foo = /\\#/", {
|
|
rules: {
|
|
semi: 2,
|
|
"no-useless-escape": 2
|
|
}
|
|
});
|
|
|
|
assert.strictEqual(fixResult.output, "var foo = /\\#/;");
|
|
assert.strictEqual(fixResult.fixed, true);
|
|
assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true);
|
|
});
|
|
|
|
it("does not apply autofixes when fix argument is `false`", () => {
|
|
const fixResult = linter.verifyAndFix("var a", {
|
|
rules: {
|
|
semi: 2
|
|
}
|
|
}, { fix: false });
|
|
|
|
assert.strictEqual(fixResult.fixed, false);
|
|
});
|
|
|
|
it("stops fixing after 10 passes", () => {
|
|
linter.defineRule("add-spaces", context => ({
|
|
Program(node) {
|
|
context.report({
|
|
node,
|
|
message: "Add a space before this node.",
|
|
fix: fixer => fixer.insertTextBefore(node, " ")
|
|
});
|
|
}
|
|
}));
|
|
|
|
const fixResult = linter.verifyAndFix("a", { rules: { "add-spaces": "error" } });
|
|
|
|
assert.strictEqual(fixResult.fixed, true);
|
|
assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`);
|
|
assert.strictEqual(fixResult.messages.length, 1);
|
|
});
|
|
|
|
it("should throw an error if fix is passed but meta has no `fixable` property", () => {
|
|
linter.defineRule("test-rule", {
|
|
meta: {
|
|
docs: {},
|
|
schema: []
|
|
},
|
|
create: context => ({
|
|
Program(node) {
|
|
context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
|
|
}
|
|
})
|
|
});
|
|
|
|
assert.throws(() => {
|
|
linter.verify("0", { rules: { "test-rule": "error" } });
|
|
}, /Fixable rules should export a `meta\.fixable` property.\nOccurred while linting <input>:1$/u);
|
|
});
|
|
|
|
it("should not throw an error if fix is passed and there is no metadata", () => {
|
|
linter.defineRule("test-rule", {
|
|
create: context => ({
|
|
Program(node) {
|
|
context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" }));
|
|
}
|
|
})
|
|
});
|
|
|
|
linter.verify("0", { rules: { "test-rule": "error" } });
|
|
});
|
|
});
|
|
|
|
describe("Edge cases", () => {
|
|
|
|
it("should properly parse import statements when sourceType is module", () => {
|
|
const code = "import foo from 'foo';";
|
|
const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should properly parse import all statements when sourceType is module", () => {
|
|
const code = "import * as foo from 'foo';";
|
|
const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should properly parse default export statements when sourceType is module", () => {
|
|
const code = "export default function initialize() {}";
|
|
const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } });
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
// https://github.com/eslint/eslint/issues/9687
|
|
it("should report an error when invalid parserOptions found", () => {
|
|
let messages = linter.verify("", { parserOptions: { ecmaVersion: 222 } });
|
|
|
|
assert.deepStrictEqual(messages.length, 1);
|
|
assert.ok(messages[0].message.includes("Invalid ecmaVersion"));
|
|
|
|
messages = linter.verify("", { parserOptions: { sourceType: "foo" } });
|
|
assert.deepStrictEqual(messages.length, 1);
|
|
assert.ok(messages[0].message.includes("Invalid sourceType"));
|
|
|
|
messages = linter.verify("", { parserOptions: { ecmaVersion: 5, sourceType: "module" } });
|
|
assert.deepStrictEqual(messages.length, 1);
|
|
assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015"));
|
|
});
|
|
|
|
it("should not crash when invalid parentheses syntax is encountered", () => {
|
|
linter.verify("left = (aSize.width/2) - ()");
|
|
});
|
|
|
|
it("should not crash when let is used inside of switch case", () => {
|
|
linter.verify("switch(foo) { case 1: let bar=2; }", { parserOptions: { ecmaVersion: 6 } });
|
|
});
|
|
|
|
it("should not crash when parsing destructured assignment", () => {
|
|
linter.verify("var { a='a' } = {};", { parserOptions: { ecmaVersion: 6 } });
|
|
});
|
|
|
|
it("should report syntax error when a keyword exists in object property shorthand", () => {
|
|
const messages = linter.verify("let a = {this}", { parserOptions: { ecmaVersion: 6 } });
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].fatal, true);
|
|
});
|
|
|
|
it("should not rewrite env setting in core (https://github.com/eslint/eslint/issues/4814)", () => {
|
|
|
|
/*
|
|
* This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28
|
|
* This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416
|
|
*/
|
|
linter.defineRule("test", () => ({}));
|
|
linter.verify("var a = 0;", {
|
|
env: { node: true },
|
|
parserOptions: { ecmaVersion: 6, sourceType: "module" },
|
|
rules: { test: 2 }
|
|
});
|
|
|
|
// This `verify()` takes the instance and tests that the instance was not modified.
|
|
let ok = false;
|
|
|
|
linter.defineRule("test", context => {
|
|
assert(
|
|
context.parserOptions.ecmaFeatures.globalReturn,
|
|
"`ecmaFeatures.globalReturn` of the node environment should not be modified."
|
|
);
|
|
ok = true;
|
|
return {};
|
|
});
|
|
linter.verify("var a = 0;", {
|
|
env: { node: true },
|
|
rules: { test: 2 }
|
|
});
|
|
|
|
assert(ok);
|
|
});
|
|
});
|
|
|
|
describe("Custom parser", () => {
|
|
|
|
const errorPrefix = "Parsing error: ";
|
|
|
|
it("should have file path passed to it", () => {
|
|
const code = "/* this is code */";
|
|
const parseSpy = sinon.spy(testParsers.stubParser, "parse");
|
|
|
|
linter.defineParser("stub-parser", testParsers.stubParser);
|
|
linter.verify(code, { parser: "stub-parser" }, filename, true);
|
|
|
|
sinon.assert.calledWithMatch(parseSpy, "", { filePath: filename });
|
|
});
|
|
|
|
it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => {
|
|
const code = "var myDivElement = <div {...this.props} />;";
|
|
|
|
linter.defineParser("esprima", esprima);
|
|
const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should return an error when the custom parser can't be found", () => {
|
|
const code = "var myDivElement = <div {...this.props} />;";
|
|
const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.strictEqual(messages[0].message, "Configured parser 'esprima-xyz' was not found.");
|
|
});
|
|
|
|
it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => {
|
|
const code = "null %% 'foo'";
|
|
|
|
linter.defineParser("unknown-logical-operator", testParsers.unknownLogicalOperator);
|
|
|
|
// This shouldn't throw
|
|
const messages = linter.verify(code, { parser: "unknown-logical-operator" }, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
|
|
const code = "foo && bar %% baz";
|
|
|
|
linter.defineParser("unknown-logical-operator-nested", testParsers.unknownLogicalOperatorNested);
|
|
|
|
// This shouldn't throw
|
|
const messages = linter.verify(code, { parser: "unknown-logical-operator-nested" }, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
|
|
it("should not throw or return errors when the custom parser returns unknown AST nodes", () => {
|
|
const code = "foo && bar %% baz";
|
|
|
|
const nodes = [];
|
|
|
|
linter.defineRule("collect-node-types", () => ({
|
|
"*"(node) {
|
|
nodes.push(node.type);
|
|
}
|
|
}));
|
|
|
|
linter.defineParser("non-js-parser", testParsers.nonJSParser);
|
|
|
|
const messages = linter.verify(code, {
|
|
parser: "non-js-parser",
|
|
rules: {
|
|
"collect-node-types": "error"
|
|
}
|
|
}, filename, true);
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
assert.isTrue(nodes.length > 0);
|
|
});
|
|
|
|
it("should strip leading line: prefix from parser error", () => {
|
|
linter.defineParser("line-error", testParsers.lineError);
|
|
const messages = linter.verify(";", { parser: "line-error" }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError);
|
|
});
|
|
|
|
it("should not modify a parser error message without a leading line: prefix", () => {
|
|
linter.defineParser("no-line-error", testParsers.noLineError);
|
|
const messages = linter.verify(";", { parser: "no-line-error" }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 1);
|
|
assert.strictEqual(messages[0].severity, 2);
|
|
assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError);
|
|
});
|
|
|
|
describe("if a parser provides 'visitorKeys'", () => {
|
|
let types = [];
|
|
let sourceCode;
|
|
let scopeManager;
|
|
|
|
beforeEach(() => {
|
|
types = [];
|
|
linter.defineRule("collect-node-types", () => ({
|
|
"*"(node) {
|
|
types.push(node.type);
|
|
}
|
|
}));
|
|
linter.defineRule("save-scope-manager", context => {
|
|
scopeManager = context.getSourceCode().scopeManager;
|
|
|
|
return {};
|
|
});
|
|
linter.defineParser("enhanced-parser2", testParsers.enhancedParser2);
|
|
linter.verify("@foo class A {}", {
|
|
parser: "enhanced-parser2",
|
|
rules: {
|
|
"collect-node-types": "error",
|
|
"save-scope-manager": "error"
|
|
}
|
|
});
|
|
|
|
sourceCode = linter.getSourceCode();
|
|
});
|
|
|
|
it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => {
|
|
assert.deepStrictEqual(
|
|
types,
|
|
["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
|
|
);
|
|
});
|
|
|
|
it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
|
|
assert.deepStrictEqual(
|
|
scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle
|
|
["experimentalDecorators", "id", "superClass", "body"]
|
|
);
|
|
});
|
|
|
|
it("should use the same visitorKeys if the source code object is reused", () => {
|
|
const types2 = [];
|
|
|
|
linter.defineRule("collect-node-types", () => ({
|
|
"*"(node) {
|
|
types2.push(node.type);
|
|
}
|
|
}));
|
|
linter.verify(sourceCode, {
|
|
rules: {
|
|
"collect-node-types": "error"
|
|
}
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
types2,
|
|
["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("if a parser provides 'scope'", () => {
|
|
let scope = null;
|
|
let sourceCode = null;
|
|
|
|
beforeEach(() => {
|
|
linter.defineParser("enhanced-parser3", testParsers.enhancedParser3);
|
|
linter.defineRule("save-scope1", context => ({
|
|
Program() {
|
|
scope = context.getScope();
|
|
}
|
|
}));
|
|
linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } });
|
|
|
|
sourceCode = linter.getSourceCode();
|
|
});
|
|
|
|
it("should use the scope (so the global scope has the reference of '@foo')", () => {
|
|
assert.strictEqual(scope.references.length, 1);
|
|
assert.deepStrictEqual(
|
|
scope.references[0].identifier.name,
|
|
"foo"
|
|
);
|
|
});
|
|
|
|
it("should use the same scope if the source code object is reused", () => {
|
|
let scope2 = null;
|
|
|
|
linter.defineRule("save-scope2", context => ({
|
|
Program() {
|
|
scope2 = context.getScope();
|
|
}
|
|
}));
|
|
linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js");
|
|
|
|
assert(scope2 !== null);
|
|
assert(scope2 === scope);
|
|
});
|
|
});
|
|
|
|
it("should not pass any default parserOptions to the parser", () => {
|
|
linter.defineParser("throws-with-options", testParsers.throwsWithOptions);
|
|
const messages = linter.verify(";", { parser: "throws-with-options" }, "filename");
|
|
|
|
assert.strictEqual(messages.length, 0);
|
|
});
|
|
});
|
|
});
|