Skip to content

feat(lint/js): add noTopLevelBrowserGlobals - #11632

Draft
dyc3 wants to merge 2 commits into
mainfrom
dyc3/add-top-level-browser-globals-rule
Draft

dyc3 wants to merge 2 commits into
mainfrom
dyc3/add-top-level-browser-globals-rule

Conversation

@dyc3

@dyc3 dyc3 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

this pr adds noTopLevelBrowserGlobals, a cross framework rule intended to protect projects that use server side rendering.

  • should we make an SSR domain? feels like it could make sense, but not sure what other rules could go in there.
  • im sure this would apply to angular eventually, but would this also apply to astro?

inspired by (and pretty much an expanded scope port of) https://sveltejs.github.io/eslint-plugin-svelte/rules/no-top-level-browser-globals/

implemented by astra

Test Plan

snapshots

Docs

- Detect unguarded browser globals in top-level Vue and Svelte scripts
- Add configuration, diagnostics, fixtures, snapshots, and changeset
@changeset-bot

changeset-bot Bot commented Sep 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 04f1af2

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-darwin-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-win32-x64 Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/wasm-web 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-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Sep 6, 2026
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 7be46f02-51c9-4371-97c2-35a84f8c2075

📥 Commits

Reviewing files that changed from the base of the PR and between 62e1fc5 and 794e67b.

⛔ Files ignored due to path filters (26)
  • 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/noTopLevelBrowserGlobals/invalid-class-fields.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-guards.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-script.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-wrapped.svelte.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-ambient.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-guards.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-guards.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-instance-fields.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-lifecycle.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-lifecycle.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-shadowed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-types.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-types.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-wrapped.svelte.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.svelte.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.vue.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (26)
  • .changeset/quiet-browser-scripts.md
  • crates/biome_js_analyze/src/lint/nursery/no_top_level_browser_globals.rs
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-class-fields.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-guards.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte.js
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-module.svelte.ts
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-script.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid-wrapped.svelte.ts
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/invalid.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-ambient.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-guards.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-guards.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-instance-fields.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-lifecycle.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-lifecycle.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-shadowed.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-types.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-types.vue
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid-wrapped.svelte.ts
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.js
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.svelte
  • crates/biome_js_analyze/tests/specs/nursery/noTopLevelBrowserGlobals/valid.vue
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_top_level_browser_globals.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

Adds the noTopLevelBrowserGlobals nursery rule for Vue and Svelte scripts and Svelte modules. The rule reports unguarded top-level browser-global references, while excluding types, functions, templates, instance fields, shadowed bindings, and recognised browser guards. It adds public rule options, exports the options module, documents the rule, and adds valid and invalid fixtures for supported access patterns.

Merge Risk: ⚪ Minimal · up to 794e6

The new rule, options export, and fixtures have no identified merge-blocking risk.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the addition of the noTopLevelBrowserGlobals lint rule and matches the main change.
Description check ✅ Passed The description explains the new cross-framework SSR rule, its motivation, and the test approach. It directly relates to the changeset.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/add-top-level-browser-globals-rule

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

@github-actions github-actions Bot added the A-CLI Area: CLI label Sep 6, 2026
@codspeed

codspeed Bot commented Sep 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 74 untouched benchmarks
⏩ 270 skipped benchmarks1


Comparing dyc3/add-top-level-browser-globals-rule (04f1af2) with main (62e1fc5)

Open in CodSpeed

Footnotes

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

@ematipico ematipico 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.

IMHO, as it is, the rule is a bit limited. I believe we can make it more powerful if we make it a project rule, and we inspect the also the modules imported by a component.

The moment you move the unguarded global into a simple file, and import it inside the component, the rule cease to function

use biome_rule_options::no_top_level_browser_globals::NoTopLevelBrowserGlobalsOptions;

declare_lint_rule! {
/// Disallow unguarded browser globals at the top level of Vue and Svelte scripts.

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.

what about Astro?

@dyc3
dyc3 marked this pull request as draft September 17, 2026 17:33

This branch has not been deployed

No deployments
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.

2 participants