125 lines
4.1 KiB
JavaScript
125 lines
4.1 KiB
JavaScript
/**
|
|
* The meat of this Grunt plugin's task implementation is broken out into
|
|
* a library file so that it can be called directly with dependencies injected.
|
|
|
|
* This facilitates testing.
|
|
*/
|
|
|
|
const prose = require('../lib/formatters/prose');
|
|
const msbuild = require('../lib/formatters/visual-studio');
|
|
|
|
const exception = require('../lib/reporters/exception');
|
|
const jshint = require('../lib/reporters/jshint-style');
|
|
|
|
const FORMATTERS = {
|
|
prose,
|
|
msbuild
|
|
};
|
|
|
|
const REPORTERS = {
|
|
exception,
|
|
jshint
|
|
};
|
|
|
|
module.exports = function makeTask(grunt, jsonlint, validator, sorter, printer) {
|
|
return function runTask() {
|
|
const options = this.options({
|
|
schema: {},
|
|
ignoreComments: false,
|
|
ignoreTrailingCommas: false,
|
|
allowSingleQuotedStrings: false,
|
|
allowDuplicateObjectKeys: true,
|
|
cjson: false,
|
|
mode: 'json',
|
|
format: false,
|
|
prettyPrint: false,
|
|
indent: 2,
|
|
sortKeys: false,
|
|
pruneComments: false,
|
|
stripObjectKeys: false,
|
|
enforceDoubleQuotes: false,
|
|
enforceSingleQuotes: false,
|
|
trimTrailingCommas: false,
|
|
formatter: 'prose',
|
|
reporter: 'exception'
|
|
});
|
|
const { schema } = options;
|
|
// Share the same options for parsing both data and schema for simplicity.
|
|
const parserOptions = {
|
|
mode: options.mode,
|
|
ignoreComments: options.ignoreComments || options.cjson
|
|
|| options.mode === 'cjson' || options.mode === 'json5',
|
|
ignoreTrailingCommas: options.ignoreTrailingCommas || options.mode === 'json5',
|
|
allowSingleQuotedStrings: options.allowSingleQuotedStrings || options.mode === 'json5',
|
|
allowDuplicateObjectKeys: options.allowDuplicateObjectKeys,
|
|
environment: schema.environment
|
|
};
|
|
|
|
const format = FORMATTERS[options.formatter];
|
|
const report = REPORTERS[options.reporter];
|
|
|
|
function parseInput(data) {
|
|
// Parse JSON data from string by the schema validator to get
|
|
// error messages including the location in the source string.
|
|
if (schema.src) {
|
|
const parsedSchema = grunt.file.read(schema.src);
|
|
const validate = validator.compile(parsedSchema, parserOptions);
|
|
return validate(data, parserOptions);
|
|
}
|
|
return jsonlint.parse(data, parserOptions);
|
|
}
|
|
|
|
function formatOutput(data, parsedData, file) {
|
|
let formatted;
|
|
if (options.format) {
|
|
const preparedData = options.sortKeys
|
|
? sorter.sortObject(parsedData) : parsedData;
|
|
formatted = `${JSON.stringify(preparedData, null, options.indent)}\n`;
|
|
}
|
|
else if (options.prettyPrint) {
|
|
parserOptions.rawTokens = true;
|
|
const tokens = jsonlint.tokenize(data, parserOptions);
|
|
// TODO: Support sorting tor the tokenized input too.
|
|
formatted = `${printer.print(tokens, {
|
|
indent: options.indent,
|
|
pruneComments: options.pruneComments,
|
|
stripObjectKeys: options.stripObjectKeys,
|
|
enforceDoubleQuotes: options.enforceDoubleQuotes,
|
|
enforceSingleQuotes: options.enforceSingleQuotes,
|
|
trimTrailingCommas: options.trimTrailingCommas
|
|
})}\n`;
|
|
}
|
|
if (formatted) {
|
|
grunt.file.write(file, formatted);
|
|
grunt.verbose.ok(`File "${file}" formatted.`);
|
|
}
|
|
}
|
|
|
|
if (this.filesSrc) {
|
|
let failed = 0;
|
|
this.filesSrc.forEach((file) => {
|
|
grunt.log.debug(`Validating "${file}"...`);
|
|
try {
|
|
const data = grunt.file.read(file);
|
|
const parsedData = parseInput(data);
|
|
grunt.verbose.ok(`File "${file}" is valid JSON.`);
|
|
formatOutput(data, parsedData, file);
|
|
}
|
|
catch (error) {
|
|
failed++;
|
|
grunt.log.error(format(file, error));
|
|
grunt.log.writeln(report(file, error, grunt));
|
|
}
|
|
});
|
|
|
|
if (failed > 0) {
|
|
grunt.fail.warn(`${failed} ${grunt.util.pluralize(failed, 'file/files')} failed validation`);
|
|
}
|
|
else {
|
|
const successful = this.filesSrc.length - failed;
|
|
grunt.log.ok(`${successful} file${successful === 1 ? '' : 's'} lint free.`);
|
|
}
|
|
}
|
|
};
|
|
};
|