Repository: biomejs/biome (language: rust)

Issue:
useFilenamingConvention fails for export
### Environment information

```bash
CLI:
  Version:                      1.9.3
  Color support:                true

Platform:
  CPU Architecture:             aarch64
  OS:                           macos

Environment:
  BIOME_LOG_PATH:               unset
  BIOME_LOG_PREFIX_NAME:        unset
  BIOME_CONFIG_PATH:            unset
  NO_COLOR:                     unset
  TERM:                         "xterm-256color"
  JS_RUNTIME_VERSION:           "v20.13.1"
  JS_RUNTIME_NAME:              "node"
  NODE_PACKAGE_MANAGER:         "yarn/4.5.0"

Biome Configuration:
  Status:                       Loaded successfully
  Formatter disabled:           false
  Linter disabled:              false
  Organize imports disabled:    true
  VCS disabled:                 false

Linter:
  JavaScript enabled:           true
  JSON enabled:                 true
  CSS enabled:                  true
  GraphQL enabled:              false
  Recommended:                  true
  All:                          false
  Enabled rules:
  performance/noDelete
  suspicious/noCatchAssign
  suspicious/noUnsafeNegation
  complexity/useLiteralKeys
  complexity/noMultipleSpacesInRegularExpressionLiterals
  a11y/useValidLang
  complexity/noUselessEmptyExport
  suspicious/useNamespaceKeyword
  suspicious/useValidTypeof
  a11y/useValidAriaRole
  correctness/noConstantCondition
  a11y/useAriaActivedescendantWithTabindex
  suspicious/noAssignInExpressions
  style/useDefaultParameterLast
  complexity/noEmptyTypeParameters
  correctness/noConstructorReturn
  style/useSelfClosingElements
  suspicious/noDuplicateParameters
  suspicious/noDuplicateSelectorsKeyframeBlock
  correctness/noUnknownProperty
  style/useTemplate
  correctness/noUnusedLabels
  complexity/noUselessTernary
  correctness/noUnreachableSuper
  suspicious/noCompareNegZero
  correctness/noSwitchDeclarations
  a11y/noAutofocus
  correctness/noUnsafeOptionalChaining
  correctness/noConstAssign
  suspicious/noControlCharactersInRegex
  complexity/noUselessTypeConstraint
  style/noVar
  suspicious/noDoubleEquals
  suspicious/noRedundantUseStrict
  style/useLiteralEnumMembers
  suspicious/noGlobalIsNan
  suspicious/noEmptyInterface
  suspicious/noConstEnum
  suspicious/noMisleadingCharacterClass
  correctness/noPrecisionLoss
  a11y/noLabelWithoutControl
  suspicious/noRedeclare
  correctness/noStringCaseMismatch
  correctness/noSetterReturn
  correctness/noInvalidConstructorSuper
  suspicious/noImplicitAnyLet
  suspicious/noFallthroughSwitchClause
  suspicious/noUnsafeDeclarationMerging
  correctness/noUnreachable
  a11y/useKeyWithClickEvents
  suspicious/noDuplicateObjectKeys
  complexity/noUselessThisAlias
  complexity/noThisInStatic
  complexity/useOptionalChain
  correctness/noInnerDeclarations
  suspicious/noDuplicateCase
  a11y/useValidAnchor
  complexity/useRegexLiterals
  correctness/noSelfAssign
  correctness/noInvalidBuiltinInstantiation
  style/noUselessElse
  style/useShorthandFunctionType
  suspicious/noShadowRestrictedNames
  correctness/noInvalidDirectionInLinearGradient
  suspicious/noImportantInKeyframe
  a11y/useMediaCaption
  complexity/noUselessLabel
  complexity/noUselessCatch
  correctness/noUnsafeFinally
  correctness/noNonoctalDecimalEscape
  style/useEnumInitializers
  a11y/useHtmlLang
  suspicious/noDuplicateTestHooks
  complexity/noStaticOnlyClass
  style/useWhile
  complexity/useArrowFunction
  style/noInferrableTypes
  a11y/noNoninteractiveTabindex
  complexity/useSimpleNumberKeys
  correctness/useYield
  a11y/noInteractiveElementToNoninteractiveRole
  style/useNumericLiterals
  correctness/noUnnecessaryContinue
  suspicious/noApproximativeNumericConstant
  suspicious/noImportAssign
  suspicious/noLabelVar
  correctness/noGlobalObjectCalls
  suspicious/useDefaultSwitchClauseLast
  a11y/useAltText
  correctness/noEmptyCharacterClassInRegex
  correctness/noUnknownUnit
  suspicious/noSparseArray
  a11y/useIframeTitle
  complexity/noBannedTypes
  a11y/noSvgWithoutTitle
  correctness/noVoidElementsWithChildren
  style/useAsConstAssertion
  correctness/useJsxKeyInIterable
  style/useExportType
  complexity/noUselessLoneBlockStatements
  style/noArguments
  suspicious/noMisleadingInstantiator
  suspicious/noDebugger
  a11y/useValidAriaValues
  correctness/noUnmatchableAnbSelector
  suspicious/noCommentText
  suspicious/noGlobalAssign
  suspicious/noDuplicateJsxProps
  suspicious/noThenProperty
  a11y/noPositiveTabindex
  correctness/noEmptyPattern
  complexity/noExcessiveNestedTestSuites
  security/noDangerouslySetInnerHtmlWithChildren
  a11y/useKeyWithMouseEvents
  suspicious/noExtraNonNullAssertion
  suspicious/noShorthandPropertyOverrides
  correctness/noRenderReturnValue
  security/noGlobalEval
  style/useConst
  suspicious/useGetterReturn
  a11y/noRedundantRoles
  complexity/useFlatMap
  correctness/useIsNan
  suspicious/noGlobalIsFinite
  suspicious/noSelfCompare
  suspicious/noAsyncPromiseExecutor
  suspicious/noDuplicateFontNames
  security/noDangerouslySetInnerHtml
  style/useNodejsImportProtocol
  a11y/noDistractingElements
  suspicious/noArrayIndexKey
  complexity/noWith
  suspicious/noDuplicateClassMembers
  complexity/noExtraBooleanCast
  performance/noAccumulatingSpread
  a11y/useValidAriaProps
  a11y/noRedundantAlt
  correctness/noChildrenProp
  correctness/noUnknownFunction
  correctness/noInvalidPositionAtImportRule
  suspicious/noConfusingLabels
  suspicious/noPrototypeBuiltins
  suspicious/noConfusingVoidType
  suspicious/noFocusedTests
  a11y/useButtonType
  a11y/noAriaUnsupportedElements
  correctness/noInvalidGridAreas
  style/useFilenamingConvention
  correctness/noFlatMapIdentity
  suspicious/noSuspiciousSemicolonInJsx
  a11y/noBlankTarget
  a11y/useHeadingContent
  correctness/useValidForDirection
  correctness/noVoidTypeReturn
  correctness/noInvalidUseBeforeDeclaration
  a11y/noAriaHiddenOnFocusable
  a11y/useGenericFontNames
  correctness/noUnknownMediaFeatureName
  a11y/useAnchorContent
  complexity/noUselessRename
  style/useNumberNamespace
  complexity/noUselessConstructor
  a11y/noAccessKey
  style/useExponentiationOperator
  style/noUnusedTemplateLiteral
  complexity/noUselessSwitchCase
  style/useSingleVarDeclarator
  suspicious/noExportsInTest
  a11y/noNoninteractiveElementToInteractiveRole
  style/noCommaOperator
  suspicious/noDuplicateAtImportRules
  suspicious/useIsArray
  a11y/noHeaderScope
  complexity/noUselessFragments
  suspicious/noMisrefactoredShorthandAssign
  complexity/noForEach
  suspicious/noClassAssign
  suspicious/noEmptyBlock
  suspicious/noFunctionAssign

Workspace:
  Open Documents:               0
```


### Rule name

useFilenamingConvention

### Playground link

Basically a renamed :

```js
const _Button = 1;

export {_Button as Button};
```
Repro: https://github.com/artola/biome-issue-4254

### Expected result

With this config should not error for a named export `export {_Button as Button};`

```text
        "useFilenamingConvention": {
          "level": "error",
          "options": {
            "strictCase": false,
            "requireAscii": true,
            "filenameCases": ["kebab-case", "export"]
          }
        },
```

### Code of Conduct

- [X] I agree to follow Biome's Code of Conduct


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e1af31d6cd5..a5c7d9c850c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -131,6 +131,27 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
 
   Contributed by @Conaclos
 
+- [useFilenamingConvention](https://biomejs.dev/linter/rules/use-filenaming-convention) now correctly handles renamed exports ([#4254](https://github.com/biomejs/biome/issues/4254)).
+
+  The rule allows the filename to be named as one of the exports of the module.
+  For instance, the file containing the following export can be named `Button`.
+
+  ```js
+  class Button {}
+  export { Button }
+  ```
+
+  The rule now correctly handles the renaming of an export.
+  For example, the file containing the following export can only be named `Button`.
+  Previously the rule expected the file to be named `A`.
+
+  ```js
+  class A {}
+  export { A as Button }
+  ```
+
+  Contributed by @Conaclos
+
 ### Parser
 
 #### Bug Fixes
diff --git a/Cargo.lock b/Cargo.lock
index f71699e99604..ef2708145843 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -836,6 +836,7 @@ dependencies = [
  "biome_rowan",
  "rust-lapper",
  "rustc-hash 1.1.0",
+ "smallvec",
 ]
 
 [[package]]
diff --git a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs
index 9bfafd14293b..0dd150df42a5 100644
--- a/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs
+++ b/crates/biome_js_analyze/src/lint/style/use_filenaming_convention.rs
@@ -4,7 +4,10 @@ use biome_analyze::{
 };
 use biome_console::markup;
 use biome_deserialize_macros::Deserializable;
-use biome_rowan::TextRange;
+use biome_js_syntax::{
+    binding_ext::AnyJsIdentifierBinding, AnyJsIdentifierUsage, JsExportNamedSpecifier,
+};
+use biome_rowan::{AstNode, TextRange};
 use biome_string_case::{Case, Cases};
 use rustc_hash::FxHashSet;
 use serde::{Deserialize, Serialize};
@@ -210,13 +213,20 @@ impl Rule for UseFilenamingConvention {
         }
         if options.filename_cases.0.contains(&FilenameCase::Export) {
             // If no exported binding has the file name, then reports the filename
-            let model = ctx.model();
-            model
-                .all_bindings()
-                .map(|binding| binding.tree())
-                .filter(|binding| model.is_exported(binding))
-                .filter_map(|exported_binding| exported_binding.name_token().ok())
-                .all(|exported_name_token| exported_name_token.text_trimmed() != name)
+            ctx.model()
+                .all_exported_bindings()
+                .all(|exported_binding| {
+                    exported_binding
+                        .exports()
+                        .filter_map(|export| match AnyJsIdentifierBinding::try_cast(export) {
+                            Ok(id) => id.name_token().ok(),
+                            Err(export) => match JsExportNamedSpecifier::cast(export.parent()?) {
+                                Some(specifier) => specifier.exported_name().ok()?.value().ok(),
+                                None => AnyJsIdentifierUsage::cast(export)?.value_token().ok(),
+                            },
+                        })
+                        .all(|exported_name_token| exported_name_token.text_trimmed() != name)
+                })
                 .then_some(FileNamingConventionState::Filename)
         } else {
             Some(FileNamingConventionState::Filename)
diff --git a/crates/biome_js_semantic/Cargo.toml b/crates/biome_js_semantic/Cargo.toml
index a7a4ed94c3e8..21f41f0988ef 100644
--- a/crates/biome_js_semantic/Cargo.toml
+++ b/crates/biome_js_semantic/Cargo.toml
@@ -15,6 +15,7 @@ biome_js_syntax = { workspace = true }
 biome_rowan     = { workspace = true }
 rust-lapper     = "1.1.0"
 rustc-hash      = { workspace = true }
+smallvec        = { workspace = true }
 
 [dev-dependencies]
 biome_console     = { path = "../biome_console" }
diff --git a/crates/biome_js_semantic/src/semantic_model/binding.rs b/crates/biome_js_semantic/src/semantic_model/binding.rs
index 4eecd0fe5ab0..54f3b33b87f2 100644
--- a/crates/biome_js_semantic/src/semantic_model/binding.rs
+++ b/crates/biome_js_semantic/src/semantic_model/binding.rs
@@ -6,6 +6,8 @@ use biome_js_syntax::{binding_ext::AnyJsIdentifierBinding, TextRange, TsTypePara
 pub(crate) struct SemanticModelBindingData {
     pub(crate) range: TextRange,
     pub(crate) references: Vec<SemanticModelReference>,
+    // We use a SmallVec because most of the time a binding is expected once.
+    pub(crate) export_by_start: smallvec::SmallVec<[TextSize; 4]>,
 }
 
 #[derive(Clone, Copy, Debug)]
@@ -117,6 +119,17 @@ impl Binding {
         std::iter::successors(first, Reference::find_next_write)
     }
 
+    /// Returns all exports of the binding.
+    /// The node kind is either an identifier binding (tehd eclaration is self exported)
+    /// or an identifier usage.
+    pub fn exports(&self) -> impl Iterator<Item = JsSyntaxNode> + '_ {
+        let binding = self.data.binding(self.id);
+        binding
+            .export_by_start
+            .iter()
+            .map(|export_start| self.data.binding_node_by_start[export_start].clone())
+    }
+
     pub fn is_imported(&self) -> bool {
         super::is_imported(self.syntax())
     }
diff --git a/crates/biome_js_semantic/src/semantic_model/builder.rs b/crates/biome_js_semantic/src/semantic_model/builder.rs
index 753232abab9f..df2aebde63b5 100644
--- a/crates/biome_js_semantic/src/semantic_model/builder.rs
+++ b/crates/biome_js_semantic/src/semantic_model/builder.rs
@@ -175,7 +175,8 @@ impl SemanticModelBuilder {
                 let binding_id = BindingId::new(self.bindings.len());
                 self.bindings.push(SemanticModelBindingData {
                     range,
-                    references: vec![],
+                    references: Vec::new(),
+                    export_by_start: smallvec::SmallVec::new(),
                 });
                 self.bindings_by_start.insert(range.start(), binding_id);
 
@@ -308,8 +309,15 @@ impl SemanticModelBuilder {
                         .push(SemanticModelUnresolvedReference { range }),
                 }
             }
-            Export { declaration_at, .. } => {
+            Export {
+                declaration_at,
+                range,
+            } => {
                 self.exported.insert(declaration_at);
+
+                let binding_id = self.bindings_by_start[&declaration_at];
+                let binding = &mut self.bindings[binding_id.index()];
+                binding.export_by_start.push(range.start());
             }
         }
     }
diff --git a/crates/biome_js_semantic/src/semantic_model/model.rs b/crates/biome_js_semantic/src/semantic_model/model.rs
index 97e34d49bb11..ff0169c9d5bc 100644
--- a/crates/biome_js_semantic/src/semantic_model/model.rs
+++ b/crates/biome_js_semantic/src/semantic_model/model.rs
@@ -251,6 +251,17 @@ impl SemanticModel {
             })
     }
 
+    pub fn all_exported_bindings(&self) -> impl Iterator<Item = Binding> + '_ {
+        self.data
+            .exported
+            .iter()
+            .filter_map(|declared_at| self.data.bindings_by_start.get(declared_at).copied())
+            .map(|id| Binding {
+                data: self.data.clone(),
+                id,
+            })
+    }
+
     /// Returns the [Binding] of a reference.
     /// Can also be called from "binding" extension method.
     ///


Test patch:
diff --git a/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js
new file mode 100644
index 000000000000..2ead5ec742db
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js
@@ -0,0 +1,2 @@
+class Invalid_Renamed_Export {}
+export { Invalid_Renamed_Export as A }
\ No newline at end of file
diff --git a/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js.snap b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js.snap
new file mode 100644
index 000000000000..14c8e6a8951c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Invalid_Renamed_Export.js.snap
@@ -0,0 +1,23 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: Invalid_Renamed_Export.js
+---
+# Input
+```jsx
+class Invalid_Renamed_Export {}
+export { Invalid_Renamed_Export as A }
+```
+
+# Diagnostics
+```
+Invalid_Renamed_Export.js lint/style/useFilenamingConvention ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  ! The filename should be in camelCase or kebab-case or snake_case or equal to the name of an export.
+  
+  i The filename could be renamed to one of the following names:
+    invalid-renamed-export.js
+    invalidRenamedExport.js
+    invalid_renamed_export.js
+  
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js
new file mode 100644
index 000000000000..e4e7855185d3
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js
@@ -0,0 +1,2 @@
+class A {}
+export { A as Valid_Renamed_Export }
\ No newline at end of file
diff --git a/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js.snap b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js.snap
new file mode 100644
index 000000000000..aee2c63f182c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.js.snap
@@ -0,0 +1,9 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: Valid_Renamed_Export.js
+---
+# Input
+```jsx
+class A {}
+export { A as Valid_Renamed_Export }
+```
diff --git a/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.snap b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.snap
new file mode 100644
index 000000000000..aee2c63f182c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/style/useFilenamingConvention/Valid_Renamed_Export.snap
@@ -0,0 +1,9 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: Valid_Renamed_Export.js
+---
+# Input
+```jsx
+class A {}
+export { A as Valid_Renamed_Export }
+```