Skip to content

fix(linter): detect Svelte TS and module scripts correctly#20819

Merged
camc314 merged 10 commits intooxc-project:mainfrom
mustafa0x:fix/svelte-script-detection-micro
Apr 27, 2026
Merged

fix(linter): detect Svelte TS and module scripts correctly#20819
camc314 merged 10 commits intooxc-project:mainfrom
mustafa0x:fix/svelte-script-detection-micro

Conversation

@mustafa0x
Copy link
Copy Markdown
Contributor

Summary

  • parse Svelte <script> attributes instead of broad substring checks
  • detect both module and context="module" scripts when creating partial JS sources
  • add unit and CLI coverage for module/instance scripts in both JS and TS

Validation

  • mise x -- cargo test -p oxlint lint_svelte_
  • mise x -- cargo test -p oxc_linter partial_loader::svelte

AI usage disclosure

This PR was prepared with AI assistance (Codex). I reviewed and take responsibility for the final changes.

@mustafa0x mustafa0x requested a review from camc314 as a code owner March 28, 2026 12:32
@github-actions github-actions Bot added A-linter Area - Linter A-cli Area - CLI C-bug Category - Bug labels Mar 28, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9900f83b6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs Outdated
Copy link
Copy Markdown
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

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

Do you mind explaining a little bit more about this change - was something not working previously that this now fixes?

@camc314 camc314 self-assigned this Mar 29, 2026
@mustafa0x
Copy link
Copy Markdown
Contributor Author

This PR fixes real Svelte parsing correctness issues in oxlint’s partial-loader path.

Previously, TypeScript detection used a broad content.contains("ts") check on the whole <script ...> tag text, which could misclassify plain JS blocks as TS when "ts" appeared in unrelated attributes or values. It also treated only ' ' and '>' as valid characters after <script, so valid tags like <script\nlang="ts"> or <script\tcontext="module"> could be skipped entirely, causing missed diagnostics.

This change makes parsing attribute-aware (lang, module, context="module"), accepts general whitespace after <script, preserves offsets/module behavior, and adds unit + CLI regression coverage (including newline/tab and TS/module script cases).

@camc314
Copy link
Copy Markdown
Contributor

camc314 commented Mar 30, 2026

@mustafa0x do you mind sharing a real-world example of when it didn't work before?

@mustafa0x
Copy link
Copy Markdown
Contributor Author

Thanks for the follow-up. Yes, this fixes a real behavior gap, and I validated it with a negative regression check.

Before this PR, parse_script only accepted ' ' or '>' immediately after <script:

if !self.source_text[*pointer..].starts_with([' ', '>']) {
    return self.parse_script(pointer);
}

That means valid Svelte tags like these could be skipped:

<script
  lang="ts">
  debugger;
</script>
<script	context="module">
  debugger;
</script>

So we could silently miss diagnostics for legitimate .svelte files depending on formatting/whitespace.

What this PR changes

The boundary check now accepts any whitespace (space/tab/newline) or >:

let is_script_tag_boundary = self.source_text[*pointer..]
    .chars()
    .next()
    .is_some_and(|ch| ch.is_whitespace() || ch == '>');

It also keeps the existing <script-...> guard behavior and adds attribute-aware handling for lang, module, and context="module".

Regression proof (negative test)

I validated this by doing a throwaway revert in a temporary worktree:

  • kept all new tests
  • reverted only the whitespace boundary logic back to the old starts_with([' ', '>']) check
  • ran:
mise x -- cargo test -p oxc_linter partial_loader::svelte

Result: tests fail exactly where expected:

  • loader::partial_loader::svelte::test::test_parse_svelte_script_tag_allows_newline_after_script_name
  • loader::partial_loader::svelte::test::test_parse_svelte_script_tag_allows_tab_after_script_name

Both failed with called Option::unwrap() on a None value, which confirms the loader failed to extract a script block under those valid formats when the fix is removed.

So this is not just refactoring; it closes a real parsing correctness hole and the new regressions are effective at catching it.

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e30a331e93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b716135f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves Svelte <script> parsing in the partial loader so TypeScript and module/context-module scripts are detected reliably, and adds regression coverage to ensure both module + instance blocks are linted with correct locations.

Changes:

  • Parse Svelte <script> attributes to detect lang, module, and context="module" instead of substring checks.
  • Ensure extracted partial JS sources use the correct SourceType (TS vs JS and module semantics).
  • Add unit + CLI tests (and fixtures) covering module/instance scripts in JS and TS.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/oxc_linter/src/loader/partial_loader/svelte.rs Adds attribute parsing for <script> blocks and expands unit tests for module/context-module + TS handling.
apps/oxlint/src/tester.rs Adds a helper to capture non-silent CLI output for assertion-based tests.
apps/oxlint/src/lint.rs Adds CLI regression tests asserting module + instance scripts both produce diagnostics with correct positions.
apps/oxlint/fixtures/cli/svelte/module-script.svelte New fixture for <script module> + instance script.
apps/oxlint/fixtures/cli/svelte/context-module-script.svelte New fixture for <script context="module"> + instance script.
apps/oxlint/fixtures/cli/svelte/context-module-script-ts.svelte New fixture for context-module + instance scripts using lang="ts".

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs
Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 934ebf107f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/oxc_linter/src/loader/partial_loader/svelte.rs Outdated
@janosh
Copy link
Copy Markdown

janosh commented Apr 25, 2026

Svelte support in oxc would be huge! wanted to ask if this PR is still active or stale/superseded? what's the best way for community to help get this shipped?

@mustafa0x
Copy link
Copy Markdown
Contributor Author

@janosh you can also test my up-to-date fork for svelte

Copy link
Copy Markdown
Contributor

@camc314 camc314 left a comment

Choose a reason for hiding this comment

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

Thanks!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 27, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 47 skipped benchmarks1


Comparing mustafa0x:fix/svelte-script-detection-micro (2245b5d) with main (3612db1)2

Open in CodSpeed

Footnotes

  1. 47 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 (15ded13) during the generation of this report, so 3612db1 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@camc314 camc314 merged commit aace797 into oxc-project:main Apr 27, 2026
26 checks passed
camc314 added a commit that referenced this pull request Apr 27, 2026
# Oxlint
### 💥 BREAKING CHANGES

- 502e804 ast: [**BREAKING**] Reduce size of `TSTypePredicateName`
(#21711) (overlookmotel)
- 5651539 ast: [**BREAKING**] Reduce size of `JSXExpression` (#21710)
(overlookmotel)
- c44e280 ast: [**BREAKING**] Reduce size of `ArrayExpressionElement`
(#21709) (overlookmotel)

### 🚀 Features

- b4acdbd linter/vue: Implement no-deprecated-delete-set rule (#21766)
(bab)
- 613bfef linter: Split jest/prefer-to-contain into a jest and a vitest
rule. (#21821) (connorshea)
- c629719 linter/vue: Implement no-deprecated-events-api rule (#21793)
(Alex Peshkov)
- f2a8528 linter: Split jest/require-top-level-describe rule into a Jest
and a Vitest rule. (#21822) (connorshea)
- ea4b358 linter: Split jest/prefer-todo into a shared rule and a
vitest-specific rule. (#21820) (connorshea)
- 3a66819 linter: Split jest/no-mocks-import into vitest/no-mocks-import
(#21818) (camchenry)
- 0b3ffce linter: Split jest/no-large-snapshots into
vitest/no-large-snapshots (#21814) (camchenry)
- d1cf22e linter: Split jest/no-interpolation-in-snapshots into
vitest/no-interpolation-in-snapshots (#21812) (camchenry)
- bb0c359 linter: Split jest/no-identical-title into
vitest/no-identical-title (#21810) (camchenry)
- c26ea41 linter: Split jest/no-hooks into vitest/no-hooks (#21809)
(camchenry)
- 46b32f2 linter: Split jest/no-focused-tests into
vitest/no-focused-tests (#21804) (camchenry)
- acf41a8 linter: Split jest/no-duplicate-hooks into
vitest/no-duplicate-hooks (#21803) (camchenry)
- 54d787f linter: Split jest/no-disabled-tests into
vitest/no-disabled-tests (#21802) (camchenry)
- 9c9a676 linter/vue: Implement no-deprecated-data-object-declaration
rule (#21764) (bab)
- 4445855 linter: Split `jest/no-conditional-in-test` into
`vitest/no-conditional-in-test` (#21763) (camchenry)
- b8604de linter: Split `jest/no-conditional-expect` into
`vitest/no-conditional-expect` (#21762) (camchenry)
- 0dbd650 linter: Split `jest/no-commented-out-tests` into
`vitest/no-commented-out-tests` (#21761) (camchenry)
- 7f1a97c linter: Split `jest/no-alias-methods` into
`vitest/no-alias-methods` (#21760) (camchenry)
- eb97c49 linter: Split `jest/max-nested-describe` into
`vitest/max-nested-describe` (#21759) (camchenry)
- d870cad linter: Split `jest/max-expects` into `vitest/max-expects`
(#21758) (camchenry)
- 1b97124 linter/vue: Implement no-deprecated-vue-config-keycodes rule
(#21699) (bab)
- 5f81883 linter/eslint: Implement `func-name-matching` rule (#21708)
(Mikhail Baev)
- 348f46c linter: Add `respectEslintDisableDirectives` option (#21384)
(Christian Vuerings)
- 63ec351 linter: Support nested vite+ config discovery (#21638)
(camc314)
- 560feb4 linter: Introduce `Vite` variant to `DiscoveredConfigFile`
(#21637) (camc314)
- 07dc41e linter/eslint: Implement `no-underscore-dangle` rule (#21630)
(Paul-Arthur THIERY)
- e270d54 linter/react: Impl react/no-did-update-set-state (#17322)
(Kenzo Wada)
- ca81199 linter/react: Implement `forbid-component-props` rule (#20005)
(Mikhail Baev)
- 6776403 linter/branches-sharing-code: Move rule from nursery to
pedantic (#21621) (camc314)
- ce7a4dc linter/no-unreachable: Move rule from nursery to correctness
(#21618) (camc314)
- e3b5e78 linter/getter-return: Move rule from nursery to correctness
(#21617) (camc314)
- d3a7e9a linter/unicorn: Implement suggestion for
`no-useless-iterator-to-array` rule (#21610) (Mikhail Baev)
- a0c883c oxlint/lsp: Add vite plus version to server info (#21587)
(Sysix)
- 67ff860 linter/no-unknown-property: Support React 19 `precedence` prop
(#21590) (João Pedro Schmitz)

### 🐛 Bug Fixes

- aace797 linter: Detect Svelte TS and module scripts correctly (#20819)
(mustafa0x)
- 3612db1 linter/prefer-default-parameters: Skip reporting on
object/class setters (#21836) (camc314)
- 28c3521 oxlint/lsp: Remove overlapping edits for
`source.fixAllDangerous.oxc` code action (#21785) (Sysix)
- 705a82c linter/no-non-null-asserted-nullish-coalescing: Add fixer
(#21827) (yyh)
- 33f5535 linter: Add checks that `Program` is in current allocator
chunk before JS plugins linting (#21774) (overlookmotel)
- d122877 linter/no-extra-non-null-assertions: Add fixer (#21744) (yyh)
- 37f0731 linter: `with_plugin_vitest(true)` working realiable in test
mode (#21769) (Said Atrahouch)
- e42e6a6 linter/role-supports-aria-props: False positive with
`combobox` and `haspopup` (#21725) (Leonabcd123)
- e24324f linter: Iframe-has-title false positive for template literals
(#21714) (Leonabcd123)
- 41a6510 linter/vitest/hoisted-apis-on-top: Only check first member
(#20068) (Sidharth Vinod)
- 98ed888 linter: Check initializer for allowConstantExport in
only-export-components (#20608) (Eyüp Can Akman)
- 1946e8b linter: Avoid applying override plugin categories to eslint
rules (#21521) (bab)
- 3d1e83a linter: Parse `<script>` tag attributes with curly braces in
Svelte files (#21089) (bab)
- bc6ade5 linter: Report actual disable directive prefix (#21682)
(camc314)
- bf84466 linter: Respect category settings in overrides (#19411)
(Connor Shea)
- 52ecb45 linter/vitest: Don't treat `test.extend` or `it.extend` as
test functions (#21668) (Said Atrahouch)
- aa1a00c linter: Support jsx-a11y attributes setting in anchor-is-valid
rule (#21665) (camchenry)
- 30e0ad3 linter/prefer-default-parameters: Preserve TS annotations in
fixer (#21655) (camc314)
- 8c425db linter: Allow string for jest version in config schema
(#21649) (camc314)
- 3617864 linter/react/display-name: Fix false positive for named
default class (#21643) (Mikhail Baev)
- f3a02cc linter/no-non-null-assertion: Improve diagnostic message
(#21616) (Cameron)
- c2ada2c linter: Make `--fix-dangerously` fix dangerous fixes and
suggestions as documented (#13366) (Ulrich Stark)
- 8265ed9 linter/sort-keys: Handle CRLF separated groups (#21608)
(camc314)
- 96c559e linter/no-shadow: Add note explaining enum member shadowing
(#21607) (camc314)
- 3b49389 linter: No-irregular-whitespace: add config options (#21559)
(camchenry)

### ⚡ Performance

- cdc9eae oxlint/lsp: Avoid clones on lsp options deserializion (#21748)
(Sysix)
- be2db80 linter/sort-keys: Reduce allocations (#21560) (camchenry)

### 📚 Documentation

- 453d647 linter: Remove now-unnecessary compatibility notes from shared
Jest/Vitest rules. (#21826) (connorshea)
- b1574a2 linter: Fix the configuration docs for no-underscore-dangle
rule. (#21801) (connorshea)
- 6946aee linter: Remove no-longer-relevant documentation about vitest
compatibility from shared Jest/Vitest rules. (#21799) (connorshea)
- 0652ea2 linter: Misc grammar cleanup for Vitest rules. (#21755)
(connorshea)
- 99f0b68 linter: Improve docs for `vitest/require-mock-type-parameters`
rule. (#21754) (connorshea)
- 9e2796b linter: Improve the docs for the `vitest/hoisted-apis-on-top`
rule. (#21753) (connorshea)
- 2ccede2 linter: Improve function usage examples in doc comments
(#21718) (overlookmotel)
- 43adae9 linter: Add version section to rules page (#21601) (camchenry)
- d15dad2 linter: Export rule version metadata (#21588) (Old Autumn)
# Oxfmt
### 💥 BREAKING CHANGES

- 502e804 ast: [**BREAKING**] Reduce size of `TSTypePredicateName`
(#21711) (overlookmotel)
- 5651539 ast: [**BREAKING**] Reduce size of `JSXExpression` (#21710)
(overlookmotel)
- c44e280 ast: [**BREAKING**] Reduce size of `ArrayExpressionElement`
(#21709) (overlookmotel)

### 🚀 Features

- 3bc54a9 oxfmt: Respect nested config for `--stdin-filepath` (#21627)
(leaysgur)
- 144f27a oxfmt: Respect ignore settings for `--stdin-filepath` (#21625)
(leaysgur)
- 81c7ae4 oxfmt/lsp: Add vite plus version to server info (#21586)
(Sysix)

### 🐛 Bug Fixes

- 477435b formatter/sort_imports: Keep leading blank line when
decreasing group transitions (#21835) (leaysgur)
- 38d1e82 oxfmt/lsp: Format non `file://` URIs without a authority
(#21647) (Sysix)
- 5eb8e2b formatter/sort_imports: Preserve blank lines around ignored
side-effect imports (#21692) (leaysgur)
- 0dce3c6 oxfmt: Handle invalid `overrides` config without panic
(#21661) (Yuji Sugiura)
- 9f82ed4 formatter: Escape backticks in JSDoc inline code spans
(#21577) (bab)

### ⚡ Performance

- db6c603 oxfmt/lsp: Avoid clones on lsp options deserializion (#21749)
(Sysix)
- 6a96c76 formatter: Avoid heap alloc for jsdoc delimiter (#21597)
(leaysgur)

---------

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
@benmccann
Copy link
Copy Markdown

When plugin support is added, I wonder how this will interact with eslint-plugin-svelte. I wonder if it's going to cause duplicate checks

@mustafa0x
Copy link
Copy Markdown
Contributor Author

@camc314 Thanks for the merge!

Should I continue opening small and self-contained PRs for svelte? Or pause for now, especially as a new plugin system is impending?

@camc314
Copy link
Copy Markdown
Contributor

camc314 commented Apr 28, 2026

@camc314 Thanks for the merge!

Should I continue opening small and self-contained PRs for svelte? Or pause for now, especially as a new plugin system is impending?

Thanks for contributing!

Any bug fixes would be much appreciated. But for new features, let's hold until the embedded framework plugins are released! Thanks!

@camc314
Copy link
Copy Markdown
Contributor

camc314 commented Apr 28, 2026

When plugin support is added, I wonder how this will interact with eslint-plugin-svelte. I wonder if it's going to cause duplicate checks

possibly - we'll probably add an optin to disable the built in svelte linting - TBD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter C-bug Category - Bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants