use super::Context;
use super::LintRule;
use crate::diagnostic::LintFix;
use crate::diagnostic::LintFixChange;
use crate::handler::Handler;
use crate::handler::Traverse;
use crate::tags;
use crate::tags::Tags;
use crate::Program;
use deno_ast::view as ast_view;
use deno_ast::SourceRanged;
use if_chain::if_chain;
use once_cell::sync::Lazy;
use std::collections::HashSet;
#[derive(Debug)]
pub struct NoWindowPrefix;
const CODE: &str = "no-window-prefix";
const MESSAGE: &str = "For compatibility between the Window context and the Web Workers, calling Web APIs via `window` is disallowed";
const HINT: &str =
"Instead, call this API via `self`, `globalThis`, or no extra prefix";
const FIX_DESC: &str = "Rename window to globalThis";
impl LintRule for NoWindowPrefix {
fn tags(&self) -> Tags {
&[tags::RECOMMENDED]
}
fn code(&self) -> &'static str {
CODE
}
fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program<'_>,
) {
NoWindowPrefixHandler.traverse(program, context);
}
}
static PROPERTY_DENY_LIST: Lazy<HashSet<&'static str>> = Lazy::new(|| {
[
"AbortController",
"AbortSignal",
"Blob",
"BroadcastChannel",
"ByteLengthQueuingStrategy",
"Cache",
"CacheStorage",
"CanvasGradient",
"CanvasPattern",
"CloseEvent",
"CountQueuingStrategy",
"Crypto",
"CryptoKey",
"CustomEvent",
"DOMException",
"DOMMatrix",
"DOMMatrixReadOnly",
"DOMPoint",
"DOMPointReadOnly",
"DOMQuad",
"DOMRect",
"DOMRectReadOnly",
"DOMStringList",
"ErrorEvent",
"Event",
"EventSource",
"EventTarget",
"File",
"FileList",
"FileReader",
"FontFace",
"FontFaceSet",
"FontFaceSetLoadEvent",
"FormData",
"Headers",
"IDBCursor",
"IDBCursorWithValue",
"IDBDatabase",
"IDBFactory",
"IDBIndex",
"IDBKeyRange",
"IDBObjectStore",
"IDBOpenDBRequest",
"IDBRequest",
"IDBTransaction",
"IDBVersionChangeEvent",
"ImageBitmap",
"ImageBitmapRenderingContext",
"ImageData",
"MediaCapabilities",
"MessageChannel",
"MessageEvent",
"MessagePort",
"NetworkInformation",
"Notification",
"Path2D",
"Performance",
"PerformanceEntry",
"PerformanceMark",
"PerformanceMeasure",
"PerformanceObserver",
"PerformanceObserverEntryList",
"PerformanceResourceTiming",
"PerformanceServerTiming",
"PermissionStatus",
"Permissions",
"ProgressEvent",
"PromiseRejectionEvent",
"PushManager",
"PushSubscription",
"PushSubscriptionOptions",
"ReadableStream",
"ReadableStreamDefaultController",
"ReadableStreamDefaultReader",
"Request",
"Response",
"SecurityPolicyViolationEvent",
"ServiceWorker",
"ServiceWorkerContainer",
"ServiceWorkerRegistration",
"StorageManager",
"SubtleCrypto",
"TextDecoder",
"TextDecoderStream",
"TextEncoder",
"TextEncoderStream",
"TextMetrics",
"TransformStream",
"TransformStreamDefaultController",
"URL",
"URLSearchParams",
"WebGL2RenderingContext",
"WebGLActiveInfo",
"WebGLBuffer",
"WebGLContextEvent",
"WebGLFramebuffer",
"WebGLProgram",
"WebGLQuery",
"WebGLRenderbuffer",
"WebGLRenderingContext",
"WebGLSampler",
"WebGLShader",
"WebGLShaderPrecisionFormat",
"WebGLSync",
"WebGLTexture",
"WebGLTransformFeedback",
"WebGLUniformLocation",
"WebGLVertexArrayObject",
"WebSocket",
"Worker",
"WritableStream",
"WritableStreamDefaultController",
"WritableStreamDefaultWriter",
"XMLHttpRequest",
"XMLHttpRequestEventTarget",
"XMLHttpRequestUpload",
"console",
"WebAssembly",
"name",
"navigator",
"self",
"close",
"postMessage",
"dispatchEvent",
"cancelAnimationFrame",
"requestAnimationFrame",
"onerror",
"onlanguagechange",
"onmessage",
"onmessageerror",
"onoffline",
"ononline",
"onrejectionhandled",
"onunhandledrejection",
"caches",
"crossOriginIsolated",
"crypto",
"indexedDB",
"isSecureContext",
"origin",
"performance",
"atob",
"btoa",
"clearInterval",
"clearTimeout",
"createImageBitmap",
"fetch",
"queueMicrotask",
"setInterval",
"setTimeout",
"addEventListener",
"removeEventListener",
"Deno",
]
.iter()
.copied()
.collect()
});
fn extract_symbol<'a>(expr: &'a ast_view::MemberExpr) -> Option<&'a str> {
use deno_ast::view::{Expr, Lit, MemberProp, Tpl};
match &expr.prop {
MemberProp::Ident(ident) => Some(ident.sym()),
MemberProp::PrivateName(name) => Some(name.name()),
MemberProp::Computed(prop) => match &prop.expr {
Expr::Lit(Lit::Str(s)) => s.value().as_str(),
Expr::Ident(_) => None,
Expr::Tpl(Tpl { exprs, quasis, .. })
if exprs.is_empty() && quasis.len() == 1 =>
{
Some(quasis[0].raw())
}
_ => None,
},
}
}
struct NoWindowPrefixHandler;
impl Handler for NoWindowPrefixHandler {
fn member_expr(
&mut self,
member_expr: &ast_view::MemberExpr,
ctx: &mut Context,
) {
if member_expr.parent().is::<ast_view::MemberExpr>() {
return;
}
use deno_ast::view::Expr;
if_chain! {
if let Expr::Ident(obj_ident) = &member_expr.obj;
let obj_symbol = obj_ident.sym();
if obj_symbol == "window";
if ctx.scope().is_global(&obj_ident.inner.to_id());
if let Some(prop_symbol) = extract_symbol(member_expr);
if PROPERTY_DENY_LIST.contains(prop_symbol);
then {
ctx.add_diagnostic_with_fixes(
member_expr.range(),
CODE,
MESSAGE,
Some(HINT.into()),
vec![LintFix {
description: FIX_DESC.into(),
changes: vec![LintFixChange {
new_text: "globalThis".into(),
range: obj_ident.range(),
}],
}]
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_window_prefix_valid() {
assert_lint_ok! {
NoWindowPrefix,
"fetch();",
"self.fetch();",
"globalThis.fetch();",
"Deno.metrics();",
"self.Deno.metrics();",
"globalThis.Deno.metrics();",
"onload();",
"self.onload();",
"globalThis.onload();",
"window.onload();",
r#"window["onload"]();"#,
r#"window[`onload`]();"#,
"onunload();",
"self.onunload();",
"globalThis.onunload();",
"window.onunload();",
r#"window["onunload"]();"#,
r#"window[`onunload`]();"#,
"closed;",
"self.closed;",
"globalThis.closed;",
"window.closed;",
r#"window["closed"];"#,
r#"window[`closed`];"#,
"alert();",
"self.alert();",
"globalThis.alert();",
"window.alert();",
r#"window["alert"]();"#,
r#"window[`alert`]();"#,
"confirm();",
"self.confirm();",
"globalThis.confirm();",
"window.confirm();",
r#"window["confirm"]();"#,
r#"window[`confirm`]();"#,
"prompt();",
"self.prompt();",
"globalThis.prompt();",
"window.prompt();",
r#"window["prompt"]();"#,
r#"window[`prompt`]();"#,
"localStorage;",
"self.localStorage;",
"globalThis.localStorage;",
"window.localStorage;",
r#"window["localStorage"];"#,
r#"window[`localStorage`];"#,
"sessionStorage;",
"self.sessionStorage;",
"globalThis.sessionStorage;",
"window.sessionStorage;",
r#"window["sessionStorage"];"#,
r#"window[`sessionStorage`];"#,
"window;",
"self.window;",
"globalThis.window;",
"window.window;",
r#"window["window"];"#,
r#"window[`window`];"#,
"Navigator;",
"self.Navigator;",
"globalThis.Navigator;",
"window.Navigator;",
r#"window["Navigator"];"#,
r#"window[`Navigator`];"#,
"location;",
"self.location;",
"globalThis.location;",
"window.location;",
r#"window["location"];"#,
r#"window[`location`];"#,
"history;",
"self.history;",
"globalThis.history;",
"window.history;",
r#"window["history"];"#,
r#"window[`history`];"#,
"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`]();"#,
r#"const f = "fetch"; window[f]();"#,
r#"const f = "fetch"; window[`${f}`]();"#,
r#"foo.window.fetch();"#,
};
}
#[test]
fn no_window_prefix_invalid() {
assert_lint_err! {
NoWindowPrefix,
MESSAGE,
HINT,
r#"window.fetch()"#: [
{
col: 0,
fix: (FIX_DESC, "globalThis.fetch()"),
}
],
r#"window["fetch"]()"#: [
{
col: 0,
fix: (FIX_DESC, r#"globalThis["fetch"]()"#),
}
],
r#"window[`fetch`]()"#: [
{
col: 0,
fix: (FIX_DESC, "globalThis[`fetch`]()"),
}
],
"
function foo() {
const window = 42;
return window;
}
window.fetch();": [
{
col: 0,
line: 6,
fix: (FIX_DESC, "
function foo() {
const window = 42;
return window;
}
globalThis.fetch();"),
}
],
};
}
}