Skip to content

fix(jsxref): build Web/JavaScript index to resolve names#715

Merged
caugner merged 15 commits into
mainfrom
jsxref-with-index
Jul 17, 2026
Merged

fix(jsxref): build Web/JavaScript index to resolve names#715
caugner merged 15 commits into
mainfrom
jsxref-with-index

Conversation

@caugner

@caugner caugner commented May 21, 2026

Copy link
Copy Markdown
Contributor

Description

Update jsxref to resolve names against a lazy in-memory index of all en-US Web/JavaScript/Reference/* page slugs, instead of two per-call RariApi::get_page_nowarn probes. The index:

  • Indexes each page under its full sub-path (Statements/for...of).
  • Adds aliases for the Global_Objects/-stripped form (Array, Array/from, undefined), the Operators/* and Statements/* bare leaves (null, typeof, const, return), and a namespace strip for the class-style namespaces Intl and Temporal (CollatorIntl/Collator, Collator/compareIntl/Collator/compare).
  • Looks up case-sensitively (so Set / set and Function / function are distinguishable), falling back to a precomputed lowercase map on miss to keep the case-folded fallback O(1).

When a case-sensitive miss has exactly one case-folded match, the lookup resolves but emits a new templ-ill-cased-arg flaw pointing at the canonical casing. When a bucket has multiple case-sensitive candidates (today: lowercase function, class, import, async_function), the resolver refuses and emits templ-invalid-arg.

A fragment embedded directly in the argument (e.g. {{jsxref("Array.prototype.map#examples")}}) is split off before normalization and the index lookup, then re-appended to the URL, so the bare name still resolves via the index. When both an embedded fragment and the separate anchor argument are given, the embedded fragment wins and anchor is ignored (emitting templ-invalid-arg); empty anchor arguments are filtered first. This mirrors cssxref (#682).

Motivation

  • Translated content frequently uses unqualified forms (e.g. {{jsxref("Collator", "Intl.Collator")}}) that the old code couldn't resolve to a canonical path and emitted as templ-redirected-link.
  • The old per-call get_page_nowarn probes followed redirects silently, masking stale slugs in content and inflating templ-redirected-link noise (2,588 on main).

Additional details

build --grep jsxref across en-US + all translated locales (es,fr,ja,ko,pt-br,ru,zh-cn,zh-tw), counting only jsxref-sourced issues, against current content (2026-07-16, after the prerequisite cleanups below merged):

Source main this PR Δ
templ-redirected-link 2,588 0 −2,588
templ-broken-link 170 177 +7
templ-ill-cased-arg (new) 0 16 +16
templ-invalid-arg (new) 0 7 +7
jsxref total 2,758 200 −2,558

(Excludes 2 unrelated must be a string argument-type errors, identical on both sides.)

The remaining diagnostics are genuine and were previously masked behind redirect chains:

Related issues and pull requests

Relates to #672, #682 (same indexing pattern applied to domxref and cssxref).

Follow-up translated-content cleanup for the remaining stale templ-broken-link references surfaced above (one draft PR per locale):

Prerequisite translated-content cleanups (all merged):

caugner added 7 commits May 21, 2026 12:12
Replace the two per-call `RariApi::get_page_nowarn()` probes in `jsxref`
with a single lookup against a lazy `HashMap<String, IndexSet<String>>`
of all `Web/JavaScript/Reference/*` page slugs.

Each page is indexed under its full sub-path, its `Global_Objects/`-
stripped form, and — for pages under a `JavascriptNamespace` parent
(`Intl`, `Temporal`) — its namespace-stripped form. The path structure
naturally separates a namespace class (`Collator`) from its constructor
(`Collator/Collator`) and members (`Collator/compare`).

Authors can now reference:

- `{{jsxref("Collator", "Intl.Collator")}}` → `Intl/Collator`
- `{{jsxref("Collator/compare")}}` → `Intl/Collator/compare`

without the `Intl/` prefix, fixing `templ-redirected-link` flaws in
translated-content where the prefix was omitted.
`{{jsxref("null")}}`, `{{jsxref("typeof")}}`, `{{jsxref("instanceof")}}`
and similar bare operator references resolve via a leaf shortcut for
each `Web/JavaScript/Reference/Operators/<leaf>` page.

Fixes a regression introduced by the index where translated content
using bare operator names produced `templ-broken-link` flaws — the old
code's per-call `get_page_nowarn` would follow a `Global_Objects/null`
redirect, while the index only contained the canonical `Operators/null`
slug.
Adds bare-leaf shortcuts for `Statements/<keyword>` pages so
`{{jsxref("const")}}`, `{{jsxref("let")}}`, `{{jsxref("return")}}` etc.
resolve without a `Statements/` prefix.

A handful of keywords (`function`, `class`, `import`, `async_function`)
exist as both a statement and an operator expression. Instead of
picking one with a heuristic, `resolve_js_ref` refuses to resolve when
a bucket holds multiple canonical sub-paths and emits a
`templ-invalid-arg` tracing event listing the candidates, so the
author can add the qualifying category prefix.
Drops the `.to_lowercase()` from index keys and from lookup. JavaScript
naming distinguishes classes (`Set`, `Function`, `Map`) from
methods/keywords (`set`, `function`, `class`) by case, and authors use
that distinction consistently. The previous case-insensitive scheme
collapsed both into one bucket and reported ~826 spurious "ambiguous"
flaws for PascalCase class references like `{{jsxref("Set")}}` (446
occurrences) and `{{jsxref("Function")}}` (380 occurrences) across
content and translated-content.

Also replaces the dynamic `JavascriptNamespace` page-type scan with a
hardcoded list of *class-style* namespaces (`Intl`, `Temporal`).
Static-API namespaces (`Reflect`, `Atomics`, `Math`, `JSON`) are now
excluded: their direct children are methods, not classes, and aliasing
e.g. `Reflect.set` under bare `set` collided with the global `Set`
class.

Only genuinely ambiguous bare references — lowercase `function`,
`class`, `import`, `async_function` — still trip the
`templ-invalid-arg` flaw.
Lookups remain case-sensitive by default (so `Set` ≠ `set` and
`Function` ≠ `function`), but a wrong-case input that has a single
canonical match — e.g. `{{jsxref("Undefined")}}` for
`Global_Objects/undefined` — now resolves via a case-insensitive
fallback. The resolver still flags it with a new `templ-ill-cased-arg`
tracing event listing the canonical sub-path so authors can fix the
casing.

Multiple case-folded matches (e.g. some hypothetical `Foo`/`foo` pair)
fall back to the existing `templ-invalid-arg` refusal.

Adds the new `IssueType::TemplIllCasedArg` variant with a dedicated
explanation that references both the supplied arg and the canonical
form.
Pairs the case-preserved primary map with a derived lowercase map in a
`JsRefIndex` struct, both populated once when `build_index` runs.

The case-insensitive fallback in `resolve_from_index` is now O(1)
instead of an O(N) scan across all ~6k primary keys on every miss.
Switch `JsRefIndex` value type from `String` to `Arc<str>` so the
canonical sub-path heap buffer is shared between `primary` and
`lowercase`, and across the multiple primary keys that point to the
same page (e.g. `Statements/const` and the bare `const` leaf). Cuts
the per-page allocations done by `index_one` and `from_primary` to
one `Arc::from` plus refcount bumps.

Lookup behavior is unchanged; `resolve_from_index` still returns
`&str` via `Arc::as_ref`.
@caugner caugner changed the title Fix jsxref to resolve names via a Web/JavaScript/Reference index fix(jsxref): build Web/JavaScript index to resolve names May 21, 2026
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

fb02ef6 was deployed to: https://rari-pr715.review.mdn.allizom.net/

This was referenced May 22, 2026
caugner added a commit to mdn/translated-content that referenced this pull request May 22, 2026
…ator shorthands

Use the bare-keyword shortform for the 5 operator keywords `null`,
`this`, `delete`, `new`, `instanceof` per the convention introduced
in mdn/content#44221. The shortform resolves cleanly on current rari
thanks to the `Reference/<kw>` -> `Reference/Operators/<kw>`
redirects that PR added, and resolves natively under the index-aware
rari from mdn/rari#715.

Only collapses the redundant-display pattern (`("Operators/X", "X")`);
longforms with custom display text (`"delete x"`, `"deleted"`,
`"x instanceof y"`) are preserved.
caugner added a commit to mdn/translated-content that referenced this pull request May 22, 2026
Mirrors the en-US redirects added in mdn/content#44221 for the 5
operator-keyword shorthands (`null`, `this`, `delete`, `new`,
`instanceof`). Without these per-locale redirects, the bare-keyword
`{{jsxref("null")}}` shortform produces a 404 under the deployed
rari; with them, it falls back via redirect to the canonical
`Reference/Operators/<kw>` page. The index-aware rari from
mdn/rari#715 resolves the shortform directly without needing the
redirect.
caugner added a commit to mdn/translated-content that referenced this pull request May 22, 2026
…ator shorthands

Use the bare-keyword shortform for the 5 operator keywords `null`,
`this`, `delete`, `new`, `instanceof` per the convention introduced
in mdn/content#44221. The shortform resolves cleanly on current rari
thanks to the `Reference/<kw>` -> `Reference/Operators/<kw>`
redirects that PR added, and resolves natively under the index-aware
rari from mdn/rari#715.

Only collapses the redundant-display pattern (`("Operators/X", "X")`);
longforms with custom display text (`"delete x"`, `"deleted"`,
`"x instanceof y"`) are preserved.
caugner added a commit to mdn/translated-content that referenced this pull request Jul 16, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.
caugner added a commit to mdn/translated-content that referenced this pull request Jul 16, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.
caugner added a commit to mdn/translated-content that referenced this pull request Jul 16, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.
@caugner
caugner marked this pull request as ready for review July 16, 2026 10:21
@caugner
caugner requested a review from a team as a code owner July 16, 2026 10:21
@caugner
caugner requested a review from LeoMcA July 16, 2026 10:21
mfuji09 pushed a commit to mdn/translated-content that referenced this pull request Jul 16, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.

@LeoMcA LeoMcA left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good! A handful of things I noticed:

Comment thread crates/rari-doc/src/templ/js_ref_index.rs Outdated
Comment thread crates/rari-doc/src/templ/js_ref_index.rs
Comment thread crates/rari-doc/src/templ/js_ref_index.rs Outdated
Move name normalization (`()` strip, `.prototype.` collapse, member
`.` → `/`) out of `jsxref_parts` and into `resolve_js_ref`. The `.` → `/`
mapping only makes sense against the `/`-separated sub-paths the index is
keyed by, so the index should own it — indexing and normalization now
evolve together.

Convert only a *lone* `.`; leave runs intact so triple-dot statements
resolve by their bare leaf (`for...of` no longer mangles to `for///of`).
No current en-US or translated content uses the bare triple-dot forms, so
build issue counts are unchanged.

The dotted-member index tests now feed raw names (`Array.prototype.map`)
through `normalize` instead of pre-slashed keys, so they exercise the
normalization they describe.
Replace the packed negated condition with named `prev_is_dot` /
`next_is_dot` neighbor checks and an `in_dot_run` intermediate, so the
member-separator rule reads directly. No behavior change.
caugner added a commit to mdn/translated-content that referenced this pull request Jul 17, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.
@caugner
caugner merged commit 2ff4f72 into main Jul 17, 2026
19 checks passed
@caugner
caugner deleted the jsxref-with-index branch July 17, 2026 13:25
caugner added a commit to mdn/translated-content that referenced this pull request Jul 17, 2026
Resolve `jsxref` macro arguments that no longer point at an existing
`Web/JavaScript/Reference/*` page. An upcoming rari change (mdn/rari#715)
resolves these against an index instead of probing candidate URLs and
following redirects, which surfaces such stale references as broken links.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants