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
5 changes: 5 additions & 0 deletions .changeset/violet-carpets-lie.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down Expand Up @@ -58,15 +58,8 @@ declare_lint_rule! {
}
}

declare_node_union! {
pub AnyGlobalDirnameFileName =
JsVariableDeclarator
| JsObjectExpression
| JsStaticMemberExpression
}

impl Rule for NoGlobalDirnameFilename {
type Query = Semantic<AnyGlobalDirnameFileName>;
type Query = Semantic<JsReferenceIdentifier>;
type State = (JsSyntaxToken, String);
type Signals = Option<Self::State>;
type Options = NoGlobalDirnameFilenameOptions;
Expand All @@ -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<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
Expand All @@ -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::<JsIdentifierExpression>() {
mutation.replace_node::<AnyJsExpression>(
expr.into(),
make_import_meta(dirname_or_filename).into(),
);
} else if let Some(member) = node.parent::<JsShorthandPropertyObjectMember>() {
mutation.replace_node::<AnyJsObjectMember>(
member.into(),
make_property_object_member(syntax_token, dirname_or_filename).into(),
)
} else {
return None;
}

Some(JsRuleAction::new(
Expand All @@ -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<String> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, string> = {};
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 = () => <div data-path={__dirname} />;

// Class properties
class FileLogger {
currentFile = __filename;
}
new FileLogger().currentFile = __filename;
Loading