Skip to content

feat(js_analyze): implement useQwikLoaderLocation#9809

Merged
Netail merged 4 commits intobiomejs:mainfrom
Netail:feat/loader-location
Apr 8, 2026
Merged

feat(js_analyze): implement useQwikLoaderLocation#9809
Netail merged 4 commits intobiomejs:mainfrom
Netail:feat/loader-location

Conversation

@Netail
Copy link
Copy Markdown
Member

@Netail Netail commented Apr 5, 2026

Summary

Port Eslint Qwik's loader-location, which enforces that Qwik loader functions are declared in the correct location.

The is_exported function has been generated with Claude Sonnet 4.6

Closes #3498 (Last rule to be implemented)

Test Plan

unit tests

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 5, 2026

🦋 Changeset detected

Latest commit: a8f5b6e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Apr 5, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 5, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing Netail:feat/loader-location (a8f5b6e) with main (3d4e70a)

Open in CodSpeed

Footnotes

  1. 196 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Introduces a new nursery lint rule UseQwikLoaderLocation in the Biome JS analyser, plus a corresponding UseQwikLoaderLocationOptions type and a Changeset entry. The rule checks calls to routeLoader$, routeAction$, and globalAction$ for correct file location (route boundary files under src/routes/), enforces assignment as const <id> = <call>() with an exported identifier whose name starts with use, and emits diagnostics for invalid location, missing export, wrong name, or recommended value usage. Adds tests covering valid and invalid cases.

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing a new Qwik loader location linter rule in the JavaScript analyzer.
Description check ✅ Passed The description is directly related to the changeset, explaining the port of ESLint Qwik's loader-location rule, test plan, and closure of issue #3498.
Linked Issues check ✅ Passed The PR successfully implements the useQwikLoaderLocation rule, fulfilling the 'Implement custom Biome rules for Qwik' objective from issue #3498 with complete unit test coverage.
Out of Scope Changes check ✅ Passed All changes are narrowly scoped to implementing the useQwikLoaderLocation rule: rule logic, configuration options, and test fixtures. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs`:
- Around line 124-129: The current check only finds a JsVariableDeclarator (via
variable `declarator`) but doesn't ensure the variable declaration is `const`,
allowing `let`/`var` to pass; update the logic that finds `declarator` to also
traverse to the surrounding JsVariableDeclaration (e.g.,
call.syntax().parent().and_then(|n|
n.parent()).and_then(JsVariableDeclarator::cast).and_then(|d|
d.parent()).and_then(JsVariableDeclaration::cast)) and verify the declaration's
kind is `const` (use the declaration kind accessor on JsVariableDeclaration)
before proceeding with the export/name handling in use_qwik_loader_location.rs
so only `const <id> = <fn>()` matches.
- Around line 109-115: The route-path check fails for relative paths like
"src/routes/index.tsx" because contains("/src/routes/") requires a leading
slash; update the is_inside_routes check so it handles both forms—either
normalize file_path (e.g., ensure it starts with '/' before checking) or change
the check to use file_path.contains("src/routes/") (and keep existing
LAYOUT_REGEX/INDEX_REGEX/PLUGIN_REGEX logic). Locate the file_path,
is_inside_routes and the subsequent can_contain_loader expression and apply the
normalization or altered contains check so both "src/routes/..." and
"/src/routes/..." match.
- Around line 96-104: The check currently matches raw identifier text (callee ->
callee_ident_expr -> callee_ref_ident -> callee_name_text) which causes false
positives/negatives; instead resolve the callee's binding via the semantic model
and ensure it originates from the `@builder.io/qwik-city` package (or is a known
global) before applying LINTER_FNS. Concretely: obtain the semantic context
(e.g. ctx.semantic() or the analyzer's semantic API), resolve the symbol/binding
for callee_ref_ident (not just its trimmed text), follow import aliases to the
original import declaration and verify the module specifier equals
"@builder.io/qwik-city" (handle aliased imports like import { routeLoader$ as
defineLoader }), and only then check if the symbol name is in LINTER_FNS; if the
binding is a local variable or from another module, return None.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ea7f86a7-5e54-4df1-973b-96964dae18a6

📥 Commits

Reviewing files that changed from the base of the PR and between 1d09f0f and 7b79cef.

⛔ Files ignored due to path filters (13)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-location/src/components/product/product.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-missing-export/src/routes/index.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-name/src/routes/index.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-recommended-value/src/routes/index.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader-export/src/routes/index.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader/src/routes/index.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (10)
  • .changeset/ripe-pigs-burn.md
  • crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-location/src/components/product/product.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-missing-export/src/routes/index.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-name/src/routes/index.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-recommended-value/src/routes/index.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader-export/src/routes/index.jsx
  • crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader/src/routes/index.jsx
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_qwik_loader_location.rs

Comment on lines +96 to +104
let callee = call.callee().ok()?.omit_parentheses();
let callee_ident_expr = callee.as_js_identifier_expression()?;
let callee_ref_ident = callee_ident_expr.name().ok()?;
let callee_name = callee_ref_ident.to_trimmed_text();
let callee_name_text = callee_name.text();

if !LINTER_FNS.contains(&callee_name_text) {
return None;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve the callee symbol, not just the spelling.

This keys off raw identifier text only, so a local routeLoader$ helper becomes a false positive and import { routeLoader$ as defineLoader } becomes a false negative. Please resolve the binding and confirm it comes from @builder.io/qwik-city before reporting. Based on learnings: Check if a variable is global using the semantic model before reporting rules that ban certain functions or variables to avoid false positives on locally redeclared variables.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 96 - 104, The check currently matches raw identifier text (callee ->
callee_ident_expr -> callee_ref_ident -> callee_name_text) which causes false
positives/negatives; instead resolve the callee's binding via the semantic model
and ensure it originates from the `@builder.io/qwik-city` package (or is a known
global) before applying LINTER_FNS. Concretely: obtain the semantic context
(e.g. ctx.semantic() or the analyzer's semantic API), resolve the symbol/binding
for callee_ref_ident (not just its trimmed text), follow import aliases to the
original import declaration and verify the module specifier equals
"@builder.io/qwik-city" (handle aliased imports like import { routeLoader$ as
defineLoader }), and only then check if the symbol name is in LINTER_FNS; if the
binding is a local variable or from another module, return None.

Comment thread crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs Outdated
Comment on lines +124 to +129
// Check parent structure: must be `const <id> = <fn>()`
let declarator = call
.syntax()
.parent()
.and_then(|n| n.parent())
.and_then(JsVariableDeclarator::cast);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

let and var currently sneak past the const requirement.

The comment says const <id> = <fn>(), but this only proves that the call sits under a JsVariableDeclarator. export let useProducts = routeLoader$(...) will pass the rule today, so the declaration kind needs checking before the export/name logic runs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 124 - 129, The current check only finds a JsVariableDeclarator (via
variable `declarator`) but doesn't ensure the variable declaration is `const`,
allowing `let`/`var` to pass; update the logic that finds `declarator` to also
traverse to the surrounding JsVariableDeclaration (e.g.,
call.syntax().parent().and_then(|n|
n.parent()).and_then(JsVariableDeclarator::cast).and_then(|d|
d.parent()).and_then(JsVariableDeclaration::cast)) and verify the declaration's
kind is `const` (use the declaration kind accessor on JsVariableDeclaration)
before proceeding with the export/name handling in use_qwik_loader_location.rs
so only `const <id> = <fn>()` matches.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Mhm I don't we should hard require that

Comment on lines +23 to +28
static LAYOUT_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"/layout(?:|!|-.+)\.[jt]sx?$").unwrap());
static INDEX_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"/index(?:|!|@.+)\.[jt]sx?$").unwrap());
static PLUGIN_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"/plugin(?:|@.+)\.[jt]sx?$").unwrap());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we avoid the regexes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't think so, as Qwik has some funky stuff with file names;

And you don't want to allow false positives

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these are just for file names, we can definitely avoid using the regex. call the functions on the path to extract the filename and then do starts_with(). don't even need to check the extension because its always going to be a js/ts/jsx/tsx file.


if !can_contain_loader {
return Some(RuleState::InvalidLoaderLocation {
fn_name: callee_name_text.to_string(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this could probably be a TokenText

Copy link
Copy Markdown
Member Author

@Netail Netail Apr 5, 2026

Choose a reason for hiding this comment

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

We can use Box<str> actually

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's still a heap allocation

Comment on lines +296 to +306
AnyJsExportNamedSpecifier::JsExportNamedShorthandSpecifier(s) => s
.name()
.ok()
.and_then(|r| r.value_token().ok())
.map(|t| t.text_trimmed().to_string()),
AnyJsExportNamedSpecifier::JsExportNamedSpecifier(s) => s
.local_name()
.ok()
.and_then(|r| r.value_token().ok())
.map(|t| t.text_trimmed().to_string()),
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

again, TokenText

Copy link
Copy Markdown
Member Author

@Netail Netail Apr 5, 2026

Choose a reason for hiding this comment

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

We can use Box<str> actually

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs (2)

125-130: ⚠️ Potential issue | 🟠 Major

let and var still sneak past the const contract.

Line 125 says const <id> = <fn>(), but Lines 126-130 only prove there is a JsVariableDeclarator. export let useProducts = routeLoader$(...) will sail through today.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 125 - 130, The current check only casts to JsVariableDeclarator
(let/var/const not distinguished), so declarations like `export let ...` pass;
update the validation to assert the variable declaration is actually a const.
After obtaining the JsVariableDeclarator (from
`call.syntax().parent().and_then(|n|
n.parent()).and_then(JsVariableDeclarator::cast)`), walk up to its parent
JsVariableDeclaration (e.g.,
`declarator.syntax().parent().and_then(JsVariableDeclaration::cast)`) and verify
the declaration kind is Const (or use the API method that checks `is_const()` /
`kind()`), and bail out if it is `let` or `var`; keep the rest of the logic
unchanged.

96-104: ⚠️ Potential issue | 🟠 Major

Resolve the callee symbol before matching Qwik helpers.

Lines 96-104 still key off raw identifier text, so a local routeLoader$ helper becomes a false positive and import { routeLoader$ as defineLoader } becomes a false negative. This rule needs the imported binding, not just the spelling. Based on learnings: check bindings with the semantic model to avoid false positives on locally redeclared variables.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 96 - 104, The check currently matches on raw identifier text (variables
callee, callee_ident_expr, callee_ref_ident, callee_name_text) which causes
false positives/negatives for locally redeclared or aliased imports; update the
logic to resolve the identifier to its binding via the semantic model (resolve
the symbol/binding for callee_ident_expr) and verify that the resolved import
specifier or original imported symbol name is one of LINTER_FNS instead of
comparing the trimmed identifier text; use the semantic resolution APIs
available in this crate to get the import/source binding before matching against
LINTER_FNS so aliased imports (e.g., import { routeLoader$ as defineLoader })
and local redeclarations are handled correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs`:
- Around line 154-167: The WrongName check uses the local binding's text
(id_name from binding.name_token()) but is_exported(&declarator, &id_name,
&ctx.root()) can return true for alias exports (export { local as exported }),
causing false positives or mismatches when shadowed bindings exist; update the
export check to compare binding identity via the semantic model rather than raw
strings — e.g., look up the symbol for `binding` in the semantic model (use the
context/semantic API available on `ctx`), and verify that the exact binding
symbol is one of the exported symbols (adjust `is_exported` or replace its use
with a symbol-based check) so RuleState::WrongName, `binding`, `declarator`, and
export-detection logic all agree; apply the same symbol-based fix to the similar
logic around the code handling export-name comparisons (the helper used in the
280-309 region).

---

Duplicate comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs`:
- Around line 125-130: The current check only casts to JsVariableDeclarator
(let/var/const not distinguished), so declarations like `export let ...` pass;
update the validation to assert the variable declaration is actually a const.
After obtaining the JsVariableDeclarator (from
`call.syntax().parent().and_then(|n|
n.parent()).and_then(JsVariableDeclarator::cast)`), walk up to its parent
JsVariableDeclaration (e.g.,
`declarator.syntax().parent().and_then(JsVariableDeclaration::cast)`) and verify
the declaration kind is Const (or use the API method that checks `is_const()` /
`kind()`), and bail out if it is `let` or `var`; keep the rest of the logic
unchanged.
- Around line 96-104: The check currently matches on raw identifier text
(variables callee, callee_ident_expr, callee_ref_ident, callee_name_text) which
causes false positives/negatives for locally redeclared or aliased imports;
update the logic to resolve the identifier to its binding via the semantic model
(resolve the symbol/binding for callee_ident_expr) and verify that the resolved
import specifier or original imported symbol name is one of LINTER_FNS instead
of comparing the trimmed identifier text; use the semantic resolution APIs
available in this crate to get the import/source binding before matching against
LINTER_FNS so aliased imports (e.g., import { routeLoader$ as defineLoader })
and local redeclarations are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f4e03603-f7f0-4b99-8acc-f604cd2ac408

📥 Commits

Reviewing files that changed from the base of the PR and between 7b79cef and dba50e9.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs

Comment on lines +249 to +258
pub enum RuleState {
/// The loader is declared outside of a route boundary file.
InvalidLoaderLocation { fn_name: Box<str> },
/// The loader return value is not exported from the module.
MissingExport { fn_name: Box<str>, span: TextRange },
/// The exported name does not follow the `use*` convention.
WrongName { fn_name: Box<str>, span: TextRange },
/// The first argument is a reference instead of an inlined arrow function.
RecommendedValue { span: TextRange },
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These Box<str> can all be TokenText, which would avoid any heap allocations.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ahh, alright. Changed to TokenText

@Netail Netail requested a review from dyc3 April 5, 2026 22:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs (2)

157-159: ⚠️ Potential issue | 🟠 Major

Make export detection symbol-aware.

is_exported currently relies on name equality, so alias/shadowing cases can drift from the actual binding checked for naming. Compare exported symbols against the declarator’s symbol identity via the semantic model instead of raw strings.

Based on learnings: "Check if a variable is global using the semantic model before reporting rules that ban certain functions or variables to avoid false positives on locally redeclared variables."

Also applies to: 256-305

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 157 - 159, The export check uses string equality (id_name) which
misidentifies aliases/shadows; update is_exported to compare symbol identities
from the semantic model rather than names: resolve the declarator's symbol
(using the semantic/TypeContext API) and compare that Symbol/Binding to exported
symbols on the module root (ctx.root() via the semantic model) when deciding to
return RuleState::MissingExport in the code paths around
is_exported(&declarator, &id_name, &ctx.root()); apply the same symbol-aware
change to the later checks in the file (lines ~256-305) so all export/global
checks use the declarator's resolved symbol identity instead of raw strings.

86-95: ⚠️ Potential issue | 🟠 Major

Resolve the callee by symbol, not by spelling.

This text match still catches local routeLoader$ helpers and misses aliased imports. Please resolve the callee binding via the semantic model and only apply this rule when it comes from @builder.io/qwik-city.

Based on learnings: "Check if a variable is global using the semantic model before reporting rules that ban certain functions or variables to avoid false positives on locally redeclared variables."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 86 - 95, The current code compares the callee by its token text (callee,
callee_ident_expr, callee_ref_ident, callee_name, LINTER_FNS) which yields false
positives/negatives; instead use the semantic model to resolve the callee's
binding: get the symbol for call.callee(), follow its declaration to determine
its module/source, and only apply the rule when the symbol originates from the
`@builder.io/qwik-city` package (handling renamed/aliased imports). Concretely,
replace the text-based checks with a semantic lookup on the callee node, verify
the symbol's import source (or that it's the package export) and skip reporting
if the symbol is a local redeclaration or not from `@builder.io/qwik-city`. Ensure
LINTER_FNS is still used to match exported names after resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs`:
- Around line 157-159: The export check uses string equality (id_name) which
misidentifies aliases/shadows; update is_exported to compare symbol identities
from the semantic model rather than names: resolve the declarator's symbol
(using the semantic/TypeContext API) and compare that Symbol/Binding to exported
symbols on the module root (ctx.root() via the semantic model) when deciding to
return RuleState::MissingExport in the code paths around
is_exported(&declarator, &id_name, &ctx.root()); apply the same symbol-aware
change to the later checks in the file (lines ~256-305) so all export/global
checks use the declarator's resolved symbol identity instead of raw strings.
- Around line 86-95: The current code compares the callee by its token text
(callee, callee_ident_expr, callee_ref_ident, callee_name, LINTER_FNS) which
yields false positives/negatives; instead use the semantic model to resolve the
callee's binding: get the symbol for call.callee(), follow its declaration to
determine its module/source, and only apply the rule when the symbol originates
from the `@builder.io/qwik-city` package (handling renamed/aliased imports).
Concretely, replace the text-based checks with a semantic lookup on the callee
node, verify the symbol's import source (or that it's the package export) and
skip reporting if the symbol is a local redeclaration or not from
`@builder.io/qwik-city`. Ensure LINTER_FNS is still used to match exported names
after resolution.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aa7c4a48-27ab-4fc6-8ef3-c1ea03193105

📥 Commits

Reviewing files that changed from the base of the PR and between dba50e9 and 19f5efa.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs

@Netail Netail force-pushed the feat/loader-location branch from 19f5efa to 0f52053 Compare April 5, 2026 23:01
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs (1)

145-146: Avoid heap allocation with TokenText.

to_string() allocates unnecessarily. You can keep the TokenText and compare via .text() downstream.

Suggested diff
         let id_token = binding.name_token().ok()?;
-        let id_name = id_token.text_trimmed().to_string();
+        let id_name = id_token.token_text_trimmed();
         let span = binding.range();
 
         // Check naming convention
-        if !id_name.starts_with("use") {
+        if !id_name.text().starts_with("use") {
             return Some(RuleState::WrongName {

Then update is_exported signature to accept &TokenText or compare .text().

Based on learnings: Avoid calling 'to_string()' on values when comparing strings; use '&str' or 'TokenText' instead to avoid unnecessary heap allocations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs` around
lines 145 - 146, Replace the unnecessary heap allocation from
id_token.text_trimmed().to_string() by keeping the TokenText value and comparing
it without allocating: obtain the trimmed TokenText from binding.name_token()
(keep as TokenText or &TokenText) instead of converting to String, then update
the is_exported function signature (and any callers) to accept &TokenText (or
accept TokenText and compare via .text()) so comparisons use TokenText/.text()
rather than creating a String; reference symbols: binding.name_token(),
id_token, id_name, and is_exported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs`:
- Around line 145-146: Replace the unnecessary heap allocation from
id_token.text_trimmed().to_string() by keeping the TokenText value and comparing
it without allocating: obtain the trimmed TokenText from binding.name_token()
(keep as TokenText or &TokenText) instead of converting to String, then update
the is_exported function signature (and any callers) to accept &TokenText (or
accept TokenText and compare via .text()) so comparisons use TokenText/.text()
rather than creating a String; reference symbols: binding.name_token(),
id_token, id_name, and is_exported.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 10740113-1af6-4075-bec3-ab98ea6c5068

📥 Commits

Reviewing files that changed from the base of the PR and between 19f5efa and 0f52053.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs

@Netail Netail force-pushed the feat/loader-location branch from 0f52053 to 66c7572 Compare April 5, 2026 23:13
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

If this is the last qwik rule to be implemented, we should update the unsupported rules migration metadata to mark the unused-server rule as unnecessary. I'm fine if you do that in this PR or a new one.

@Netail
Copy link
Copy Markdown
Member Author

Netail commented Apr 8, 2026

If this is the last qwik rule to be implemented, we should update the unsupported rules migration metadata to mark the unused-server rule as unnecessary. I'm fine if you do that in this PR or a new one.

Didn't knew we had that, nice. Will do that in a separate PR

@Netail Netail merged commit e8cad58 into biomejs:main Apr 8, 2026
21 checks passed
@Netail Netail deleted the feat/loader-location branch April 8, 2026 18:45
@github-actions github-actions Bot mentioned this pull request Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

☂️ Qwik lint rules

2 participants