Skip to content

Commit 393f055

Browse files
authored
Improve memory usage of formatter (#1107)
1 parent 8f0e49f commit 393f055

File tree

7 files changed

+139
-75
lines changed

7 files changed

+139
-75
lines changed

internal/execute/tsc.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,34 @@ import (
1313
"github.com/microsoft/typescript-go/internal/compiler"
1414
"github.com/microsoft/typescript-go/internal/core"
1515
"github.com/microsoft/typescript-go/internal/diagnostics"
16+
"github.com/microsoft/typescript-go/internal/format"
17+
"github.com/microsoft/typescript-go/internal/parser"
1618
"github.com/microsoft/typescript-go/internal/pprof"
19+
"github.com/microsoft/typescript-go/internal/scanner"
1720
"github.com/microsoft/typescript-go/internal/tsoptions"
1821
"github.com/microsoft/typescript-go/internal/tspath"
1922
)
2023

2124
type cbType = func(p any) any
2225

26+
func applyBulkEdits(text string, edits []core.TextChange) string {
27+
b := strings.Builder{}
28+
b.Grow(len(text))
29+
lastEnd := 0
30+
for _, e := range edits {
31+
start := e.TextRange.Pos()
32+
if start != lastEnd {
33+
b.WriteString(text[lastEnd:e.TextRange.Pos()])
34+
}
35+
b.WriteString(e.NewText)
36+
37+
lastEnd = e.TextRange.End()
38+
}
39+
b.WriteString(text[lastEnd:])
40+
41+
return b.String()
42+
}
43+
2344
func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus {
2445
if len(commandLineArgs) > 0 {
2546
// !!! build mode
@@ -28,6 +49,8 @@ func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus {
2849
fmt.Fprint(sys.Writer(), "Build mode is currently unsupported."+sys.NewLine())
2950
sys.EndWrite()
3051
return ExitStatusNotImplemented
52+
// case "-f":
53+
// return fmtMain(sys, commandLineArgs[1], commandLineArgs[1])
3154
}
3255
}
3356

@@ -39,6 +62,38 @@ func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus {
3962
return start(watcher)
4063
}
4164

65+
func fmtMain(sys System, input, output string) ExitStatus {
66+
ctx := format.WithFormatCodeSettings(context.Background(), format.GetDefaultFormatCodeSettings(sys.NewLine()), sys.NewLine())
67+
input = string(tspath.ToPath(input, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames()))
68+
output = string(tspath.ToPath(output, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames()))
69+
fileContent, ok := sys.FS().ReadFile(input)
70+
if !ok {
71+
fmt.Fprint(sys.Writer(), "File not found: "+input+sys.NewLine())
72+
return ExitStatusNotImplemented
73+
}
74+
text := fileContent
75+
pathified := tspath.ToPath(input, sys.GetCurrentDirectory(), true)
76+
sourceFile := parser.ParseSourceFile(
77+
string(pathified),
78+
pathified,
79+
text,
80+
&core.SourceFileAffectingCompilerOptions{
81+
EmitScriptTarget: core.ScriptTargetLatest,
82+
},
83+
nil,
84+
scanner.JSDocParsingModeParseAll,
85+
)
86+
ast.SetParentInChildren(sourceFile.AsNode())
87+
edits := format.FormatDocument(ctx, sourceFile)
88+
newText := applyBulkEdits(text, edits)
89+
90+
if err := sys.FS().WriteFile(output, newText, false); err != nil {
91+
fmt.Fprint(sys.Writer(), err.Error()+sys.NewLine())
92+
return ExitStatusNotImplemented
93+
}
94+
return ExitStatusSuccess
95+
}
96+
4297
func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.ParsedCommandLine) (ExitStatus, *watcher) {
4398
configFileName := ""
4499
reportDiagnostic := createDiagnosticReporter(sys, commandLine.CompilerOptions())

internal/format/context.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ func GetDefaultFormatCodeSettings(newLineCharacter string) *FormatCodeSettings {
8686
}
8787

8888
type formattingContext struct {
89-
currentTokenSpan *TextRangeWithKind
90-
nextTokenSpan *TextRangeWithKind
89+
currentTokenSpan TextRangeWithKind
90+
nextTokenSpan TextRangeWithKind
9191
contextNode *ast.Node
9292
currentTokenParent *ast.Node
9393
nextTokenParent *ast.Node
@@ -117,16 +117,10 @@ func NewFormattingContext(file *ast.SourceFile, kind FormatRequestKind, options
117117
return res
118118
}
119119

120-
func (this *formattingContext) UpdateContext(cur *TextRangeWithKind, curParent *ast.Node, next *TextRangeWithKind, nextParent *ast.Node, commonParent *ast.Node) {
121-
if cur == nil {
122-
panic("nil current range in update context")
123-
}
120+
func (this *formattingContext) UpdateContext(cur TextRangeWithKind, curParent *ast.Node, next TextRangeWithKind, nextParent *ast.Node, commonParent *ast.Node) {
124121
if curParent == nil {
125122
panic("nil current range node parent in update context")
126123
}
127-
if next == nil {
128-
panic("nil next range in update context")
129-
}
130124
if nextParent == nil {
131125
panic("nil next range node parent in update context")
132126
}

internal/format/indent.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,11 @@ func getActualIndentationForListItem(node *ast.Node, sourceFile *ast.SourceFile,
152152
if listIndentsChild {
153153
delta = options.IndentSize
154154
}
155-
return getActualIndentationForListStartLine(containingList, sourceFile, options) + delta
155+
res := getActualIndentationForListStartLine(containingList, sourceFile, options)
156+
if res == -1 {
157+
return delta
158+
}
159+
return res + delta
156160
}
157161
return -1
158162
}

internal/format/rulecontext.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import (
1515
///
1616

1717
type (
18-
optionSelector = func(options *FormatCodeSettings) core.Tristate
19-
anyOptionSelector = func(options *FormatCodeSettings) any
18+
optionSelector = func(options *FormatCodeSettings) core.Tristate
19+
anyOptionSelector[T comparable] = func(options *FormatCodeSettings) T
2020
)
2121

22-
func semicolonOption(options *FormatCodeSettings) any { return options.Semicolons }
22+
func semicolonOption(options *FormatCodeSettings) SemicolonPreference { return options.Semicolons }
2323
func insertSpaceAfterCommaDelimiterOption(options *FormatCodeSettings) core.Tristate {
2424
return options.InsertSpaceAfterCommaDelimiter
2525
}
@@ -96,7 +96,7 @@ func indentSwitchCaseOption(options *FormatCodeSettings) core.Tristate {
9696
return options.IndentSwitchCase
9797
}
9898

99-
func optionEquals(optionName anyOptionSelector, optionValue any) contextPredicate {
99+
func optionEquals[T comparable](optionName anyOptionSelector[T], optionValue T) contextPredicate {
100100
return func(context *formattingContext) bool {
101101
if context.Options == nil {
102102
return false
@@ -494,7 +494,7 @@ func isConstructorSignatureContext(context *formattingContext) bool {
494494
return context.contextNode.Kind == ast.KindConstructSignature
495495
}
496496

497-
func isTypeArgumentOrParameterOrAssertion(token *TextRangeWithKind, parent *ast.Node) bool {
497+
func isTypeArgumentOrParameterOrAssertion(token TextRangeWithKind, parent *ast.Node) bool {
498498
if token.Kind != ast.KindLessThanToken && token.Kind != ast.KindGreaterThanToken {
499499
return false
500500
}

internal/format/rulesmap.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import (
77
"github.com/microsoft/typescript-go/internal/ast"
88
)
99

10-
func getRules(context *formattingContext) []*ruleImpl {
10+
func getRules(context *formattingContext, rules []*ruleImpl) []*ruleImpl {
1111
bucket := getRulesMap()[getRuleBucketIndex(context.currentTokenSpan.Kind, context.nextTokenSpan.Kind)]
1212
if len(bucket) > 0 {
13-
var rules []*ruleImpl
1413
ruleActionMask := ruleActionNone
1514
outer:
1615
for _, rule := range bucket {
@@ -28,7 +27,7 @@ func getRules(context *formattingContext) []*ruleImpl {
2827
}
2928
return rules
3029
}
31-
return nil
30+
return rules
3231
}
3332

3433
func getRuleBucketIndex(row ast.Kind, column ast.Kind) int {

internal/format/scanner.go

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,30 @@ type TextRangeWithKind struct {
1313
Kind ast.Kind
1414
}
1515

16-
func NewTextRangeWithKind(pos int, end int, kind ast.Kind) *TextRangeWithKind {
17-
return &TextRangeWithKind{
16+
func NewTextRangeWithKind(pos int, end int, kind ast.Kind) TextRangeWithKind {
17+
return TextRangeWithKind{
1818
Loc: core.NewTextRange(pos, end),
1919
Kind: kind,
2020
}
2121
}
2222

2323
type tokenInfo struct {
24-
leadingTrivia []*TextRangeWithKind
25-
token *TextRangeWithKind
26-
trailingTrivia []*TextRangeWithKind
24+
leadingTrivia []TextRangeWithKind
25+
token TextRangeWithKind
26+
trailingTrivia []TextRangeWithKind
2727
}
2828

2929
type formattingScanner struct {
30-
s *scanner.Scanner
31-
startPos int
32-
endPos int
33-
savedPos int
34-
lastTokenInfo *tokenInfo
35-
lastScanAction scanAction
36-
leadingTrivia []*TextRangeWithKind
37-
trailingTrivia []*TextRangeWithKind
38-
wasNewLine bool
30+
s *scanner.Scanner
31+
startPos int
32+
endPos int
33+
savedPos int
34+
hasLastTokenInfo bool
35+
lastTokenInfo tokenInfo
36+
lastScanAction scanAction
37+
leadingTrivia []TextRangeWithKind
38+
trailingTrivia []TextRangeWithKind
39+
wasNewLine bool
3940
}
4041

4142
func newFormattingScanner(text string, languageVariant core.LanguageVariant, startPos int, endPos int, worker *formatSpanWorker) []core.TextChange {
@@ -54,14 +55,14 @@ func newFormattingScanner(text string, languageVariant core.LanguageVariant, sta
5455

5556
res := worker.execute(fmtScn)
5657

57-
fmtScn.lastTokenInfo = nil
58+
fmtScn.hasLastTokenInfo = false
5859
scan.Reset()
5960

6061
return res
6162
}
6263

6364
func (s *formattingScanner) advance() {
64-
s.lastTokenInfo = nil
65+
s.hasLastTokenInfo = false
6566
isStarted := s.s.TokenFullStart() != s.startPos
6667

6768
if isStarted {
@@ -124,7 +125,7 @@ func (s *formattingScanner) shouldRescanJsxText(node *ast.Node) bool {
124125
if ast.IsJsxText(node) {
125126
return true
126127
}
127-
if !ast.IsJsxElement(node) || s.lastTokenInfo == nil {
128+
if !ast.IsJsxElement(node) || s.hasLastTokenInfo == false {
128129
return false
129130
}
130131

@@ -160,14 +161,14 @@ const (
160161
actionRescanJsxAttributeValue
161162
)
162163

163-
func fixTokenKind(tokenInfo *tokenInfo, container *ast.Node) *tokenInfo {
164+
func fixTokenKind(tokenInfo tokenInfo, container *ast.Node) tokenInfo {
164165
if ast.IsTokenKind(container.Kind) && tokenInfo.token.Kind != container.Kind {
165166
tokenInfo.token.Kind = container.Kind
166167
}
167168
return tokenInfo
168169
}
169170

170-
func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo {
171+
func (s *formattingScanner) readTokenInfo(n *ast.Node) tokenInfo {
171172
// Debug.assert(isOnToken()); // !!!
172173

173174
// normally scanner returns the smallest available token
@@ -190,14 +191,15 @@ func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo {
190191
expectedScanAction = actionScan
191192
}
192193

193-
if s.lastTokenInfo != nil && expectedScanAction == s.lastScanAction {
194+
if s.hasLastTokenInfo && expectedScanAction == s.lastScanAction {
194195
// readTokenInfo was called before with the same expected scan action.
195196
// No need to re-scan text, return existing 'lastTokenInfo'
196197
// it is ok to call fixTokenKind here since it does not affect
197198
// what portion of text is consumed. In contrast rescanning can change it,
198199
// i.e. for '>=' when originally scanner eats just one character
199200
// and rescanning forces it to consume more.
200-
return fixTokenKind(s.lastTokenInfo, n)
201+
s.lastTokenInfo = fixTokenKind(s.lastTokenInfo, n)
202+
return s.lastTokenInfo
201203
}
202204

203205
if s.s.TokenFullStart() != s.savedPos {
@@ -237,13 +239,15 @@ func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo {
237239
}
238240
}
239241

240-
s.lastTokenInfo = &tokenInfo{
242+
s.hasLastTokenInfo = true
243+
s.lastTokenInfo = tokenInfo{
241244
leadingTrivia: slices.Clone(s.leadingTrivia),
242245
token: token,
243246
trailingTrivia: slices.Clone(s.trailingTrivia),
244247
}
248+
s.lastTokenInfo = fixTokenKind(s.lastTokenInfo, n)
245249

246-
return fixTokenKind(s.lastTokenInfo, n)
250+
return s.lastTokenInfo
247251
}
248252

249253
func (s *formattingScanner) getNextToken(n *ast.Node, expectedScanAction scanAction) ast.Kind {
@@ -287,7 +291,7 @@ func (s *formattingScanner) getNextToken(n *ast.Node, expectedScanAction scanAct
287291
return token
288292
}
289293

290-
func (s *formattingScanner) readEOFTokenRange() *TextRangeWithKind {
294+
func (s *formattingScanner) readEOFTokenRange() TextRangeWithKind {
291295
// Debug.assert(isOnEOF()); // !!!
292296
return NewTextRangeWithKind(
293297
s.s.TokenFullStart(),
@@ -298,15 +302,15 @@ func (s *formattingScanner) readEOFTokenRange() *TextRangeWithKind {
298302

299303
func (s *formattingScanner) isOnToken() bool {
300304
current := s.s.Token()
301-
if s.lastTokenInfo != nil {
305+
if s.hasLastTokenInfo {
302306
current = s.lastTokenInfo.token.Kind
303307
}
304308
return current != ast.KindEndOfFile && !ast.IsTrivia(current)
305309
}
306310

307311
func (s *formattingScanner) isOnEOF() bool {
308312
current := s.s.Token()
309-
if s.lastTokenInfo != nil {
313+
if s.hasLastTokenInfo {
310314
current = s.lastTokenInfo.token.Kind
311315
}
312316
return current == ast.KindEndOfFile
@@ -316,7 +320,7 @@ func (s *formattingScanner) skipToEndOf(r *core.TextRange) {
316320
s.s.ResetTokenState(r.End())
317321
s.savedPos = s.s.TokenFullStart()
318322
s.lastScanAction = actionScan
319-
s.lastTokenInfo = nil
323+
s.hasLastTokenInfo = false
320324
s.wasNewLine = false
321325
s.leadingTrivia = nil
322326
s.trailingTrivia = nil
@@ -326,13 +330,13 @@ func (s *formattingScanner) skipToStartOf(r *core.TextRange) {
326330
s.s.ResetTokenState(r.Pos())
327331
s.savedPos = s.s.TokenFullStart()
328332
s.lastScanAction = actionScan
329-
s.lastTokenInfo = nil
333+
s.hasLastTokenInfo = false
330334
s.wasNewLine = false
331335
s.leadingTrivia = nil
332336
s.trailingTrivia = nil
333337
}
334338

335-
func (s *formattingScanner) getCurrentLeadingTrivia() []*TextRangeWithKind {
339+
func (s *formattingScanner) getCurrentLeadingTrivia() []TextRangeWithKind {
336340
return s.leadingTrivia
337341
}
338342

@@ -341,7 +345,7 @@ func (s *formattingScanner) lastTrailingTriviaWasNewLine() bool {
341345
}
342346

343347
func (s *formattingScanner) getTokenFullStart() int {
344-
if s.lastTokenInfo != nil {
348+
if s.hasLastTokenInfo {
345349
return s.lastTokenInfo.token.Loc.Pos()
346350
}
347351
return s.s.TokenFullStart()

0 commit comments

Comments
 (0)