Skip to content

Commit 311c6df

Browse files
committed
feat: Ignore the leading UTF-8 byte-order mark (BOM)
1 parent 81526ce commit 311c6df

File tree

9 files changed

+48
-3
lines changed

9 files changed

+48
-3
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This is a fork of the original project ([zaach/jsonlint](https://github.com/zaac
1212
* Handles multiple files on the command line (by Greg Inman).
1313
* Walks directories recursively (by Paul Vollmer).
1414
* Provides 100% compatible interface to the native `JSON.parse` method.
15+
* Optionally ignores the leading UTF-8 byte-order mark (BOM).
1516
* Optionally recognizes JavaScript-style comments (CJSON) and single quoted strings (JSON5).
1617
* Optionally ignores trailing commas and reports duplicate object keys as an error.
1718
* Optionally checks that also the expected format matches, including sorted object keys.
@@ -129,6 +130,7 @@ Usage: `jsonlint [options] [<file, directory, pattern> ...]`
129130
-c, --compact compact error display
130131
-M, --mode [mode] set other parsing flags according to a format
131132
type (default: "json")
133+
-B, --bom ignore the leading UTF-8 byte-order mark
132134
-C, --comments recognize and ignore JavaScript-style comments
133135
-S, --single-quoted-strings support single quotes as string delimiters
134136
-T, --trailing-commas ignore trailing commas in objects and arrays
@@ -194,6 +196,7 @@ The configuration is an object with the following properties, described above, w
194196
| indent | |
195197
| compact | |
196198
| mode | |
199+
| bom | |
197200
| comments | |
198201
| single-quoted-strings | singleQuotedStrings |
199202
| trailing-commas | trailingCommas |
@@ -251,6 +254,7 @@ The `parse` method offers more detailed [error information](#error-handling), th
251254

252255
| Option | Description |
253256
| -------------------------- | ------------------------------------------- |
257+
| `ignoreBOM` | ignores the leading UTF-8 byte-order mark (boolean) |
254258
| `ignoreComments` | ignores single-line and multi-line JavaScript-style comments during parsing as another "whitespace" (boolean) |
255259
| `ignoreTrailingCommas` | ignores trailing commas in objects and arrays (boolean) |
256260
| `allowSingleQuotedStrings` | accepts strings delimited by single-quotes too (boolean) |

lib/cli.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const commander = require('commander')
2626
.option('-t, --indent [num|char]', 'number of spaces or specific characters to use for indentation', 2)
2727
.option('-c, --compact', 'compact error display')
2828
.option('-M, --mode [mode]', 'set other parsing flags according to a format type', 'json')
29+
.option('-B, --bom', 'ignore the leading UTF-8 byte-order mark')
2930
.option('-C, --comments', 'recognize and ignore JavaScript-style comments')
3031
.option('-S, --single-quoted-strings', 'support single quotes as string delimiters')
3132
.option('-T, --trailing-commas', 'ignore trailing commas in objects and arrays')
@@ -111,7 +112,7 @@ function mergeOptions (target, ...sources) {
111112
return target
112113
}
113114

114-
function separateBlocks() {
115+
function separateBlocks () {
115116
if (reported) {
116117
console.log()
117118
} else {
@@ -135,6 +136,7 @@ function processContents (source, file) {
135136
try {
136137
parserOptions = {
137138
mode: options.mode,
139+
ignoreBOM: options.bom,
138140
ignoreComments: options.comments,
139141
ignoreTrailingCommas: options.trailingCommas,
140142
allowSingleQuotedStrings: options.singleQuotedStrings,
@@ -249,7 +251,7 @@ function checkContents (file, source, parsed) {
249251
}
250252
}
251253

252-
function diffContents(file, source, parsed) {
254+
function diffContents (file, source, parsed) {
253255
const { createTwoFilesPatch, structuredPatch } = require('diff')
254256
const compact = options.quiet || options.compact
255257
let diff, length

lib/jsonlint.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
type ParseMode = 'json' | 'cjson' | 'json5'
22

33
interface ParseOptions {
4+
ignoreBOM?: boolean
45
ignoreComments?: boolean
56
ignoreTrailingCommas?: boolean
67
allowSingleQuotedStrings?: boolean
@@ -27,6 +28,7 @@ interface ParseOptions {
2728
declare function parse (input: string, reviverOrOptions?: Function | ParseOptions): object
2829

2930
interface TokenizeOptions {
31+
ignoreBOM?: boolean
3032
ignoreComments?: boolean
3133
ignoreTrailingCommas?: boolean
3234
allowSingleQuotedStrings?: boolean
@@ -58,6 +60,7 @@ declare module '@prantlf/jsonlint/lib/validator' {
5860
type Environment = 'json-schema-draft-04' | 'json-schema-draft-06' | 'json-schema-draft-07'
5961

6062
interface CompileOptions {
63+
ignoreBOM?: boolean
6164
ignoreComments?: boolean
6265
ignoreTrailingCommas?: boolean
6366
allowSingleQuotedStrings?: boolean

lib/validator.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
const ajv = createAjv(environment)
149149
const parseOptions = {
150150
mode: options.mode,
151+
ignoreBOM: options.ignoreBOM,
151152
ignoreComments: options.ignoreComments,
152153
ignoreTrailingCommas: options.ignoreTrailingCommas,
153154
allowSingleQuotedStrings: options.allowSingleQuotedStrings,

src/configurable-parser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const isSafari = typeof navigator !== 'undefined' && /Safari/.test(navigator.use
44
const oldNode = typeof process !== 'undefined' && process.version.startsWith('v4.')
55

66
function needsCustomParser (options) {
7-
return options.ignoreComments || options.ignoreTrailingCommas ||
7+
return options.ignoreBOM || options.ignoreComments || options.ignoreTrailingCommas ||
88
options.allowSingleQuotedStrings || options.allowDuplicateObjectKeys === false ||
99
options.mode === 'cjson' || options.mode === 'json5' || isSafari || oldNode
1010
}

src/custom-parser.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ function isDecDigit (x) {
1616
return x >= '0' && x <= '9'
1717
}
1818

19+
function isBOM (x) {
20+
// This catches EFBBBF (UTF-8 BOM) because the buffer-to-string
21+
// conversion in `fs.readFileSync()` translates it to FEFF (UTF-16 BOM).
22+
return x.charCodeAt(0) === 0xFEFF
23+
}
24+
1925
const unescapeMap = {
2026
'\'': '\'',
2127
'"': '"',
@@ -35,6 +41,7 @@ function parseInternal (input, options) {
3541
}
3642

3743
const json5 = options.mode === 'json5'
44+
const ignoreBOM = options.ignoreBOM
3845
const ignoreComments = options.ignoreComments || options.mode === 'cjson' || json5
3946
const ignoreTrailingCommas = options.ignoreTrailingCommas || json5
4047
const allowSingleQuotedStrings = options.allowSingleQuotedStrings || json5
@@ -221,6 +228,14 @@ function parseInternal (input, options) {
221228
}
222229
}
223230

231+
function skipBOM () {
232+
if (isBOM(input)) {
233+
startToken && startToken()
234+
++position
235+
endToken && endToken('bom')
236+
}
237+
}
238+
224239
function skipWhiteSpace () {
225240
let insideWhiteSpace
226241
function startWhiteSpace () {
@@ -615,6 +630,9 @@ function parseInternal (input, options) {
615630
fail()
616631
}
617632

633+
if (ignoreBOM) {
634+
skipBOM()
635+
}
618636
skipWhiteSpace()
619637
let returnValue = parseGeneric()
620638
if (returnValue !== undefined || position < inputLength) {

test/fails/bom.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"bom": "utf8"
3+
}

test/parse1.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ exports['test extra brace'] = function () {
255255
assert.throws(function () { parse(json) }, 'should throw error')
256256
}
257257

258+
exports['test failing with bom'] = function () {
259+
const json = fs.readFileSync(path.join(__dirname, '/fails/bom.json')).toString()
260+
assert.throws(function () { parse(json) }, 'should throw error')
261+
}
262+
263+
if (!nativeParser) {
264+
exports['test ignoring bom'] = function () {
265+
const json = fs.readFileSync(path.join(__dirname, '/fails/bom.json')).toString()
266+
assert.deepEqual(parse(json, { ignoreBOM: true }), { bom: 'utf8' })
267+
}
268+
}
269+
258270
if (!oldNode) {
259271
exports['test error location with Windows line breaks using the native parser'] = function () {
260272
const json = '{\r\n"foo": {\r\n "bar":\r\n }\r\n \r\n }'

test/typings.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ addTest('parse', () => {
2222
parse('{}', { mode: 'cjson' })
2323
parse('{}', { mode: 'json5' })
2424
parse('{}', {
25+
ignoreBOM: true,
2526
ignoreComments: true,
2627
ignoreTrailingCommas: true,
2728
allowSingleQuotedStrings: true,
@@ -45,6 +46,7 @@ addTest('compile', () => {
4546
compile('{}', { environment: 'json-schema-draft-06' })
4647
compile('{}', { environment: 'json-schema-draft-07' })
4748
compile('{}', {
49+
ignoreBOM: true,
4850
ignoreComments: true,
4951
ignoreTrailingCommas: true,
5052
allowSingleQuotedStrings: true,

0 commit comments

Comments
 (0)