Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ the formatter falls back to a compact style with no extra spaces or indentation.
|------------------------------|----------------------|--------------------------------------------------------|
| `indentationLevel` | `number` | Base indentation level for the document. |
| `indentationCharacter` | `'space'\|'tab'` | Character used for indentation. |
| `lineEnding` | `'lf'\|'crlf'` | Newline sequence to use in the document. |
| `string.quote` | `'single'\|'double'` | Quotation style for string values. |
| `property.quote` | `'single'\|'double'` | Quotation style for property keys. |
| `property.unquoted` | `boolean` | Allow unquoted property keys when valid. |
Expand Down
1 change: 1 addition & 0 deletions src/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type BlockFormatting = {
export type Formatting = {
indentationLevel?: number,
indentationCharacter?: 'space' | 'tab',
lineEnding?: 'lf' | 'crlf',
string?: {
quote?: 'single' | 'double',
},
Expand Down
30 changes: 14 additions & 16 deletions src/node/structureNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,7 @@ export abstract class JsonStructureNode extends JsonValueNode {
if (indentationSize > 0 && manipulator.matchesNext(node => endToken.isEquivalent(node))) {
// If the following token is the end token, always indent.
// This ensures it won't consume the indentation of the end delimiter.
manipulator.node(
new JsonTokenNode({
type: JsonTokenType.NEWLINE,
value: '\n',
}),
);
manipulator.node(this.getNewlineToken(formatting));

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

if (manipulator.matchesPreviousToken(JsonTokenType.LINE_COMMENT)) {
manipulator.insert(
new JsonTokenNode({
type: JsonTokenType.NEWLINE,
value: '\n',
}),
);
manipulator.insert(this.getNewlineToken(formatting));
} else if (
manipulator.position > 1
&& !currentMatched
Expand Down Expand Up @@ -373,6 +363,10 @@ export abstract class JsonStructureNode extends JsonValueNode {
}
}

if (NEWLINE(token)) {
formatting.lineEnding = token.value.includes('\r\n') ? 'crlf' : 'lf';
}

if (inlineComma && index > 0 && tokens[index - 1].depth === 0) {
if (!NEWLINE(token)) {
blockFormatting.commaSpacing = WHITESPACE(token);
Expand Down Expand Up @@ -485,10 +479,7 @@ export abstract class JsonStructureNode extends JsonValueNode {
return;
}

const newLine = new JsonTokenNode({
type: JsonTokenType.NEWLINE,
value: '\n',
});
const newLine = this.getNewlineToken(formatting);

manipulator.token(newLine, optional);

Expand All @@ -511,6 +502,13 @@ export abstract class JsonStructureNode extends JsonValueNode {
});
}

private getNewlineToken(formatting: Formatting): JsonTokenNode {
return new JsonTokenNode({
type: JsonTokenType.NEWLINE,
value: formatting.lineEnding === 'crlf' ? '\r\n' : '\n',
});
}

private static* iterate(
parent: JsonCompositeNode,
maxDepth: number,
Expand Down
40 changes: 40 additions & 0 deletions test/functional.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ describe('Functional test', () => {
input: "'\uD83C\uDFBC'",
expected: '🎼',
},
{
input: '{\r\n "foo": "1",\r\n "bar": "2"\r\n}',
expected: {
foo: '1',
bar: '2',
},
},
])('should parse $input', ({input, expected}) => {
const parser = new JsonParser(input);
const node = parser.parseValue();
Expand Down Expand Up @@ -334,6 +341,17 @@ describe('Functional test', () => {
node.set('bar', 2);
},
},
{
description: 'use \\r\\n line endings when detected',
// language=JSON
input: '{\r\n "foo": "1"\r\n}',
// language=JSON
output: '{\r\n "foo": "1",\r\n "bar": "2"\r\n}',
type: JsonObjectNode,
mutation: (node: JsonObjectNode): void => {
node.set('bar', '2');
},
},
{
description: 'use the same indentation character as the the parent',
// language=JSON
Expand Down Expand Up @@ -3028,6 +3046,28 @@ describe('Functional test', () => {
node.set('bar', 'qux');
},
},
{
description: 'preserve carriage return and line feed as line ending',
// language=JSON5
input: '{\r\n "foo": 1,\r\n "bar": 2\r\n}',
// language=JSON5
output: '{\r\n "foo": 1\r\n}',
type: JsonObjectNode,
mutation: (node: JsonObjectNode): void => {
node.delete('bar');
},
},
{
description: 'detect carriage return and line feed as line ending',
// language=JSON5
input: '{\r\n "foo": 1,\r\n "bar": 2\r\n}',
// language=JSON5
output: '{\r\n "foo": 1\r\n}',
type: JsonObjectNode,
mutation: (node: JsonObjectNode): void => {
node.delete('bar');
},
},
])('should $description', ({input, output, type, mutation, format}) => {
const node = JsonParser.parse(input, type);

Expand Down
Loading