mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(linter/import): add no-dynamic-require rule (#5389)
Rule Detail: [link](https://github.com/import-js/eslint-plugin-import/blob/v2.29.1/docs/rules/no-dynamic-require.md)
This commit is contained in:
parent
979c16c486
commit
a786acff41
3 changed files with 213 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
132
crates/oxc_linter/src/rules/import/no_dynamic_require.rs
Normal file
132
crates/oxc_linter/src/rules/import/no_dynamic_require.rs
Normal file
|
|
@ -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
crates/oxc_linter/src/snapshots/no_dynamic_require.snap
Normal file
79
crates/oxc_linter/src/snapshots/no_dynamic_require.snap
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue