rule-validator.js
4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @fileoverview Rule Validator
* @author Nicholas C. Zakas
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const ajv = require("../shared/ajv")();
const {
parseRuleId,
getRuleFromConfig,
getRuleOptionsSchema
} = require("./flat-config-helpers");
const ruleReplacements = require("../../conf/replacements.json");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Throws a helpful error when a rule cannot be found.
* @param {Object} ruleId The rule identifier.
* @param {string} ruleId.pluginName The ID of the rule to find.
* @param {string} ruleId.ruleName The ID of the rule to find.
* @param {Object} config The config to search in.
* @throws {TypeError} For missing plugin or rule.
* @returns {void}
*/
function throwRuleNotFoundError({ pluginName, ruleName }, config) {
const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`;
const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
// if the plugin exists then we need to check if the rule exists
if (config.plugins && config.plugins[pluginName]) {
const replacementRuleName = ruleReplacements.rules[ruleName];
if (pluginName === "@" && replacementRuleName) {
errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`;
} else {
errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
// otherwise, let's see if we can find the rule name elsewhere
for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
break;
}
}
}
// falls through to throw error
}
throw new TypeError(errorMessage);
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* Implements validation functionality for the rules portion of a config.
*/
class RuleValidator {
/**
* Creates a new instance.
*/
constructor() {
/**
* A collection of compiled validators for rules that have already
* been validated.
* @type {WeakMap}
*/
this.validators = new WeakMap();
}
/**
* Validates all of the rule configurations in a config against each
* rule's schema.
* @param {Object} config The full config to validate. This object must
* contain both the rules section and the plugins section.
* @returns {void}
* @throws {Error} If a rule's configuration does not match its schema.
*/
validate(config) {
if (!config.rules) {
return;
}
for (const [ruleId, ruleOptions] of Object.entries(config.rules)) {
// check for edge case
if (ruleId === "__proto__") {
continue;
}
/*
* If a rule is disabled, we don't do any validation. This allows
* users to safely set any value to 0 or "off" without worrying
* that it will cause a validation error.
*
* Note: ruleOptions is always an array at this point because
* this validation occurs after FlatConfigArray has merged and
* normalized values.
*/
if (ruleOptions[0] === 0) {
continue;
}
const rule = getRuleFromConfig(ruleId, config);
if (!rule) {
throwRuleNotFoundError(parseRuleId(ruleId), config);
}
// Precompile and cache validator the first time
if (!this.validators.has(rule)) {
const schema = getRuleOptionsSchema(rule);
if (schema) {
this.validators.set(rule, ajv.compile(schema));
}
}
const validateRule = this.validators.get(rule);
if (validateRule) {
validateRule(ruleOptions.slice(1));
if (validateRule.errors) {
throw new Error(`Key "rules": Key "${ruleId}": ${
validateRule.errors.map(
error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
).join("")
}`);
}
}
}
}
}
exports.RuleValidator = RuleValidator;