Skip to content

Commit

Permalink
Fix Multiple Sequential Comments and Comments in Empty Blocks
Browse files Browse the repository at this point in the history
Fix line comments in wrapped method arguments.

Fix line comments following block comments and vice versa.

Fix comments within empty blocks.
  • Loading branch information
cwarden committed Jan 8, 2025
1 parent 443971c commit 8e2a540
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 31 deletions.
151 changes: 135 additions & 16 deletions formatter/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (e *testErrorListener) SyntaxError(_ antlr.Recognizer, _ interface{}, line,

func TestStatement(t *testing.T) {
if testing.Verbose() {
log.SetLevel(log.DebugLevel)
log.SetLevel(log.TraceLevel)
}
tests :=
[]struct {
Expand Down Expand Up @@ -319,26 +319,63 @@ OneDayDischargeFollowUp.twoHoursDelay,
when Schema.Account a {
system.debug('doot');
}
}`,
},
{
`go(a, // line comment in args
b);`,
`go(a, // line comment in args
b);`,
},
{
`Integer i = new doot(go(a, b)).then();`,
`Integer i = new doot(go(a, b)).then();`,
},
{
`System.runAs(user) {
Fixtures.MegaOrder mo = new Fixtures.MegaOrderFactory()
.setOrderFac(Fixtures.order()
.put(Order__c.Shipping_Destination__c, Fixtures.shippingDestination()
.save().Id)
.put(Order__c.Subtotal__c, 100) // We need any value here
).usingShipCompliant(true)
.save();
}`,
`System.runAs(user) {
Fixtures.MegaOrder mo = new Fixtures.MegaOrderFactory()
.setOrderFac(Fixtures.order()
.put(Order__c.Shipping_Destination__c, Fixtures.shippingDestination()
.save().Id)
.put(Order__c.Subtotal__c, 100) // We need any value here
)
.usingShipCompliant(true)
.save();
}`,
},
}
for _, tt := range tests {
input := antlr.NewInputStream(tt.input)
lexer := parser.NewApexLexer(input)
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
dmp := diffmatchpatch.New()
for i, tt := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {

p := parser.NewApexParser(stream)
p.RemoveErrorListeners()
p.AddErrorListener(&testErrorListener{t: t})
input := antlr.NewInputStream(tt.input)
lexer := parser.NewApexLexer(input)
stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)

v := NewFormatVisitor(stream)
out, ok := v.visitRule(p.Statement()).(string)
if !ok {
t.Errorf("Unexpected result parsing apex")
}
if out != tt.output {
t.Errorf("unexpected format. expected:\n%q\ngot:\n%q\n", tt.output, out)
}
p := parser.NewApexParser(stream)
p.RemoveErrorListeners()
p.AddErrorListener(&testErrorListener{t: t})

v := NewFormatVisitor(stream)
out, ok := v.visitRule(p.Statement()).(string)
if !ok {
t.Errorf("Unexpected result parsing apex")
}
out = removeExtraCommentIndentation(out)
if out != tt.output {
diffs := dmp.DiffMain(tt.output, out, false)
t.Errorf("unexpected format. expected:\n%q\ngot:\n%q\ndiff:\n%s\n", tt.output, out, dmp.DiffPrettyText(diffs))
}
})
}

}
Expand Down Expand Up @@ -617,6 +654,88 @@ public class A {}`,
public String getSid() {
return this.getProperty(SID_PROPERTY);
}
}`,
},
{
`class TestClass {
List<String> vals = new List<String>{
/* MULTI
*/
// test comment 2
'val3'
};
}`,
`class TestClass {
List<String> vals = new List<String>{
/* MULTI
*/
// test comment 2
'val3' };
}`,
},
{
`class TestClass {
List<String> vals = new List<String>{
/* MULTI
*/
// test comment 1
// test comment 2
'val3'
};
}`,
`class TestClass {
List<String> vals = new List<String>{
/* MULTI
*/
// test comment 1
// test comment 2
'val3' };
}`,
},
{
`class TestClass {
public void go() {
// Line Comment
/* MULTI
*
*/
}
}`,
`class TestClass {
public void go() {
// Line Comment
/* MULTI
*
*/
}
}`,
},
{
`public class TestClass {
public void go() {
// Line Comment
/* MULTI
*
*/
return;
}
}`,
`public class TestClass {
public void go() {
// Line Comment
/* MULTI
*
*/
return;
}
}`,
},
}
Expand Down
4 changes: 4 additions & 0 deletions formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ func removeExtraCommentIndentation(input string) string {
input = strings.ReplaceAll(input, "\n\uFFFB\n", "\n\uFFFB")
log.Trace(fmt.Sprintf("ADJUSTED(4): %q", input))

doubleCapturedNewlines := regexp.MustCompile("\n(\ufffb\t*\ufffa\n)")
input = doubleCapturedNewlines.ReplaceAllString(input, "$1")
log.Trace(fmt.Sprintf("ADJUSTED(5): %q", input))

newlinePrefixedInlineComment := regexp.MustCompile("\n\t*\uFFF9\n")
input = newlinePrefixedInlineComment.ReplaceAllString(input, "\uFFF9\n")

Expand Down
13 changes: 12 additions & 1 deletion formatter/indent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestIndent(t *testing.T) {
},
{
"\ufffa\n// First Comment\n\n\ufffb\ufffa// Second Comment\n\ufffbgo();",
"\t\ufffa\n\t// First Comment\n\ufffb\n\t\ufffa// Second Comment\n\ufffb\n\tgo();",
"\t\ufffa\n\t// First Comment\n\n\ufffb\t\ufffa// Second Comment\n\ufffb\n\tgo();",
},
{
"\ufffa\n/*\n\t * Property getters\n\t **/\n\ufffb",
Expand Down Expand Up @@ -180,6 +180,7 @@ func TestSplitLeadingFFFAOrFFFBOrNewline(t *testing.T) {
"/**",
"\t\t */",
"",
"",
"\ufffb",
"}",
},
Expand Down Expand Up @@ -315,6 +316,16 @@ func TestSplitLeadingFFFAOrFFFBOrNewline(t *testing.T) {
"\ufffb",
},
},
{
name: "Preserve all newlines in comments",
input: "\t*/\n\n\ufffb",
expected: []string{
"\t*/",
"",
"",
"\ufffb",
},
},
}

for _, tc := range testCases {
Expand Down
71 changes: 57 additions & 14 deletions formatter/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ const (
PositionAfter
)

var manyNewlines = regexp.MustCompile(`\n{3,}`)

func NewFormatVisitor(tokens *antlr.CommonTokenStream) *FormatVisitor {
return &FormatVisitor{
tokens: tokens,
Expand Down Expand Up @@ -78,6 +76,13 @@ func (v *FormatVisitor) visitRule(node antlr.RuleNode) interface{} {
_ = afterHiddenTokens
result = appendHiddenTokens(v, result, afterHiddenTokens, PositionAfter)

if result.(string) == "{}" {
inbetweenTokens := interleaveHiddenTokens(
getHiddenTokensBetween(v.tokens, start, stop),
)
result = fmt.Sprintf("%s}", appendHiddenTokens(v, "{", inbetweenTokens, PositionAfter))
}

return result
}

Expand Down Expand Up @@ -274,15 +279,20 @@ func SplitLeadingFFFAOrFFFBOrNewline(data []byte, atEOF bool) (advance int, toke
delimiterLen := len(fffb)
// Split AFTER the delimiter
log.Trace(fmt.Sprintf("HAS \\uFFFB IN LINE: %q", string(line[:fffbIdx+delimiterLen])))
// Advance past the newline after \uFFFB
return fffbIdx + delimiterLen + 1, line[:fffbIdx+delimiterLen], nil
advance := 0
if bytes.IndexByte(line[:fffbIdx+delimiterLen], '\n') == 0 {
// Advance past the newline after \uFFFB
advance = 1
}
return fffbIdx + delimiterLen + advance, line[:fffbIdx+delimiterLen], nil
}

// ----------------------------------------------------------------
// 2c. No Delimiters => Return Entire Line
// ----------------------------------------------------------------
log.Trace(fmt.Sprintf("NO DELIMITER: %q", string(line)))
if len(line) > 0 && bytes.Index(data, fffb) == newlineIdx+1 {
var fffbFollowsNewlines = regexp.MustCompile(`(s?)^` + "\n+\uFFFB")
if len(line) > 0 && fffbFollowsNewlines.Match(data[newlineIdx:]) {
// \uFFFB follows newline. We want to keep the newline by returning an
// extra empty line so we don't advance over the newline.
return newlineIdx, line, nil
Expand All @@ -308,22 +318,21 @@ func indentTo(text string, indents int) string {
log.Debug(fmt.Sprintf("INDENTING: %q\n", text))

for scanner.Scan() {
log.Trace(fmt.Sprintf("INDENTING LINE: %q\n", scanner.Text()))
if scanner.Text() == "\uFFFB" {
// indentedText.WriteString("\n")
indentedText.WriteString(scanner.Text())
t := scanner.Text()
log.Trace(fmt.Sprintf("INDENTING LINE: %q\n", t))
if t == "\uFFFB" {
indentedText.WriteString(t)
continue
}
if isFirstLine {
isFirstLine = false
} else {
} else if !strings.HasPrefix(t, "\uFFFA") && !strings.HasPrefix(t, "\uFFF9") {
indentedText.WriteString("\n")
}
if scanner.Text() == "" {
indentedText.WriteString(scanner.Text())
continue
}
t := scanner.Text()
t = strings.Repeat("\t", indents) + t
indentedText.WriteString(t)
}
Expand Down Expand Up @@ -375,8 +384,10 @@ func appendHiddenTokens(v *FormatVisitor, result interface{}, tokens []antlr.Tok
trailingWhitespace := getTrailingWhitespace(text)
leading := ""
trailing := ""
if n := countNewlines(leadingWhitespace); n > 0 {
leading = strings.Repeat("\n", n)
if n := countNewlines(leadingWhitespace); n > 1 {
leading = strings.Repeat("\n", 2)
} else if countNewlines(leadingWhitespace) == 1 {
leading = "\n"
} else if len(leadingWhitespace) > 0 && position == PositionAfter {
leading = " "
}
Expand All @@ -396,7 +407,7 @@ func appendHiddenTokens(v *FormatVisitor, result interface{}, tokens []antlr.Tok
if containsNewline {
text = "\uFFFA" + text + "\uFFFB" + "\n"
} else if lineComment {
text = "\uFFF9" + text + "\uFFFB" + "\n"
text = "\uFFF9" + text + "\n\uFFFB"
} else {
text = "\uFFF9" + text + "\uFFFB"
}
Expand Down Expand Up @@ -434,6 +445,38 @@ func getStartStop(node antlr.RuleNode) (start, stop antlr.Token) {
return ctx.GetStart(), ctx.GetStop()
}

func getHiddenTokensBetween(tokens *antlr.CommonTokenStream, start, stop antlr.Token) ([]antlr.Token, []antlr.Token) {
if start == nil || stop == nil || len(tokens.GetAllTokens()) == 0 {
return nil, nil
}
after := tokens.GetHiddenTokensToRight(start.GetTokenIndex(), WHITESPACE_CHANNEL)
before := tokens.GetHiddenTokensToLeft(stop.GetTokenIndex(), WHITESPACE_CHANNEL)
inAfter := make(map[int]struct{})
for _, t := range after {
inAfter[t.GetTokenIndex()] = struct{}{}
}
whitespaceTokens := []antlr.Token{}
for _, t := range before {
if _, exists := inAfter[t.GetTokenIndex()]; exists {
whitespaceTokens = append(whitespaceTokens, t)
}
}

after = tokens.GetHiddenTokensToRight(start.GetTokenIndex(), COMMENTS_CHANNEL)
before = tokens.GetHiddenTokensToLeft(stop.GetTokenIndex(), COMMENTS_CHANNEL)
inAfter = make(map[int]struct{})
for _, t := range after {
inAfter[t.GetTokenIndex()] = struct{}{}
}
commentTokens := []antlr.Token{}
for _, t := range before {
if _, exists := inAfter[t.GetTokenIndex()]; exists {
commentTokens = append(commentTokens, t)
}
}
return whitespaceTokens, commentTokens
}

func getHiddenTokens(tokens *antlr.CommonTokenStream, token antlr.Token, direction HiddenTokenDirection) ([]antlr.Token, []antlr.Token) {
if token == nil || len(tokens.GetAllTokens()) == 0 {
return nil, nil
Expand Down

0 comments on commit 8e2a540

Please sign in to comment.