Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,21 @@ pub fn legacy_treesitter_slots_for_kind(kind: JsSyntaxKind) -> &'static [(&'stat
.map_or(&[], |p| p.slots)
}

/// Returns the syntax kind for a legacy or native node name.
/// Resolve a legacy TreeSitter or native Biome node name to the corresponding syntax kind.
///
/// # Examples
///
/// ```
/// let k = kind_by_name("identifier");
/// assert_eq!(k, Some(JsSyntaxKind::JS_REFERENCE_IDENTIFIER));
///
/// let missing = kind_by_name("NonExistentNode");
/// assert_eq!(missing, None);
/// ```
///
/// # Returns
///
/// `Some(JsSyntaxKind)` if a mapping exists for `node_name`, `None` otherwise.
pub fn kind_by_name(node_name: &str) -> Option<JsSyntaxKind> {
match node_name {
// Legacy TreeSitter patterns
Expand Down Expand Up @@ -298,6 +312,7 @@ pub fn kind_by_name(node_name: &str) -> Option<JsSyntaxKind> {
"JsFunctionExpression" => lang::JsFunctionExpression::KIND_SET.iter().next(),
"JsGetterClassMember" => lang::JsGetterClassMember::KIND_SET.iter().next(),
"JsGetterObjectMember" => lang::JsGetterObjectMember::KIND_SET.iter().next(),
"JsGlimmerTemplate" => lang::JsGlimmerTemplate::KIND_SET.iter().next(),
"JsIdentifierAssignment" => lang::JsIdentifierAssignment::KIND_SET.iter().next(),
"JsIdentifierBinding" => lang::JsIdentifierBinding::KIND_SET.iter().next(),
"JsIdentifierExpression" => lang::JsIdentifierExpression::KIND_SET.iter().next(),
Expand Down Expand Up @@ -591,4 +606,4 @@ pub fn kind_by_name(node_name: &str) -> Option<JsSyntaxKind> {
"TsVoidType" => lang::TsVoidType::KIND_SET.iter().next(),
_ => None,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ impl Rule for NoStaticOnlyClass {
type Signals = Option<Self::State>;
type Options = NoStaticOnlyClassOptions;

/// Determines whether the queried class contains only static members and signals the lint when it does.
///
/// The rule returns `Some(())` when the class has at least one relevant member, has no decorators, does not extend another class,
/// and every relevant member is static (constructors count as non-static; certain placeholder/template members are ignored).
/// Otherwise it returns `None`.
///
/// # Examples
///
/// ```
/// // Example (illustrative): given a `RuleContext` for `class C { static a() {} }`:
/// // let ctx = ...;
/// // assert_eq!(NoStaticOnlyClass::run(&ctx), Some(()));
///
/// // For `class C { a() {} }`:
/// // assert_eq!(NoStaticOnlyClass::run(&ctx), None);
/// ```
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let class_declaration = ctx.query();

Expand All @@ -162,6 +178,7 @@ impl Rule for NoStaticOnlyClass {
.filter_map(|member| match member {
AnyJsClassMember::JsBogusMember(_)
| AnyJsClassMember::JsMetavariable(_)
| AnyJsClassMember::JsGlimmerTemplate(_)
| AnyJsClassMember::JsEmptyClassMember(_) => None,
AnyJsClassMember::JsConstructorClassMember(_) => Some(false), // See GH#4482: Constructors are not regarded as static
AnyJsClassMember::TsConstructorSignatureClassMember(_) => Some(false), // See GH#4482: Constructors are not regarded as static
Expand Down Expand Up @@ -202,4 +219,4 @@ impl Rule for NoStaticOnlyClass {
}),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ impl Rule for NoInvalidConstructorSuper {
}
}

/// Determines whether a given JavaScript expression can serve as a valid constructor target for `super()`.
///
/// Returns `Some(true)` if the expression is a valid constructor (e.g., `this`, `new`, function/class expressions,
/// certain call-like expressions, or identifiers that are not the special `undefined` identifier),
/// `Some(false)` if the expression is definitely not a constructor (e.g., literals, arrays, object expressions,
/// binary expressions, glimmer templates), and `None` if the analysis is indeterminate for the expression form.
///
/// # Examples
///
/// ```no_run
/// // Pseudocode example — construct a JsIdentifierExpression for `Parent` from a parser,
/// // then call is_valid_constructor(expression) to determine whether `super()` is allowed.
/// // let expr = parse_identifier_expression("Parent");
/// // assert_eq!(is_valid_constructor(expr), Some(true));
/// ```
fn is_valid_constructor(expression: AnyJsExpression) -> Option<bool> {
match expression.omit_parentheses() {
AnyJsExpression::JsAwaitExpression(await_expression) => {
Expand Down Expand Up @@ -226,6 +241,7 @@ fn is_valid_constructor(expression: AnyJsExpression) -> Option<bool> {
| AnyJsExpression::JsBinaryExpression(_)
| AnyJsExpression::JsBogusExpression(_)
| AnyJsExpression::JsMetavariable(_)
| AnyJsExpression::JsGlimmerTemplate(_)
| AnyJsExpression::JsInstanceofExpression(_)
| AnyJsExpression::JsObjectExpression(_)
| AnyJsExpression::JsPostUpdateExpression(_)
Expand All @@ -237,4 +253,4 @@ fn is_valid_constructor(expression: AnyJsExpression) -> Option<bool> {
// Should not be triggered because we called `omit_parentheses`
AnyJsExpression::JsParenthesizedExpression(_) => None,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,28 @@ fn looks_like_directive(node: &JsExpressionStatement) -> bool {
)
}

/// Determines whether a JavaScript/TypeScript expression is disallowed when used as a standalone expression statement.
///
/// Returns `true` for expressions that should be flagged if they appear alone as a statement (for example: literals, arrays, objects, identifiers, class/function expressions, template literals, JSX, glimmer templates, binary/conditional expressions, etc.). Returns `false` for expressions that are valid statement expressions (for example: call, assignment, `new`, `await`, `yield`, and update expressions).
///
/// # Parameters
///
/// - `expr`: the AST node representing the JavaScript/TypeScript expression to evaluate.
///
/// # Returns
///
/// `true` if the given expression is disallowed as an expression statement, `false` otherwise.
///
/// # Examples
///
/// ```no_run
/// use rome_js_syntax::AnyJsExpression;
/// // Assume `expr` is obtained from parsing source, e.g. a literal or a call expression.
/// # fn _example(expr: AnyJsExpression) -> rome_rowan::SyntaxResult<bool> {
/// let disallowed = is_disallowed(&expr)?;
/// Ok(disallowed)
/// # }
/// ```
fn is_disallowed(expr: &AnyJsExpression) -> SyntaxResult<bool> {
let is_disallowed = match expr {
AnyJsExpression::AnyJsLiteralExpression(_)
Expand All @@ -259,6 +281,7 @@ fn is_disallowed(expr: &AnyJsExpression) -> SyntaxResult<bool> {
| AnyJsExpression::JsComputedMemberExpression(_)
| AnyJsExpression::JsConditionalExpression(_)
| AnyJsExpression::JsFunctionExpression(_)
| AnyJsExpression::JsGlimmerTemplate(_)
| AnyJsExpression::JsIdentifierExpression(_)
| AnyJsExpression::JsImportMetaExpression(_)
| AnyJsExpression::JsInExpression(_)
Expand Down Expand Up @@ -298,4 +321,4 @@ fn is_disallowed(expr: &AnyJsExpression) -> SyntaxResult<bool> {
AnyJsExpression::TsTypeAssertionExpression(expr) => is_disallowed(&expr.expression()?)?,
};
Ok(is_disallowed)
}
}
26 changes: 25 additions & 1 deletion crates/biome_js_analyze/src/lint/style/use_naming_convention.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1148,13 +1148,37 @@ fn selector_from_name(js_name: &AnyIdentifierBindingLike) -> Option<Selector> {
}
}

/// Derives a naming-convention Selector for a class member, or returns `None` when the member should be ignored.
///
/// Returns `None` for members that are not subject to naming checks (constructors, bogus/metavariable/empty members,
/// Glimmer templates, static initialization blocks) or when the member is explicitly marked `override`. For other
/// class members (methods, getters, setters, properties, index signatures) this returns a `Selector` whose `Kind`
/// reflects the member kind and whose modifiers are mapped to the restricted modifier set used by conventions.
///
/// # Parameters
///
/// - `member`: the class member to analyze.
///
/// # Returns
///
/// `Some(Selector)` when the member is subject to naming-convention checks, with the selector's kind and restricted
/// modifiers set accordingly; `None` when the member should be ignored.
///
/// # Examples
///
/// ```
/// // Example (illustrative):
/// // let sel = selector_from_class_member(&some_method_member);
/// // assert!(sel.is_some());
/// ```
fn selector_from_class_member(member: &AnyJsClassMember) -> Option<Selector> {
let (kind, modifiers): (_, BitFlags<_>) = match member {
AnyJsClassMember::JsBogusMember(_)
| AnyJsClassMember::JsMetavariable(_)
| AnyJsClassMember::JsConstructorClassMember(_)
| AnyJsClassMember::TsConstructorSignatureClassMember(_)
| AnyJsClassMember::JsEmptyClassMember(_)
| AnyJsClassMember::JsGlimmerTemplate(_)
| AnyJsClassMember::JsStaticInitializationBlockClassMember(_) => return None,
AnyJsClassMember::TsIndexSignatureClassMember(member) => {
(Kind::ClassProperty, (&member.modifiers()).into())
Expand Down Expand Up @@ -1513,4 +1537,4 @@ fn to_restricted_modifiers(bitflag: enumflags2::BitFlags<Modifier>) -> Restricte
| Modifier::Accessor => None,
})
.collect()
}
}
79 changes: 78 additions & 1 deletion crates/biome_js_formatter/src/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2256,13 +2256,90 @@ impl IntoFormat<JsFormatContext> for biome_js_syntax::JsGetterObjectMember {
biome_js_syntax::JsGetterObjectMember,
crate::js::objects::getter_object_member::FormatJsGetterObjectMember,
>;
/// Converts this value into an owned formatter that formats a getter object member using the
/// default `FormatJsGetterObjectMember` rule.
///
/// # Examples
///
/// ```no_run
/// let node = get_a_js_getter_object_member(); // returns a `JsGetterObjectMember`
/// let formatted = node.into_format();
/// // `formatted` can be passed to the formatting API.
/// ```
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::js::objects::getter_object_member::FormatJsGetterObjectMember::default(),
)
}
}
impl FormatRule<biome_js_syntax::JsGlimmerTemplate>
for crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate
{
type Context = JsFormatContext;
/// Formats a `JsGlimmerTemplate` node using this formatting rule.
///
/// # Examples
///
/// ```
/// // Given a `template` of type `biome_js_syntax::JsGlimmerTemplate`
/// // and a mutable `formatter` of type `JsFormatter`:
/// // let result = crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate::default()
/// // .fmt(&template, &mut formatter);
/// // assert!(result.is_ok());
/// ```
#[inline(always)]
fn fmt(
&self,
node: &biome_js_syntax::JsGlimmerTemplate,
f: &mut JsFormatter,
) -> FormatResult<()> {
FormatNodeRule::<biome_js_syntax::JsGlimmerTemplate>::fmt(self, node, f)
}
}
impl AsFormat<JsFormatContext> for biome_js_syntax::JsGlimmerTemplate {
type Format<'a> = FormatRefWithRule<
'a,
biome_js_syntax::JsGlimmerTemplate,
crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate,
>;
/// Format a borrowed `JsGlimmerTemplate` with the default Glimmer template formatter.
///
/// # Examples
///
/// ```
/// // Given a `JsGlimmerTemplate` value `template`:
/// let formatted = template.format();
/// ```
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(
self,
crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate::default(),
)
}
}
impl IntoFormat<JsFormatContext> for biome_js_syntax::JsGlimmerTemplate {
type Format = FormatOwnedWithRule<
biome_js_syntax::JsGlimmerTemplate,
crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate,
>;
/// Converts the Glimmer template node into an owned formatter that applies the Glimmer template formatting rule.
///
/// # Examples
///
/// ```
/// use biome_js_syntax::JsGlimmerTemplate;
///
/// let node = JsGlimmerTemplate::default();
/// let owned_formatter = node.into_format();
/// ```
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(
self,
crate::js::auxiliary::glimmer_template::FormatJsGlimmerTemplate::default(),
)
}
}
impl FormatRule<biome_js_syntax::JsIdentifierAssignment>
for crate::js::assignments::identifier_assignment::FormatJsIdentifierAssignment
{
Expand Down Expand Up @@ -12926,4 +13003,4 @@ impl IntoFormat<JsFormatContext> for biome_js_syntax::AnyTsVariableAnnotation {
crate::ts::any::variable_annotation::FormatAnyTsVariableAnnotation::default(),
)
}
}
}
10 changes: 9 additions & 1 deletion crates/biome_js_formatter/src/js/any/class_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ use biome_js_syntax::AnyJsClassMember;
pub(crate) struct FormatAnyJsClassMember;
impl FormatRule<AnyJsClassMember> for FormatAnyJsClassMember {
type Context = JsFormatContext;
/// Formats an `AnyJsClassMember` by delegating to the concrete variant's formatter.
///
/// Each variant of `AnyJsClassMember` is routed to its respective `format()` implementation.
///
/// # Returns
///
/// `Ok(())` if formatting succeeds, or an error if formatting fails.
fn fmt(&self, node: &AnyJsClassMember, f: &mut JsFormatter) -> FormatResult<()> {
match node {
AnyJsClassMember::JsBogusMember(node) => node.format().fmt(f),
AnyJsClassMember::JsConstructorClassMember(node) => node.format().fmt(f),
AnyJsClassMember::JsEmptyClassMember(node) => node.format().fmt(f),
AnyJsClassMember::JsGetterClassMember(node) => node.format().fmt(f),
AnyJsClassMember::JsGlimmerTemplate(node) => node.format().fmt(f),
AnyJsClassMember::JsMetavariable(node) => node.format().fmt(f),
AnyJsClassMember::JsMethodClassMember(node) => node.format().fmt(f),
AnyJsClassMember::JsPropertyClassMember(node) => node.format().fmt(f),
Expand All @@ -28,4 +36,4 @@ impl FormatRule<AnyJsClassMember> for FormatAnyJsClassMember {
AnyJsClassMember::TsSetterSignatureClassMember(node) => node.format().fmt(f),
}
}
}
}
20 changes: 19 additions & 1 deletion crates/biome_js_formatter/src/js/any/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ use biome_js_syntax::AnyJsExpression;
pub(crate) struct FormatAnyJsExpression;
impl FormatRule<AnyJsExpression> for FormatAnyJsExpression {
type Context = JsFormatContext;
/// Delegates formatting of an AnyJsExpression to its specific expression formatter.
///
/// This method matches on the concrete variant of `AnyJsExpression` and forwards
/// formatting to the corresponding inner node's formatter implementation.
///
/// # Examples
///
/// ```rust,no_run
/// use crate::prelude::*;
///
/// let formatter = FormatAnyJsExpression::default();
/// let any_expr: AnyJsExpression = /* construct an AnyJsExpression variant */ unimplemented!();
/// let mut f = JsFormatter::new(/* ... */);
///
/// // Delegates to the underlying node's `fmt` implementation.
/// let _ = formatter.fmt(&any_expr, &mut f);
/// ```
fn fmt(&self, node: &AnyJsExpression, f: &mut JsFormatter) -> FormatResult<()> {
match node {
AnyJsExpression::AnyJsLiteralExpression(node) => node.format().fmt(f),
Expand All @@ -20,6 +37,7 @@ impl FormatRule<AnyJsExpression> for FormatAnyJsExpression {
AnyJsExpression::JsComputedMemberExpression(node) => node.format().fmt(f),
AnyJsExpression::JsConditionalExpression(node) => node.format().fmt(f),
AnyJsExpression::JsFunctionExpression(node) => node.format().fmt(f),
AnyJsExpression::JsGlimmerTemplate(node) => node.format().fmt(f),
AnyJsExpression::JsIdentifierExpression(node) => node.format().fmt(f),
AnyJsExpression::JsImportCallExpression(node) => node.format().fmt(f),
AnyJsExpression::JsImportMetaExpression(node) => node.format().fmt(f),
Expand Down Expand Up @@ -48,4 +66,4 @@ impl FormatRule<AnyJsExpression> for FormatAnyJsExpression {
AnyJsExpression::TsTypeAssertionExpression(node) => node.format().fmt(f),
}
}
}
}
Loading
Loading