Skip to content

Commit 42ed0bf

Browse files
authored
Schema (#120)
* Enhance schema config to more accurately valid config files before linting * Update ConfigSchema.js
1 parent 46ebfb5 commit 42ed0bf

File tree

5 files changed

+636
-137
lines changed

5 files changed

+636
-137
lines changed

src/config/ConfigSchema.js

Lines changed: 214 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,224 @@
1+
const Ajv = require('ajv');
2+
const ajvErrors = require('ajv-errors');
3+
4+
const ajv = new Ajv({allErrors: true, jsonPointers: true});
5+
6+
ajvErrors(ajv);
7+
18
/**
2-
* Top level config properties
3-
* @type {Object}
9+
* Formats an array of schema validation errors.
10+
*
11+
* @param {Array} errors An array of error messages to format.
12+
* @returns {String} Formatted error message
413
*/
5-
const topLevelConfigProperties = {
6-
extends: {type: ['string', 'array']},
7-
rules: {type: 'object'},
8-
root: {type: 'boolean'}
14+
const formatSchemaErrors = errors => {
15+
return errors.map(error => `\t- ${error.message}\n`).join('');
16+
};
17+
18+
const standardRuleSchema = {
19+
type: 'string',
20+
enum: ['off', 'warning', 'error'],
21+
errorMessage: {
22+
type: 'severity must be a string.',
23+
enum: 'severity must be either "off", "warning", or "error".'
24+
}
25+
};
26+
27+
const arrayRuleSchema = {
28+
type: 'array',
29+
items: [
30+
standardRuleSchema,
31+
{
32+
type: 'array',
33+
minItems: 1,
34+
uniqueItems: true,
35+
errorMessage: {
36+
type: 'the second item in an array rule config must be an array.',
37+
minItems: 'the second item in an array rule config must have at least 1 item.',
38+
uniqueItems: 'the second item in an array rule config must have unique items.'
39+
}
40+
}
41+
],
42+
minItems: 2,
43+
maxItems: 2,
44+
additionalItems: false,
45+
errorMessage: {
46+
type: 'rule config must be an array, e.g. ["error", ["value1", "value2"]].',
47+
minItems: 'array rules must have two items, severity and options array. e.g. ["error", ["value1", "value2"]].',
48+
maxItems: 'array rules must have two items, severity and options array. e.g. ["error", ["value1", "value2"]].',
49+
additionalItems:
50+
'array rules are only allowed two items, severity and the list is values. e.g. ["error", ["value1", "value2"]].'
51+
}
52+
};
53+
54+
const objectRuleSchema = {
55+
type: 'array',
56+
items: [
57+
standardRuleSchema,
58+
{
59+
type: 'object',
60+
errorMessage: {
61+
type: 'the second item in an object rule config must be an object.'
62+
}
63+
}
64+
],
65+
minItems: 2,
66+
maxItems: 2,
67+
additionalItems: false,
68+
errorMessage: {
69+
type: 'rule config must be an array, e.g. ["error", {}].',
70+
minItems: 'object rules must have two items, severity and options object. e.g. ["error", {}].',
71+
maxItems: 'object rules must have two items, severity and options object. e.g. ["error", {}].',
72+
additionalItems: 'object rules are only allowed two items, severity and options object. e.g. ["error", {}].'
73+
}
74+
};
75+
76+
const optionalObjExceptionsSchema = {
77+
type: 'array',
78+
items: [
79+
{
80+
type: 'string',
81+
errorMessage: {
82+
type: 'each exception must be a string.'
83+
}
84+
}
85+
],
86+
uniqueItems: true,
87+
minItems: 1,
88+
errorMessage: {
89+
type: 'expections must be an array.',
90+
minItems: 'expections must have at least 1 item.',
91+
uniqueItems: 'expections must have unique items.'
92+
}
993
};
1094

11-
/**
12-
* Config schema defintion
13-
* @type {Object}
14-
*/
1595
const configurationSchema = {
1696
type: 'object',
17-
properties: topLevelConfigProperties,
18-
additionalProperties: false
97+
properties: {
98+
extends: {
99+
type: ['string', 'array'],
100+
items: {
101+
type: 'string'
102+
},
103+
minItems: 1,
104+
uniqueItems: true,
105+
errorMessage: {
106+
type: 'extends must be either a string or an array of strings.',
107+
minItems: 'extends must have at least one item if it is an array.',
108+
uniqueItems: 'extends must have unique items if it is an array.'
109+
}
110+
},
111+
rules: {
112+
type: 'object',
113+
errorMessage: {
114+
type: 'rules must be an object.'
115+
}
116+
},
117+
root: {
118+
type: 'boolean',
119+
errorMessage: {
120+
type: 'root must be a boolean.'
121+
}
122+
}
123+
},
124+
additionalProperties: false,
125+
errorMessage: {
126+
type: 'npm-package-json-lint config should be an object.',
127+
additionalProperties:
128+
'npm-package-json-lint config has unexpected top-level property. Valid properties include: `extends`, `rules`, and `root`.'
129+
}
130+
};
131+
132+
/**
133+
* Validates standard rules config.
134+
*
135+
* @param {Object} ruleConfig The ruleConfig object to validate.
136+
* @returns {boolean} True if valid. Error if not.
137+
*/
138+
const isStandardRuleSchemaValid = ruleConfig => {
139+
const validate = ajv.compile(standardRuleSchema);
140+
const isValid = validate(ruleConfig);
141+
142+
if (!isValid) {
143+
throw new Error(`${formatSchemaErrors(validate.errors)}`);
144+
}
145+
146+
return true;
147+
};
148+
149+
/**
150+
* Validates array rules config.
151+
*
152+
* @param {Object} ruleConfig The ruleConfig object to validate.
153+
* @returns {boolean} True if valid. Error if not.
154+
*/
155+
const isArrayRuleSchemaValid = ruleConfig => {
156+
const validate = ajv.compile(arrayRuleSchema);
157+
const isValid = validate(ruleConfig);
158+
159+
if (!isValid) {
160+
throw new Error(`${formatSchemaErrors(validate.errors)}`);
161+
}
162+
163+
return true;
164+
};
165+
166+
/**
167+
* Validates array rules config.
168+
*
169+
* @param {Object} ruleConfig The ruleConfig object to validate.
170+
* @returns {boolean} True if valid. Error if not.
171+
*/
172+
const isObjectRuleSchemaValid = ruleConfig => {
173+
const validate = ajv.compile(objectRuleSchema);
174+
const isValid = validate(ruleConfig);
175+
176+
if (!isValid) {
177+
throw new Error(`${formatSchemaErrors(validate.errors)}`);
178+
}
179+
180+
return true;
19181
};
20182

21183
/**
22-
* Public ConfigSchema class
23-
* @class
184+
* Validates optional object exceptions config.
185+
*
186+
* @param {Object} ruleConfig The ruleConfig object to validate.
187+
* @returns {boolean} True if valid. Error if not.
24188
*/
25-
class ConfigSchema {
26-
/**
27-
* Gets configuration schema
28-
*
29-
* @returns {Object} schema object
30-
*/
31-
static get() {
32-
return configurationSchema;
33-
}
34-
}
35-
36-
module.exports = ConfigSchema;
189+
const isOptionalObjExceptSchemaValid = ruleConfig => {
190+
const validate = ajv.compile(optionalObjExceptionsSchema);
191+
const isValid = validate(ruleConfig);
192+
193+
if (!isValid) {
194+
throw new Error(`${formatSchemaErrors(validate.errors)}`);
195+
}
196+
197+
return true;
198+
};
199+
200+
/**
201+
* Validates the top level properties of the config object.
202+
*
203+
* @param {Object} config The config object to validate.
204+
* @param {string} source The name of the configuration source to report in any errors.
205+
* @returns {boolean} True if valid. Error if not.
206+
*/
207+
const isConfigObjectSchemaValid = (config, source) => {
208+
const validate = ajv.compile(configurationSchema);
209+
const isValid = validate(config);
210+
211+
if (!isValid) {
212+
throw new Error(`npm-package-json-lint configuration in ${source} is invalid:\n${formatSchemaErrors(validate.errors)}`);
213+
}
214+
215+
return true;
216+
};
217+
218+
module.exports = {
219+
isConfigObjectSchemaValid,
220+
isStandardRuleSchemaValid,
221+
isArrayRuleSchemaValid,
222+
isObjectRuleSchemaValid,
223+
isOptionalObjExceptSchemaValid
224+
};

0 commit comments

Comments
 (0)