Skip to content

Commit 94fdc92

Browse files
authored
formatter: Fix escaping logic (#186)
1 parent ca0c0bc commit 94fdc92

File tree

2 files changed

+156
-45
lines changed

2 files changed

+156
-45
lines changed

bindings/go/scip/symbol_formatter.go

Lines changed: 106 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -63,63 +63,124 @@ func (f *SymbolFormatter) Format(symbol string) (string, error) {
6363
}
6464

6565
func (f *SymbolFormatter) FormatSymbol(symbol *Symbol) string {
66-
var parts []string
67-
if f.IncludeScheme(symbol.Scheme) { // Always include the scheme for local symbols
68-
parts = append(parts, symbol.Scheme)
66+
b := &strings.Builder{}
67+
if f.IncludeScheme(symbol.Scheme) {
68+
writeEscapedPackage(b, symbol.Scheme)
6969
}
70-
if symbol.Package != nil && symbol.Package.Manager != "" && f.IncludePackageManager(symbol.Package.Manager) {
71-
parts = append(parts, symbol.Package.Manager)
72-
}
73-
if symbol.Package != nil && symbol.Package.Name != "" && f.IncludePackageName(symbol.Package.Name) {
74-
parts = append(parts, symbol.Package.Name)
70+
if symbol.Package != nil {
71+
if f.IncludePackageManager(symbol.Package.Manager) {
72+
buffer(b)
73+
writeEscapedPackage(b, symbol.Package.Manager)
74+
}
75+
if f.IncludePackageName(symbol.Package.Name) {
76+
buffer(b)
77+
writeEscapedPackage(b, symbol.Package.Name)
78+
}
79+
if f.IncludePackageVersion(symbol.Package.Version) {
80+
buffer(b)
81+
writeEscapedPackage(b, symbol.Package.Version)
82+
}
7583
}
76-
if symbol.Package != nil && symbol.Package.Version != "" && f.IncludePackageVersion(symbol.Package.Version) {
77-
parts = append(parts, symbol.Package.Version)
84+
85+
if descriptorString := f.FormatDescriptors(symbol.Descriptors); f.IncludeDescriptor(descriptorString) {
86+
buffer(b)
87+
b.WriteString(descriptorString)
7888
}
79-
descriptor := strings.Builder{}
80-
for _, desc := range symbol.Descriptors {
81-
if !f.IncludeRawDescriptor(desc) {
89+
90+
return b.String()
91+
}
92+
93+
func (f *SymbolFormatter) FormatDescriptors(descriptors []*Descriptor) string {
94+
b := &strings.Builder{}
95+
for _, descriptor := range descriptors {
96+
if !f.IncludeRawDescriptor(descriptor) {
8297
continue
8398
}
84-
switch desc.Suffix {
99+
100+
switch descriptor.Suffix {
101+
case Descriptor_Local:
102+
b.WriteString(descriptor.Name)
85103
case Descriptor_Namespace:
86-
descriptor.WriteString(desc.Name)
87-
descriptor.WriteRune('/')
104+
writeSuffixedDescriptor(b, descriptor.Name, '/')
88105
case Descriptor_Type:
89-
descriptor.WriteString(desc.Name)
90-
descriptor.WriteRune('#')
106+
writeSuffixedDescriptor(b, descriptor.Name, '#')
91107
case Descriptor_Term:
92-
descriptor.WriteString(desc.Name)
93-
descriptor.WriteRune('.')
94-
case Descriptor_Method:
95-
descriptor.WriteString(desc.Name)
96-
descriptor.WriteRune('(')
97-
if f.IncludeDisambiguator(desc.Disambiguator) {
98-
descriptor.WriteString(desc.Disambiguator)
99-
}
100-
descriptor.WriteString(").")
101-
case Descriptor_TypeParameter:
102-
descriptor.WriteRune('[')
103-
descriptor.WriteString(desc.Name)
104-
descriptor.WriteRune(']')
105-
case Descriptor_Parameter:
106-
descriptor.WriteRune('(')
107-
descriptor.WriteString(desc.Name)
108-
descriptor.WriteRune(')')
108+
writeSuffixedDescriptor(b, descriptor.Name, '.')
109109
case Descriptor_Meta:
110-
descriptor.WriteString(desc.Name)
111-
descriptor.WriteRune(':')
110+
writeSuffixedDescriptor(b, descriptor.Name, ':')
112111
case Descriptor_Macro:
113-
descriptor.WriteString(desc.Name)
114-
descriptor.WriteRune('!')
115-
case Descriptor_Local:
116-
descriptor.WriteString(desc.Name)
112+
writeSuffixedDescriptor(b, descriptor.Name, '!')
113+
case Descriptor_TypeParameter:
114+
writeSandwichedDescriptor(b, '[', descriptor.Name, ']')
115+
case Descriptor_Parameter:
116+
writeSandwichedDescriptor(b, '(', descriptor.Name, ')')
117+
118+
case Descriptor_Method:
119+
if f.IncludeDisambiguator(descriptor.Disambiguator) {
120+
writeSuffixedDescriptor(b, descriptor.Name, '(')
121+
writeSuffixedDescriptor(b, descriptor.Disambiguator, ')', '.')
122+
} else {
123+
writeSuffixedDescriptor(b, descriptor.Name, '(', ')', '.')
124+
}
125+
}
126+
}
127+
128+
return b.String()
129+
}
130+
131+
func writeEscapedPackage(b *strings.Builder, name string) {
132+
if name == "" {
133+
name = "."
134+
}
135+
136+
writeGenericEscapedIdentifier(b, name, ' ')
137+
}
138+
139+
func writeSuffixedDescriptor(b *strings.Builder, identifier string, suffixes ...rune) {
140+
escape := false
141+
for _, ch := range identifier {
142+
if !isIdentifierCharacter(ch) {
143+
escape = true
144+
break
117145
}
118146
}
119-
descriptorString := descriptor.String()
120-
if f.IncludeDescriptor(descriptorString) {
121-
parts = append(parts, descriptorString)
147+
148+
if escape {
149+
b.WriteRune('`')
150+
writeGenericEscapedIdentifier(b, identifier, '`')
151+
b.WriteRune('`')
152+
} else {
153+
b.WriteString(identifier)
122154
}
123155

124-
return strings.Join(parts, " ")
156+
for _, suffix := range suffixes {
157+
b.WriteRune(suffix)
158+
}
159+
}
160+
161+
func writeSandwichedDescriptor(b *strings.Builder, prefix rune, identifier string, suffixes ...rune) {
162+
b.WriteRune(prefix)
163+
writeSuffixedDescriptor(b, identifier, suffixes...)
164+
}
165+
166+
func writeGenericEscapedIdentifier(b *strings.Builder, identifier string, escape rune) {
167+
for {
168+
idx := strings.IndexRune(identifier, escape)
169+
if idx < 0 {
170+
break
171+
}
172+
173+
b.WriteString(identifier[:idx])
174+
b.WriteRune(escape)
175+
b.WriteRune(escape)
176+
identifier = identifier[idx+1:]
177+
}
178+
179+
b.WriteString(identifier)
180+
}
181+
182+
func buffer(b *strings.Builder) {
183+
if b.Len() > 0 {
184+
b.WriteRune(' ')
185+
}
125186
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package scip
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestSymbolFormatter(t *testing.T) {
11+
expected := "zeroth escape first escape second escape third escape `github.com/foo/``bar/n2/n3/n4`/T#f()."
12+
symbol := &Symbol{
13+
Scheme: "zeroth escape",
14+
Package: &Package{
15+
Manager: "first escape",
16+
Name: "second escape",
17+
Version: "third escape",
18+
},
19+
Descriptors: []*Descriptor{
20+
{Name: "github.com/foo/`bar/n2/n3/n4", Suffix: Descriptor_Namespace},
21+
{Name: "T", Suffix: Descriptor_Type},
22+
{Name: "f", Suffix: Descriptor_Method},
23+
},
24+
}
25+
26+
if diff := cmp.Diff(expected, VerboseSymbolFormatter.FormatSymbol(symbol)); diff != "" {
27+
t.Fatalf("unexpected response (-want +got):\n%s", diff)
28+
}
29+
}
30+
31+
func TestSymbolFormatterRoundTrip(t *testing.T) {
32+
type test struct {
33+
Symbol string
34+
}
35+
tests := []test{
36+
{"lsif-java maven package 1.0.0 java/io/File#Entry.method(+1).(param)[TypeParam]"},
37+
{"rust-analyzer cargo std 1.0.0 macros/println!"},
38+
{"zeroth escape first escape second escape third escape `github.com/foo/``bar/n2/n3/n4`/T#f()."},
39+
}
40+
for _, test := range tests {
41+
t.Run(test.Symbol, func(t *testing.T) {
42+
formatted, err := VerboseSymbolFormatter.Format(test.Symbol)
43+
require.Nil(t, err)
44+
45+
if diff := cmp.Diff(test.Symbol, formatted); diff != "" {
46+
t.Fatalf("unexpected response (-want +got):\n%s", diff)
47+
}
48+
})
49+
}
50+
}

0 commit comments

Comments
 (0)