Skip to content

Commit

Permalink
Add options validator (refs #2179)
Browse files Browse the repository at this point in the history
  • Loading branch information
btmills committed May 13, 2015
1 parent f20fd41 commit b5efd28
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
99 changes: 99 additions & 0 deletions lib/validate-options.js
@@ -0,0 +1,99 @@
/**
* @fileoverview Validates rule options.
* @author Brandon Mills
* @copyright 2015 Brandon Mills
*/

"use strict";

var rules = require("./rules"),
validator = require("is-my-json-valid");

var validators = Object.create(null); // Cache generated schema validators

/**
* Converts a rule's exported, abbreviated schema into a full schema.
* @param {object} options Exported schema from a rule.
* @returns {object} Full schema ready for validation.
*/
function makeSchema(options) {

// If no schema, only validate warning level, and permit anything after
if (!options) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
],
"minItems": 1
};
}

// Given a tuple of schemas, insert warning level at the beginning
if (Array.isArray(options)) {
return {
"type": "array",
"items": [
{
"enum": [0, 1, 2]
}
].concat(options),
"minItems": 1,
"maxItems": options.length + 1
};
}

// Given a full schema, leave it alone
return options;
}

/**
* Gets an options schema for a rule.
* @param {string} id The rule's unique name.
* @returns {object} vJSON Schema for the rule's options.
*/
function getRuleSchema(id) {
var rule = rules.get(id);
return makeSchema(rule && rule.schema);
}

/**
* Validates a rule's options against its schema.
* @param {string} id The rule's unique name.
* @param {object} config The given options for the rule.
* @param {string} source The name of the configuration source.
* @returns {void}
*/
module.exports = function (id, config, source) {
var validate = validators[id],
message;

if (!validate) {
validate = validator(getRuleSchema(id), { verbose: true });
validators[id] = validate;
}

if (typeof config === "number") {
config = [config];
}

validate(config);

if (validate.errors) {
message = [
source, ":\n",
"\tConfiguration for rule \"", id, "\" is invalid:\n"
];
validate.errors.forEach(function (error) {
message.push(
"\tValue \"", error.value, "\" ", error.message, ".\n"
);
});

throw new Error(message.join(""));
}
};

module.exports.getRuleSchema = getRuleSchema;
74 changes: 74 additions & 0 deletions tests/lib/validate-options.js
@@ -0,0 +1,74 @@
/**
* @fileoverview Tests for validate-options.
* @author Brandon Mills
* @copyright 2015 Brandon Mills
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var assert = require("chai").assert,
eslint = require("../../lib/eslint"),
validate = require("../../lib/validate-options");

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

function mockRule(context) {
return {
"Program": function(node) {
context.report(node, "Expected a validation error.");
}
};
}

mockRule.schema = [
{
"enum": ["single", "double", "backtick"]
},
{
"enum": ["avoid-escape"]
}
];

describe("ValidateOptions", function() {

beforeEach(function() {
eslint.defineRule("mock-rule", mockRule);
});

it("should throw for incorrect warning level", function() {
var fn = validate.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 = validate.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 = validate.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 = validate.bind(null, "mock-rule", [2, "doulbe", "avoidEscape"], "tests");

assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"doulbe\" must be an enum value.\n\tValue \"avoidEscape\" must be an enum value.\n");
});

it("should throw for too many configuration values", function() {
var fn = validate.bind(null, "mock-rule", [2, "single", "avoid-escape", "extra"], "tests");

assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"2,single,avoid-escape,extra\" has more items than allowed.\n");
});

});

0 comments on commit b5efd28

Please sign in to comment.