diff --git a/.changeset/violet-carpets-lie.md b/.changeset/violet-carpets-lie.md new file mode 100644 index 000000000000..7e897137561b --- /dev/null +++ b/.changeset/violet-carpets-lie.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8829](https://github.com/biomejs/biome/issues/8829): Revamped the [`noGlobalDirnameFilename`](https://biomejs.dev/linter/rules/no-global-dirname-filename/) rule to catch many false negatives that have not been reported. diff --git a/crates/biome_js_analyze/src/lint/correctness/no_global_dirname_filename.rs b/crates/biome_js_analyze/src/lint/correctness/no_global_dirname_filename.rs index c9d147dca36d..6a5419119b28 100644 --- a/crates/biome_js_analyze/src/lint/correctness/no_global_dirname_filename.rs +++ b/crates/biome_js_analyze/src/lint/correctness/no_global_dirname_filename.rs @@ -6,11 +6,11 @@ use biome_console::markup; use biome_js_factory::make; use biome_js_semantic::SemanticModel; use biome_js_syntax::{ - AnyJsExpression, AnyJsName, AnyJsObjectMember, JsFileSource, JsObjectExpression, - JsPropertyObjectMember, JsStaticMemberExpression, JsSyntaxKind, JsSyntaxToken, - JsVariableDeclarator, global_identifier, + AnyJsExpression, AnyJsName, AnyJsObjectMember, JsFileSource, JsIdentifierExpression, + JsPropertyObjectMember, JsReferenceIdentifier, JsShorthandPropertyObjectMember, + JsStaticMemberExpression, JsSyntaxKind, JsSyntaxToken, }; -use biome_rowan::{AstSeparatedList, BatchMutationExt, TriviaPieceKind, declare_node_union}; +use biome_rowan::{AstNode, BatchMutationExt, TriviaPieceKind}; use biome_rule_options::no_global_dirname_filename::NoGlobalDirnameFilenameOptions; declare_lint_rule! { @@ -58,15 +58,8 @@ declare_lint_rule! { } } -declare_node_union! { - pub AnyGlobalDirnameFileName = - JsVariableDeclarator - | JsObjectExpression - | JsStaticMemberExpression -} - impl Rule for NoGlobalDirnameFilename { - type Query = Semantic; + type Query = Semantic; type State = (JsSyntaxToken, String); type Signals = Option; type Options = NoGlobalDirnameFilenameOptions; @@ -79,39 +72,7 @@ impl Rule for NoGlobalDirnameFilename { return None; }; - match node { - // const dirname = __dirname; - AnyGlobalDirnameFileName::JsVariableDeclarator(declarator) => { - let init = declarator.initializer()?; - let expr = init.expression().ok()?; - validate_dirname_filename(&expr, model) - } - // `if (__dirname.startsWith("/project/src"))` - AnyGlobalDirnameFileName::JsStaticMemberExpression(member_expr) => { - let expr = member_expr.object().ok()?; - let expr = expr.as_js_identifier_expression()?; - let expr = AnyJsExpression::JsIdentifierExpression(expr.clone()); - validate_dirname_filename(&expr, model) - } - // const dirname = { __dirname }; - AnyGlobalDirnameFileName::JsObjectExpression(object_expr) => { - for member in object_expr.members().iter().flatten() { - match member { - AnyJsObjectMember::JsPropertyObjectMember(member) => { - let expr = member.value().ok()?; - return validate_dirname_filename(&expr, model); - } - AnyJsObjectMember::JsShorthandPropertyObjectMember(member) => { - let token = member.name().and_then(|name| name.value_token()).ok()?; - let text = maybe_text(&token)?; - return Some((token, text)); - } - _ => {} - } - } - None - } - } + validate_dirname_filename(node, model) } fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { @@ -136,59 +97,18 @@ impl Rule for NoGlobalDirnameFilename { let syntax_token = &state.0; let dirname_or_filename = state.1.as_str(); - match node { - AnyGlobalDirnameFileName::JsVariableDeclarator(declarator) => { - mutation.replace_node( - declarator.initializer()?.expression().ok()?, - AnyJsExpression::JsStaticMemberExpression(make_import_meta( - dirname_or_filename, - )), - ); - } - AnyGlobalDirnameFileName::JsObjectExpression(object_expr) => { - for member in object_expr.members().iter().flatten() { - match member { - AnyJsObjectMember::JsPropertyObjectMember(member) => { - let expr = member.value().ok()?; - let expr = expr.as_js_identifier_expression()?; - let id = expr.name().ok()?.value_token().ok()?; - if &id == syntax_token { - let key = member.name().ok()?; - let key = key.as_js_literal_member_name()?; - let property_member = make_property_object_member( - &key.value().ok()?, - dirname_or_filename, - ); - mutation.replace_node(member.clone(), property_member); - break; - }; - } - AnyJsObjectMember::JsShorthandPropertyObjectMember(member) => { - let key = member.name().ok()?.value_token().ok()?; - if &key == syntax_token { - let property_member = - make_property_object_member(&key, dirname_or_filename); - mutation.replace_node( - AnyJsObjectMember::JsShorthandPropertyObjectMember( - member.clone(), - ), - AnyJsObjectMember::JsPropertyObjectMember(property_member), - ); - break; - }; - } - _ => {} - } - } - } - AnyGlobalDirnameFileName::JsStaticMemberExpression(member_expr) => { - mutation.replace_node( - member_expr.object().ok()?, - AnyJsExpression::JsStaticMemberExpression(make_import_meta( - dirname_or_filename, - )), - ); - } + if let Some(expr) = node.parent::() { + mutation.replace_node::( + expr.into(), + make_import_meta(dirname_or_filename).into(), + ); + } else if let Some(member) = node.parent::() { + mutation.replace_node::( + member.into(), + make_property_object_member(syntax_token, dirname_or_filename).into(), + ) + } else { + return None; } Some(JsRuleAction::new( @@ -203,14 +123,19 @@ impl Rule for NoGlobalDirnameFilename { } fn validate_dirname_filename( - expr: &AnyJsExpression, + expr: &JsReferenceIdentifier, model: &SemanticModel, ) -> Option<(JsSyntaxToken, String)> { - let (reference, _name) = global_identifier(expr)?; - let token = reference.value_token().ok()?; - maybe_text(&token) - .filter(|_| model.binding(&reference).is_none()) - .map(|name| (token, name)) + match model.binding(expr) { + // Some binding exists, not global one + Some(_) => None, + // No binding exists, global one + None => { + let token = expr.value_token().ok()?; + let name = maybe_text(&token)?; + Some((token, name)) + } + } } fn maybe_text(token: &JsSyntaxToken) -> Option { diff --git a/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx b/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx new file mode 100644 index 000000000000..2672b64121ae --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx @@ -0,0 +1,42 @@ +/** + * `correctness/noGlobalDirnameFilename` does not trigger + * in any of the following scenarios + */ + +// String concatenation +const path1 = __dirname + "/data"; +const path2 = "/data" + __filename; + +// Template string literals +const command: string = `cat ${__filename}`; + +// Operators +const target = process.env.DEBUG && __filename; +const folder = __dirname || "./default"; +const dir: string = __dirname ?? ""; +const location = true ? __dirname : "unknown"; + +// Array initialization +const arr: string[] = [__filename]; + +// Computed property access +const cache: Record = {}; +const value = cache[__dirname]; + +// Function parameters +function logPath(path = __dirname) { + console.log(path); +} +const arrowLog = (f = __filename) => console.log(f); + +// Type Assertions +const fileAsAny = __filename as any; + +// JSX +const Component = () =>
; + +// Class properties +class FileLogger { + currentFile = __filename; +} +new FileLogger().currentFile = __filename; diff --git a/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx.snap b/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx.snap new file mode 100644 index 000000000000..518f6d428309 --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/correctness/noGlobalDirnameFilename/issue-8829.tsx.snap @@ -0,0 +1,432 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: issue-8829.tsx +--- +# Input +```tsx +/** + * `correctness/noGlobalDirnameFilename` does not trigger + * in any of the following scenarios + */ + +// String concatenation +const path1 = __dirname + "/data"; +const path2 = "/data" + __filename; + +// Template string literals +const command: string = `cat ${__filename}`; + +// Operators +const target = process.env.DEBUG && __filename; +const folder = __dirname || "./default"; +const dir: string = __dirname ?? ""; +const location = true ? __dirname : "unknown"; + +// Array initialization +const arr: string[] = [__filename]; + +// Computed property access +const cache: Record = {}; +const value = cache[__dirname]; + +// Function parameters +function logPath(path = __dirname) { + console.log(path); +} +const arrowLog = (f = __filename) => console.log(f); + +// Type Assertions +const fileAsAny = __filename as any; + +// JSX +const Component = () =>
; + +// Class properties +class FileLogger { + currentFile = __filename; +} +new FileLogger().currentFile = __filename; + +``` + +# Diagnostics +``` +issue-8829.tsx:7:15 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 6 │ // String concatenation + > 7 │ const path1 = __dirname + "/data"; + │ ^^^^^^^^^^ + 8 │ const path2 = "/data" + __filename; + 9 │ + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 5 5 │ + 6 6 │ // String concatenation + 7 │ - const·path1·=·__dirname·+·"/data"; + 7 │ + const·path1·=·import.meta.dirname·+·"/data"; + 8 8 │ const path2 = "/data" + __filename; + 9 9 │ + + +``` + +``` +issue-8829.tsx:8:25 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 6 │ // String concatenation + 7 │ const path1 = __dirname + "/data"; + > 8 │ const path2 = "/data" + __filename; + │ ^^^^^^^^^^ + 9 │ + 10 │ // Template string literals + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 6 6 │ // String concatenation + 7 7 │ const path1 = __dirname + "/data"; + 8 │ - const·path2·=·"/data"·+·__filename; + 8 │ + const·path2·=·"/data"·+·import.meta.filename; + 9 9 │ + 10 10 │ // Template string literals + + +``` + +``` +issue-8829.tsx:11:32 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 10 │ // Template string literals + > 11 │ const command: string = `cat ${__filename}`; + │ ^^^^^^^^^^ + 12 │ + 13 │ // Operators + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 9 9 │ + 10 10 │ // Template string literals + 11 │ - const·command:·string·=·`cat·${__filename}`; + 11 │ + const·command:·string·=·`cat·${import.meta.filename}`; + 12 12 │ + 13 13 │ // Operators + + +``` + +``` +issue-8829.tsx:14:37 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 13 │ // Operators + > 14 │ const target = process.env.DEBUG && __filename; + │ ^^^^^^^^^^ + 15 │ const folder = __dirname || "./default"; + 16 │ const dir: string = __dirname ?? ""; + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 12 12 │ + 13 13 │ // Operators + 14 │ - const·target·=·process.env.DEBUG·&&·__filename; + 14 │ + const·target·=·process.env.DEBUG·&&·import.meta.filename; + 15 15 │ const folder = __dirname || "./default"; + 16 16 │ const dir: string = __dirname ?? ""; + + +``` + +``` +issue-8829.tsx:15:16 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 13 │ // Operators + 14 │ const target = process.env.DEBUG && __filename; + > 15 │ const folder = __dirname || "./default"; + │ ^^^^^^^^^^ + 16 │ const dir: string = __dirname ?? ""; + 17 │ const location = true ? __dirname : "unknown"; + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 13 13 │ // Operators + 14 14 │ const target = process.env.DEBUG && __filename; + 15 │ - const·folder·=·__dirname·||·"./default"; + 15 │ + const·folder·=·import.meta.dirname·||·"./default"; + 16 16 │ const dir: string = __dirname ?? ""; + 17 17 │ const location = true ? __dirname : "unknown"; + + +``` + +``` +issue-8829.tsx:16:21 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 14 │ const target = process.env.DEBUG && __filename; + 15 │ const folder = __dirname || "./default"; + > 16 │ const dir: string = __dirname ?? ""; + │ ^^^^^^^^^^ + 17 │ const location = true ? __dirname : "unknown"; + 18 │ + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 14 14 │ const target = process.env.DEBUG && __filename; + 15 15 │ const folder = __dirname || "./default"; + 16 │ - const·dir:·string·=·__dirname·??·""; + 16 │ + const·dir:·string·=·import.meta.dirname·??·""; + 17 17 │ const location = true ? __dirname : "unknown"; + 18 18 │ + + +``` + +``` +issue-8829.tsx:17:25 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 15 │ const folder = __dirname || "./default"; + 16 │ const dir: string = __dirname ?? ""; + > 17 │ const location = true ? __dirname : "unknown"; + │ ^^^^^^^^^^ + 18 │ + 19 │ // Array initialization + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 15 15 │ const folder = __dirname || "./default"; + 16 16 │ const dir: string = __dirname ?? ""; + 17 │ - const·location·=·true·?·__dirname·:·"unknown"; + 17 │ + const·location·=·true·?·import.meta.dirname·:·"unknown"; + 18 18 │ + 19 19 │ // Array initialization + + +``` + +``` +issue-8829.tsx:20:24 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 19 │ // Array initialization + > 20 │ const arr: string[] = [__filename]; + │ ^^^^^^^^^^ + 21 │ + 22 │ // Computed property access + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 18 18 │ + 19 19 │ // Array initialization + 20 │ - const·arr:·string[]·=·[__filename]; + 20 │ + const·arr:·string[]·=·[import.meta.filename]; + 21 21 │ + 22 22 │ // Computed property access + + +``` + +``` +issue-8829.tsx:24:21 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 22 │ // Computed property access + 23 │ const cache: Record = {}; + > 24 │ const value = cache[__dirname]; + │ ^^^^^^^^^ + 25 │ + 26 │ // Function parameters + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 22 22 │ // Computed property access + 23 23 │ const cache: Record = {}; + 24 │ - const·value·=·cache[__dirname]; + 24 │ + const·value·=·cache[import.meta.dirname]; + 25 25 │ + 26 26 │ // Function parameters + + +``` + +``` +issue-8829.tsx:27:25 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 26 │ // Function parameters + > 27 │ function logPath(path = __dirname) { + │ ^^^^^^^^^ + 28 │ console.log(path); + 29 │ } + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 25 25 │ + 26 26 │ // Function parameters + 27 │ - function·logPath(path·=·__dirname)·{ + 27 │ + function·logPath(path·=·import.meta.dirname)·{ + 28 28 │ console.log(path); + 29 29 │ } + + +``` + +``` +issue-8829.tsx:30:23 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 28 │ console.log(path); + 29 │ } + > 30 │ const arrowLog = (f = __filename) => console.log(f); + │ ^^^^^^^^^^ + 31 │ + 32 │ // Type Assertions + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 28 28 │ console.log(path); + 29 29 │ } + 30 │ - const·arrowLog·=·(f·=·__filename)·=>·console.log(f); + 30 │ + const·arrowLog·=·(f·=·import.meta.filename)·=>·console.log(f); + 31 31 │ + 32 32 │ // Type Assertions + + +``` + +``` +issue-8829.tsx:33:19 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 32 │ // Type Assertions + > 33 │ const fileAsAny = __filename as any; + │ ^^^^^^^^^^^ + 34 │ + 35 │ // JSX + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 31 31 │ + 32 32 │ // Type Assertions + 33 │ - const·fileAsAny·=·__filename·as·any; + 33 │ + const·fileAsAny·=·import.meta.filename·as·any; + 34 34 │ + 35 35 │ // JSX + + +``` + +``` +issue-8829.tsx:36:41 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __dirname. + + 35 │ // JSX + > 36 │ const Component = () =>
; + │ ^^^^^^^^^ + 37 │ + 38 │ // Class properties + + i __dirname is not available in ES modules. + + i Safe fix: Replace __dirname with import.meta.dirname. + + 34 34 │ + 35 35 │ // JSX + 36 │ - const·Component·=·()·=>·; + 36 │ + const·Component·=·()·=>·; + 37 37 │ + 38 38 │ // Class properties + + +``` + +``` +issue-8829.tsx:40:16 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 38 │ // Class properties + 39 │ class FileLogger { + > 40 │ currentFile = __filename; + │ ^^^^^^^^^^ + 41 │ } + 42 │ new FileLogger().currentFile = __filename; + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 38 38 │ // Class properties + 39 39 │ class FileLogger { + 40 │ - → currentFile·=·__filename; + 40 │ + → currentFile·=·import.meta.filename; + 41 41 │ } + 42 42 │ new FileLogger().currentFile = __filename; + + +``` + +``` +issue-8829.tsx:42:32 lint/correctness/noGlobalDirnameFilename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Don't use __filename. + + 40 │ currentFile = __filename; + 41 │ } + > 42 │ new FileLogger().currentFile = __filename; + │ ^^^^^^^^^^ + 43 │ + + i __filename is not available in ES modules. + + i Safe fix: Replace __filename with import.meta.filename. + + 40 40 │ currentFile = __filename; + 41 41 │ } + 42 │ - new·FileLogger().currentFile·=·__filename; + 42 │ + new·FileLogger().currentFile·=·import.meta.filename; + 43 43 │ + + +```