Skip to content

fix(linter/eslint/no-unassigned-vars): skip Svelte and Vue files - #26042

Open
hamodywe wants to merge 1 commit into
oxc-project:mainfrom
hamodywe:fix/no-unassigned-vars-skip-svelte-vue
Open

fix(linter/eslint/no-unassigned-vars): skip Svelte and Vue files#26042
hamodywe wants to merge 1 commit into
oxc-project:mainfrom
hamodywe:fix/no-unassigned-vars-skip-svelte-vue

Conversation

@hamodywe

Copy link
Copy Markdown
Contributor

Summary

Closes #26038.

no-unassigned-vars is a correctness rule, so this fires by default with no config:

<script lang="ts">
  let dialog: HTMLDialogElement;

  function openDialog() {
    dialog.showModal();
  }
</script>

<dialog bind:this={dialog}>Hello</dialog>
repro.svelte:2:7: warning eslint(no-unassigned-vars): 'dialog' is always 'undefined' because it's never assigned.

Why the rule cannot get this right today

The rule's decision is has_read && no reference is_write(), taken over the symbol's references in the current script block. Oxlint reaches .svelte/.vue through PartialLoader, which extracts the <script> blocks and throws the markup away before parsing — so the template's write is not in the AST the rule sees, and no reference on dialog is ever a write. Every ingredient of the diagnostic is satisfied, and the diagnostic is still wrong: Svelte assigns the element to dialog on mount.

The same shape exists in Vue, where a <script setup> let is a setup-let binding that v-model="x" and inline handlers such as @click="x = 1" compile to direct assignments against.

This is not a matter of tightening the check — the write is in a part of the file the linter never parses.

The change

prefer-const hit exactly this and already skips both extensions (#25148):

fn should_run(&self, ctx: &crate::context::ContextHost) -> bool {
    // ignore svelte/vue: their templates can reassign a binding, which we can't see.
    !ctx.file_extension().is_some_and(|ext| ext == "svelte" || ext == "vue")
}

This applies the sibling rule's guard rather than inventing one, and documents it in the rule docs under an #### Ignored Files heading, matching prefer-const's wording.

.astro is deliberately not included: an Astro template renders a frontmatter binding but has no construct that writes back to it, so the false positive does not arise there. prefer-const drew the same line.

Scope, stated honestly

This trades a false positive for lost coverage: a genuinely unassigned variable inside a .svelte/.vue <script> block is no longer reported. That matches the precedent and the reporter's own request, and a correctness false positive that fires on the documented Svelte idiom costs more than the missed report. Restoring coverage needs template-aware analysis, which is the language-support effort tracked separately (#13017, #17991).

Verification

  • cargo test -p oxc_linter --lib1254 passed, 0 failed.
  • Two new tests, test_svelte and test_vue, cover the reported repro, bind:value, a two-<script> Svelte component, v-model, and an inline @click handler.
  • Revert-checked: with should_run removed and the tests kept, exactly those two tests fail and the rest pass. The failure output reproduces the reported diagnostic verbatim, including the let shared case from the two-script component — so the tests fail for the right reason, not because the fixture path changed.
  • cargo clippy -p oxc_linter --lib --all-features — no new findings (the two FromIterator::from_iter warnings are pre-existing, in tsgolint.rs and lib.rs).
  • cargo fmt --check clean.
  • The rule is not part of docs_rule_pages.snap, so the doc addition needs no snapshot regeneration.

Built and tested on Windows with the 1.97.1-x86_64-pc-windows-gnu toolchain rather than the pinned 1.98.0, and with RUSTFLAGS overridden to drop the MSVC-only link args that .cargo/config.toml applies to every target_os = "windows" target. Nothing in the repository was changed for that.


Written with AI assistance (Claude Code), reviewed and tested by me before submitting, per AGENTS.md.

Oxlint parses only the `<script>` blocks of `.svelte` and `.vue` files, so
a binding that the template assigns looks, from the script alone, like it
is declared and read but never assigned. `no-unassigned-vars` then reports
it as always `undefined`.

The reported case is a Svelte element reference:

    <script lang="ts">
      let dialog: HTMLDialogElement;
      function openDialog() { dialog.showModal(); }
    </script>
    <dialog bind:this={dialog}>Hello</dialog>

Svelte assigns the element to `dialog` on mount. Vue has the same shape:
a `<script setup>` `let` is a `setup-let` binding that `v-model="x"` and
inline handlers such as `@click="x = 1"` assign to directly.

`prefer-const` already skips both extensions for this exact reason, so
this applies the sibling rule's `should_run` guard rather than inventing
one. `.astro` is left alone: its templates cannot write back to a
frontmatter binding.

Closes oxc-project#26038
@hamodywe
hamodywe requested a review from camc314 as a code owner August 24, 2026 08:11
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@codspeed-hq

codspeed-hq Bot commented Aug 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 76 skipped benchmarks1


Comparing hamodywe:fix/no-unassigned-vars-skip-svelte-vue (c70dfb2) with main (fe444cc)2

Open in CodSpeed

Footnotes

  1. 76 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.

  2. No successful run was found on main (5b43c60) during the generation of this report, so fe444cc was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

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.

linter: no-unassigned-vars incorrectly reports Svelte bind:this targets

1 participant