Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New: Add config validator (refs #2179)
- Loading branch information
Showing
3 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/** | ||
* @fileoverview Validates configs. | ||
* @author Brandon Mills | ||
* @copyright 2015 Brandon Mills | ||
*/ | ||
|
||
"use strict"; | ||
|
||
var rules = require("./rules"), | ||
schemaValidator = require("is-my-json-valid"); | ||
|
||
var validators = { | ||
rules: Object.create(null) | ||
}; | ||
|
||
/** | ||
* Gets a complete options schema for a rule. | ||
* @param {string} id The rule's unique name. | ||
* @returns {object} JSON Schema for the rule's options. | ||
*/ | ||
function getRuleOptionsSchema(id) { | ||
var rule = rules.get(id), | ||
schema = rule && rule.schema; | ||
|
||
if (!schema) { | ||
return { | ||
"type": "array", | ||
"items": [ | ||
{ | ||
"enum": [0, 1, 2] | ||
} | ||
], | ||
"minItems": 1 | ||
}; | ||
} | ||
|
||
// Given a tuple of schemas, insert warning level at the beginning | ||
if (Array.isArray(schema)) { | ||
return { | ||
"type": "array", | ||
"items": [ | ||
{ | ||
"enum": [0, 1, 2] | ||
} | ||
].concat(schema), | ||
"minItems": 1, | ||
"maxItems": schema.length + 1 | ||
}; | ||
} | ||
|
||
// Given a full schema, leave it alone | ||
return schema; | ||
} | ||
|
||
/** | ||
* Validates a rule's options against its schema. | ||
* @param {string} id The rule's unique name. | ||
* @param {object} options The given options for the rule. | ||
* @param {string} source The name of the configuration source. | ||
* @returns {void} | ||
*/ | ||
function validateRuleOptions(id, options, source) { | ||
var validateRule = validators.rules[id], | ||
message; | ||
|
||
if (!validateRule) { | ||
validateRule = schemaValidator(getRuleOptionsSchema(id), { verbose: true }); | ||
validators.rules[id] = validateRule; | ||
} | ||
|
||
if (typeof options === "number") { | ||
options = [options]; | ||
} | ||
|
||
validateRule(options); | ||
|
||
if (validateRule.errors) { | ||
message = [ | ||
source, ":\n", | ||
"\tConfiguration for rule \"", id, "\" is invalid:\n" | ||
]; | ||
validateRule.errors.forEach(function (error) { | ||
message.push( | ||
"\tValue \"", error.value, "\" ", error.message, ".\n" | ||
); | ||
}); | ||
|
||
throw new Error(message.join("")); | ||
} | ||
} | ||
|
||
/** | ||
* Validates an entire config object. | ||
* @param {Object} config The config object to validate. | ||
* @param {string} source The location to report with any errors. | ||
* @returns {void} | ||
*/ | ||
function validate(config, source) { | ||
if (typeof config.rules === "object") { | ||
Object.keys(config.rules).forEach(function (id) { | ||
validateRuleOptions(id, config.rules[id], source); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = { | ||
getRuleOptionsSchema: getRuleOptionsSchema, | ||
validate: validate, | ||
validateRuleOptions: validateRuleOptions | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/** | ||
* @fileoverview Tests for config validator. | ||
* @author Brandon Mills | ||
* @copyright 2015 Brandon Mills | ||
*/ | ||
|
||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
var assert = require("chai").assert, | ||
eslint = require("../../lib/eslint"), | ||
validator = require("../../lib/config-validator"); | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
function mockRule(context) { | ||
return { | ||
"Program": function(node) { | ||
context.report(node, "Expected a validation error."); | ||
} | ||
}; | ||
} | ||
|
||
mockRule.schema = [ | ||
{ | ||
"enum": ["first", "second"] | ||
} | ||
]; | ||
|
||
describe("Validator", function() { | ||
|
||
beforeEach(function() { | ||
eslint.defineRule("mock-rule", mockRule); | ||
}); | ||
|
||
describe("validate", function() { | ||
|
||
it("should do nothing with an empty config", function() { | ||
var fn = validator.validate.bind(null, {}, "tests"); | ||
|
||
assert.doesNotThrow(fn); | ||
}); | ||
|
||
it("should do nothing with an empty rules object", function() { | ||
var fn = validator.validate.bind(null, { rules: {} }, "tests"); | ||
|
||
assert.doesNotThrow(fn); | ||
}); | ||
|
||
it("should do nothing with a valid config", function() { | ||
var fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests"); | ||
|
||
assert.doesNotThrow(fn); | ||
}); | ||
|
||
it("should catch invalid rule options", function() { | ||
var fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"3\" must be an enum value.\n\tValue \"third\" must be an enum value.\n"); | ||
}); | ||
|
||
}); | ||
|
||
describe("getRuleOptionsSchema", function() { | ||
|
||
it("should return a default schema for a missing rule", function() { | ||
assert.deepEqual(validator.getRuleOptionsSchema("non-existent-rule"), { | ||
"type": "array", | ||
"items": [ | ||
{ | ||
"enum": [0, 1, 2] | ||
} | ||
], | ||
"minItems": 1 | ||
}); | ||
}); | ||
|
||
it("should add warning level validation to provided schemas", function() { | ||
assert.deepEqual(validator.getRuleOptionsSchema("mock-rule"), { | ||
"type": "array", | ||
"items": [ | ||
{ | ||
"enum": [0, 1, 2] | ||
}, | ||
{ | ||
"enum": ["first", "second"] | ||
} | ||
], | ||
"minItems": 1, | ||
"maxItems": 2 | ||
}); | ||
}); | ||
|
||
}); | ||
|
||
describe("validateRuleOptions", function() { | ||
|
||
it("should throw for incorrect warning level", function() { | ||
var fn = validator.validateRuleOptions.bind(null, "mock-rule", 3, "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"3\" must be an enum value.\n"); | ||
}); | ||
|
||
it("should only check warning level for nonexistent rules", function() { | ||
var fn = validator.validateRuleOptions.bind(null, "non-existent-rule", [3, "foobar"], "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"non-existent-rule\" is invalid:\n\tValue \"3\" must be an enum value.\n"); | ||
}); | ||
|
||
it("should only check warning level for plugin rules", function() { | ||
var fn = validator.validateRuleOptions.bind(null, "plugin/rule", 3, "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"plugin/rule\" is invalid:\n\tValue \"3\" must be an enum value.\n"); | ||
}); | ||
|
||
it("should throw for incorrect configuration values", function() { | ||
var fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" must be an enum value.\n"); | ||
}); | ||
|
||
it("should throw for too many configuration values", function() { | ||
var fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "first", "second"], "tests"); | ||
|
||
assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"2,first,second\" has more items than allowed.\n"); | ||
}); | ||
|
||
}); | ||
|
||
}); |