diff --git a/crates/oxc_linter/src/generated/rule_runner_impls.rs b/crates/oxc_linter/src/generated/rule_runner_impls.rs index 8d5f4af123a7c..9a57bdf735b0e 100644 --- a/crates/oxc_linter/src/generated/rule_runner_impls.rs +++ b/crates/oxc_linter/src/generated/rule_runner_impls.rs @@ -4274,6 +4274,12 @@ impl RuleRunner for crate::rules::node::no_new_require::NoNewRequire { const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; } +impl RuleRunner for crate::rules::node::no_path_concat::NoPathConcat { + const NODE_TYPES: Option<&AstTypesBitset> = + Some(&AstTypesBitset::from_types(&[AstType::BinaryExpression, AstType::TemplateLiteral])); + const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Run; +} + impl RuleRunner for crate::rules::node::no_process_env::NoProcessEnv { const NODE_TYPES: Option<&AstTypesBitset> = Some(&AstTypesBitset::from_types(&[ AstType::ComputedMemberExpression, diff --git a/crates/oxc_linter/src/generated/rules_enum.rs b/crates/oxc_linter/src/generated/rules_enum.rs index 68dedf49b9486..9d20c26aac116 100644 --- a/crates/oxc_linter/src/generated/rules_enum.rs +++ b/crates/oxc_linter/src/generated/rules_enum.rs @@ -335,6 +335,7 @@ pub use crate::rules::nextjs::no_unwanted_polyfillio::NoUnwantedPolyfillio as Ne pub use crate::rules::node::global_require::GlobalRequire as NodeGlobalRequire; pub use crate::rules::node::no_exports_assign::NoExportsAssign as NodeNoExportsAssign; pub use crate::rules::node::no_new_require::NoNewRequire as NodeNoNewRequire; +pub use crate::rules::node::no_path_concat::NoPathConcat as NodeNoPathConcat; pub use crate::rules::node::no_process_env::NoProcessEnv as NodeNoProcessEnv; pub use crate::rules::oxc::approx_constant::ApproxConstant as OxcApproxConstant; pub use crate::rules::oxc::bad_array_method_on_arguments::BadArrayMethodOnArguments as OxcBadArrayMethodOnArguments; @@ -1379,6 +1380,7 @@ pub enum RuleEnum { NodeGlobalRequire(NodeGlobalRequire), NodeNoExportsAssign(NodeNoExportsAssign), NodeNoNewRequire(NodeNoNewRequire), + NodeNoPathConcat(NodeNoPathConcat), NodeNoProcessEnv(NodeNoProcessEnv), VueDefineEmitsDeclaration(VueDefineEmitsDeclaration), VueDefinePropsDeclaration(VueDefinePropsDeclaration), @@ -2152,7 +2154,8 @@ const VITEST_WARN_TODO_ID: usize = const NODE_GLOBAL_REQUIRE_ID: usize = VITEST_WARN_TODO_ID + 1usize; const NODE_NO_EXPORTS_ASSIGN_ID: usize = NODE_GLOBAL_REQUIRE_ID + 1usize; const NODE_NO_NEW_REQUIRE_ID: usize = NODE_NO_EXPORTS_ASSIGN_ID + 1usize; -const NODE_NO_PROCESS_ENV_ID: usize = NODE_NO_NEW_REQUIRE_ID + 1usize; +const NODE_NO_PATH_CONCAT_ID: usize = NODE_NO_NEW_REQUIRE_ID + 1usize; +const NODE_NO_PROCESS_ENV_ID: usize = NODE_NO_PATH_CONCAT_ID + 1usize; const VUE_DEFINE_EMITS_DECLARATION_ID: usize = NODE_NO_PROCESS_ENV_ID + 1usize; const VUE_DEFINE_PROPS_DECLARATION_ID: usize = VUE_DEFINE_EMITS_DECLARATION_ID + 1usize; const VUE_DEFINE_PROPS_DESTRUCTURING_ID: usize = VUE_DEFINE_PROPS_DECLARATION_ID + 1usize; @@ -2949,6 +2952,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NODE_GLOBAL_REQUIRE_ID, Self::NodeNoExportsAssign(_) => NODE_NO_EXPORTS_ASSIGN_ID, Self::NodeNoNewRequire(_) => NODE_NO_NEW_REQUIRE_ID, + Self::NodeNoPathConcat(_) => NODE_NO_PATH_CONCAT_ID, Self::NodeNoProcessEnv(_) => NODE_NO_PROCESS_ENV_ID, Self::VueDefineEmitsDeclaration(_) => VUE_DEFINE_EMITS_DECLARATION_ID, Self::VueDefinePropsDeclaration(_) => VUE_DEFINE_PROPS_DECLARATION_ID, @@ -3735,6 +3739,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::NAME, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::NAME, Self::NodeNoNewRequire(_) => NodeNoNewRequire::NAME, + Self::NodeNoPathConcat(_) => NodeNoPathConcat::NAME, Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::NAME, Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::NAME, Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::NAME, @@ -4565,6 +4570,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::CATEGORY, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::CATEGORY, Self::NodeNoNewRequire(_) => NodeNoNewRequire::CATEGORY, + Self::NodeNoPathConcat(_) => NodeNoPathConcat::CATEGORY, Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::CATEGORY, Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::CATEGORY, Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::CATEGORY, @@ -5354,6 +5360,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::FIX, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::FIX, Self::NodeNoNewRequire(_) => NodeNoNewRequire::FIX, + Self::NodeNoPathConcat(_) => NodeNoPathConcat::FIX, Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::FIX, Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::FIX, Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::FIX, @@ -6333,6 +6340,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::documentation(), Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::documentation(), Self::NodeNoNewRequire(_) => NodeNoNewRequire::documentation(), + Self::NodeNoPathConcat(_) => NodeNoPathConcat::documentation(), Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::documentation(), Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::documentation(), Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::documentation(), @@ -8261,6 +8269,8 @@ impl RuleEnum { .or_else(|| NodeNoExportsAssign::schema(generator)), Self::NodeNoNewRequire(_) => NodeNoNewRequire::config_schema(generator) .or_else(|| NodeNoNewRequire::schema(generator)), + Self::NodeNoPathConcat(_) => NodeNoPathConcat::config_schema(generator) + .or_else(|| NodeNoPathConcat::schema(generator)), Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::config_schema(generator) .or_else(|| NodeNoProcessEnv::schema(generator)), Self::VueDefineEmitsDeclaration(_) => { @@ -8988,6 +8998,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => "node", Self::NodeNoExportsAssign(_) => "node", Self::NodeNoNewRequire(_) => "node", + Self::NodeNoPathConcat(_) => "node", Self::NodeNoProcessEnv(_) => "node", Self::VueDefineEmitsDeclaration(_) => "vue", Self::VueDefinePropsDeclaration(_) => "vue", @@ -11165,6 +11176,9 @@ impl RuleEnum { Self::NodeNoNewRequire(_) => { Ok(Self::NodeNoNewRequire(NodeNoNewRequire::from_configuration(value)?)) } + Self::NodeNoPathConcat(_) => { + Ok(Self::NodeNoPathConcat(NodeNoPathConcat::from_configuration(value)?)) + } Self::NodeNoProcessEnv(_) => { Ok(Self::NodeNoProcessEnv(NodeNoProcessEnv::from_configuration(value)?)) } @@ -11897,6 +11911,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.to_configuration(), Self::NodeNoExportsAssign(rule) => rule.to_configuration(), Self::NodeNoNewRequire(rule) => rule.to_configuration(), + Self::NodeNoPathConcat(rule) => rule.to_configuration(), Self::NodeNoProcessEnv(rule) => rule.to_configuration(), Self::VueDefineEmitsDeclaration(rule) => rule.to_configuration(), Self::VueDefinePropsDeclaration(rule) => rule.to_configuration(), @@ -12589,6 +12604,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.run(node, ctx), Self::NodeNoExportsAssign(rule) => rule.run(node, ctx), Self::NodeNoNewRequire(rule) => rule.run(node, ctx), + Self::NodeNoPathConcat(rule) => rule.run(node, ctx), Self::NodeNoProcessEnv(rule) => rule.run(node, ctx), Self::VueDefineEmitsDeclaration(rule) => rule.run(node, ctx), Self::VueDefinePropsDeclaration(rule) => rule.run(node, ctx), @@ -13281,6 +13297,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.run_once(ctx), Self::NodeNoExportsAssign(rule) => rule.run_once(ctx), Self::NodeNoNewRequire(rule) => rule.run_once(ctx), + Self::NodeNoPathConcat(rule) => rule.run_once(ctx), Self::NodeNoProcessEnv(rule) => rule.run_once(ctx), Self::VueDefineEmitsDeclaration(rule) => rule.run_once(ctx), Self::VueDefinePropsDeclaration(rule) => rule.run_once(ctx), @@ -14071,6 +14088,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoExportsAssign(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoNewRequire(rule) => rule.run_on_jest_node(jest_node, ctx), + Self::NodeNoPathConcat(rule) => rule.run_on_jest_node(jest_node, ctx), Self::NodeNoProcessEnv(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VueDefineEmitsDeclaration(rule) => rule.run_on_jest_node(jest_node, ctx), Self::VueDefinePropsDeclaration(rule) => rule.run_on_jest_node(jest_node, ctx), @@ -14763,6 +14781,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.should_run(ctx), Self::NodeNoExportsAssign(rule) => rule.should_run(ctx), Self::NodeNoNewRequire(rule) => rule.should_run(ctx), + Self::NodeNoPathConcat(rule) => rule.should_run(ctx), Self::NodeNoProcessEnv(rule) => rule.should_run(ctx), Self::VueDefineEmitsDeclaration(rule) => rule.should_run(ctx), Self::VueDefinePropsDeclaration(rule) => rule.should_run(ctx), @@ -15741,6 +15760,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::IS_TSGOLINT_RULE, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::IS_TSGOLINT_RULE, Self::NodeNoNewRequire(_) => NodeNoNewRequire::IS_TSGOLINT_RULE, + Self::NodeNoPathConcat(_) => NodeNoPathConcat::IS_TSGOLINT_RULE, Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::IS_TSGOLINT_RULE, Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::IS_TSGOLINT_RULE, Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::IS_TSGOLINT_RULE, @@ -16600,6 +16620,7 @@ impl RuleEnum { Self::NodeGlobalRequire(_) => NodeGlobalRequire::HAS_CONFIG, Self::NodeNoExportsAssign(_) => NodeNoExportsAssign::HAS_CONFIG, Self::NodeNoNewRequire(_) => NodeNoNewRequire::HAS_CONFIG, + Self::NodeNoPathConcat(_) => NodeNoPathConcat::HAS_CONFIG, Self::NodeNoProcessEnv(_) => NodeNoProcessEnv::HAS_CONFIG, Self::VueDefineEmitsDeclaration(_) => VueDefineEmitsDeclaration::HAS_CONFIG, Self::VueDefinePropsDeclaration(_) => VueDefinePropsDeclaration::HAS_CONFIG, @@ -17294,6 +17315,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.types_info(), Self::NodeNoExportsAssign(rule) => rule.types_info(), Self::NodeNoNewRequire(rule) => rule.types_info(), + Self::NodeNoPathConcat(rule) => rule.types_info(), Self::NodeNoProcessEnv(rule) => rule.types_info(), Self::VueDefineEmitsDeclaration(rule) => rule.types_info(), Self::VueDefinePropsDeclaration(rule) => rule.types_info(), @@ -17986,6 +18008,7 @@ impl RuleEnum { Self::NodeGlobalRequire(rule) => rule.run_info(), Self::NodeNoExportsAssign(rule) => rule.run_info(), Self::NodeNoNewRequire(rule) => rule.run_info(), + Self::NodeNoPathConcat(rule) => rule.run_info(), Self::NodeNoProcessEnv(rule) => rule.run_info(), Self::VueDefineEmitsDeclaration(rule) => rule.run_info(), Self::VueDefinePropsDeclaration(rule) => rule.run_info(), @@ -18794,6 +18817,7 @@ pub static RULES: std::sync::LazyLock> = std::sync::LazyLock::new( RuleEnum::NodeGlobalRequire(NodeGlobalRequire::default()), RuleEnum::NodeNoExportsAssign(NodeNoExportsAssign::default()), RuleEnum::NodeNoNewRequire(NodeNoNewRequire::default()), + RuleEnum::NodeNoPathConcat(NodeNoPathConcat::default()), RuleEnum::NodeNoProcessEnv(NodeNoProcessEnv::default()), RuleEnum::VueDefineEmitsDeclaration(VueDefineEmitsDeclaration::default()), RuleEnum::VueDefinePropsDeclaration(VueDefinePropsDeclaration::default()), diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index b4d26284e874b..1a21622f52165 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -715,6 +715,7 @@ pub(crate) mod node { pub mod global_require; pub mod no_exports_assign; pub mod no_new_require; + pub mod no_path_concat; pub mod no_process_env; } diff --git a/crates/oxc_linter/src/rules/node/no_path_concat.rs b/crates/oxc_linter/src/rules/node/no_path_concat.rs new file mode 100644 index 0000000000000..0032083e00a2c --- /dev/null +++ b/crates/oxc_linter/src/rules/node/no_path_concat.rs @@ -0,0 +1,185 @@ +use oxc_ast::{ + AstKind, + ast::{Expression, TemplateLiteral}, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_semantic::IsGlobalReference; +use oxc_span::{Ident, Span}; +use oxc_syntax::operator::BinaryOperator; + +use crate::{AstNode, context::LintContext, rule::Rule}; + +fn no_path_concat_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Use `path.join()` or `path.resolve()` instead of string concatenation") + .with_help("Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`.") + .with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct NoPathConcat; + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallows string concatenation with `__dirname` and `__filename`. + /// + /// ### Why is this bad? + /// + /// In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. + /// Sometimes, developers try to use these variables to create paths to other files, such as: + /// + /// ```js + /// var fullPath = __dirname + "/foo.js"; + /// ``` + /// + /// However, this is error-prone because it doesn't account for different + /// operating systems, which use different path separators. Using `path.join()` + /// or `path.resolve()` is the proper way to create cross-platform file paths. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// const fullPath1 = __dirname + "/foo.js"; + /// const fullPath2 = __filename + "/foo.js"; + /// const fullPath3 = `${__dirname}/foo.js`; + /// const fullPath4 = `${__filename}/foo.js`; + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// const fullPath1 = path.join(__dirname, "foo.js"); + /// const fullPath2 = path.join(__filename, "foo.js"); + /// const fullPath3 = __dirname + ".js"; + /// const fullPath4 = __filename + ".map"; + /// const fullPath5 = `${__dirname}_foo.js`; + /// const fullPath6 = `${__filename}.test.js`; + /// ``` + NoPathConcat, + node, + restriction +); + +impl Rule for NoPathConcat { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::BinaryExpression(bin_expr) => { + if bin_expr.operator != BinaryOperator::Addition { + return; + } + + if is_dirname_or_filename(&bin_expr.left, ctx) + && starts_with_path_separator(&bin_expr.right) + { + ctx.diagnostic(no_path_concat_diagnostic(bin_expr.span)); + } + } + AstKind::TemplateLiteral(temp_lit) => { + for (i, expr) in temp_lit.expressions.iter().enumerate() { + if is_dirname_or_filename(expr, ctx) + && template_element_starts_with_path_separator(temp_lit, i + 1) + { + ctx.diagnostic(no_path_concat_diagnostic(temp_lit.span)); + } + } + } + _ => {} + } + } +} + +fn is_path_sep(expr: &Expression) -> bool { + expr.is_specific_member_access("path", "sep") +} + +fn is_path_separator(c: char) -> bool { + c == '/' || c == '\\' +} + +fn is_dirname_or_filename(expr: &Expression, ctx: &LintContext) -> bool { + let Expression::Identifier(ident) = expr else { + return false; + }; + ident.is_global_reference_name(Ident::new_const("__dirname"), ctx.scoping()) + || ident.is_global_reference_name(Ident::new_const("__filename"), ctx.scoping()) +} + +fn starts_with_path_separator(expr: &Expression) -> bool { + match expr { + Expression::StringLiteral(s) => s.value.chars().next().is_some_and(is_path_separator), + Expression::TemplateLiteral(temp_lit) => { + template_element_starts_with_path_separator(temp_lit, 0) + } + Expression::BinaryExpression(bin) if bin.operator == BinaryOperator::Addition => { + starts_with_path_separator(&bin.left) + } + Expression::ConditionalExpression(cond) => { + starts_with_path_separator(&cond.consequent) + || starts_with_path_separator(&cond.alternate) + } + Expression::LogicalExpression(logical) => { + starts_with_path_separator(&logical.left) || starts_with_path_separator(&logical.right) + } + Expression::AssignmentExpression(assign) => starts_with_path_separator(&assign.right), + Expression::SequenceExpression(seq) => { + seq.expressions.last().is_some_and(|last| starts_with_path_separator(last)) + } + Expression::ParenthesizedExpression(paren) => starts_with_path_separator(&paren.expression), + _ => is_path_sep(expr), + } +} + +fn template_element_starts_with_path_separator(temp_lit: &TemplateLiteral, i: usize) -> bool { + let Some(quasi) = temp_lit.quasis.get(i) else { + return false; + }; + + if let Some(c) = quasi.value.cooked.as_ref().and_then(|cooked| cooked.chars().next()) + && is_path_separator(c) + { + return true; + } + + temp_lit.expressions.get(i).is_some_and(|expr| starts_with_path_separator(expr)) +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r#"var fullPath = dirname + "foo.js";"#, + r#"var fullPath = __dirname == "foo.js";"#, + "if (fullPath === __dirname) {}", + "if (__dirname === fullPath) {}", + r#"var fullPath = "/foo.js" + __filename;"#, + r#"var fullPath = "/foo.js" + __dirname;"#, + r#"var fullPath = __filename + ".map";"#, + "var fullPath = `${__filename}.map`;", + r#"var fullPath = __filename + (test ? ".js" : ".ts");"#, + r#"var fullPath = __filename + (ext || ".js");"#, + r"var fullPath = `${__dirname}\nfoo.js`;", + ]; + + let fail = vec![ + r#"var fullPath = __dirname + "/foo.js";"#, + r#"var fullPath = __filename + "/foo.js";"#, + "var fullPath = `${__dirname}/foo.js`;", + "var fullPath = `${__filename}/foo.js`;", + r#"var path = require("path"); var fullPath = `${__dirname}${path.sep}foo.js`;"#, + r#"var path = require("path"); var fullPath = `${__filename}${path.sep}foo.js`;"#, + r#"var path = require("path"); var fullPath = __dirname + path.sep + `foo.js`;"#, + r#"var fullPath = __dirname + "/" + "foo.js";"#, + r#"var fullPath = __dirname + ("/" + "foo.js");"#, + r#"var fullPath = __dirname + (test ? "/foo.js" : "/bar.js");"#, + r#"var fullPath = __dirname + (extraPath || "/default.js");"#, + r#"var fullPath = __dirname + "\\foo.js";"#, + r#"var fullPath = __dirname + "\\${path.sep}foo.js";"#, + r#"var fullPath = __filename + "\\${path.sep}foo.js";"#, + r"var fullPath = `${__dirname}\\${path.sep}foo.js`;", + r"var fullPath = `${__filename}\\${path.sep}foo.js`;", + ]; + + Tester::new(NoPathConcat::NAME, NoPathConcat::PLUGIN, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/node_no_path_concat.snap b/crates/oxc_linter/src/snapshots/node_no_path_concat.snap new file mode 100644 index 0000000000000..4b04efc1d0095 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/node_no_path_concat.snap @@ -0,0 +1,115 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + "/foo.js"; + · ───────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __filename + "/foo.js"; + · ────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = `${__dirname}/foo.js`; + · ───────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = `${__filename}/foo.js`; + · ────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:44] + 1 │ var path = require("path"); var fullPath = `${__dirname}${path.sep}foo.js`; + · ─────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:44] + 1 │ var path = require("path"); var fullPath = `${__filename}${path.sep}foo.js`; + · ──────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:44] + 1 │ var path = require("path"); var fullPath = __dirname + path.sep + `foo.js`; + · ──────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + "/" + "foo.js"; + · ─────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + ("/" + "foo.js"); + · ──────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + (test ? "/foo.js" : "/bar.js"); + · ────────────────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + (extraPath || "/default.js"); + · ──────────────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + "\\foo.js"; + · ────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __dirname + "\\${path.sep}foo.js"; + · ───────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = __filename + "\\${path.sep}foo.js"; + · ────────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = `${__dirname}\\${path.sep}foo.js`; + · ───────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`. + + ⚠ eslint-plugin-node(no-path-concat): Use `path.join()` or `path.resolve()` instead of string concatenation + ╭─[no_path_concat.tsx:1:16] + 1 │ var fullPath = `${__filename}\\${path.sep}foo.js`; + · ────────────────────────────────── + ╰──── + help: Replace string concatenation of `__dirname` or `__filename` with `path.join()` or `path.resolve()`.