Skip to content

Commit c6d4008

Browse files
authored
perf(format): internalize breaklines and spaces as much as possible (#81)
1 parent e82ebd9 commit c6d4008

File tree

3 files changed

+80
-12
lines changed

3 files changed

+80
-12
lines changed

src/impl/format.ts

+27-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

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

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

3941
let numberLineBreaks = 0;
4042

4143
let indentLevel = 0;
4244
let indentValue: string;
4345
if (options.insertSpaces) {
44-
indentValue = repeat(' ', options.tabSize || 4);
46+
indentValue = cachedSpaces[options.tabSize || 4] ?? repeat(cachedSpaces[1], options.tabSize || 4);
4547
} else {
4648
indentValue = '\t';
4749
}
50+
const indentType = indentValue === '\t' ? '\t' : ' ';
4851

4952
let scanner = createScanner(formatText, false);
5053
let hasError = false;
5154

5255
function newLinesAndIndent(): string {
5356
if (numberLineBreaks > 1) {
5457
return repeat(eol, numberLineBreaks) + repeat(indentValue, initialIndentLevel + indentLevel);
55-
} else {
58+
}
59+
60+
const amountOfSpaces = indentValue.length * (initialIndentLevel + indentLevel);
61+
62+
if (!eolFastPathSupported || amountOfSpaces > cachedBreakLinesWithSpaces[indentType][eol as SupportedEOL].length) {
5663
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
5764
}
65+
66+
if (amountOfSpaces <= 0) {
67+
return eol;
68+
}
69+
70+
return cachedBreakLinesWithSpaces[indentType][eol as SupportedEOL][amountOfSpaces];
5871
}
5972

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

87100
if (firstToken !== SyntaxKind.EOF) {
88101
let firstTokenStart = scanner.getTokenOffset() + formatTextStart;
89-
let initialIndent = repeat(indentValue, initialIndentLevel);
102+
let initialIndent = (indentValue.length * initialIndentLevel < 20) && options.insertSpaces
103+
? cachedSpaces[indentValue.length * initialIndentLevel]
104+
: repeat(indentValue, initialIndentLevel);
90105
addEdit(initialIndent, formatTextStart, firstTokenStart);
91106
}
92107

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

100115
while (numberLineBreaks === 0 && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) {
101116
let commentTokenStart = scanner.getTokenOffset() + formatTextStart;
102-
addEdit(' ', firstTokenEnd, commentTokenStart);
117+
addEdit(cachedSpaces[1], firstTokenEnd, commentTokenStart);
103118
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
104119
needsLineBreak = secondToken === SyntaxKind.LineCommentTrivia;
105120
replaceContent = needsLineBreak ? newLinesAndIndent() : '';
@@ -112,15 +127,15 @@ export function format(documentText: string, range: Range | undefined, options:
112127
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBraceToken) {
113128
replaceContent = newLinesAndIndent();
114129
} else if (options.keepLines) {
115-
replaceContent = ' ';
130+
replaceContent = cachedSpaces[1];
116131
}
117132
} else if (secondToken === SyntaxKind.CloseBracketToken) {
118133
if (firstToken !== SyntaxKind.OpenBracketToken) { indentLevel--; };
119134

120135
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== SyntaxKind.OpenBracketToken) {
121136
replaceContent = newLinesAndIndent();
122137
} else if (options.keepLines) {
123-
replaceContent = ' ';
138+
replaceContent = cachedSpaces[1];
124139
}
125140
} else {
126141
switch (firstToken) {
@@ -130,14 +145,14 @@ export function format(documentText: string, range: Range | undefined, options:
130145
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
131146
replaceContent = newLinesAndIndent();
132147
} else {
133-
replaceContent = ' ';
148+
replaceContent = cachedSpaces[1];
134149
}
135150
break;
136151
case SyntaxKind.CommaToken:
137152
if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
138153
replaceContent = newLinesAndIndent();
139154
} else {
140-
replaceContent = ' ';
155+
replaceContent = cachedSpaces[1];
141156
}
142157
break;
143158
case SyntaxKind.LineCommentTrivia:
@@ -147,14 +162,14 @@ export function format(documentText: string, range: Range | undefined, options:
147162
if (numberLineBreaks > 0) {
148163
replaceContent = newLinesAndIndent();
149164
} else if (!needsLineBreak) {
150-
replaceContent = ' ';
165+
replaceContent = cachedSpaces[1];
151166
}
152167
break;
153168
case SyntaxKind.ColonToken:
154169
if (options.keepLines && numberLineBreaks > 0) {
155170
replaceContent = newLinesAndIndent();
156171
} else if (!needsLineBreak) {
157-
replaceContent = ' ';
172+
replaceContent = cachedSpaces[1];
158173
}
159174
break;
160175
case SyntaxKind.StringLiteral:
@@ -174,7 +189,7 @@ export function format(documentText: string, range: Range | undefined, options:
174189
replaceContent = newLinesAndIndent();
175190
} else {
176191
if ((secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia) && !needsLineBreak) {
177-
replaceContent = ' ';
192+
replaceContent = cachedSpaces[1];
178193
} else if (secondToken !== SyntaxKind.CommaToken && secondToken !== SyntaxKind.EOF) {
179194
hasError = true;
180195
}
@@ -216,7 +231,7 @@ function computeIndentLevel(content: string, options: FormattingOptions): number
216231
const tabSize = options.tabSize || 4;
217232
while (i < content.length) {
218233
let ch = content.charAt(i);
219-
if (ch === ' ') {
234+
if (ch === cachedSpaces[1]) {
220235
nChars++;
221236
} else if (ch === '\t') {
222237
nChars += tabSize;

src/impl/string-intern.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export const cachedSpaces = new Array(20).fill(0).map((_, index) => {
2+
return ' '.repeat(index);
3+
});
4+
5+
const maxCachedValues = 200;
6+
7+
export const cachedBreakLinesWithSpaces = {
8+
' ': {
9+
'\n': new Array(maxCachedValues).fill(0).map((_, index) => {
10+
return '\n' + ' '.repeat(index);
11+
}),
12+
'\r': new Array(maxCachedValues).fill(0).map((_, index) => {
13+
return '\r' + ' '.repeat(index);
14+
}),
15+
'\r\n': new Array(maxCachedValues).fill(0).map((_, index) => {
16+
return '\r\n' + ' '.repeat(index);
17+
}),
18+
},
19+
'\t': {
20+
'\n': new Array(maxCachedValues).fill(0).map((_, index) => {
21+
return '\n' + '\t'.repeat(index);
22+
}),
23+
'\r': new Array(maxCachedValues).fill(0).map((_, index) => {
24+
return '\r' + '\t'.repeat(index);
25+
}),
26+
'\r\n': new Array(maxCachedValues).fill(0).map((_, index) => {
27+
return '\r\n' + '\t'.repeat(index);
28+
}),
29+
}
30+
};
31+
32+
export const supportedEols = ['\n', '\r', '\r\n'] as const;
33+
export type SupportedEOL = '\n' | '\r' | '\r\n';

src/test/string-intern.test.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cachedBreakLinesWithSpaces, cachedSpaces, supportedEols } from '../impl/string-intern';
2+
import * as assert from 'assert';
3+
4+
suite('string intern', () => {
5+
test('should correctly define spaces intern', () => {
6+
for (let i = 0; i < cachedSpaces.length; i++) {
7+
assert.strictEqual(cachedSpaces[i], ' '.repeat(i));
8+
}
9+
});
10+
11+
test('should correctly define break lines with spaces intern', () => {
12+
for (const indentType of [' ', '\t'] as const) {
13+
for (const eol of supportedEols) {
14+
for (let i = 0; i < cachedBreakLinesWithSpaces[indentType][eol].length; i++) {
15+
assert.strictEqual(cachedBreakLinesWithSpaces[indentType][eol][i], eol + indentType.repeat(i));
16+
}
17+
}
18+
}
19+
});
20+
});

0 commit comments

Comments
 (0)