Skip to content
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
3.1.0 2022-07-07
==================
* Added an an additional feature `keepLines` in the formatting of JSON documents which leaves the lines as is

3.0.0 2020-11-13
==================
* fixed API spec for `parseTree`. Can return `undefine` for empty input.
Expand Down
151 changes: 115 additions & 36 deletions src/impl/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import { mainModule } from 'process';
import { Range, FormattingOptions, Edit, SyntaxKind, ScanError } from '../main';
import { createScanner } from './scanner';

Expand Down Expand Up @@ -36,7 +37,7 @@ export function format(documentText: string, range: Range | undefined, options:
}
let eol = getEOL(options, documentText);

let lineBreak = false;
let numberLineBreaks = 0;
let indentLevel = 0;
let indentValue: string;
if (options.insertSpaces) {
Expand All @@ -48,14 +49,23 @@ export function format(documentText: string, range: Range | undefined, options:
let scanner = createScanner(formatText, false);
let hasError = false;

function newLineAndIndent(): string {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
function newLinesAndIndent(): string {
if (numberLineBreaks > 1) {
return repeat(eol, numberLineBreaks) + repeat(indentValue, initialIndentLevel + indentLevel);
} else {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
}
}

function scanNext(): SyntaxKind {
let token = scanner.scan();
lineBreak = false;
numberLineBreaks = 0;
while (token === SyntaxKind.Trivia || token === SyntaxKind.LineBreakTrivia) {
lineBreak = lineBreak || (token === SyntaxKind.LineBreakTrivia);
if (token === SyntaxKind.LineBreakTrivia && options.keepLines) {
numberLineBreaks += 1;
} else if (token === SyntaxKind.LineBreakTrivia) {
numberLineBreaks = 1;
}
token = scanner.scan();
}
hasError = token === SyntaxKind.Unknown || scanner.getTokenError() !== ScanError.None;
Expand All @@ -79,85 +89,154 @@ export function format(documentText: string, range: Range | undefined, options:
while (firstToken !== SyntaxKind.EOF) {
let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
let secondToken = scanNext();

let replaceContent = '';
let needsLineBreak = false;
while (!lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
// comments on the same line: keep them on the same line, but ignore them otherwise
while (numberLineBreaks === 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
let commentTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(' ', firstTokenEnd, commentTokenStart);
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
needsLineBreak = secondToken === SyntaxKind.LineCommentTrivia;
replaceContent = needsLineBreak ? newLineAndIndent() : '';
replaceContent = needsLineBreak ? newLinesAndIndent() : '';
secondToken = scanNext();
}

if (secondToken === SyntaxKind.CloseBraceToken) {
if (firstToken !== SyntaxKind.OpenBraceToken) {
indentLevel--;
replaceContent = newLineAndIndent();
if (firstToken !== SyntaxKind.OpenBraceToken) { indentLevel--; };
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = ' ';
}
} else if (firstToken !== SyntaxKind.OpenBraceToken) {
replaceContent = newLinesAndIndent();
}
} else if (secondToken === SyntaxKind.CloseBracketToken) {
if (firstToken !== SyntaxKind.OpenBracketToken) {
indentLevel--;
replaceContent = newLineAndIndent();
if (firstToken !== SyntaxKind.OpenBracketToken) { indentLevel--; };
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = ' ';
}
}
else if (firstToken !== SyntaxKind.OpenBracketToken) {
replaceContent = newLinesAndIndent();
}
} else {
switch (firstToken) {
case SyntaxKind.OpenBracketToken:
case SyntaxKind.OpenBraceToken:
indentLevel++;
replaceContent = newLineAndIndent();
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = " ";
}
} else {
replaceContent = newLinesAndIndent();
}
break;
case SyntaxKind.CommaToken:
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = " ";
}
} else {
replaceContent = newLinesAndIndent();
}
break;
case SyntaxKind.LineCommentTrivia:
replaceContent = newLineAndIndent();
replaceContent = newLinesAndIndent();
break;
case SyntaxKind.BlockCommentTrivia:
if (lineBreak) {
replaceContent = newLineAndIndent();
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
// symbol following comment on the same line: keep on same line, separate with ' '
replaceContent = ' ';
}
break;
case SyntaxKind.ColonToken:
if (!needsLineBreak) {
replaceContent = ' ';
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
replaceContent = ' ';
}
} else {
if (!needsLineBreak) {
replaceContent = ' ';
}
}
break;
case SyntaxKind.StringLiteral:
if (secondToken === SyntaxKind.ColonToken) {
if (!needsLineBreak) {
replaceContent = '';
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
if (secondToken === SyntaxKind.ColonToken) {
if (!needsLineBreak) {
replaceContent = '';
}
}
}
} else {
if (secondToken === SyntaxKind.ColonToken) {
if (!needsLineBreak) {
replaceContent = '';
}
}
break;
}
// fall through
break;
case SyntaxKind.NullKeyword:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
case SyntaxKind.NumericLiteral:
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken:
if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) {
if (!needsLineBreak) {
replaceContent = ' ';
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) {
if (!needsLineBreak) {
replaceContent = ' ';
}
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
}
} else {
if (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) {
if (!needsLineBreak) {
replaceContent = ' ';
}
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
break;
case SyntaxKind.Unknown:
hasError = true;
break;
}
if (lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
replaceContent = newLineAndIndent();
if (numberLineBreaks > 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
replaceContent = newLinesAndIndent();
}
}
if (secondToken === SyntaxKind.EOF) {
replaceContent = options.insertFinalNewline ? eol : '';
if (options.keepLines) {
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = options.insertFinalNewline ? eol : '';
}
} else {
replaceContent = options.insertFinalNewline ? eol : '';
}
}
let secondTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(replaceContent, firstTokenEnd, secondTokenStart);
Expand Down Expand Up @@ -209,4 +288,4 @@ function getEOL(options: FormattingOptions, text: string): string {

export function isEOL(text: string, offset: number) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
}
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ export interface FormattingOptions {
* If set, will add a new line at the end of the document.
*/
insertFinalNewline?: boolean;
/**
* If true, will keep line positions as is in the formatting
*/
keepLines?: boolean;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/test/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ suite('JSON - edits', () => {
let formattingOptions: FormattingOptions = {
insertSpaces: true,
tabSize: 2,
eol: '\n'
eol: '\n',
keepLines: false
};

let options: ModificationOptions = {
Expand Down
Loading