62 lines
1.7 KiB
Go
62 lines
1.7 KiB
Go
package format
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/pgtidy/pkg/lexer"
|
|
)
|
|
|
|
// SemanticallyEqual reports whether a and b have the same non-trivia token
|
|
// stream, i.e. formatting may only ever change whitespace/comment trivia and
|
|
// layout — it must never add, remove, or alter a token of actual code.
|
|
// Unquoted identifiers/keywords compare case-insensitively (casing is a
|
|
// style choice); everything else (strings, numbers, operators, punctuation)
|
|
// must match exactly. Dollar-quoted body tokens are compared recursively so
|
|
// that independent body reformatting doesn't trigger a false failure.
|
|
//
|
|
// The CLI and LSP must call this before ever writing or emitting formatted
|
|
// output: if it returns false, the formatter has a bug and the original
|
|
// source must be kept, never the (corrupting) formatted output.
|
|
func SemanticallyEqual(a, b string) bool {
|
|
ta := significantTokens(a)
|
|
tb := significantTokens(b)
|
|
if len(ta) != len(tb) {
|
|
return false
|
|
}
|
|
for i := range ta {
|
|
if ta[i].Kind != tb[i].Kind {
|
|
return false
|
|
}
|
|
switch ta[i].Kind {
|
|
case lexer.Ident:
|
|
if !strings.EqualFold(ta[i].Text, tb[i].Text) {
|
|
return false
|
|
}
|
|
case lexer.DollarString:
|
|
_, innerA, _, okA := splitDollarQuote(ta[i].Text)
|
|
_, innerB, _, okB := splitDollarQuote(tb[i].Text)
|
|
if okA != okB || (okA && !SemanticallyEqual(innerA, innerB)) {
|
|
return false
|
|
}
|
|
default:
|
|
if ta[i].Text != tb[i].Text {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// significantTokens lexes src and returns its tokens excluding EOF and trivia
|
|
// (whitespace/comments).
|
|
func significantTokens(src string) []lexer.Token {
|
|
var out []lexer.Token
|
|
for _, t := range lexer.Lex(src) {
|
|
if t.Kind == lexer.EOF || t.IsTrivia() {
|
|
continue
|
|
}
|
|
out = append(out, t)
|
|
}
|
|
return out
|
|
}
|