From 4def8764273e7cc53c71f0dd67da271636e4c1be Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Fri, 10 Nov 2017 02:57:27 -0500 Subject: [PATCH] Chore: avoid handling rules instances in config-validator (#9364) This is a modified version of #9277, which was merged but then reverted later for performance reasons. The performance issues from #9177 have been fixed (`Linter` no longer creates a new map of rules whenever it lints text). The goal of this change is to progress towards being able to remove the `rules` property from `Linter` instances. Unfortunately, this isn't possible yet, since there are a few other modules that rely on reading the list of defined rules. --- lib/cli-engine.js | 10 ++- lib/config/config-file.js | 4 +- lib/config/config-validator.js | 52 ++++++------- lib/linter.js | 8 +- lib/testers/rule-tester.js | 57 +++++++------- tests/lib/config/config-validator.js | 111 +++++++++++++++------------ 6 files changed, 130 insertions(+), 112 deletions(-) diff --git a/lib/cli-engine.js b/lib/cli-engine.js index 39169dc427f..55450fd633e 100644 --- a/lib/cli-engine.js +++ b/lib/cli-engine.js @@ -409,9 +409,13 @@ class CLIEngine { }); } - Object.keys(this.options.rules || {}).forEach(name => { - validator.validateRuleOptions(name, this.options.rules[name], "CLI", this.linter.rules); - }); + if (this.options.rules && Object.keys(this.options.rules).length) { + const loadedRules = this.linter.getRules(); + + Object.keys(this.options.rules).forEach(name => { + validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI"); + }); + } this.config = new Config(this.options, this.linter); } diff --git a/lib/config/config-file.js b/lib/config/config-file.js index 8cefd437d77..c5ff073cfcb 100644 --- a/lib/config/config-file.js +++ b/lib/config/config-file.js @@ -501,8 +501,10 @@ function loadFromDisk(resolvedPath, configContext) { } } + const ruleMap = configContext.linterContext.getRules(); + // validate the configuration before continuing - validator.validate(config, resolvedPath.configFullName, configContext.linterContext.rules, configContext.linterContext.environments); + validator.validate(config, resolvedPath.configFullName, ruleMap.get.bind(ruleMap), configContext.linterContext.environments); /* * If an `extends` property is defined, it represents a configuration file to use as diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js index f2307dc637d..3d98b510455 100644 --- a/lib/config/config-validator.js +++ b/lib/config/config-validator.js @@ -14,9 +14,7 @@ const ajv = require("../util/ajv"), configSchema = require("../../conf/config-schema.js"), util = require("util"); -const validators = { - rules: Object.create(null) -}; +const ruleValidators = new WeakMap(); //------------------------------------------------------------------------------ // Private @@ -25,13 +23,11 @@ let validateSchema; /** * Gets a complete options schema for a rule. - * @param {string} id The rule's unique name. - * @param {Rules} rulesContext Rule context + * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object * @returns {Object} JSON Schema for the rule's options. */ -function getRuleOptionsSchema(id, rulesContext) { - const rule = rulesContext.get(id), - schema = rule && rule.schema || rule && rule.meta && rule.meta.schema; +function getRuleOptionsSchema(rule) { + const schema = rule.schema || rule.meta && rule.meta.schema; // Given a tuple of schemas, insert warning level at the beginning if (Array.isArray(schema)) { @@ -72,19 +68,20 @@ function validateRuleSeverity(options) { /** * Validates the non-severity options passed to a rule, based on its schema. - * @param {string} id The rule's unique name + * @param {{create: Function}} rule The rule to validate * @param {array} localOptions The options for the rule, excluding severity - * @param {Rules} rulesContext Rule context * @returns {void} */ -function validateRuleSchema(id, localOptions, rulesContext) { - const schema = getRuleOptionsSchema(id, rulesContext); +function validateRuleSchema(rule, localOptions) { + if (!ruleValidators.has(rule)) { + const schema = getRuleOptionsSchema(rule); - if (!validators.rules[id] && schema) { - validators.rules[id] = ajv.compile(schema); + if (schema) { + ruleValidators.set(rule, ajv.compile(schema)); + } } - const validateRule = validators.rules[id]; + const validateRule = ruleValidators.get(rule); if (validateRule) { validateRule(localOptions); @@ -98,21 +95,24 @@ function validateRuleSchema(id, localOptions, rulesContext) { /** * Validates a rule's options against its schema. - * @param {string} id The rule's unique name. + * @param {{create: Function}|null} rule The rule that the config is being validated for + * @param {string} ruleId The rule's unique name. * @param {array|number} options The given options for the rule. * @param {string} source The name of the configuration source to report in any errors. - * @param {Rules} rulesContext Rule context * @returns {void} */ -function validateRuleOptions(id, options, source, rulesContext) { +function validateRuleOptions(rule, ruleId, options, source) { + if (!rule) { + return; + } try { const severity = validateRuleSeverity(options); if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) { - validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : [], rulesContext); + validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []); } } catch (err) { - throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`); + throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`); } } @@ -143,16 +143,16 @@ function validateEnvironment(environment, source, envContext) { * Validates a rules config object * @param {Object} rulesConfig The rules config object to validate. * @param {string} source The name of the configuration source to report in any errors. - * @param {Rules} rulesContext Rule context + * @param {function(string): {create: Function}} ruleMapper A mapper function from strings to loaded rules * @returns {void} */ -function validateRules(rulesConfig, source, rulesContext) { +function validateRules(rulesConfig, source, ruleMapper) { if (!rulesConfig) { return; } Object.keys(rulesConfig).forEach(id => { - validateRuleOptions(id, rulesConfig[id], source, rulesContext); + validateRuleOptions(ruleMapper(id), id, rulesConfig[id], source); }); } @@ -223,13 +223,13 @@ function validateConfigSchema(config, source) { * Validates an entire config object. * @param {Object} config The config object to validate. * @param {string} source The name of the configuration source to report in any errors. - * @param {Rules} rulesContext The rules context + * @param {function(string): {create: Function}} ruleMapper A mapper function from rule IDs to defined rules * @param {Environments} envContext The env context * @returns {void} */ -function validate(config, source, rulesContext, envContext) { +function validate(config, source, ruleMapper, envContext) { validateConfigSchema(config, source); - validateRules(config.rules, source, rulesContext); + validateRules(config.rules, source, ruleMapper); validateEnvironment(config.env, source, envContext); } diff --git a/lib/linter.js b/lib/linter.js index 12879842453..35ecf7c75be 100755 --- a/lib/linter.js +++ b/lib/linter.js @@ -278,7 +278,7 @@ function createDisableDirectives(type, loc, value) { * @param {string} filename The file being checked. * @param {ASTNode} ast The top node of the AST. * @param {Object} config The existing configuration data. - * @param {Linter} linterContext Linter context object + * @param {function(string): {create: Function}} ruleMapper A map from rule IDs to defined rules * @returns {{ * config: Object, * problems: Problem[], @@ -291,7 +291,7 @@ function createDisableDirectives(type, loc, value) { * }} Modified config object, along with any problems encountered * while parsing config comments */ -function modifyConfigsFromComments(filename, ast, config, linterContext) { +function modifyConfigsFromComments(filename, ast, config, ruleMapper) { const commentConfig = { exported: {}, @@ -337,7 +337,7 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) { Object.keys(parseResult.config).forEach(name => { const ruleValue = parseResult.config[name]; - validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules); + validator.validateRuleOptions(ruleMapper(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`); commentRules[name] = ruleValue; }); } else { @@ -806,7 +806,7 @@ module.exports = class Linter { // parse global comments and modify config if (allowInlineConfig !== false) { - const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this); + const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, ruleId => this.rules.get(ruleId)); config = modifyConfigResult.config; modifyConfigResult.problems.forEach(problem => problems.push(problem)); diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js index 9861998ff77..aae66301d87 100644 --- a/lib/testers/rule-tester.js +++ b/lib/testers/rule-tester.js @@ -271,6 +271,23 @@ class RuleTester { ].concat(scenarioErrors).join("\n")); } + + linter.defineRule(ruleName, Object.assign({}, rule, { + + // Create a wrapper rule that freezes the `context` properties. + create(context) { + freezeDeeply(context.options); + freezeDeeply(context.settings); + freezeDeeply(context.parserOptions); + + return (typeof rule === "function" ? rule : rule.create)(context); + } + })); + + linter.defineRules(this.rules); + + const ruleMap = linter.getRules(); + /** * Run the rule for the given item * @param {string|Object} item Item to run the rule against @@ -313,20 +330,22 @@ class RuleTester { config.rules[ruleName] = 1; } - linter.defineRule(ruleName, Object.assign({}, rule, { + const schema = validator.getRuleOptionsSchema(rule); - // Create a wrapper rule that freezes the `context` properties. - create(context) { - freezeDeeply(context.options); - freezeDeeply(context.settings); - freezeDeeply(context.parserOptions); - - return (typeof rule === "function" ? rule : rule.create)(context); + /* + * Setup AST getters. + * The goal is to check whether or not AST was modified when + * running the rule under test. + */ + linter.defineRule("rule-tester/validate-ast", () => ({ + Program(node) { + beforeAST = cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; } })); - const schema = validator.getRuleOptionsSchema(ruleName, linter.rules); - if (schema) { ajv.validateSchema(schema); @@ -341,21 +360,7 @@ class RuleTester { } } - validator.validate(config, "rule-tester", linter.rules, new Environments()); - - /* - * Setup AST getters. - * The goal is to check whether or not AST was modified when - * running the rule under test. - */ - linter.defineRule("rule-tester/validate-ast", () => ({ - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; - } - })); + validator.validate(config, "rule-tester", ruleMap.get.bind(ruleMap), new Environments()); return { messages: linter.verify(code, config, filename, true), @@ -532,7 +537,6 @@ class RuleTester { RuleTester.describe("valid", () => { test.valid.forEach(valid => { RuleTester.it(typeof valid === "object" ? valid.code : valid, () => { - linter.defineRules(this.rules); testValidTemplate(valid); }); }); @@ -541,7 +545,6 @@ class RuleTester { RuleTester.describe("invalid", () => { test.invalid.forEach(invalid => { RuleTester.it(invalid.code, () => { - linter.defineRules(this.rules); testInvalidTemplate(invalid); }); }); diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js index 5f3d6527825..5d6cf8cc0d2 100644 --- a/tests/lib/config/config-validator.js +++ b/tests/lib/config/config-validator.js @@ -90,6 +90,15 @@ const mockRequiredOptionsRule = { describe("Validator", () => { + /** + * Gets a loaded rule given a rule ID + * @param {string} ruleId The ID of the rule + * @returns {{create: Function}} The loaded rule + */ + function ruleMapper(ruleId) { + return linter.getRules().get(ruleId); + } + beforeEach(() => { linter.defineRule("mock-rule", mockRule); linter.defineRule("mock-required-options-rule", mockRequiredOptionsRule); @@ -98,7 +107,7 @@ describe("Validator", () => { describe("validate", () => { it("should do nothing with an empty config", () => { - const fn = validator.validate.bind(null, {}, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, {}, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); @@ -118,7 +127,7 @@ describe("Validator", () => { rules: {} }, "tests", - linter.rules, + ruleMapper, linter.environments ); @@ -132,7 +141,7 @@ describe("Validator", () => { foo: true }, "tests", - linter.rules, + ruleMapper, linter.environments ); @@ -141,13 +150,13 @@ describe("Validator", () => { describe("root", () => { it("should throw with a string value", () => { - const fn = validator.validate.bind(null, { root: "true" }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { root: "true" }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `\"true\"`)."); }); it("should throw with a numeric value", () => { - const fn = validator.validate.bind(null, { root: 0 }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { root: 0 }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `0`)."); }); @@ -155,13 +164,13 @@ describe("Validator", () => { describe("globals", () => { it("should throw with a string value", () => { - const fn = validator.validate.bind(null, { globals: "jQuery" }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { globals: "jQuery" }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `\"jQuery\"`)."); }); it("should throw with an array value", () => { - const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `[\"jQuery\"]`)."); }); @@ -169,7 +178,7 @@ describe("Validator", () => { describe("parser", () => { it("should not throw with a null value", () => { - const fn = validator.validate.bind(null, { parser: null }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { parser: null }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); @@ -178,31 +187,31 @@ describe("Validator", () => { describe("env", () => { it("should throw with an array environment", () => { - const fn = validator.validate.bind(null, { env: [] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { env: [] }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `[]`)."); }); it("should throw with a primitive environment", () => { - const fn = validator.validate.bind(null, { env: 1 }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { env: 1 }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `1`)."); }); it("should catch invalid environments", () => { - const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, ruleMapper, linter.environments); assert.throws(fn, "Environment key \"invalid\" is unknown\n"); }); it("should catch disabled invalid environments", () => { - const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, ruleMapper, linter.environments); assert.throws(fn, "Environment key \"invalid\" is unknown\n"); }); it("should do nothing with an undefined environment", () => { - const fn = validator.validate.bind(null, {}, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, {}, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); @@ -211,13 +220,13 @@ describe("Validator", () => { describe("plugins", () => { it("should not throw with an empty array", () => { - const fn = validator.validate.bind(null, { plugins: [] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { plugins: [] }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should throw with a string", () => { - const fn = validator.validate.bind(null, { plugins: "react" }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { plugins: "react" }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"plugins\" is the wrong type (expected array but got `\"react\"`)."); }); @@ -225,13 +234,13 @@ describe("Validator", () => { describe("settings", () => { it("should not throw with an empty object", () => { - const fn = validator.validate.bind(null, { settings: {} }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { settings: {} }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should throw with an array", () => { - const fn = validator.validate.bind(null, { settings: ["foo"] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { settings: ["foo"] }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"settings\" is the wrong type (expected object but got `[\"foo\"]`)."); }); @@ -239,19 +248,19 @@ describe("Validator", () => { describe("extends", () => { it("should not throw with an empty array", () => { - const fn = validator.validate.bind(null, { extends: [] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { extends: [] }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should not throw with a string", () => { - const fn = validator.validate.bind(null, { extends: "react" }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { extends: "react" }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should throw with an object", () => { - const fn = validator.validate.bind(null, { extends: {} }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { extends: {} }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"extends\" is the wrong type (expected string/array but got `{}`)."); }); @@ -259,13 +268,13 @@ describe("Validator", () => { describe("parserOptions", () => { it("should not throw with an empty object", () => { - const fn = validator.validate.bind(null, { parserOptions: {} }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { parserOptions: {} }, null, ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should throw with an array", () => { - const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, linter.rules, linter.environments); + const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, ruleMapper, linter.environments); assert.throws(fn, "Property \"parserOptions\" is the wrong type (expected object but got `[\"foo\"]`)."); }); @@ -274,67 +283,67 @@ describe("Validator", () => { describe("rules", () => { it("should do nothing with an empty rules object", () => { - const fn = validator.validate.bind(null, { rules: {} }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: {} }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config with rules", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is off", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with an invalid config when severity is off", () => { - const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with an invalid config when severity is an array with 'off'", () => { - const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is warn", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is error", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is Off", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is Warn", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should do nothing with a valid config when severity is Error", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should catch invalid rule options", () => { - const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", ruleMapper, linter.environments); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n"); }); @@ -342,7 +351,7 @@ describe("Validator", () => { it("should allow for rules with no options", () => { linter.defineRule("mock-no-options-rule", mockNoOptionsRule); - const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); @@ -350,7 +359,7 @@ describe("Validator", () => { it("should not allow options for rules with no options", () => { linter.defineRule("mock-no-options-rule", mockNoOptionsRule); - const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", ruleMapper, linter.environments); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue [\"extra\"] should NOT have more than 0 items.\n"); }); @@ -358,43 +367,43 @@ describe("Validator", () => { describe("overrides", () => { it("should not throw with an empty overrides array", () => { - const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [] }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should not throw with a valid overrides array", () => { - const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", ruleMapper, linter.environments); assert.doesNotThrow(fn); }); it("should throw if override does not specify files", () => { - const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", ruleMapper, linter.environments); assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n"); }); it("should throw if override has an empty files array", () => { - const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", ruleMapper, linter.environments); assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n"); }); it("should throw if override has nested overrides", () => { - const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", ruleMapper, linter.environments); assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n"); }); it("should throw if override extends", () => { - const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", ruleMapper, linter.environments); assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n"); }); it("should throw if override tries to set root", () => { - const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.rules, linter.environments); + const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", ruleMapper, linter.environments); assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n"); }); @@ -405,12 +414,12 @@ describe("Validator", () => { describe("getRuleOptionsSchema", () => { it("should return null for a missing rule", () => { - assert.strictEqual(validator.getRuleOptionsSchema("non-existent-rule", linter.rules), null); + assert.strictEqual(validator.getRuleOptionsSchema(linter.rules.get("non-existent-rule")), null); }); it("should not modify object schema", () => { linter.defineRule("mock-object-rule", mockObjectRule); - assert.deepStrictEqual(validator.getRuleOptionsSchema("mock-object-rule", linter.rules), { + assert.deepStrictEqual(validator.getRuleOptionsSchema(linter.rules.get("mock-object-rule")), { enum: ["first", "second"] }); }); @@ -420,43 +429,43 @@ describe("Validator", () => { describe("validateRuleOptions", () => { it("should throw for incorrect warning level number", () => { - const fn = validator.validateRuleOptions.bind(null, "mock-rule", 3, "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", 3, "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n"); }); it("should throw for incorrect warning level string", () => { - const fn = validator.validateRuleOptions.bind(null, "mock-rule", "booya", "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", "booya", "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '\"booya\"').\n"); }); it("should throw for invalid-type warning level", () => { - const fn = validator.validateRuleOptions.bind(null, "mock-rule", [["error"]], "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [["error"]], "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '[ \"error\" ]').\n"); }); it("should only check warning level for nonexistent rules", () => { - const fn = validator.validateRuleOptions.bind(null, "non-existent-rule", [3, "foobar"], "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("non-existent-rule"), "non-existent-rule", [3, "foobar"], "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"non-existent-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n"); }); it("should only check warning level for plugin rules", () => { - const fn = validator.validateRuleOptions.bind(null, "plugin/rule", 3, "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("plugin/rule"), "plugin/rule", 3, "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"plugin/rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n"); }); it("should throw for incorrect configuration values", () => { - const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "frist"], "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" should be equal to one of the allowed values.\n"); }); it("should throw for too many configuration values", () => { - const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "first", "second"], "tests", linter.rules); + const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "first", "second"], "tests"); assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue [\"first\",\"second\"] should NOT have more than 1 items.\n"); });