feat(js_analyze): implement useQwikLoaderLocation#9809
Conversation
🦋 Changeset detectedLatest commit: a8f5b6e The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
Merging this PR will not alter performance
Comparing Footnotes
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughIntroduces a new nursery lint rule Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (13)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_configuration/src/generated/domain_selector.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_configuration/src/generated/linter_options_check.rsis excluded by!**/generated/**,!**/generated/**and included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-location/src/components/product/product.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-missing-export/src/routes/index.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-name/src/routes/index.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-recommended-value/src/routes/index.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader-export/src/routes/index.jsx.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader/src/routes/index.jsx.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (10)
.changeset/ripe-pigs-burn.mdcrates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rscrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-location/src/components/product/product.jsxcrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-missing-export/src/routes/index.jsxcrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-name/src/routes/index.jsxcrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/invalid-loader-recommended-value/src/routes/index.jsxcrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader-export/src/routes/index.jsxcrates/biome_js_analyze/tests/specs/nursery/useQwikLoaderLocation/valid-loader/src/routes/index.jsxcrates/biome_rule_options/src/lib.rscrates/biome_rule_options/src/use_qwik_loader_location.rs
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| // Check parent structure: must be `const <id> = <fn>()` | ||
| let declarator = call | ||
| .syntax() | ||
| .parent() | ||
| .and_then(|n| n.parent()) | ||
| .and_then(JsVariableDeclarator::cast); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Mhm I don't we should hard require that
| 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()); |
There was a problem hiding this comment.
I don't think so, as Qwik has some funky stuff with file names;
- e.g.
plugin@<name>.jsxhttps://qwik.dev/docs/advanced/plugins/#the-order-of-execution-of-plugints-files
And you don't want to allow false positives
There was a problem hiding this comment.
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(), |
There was a problem hiding this comment.
this could probably be a TokenText
There was a problem hiding this comment.
We can use Box<str> actually
There was a problem hiding this comment.
That's still a heap allocation
| 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()), | ||
| }; |
There was a problem hiding this comment.
We can use Box<str> actually
There was a problem hiding this comment.
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
letandvarstill sneak past theconstcontract.Line 125 says
const <id> = <fn>(), but Lines 126-130 only prove there is aJsVariableDeclarator.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 | 🟠 MajorResolve 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 andimport { 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
📒 Files selected for processing (1)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs
| 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 }, | ||
| } |
There was a problem hiding this comment.
These Box<str> can all be TokenText, which would avoid any heap allocations.
There was a problem hiding this comment.
Ahh, alright. Changed to TokenText
There was a problem hiding this comment.
♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs (2)
157-159:⚠️ Potential issue | 🟠 MajorMake export detection symbol-aware.
is_exportedcurrently 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 | 🟠 MajorResolve 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
📒 Files selected for processing (1)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs
19f5efa to
0f52053
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs (1)
145-146: Avoid heap allocation withTokenText.
to_string()allocates unnecessarily. You can keep theTokenTextand 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_exportedsignature to accept&TokenTextor 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
📒 Files selected for processing (1)
crates/biome_js_analyze/src/lint/nursery/use_qwik_loader_location.rs
0f52053 to
66c7572
Compare
dyc3
left a comment
There was a problem hiding this comment.
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 |
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