Files
2025-07-24 17:21:45 +08:00

194 lines
6.7 KiB
JavaScript

#!/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] [<file or directory> ...]')
.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, '<stdin>')
if (!options.quiet) {
console.log(parsed)
}
})
}
}
main()