-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintent_tokenize.go
More file actions
175 lines (164 loc) · 5.1 KB
/
Copy pathintent_tokenize.go
File metadata and controls
175 lines (164 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package repomap
import (
"strings"
"unicode"
)
// intentStopwords are terms that appear in almost every Go file and add no
// discriminating signal to BM25 scoring.
var intentStopwords = map[string]bool{
// Go keywords
"func": true, "return": true, "error": true, "string": true,
"int": true, "bool": true, "nil": true, "var": true,
"const": true, "type": true, "struct": true, "interface": true,
"package": true, "import": true, "if": true, "else": true,
"for": true, "range": true, "switch": true, "case": true,
"defer": true, "go": true, "chan": true, "map": true,
"select": true, "break": true, "continue": true, "fallthrough": true,
// Common English stopwords
"the": true, "a": true, "an": true, "is": true, "are": true,
"was": true, "were": true, "be": true, "been": true, "being": true,
"have": true, "has": true, "had": true, "do": true, "does": true,
"did": true, "will": true, "would": true, "could": true, "should": true,
"may": true, "might": true, "shall": true, "can": true,
"this": true, "that": true, "these": true, "those": true,
"it": true, "its": true, "in": true, "on": true, "at": true,
"to": true, "of": true, "with": true, "from": true, "by": true,
"and": true, "or": true, "but": true, "so": true, "yet": true,
"as": true, "into": true, "about": true,
// Generic programming terms (noise in code search)
"file": true, "files": true, "code": true, "implement": true,
"add": true, "fix": true, "update": true, "change": true,
"make": true, "use": true, "using": true, "get": true, "set": true,
"bug": true, "issue": true, "handle": true, "support": true,
"refactor": true, "improve": true, "cleanup": true,
}
// tokenizeIntent splits text into lowercase tokens, splitting CamelCase identifiers
// and non-alphanumeric characters while preserving hyphens within words, then drops
// stopwords.
func tokenizeIntent(s string) []string {
var tokens []string
var cur strings.Builder
flush := func() {
if cur.Len() == 0 {
return
}
word := strings.Trim(cur.String(), "-")
cur.Reset()
if word == "" {
return
}
var words []string
if strings.ContainsRune(word, '-') {
words = []string{strings.ToLower(word)}
} else {
words = tokenizeCamelCase(word)
}
for _, tok := range words {
if tok != "" && !intentStopwords[tok] {
tokens = append(tokens, tok)
}
}
}
for _, r := range s {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' {
cur.WriteRune(r)
} else {
flush()
}
}
flush()
return tokens
}
// tokenizeCamelCase splits a CamelCase or snake_case identifier into component words.
// Examples:
//
// "ParseGoFile" → ["parse", "go", "file"]
// "http_client" → ["http", "client"]
// "ALLCAPS" → ["allcaps"]
func tokenizeCamelCase(s string) []string {
// First split on underscores and hyphens
parts := strings.FieldsFunc(s, func(r rune) bool {
return r == '_' || r == '-'
})
var tokens []string
for _, part := range parts {
tokens = append(tokens, splitCamel(part)...)
}
return tokens
}
// splitCamel splits a single CamelCase word into lowercase tokens.
func splitCamel(s string) []string {
if s == "" {
return nil
}
runes := []rune(s)
var tokens []string
start := 0
for i := 1; i < len(runes); i++ {
if unicode.IsUpper(runes[i]) && unicode.IsLower(runes[i-1]) {
tokens = append(tokens, strings.ToLower(string(runes[start:i])))
start = i
} else if i+1 < len(runes) && unicode.IsUpper(runes[i]) && unicode.IsLower(runes[i+1]) && unicode.IsUpper(runes[i-1]) {
tokens = append(tokens, strings.ToLower(string(runes[start:i])))
start = i
}
}
tokens = append(tokens, strings.ToLower(string(runes[start:])))
// Filter empty
out := tokens[:0]
for _, t := range tokens {
if t != "" {
out = append(out, t)
}
}
return out
}
// tokenizeSignatureFields extracts parameter and field names from a Symbol.Signature string.
// Handles formats like "(ctx context.Context, name string) error" or "{Name string, ID int}".
func tokenizeSignatureFields(sig string) []string {
// Strip outer parens/braces
sig = strings.TrimSpace(sig)
sig = strings.TrimPrefix(sig, "(")
sig = strings.TrimPrefix(sig, "{")
sig = strings.TrimSuffix(sig, ")")
sig = strings.TrimSuffix(sig, "}")
// Split on commas to get individual fields/params
var tokens []string
for _, field := range strings.Split(sig, ",") {
field = strings.TrimSpace(field)
if field == "" {
continue
}
// First token is the name (before the type)
parts := strings.Fields(field)
if len(parts) > 0 {
for _, t := range tokenizeCamelCase(parts[0]) {
if !intentStopwords[t] && len(t) > 1 {
tokens = append(tokens, t)
}
}
}
}
return tokens
}
// extractNegated splits tokens into those to keep and those that were
// preceded by a negation word ("not", "without", "except", "no").
func extractNegated(tokens []string) (keep, negated []string) {
negationWords := map[string]bool{
"not": true, "without": true, "except": true, "no": true,
}
negate := false
for _, t := range tokens {
if negationWords[t] {
negate = true
continue
}
if negate {
negated = append(negated, t)
negate = false
} else {
keep = append(keep, t)
}
}
return keep, negated
}