Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tsc/internal/api/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -4376,7 +4376,7 @@ func (s *Session) handleFormatNodeForInsertion(ctx context.Context, params *Form

factory := ast.NewNodeFactory(ast.NodeFactoryHooks{})
text, nodeWithPos := printer.PrintAndPositionNode(factory, node, nil, newLine, formatOptions.IndentSize, nil)
syntheticFile := printer.CreateSyntheticSourceFile(factory, nodeWithPos, text, targetSourceFile.ParseOptions())
syntheticFile := printer.CreateSyntheticSourceFile(factory, text, targetSourceFile.ParseOptions())

isAtLineStart := format.GetLineStartPositionForPosition(pos, targetSourceFile) == pos
initialIndentation := format.GetIndentation(pos, targetSourceFile, formatOptions, isAtLineStart)
Expand Down
4 changes: 3 additions & 1 deletion tsc/internal/format/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ func FormatSpan(ctx context.Context, span core.TextRange, file *ast.SourceFile,
)
}

// FormatNodeGivenIndentation formats a printed fragment using node as its traversal
// root and file as its text context, including comments outside the node's range.
func FormatNodeGivenIndentation(ctx context.Context, node *ast.Node, file *ast.SourceFile, languageVariant core.LanguageVariant, initialIndentation int, delta int) []core.TextChange {
textRange := core.NewTextRange(node.Pos(), node.End())
textRange := core.NewTextRange(0, len(file.Text()))
return newFormattingScanner(
file.Text(),
languageVariant,
Expand Down
81 changes: 81 additions & 0 deletions tsc/internal/format/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package format_test
import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"

Expand All @@ -13,6 +14,7 @@ import (
"github.com/microsoft/TypeScript/tsc/internal/parser"
"github.com/microsoft/TypeScript/tsc/internal/printer"
"github.com/microsoft/TypeScript/tsc/internal/repo"
"github.com/microsoft/TypeScript/tsc/internal/testutil/parsetestutil"
"gotest.tools/v3/assert"
)

Expand Down Expand Up @@ -64,6 +66,85 @@ func TestFormat(t *testing.T) {
})
}

func TestFormatPrintedFragment(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
text string
kind ast.Kind
want string
semicolons lsutil.SemicolonPreference
}{
{"class member", "class C { method(x:number){return x;} }", ast.KindMethodDeclaration, "method(x: number) { return x; }", lsutil.SemicolonPreferenceIgnore},
{"case clause", "switch (x) { case 1: break; }", ast.KindCaseClause, "case 1: break;", lsutil.SemicolonPreferenceIgnore},
{"type", "type T = {x:number};", ast.KindTypeLiteral, "{\n x: number;\n}", lsutil.SemicolonPreferenceIgnore},
{"import specifier", `import {x as y} from "m";`, ast.KindImportSpecifier, "x as y", lsutil.SemicolonPreferenceIgnore},
{"trailing line comment", `import { x } from "m"; // comment`, ast.KindImportDeclaration, `import { x } from "m" // comment`, lsutil.SemicolonPreferenceRemove},
{"trailing block comment", `import { x } from "m"; /* comment */`, ast.KindImportDeclaration, `import { x } from "m" /* comment */`, lsutil.SemicolonPreferenceRemove},
{
"multiline else branches",
"function f() {\nif (a)\none();\nelse if (b)\ntwo();\nelse // comment\nthree();\n}",
ast.KindFunctionDeclaration,
"function f() {\n if (a) one();\n else if (b) two();\n else // comment\n three();\n}",
lsutil.SemicolonPreferenceIgnore,
},
{
"else block trivia",
"class C { method() {\nif (a) {\none();\n} else /* comment */ {\ntwo();\n}\n} }",
ast.KindMethodDeclaration,
"method() {\n if (a) {\n one();\n } else /* comment */ {\n two();\n }\n}",
lsutil.SemicolonPreferenceIgnore,
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
source := parsetestutil.ParseTypeScript(tt.text, false)
parsetestutil.CheckDiagnostics(t, source)
var fragment *ast.Node
var visit ast.Visitor
visit = func(node *ast.Node) bool {
if node.Kind == tt.kind {
fragment = node
return true
}
return node.ForEachChild(visit)
}
source.AsNode().ForEachChild(visit)
assert.Assert(t, fragment != nil)
factory := ast.NewNodeFactory(ast.NodeFactoryHooks{})
text, positioned := printer.PrintAndPositionNode(factory, fragment, source, "\n", 4, nil)
file := printer.CreateSyntheticSourceFile(factory, text, source.ParseOptions())
// A source-file context must remain a valid AST for ordinary consumers.
file.AsNode().ForEachChild(func(child *ast.Node) bool {
assert.Assert(t, ast.IsStatement(child) || child.Kind == ast.KindEndOfFile, "invalid source-file child: %v", child.Kind)
return false
})
options := lsutil.GetDefaultFormatCodeSettings()
options.Semicolons = tt.semicolons
ctx := format.WithFormatCodeSettings(t.Context(), options, "\n")
edits := format.FormatNodeGivenIndentation(ctx, positioned, file, core.LanguageVariantStandard, 0, 0)
assert.Equal(t, applyBulkEdits(text, edits), tt.want)
})
}
}

func BenchmarkFormatPrintedFragmentBranches(b *testing.B) {
for _, branches := range []int{16, 64, 256, 1024} {
b.Run(strconv.Itoa(branches), func(b *testing.B) {
source := parsetestutil.ParseTypeScript("function f() {\n"+strings.Repeat("if (x)\na();\nelse\nb();\n", branches)+"}", false)
assert.Equal(b, len(source.Diagnostics()), 0)
factory := ast.NewNodeFactory(ast.NodeFactoryHooks{})
text, positioned := printer.PrintAndPositionNode(factory, source.Statements.Nodes[0], source, "\n", 4, nil)
ctx := format.WithFormatCodeSettings(b.Context(), lsutil.GetDefaultFormatCodeSettings(), "\n")
b.ReportAllocs()
for b.Loop() {
file := printer.CreateSyntheticSourceFile(factory, text, source.ParseOptions())
format.FormatNodeGivenIndentation(ctx, positioned, file, core.LanguageVariantStandard, 0, 0)
}
})
}
}

func BenchmarkFormat(b *testing.B) {
ctx := format.WithFormatCodeSettings(b.Context(), lsutil.FormatCodeSettings{
TabSize: 4,
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/format/indent.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ func findFirstNonWhitespaceCharacterAndColumn(startPos int, endPos int, sourceFi

func childStartsOnTheSameLineWithElseInIfStatement(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool {
if parent.Kind == ast.KindIfStatement && parent.AsIfStatement().ElseStatement == child {
elseKeyword := astnav.FindPrecedingToken(sourceFile, child.Pos())
elseKeyword := astnav.FindPrecedingTokenEx(sourceFile, child.Pos(), parent, false /*excludeJSDoc*/)
debug.Assert(elseKeyword != nil)
elseKeywordStartLine := getStartLineForNode(elseKeyword, sourceFile)
return elseKeywordStartLine == childStartLine
Expand Down
14 changes: 4 additions & 10 deletions tsc/internal/ls/change/trackerimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ func leadingIndentation(text string) string {
}

func (t *Tracker) getFormattedTextOfNode(nodeIn *ast.Node, targetSourceFile *ast.SourceFile, sourceFile *ast.SourceFile, pos int, options NodeOptions) string {
text, sourceFileLike := t.getNonformattedText(nodeIn, targetSourceFile)
text, nodeOut := t.getNonformattedText(nodeIn, targetSourceFile)
formatFile := printer.CreateSyntheticSourceFile(t.NodeFactory, text, targetSourceFile.ParseOptions())
// !!! if (validate) validate(node, text);
formatOptions := GetFormatCodeSettingsForWriting(t.formatSettings, targetSourceFile)

Expand All @@ -246,7 +247,7 @@ func (t *Tracker) getFormattedTextOfNode(nodeIn *ast.Node, targetSourceFile *ast
delta = formatOptions.IndentSize
}

changes := format.FormatNodeGivenIndentation(t.ctx, sourceFileLike, sourceFileLike.AsSourceFile(), targetSourceFile.LanguageVariant, initialIndentation, delta)
changes := format.FormatNodeGivenIndentation(t.ctx, nodeOut, formatFile, targetSourceFile.LanguageVariant, initialIndentation, delta)
Comment thread
jakebailey marked this conversation as resolved.
Resolved
return core.ApplyBulkEdits(text, changes)
}

Expand All @@ -261,14 +262,7 @@ func GetFormatCodeSettingsForWriting(options lsutil.FormatCodeSettings, sourceFi
}

func (t *Tracker) getNonformattedText(node *ast.Node, sourceFile *ast.SourceFile) (string, *ast.Node) {
text, nodeOut := printer.PrintAndPositionNode(t.NodeFactory, node, sourceFile, t.newLine, t.formatSettings.IndentSize, t.EmitContext)
sourceFileLike := printer.CreateSyntheticSourceFile(
t.NodeFactory,
nodeOut,
text,
ast.SourceFileParseOptions{FileName: sourceFile.FileName(), PathKey: sourceFile.PathKey()},
)
return text, sourceFileLike.AsNode()
return printer.PrintAndPositionNode(t.NodeFactory, node, sourceFile, t.newLine, t.formatSettings.IndentSize, t.EmitContext)
}

// method on the changeTracker because use of converters
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/ls/completions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6826,7 +6826,7 @@ func (p *snippetPrinter) printAndFormatNode(ctx context.Context, node *ast.Node,
func (p *snippetPrinter) printAndFormatNodeWithSettings(ctx context.Context, node *ast.Node, sourceFile *ast.SourceFile, formatOptions lsutil.FormatCodeSettings) string {
text := p.printUnescapedNode(node)
nodeWithPos := p.baseWriter.AssignPositionsToNode(node, p.factory)
syntheticFile := printer.CreateSyntheticSourceFile(p.factory, nodeWithPos, text, sourceFile.ParseOptions())
syntheticFile := printer.CreateSyntheticSourceFile(p.factory, text, sourceFile.ParseOptions())
ctx = format.WithFormatCodeSettings(ctx, formatOptions, formatOptions.NewLineCharacter)
changes := format.FormatNodeGivenIndentation(
ctx,
Expand Down
2 changes: 1 addition & 1 deletion tsc/internal/printer/changetrackerwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func (ct *ChangeTrackerWriter) assignPositionsToNodeWorker(
}
newNode.ForEachChild(func(child *ast.Node) bool {
child.Parent = newNode
return true
return false
})
newNode.Loc = core.NewTextRange(ct.getPos(node), ct.getEnd(node))
return newNode
Expand Down
12 changes: 6 additions & 6 deletions tsc/internal/printer/syntheticfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ func PrintAndPositionNode(factory *ast.NodeFactory, node *ast.Node, sourceFile *
return text, positioned
}

// CreateSyntheticSourceFile wraps a positioned node in a synthetic source file
// suitable for use with the formatter. The node must already have valid source
// positions assigned (e.g. via PrintAndPositionNode or AssignPositionsToNode).
func CreateSyntheticSourceFile(factory *ast.NodeFactory, node *ast.Node, text string, parseOptions ast.SourceFileParseOptions) *ast.SourceFile {
// CreateSyntheticSourceFile provides text and line maps for formatting a printed
// fragment. The fragment is formatted separately: it need not be a statement and
// must not be added to the source file's statement list.
func CreateSyntheticSourceFile(factory *ast.NodeFactory, text string, parseOptions ast.SourceFileParseOptions) *ast.SourceFile {
eof := factory.NewToken(ast.KindEndOfFile)
eof.Loc = core.NewTextRange(len(text), len(text))
statements := factory.NewNodeList([]*ast.Node{node})
statements.Loc = core.NewTextRange(node.Pos(), node.End())
statements := factory.NewNodeList(nil)
statements.Loc = core.NewTextRange(0, 0)
syntheticFile := factory.NewSourceFile(
parseOptions,
text,
Expand Down
Loading