Skip to content

Commit

Permalink
perf(format): internalize breaklines and spaces as much as possible (#81
Browse files Browse the repository at this point in the history
)
  • Loading branch information
H4ad authored Jan 22, 2024
1 parent e82ebd9 commit c6d4008
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 12 deletions.
39 changes: 27 additions & 12 deletions src/impl/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { Range, FormattingOptions, Edit, SyntaxKind, ScanError } from '../main';
import { createScanner } from './scanner';
import { cachedSpaces, cachedBreakLinesWithSpaces, supportedEols, SupportedEOL } from './string-intern';

export function format(documentText: string, range: Range | undefined, options: FormattingOptions): Edit[] {
let initialIndentLevel: number;
Expand Down Expand Up @@ -35,26 +36,38 @@ export function format(documentText: string, range: Range | undefined, options:
rangeEnd = documentText.length;
}
const eol = getEOL(options, documentText);
const eolFastPathSupported = supportedEols.includes(eol as any);

let numberLineBreaks = 0;

let indentLevel = 0;
let indentValue: string;
if (options.insertSpaces) {
indentValue = repeat(' ', options.tabSize || 4);
indentValue = cachedSpaces[options.tabSize || 4] ?? repeat(cachedSpaces[1], options.tabSize || 4);
} else {
indentValue = '\t';
}
const indentType = indentValue === '\t' ? '\t' : ' ';

let scanner = createScanner(formatText, false);
let hasError = false;

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

const amountOfSpaces = indentValue.length * (initialIndentLevel + indentLevel);

if (!eolFastPathSupported || amountOfSpaces > cachedBreakLinesWithSpaces[indentType][eol as SupportedEOL].length) {
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
}

if (amountOfSpaces <= 0) {
return eol;
}

return cachedBreakLinesWithSpaces[indentType][eol as SupportedEOL][amountOfSpaces];
}

function scanNext(): SyntaxKind {
Expand Down Expand Up @@ -86,7 +99,9 @@ export function format(documentText: string, range: Range | undefined, options:

if (firstToken !== SyntaxKind.EOF) {
let firstTokenStart = scanner.getTokenOffset() + formatTextStart;
let initialIndent = repeat(indentValue, initialIndentLevel);
let initialIndent = (indentValue.length * initialIndentLevel < 20) && options.insertSpaces
? cachedSpaces[indentValue.length * initialIndentLevel]
: repeat(indentValue, initialIndentLevel);
addEdit(initialIndent, formatTextStart, firstTokenStart);
}

Expand All @@ -99,7 +114,7 @@ export function format(documentText: string, range: Range | undefined, options:

while (numberLineBreaks === 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
let commentTokenStart = scanner.getTokenOffset() + formatTextStart;
addEdit(' ', firstTokenEnd, commentTokenStart);
addEdit(cachedSpaces[1], firstTokenEnd, commentTokenStart);
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
needsLineBreak = secondToken === SyntaxKind.LineCommentTrivia;
replaceContent = needsLineBreak ? newLinesAndIndent() : '';
Expand All @@ -112,15 +127,15 @@ export function format(documentText: string, range: Range | undefined, options:
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBraceToken) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
} else if (secondToken === SyntaxKind.CloseBracketToken) {
if (firstToken !== SyntaxKind.OpenBracketToken) { indentLevel--; };

if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBracketToken) {
replaceContent = newLinesAndIndent();
} else if (options.keepLines) {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
} else {
switch (firstToken) {
Expand All @@ -130,14 +145,14 @@ export function format(documentText: string, range: Range | undefined, options:
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
break;
case SyntaxKind.CommaToken:
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
replaceContent = newLinesAndIndent();
} else {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
break;
case SyntaxKind.LineCommentTrivia:
Expand All @@ -147,14 +162,14 @@ export function format(documentText: string, range: Range | undefined, options:
if (numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
break;
case SyntaxKind.ColonToken:
if (options.keepLines && numberLineBreaks > 0) {
replaceContent = newLinesAndIndent();
} else if (!needsLineBreak) {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
}
break;
case SyntaxKind.StringLiteral:
Expand All @@ -174,7 +189,7 @@ export function format(documentText: string, range: Range | undefined, options:
replaceContent = newLinesAndIndent();
} else {
if ((secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) && !needsLineBreak) {
replaceContent = ' ';
replaceContent = cachedSpaces[1];
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
hasError = true;
}
Expand Down Expand Up @@ -216,7 +231,7 @@ function computeIndentLevel(content: string, options: FormattingOptions): number
const tabSize = options.tabSize || 4;
while (i < content.length) {
let ch = content.charAt(i);
if (ch === ' ') {
if (ch === cachedSpaces[1]) {
nChars++;
} else if (ch === '\t') {
nChars += tabSize;
Expand Down
33 changes: 33 additions & 0 deletions src/impl/string-intern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export const cachedSpaces = new Array(20).fill(0).map((_, index) => {
return ' '.repeat(index);
});

const maxCachedValues = 200;

export const cachedBreakLinesWithSpaces = {
' ': {
'\n': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\n' + ' '.repeat(index);
}),
'\r': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\r' + ' '.repeat(index);
}),
'\r\n': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\r\n' + ' '.repeat(index);
}),
},
'\t': {
'\n': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\n' + '\t'.repeat(index);
}),
'\r': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\r' + '\t'.repeat(index);
}),
'\r\n': new Array(maxCachedValues).fill(0).map((_, index) => {
return '\r\n' + '\t'.repeat(index);
}),
}
};

export const supportedEols = ['\n', '\r', '\r\n'] as const;
export type SupportedEOL = '\n' | '\r' | '\r\n';
20 changes: 20 additions & 0 deletions src/test/string-intern.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { cachedBreakLinesWithSpaces, cachedSpaces, supportedEols } from '../impl/string-intern';
import * as assert from 'assert';

suite('string intern', () => {
test('should correctly define spaces intern', () => {
for (let i = 0; i < cachedSpaces.length; i++) {
assert.strictEqual(cachedSpaces[i], ' '.repeat(i));
}
});

test('should correctly define break lines with spaces intern', () => {
for (const indentType of [' ', '\t'] as const) {
for (const eol of supportedEols) {
for (let i = 0; i < cachedBreakLinesWithSpaces[indentType][eol].length; i++) {
assert.strictEqual(cachedBreakLinesWithSpaces[indentType][eol][i], eol + indentType.repeat(i));
}
}
}
});
});

0 comments on commit c6d4008

Please sign in to comment.