Skip to content

Commit 75167f7

Browse files
committed
feat: Introduce a check that the formatted output is the same as the input
Checks if all JSON files coply with the coding standard, not only being valid JSON.
1 parent 11632a8 commit 75167f7

File tree

5 files changed

+124
-69
lines changed

5 files changed

+124
-69
lines changed

.vscode/launch.json

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@
22
"version": "0.2.0",
33
"configurations": [
44
{
5+
"name": "cli",
56
"type": "node",
67
"request": "launch",
7-
"name": "cli",
88
"program": "${workspaceRoot}/lib/cli.js",
99
"args": [
10-
"-M",
11-
"json5",
12-
"test/passes/json5.txt"
10+
"-k",
11+
"package.json"
1312
],
1413
"skipFiles": [
1514
"<node_internals>/**/*.js"
1615
]
1716
},
1817
{
18+
"name": "test",
1919
"type": "node",
2020
"request": "launch",
21-
"name": "test",
2221
"program": "${workspaceRoot}/test/parse2.js",
2322
"args": [
2423
"--native-parser"
@@ -28,9 +27,9 @@
2827
]
2928
},
3029
{
30+
"name": "fail",
3131
"type": "node",
3232
"request": "launch",
33-
"name": "fail",
3433
"program": "${workspaceRoot}/benchmarks/fail",
3534
"skipFiles": [
3635
"<node_internals>/**/*.js"

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This is a fork of the original project ([zaach/jsonlint](https://github.com/zaac
1414
* Provides 100% compatible interface to the native `JSON.parse` method.
1515
* Optionally recognizes JavaScript-style comments (CJSON) and single quoted strings (JSON5).
1616
* 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.
1718
* Supports [JSON Schema] drafts 04, 06 and 07.
1819
* Offers pretty-printing including comment-stripping and object keys without quotes (JSON5).
1920
* 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:
9293
}
9394
```
9495

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+
\ No newline at end of file
112+
95113
### Usage
96114

97115
Usage: `jsonlint [options] [<file, directory, pattern> ...]`
@@ -104,6 +122,7 @@ Usage: `jsonlint [options] [<file, directory, pattern> ...]`
104122
-E, --extensions [ext] file extensions to process for directory walk
105123
(default: ["json","JSON"])
106124
-i, --in-place overwrite the input files
125+
-k, --check check that the input is equal to the output
107126
-t, --indent [num|char] number of spaces or specific characters
108127
to use for indentation (default: 2)
109128
-c, --compact compact error display

lib/cli.js

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs')
44
const { extname, join, normalize } = require('path')
55
const { isDynamicPattern, sync } = require('fast-glob')
6-
const { cosmiconfigSync } = require('cosmiconfig')
76
const { parse, tokenize } = require('./jsonlint')
87
const { format } = require('./formatter')
98
const { print } = require('./printer')
@@ -22,6 +21,7 @@ const commander = require('commander')
2221
.option('-s, --sort-keys', 'sort object keys (not when prettifying)')
2322
.option('-E, --extensions [ext]', 'file extensions to process for directory walk', collectValues, ['json', 'JSON'])
2423
.option('-i, --in-place', 'overwrite the input files')
24+
.option('-k, --check', 'check that the input is equal to the output')
2525
.option('-t, --indent [num|char]', 'number of spaces or specific characters to use for indentation', 2)
2626
.option('-c, --compact', 'compact error display')
2727
.option('-M, --mode [mode]', 'set other parsing flags according to a format type', 'json')
@@ -79,6 +79,7 @@ let options
7979
if (params.config === false) {
8080
options = params
8181
} else {
82+
const { cosmiconfigSync } = require('cosmiconfig')
8283
const configurator = cosmiconfigSync('jsonlint')
8384
const { config = {} } = (params.config && configurator.load(params.config)) ||
8485
configurator.search() || {}
@@ -197,24 +198,49 @@ function processContents (source, file) {
197198
}
198199
}
199200

201+
function ensureLineBreak (parsed, source) {
202+
const lines = source.split(/\r?\n/)
203+
const newLine = !lines[lines.length - 1]
204+
if (options.trailingNewline === true ||
205+
(options.trailingNewline !== false && newLine)) {
206+
parsed += '\n'
207+
}
208+
return parsed
209+
}
210+
211+
function checkContents (file, source, parsed) {
212+
const { createTwoFilesPatch } = require('diff')
213+
const diff = createTwoFilesPatch(`${file}.orig`, file, source, parsed, '', '', { context: 3 })
214+
if (diff.split(/\r?\n/).length > 4) {
215+
const err = new Error('Formatted output differs')
216+
if (options.compact) {
217+
logCompactError(err, file)
218+
} else {
219+
logNormalError(err, file)
220+
}
221+
console.log(diff)
222+
if (options.continue) {
223+
process.exitCode = 1
224+
} else {
225+
process.exit(1)
226+
}
227+
}
228+
}
229+
200230
function processFile (file) {
201231
file = normalize(file)
202232
if (options.logFiles) {
203233
console.log(file)
204234
}
205-
const original = readFileSync(file, 'utf8')
206-
let source = processContents(original, file)
235+
const source = readFileSync(file, 'utf8')
236+
const parsed = processContents(source, file)
207237
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'
213-
}
214-
writeFileSync(file, source)
238+
writeFileSync(file, ensureLineBreak(parsed, source))
239+
} else if (options.check) {
240+
checkContents(file, source, ensureLineBreak(parsed, source))
215241
} else {
216242
if (!(options.quiet || options.logFiles)) {
217-
console.log(source)
243+
console.log(parsed)
218244
}
219245
}
220246
}
@@ -278,11 +304,14 @@ function main () {
278304
source += chunk.toString('utf8')
279305
})
280306
stdin.on('end', () => {
307+
const file = '<stdin>'
281308
if (options.logFiles) {
282-
console.log('<stdin>')
309+
console.log(file)
283310
}
284-
const parsed = processContents(source, '<stdin>')
285-
if (!(options.quiet || options.logFiles)) {
311+
const parsed = processContents(source, file)
312+
if (options.check) {
313+
checkContents(file, source, ensureLineBreak(parsed, source))
314+
} else if (!(options.quiet || options.logFiles)) {
286315
console.log(parsed)
287316
}
288317
})

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,16 @@
7777
"dependencies": {
7878
"ajv": "6.12.6",
7979
"commander": "9.2.0",
80-
"cosmiconfig": "^7.0.1",
80+
"cosmiconfig": "7.0.1",
81+
"diff": "5.0.0",
8182
"fast-glob": "3.2.11"
8283
},
8384
"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",
8990
"eslint": "8.14.0",
9091
"eslint-config-standard": "17.0.0",
9192
"eslint-plugin-import": "2.26.0",

0 commit comments

Comments
 (0)