Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod import {
// pub mod no_deprecated;
pub mod max_dependencies;
pub mod no_duplicates;
pub mod no_dynamic_require;
pub mod no_named_as_default;
pub mod no_named_as_default_member;
pub mod no_self_import;
Expand Down Expand Up @@ -791,6 +792,7 @@ oxc_macros::declare_all_lint_rules! {
import::no_named_as_default_member,
import::no_self_import,
// import::no_unused_modules,
import::no_dynamic_require,
import::no_duplicates,
import::no_default_export,
import::no_webpack_loader_syntax,
Expand Down
132 changes: 132 additions & 0 deletions crates/oxc_linter/src/rules/import/no_dynamic_require.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use oxc_ast::{ast::Expression, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};

use crate::{context::LintContext, rule::Rule, AstNode};

fn no_dnyamic_require_diagnostic(span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("Expected a literal string or immutable template literal")
.with_help("Replace the argument with a literal string or immutable template literal")
.with_label(span)
}

#[derive(Debug, Default, Clone)]
pub struct NoDynamicRequire {
esmodule: bool,
}

declare_oxc_lint!(
/// ### What it does
///
/// Forbid imports which use an expression for the module argument.
///
/// ### Why is this bad?
///
/// Import statements which use an expression resolved at runtime makes it to find where the
/// import comes from and some static code analysis tools might not be able to resolve them.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```javascript
/// require(name);
/// require(`../${name}`);
/// ```
///
/// Examples of **correct** code for this rule:
/// ```javascript
/// require('../name');
/// require(`../name`);
/// ```
NoDynamicRequire,
restriction,
);

impl Rule for NoDynamicRequire {
fn from_configuration(value: serde_json::Value) -> Self {
let esmodule = value
.get(0)
.and_then(|config| config.get("esmodule"))
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);

Self { esmodule }
}

fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::ImportExpression(import) => {
if self.esmodule && !is_static_value(&import.source) {
ctx.diagnostic(no_dnyamic_require_diagnostic(import.source.span()));
}
}
AstKind::CallExpression(call) => {
if call.arguments.is_empty() {
return;
}

if !call.callee.is_specific_id("require") {
return;
}

let Some(expr) = &call.arguments[0].as_expression() else {
return;
};

if !is_static_value(expr) {
ctx.diagnostic(no_dnyamic_require_diagnostic(call.callee.span()));
}
}
_ => {}
};
}
}

fn is_static_value(expr: &Expression) -> bool {
expr.is_string_literal() && expr.is_immutable_value()
}

#[test]
fn test() {
use crate::tester::Tester;
use serde_json::json;

let pass = vec![
(r#"import _ from "lodash""#, None),
(r#"require("foo")"#, None),
("require(`foo`)", None),
(r#"require("./foo")"#, None),
(r#"require("@scope/foo")"#, None),
("require()", None),
(r#"require("./foo", "bar" + "okay")"#, None),
(r#"var foo = require("foo")"#, None),
("var foo = require(`foo`)", None),
(r#"var foo = require("./foo")"#, None),
(r#"var foo = require("@scope/foo")"#, None),
(r#"import("foo")"#, Some(json!([{ "esmodule": true }]))),
("import(`foo`)", Some(json!([{ "esmodule": true }]))),
(r#"import("./foo")"#, Some(json!([{ "esmodule": true }]))),
(r#"import("@scope/foo")"#, Some(json!([{ "esmodule": true }]))),
(r#"var foo = import("foo")"#, Some(json!([{ "esmodule": true }]))),
("var foo = import(`foo`)", Some(json!([{ "esmodule": true }]))),
(r#"var foo = import("./foo")"#, Some(json!([{ "esmodule": true }]))),
(r#"var foo = import("@scope/foo")"#, Some(json!([{ "esmodule": true }]))),
];

let fail = vec![
(r#"require("../" + name)"#, None),
("require(`../${name}`)", None),
("require(name)", None),
("require(name())", None),
("require(`foo${x}`)", None),
("var foo = require(`foo${x}`)", None),
(r#"require(name + "foo", "bar")"#, Some(json!([{ "esmodule": true }]))),
(r#"import("../" + "name")"#, Some(json!([{ "esmodule": true }]))),
("import(`../${name}`)", Some(json!([{ "esmodule": true }]))),
("import(name)", Some(json!([{ "esmodule": true }]))),
("import(name())", Some(json!([{ "esmodule": true }]))),
];

Tester::new(NoDynamicRequire::NAME, pass, fail).test_and_snapshot();
}
79 changes: 79 additions & 0 deletions crates/oxc_linter/src/snapshots/no_dynamic_require.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
source: crates/oxc_linter/src/tester.rs
---
⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require("../" + name)
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require(`../${name}`)
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require(name)
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require(name())
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require(`foo${x}`)
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:11]
1 │ var foo = require(`foo${x}`)
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:1]
1 │ require(name + "foo", "bar")
· ───────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:8]
1 │ import("../" + "name")
· ──────────────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:8]
1 │ import(`../${name}`)
· ────────────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:8]
1 │ import(name)
· ────
╰────
help: Replace the argument with a literal string or immutable template literal

⚠ eslint-plugin-import(no-dynamic-require): Expected a literal string or immutable template literal
╭─[no_dynamic_require.tsx:1:8]
1 │ import(name())
· ──────
╰────
help: Replace the argument with a literal string or immutable template literal