Skip to content

Commit 6b6da17

Browse files
committed
feat: Allow unifying quotes around object keys to double or single ones (JSON5)
1 parent 8949b67 commit 6b6da17

File tree

7 files changed

+138
-74
lines changed

7 files changed

+138
-74
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ The [`print`](#pretty-printing) method accepts an object `options` as the second
202202
| `indent` | count of spaces or the specific characters to be used as an indentation unit |
203203
| `pruneComments` | will omit all tokens with comments |
204204
| `stripObjectKeys` | will not print quotes around object keys which are JavaScript identifier names |
205+
| `enforceDoubleQuotes` | will surround all strings with double quotes |
206+
| `enforceSingleQuotes` | will surround all strings with single quotes |
205207

206208
```js
207209
// Just concatenate the tokens to produce the same output as was the input.
@@ -220,6 +222,8 @@ print(tokens, { indent: 2 })
220222
print(tokens, { indent: ' ', pruneComments: true })
221223
// Print to multiple lines with indentation enabled and JSON5 object keys.
222224
print(tokens, { indent: '\t', stripObjectKeys: true })
225+
// Print to multiple lines with indentation enabled, unify JSON5 formatting.
226+
print(tokens, { indent: ' ', enforceDoubleQuotes: true })
223227
```
224228

225229
### Tokenizing

lib/cli.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ var options = require('commander')
3434
.option('-P, --pretty-print-invalid', 'force pretty-printing even for invalid input')
3535
.option('--prune-comments', 'omit comments from the prettified output')
3636
.option('--strip-object-keys', 'strip quotes from object keys if possible (JSON5)')
37+
.option('--enforce-double-quotes', 'surrounds all strings with double quotes')
38+
.option('--enforce-single-quotes', 'surrounds all strings with single quotes (JSON5)')
3739
.version(pkg.version, '-v, --version')
3840
.on('--help', () => {
3941
console.log()
@@ -86,7 +88,9 @@ function parse (source, file) {
8688
return printer.print(tokens, {
8789
indent: options.indent,
8890
pruneComments: options.pruneComments,
89-
stripObjectKeys: options.stripObjectKeys
91+
stripObjectKeys: options.stripObjectKeys,
92+
enforceDoubleQuotes: options.enforceDoubleQuotes,
93+
enforceSingleQuotes: options.enforceSingleQuotes
9094
})
9195
}
9296
if (options.sortKeys) {

lib/jsonlint.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ declare module '@prantlf/jsonlint/lib/printer' {
9090
indent?: number | string
9191
pruneComments?: boolean
9292
stripObjectKeys?: boolean
93+
enforceDoubleQuotes?: boolean
94+
enforceSingleQuotes?: boolean
9395
}
9496

9597
/**

lib/printer.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
var prettyPrint = indentString !== undefined
4949
var pruneComments = options.pruneComments
5050
var stripObjectKeys = options.stripObjectKeys
51+
var enforceDoubleQuotes = options.enforceDoubleQuotes
52+
var enforceSingleQuotes = options.enforceSingleQuotes
5153

5254
var outputString = ''
5355
var foundLineBreak, addedLineBreak, needsLineBreak
@@ -184,6 +186,14 @@
184186
if (stripObjectKeys && scopeType === '{' && !isValue &&
185187
isIdentifierName(tokenValue)) {
186188
outputString += tokenValue
189+
} else if (typeof tokenValue === 'string') {
190+
if (enforceDoubleQuotes && tokenContent[0] !== '"') {
191+
outputString += JSON.stringify(tokenValue)
192+
} else if (enforceSingleQuotes && tokenContent[0] !== '\'') {
193+
outputString += '\'' + tokenValue.replace(/'/g, '\\\'') + '\''
194+
} else {
195+
outputString += tokenContent
196+
}
187197
} else {
188198
outputString += tokenContent
189199
}

test/print.js

Lines changed: 81 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function addTest (description, test) {
1313
}
1414
}
1515

16-
var tokens = [ // '/* start */{ "a":1, // c\n"0b": [ 2,3 ]}'
16+
var mixedTokens = [ // '/* start */{ "a":1, // c\n"0b": [ 2,3 ]}'
1717
{ type: 'comment', raw: '/* start */' },
1818
{ type: 'symbol', raw: '{', value: '{' },
1919
{ type: 'whitespace', raw: ' ' },
@@ -37,73 +37,92 @@ var tokens = [ // '/* start */{ "a":1, // c\n"0b": [ 2,3 ]}'
3737
{ type: 'symbol', raw: '}', value: '}' }
3838
]
3939

40+
// `// test
41+
// {// test
42+
// // test
43+
// a:/* test */1,// test
44+
// b:2// test
45+
// // test
46+
// }// test
47+
// // test`
48+
var commentTokens = [
49+
{ type: 'comment', raw: '// test' },
50+
{ type: 'whitespace', raw: '\n' },
51+
{ type: 'symbol', raw: '{', value: '{' },
52+
{ type: 'comment', raw: '// test' },
53+
{ type: 'whitespace', raw: '\n' },
54+
{ type: 'comment', raw: '// test' },
55+
{ type: 'whitespace', raw: '\n' },
56+
{ type: 'literal', raw: 'a', value: 'a' },
57+
{ type: 'symbol', raw: ':', value: ':' },
58+
{ type: 'comment', raw: '/* test */' },
59+
{ type: 'literal', raw: '1', value: 1 },
60+
{ type: 'symbol', raw: ',', value: ',' },
61+
{ type: 'comment', raw: '// test' },
62+
{ type: 'whitespace', raw: '\n' },
63+
{ type: 'literal', raw: 'b', value: 'b' },
64+
{ type: 'symbol', raw: ':', value: ':' },
65+
{ type: 'literal', raw: '2', value: 2 },
66+
{ type: 'comment', raw: '// test' },
67+
{ type: 'whitespace', raw: '\n' },
68+
{ type: 'comment', raw: '// test' },
69+
{ type: 'whitespace', raw: '\n' },
70+
{ type: 'symbol', raw: '}', value: '}' },
71+
{ type: 'comment', raw: '// test' },
72+
{ type: 'whitespace', raw: '\n' },
73+
{ type: 'comment', raw: '// test' }
74+
]
75+
76+
// `{
77+
// // String parameter
78+
// "key": 'value',
79+
// }`
80+
var stringTokens = [
81+
{ type: 'symbol', raw: '{', value: '{' },
82+
{ type: 'whitespace', raw: '\n' },
83+
{ type: 'comment', raw: '// String parameter' },
84+
{ type: 'whitespace', raw: '\n' },
85+
{ type: 'literal', raw: '"key"', value: 'key' },
86+
{ type: 'symbol', raw: ':', value: ':' },
87+
{ type: 'whitespace', raw: ' ' },
88+
{ type: 'literal', raw: '\'value\'', value: 'value' },
89+
{ type: 'symbol', raw: ',', value: ',' },
90+
{ type: 'whitespace', raw: '\n' },
91+
{ type: 'symbol', raw: '}', value: '}' }
92+
]
93+
4094
addTest('concatenate tokens', function () {
41-
var output = print(tokens)
95+
var output = print(mixedTokens)
4296
assert.equal(output, '/* start */{ "a":1, // c\n"0b": [ 2,3 ]}')
4397
})
4498

4599
addTest('omit whitespace', function () {
46-
var output = print(tokens, {})
100+
var output = print(mixedTokens, {})
47101
assert.equal(output, '/* start */{"a":1,/* c */"0b":[2,3]}')
48102
})
49103

50104
addTest('introduce line breaks', function () {
51-
var output = print(tokens, { indent: '' })
105+
var output = print(mixedTokens, { indent: '' })
52106
assert.equal(output, '/* start */\n{\n"a": 1, // c\n"0b": [\n2,\n3\n]\n}')
53107
})
54108

55109
addTest('apply indent', function () {
56-
var output = print(tokens, { indent: 2 })
110+
var output = print(mixedTokens, { indent: 2 })
57111
assert.equal(output, '/* start */\n{\n "a": 1, // c\n "0b": [\n 2,\n 3\n ]\n}')
58112
})
59113

60114
addTest('omit comments', function () {
61-
var output = print(tokens, { pruneComments: true })
115+
var output = print(mixedTokens, { pruneComments: true })
62116
assert.equal(output, '{"a":1,"0b":[2,3]}')
63117
})
64118

65119
addTest('strip quotes from object keys', function () {
66-
var output = print(tokens, { stripObjectKeys: true })
120+
var output = print(mixedTokens, { stripObjectKeys: true })
67121
assert.equal(output, '/* start */{a:1,/* c */"0b":[2,3]}')
68122
})
69123

70124
addTest('keep comment locations', function () {
71-
// `// test
72-
// {// test
73-
// // test
74-
// a:/* test */1,// test
75-
// b:2// test
76-
// // test
77-
// }// test
78-
// // test`
79-
var tokens = [
80-
{ type: 'comment', raw: '// test' },
81-
{ type: 'whitespace', raw: '\n' },
82-
{ type: 'symbol', raw: '{', value: '{' },
83-
{ type: 'comment', raw: '// test' },
84-
{ type: 'whitespace', raw: '\n' },
85-
{ type: 'comment', raw: '// test' },
86-
{ type: 'whitespace', raw: '\n' },
87-
{ type: 'literal', raw: 'a', value: 'a' },
88-
{ type: 'symbol', raw: ':', value: ':' },
89-
{ type: 'comment', raw: '/* test */' },
90-
{ type: 'literal', raw: '1', value: 1 },
91-
{ type: 'symbol', raw: ',', value: ',' },
92-
{ type: 'comment', raw: '// test' },
93-
{ type: 'whitespace', raw: '\n' },
94-
{ type: 'literal', raw: 'b', value: 'b' },
95-
{ type: 'symbol', raw: ':', value: ':' },
96-
{ type: 'literal', raw: '2', value: 2 },
97-
{ type: 'comment', raw: '// test' },
98-
{ type: 'whitespace', raw: '\n' },
99-
{ type: 'comment', raw: '// test' },
100-
{ type: 'whitespace', raw: '\n' },
101-
{ type: 'symbol', raw: '}', value: '}' },
102-
{ type: 'comment', raw: '// test' },
103-
{ type: 'whitespace', raw: '\n' },
104-
{ type: 'comment', raw: '// test' }
105-
]
106-
var output = print(tokens, { indent: ' ' })
125+
var output = print(commentTokens, { indent: ' ' })
107126
assert.equal(output, '// test\n{ // test\n // test\n a: /* test */ 1, // test\n b: 2 // test\n // test\n} // test\n// test')
108127
// `// test
109128
// { // test
@@ -116,29 +135,30 @@ addTest('keep comment locations', function () {
116135
})
117136

118137
addTest('keep comment after opening an object scope indented', function () {
119-
// `{
120-
// // String parameter
121-
// "key": 'value',
122-
// }`
123-
var tokens = [
124-
{ type: 'symbol', raw: '{', value: '{' },
125-
{ type: 'whitespace', raw: '\n' },
126-
{ type: 'comment', raw: '// String parameter' },
127-
{ type: 'whitespace', raw: '\n' },
128-
{ type: 'literal', raw: '"key"', value: 'key' },
129-
{ type: 'symbol', raw: ':', value: ':' },
130-
{ type: 'whitespace', raw: ' ' },
131-
{ type: 'literal', raw: '\'value\'', value: 'value' },
132-
{ type: 'symbol', raw: ',', value: ',' },
133-
{ type: 'whitespace', raw: '\n' },
134-
{ type: 'symbol', raw: '}', value: '}' }
135-
]
136-
var output = print(tokens, { indent: ' ' })
138+
var output = print(stringTokens, { indent: ' ' })
137139
assert.equal(output, '{\n // String parameter\n "key": \'value\',\n \n}')
138140
// `{
139141
// // String parameter
140142
// "key": 'value',
141143
// }`
142144
})
143145

146+
addTest('enforce double quotes', function () {
147+
var output = print(stringTokens, { enforceDoubleQuotes: true })
148+
assert.equal(output, '{/* String parameter */"key":"value",}')
149+
})
150+
151+
addTest('enforce single quotes', function () {
152+
var output = print(stringTokens, { enforceSingleQuotes: true })
153+
assert.equal(output, '{/* String parameter */\'key\':\'value\',}')
154+
})
155+
156+
addTest('enforce double quotes, but strip quotes from object keys', function () {
157+
var output = print(stringTokens, {
158+
stripObjectKeys: true,
159+
enforceDoubleQuotes: true
160+
})
161+
assert.equal(output, '{/* String parameter */key:"value",}')
162+
})
163+
144164
if (require.main === module) { require('test').run(exports) }

test/typings.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ addTest('print', () => {
6565
print(tokens, { indent: ' ' })
6666
print(tokens, { pruneComments: true })
6767
print(tokens, { stripObjectKeys: true })
68+
print(tokens, { enforceDoubleQuotes: true })
69+
print(tokens, { enforceSingleQuotes: true })
6870
assert.ok(true)
6971
})
7072

web/jsonlint.html

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,14 @@ <h1>JSON Lint</h1>
136136
<input type="checkbox" id="strip-object-keys">
137137
<label for="strip-object-keys">Strip quotes from object keys</label>
138138
</div>
139+
<div style="display: none">
140+
<input type="checkbox" id="enforce-double-quotes">
141+
<label for="enforce-double-quotes">Enforce double quotes</label>
142+
</div>
143+
<div style="display: none">
144+
<input type="checkbox" id="enforce-single-quotes">
145+
<label for="enforce-single-quotes">Enforce single quotes</label>
146+
</div>
139147
</div>
140148
<div>
141149
<span>Standard:</span>
@@ -196,6 +204,12 @@ <h2>Result</h2>
196204
document
197205
.getElementById('pretty-print')
198206
.addEventListener('change', correctFormatting.bind(null, 'pretty-print', 'format'))
207+
document
208+
.getElementById('enforce-double-quotes')
209+
.addEventListener('change', ensureOneSelected.bind(null, 'enforce-double-quotes', 'enforce-single-quotes'))
210+
document
211+
.getElementById('enforce-single-quotes')
212+
.addEventListener('change', ensureOneSelected.bind(null, 'enforce-single-quotes', 'enforce-double-quotes'))
199213

200214
function toggleSchemaValidation () {
201215
var className = document.getElementById('with-schema').checked
@@ -214,33 +228,39 @@ <h2>Result</h2>
214228
}
215229

216230
function correctFormatting (selected, other) {
217-
if (document.getElementById(selected).checked &&
218-
document.getElementById(other).checked) {
219-
document.getElementById(other).checked = false
220-
}
221-
231+
ensureOneSelected(selected, other)
222232
var forFormat = ['sort-object-keys']
223-
var forPrettyPrint = ['prune-comments', 'strip-object-keys']
224-
swapVisibility(document.getElementById('pretty-print').checked,
233+
var forPrettyPrint = [
234+
'prune-comments', 'strip-object-keys',
235+
'enforce-double-quotes', 'enforce-single-quotes'
236+
]
237+
swapWrapperVisibility(document.getElementById('pretty-print').checked,
225238
forPrettyPrint, forFormat)
226239

227-
function swapVisibility (flag, show, hide) {
240+
function swapWrapperVisibility (flag, show, hide) {
228241
if (!flag) {
229242
var temp = show
230243
show = hide
231244
hide = temp
232245
}
233-
setVisibility(show, 'block')
234-
setVisibility(hide, 'none')
246+
setWrapperVisibility(show, 'block')
247+
setWrapperVisibility(hide, 'none')
235248
}
236249

237-
function setVisibility (elements, display) {
250+
function setWrapperVisibility (elements, display) {
238251
elements.forEach(function (element) {
239252
document.getElementById(element).parentElement.style.display = display
240253
})
241254
}
242255
}
243256

257+
function ensureOneSelected (selected, other) {
258+
if (document.getElementById(selected).checked &&
259+
document.getElementById(other).checked) {
260+
document.getElementById(other).checked = false
261+
}
262+
}
263+
244264
function performValidation () {
245265
document.getElementById('result-section').style.display = 'block'
246266
var parserOptions = getParserOptions()
@@ -282,7 +302,9 @@ <h2>Result</h2>
282302
return jsonlintPrinter.print(tokens, {
283303
indent: 2,
284304
pruneComments: document.getElementById('prune-comments').checked,
285-
stripObjectKeys: document.getElementById('strip-object-keys').checked
305+
stripObjectKeys: document.getElementById('strip-object-keys').checked,
306+
enforceDoubleQuotes: document.getElementById('enforce-double-quotes').checked,
307+
enforceSingleQuotes: document.getElementById('enforce-single-quotes').checked
286308
})
287309
}
288310

0 commit comments

Comments
 (0)