Skip to content

Commit

Permalink
feat: no-window (#1166)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Jan 16, 2024
1 parent 9cfd195 commit 828d66c
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 2 deletions.
35 changes: 35 additions & 0 deletions docs/rules/no_window_global.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Disallows the use of the `window` object.

Using the `window` global is deprecated and scheduled for removal in Deno 2.0.
Deno does not have a window and `typeof window === "undefined"` is often used to
tell if the code is running in the browser.

### Invalid:

```typescript
const a = await window.fetch("https://deno.land");

const b = window.Deno.metrics();
console.log(window);

window.addEventListener("load", () => {
console.log("Loaded.");
});
```

### Valid:

```typescript
const a1 = await fetch("https://deno.land");
const a2 = await globalThis.fetch("https://deno.land");
const a3 = await self.fetch("https://deno.land");

const b1 = Deno.metrics();
const b2 = globalThis.Deno.metrics();
const b3 = self.Deno.metrics();
console.log(globalThis);

addEventListener("load", () => {
console.log("Loaded.");
});
```
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ pub mod no_unsafe_negation;
pub mod no_unused_labels;
pub mod no_unused_vars;
pub mod no_var;
pub mod no_window;
pub mod no_window_prefix;
pub mod no_with;
pub mod prefer_as_const;
Expand Down Expand Up @@ -312,6 +313,7 @@ fn get_all_rules_raw() -> Vec<&'static dyn LintRule> {
&no_unused_labels::NoUnusedLabels,
&no_unused_vars::NoUnusedVars,
&no_var::NoVar,
&no_window::NoWindow,
&no_window_prefix::NoWindowPrefix,
&no_with::NoWith,
&prefer_as_const::PreferAsConst,
Expand Down
119 changes: 119 additions & 0 deletions src/rules/no_window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license.
use super::Context;
use super::LintRule;
use crate::handler::Handler;
use crate::handler::Traverse;
use crate::Program;

use deno_ast::view as ast_view;
use deno_ast::SourceRanged;
use if_chain::if_chain;

#[derive(Debug)]
pub struct NoWindow;

const CODE: &str = "no-window";
const MESSAGE: &str =
"window is deprecated and scheduled for removal in Deno 2.0";
const HINT: &str = "Instead, use `globalThis`";

impl LintRule for NoWindow {
fn tags(&self) -> &'static [&'static str] {
&["recommended"]
}

fn code(&self) -> &'static str {
CODE
}

fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program<'_>,
) {
NoWindowGlobalHandler.traverse(program, context);
}

#[cfg(feature = "docs")]
fn docs(&self) -> &'static str {
include_str!("../../docs/rules/no_window_global.md")
}
}

struct NoWindowGlobalHandler;

impl Handler for NoWindowGlobalHandler {
fn ident(&mut self, ident: &ast_view::Ident, ctx: &mut Context) {
if_chain! {
if ident.sym() == "window";
if ctx.scope().is_global(&ident.to_id());
then {
ctx.add_diagnostic_with_hint(
ident.range(),
CODE,
MESSAGE,
HINT,
);
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn no_window_valid() {
assert_lint_ok! {
NoWindow,
"fetch();",
"self.fetch();",
"globalThis.fetch();",

// `window` is shadowed
"const window = 42; window.fetch();",
r#"const window = 42; window["fetch"]();"#,
r#"const window = 42; window[`fetch`]();"#,
"const window = 42; window.alert();",
r#"const window = 42; window["alert"]();"#,
r#"const window = 42; window[`alert`]();"#,
};
}

#[test]
fn no_window_invalid() {
assert_lint_err! {
NoWindow,
MESSAGE,
HINT,
r#"window.fetch()"#: [
{
col: 0,
}
],
r#"window["fetch"]()"#: [
{
col: 0,
}
],
r#"window[`fetch`]()"#: [
{
col: 0,
}
],
r#"
function foo() {
const window = 42;
return window;
}
window;
"#: [
{
col: 0,
line: 6,
}
],
};
}
}
4 changes: 2 additions & 2 deletions src/rules/no_window_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ mod tests {
use super::*;

#[test]
fn no_deprecated_deno_api_valid() {
fn no_window_prefix_valid() {
assert_lint_ok! {
NoWindowPrefix,
"fetch();",
Expand Down Expand Up @@ -377,7 +377,7 @@ mod tests {
}

#[test]
fn no_deprecated_deno_api_invalid() {
fn no_window_prefix_invalid() {
assert_lint_err! {
NoWindowPrefix,
MESSAGE,
Expand Down
7 changes: 7 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@
"recommended"
]
},
{
"code": "no-window",
"docs": "Disallows the use of the `window` object.\n\nUsing the `window` global is deprecated and scheduled for removal in Deno 2.0.\nDeno does not have a window and `typeof window === \"undefined\"` is often used to\ntell if the code is running in the browser.\n\n### Invalid:\n\n```typescript\nconst a = await window.fetch(\"https://deno.land\");\n\nconst b = window.Deno.metrics();\nconsole.log(window);\n\nwindow.addEventListener(\"load\", () => {\n console.log(\"Loaded.\");\n});\n```\n\n### Valid:\n\n```typescript\nconst a1 = await fetch(\"https://deno.land\");\nconst a2 = await globalThis.fetch(\"https://deno.land\");\nconst a3 = await self.fetch(\"https://deno.land\");\n\nconst b1 = Deno.metrics();\nconst b2 = globalThis.Deno.metrics();\nconst b3 = self.Deno.metrics();\nconsole.log(globalThis);\n\naddEventListener(\"load\", () => {\n console.log(\"Loaded.\");\n});\n```\n",
"tags": [
"recommended"
]
},
{
"code": "no-window-prefix",
"docs": "Disallows the use of Web APIs via the `window` object.\n\nIn most situations, the global variable `window` works like `globalThis`. For\nexample, you could call the `fetch` API like `window.fetch(..)` instead of\n`fetch(..)` or `globalThis.fetch(..)`. In Web Workers, however, `window` is not\navailable, but instead `self`, `globalThis`, or no prefix work fine. Therefore,\nfor compatibility between Web Workers and other contexts, it's highly\nrecommended to not access global properties via `window`.\n\nSome APIs, including `window.alert`, `window.location` and `window.history`, are\nallowed to call with `window` because these APIs are not supported or have\ndifferent meanings in Workers. In other words, this lint rule complains about\nthe use of `window` only if it's completely replaceable with `self`,\n`globalThis`, or no prefix.\n\n### Invalid:\n\n```typescript\nconst a = await window.fetch(\"https://deno.land\");\n\nconst b = window.Deno.metrics();\n```\n\n### Valid:\n\n```typescript\nconst a1 = await fetch(\"https://deno.land\");\nconst a2 = await globalThis.fetch(\"https://deno.land\");\nconst a3 = await self.fetch(\"https://deno.land\");\n\nconst b1 = Deno.metrics();\nconst b2 = globalThis.Deno.metrics();\nconst b3 = self.Deno.metrics();\n\n// `alert` is allowed to call with `window` because it's not supported in Workers\nwindow.alert(\"🍣\");\n\n// `location` is also allowed\nwindow.location.host;\n```\n",
Expand Down

0 comments on commit 828d66c

Please sign in to comment.