diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 1e6491f01..4e9f211cf 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -64,6 +64,7 @@ oxc_macros::declare_all_lint_rules! { typescript::no_unnecessary_type_constraint, typescript::no_misused_new, typescript::no_this_alias, + typescript::no_var_requires, jest::no_disabled_tests, jest::no_test_prefixes, } diff --git a/crates/oxc_linter/src/rules/typescript/no_var_requires.rs b/crates/oxc_linter/src/rules/typescript/no_var_requires.rs new file mode 100644 index 000000000..9fbdba70b --- /dev/null +++ b/crates/oxc_linter/src/rules/typescript/no_var_requires.rs @@ -0,0 +1,113 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Error, Diagnostic)] +#[error( + "typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require(\"foo\") imports." +)] +#[diagnostic(severity(error))] +struct NoVarRequiresDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct NoVarRequires; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow `require` statements except in import statements + /// + /// ### Why is this bad? + /// + /// In other words, the use of forms such as var foo = require("foo") are banned. Instead use ES6 style imports or import foo = require("foo") imports. + /// + /// ```typescript + /// var foo = require('foo'); + /// const foo = require('foo'); + /// let foo = require('foo'); + /// ``` + NoVarRequires, + correctness +); + +impl Rule for NoVarRequires { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + if !ctx.source_type().is_typescript() { + return; + } + if let AstKind::CallExpression(expr) = node.kind() && expr.is_require_call() { + if ctx.scopes().get_bindings(node.scope_id()).contains_key("require") { + return; + } + + if let Some(parent_node) = ctx.nodes().parent_node(node.id()) { + if let AstKind::Argument(_) = parent_node.kind() { + if let Some(parent_node) = ctx.nodes().parent_node(parent_node.id()) { + if is_target_node(&parent_node.kind()) { + ctx.diagnostic(NoVarRequiresDiagnostic(expr.span)); + } + } + } + + if is_target_node(&parent_node.kind()) { + ctx.diagnostic(NoVarRequiresDiagnostic(expr.span)); + } + } + } + } +} + +fn is_target_node(node_kind: &AstKind<'_>) -> bool { + matches!(node_kind, AstKind::CallExpression(_) + | AstKind::MemberExpression(_) + | AstKind::NewExpression(_) + | AstKind::TSAsExpression(_) + | AstKind::TSTypeAssertion(_) + | AstKind::VariableDeclarator(_)) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "import foo = require('foo');", + "require('foo');", + "require?.('foo');", + r#" + import { createRequire } from 'module'; + const require = createRequire('foo'); + const json = require('./some.json'); + "#, + ]; + + let fail = vec![ + "var foo = require('foo');", + "const foo = require('foo');", + "let foo = require('foo');", + "let foo = trick(require('foo'));", + "var foo = require?.('foo');", + "const foo = require?.('foo');", + "let foo = require?.('foo');", + "let foo = trick(require?.('foo'));", + "let foo = trick?.(require('foo'));", + "const foo = require('./foo.json') as Foo;", + + // Because of TypeScript disallows angle bracket type assertions in .tsx files, comment out this below case all tests parsing as tsx. + // "const foo = require('./foo.json');", + + "const foo: Foo = require('./foo.json').default;", + r#" + const configValidator = new Validator(require('./a.json')); + configValidator.addSchema(require('./a.json')); + "# + ]; + + Tester::new_without_config(NoVarRequires::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/no_var_requires.snap b/crates/oxc_linter/src/snapshots/no_var_requires.snap new file mode 100644 index 000000000..eaa5621ed --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_var_requires.snap @@ -0,0 +1,87 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_var_requires +--- + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ var foo = require('foo'); + · ────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ const foo = require('foo'); + · ────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ let foo = require('foo'); + · ────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ let foo = trick(require('foo')); + · ────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ var foo = require?.('foo'); + · ──────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ const foo = require?.('foo'); + · ──────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ let foo = require?.('foo'); + · ──────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ let foo = trick(require?.('foo')); + · ──────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ let foo = trick?.(require('foo')); + · ────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ const foo = require('./foo.json') as Foo; + · ───────────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ const foo: Foo = require('./foo.json').default; + · ───────────────────── + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:1:1] + 1 │ + 2 │ const configValidator = new Validator(require('./a.json')); + · ─────────────────── + 3 │ configValidator.addSchema(require('./a.json')); + ╰──── + + × typescript-eslint(no-var-requires): Use ES6 style imports or import foo = require("foo") imports. + ╭─[no_var_requires.tsx:2:1] + 2 │ const configValidator = new Validator(require('./a.json')); + 3 │ configValidator.addSchema(require('./a.json')); + · ─────────────────── + 4 │ + ╰──── + +