Skip to content

Commit 945231c

Browse files
committed
fix(xref): disambiguate overloaded method signatures in search results
When multiple xref results share the same term and context (e.g., overloaded methods like Window/postMessage), extract parameter info from the URI fragment to show distinct linking text suggestions. Closes #383
1 parent 5916d39 commit 945231c

1 file changed

Lines changed: 90 additions & 5 deletions

File tree

static/xref/script.js

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,18 @@ function renderResults(entries, query) {
144144
return;
145145
}
146146

147+
// Detect overloaded IDL entries that would produce identical citations.
148+
// Build a map of citation key -> list of entries to find duplicates.
149+
const overloadedURIs = detectOverloadedEntries(entries, term);
150+
147151
let html = '';
148152
for (const entry of entries) {
149153
const specInfo = metadata.specs[entry.status][entry.spec];
150154
const link = new URL(entry.uri, specInfo.url).href;
151155
const title = escapeHTML(specInfo.title);
156+
const isOverloaded = overloadedURIs.has(entry.uri);
152157
const cite = metadata.types.idl.has(entry.type)
153-
? howToCiteIDL(term, entry)
158+
? howToCiteIDL(term, entry, isOverloaded)
154159
: metadata.types.markup.has(entry.type)
155160
? howToCiteMarkup(term, entry)
156161
: metadata.types.css.has(entry.type) ||
@@ -169,24 +174,104 @@ function renderResults(entries, query) {
169174
output.innerHTML = html;
170175
}
171176

172-
function howToCiteIDL(term, entry) {
177+
/**
178+
* Detects IDL entries that would produce identical citations (overloaded
179+
* methods/constructors). Returns a Set of URIs that need disambiguation.
180+
*/
181+
function detectOverloadedEntries(entries, term) {
182+
const citationGroups = new Map();
183+
for (const entry of entries) {
184+
if (!metadata.types.idl.has(entry.type)) continue;
185+
const forKey = (entry.for || []).join(',');
186+
const key = `${entry.type}|${forKey}|${term}`;
187+
const group = citationGroups.get(key) ?? [];
188+
if (!group.length) citationGroups.set(key, group);
189+
group.push(entry);
190+
}
191+
const overloadedURIs = new Set();
192+
for (const group of citationGroups.values()) {
193+
if (group.length > 1) {
194+
group.forEach(entry => overloadedURIs.add(entry.uri));
195+
}
196+
}
197+
return overloadedURIs;
198+
}
199+
200+
function howToCiteIDL(term, entry, isOverloaded = false) {
173201
const { type, for: forList } = entry;
174202
if (forList) {
175203
return forList
176204
.map(f => {
177205
const termPart = type === 'enum-value' ? `"${term}"` : term;
178-
return `{{${f}/${term ? termPart : '""'}}}`;
206+
let cite = `{{${f}/${term ? termPart : '""'}}}`;
207+
if (isOverloaded) {
208+
const hint = extractOverloadHint(entry.uri, f, term);
209+
if (hint) {
210+
cite += ` <small>(${escapeHTML(hint)})</small>`;
211+
}
212+
}
213+
return cite;
179214
})
180215
.join('<br>');
181216
}
217+
let cite;
182218
switch (type) {
183219
case 'exception':
184220
if (!exceptionExceptions.has(term)) {
185-
return `{{"${term}"}}`;
221+
cite = `{{"${term}"}}`;
222+
break;
186223
}
187224
default:
188-
return `{{${term}}}`;
225+
cite = `{{${term}}}`;
226+
}
227+
if (isOverloaded) {
228+
const hint = extractOverloadHint(entry.uri, null, term);
229+
if (hint) {
230+
cite += ` <small>(${escapeHTML(hint)})</small>`;
231+
}
232+
}
233+
return cite;
234+
}
235+
236+
/**
237+
* Extracts a human-readable overload hint from a URI fragment.
238+
*
239+
* URI fragments for WebIDL definitions follow the pattern:
240+
* #dom-<interface>-<method>-<param1>-<param2>-...
241+
*
242+
* For example:
243+
* #dom-window-postmessage-message-targetorigin-transfer
244+
* #dom-window-postmessage-message-options
245+
*
246+
* This function strips the known prefix (interface + method) and returns
247+
* the remaining parts as a parameter list, e.g. "message, targetorigin, transfer".
248+
*/
249+
function extractOverloadHint(uri, forContext, term) {
250+
if (!uri) return '';
251+
const hash = uri.includes('#') ? uri.split('#')[1] : uri;
252+
if (!hash) return '';
253+
254+
const parts = hash.toLowerCase().split('-');
255+
256+
// Build the prefix to strip: typically "dom", interface, method
257+
const prefixParts = ['dom'];
258+
if (forContext) {
259+
prefixParts.push(...forContext.toLowerCase().split('-'));
260+
}
261+
const cleanTerm = term.replace(/\(.*\)$/, '').toLowerCase();
262+
if (cleanTerm) {
263+
prefixParts.push(...cleanTerm.split('-'));
189264
}
265+
266+
const matches =
267+
prefixParts.length <= parts.length &&
268+
prefixParts.every((p, i) => parts[i] === p);
269+
270+
if (matches && prefixParts.length < parts.length) {
271+
return parts.slice(prefixParts.length).join(', ');
272+
}
273+
274+
return '';
190275
}
191276

192277
function howToCiteMarkup(term, entry) {

0 commit comments

Comments
 (0)