@prantlf/jsonlint 11.3.0 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +23 -0
  2. package/lib/cli.js +115 -23
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -14,6 +14,7 @@ This is a fork of the original project ([zaach/jsonlint](https://github.com/zaac
14
14
  * Provides 100% compatible interface to the native `JSON.parse` method.
15
15
  * Optionally recognizes JavaScript-style comments (CJSON) and single quoted strings (JSON5).
16
16
  * Optionally ignores trailing commas and reports duplicate object keys as an error.
17
+ * Optionally checks that also the expected format matches, including sorted object keys.
17
18
  * Supports [JSON Schema] drafts 04, 06 and 07.
18
19
  * Offers pretty-printing including comment-stripping and object keys without quotes (JSON5).
19
20
  * Prefers the native JSON parser if possible to run [7x faster than the custom parser].
@@ -92,6 +93,23 @@ The same parameters can be passed from a configuration file:
92
93
  }
93
94
  ```
94
95
 
96
+ The input can be checked not only to be a valid JSON, but also to be formatted according to the coding standard. For example, check that there is a trailing li break in each JSON file, in addition to alphabetically sorted keys and no duplicate keys:
97
+
98
+ $ jsonlint -ksDr *.json
99
+
100
+ File: package.json
101
+ Formatted output differs
102
+ ===================================================================
103
+ --- package.json.orig
104
+ +++ package.json
105
+ @@ -105,4 +105,4 @@
106
+ "lint",
107
+ "jsonlint"
108
+ ]
109
+ -}
110
+ +}
111
+
112
+
95
113
  ### Usage
96
114
 
97
115
  Usage: `jsonlint [options] [<file, directory, pattern> ...]`
@@ -104,6 +122,8 @@ Usage: `jsonlint [options] [<file, directory, pattern> ...]`
104
122
  -E, --extensions [ext] file extensions to process for directory walk
105
123
  (default: ["json","JSON"])
106
124
  -i, --in-place overwrite the input files
125
+ -j, --diff print difference instead of writing the output
126
+ -k, --check check that the input is equal to the output
107
127
  -t, --indent [num|char] number of spaces or specific characters
108
128
  to use for indentation (default: 2)
109
129
  -c, --compact compact error display
@@ -116,6 +136,7 @@ Usage: `jsonlint [options] [<file, directory, pattern> ...]`
116
136
  -V, --validate [file] JSON schema file to use for validation
117
137
  -e, --environment [env] which specification of JSON Schema the
118
138
  validation file uses
139
+ -x, --context [num] line count used as the diff context (default: 3)
119
140
  -l, --log-files print only the parsed file names to stdout
120
141
  -q, --quiet do not print the parsed json to stdout
121
142
  -n, --continue continue with other files if an error occurs
@@ -168,6 +189,8 @@ The configuration is an object with the following properties, described above, w
168
189
  | sort-keys | sortKeys |
169
190
  | extensions | |
170
191
  | in-place | inPlace |
192
+ | diff | |
193
+ | check | |
171
194
  | indent | |
172
195
  | compact | |
173
196
  | mode | |
package/lib/cli.js CHANGED
@@ -3,7 +3,6 @@
3
3
  const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs')
4
4
  const { extname, join, normalize } = require('path')
5
5
  const { isDynamicPattern, sync } = require('fast-glob')
6
- const { cosmiconfigSync } = require('cosmiconfig')
7
6
  const { parse, tokenize } = require('./jsonlint')
8
7
  const { format } = require('./formatter')
9
8
  const { print } = require('./printer')
@@ -22,6 +21,8 @@ const commander = require('commander')
22
21
  .option('-s, --sort-keys', 'sort object keys (not when prettifying)')
23
22
  .option('-E, --extensions [ext]', 'file extensions to process for directory walk', collectValues, ['json', 'JSON'])
24
23
  .option('-i, --in-place', 'overwrite the input files')
24
+ .option('-j, --diff', 'print difference instead of writing the output')
25
+ .option('-k, --check', 'check that the input is equal to the output')
25
26
  .option('-t, --indent [num|char]', 'number of spaces or specific characters to use for indentation', 2)
26
27
  .option('-c, --compact', 'compact error display')
27
28
  .option('-M, --mode [mode]', 'set other parsing flags according to a format type', 'json')
@@ -31,6 +32,7 @@ const commander = require('commander')
31
32
  .option('-D, --no-duplicate-keys', 'report duplicate object keys as an error')
32
33
  .option('-V, --validate [file]', 'JSON schema file to use for validation')
33
34
  .option('-e, --environment [env]', 'which specification of JSON Schema the validation file uses')
35
+ .option('-x, --context [num]', 'line count used as the diff context', 3)
34
36
  .option('-l, --log-files', 'print only the parsed file names to stdout')
35
37
  .option('-q, --quiet', 'do not print the parsed json to stdout')
36
38
  .option('-n, --continue', 'continue with other files if an error occurs')
@@ -79,6 +81,7 @@ let options
79
81
  if (params.config === false) {
80
82
  options = params
81
83
  } else {
84
+ const { cosmiconfigSync } = require('cosmiconfig')
82
85
  const configurator = cosmiconfigSync('jsonlint')
83
86
  const { config = {} } = (params.config && configurator.load(params.config)) ||
84
87
  configurator.search() || {}
@@ -86,6 +89,7 @@ if (params.config === false) {
86
89
  }
87
90
 
88
91
  const extensions = options.extensions.map(extension => '.' + extension)
92
+ let reported
89
93
 
90
94
  function convertConfig (config) {
91
95
  const result = {}
@@ -107,11 +111,17 @@ function mergeOptions (target, ...sources) {
107
111
  return target
108
112
  }
109
113
 
110
- function logNormalError (error, file) {
111
- if (process.exitCode > 0) {
114
+ function separateBlocks () {
115
+ if (reported) {
112
116
  console.log()
117
+ } else {
118
+ reported = true
113
119
  }
114
- console.log('File:', file)
120
+ }
121
+
122
+ function logNormalError (error, file) {
123
+ separateBlocks()
124
+ console.info('File:', file)
115
125
  console.error(error.message)
116
126
  }
117
127
 
@@ -197,24 +207,96 @@ function processContents (source, file) {
197
207
  }
198
208
  }
199
209
 
210
+ function ensureLineBreak (parsed, source) {
211
+ const lines = source.split(/\r?\n/)
212
+ const newLine = !lines[lines.length - 1]
213
+ if (options.trailingNewline === true ||
214
+ (options.trailingNewline !== false && newLine)) {
215
+ parsed += '\n'
216
+ }
217
+ return parsed
218
+ }
219
+
220
+ function checkContents (file, source, parsed) {
221
+ const { createTwoFilesPatch, structuredPatch } = require('diff')
222
+ const structured = structuredPatch(`${file}.orig`, file, source, parsed, '', '', { context: options.context })
223
+ const length = structured.hunks && structured.hunks.length
224
+ if (length > 0) {
225
+ const hunk = length === 1 ? 'hunk differs' : 'hunks differ'
226
+ const message = `${length} ${hunk}`
227
+ if (options.compact) {
228
+ console.error(`${file}: ${message}`)
229
+ } else {
230
+ separateBlocks()
231
+ console.info('File:', file)
232
+ console.error(message)
233
+ }
234
+ if (!options.quiet) {
235
+ const diff = createTwoFilesPatch(`${file}.orig`, file, source, parsed, '', '', { context: options.context })
236
+ console.log(diff)
237
+ }
238
+ if (options.continue) {
239
+ process.exitCode = 1
240
+ } else {
241
+ process.exit(1)
242
+ }
243
+ } else {
244
+ if (options.compact) {
245
+ console.info(`${file}: no difference`)
246
+ } else if (options.logFiles) {
247
+ console.info(file)
248
+ }
249
+ }
250
+ }
251
+
252
+ function diffContents (file, source, parsed) {
253
+ const { createTwoFilesPatch, structuredPatch } = require('diff')
254
+ const compact = options.quiet || options.compact
255
+ let diff, length
256
+ if (compact) {
257
+ diff = structuredPatch(`${file}.orig`, file, source, parsed, '', '', { context: options.context })
258
+ length = diff.hunks && diff.hunks.length
259
+ } else {
260
+ diff = createTwoFilesPatch(`${file}.orig`, file, source, parsed, '', '', { context: options.context })
261
+ length = diff.split(/\r?\n/).length - 4
262
+ }
263
+ if (length > 0) {
264
+ if (compact) {
265
+ const hunk = length === 1 ? 'hunk differs' : 'hunks differ'
266
+ console.info(`${file}: ${length} ${hunk}`)
267
+ } else {
268
+ separateBlocks()
269
+ console.info('File:', file)
270
+ console.log(diff)
271
+ }
272
+ } else {
273
+ if (options.compact) {
274
+ console.info(`${file}: no difference`)
275
+ } else if (options.logFiles) {
276
+ console.info(file)
277
+ }
278
+ }
279
+ }
280
+
200
281
  function processFile (file) {
201
282
  file = normalize(file)
202
- if (options.logFiles) {
203
- console.log(file)
283
+ if (options.logFiles && !(options.compact || options.check || options.diff)) {
284
+ console.info(file)
204
285
  }
205
- const original = readFileSync(file, 'utf8')
206
- let source = processContents(original, file)
286
+ const source = readFileSync(file, 'utf8')
287
+ const parsed = processContents(source, file)
207
288
  if (options.inPlace) {
208
- const lines = original.split(/\r?\n/)
209
- const newLine = !lines[lines.length - 1]
210
- if (options.trailingNewline === true ||
211
- (options.trailingNewline !== false && newLine)) {
212
- source += '\n'
289
+ if (options.logFiles && options.compact) {
290
+ console.info(file)
213
291
  }
214
- writeFileSync(file, source)
292
+ writeFileSync(file, ensureLineBreak(parsed, source))
293
+ } else if (options.check) {
294
+ checkContents(file, source, ensureLineBreak(parsed, source))
295
+ } else if (options.diff) {
296
+ diffContents(file, source, ensureLineBreak(parsed, source))
215
297
  } else {
216
298
  if (!(options.quiet || options.logFiles)) {
217
- console.log(source)
299
+ console.log(parsed)
218
300
  }
219
301
  }
220
302
  }
@@ -237,21 +319,21 @@ function processSource (src, checkExtension) {
237
319
  }
238
320
  }
239
321
  } catch ({ message }) {
240
- console.log('WARN', message)
322
+ console.warn('WARN', message)
241
323
  }
242
324
  }
243
325
 
244
326
  function processPatterns (patterns) {
245
327
  const files = sync(patterns, { onlyFiles: true })
246
328
  if (!files.length) {
247
- console.log('no files found')
329
+ console.error('no files found')
248
330
  process.exit(1)
249
331
  }
250
332
  for (const file of files) {
251
333
  try {
252
334
  processFile(file)
253
335
  } catch ({ message }) {
254
- console.log('WARN', message)
336
+ console.warn('WARN', message)
255
337
  }
256
338
  }
257
339
  }
@@ -278,12 +360,22 @@ function main () {
278
360
  source += chunk.toString('utf8')
279
361
  })
280
362
  stdin.on('end', () => {
281
- if (options.logFiles) {
282
- console.log('<stdin>')
363
+ const file = '<stdin>'
364
+ if (options.logFiles && !(options.compact || options.check || options.diff)) {
365
+ console.info(file)
283
366
  }
284
- const parsed = processContents(source, '<stdin>')
285
- if (!(options.quiet || options.logFiles)) {
286
- console.log(parsed)
367
+ const parsed = processContents(source, file)
368
+ if (options.check) {
369
+ checkContents(file, source, ensureLineBreak(parsed, source))
370
+ } else if (options.diff) {
371
+ diffContents(file, source, ensureLineBreak(parsed, source))
372
+ } else {
373
+ if (options.logFiles && options.compact) {
374
+ console.info(file)
375
+ }
376
+ if (!(options.quiet || options.logFiles)) {
377
+ console.log(parsed)
378
+ }
287
379
  }
288
380
  })
289
381
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prantlf/jsonlint",
3
- "version": "11.3.0",
3
+ "version": "11.6.0",
4
4
  "description": "JSON/CJSON/JSON5 parser, syntax and schema validator and pretty-printer.",
5
5
  "author": "Ferdinand Prantl <prantlf@gmail.com> (http://prantl.tk)",
6
6
  "contributors": [
@@ -77,15 +77,16 @@
77
77
  "dependencies": {
78
78
  "ajv": "6.12.6",
79
79
  "commander": "9.2.0",
80
- "cosmiconfig": "^7.0.1",
80
+ "cosmiconfig": "7.0.1",
81
+ "diff": "5.0.0",
81
82
  "fast-glob": "3.2.11"
82
83
  },
83
84
  "devDependencies": {
84
- "@semantic-release/changelog": "^6.0.1",
85
- "@semantic-release/git": "^10.0.1",
86
- "@types/node": "17.0.30",
87
- "@typescript-eslint/eslint-plugin": "5.21.0",
88
- "@typescript-eslint/parser": "5.21.0",
85
+ "@semantic-release/changelog": "6.0.1",
86
+ "@semantic-release/git": "10.0.1",
87
+ "@types/node": "17.0.31",
88
+ "@typescript-eslint/eslint-plugin": "5.22.0",
89
+ "@typescript-eslint/parser": "5.22.0",
89
90
  "eslint": "8.14.0",
90
91
  "eslint-config-standard": "17.0.0",
91
92
  "eslint-plugin-import": "2.26.0",