#!/usr/bin/env node var fs = require('fs') var path = require('path') var parser = require('./jsonlint') var formatter = require('./formatter') var printer = require('./printer') var sorter = require('./sorter') var validator = require('./validator') var pkg = require('../package') function collectExtensions (extension) { return extension.split(',') } var options = require('commander') .name('jsonlint') .usage('[options] [ ...]') .description(pkg.description) .option('-s, --sort-keys', 'sort object keys (not when prettifying)') .option('-E, --extensions [ext]', 'file extensions to process for directory walk', collectExtensions, ['json', 'JSON']) .option('-i, --in-place', 'overwrite the input files') .option('-t, --indent [num|char]', 'number of spaces or specific characters to use for indentation', 2) .option('-c, --compact', 'compact error display') .option('-M, --mode [mode]', 'set other parsing flags according to a format type', 'json') .option('-C, --comments', 'recognize and ignore JavaScript-style comments') .option('-S, --single-quoted-strings', 'support single quotes as string delimiters') .option('-T, --trailing-commas', 'ignore trailing commas in objects and arrays') .option('-D, --no-duplicate-keys', 'report duplicate object keys as an error') .option('-V, --validate [file]', 'JSON schema file to use for validation') .option('-e, --environment [env]', 'which specification of JSON Schema the validation file uses') .option('-q, --quiet', 'do not print the parsed json to stdin') .option('-p, --pretty-print', 'prettify the input instead of stringifying the parsed object') .option('-P, --pretty-print-invalid', 'force pretty-printing even for invalid input') .option('--prune-comments', 'omit comments from the prettified output') .option('--strip-object-keys', 'strip quotes from object keys if possible (JSON5)') .option('--enforce-double-quotes', 'surrounds all strings with double quotes') .option('--enforce-single-quotes', 'surrounds all strings with single quotes (JSON5)') .option('--trim-trailing-commas', 'omit trailing commas from objects and arrays (JSON5)') .version(pkg.version, '-v, --version') .on('--help', () => { console.log() console.log('Parsing mode can be "cjson" or "json5" to enable other flags automatically.') console.log('If no files or directories are specified, stdin will be parsed. Environments') console.log('for JSON schema validation are "json-schema-draft-04", "json-schema-draft-06"') console.log('or "json-schema-draft-07". If not specified, it will be auto-detected.') }) .parse(process.argv) function logNormalError (error, file) { console.log('File:', file) console.error(error.message) } function logCompactError (error, file) { console.error(file + ': line ' + error.location.start.line + ', col ' + error.location.start.column + ', ' + error.reason + '.') } function parse (source, file) { var parserOptions, parsed, formatted try { parserOptions = { mode: options.mode, ignoreComments: options.comments, ignoreTrailingCommas: options.trailingCommas, allowSingleQuotedStrings: options.singleQuotedStrings, allowDuplicateObjectKeys: options.duplicateKeys } if (options.validate) { var validate try { var schema = fs.readFileSync(path.normalize(options.validate), 'utf8') parserOptions.environment = options.environment validate = validator.compile(schema, parserOptions) } catch (error) { var message = 'Loading the JSON schema failed: "' + options.validate + '".\n' + error.message throw new Error(message) } parsed = validate(source, parserOptions) } else { parsed = parser.parse(source, parserOptions) } if (options.prettyPrint) { parserOptions.rawTokens = true var tokens = parser.tokenize(source, parserOptions) // TODO: Support sorting tor the tokenized input too. return printer.print(tokens, { indent: options.indent, pruneComments: options.pruneComments, stripObjectKeys: options.stripObjectKeys, enforceDoubleQuotes: options.enforceDoubleQuotes, enforceSingleQuotes: options.enforceSingleQuotes, trimTrailingCommas: options.trimTrailingCommas }) } if (options.sortKeys) { parsed = sorter.sortObject(parsed) } return JSON.stringify(parsed, null, options.indent) } catch (e) { if (options.prettyPrintInvalid) { /* From https://github.com/umbrae/jsonlintdotcom: * If we failed to validate, run our manual formatter and then re-validate so that we * can get a better line number. On a successful validate, we don't want to run our * manual formatter because the automatic one is faster and probably more reliable. */ try { formatted = formatter.format(source, options.indent) // Re-parse so exception output gets better line numbers parsed = parser.parse(formatted) } catch (e) { if (options.compact) { logCompactError(e, file) } else { logNormalError(e, file) } // force the pretty print before exiting console.log(formatted) } } else { if (options.compact) { logCompactError(e, file) } else { logNormalError(e, file) } } process.exit(1) } } function processFile (file) { file = path.normalize(file) var source = parse(fs.readFileSync(file, 'utf8'), file) if (options.inPlace) { fs.writeFileSync(file, source) } else { if (!options.quiet) { console.log(source) } } } function processSources (src, checkExtension) { var extensions = options.extensions.map(function (extension) { return '.' + extension }) var srcStat try { srcStat = fs.statSync(src) if (srcStat.isFile()) { if (checkExtension) { var ext = path.extname(src) if (extensions.indexOf(ext) < 0) { return } } processFile(src) } else if (srcStat.isDirectory()) { var sources = fs.readdirSync(src) for (var i = 0; i < sources.length; i++) { processSources(path.join(src, sources[i]), true) } } } catch (err) { console.log('WARN', err.message) } } function main () { var files = options.args var source = '' if (files.length) { for (var i = 0; i < files.length; i++) { processSources(files[i], false) } } else { var stdin = process.openStdin() stdin.setEncoding('utf8') stdin.on('data', function (chunk) { source += chunk.toString('utf8') }) stdin.on('end', function () { var parsed = parse(source, '') if (!options.quiet) { console.log(parsed) } }) } } main()