Skip to content

Commit 330e43e

Browse files
Add support for configurable line ends (#10)
1 parent 0269e92 commit 330e43e

File tree

4 files changed

+56
-16
lines changed

4 files changed

+56
-16
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ the formatter falls back to a compact style with no extra spaces or indentation.
252252
|------------------------------|----------------------|--------------------------------------------------------|
253253
| `indentationLevel` | `number` | Base indentation level for the document. |
254254
| `indentationCharacter` | `'space'\|'tab'` | Character used for indentation. |
255+
| `lineEnding` | `'lf'\|'crlf'` | Newline sequence to use in the document. |
255256
| `string.quote` | `'single'\|'double'` | Quotation style for string values. |
256257
| `property.quote` | `'single'\|'double'` | Quotation style for property keys. |
257258
| `property.unquoted` | `boolean` | Allow unquoted property keys when valid. |

src/formatting.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type BlockFormatting = {
1111
export type Formatting = {
1212
indentationLevel?: number,
1313
indentationCharacter?: 'space' | 'tab',
14+
lineEnding?: 'lf' | 'crlf',
1415
string?: {
1516
quote?: 'single' | 'double',
1617
},

src/node/structureNode.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,7 @@ export abstract class JsonStructureNode extends JsonValueNode {
164164
if (indentationSize > 0 && manipulator.matchesNext(node => endToken.isEquivalent(node))) {
165165
// If the following token is the end token, always indent.
166166
// This ensures it won't consume the indentation of the end delimiter.
167-
manipulator.node(
168-
new JsonTokenNode({
169-
type: JsonTokenType.NEWLINE,
170-
value: '\n',
171-
}),
172-
);
167+
manipulator.node(this.getNewlineToken(formatting));
173168

174169
if (
175170
manipulator.matchesToken(JsonTokenType.WHITESPACE)
@@ -187,12 +182,7 @@ export abstract class JsonStructureNode extends JsonValueNode {
187182
previousMatched = currentMatched;
188183

189184
if (manipulator.matchesPreviousToken(JsonTokenType.LINE_COMMENT)) {
190-
manipulator.insert(
191-
new JsonTokenNode({
192-
type: JsonTokenType.NEWLINE,
193-
value: '\n',
194-
}),
195-
);
185+
manipulator.insert(this.getNewlineToken(formatting));
196186
} else if (
197187
manipulator.position > 1
198188
&& !currentMatched
@@ -373,6 +363,10 @@ export abstract class JsonStructureNode extends JsonValueNode {
373363
}
374364
}
375365

366+
if (NEWLINE(token)) {
367+
formatting.lineEnding = token.value.includes('\r\n') ? 'crlf' : 'lf';
368+
}
369+
376370
if (inlineComma && index > 0 && tokens[index - 1].depth === 0) {
377371
if (!NEWLINE(token)) {
378372
blockFormatting.commaSpacing = WHITESPACE(token);
@@ -485,10 +479,7 @@ export abstract class JsonStructureNode extends JsonValueNode {
485479
return;
486480
}
487481

488-
const newLine = new JsonTokenNode({
489-
type: JsonTokenType.NEWLINE,
490-
value: '\n',
491-
});
482+
const newLine = this.getNewlineToken(formatting);
492483

493484
manipulator.token(newLine, optional);
494485

@@ -511,6 +502,13 @@ export abstract class JsonStructureNode extends JsonValueNode {
511502
});
512503
}
513504

505+
private getNewlineToken(formatting: Formatting): JsonTokenNode {
506+
return new JsonTokenNode({
507+
type: JsonTokenType.NEWLINE,
508+
value: formatting.lineEnding === 'crlf' ? '\r\n' : '\n',
509+
});
510+
}
511+
514512
private static* iterate(
515513
parent: JsonCompositeNode,
516514
maxDepth: number,

test/functional.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ describe('Functional test', () => {
214214
input: "'\uD83C\uDFBC'",
215215
expected: '🎼',
216216
},
217+
{
218+
input: '{\r\n "foo": "1",\r\n "bar": "2"\r\n}',
219+
expected: {
220+
foo: '1',
221+
bar: '2',
222+
},
223+
},
217224
])('should parse $input', ({input, expected}) => {
218225
const parser = new JsonParser(input);
219226
const node = parser.parseValue();
@@ -334,6 +341,17 @@ describe('Functional test', () => {
334341
node.set('bar', 2);
335342
},
336343
},
344+
{
345+
description: 'use \\r\\n line endings when detected',
346+
// language=JSON
347+
input: '{\r\n "foo": "1"\r\n}',
348+
// language=JSON
349+
output: '{\r\n "foo": "1",\r\n "bar": "2"\r\n}',
350+
type: JsonObjectNode,
351+
mutation: (node: JsonObjectNode): void => {
352+
node.set('bar', '2');
353+
},
354+
},
337355
{
338356
description: 'use the same indentation character as the the parent',
339357
// language=JSON
@@ -3028,6 +3046,28 @@ describe('Functional test', () => {
30283046
node.set('bar', 'qux');
30293047
},
30303048
},
3049+
{
3050+
description: 'preserve carriage return and line feed as line ending',
3051+
// language=JSON5
3052+
input: '{\r\n "foo": 1,\r\n "bar": 2\r\n}',
3053+
// language=JSON5
3054+
output: '{\r\n "foo": 1\r\n}',
3055+
type: JsonObjectNode,
3056+
mutation: (node: JsonObjectNode): void => {
3057+
node.delete('bar');
3058+
},
3059+
},
3060+
{
3061+
description: 'detect carriage return and line feed as line ending',
3062+
// language=JSON5
3063+
input: '{\r\n "foo": 1,\r\n "bar": 2\r\n}',
3064+
// language=JSON5
3065+
output: '{\r\n "foo": 1\r\n}',
3066+
type: JsonObjectNode,
3067+
mutation: (node: JsonObjectNode): void => {
3068+
node.delete('bar');
3069+
},
3070+
},
30313071
])('should $description', ({input, output, type, mutation, format}) => {
30323072
const node = JsonParser.parse(input, type);
30333073

0 commit comments

Comments
 (0)