|
7 | 7 | * 3. Match types: |
8 | 8 | * - Namespace queries (::) and method queries (# or .) match against full_name |
9 | 9 | * - Regular queries match against unqualified name |
10 | | - * - Prefix match (1000) > substring match (100) > fuzzy match (10) |
| 10 | + * - Prefix (10000) > substring (5000) > fuzzy (1000) |
11 | 11 | * 4. First character determines type priority: |
12 | 12 | * - Starts with lowercase: methods first |
13 | 13 | * - Starts with uppercase: classes/modules/constants first |
|
22 | 22 | var MAX_RESULTS = 30; |
23 | 23 | var MIN_QUERY_LENGTH = 1; |
24 | 24 |
|
| 25 | +/* |
| 26 | + * Scoring constants - organized in tiers where each tier dominates lower tiers. |
| 27 | + * This ensures match type always beats type priority, etc. |
| 28 | + * |
| 29 | + * Tier 0: Exact matches (immediate return) |
| 30 | + * Tier 1: Match type (prefix > substring > fuzzy) |
| 31 | + * Tier 2: Exact name bonus |
| 32 | + * Tier 3: Type priority (method vs class based on query case) |
| 33 | + * Tier 4: Minor bonuses (top-level, class method, name length) |
| 34 | + */ |
| 35 | +var SCORE_EXACT_FULL_NAME = 1000000; // Tier 0: Query exactly matches full_name |
| 36 | +var SCORE_MATCH_PREFIX = 10000; // Tier 1: Query is prefix of name |
| 37 | +var SCORE_MATCH_SUBSTRING = 5000; // Tier 1: Query is substring of name |
| 38 | +var SCORE_MATCH_FUZZY = 1000; // Tier 1: Query chars appear in order |
| 39 | +var SCORE_EXACT_NAME = 500; // Tier 2: Name exactly equals query |
| 40 | +var SCORE_TYPE_PRIORITY = 100; // Tier 3: Preferred type (method/class) |
| 41 | +var SCORE_TOP_LEVEL = 50; // Tier 4: Top-level over namespaced |
| 42 | +var SCORE_CLASS_METHOD = 10; // Tier 4: Class method over instance method |
| 43 | + |
25 | 44 | /** |
26 | 45 | * Check if all characters in query appear in order in target |
27 | 46 | * e.g., "addalias" fuzzy matches "add_foo_alias" |
@@ -112,61 +131,32 @@ function computeScore(entry, q) { |
112 | 131 |
|
113 | 132 | // Exact full_name match (e.g., "Array#filter" matches Array#filter) |
114 | 133 | if (q.matchesFullName && fullNameLower === q.normalized) { |
115 | | - return 1000000; |
| 134 | + return SCORE_EXACT_FULL_NAME; |
116 | 135 | } |
117 | 136 |
|
118 | 137 | var matchScore = 0; |
119 | | - |
120 | | - if (q.matchesFullName) { |
121 | | - // For namespace queries like "Foo::Bar" or method queries like "Array#filter", |
122 | | - // match against full_name |
123 | | - if (fullNameLower.startsWith(q.normalized)) { |
124 | | - matchScore = 1000; // Prefix (e.g., "Arr" matches "Array") |
125 | | - } else if (fullNameLower.includes(q.normalized)) { |
126 | | - matchScore = 100; // Substring (e.g., "ray" matches "Array") |
127 | | - } else if (fuzzyMatch(fullNameLower, q.normalized)) { |
128 | | - matchScore = 10; // Fuzzy (e.g., "addalias" matches "add_foo_alias") |
129 | | - } else { |
130 | | - return null; |
131 | | - } |
| 138 | + var target = q.matchesFullName ? fullNameLower : nameLower; |
| 139 | + |
| 140 | + if (target.startsWith(q.normalized)) { |
| 141 | + matchScore = SCORE_MATCH_PREFIX; // Prefix (e.g., "Arr" matches "Array") |
| 142 | + } else if (target.includes(q.normalized)) { |
| 143 | + matchScore = SCORE_MATCH_SUBSTRING; // Substring (e.g., "ray" matches "Array") |
| 144 | + } else if (fuzzyMatch(target, q.normalized)) { |
| 145 | + matchScore = SCORE_MATCH_FUZZY; // Fuzzy (e.g., "addalias" matches "add_foo_alias") |
132 | 146 | } else { |
133 | | - // For regular queries, match against unqualified name |
134 | | - if (nameLower.startsWith(q.normalized)) { |
135 | | - matchScore = 1000; // Prefix |
136 | | - } else if (nameLower.includes(q.normalized)) { |
137 | | - matchScore = 100; // Substring |
138 | | - } else if (fuzzyMatch(nameLower, q.normalized)) { |
139 | | - matchScore = 10; // Fuzzy |
140 | | - } else { |
141 | | - return null; |
142 | | - } |
| 147 | + return null; |
143 | 148 | } |
144 | 149 |
|
145 | 150 | var score = matchScore; |
146 | 151 | var isMethod = (type === 'instance_method' || type === 'class_method'); |
147 | 152 |
|
148 | | - if (q.prioritizeMethod) { |
149 | | - if (isMethod) score += 10000; |
150 | | - } else { |
151 | | - if (!isMethod) score += 10000; |
152 | | - } |
153 | | - |
154 | | - // Class method > instance method |
155 | | - if (type === 'class_method') { |
156 | | - score += 500; |
157 | | - } |
158 | | - |
159 | | - // Top-level (Hash) > namespaced (Foo::Hash) |
160 | | - if (name === fullName) { |
161 | | - score += 5000; |
162 | | - } |
163 | | - |
164 | | - // Exact name match (e.g., "Hash" matches Hash over Hashable) |
165 | | - if (nameLower === q.normalized) { |
166 | | - score += 50000; |
| 153 | + if (q.prioritizeMethod ? isMethod : !isMethod) { |
| 154 | + score += SCORE_TYPE_PRIORITY; |
167 | 155 | } |
168 | 156 |
|
169 | | - // Shorter name is better (subtract name length) |
| 157 | + if (type === 'class_method') score += SCORE_CLASS_METHOD; |
| 158 | + if (name === fullName) score += SCORE_TOP_LEVEL; // Top-level (Hash) > namespaced (Foo::Hash) |
| 159 | + if (nameLower === q.normalized) score += SCORE_EXACT_NAME; // Exact name match |
170 | 160 | score -= name.length; |
171 | 161 |
|
172 | 162 | return score; |
|
0 commit comments