liudong
2023-05-29 340f156319b863525e50e900c58e59b86ecb3d5e
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#!/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()