diff --git a/internal/execute/tsc.go b/internal/execute/tsc.go index bf694fd235..d16bbf13a4 100644 --- a/internal/execute/tsc.go +++ b/internal/execute/tsc.go @@ -13,13 +13,34 @@ import ( "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" + "github.com/microsoft/typescript-go/internal/format" + "github.com/microsoft/typescript-go/internal/parser" "github.com/microsoft/typescript-go/internal/pprof" + "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" ) type cbType = func(p any) any +func applyBulkEdits(text string, edits []core.TextChange) string { + b := strings.Builder{} + b.Grow(len(text)) + lastEnd := 0 + for _, e := range edits { + start := e.TextRange.Pos() + if start != lastEnd { + b.WriteString(text[lastEnd:e.TextRange.Pos()]) + } + b.WriteString(e.NewText) + + lastEnd = e.TextRange.End() + } + b.WriteString(text[lastEnd:]) + + return b.String() +} + func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus { if len(commandLineArgs) > 0 { // !!! build mode @@ -28,6 +49,8 @@ func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus { fmt.Fprint(sys.Writer(), "Build mode is currently unsupported."+sys.NewLine()) sys.EndWrite() return ExitStatusNotImplemented + // case "-f": + // return fmtMain(sys, commandLineArgs[1], commandLineArgs[1]) } } @@ -39,6 +62,38 @@ func CommandLine(sys System, cb cbType, commandLineArgs []string) ExitStatus { return start(watcher) } +func fmtMain(sys System, input, output string) ExitStatus { + ctx := format.WithFormatCodeSettings(context.Background(), format.GetDefaultFormatCodeSettings(sys.NewLine()), sys.NewLine()) + input = string(tspath.ToPath(input, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames())) + output = string(tspath.ToPath(output, sys.GetCurrentDirectory(), sys.FS().UseCaseSensitiveFileNames())) + fileContent, ok := sys.FS().ReadFile(input) + if !ok { + fmt.Fprint(sys.Writer(), "File not found: "+input+sys.NewLine()) + return ExitStatusNotImplemented + } + text := fileContent + pathified := tspath.ToPath(input, sys.GetCurrentDirectory(), true) + sourceFile := parser.ParseSourceFile( + string(pathified), + pathified, + text, + &core.SourceFileAffectingCompilerOptions{ + EmitScriptTarget: core.ScriptTargetLatest, + }, + nil, + scanner.JSDocParsingModeParseAll, + ) + ast.SetParentInChildren(sourceFile.AsNode()) + edits := format.FormatDocument(ctx, sourceFile) + newText := applyBulkEdits(text, edits) + + if err := sys.FS().WriteFile(output, newText, false); err != nil { + fmt.Fprint(sys.Writer(), err.Error()+sys.NewLine()) + return ExitStatusNotImplemented + } + return ExitStatusSuccess +} + func executeCommandLineWorker(sys System, cb cbType, commandLine *tsoptions.ParsedCommandLine) (ExitStatus, *watcher) { configFileName := "" reportDiagnostic := createDiagnosticReporter(sys, commandLine.CompilerOptions()) diff --git a/internal/format/context.go b/internal/format/context.go index aeb864addd..aae5b935bf 100644 --- a/internal/format/context.go +++ b/internal/format/context.go @@ -86,8 +86,8 @@ func GetDefaultFormatCodeSettings(newLineCharacter string) *FormatCodeSettings { } type formattingContext struct { - currentTokenSpan *TextRangeWithKind - nextTokenSpan *TextRangeWithKind + currentTokenSpan TextRangeWithKind + nextTokenSpan TextRangeWithKind contextNode *ast.Node currentTokenParent *ast.Node nextTokenParent *ast.Node @@ -117,16 +117,10 @@ func NewFormattingContext(file *ast.SourceFile, kind FormatRequestKind, options return res } -func (this *formattingContext) UpdateContext(cur *TextRangeWithKind, curParent *ast.Node, next *TextRangeWithKind, nextParent *ast.Node, commonParent *ast.Node) { - if cur == nil { - panic("nil current range in update context") - } +func (this *formattingContext) UpdateContext(cur TextRangeWithKind, curParent *ast.Node, next TextRangeWithKind, nextParent *ast.Node, commonParent *ast.Node) { if curParent == nil { panic("nil current range node parent in update context") } - if next == nil { - panic("nil next range in update context") - } if nextParent == nil { panic("nil next range node parent in update context") } diff --git a/internal/format/indent.go b/internal/format/indent.go index 8763c46eb6..f7eab25fea 100644 --- a/internal/format/indent.go +++ b/internal/format/indent.go @@ -152,7 +152,11 @@ func getActualIndentationForListItem(node *ast.Node, sourceFile *ast.SourceFile, if listIndentsChild { delta = options.IndentSize } - return getActualIndentationForListStartLine(containingList, sourceFile, options) + delta + res := getActualIndentationForListStartLine(containingList, sourceFile, options) + if res == -1 { + return delta + } + return res + delta } return -1 } diff --git a/internal/format/rulecontext.go b/internal/format/rulecontext.go index 365d70db1c..0e57e24d38 100644 --- a/internal/format/rulecontext.go +++ b/internal/format/rulecontext.go @@ -15,11 +15,11 @@ import ( /// type ( - optionSelector = func(options *FormatCodeSettings) core.Tristate - anyOptionSelector = func(options *FormatCodeSettings) any + optionSelector = func(options *FormatCodeSettings) core.Tristate + anyOptionSelector[T comparable] = func(options *FormatCodeSettings) T ) -func semicolonOption(options *FormatCodeSettings) any { return options.Semicolons } +func semicolonOption(options *FormatCodeSettings) SemicolonPreference { return options.Semicolons } func insertSpaceAfterCommaDelimiterOption(options *FormatCodeSettings) core.Tristate { return options.InsertSpaceAfterCommaDelimiter } @@ -96,7 +96,7 @@ func indentSwitchCaseOption(options *FormatCodeSettings) core.Tristate { return options.IndentSwitchCase } -func optionEquals(optionName anyOptionSelector, optionValue any) contextPredicate { +func optionEquals[T comparable](optionName anyOptionSelector[T], optionValue T) contextPredicate { return func(context *formattingContext) bool { if context.Options == nil { return false @@ -494,7 +494,7 @@ func isConstructorSignatureContext(context *formattingContext) bool { return context.contextNode.Kind == ast.KindConstructSignature } -func isTypeArgumentOrParameterOrAssertion(token *TextRangeWithKind, parent *ast.Node) bool { +func isTypeArgumentOrParameterOrAssertion(token TextRangeWithKind, parent *ast.Node) bool { if token.Kind != ast.KindLessThanToken && token.Kind != ast.KindGreaterThanToken { return false } diff --git a/internal/format/rulesmap.go b/internal/format/rulesmap.go index e88810387b..5c482821a0 100644 --- a/internal/format/rulesmap.go +++ b/internal/format/rulesmap.go @@ -7,10 +7,9 @@ import ( "github.com/microsoft/typescript-go/internal/ast" ) -func getRules(context *formattingContext) []*ruleImpl { +func getRules(context *formattingContext, rules []*ruleImpl) []*ruleImpl { bucket := getRulesMap()[getRuleBucketIndex(context.currentTokenSpan.Kind, context.nextTokenSpan.Kind)] if len(bucket) > 0 { - var rules []*ruleImpl ruleActionMask := ruleActionNone outer: for _, rule := range bucket { @@ -28,7 +27,7 @@ func getRules(context *formattingContext) []*ruleImpl { } return rules } - return nil + return rules } func getRuleBucketIndex(row ast.Kind, column ast.Kind) int { diff --git a/internal/format/scanner.go b/internal/format/scanner.go index 0658fb3646..181b823588 100644 --- a/internal/format/scanner.go +++ b/internal/format/scanner.go @@ -13,29 +13,30 @@ type TextRangeWithKind struct { Kind ast.Kind } -func NewTextRangeWithKind(pos int, end int, kind ast.Kind) *TextRangeWithKind { - return &TextRangeWithKind{ +func NewTextRangeWithKind(pos int, end int, kind ast.Kind) TextRangeWithKind { + return TextRangeWithKind{ Loc: core.NewTextRange(pos, end), Kind: kind, } } type tokenInfo struct { - leadingTrivia []*TextRangeWithKind - token *TextRangeWithKind - trailingTrivia []*TextRangeWithKind + leadingTrivia []TextRangeWithKind + token TextRangeWithKind + trailingTrivia []TextRangeWithKind } type formattingScanner struct { - s *scanner.Scanner - startPos int - endPos int - savedPos int - lastTokenInfo *tokenInfo - lastScanAction scanAction - leadingTrivia []*TextRangeWithKind - trailingTrivia []*TextRangeWithKind - wasNewLine bool + s *scanner.Scanner + startPos int + endPos int + savedPos int + hasLastTokenInfo bool + lastTokenInfo tokenInfo + lastScanAction scanAction + leadingTrivia []TextRangeWithKind + trailingTrivia []TextRangeWithKind + wasNewLine bool } 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 res := worker.execute(fmtScn) - fmtScn.lastTokenInfo = nil + fmtScn.hasLastTokenInfo = false scan.Reset() return res } func (s *formattingScanner) advance() { - s.lastTokenInfo = nil + s.hasLastTokenInfo = false isStarted := s.s.TokenFullStart() != s.startPos if isStarted { @@ -124,7 +125,7 @@ func (s *formattingScanner) shouldRescanJsxText(node *ast.Node) bool { if ast.IsJsxText(node) { return true } - if !ast.IsJsxElement(node) || s.lastTokenInfo == nil { + if !ast.IsJsxElement(node) || s.hasLastTokenInfo == false { return false } @@ -160,14 +161,14 @@ const ( actionRescanJsxAttributeValue ) -func fixTokenKind(tokenInfo *tokenInfo, container *ast.Node) *tokenInfo { +func fixTokenKind(tokenInfo tokenInfo, container *ast.Node) tokenInfo { if ast.IsTokenKind(container.Kind) && tokenInfo.token.Kind != container.Kind { tokenInfo.token.Kind = container.Kind } return tokenInfo } -func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo { +func (s *formattingScanner) readTokenInfo(n *ast.Node) tokenInfo { // Debug.assert(isOnToken()); // !!! // normally scanner returns the smallest available token @@ -190,14 +191,15 @@ func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo { expectedScanAction = actionScan } - if s.lastTokenInfo != nil && expectedScanAction == s.lastScanAction { + if s.hasLastTokenInfo && expectedScanAction == s.lastScanAction { // readTokenInfo was called before with the same expected scan action. // No need to re-scan text, return existing 'lastTokenInfo' // it is ok to call fixTokenKind here since it does not affect // what portion of text is consumed. In contrast rescanning can change it, // i.e. for '>=' when originally scanner eats just one character // and rescanning forces it to consume more. - return fixTokenKind(s.lastTokenInfo, n) + s.lastTokenInfo = fixTokenKind(s.lastTokenInfo, n) + return s.lastTokenInfo } if s.s.TokenFullStart() != s.savedPos { @@ -237,13 +239,15 @@ func (s *formattingScanner) readTokenInfo(n *ast.Node) *tokenInfo { } } - s.lastTokenInfo = &tokenInfo{ + s.hasLastTokenInfo = true + s.lastTokenInfo = tokenInfo{ leadingTrivia: slices.Clone(s.leadingTrivia), token: token, trailingTrivia: slices.Clone(s.trailingTrivia), } + s.lastTokenInfo = fixTokenKind(s.lastTokenInfo, n) - return fixTokenKind(s.lastTokenInfo, n) + return s.lastTokenInfo } func (s *formattingScanner) getNextToken(n *ast.Node, expectedScanAction scanAction) ast.Kind { @@ -287,7 +291,7 @@ func (s *formattingScanner) getNextToken(n *ast.Node, expectedScanAction scanAct return token } -func (s *formattingScanner) readEOFTokenRange() *TextRangeWithKind { +func (s *formattingScanner) readEOFTokenRange() TextRangeWithKind { // Debug.assert(isOnEOF()); // !!! return NewTextRangeWithKind( s.s.TokenFullStart(), @@ -298,7 +302,7 @@ func (s *formattingScanner) readEOFTokenRange() *TextRangeWithKind { func (s *formattingScanner) isOnToken() bool { current := s.s.Token() - if s.lastTokenInfo != nil { + if s.hasLastTokenInfo { current = s.lastTokenInfo.token.Kind } return current != ast.KindEndOfFile && !ast.IsTrivia(current) @@ -306,7 +310,7 @@ func (s *formattingScanner) isOnToken() bool { func (s *formattingScanner) isOnEOF() bool { current := s.s.Token() - if s.lastTokenInfo != nil { + if s.hasLastTokenInfo { current = s.lastTokenInfo.token.Kind } return current == ast.KindEndOfFile @@ -316,7 +320,7 @@ func (s *formattingScanner) skipToEndOf(r *core.TextRange) { s.s.ResetTokenState(r.End()) s.savedPos = s.s.TokenFullStart() s.lastScanAction = actionScan - s.lastTokenInfo = nil + s.hasLastTokenInfo = false s.wasNewLine = false s.leadingTrivia = nil s.trailingTrivia = nil @@ -326,13 +330,13 @@ func (s *formattingScanner) skipToStartOf(r *core.TextRange) { s.s.ResetTokenState(r.Pos()) s.savedPos = s.s.TokenFullStart() s.lastScanAction = actionScan - s.lastTokenInfo = nil + s.hasLastTokenInfo = false s.wasNewLine = false s.leadingTrivia = nil s.trailingTrivia = nil } -func (s *formattingScanner) getCurrentLeadingTrivia() []*TextRangeWithKind { +func (s *formattingScanner) getCurrentLeadingTrivia() []TextRangeWithKind { return s.leadingTrivia } @@ -341,7 +345,7 @@ func (s *formattingScanner) lastTrailingTriviaWasNewLine() bool { } func (s *formattingScanner) getTokenFullStart() int { - if s.lastTokenInfo != nil { + if s.hasLastTokenInfo { return s.lastTokenInfo.token.Loc.Pos() } return s.s.TokenFullStart() diff --git a/internal/format/span.go b/internal/format/span.go index c3a90590fe..16695cb19c 100644 --- a/internal/format/span.go +++ b/internal/format/span.go @@ -161,7 +161,7 @@ type formatSpanWorker struct { formattingContext *formattingContext edits []core.TextChange - previousRange *TextRangeWithKind + previousRange TextRangeWithKind previousRangeTriviaEnd int previousParent *ast.Node previousRangeStartLine int @@ -175,6 +175,8 @@ type formatSpanWorker struct { visitingIndenter *dynamicIndenter visitingNodeStartLine int visitingUndecoratedNodeStartLine int + + currentRules []*ruleImpl } func newFormatSpanWorker( @@ -196,6 +198,7 @@ func newFormatSpanWorker( requestKind: requestKind, rangeContainsError: rangeContainsError, sourceFile: sourceFile, + currentRules: make([]*ruleImpl, 0, 32), // increaseInsertionIndex should assert there are no more than 32 rules in a given bucket } } @@ -257,7 +260,7 @@ func (w *formatSpanWorker) execute(s *formattingScanner) []core.TextChange { indentation += opt.IndentSize // !!! TODO: nil check??? } - w.indentTriviaItems(remainingTrivia, indentation, true, func(item *TextRangeWithKind) { + w.indentTriviaItems(remainingTrivia, indentation, true, func(item TextRangeWithKind) { startLine, startChar := scanner.GetLineAndCharacterOfPosition(w.sourceFile, item.Loc.Pos()) w.processRange(item, startLine, startChar, w.enclosingNode, w.enclosingNode, nil) w.insertIndentation(item.Loc.Pos(), indentation, false) @@ -268,7 +271,7 @@ func (w *formatSpanWorker) execute(s *formattingScanner) []core.TextChange { } } - if w.previousRange != nil && w.formattingScanner.getTokenFullStart() >= w.originalRange.End() { + if w.previousRange != NewTextRangeWithKind(0, 0, 0) && w.formattingScanner.getTokenFullStart() >= w.originalRange.End() { // Formatting edits happen by looking at pairs of contiguous tokens (see `processPair`), // typically inserting or deleting whitespace between them. The recursive `processNode` // logic above bails out as soon as it encounters a token that is beyond the end of the @@ -279,14 +282,14 @@ func (w *formatSpanWorker) execute(s *formattingScanner) []core.TextChange { // inclusive. We would expect a format-selection would delete the space (if rules apply), // but in order to do that, we need to process the pair ["{", "}"], but we stopped processing // just before getting there. This block handles this trailing edit. - var tokenInfo *TextRangeWithKind + var tokenInfo TextRangeWithKind if w.formattingScanner.isOnEOF() { tokenInfo = w.formattingScanner.readEOFTokenRange() } else if w.formattingScanner.isOnToken() { tokenInfo = w.formattingScanner.readTokenInfo(w.enclosingNode).token } - if tokenInfo != nil && tokenInfo.Loc.Pos() == w.previousRangeTriviaEnd { + if tokenInfo.Loc.Pos() == w.previousRangeTriviaEnd { // We need to check that tokenInfo and previousRange are contiguous: the `originalRange` // may have ended in the middle of a token, which means we will have stopped formatting // on that token, leaving `previousRange` pointing to the token before it, but already @@ -491,7 +494,7 @@ func (w *formatSpanWorker) processChildNodes( if w.formattingScanner.isOnToken() { tokenInfo = w.formattingScanner.readTokenInfo(parent) } else { - tokenInfo = nil + return } } @@ -499,7 +502,7 @@ func (w *formatSpanWorker) processChildNodes( // there might be the case when current token matches end token but does not considered as one // function (x: function) <-- // without this check close paren will be interpreted as list end token for function expression which is wrong - if tokenInfo != nil && tokenInfo.token.Kind == listEndToken && tokenInfo.token.Loc.ContainedBy(parent.Loc) { + if tokenInfo.token.Kind == listEndToken && tokenInfo.token.Loc.ContainedBy(parent.Loc) { // consume list end token w.consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent /*isListEndToken*/, true) } @@ -548,7 +551,11 @@ func (w *formatSpanWorker) computeIndentation(node *ast.Node, startLine int, inh argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, w.sourceFile) { return parentDynamicIndentation.getIndentation(), delta } else { - return parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta + i := parentDynamicIndentation.getIndentation() + if i == -1 { + return parentDynamicIndentation.getIndentation(), delta + } + return i + parentDynamicIndentation.getDelta(node), delta } } @@ -620,19 +627,20 @@ func (w *formatSpanWorker) processNode(node *ast.Node, contextNode *ast.Node, no } } -func (w *formatSpanWorker) processPair(currentItem *TextRangeWithKind, currentStartLine int, currentParent *ast.Node, previousItem *TextRangeWithKind, previousStartLine int, previousParent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) LineAction { +func (w *formatSpanWorker) processPair(currentItem TextRangeWithKind, currentStartLine int, currentParent *ast.Node, previousItem TextRangeWithKind, previousStartLine int, previousParent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) LineAction { w.formattingContext.UpdateContext(previousItem, previousParent, currentItem, currentParent, contextNode) - rules := getRules(w.formattingContext) + w.currentRules = w.currentRules[:0] + w.currentRules = getRules(w.formattingContext, w.currentRules) trimTrailingWhitespaces := w.formattingContext.Options.TrimTrailingWhitespace != false lineAction := LineActionNone - if len(rules) > 0 { + if len(w.currentRules) > 0 { // Apply rules in reverse order so that higher priority rules (which are first in the array) // win in a conflict with lower priority rules. - for i := len(rules) - 1; i >= 0; i-- { - rule := rules[i] + for i := len(w.currentRules) - 1; i >= 0; i-- { + rule := w.currentRules[i] lineAction = w.applyRuleEdits(rule, previousItem, previousStartLine, currentItem, currentStartLine) if dynamicIndentation != nil { switch lineAction { @@ -669,7 +677,7 @@ func (w *formatSpanWorker) processPair(currentItem *TextRangeWithKind, currentSt return lineAction } -func (w *formatSpanWorker) applyRuleEdits(rule *ruleImpl, previousRange *TextRangeWithKind, previousStartLine int, currentRange *TextRangeWithKind, currentStartLine int) LineAction { +func (w *formatSpanWorker) applyRuleEdits(rule *ruleImpl, previousRange TextRangeWithKind, previousStartLine int, currentRange TextRangeWithKind, currentStartLine int) LineAction { onLaterLine := currentStartLine != previousStartLine switch rule.Action() { case ruleActionStopProcessingSpaceActions: @@ -731,14 +739,14 @@ const ( LineActionLineRemoved ) -func (w *formatSpanWorker) processRange(r *TextRangeWithKind, rangeStartLine int, rangeStartCharacter int, parent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) LineAction { +func (w *formatSpanWorker) processRange(r TextRangeWithKind, rangeStartLine int, rangeStartCharacter int, parent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) LineAction { rangeHasError := w.rangeContainsError(r.Loc) lineAction := LineActionNone if !rangeHasError { - if w.previousRange == nil { + if w.previousRange == NewTextRangeWithKind(0, 0, 0) { // trim whitespaces starting from the beginning of the span up to the current line originalStartLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, w.originalRange.Pos()) - w.trimTrailingWhitespacesForLines(originalStartLine, rangeStartLine, nil) + w.trimTrailingWhitespacesForLines(originalStartLine, rangeStartLine, NewTextRangeWithKind(0, 0, 0)) } else { lineAction = w.processPair(r, rangeStartLine, parent, w.previousRange, w.previousRangeStartLine, w.previousParent, contextNode, dynamicIndentation) } @@ -752,7 +760,7 @@ func (w *formatSpanWorker) processRange(r *TextRangeWithKind, rangeStartLine int return lineAction } -func (w *formatSpanWorker) processTrivia(trivia []*TextRangeWithKind, parent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) { +func (w *formatSpanWorker) processTrivia(trivia []TextRangeWithKind, parent *ast.Node, contextNode *ast.Node, dynamicIndentation *dynamicIndenter) { for _, triviaItem := range trivia { if isComment(triviaItem.Kind) && triviaItem.Loc.ContainedBy(w.originalRange) { triviaItemStartLine, triviaItemStartCharacter := scanner.GetLineAndCharacterOfPosition(w.sourceFile, triviaItem.Loc.Pos()) @@ -765,9 +773,9 @@ func (w *formatSpanWorker) processTrivia(trivia []*TextRangeWithKind, parent *as * Trimming will be done for lines after the previous range. * Exclude comments as they had been previously processed. */ -func (w *formatSpanWorker) trimTrailingWhitespacesForRemainingRange(trivias []*TextRangeWithKind) { +func (w *formatSpanWorker) trimTrailingWhitespacesForRemainingRange(trivias []TextRangeWithKind) { startPos := w.originalRange.Pos() - if w.previousRange != nil { + if w.previousRange != NewTextRangeWithKind(0, 0, 0) { startPos = w.previousRange.Loc.End() } @@ -786,21 +794,21 @@ func (w *formatSpanWorker) trimTrailingWhitespacesForRemainingRange(trivias []*T } } -func (w *formatSpanWorker) trimTrailingWitespacesForPositions(startPos int, endPos int, previousRange *TextRangeWithKind) { +func (w *formatSpanWorker) trimTrailingWitespacesForPositions(startPos int, endPos int, previousRange TextRangeWithKind) { startLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, startPos) endLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, endPos) w.trimTrailingWhitespacesForLines(startLine, endLine+1, previousRange) } -func (w *formatSpanWorker) trimTrailingWhitespacesForLines(line1 int, line2 int, r *TextRangeWithKind) { +func (w *formatSpanWorker) trimTrailingWhitespacesForLines(line1 int, line2 int, r TextRangeWithKind) { lineStarts := scanner.GetLineStarts(w.sourceFile) for line := line1; line < line2; line++ { lineStartPosition := int(lineStarts[line]) lineEndPosition := scanner.GetEndLinePosition(w.sourceFile, line) // do not trim whitespaces in comments or template expression - if r != nil && (isComment(r.Kind) || isStringOrRegularExpressionOrTemplateLiteral(r.Kind)) && r.Loc.Pos() <= lineEndPosition && r.Loc.End() > lineEndPosition { + if r != NewTextRangeWithKind(0, 0, 0) && (isComment(r.Kind) || isStringOrRegularExpressionOrTemplateLiteral(r.Kind)) && r.Loc.Pos() <= lineEndPosition && r.Loc.End() > lineEndPosition { continue } @@ -875,7 +883,7 @@ func (w *formatSpanWorker) indentationIsDifferent(indentationString string, star return indentationString != w.sourceFile.Text()[startLinePosition:startLinePosition+len(indentationString)] } -func (w *formatSpanWorker) indentTriviaItems(trivia []*TextRangeWithKind, commentIndentation int, indentNextTokenOrTrivia bool, indentSingleLine func(item *TextRangeWithKind)) bool { +func (w *formatSpanWorker) indentTriviaItems(trivia []TextRangeWithKind, commentIndentation int, indentNextTokenOrTrivia bool, indentSingleLine func(item TextRangeWithKind)) bool { for _, triviaItem := range trivia { triviaInRange := triviaItem.Loc.ContainedBy(w.originalRange) switch triviaItem.Kind { @@ -998,7 +1006,7 @@ func (w *formatSpanWorker) recordInsert(start int, text string) { } } -func (w *formatSpanWorker) consumeTokenAndAdvanceScanner(currentTokenInfo *tokenInfo, parent *ast.Node, dynamicIndenation *dynamicIndenter, container *ast.Node, isListEndToken bool) { +func (w *formatSpanWorker) consumeTokenAndAdvanceScanner(currentTokenInfo tokenInfo, parent *ast.Node, dynamicIndenation *dynamicIndenter, container *ast.Node, isListEndToken bool) { // assert(currentTokenInfo.token.Loc.ContainedBy(parent.Loc)) // !!! lastTriviaWasNewLine := w.formattingScanner.lastTrailingTriviaWasNewLine() indentToken := false @@ -1021,7 +1029,7 @@ func (w *formatSpanWorker) consumeTokenAndAdvanceScanner(currentTokenInfo *token if !rangeHasError { if lineAction == LineActionNone { // indent token only if end line of previous range does not match start line of the token - if savePreviousRange != nil { + if savePreviousRange != NewTextRangeWithKind(0, 0, 0) { prevEndLine, _ := scanner.GetLineAndCharacterOfPosition(w.sourceFile, savePreviousRange.Loc.End()) indentToken = lastTriviaWasNewLine && tokenStartLine != prevEndLine } @@ -1044,7 +1052,7 @@ func (w *formatSpanWorker) consumeTokenAndAdvanceScanner(currentTokenInfo *token indentNextTokenOrTrivia := true if len(currentTokenInfo.leadingTrivia) > 0 { commentIndentation := dynamicIndenation.getIndentationForComment(currentTokenInfo.token.Kind, tokenIndentation, container) - indentNextTokenOrTrivia = w.indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, func(item *TextRangeWithKind) { + indentNextTokenOrTrivia = w.indentTriviaItems(currentTokenInfo.leadingTrivia, commentIndentation, indentNextTokenOrTrivia, func(item TextRangeWithKind) { w.insertIndentation(item.Loc.Pos(), commentIndentation, false) }) }