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
128 changes: 128 additions & 0 deletions crates/oxc_linter/src/ast_util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::borrow::Cow;

use oxc_ast::{
ast::{BindingIdentifier, *},
AstKind,
Expand Down Expand Up @@ -770,3 +772,129 @@ pub fn is_default_this_binding<'a>(
}
}
}

pub fn get_static_property_name<'a>(parent_node: &AstNode<'a>) -> Option<Cow<'a, str>> {
let (key, computed) = match parent_node.kind() {
AstKind::PropertyDefinition(definition) => (&definition.key, definition.computed),
AstKind::MethodDefinition(method_definition) => {
(&method_definition.key, method_definition.computed)
}
AstKind::ObjectProperty(property) => (&property.key, property.computed),
_ => return None,
};

if key.is_identifier() && !computed {
return key.name();
}

if matches!(key, PropertyKey::NullLiteral(_)) {
return Some("null".into());
}

match key {
PropertyKey::RegExpLiteral(regex) => {
Some(Cow::Owned(format!("/{}/{}", regex.regex.pattern, regex.regex.flags)))
}
PropertyKey::BigIntLiteral(bigint) => Some(Cow::Borrowed(bigint.raw.as_str())),
PropertyKey::TemplateLiteral(template) => {
if template.expressions.len() == 0 && template.quasis.len() == 1 {
if let Some(cooked) = &template.quasis[0].value.cooked {
return Some(Cow::Borrowed(cooked.as_str()));
}
}

None
}
_ => None,
}
}

/// Gets the name and kind of the given function node.
/// @see <https://github.com/eslint/eslint/blob/48117b27e98639ffe7e78a230bfad9a93039fb7f/lib/rules/utils/ast-utils.js#L1762>
pub fn get_function_name_with_kind<'a>(
node: &AstNode<'a>,
parent_node: &AstNode<'a>,
) -> Cow<'a, str> {
let (name, is_async, is_generator) = match node.kind() {
AstKind::Function(func) => (func.name(), func.r#async, func.generator),
AstKind::ArrowFunctionExpression(arrow_func) => (None, arrow_func.r#async, false),
_ => (None, false, false),
};

let mut tokens: Vec<Cow<'a, str>> = vec![];

match parent_node.kind() {
AstKind::MethodDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
tokens.push(Cow::Borrowed("private"));
} else if let Some(accessibility) = definition.accessibility {
tokens.push(Cow::Borrowed(accessibility.as_str()));
}

if definition.r#static {
tokens.push(Cow::Borrowed("static"));
}
}
AstKind::PropertyDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
tokens.push(Cow::Borrowed("private"));
} else if let Some(accessibility) = definition.accessibility {
tokens.push(Cow::Borrowed(accessibility.as_str()));
}

if definition.r#static {
tokens.push(Cow::Borrowed("static"));
}
}
_ => {}
}

if is_async {
tokens.push(Cow::Borrowed("async"));
}

if is_generator {
tokens.push(Cow::Borrowed("generator"));
}

match parent_node.kind() {
AstKind::MethodDefinition(method_definition) => match method_definition.kind {
MethodDefinitionKind::Constructor => tokens.push(Cow::Borrowed("constructor")),
MethodDefinitionKind::Get => tokens.push(Cow::Borrowed("getter")),
MethodDefinitionKind::Set => tokens.push(Cow::Borrowed("setter")),
MethodDefinitionKind::Method => tokens.push(Cow::Borrowed("method")),
},
AstKind::PropertyDefinition(_) => tokens.push(Cow::Borrowed("method")),
_ => tokens.push(Cow::Borrowed("function")),
}

match parent_node.kind() {
AstKind::MethodDefinition(method_definition)
if !method_definition.computed && method_definition.key.is_private_identifier() =>
{
if let Some(name) = method_definition.key.name() {
tokens.push(name);
}
}
AstKind::PropertyDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
if let Some(name) = definition.key.name() {
tokens.push(name);
}
} else if let Some(static_name) = get_static_property_name(parent_node) {
tokens.push(static_name);
} else if let Some(name) = name {
tokens.push(Cow::Borrowed(name.as_str()));
}
}
_ => {
if let Some(static_name) = get_static_property_name(parent_node) {
tokens.push(static_name);
} else if let Some(name) = name {
tokens.push(Cow::Borrowed(name.as_str()));
}
}
}

Cow::Owned(tokens.join(" "))
}
2 changes: 2 additions & 0 deletions crates/oxc_linter/src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ mod eslint {
pub mod init_declarations;
pub mod max_classes_per_file;
pub mod max_lines;
pub mod max_lines_per_function;
pub mod max_nested_callbacks;
pub mod max_params;
pub mod new_cap;
Expand Down Expand Up @@ -554,6 +555,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::guard_for_in,
eslint::init_declarations,
eslint::max_nested_callbacks,
eslint::max_lines_per_function,
eslint::max_classes_per_file,
eslint::max_lines,
eslint::max_params,
Expand Down
137 changes: 8 additions & 129 deletions crates/oxc_linter/src/rules/eslint/func_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::borrow::Cow;
use oxc_ast::{
ast::{
AssignmentTarget, AssignmentTargetProperty, BindingPatternKind, Expression, Function,
FunctionType, MethodDefinitionKind, PropertyKey, PropertyKind,
FunctionType, PropertyKind,
},
AstKind,
};
Expand All @@ -14,7 +14,7 @@ use oxc_span::{Atom, GetSpan, Span};
use oxc_syntax::identifier::is_identifier_name;
use phf::phf_set;

use crate::{context::LintContext, rule::Rule, AstNode};
use crate::{ast_util::get_function_name_with_kind, context::LintContext, rule::Rule, AstNode};

fn named_diagnostic(function_name: &str, span: Span) -> OxcDiagnostic {
OxcDiagnostic::warn(format!("Unexpected named {function_name}."))
Expand Down Expand Up @@ -233,127 +233,6 @@ fn get_function_identifier<'a>(func: &'a Function<'a>) -> Option<&'a Span> {
func.id.as_ref().map(|id| &id.span)
}

fn get_property_key_name<'a>(key: &PropertyKey<'a>) -> Option<Cow<'a, str>> {
if matches!(key, PropertyKey::NullLiteral(_)) {
return Some("null".into());
}

match key {
PropertyKey::RegExpLiteral(regex) => {
Some(Cow::Owned(format!("/{}/{}", regex.regex.pattern, regex.regex.flags)))
}
PropertyKey::BigIntLiteral(bigint) => Some(Cow::Borrowed(bigint.raw.as_str())),
PropertyKey::TemplateLiteral(template) => {
if template.expressions.len() == 0 && template.quasis.len() == 1 {
if let Some(cooked) = &template.quasis[0].value.cooked {
return Some(Cow::Borrowed(cooked.as_str()));
}
}

None
}
_ => None,
}
}

fn get_static_property_name<'a>(parent_node: &AstNode<'a>) -> Option<Cow<'a, str>> {
let (key, computed) = match parent_node.kind() {
AstKind::PropertyDefinition(definition) => (&definition.key, definition.computed),
AstKind::MethodDefinition(method_definition) => {
(&method_definition.key, method_definition.computed)
}
AstKind::ObjectProperty(property) => (&property.key, property.computed),
_ => return None,
};

if key.is_identifier() && !computed {
return key.name();
}

get_property_key_name(key)
}

/// Gets the name and kind of the given function node.
/// @see <https://github.com/eslint/eslint/blob/48117b27e98639ffe7e78a230bfad9a93039fb7f/lib/rules/utils/ast-utils.js#L1762>
fn get_function_name_with_kind<'a>(func: &Function<'a>, parent_node: &AstNode<'a>) -> Cow<'a, str> {
let mut tokens: Vec<Cow<'a, str>> = vec![];

match parent_node.kind() {
AstKind::MethodDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
tokens.push(Cow::Borrowed("private"));
} else if let Some(accessibility) = definition.accessibility {
tokens.push(Cow::Borrowed(accessibility.as_str()));
}

if definition.r#static {
tokens.push(Cow::Borrowed("static"));
}
}
AstKind::PropertyDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
tokens.push(Cow::Borrowed("private"));
} else if let Some(accessibility) = definition.accessibility {
tokens.push(Cow::Borrowed(accessibility.as_str()));
}

if definition.r#static {
tokens.push(Cow::Borrowed("static"));
}
}
_ => {}
}

if func.r#async {
tokens.push(Cow::Borrowed("async"));
}

if func.generator {
tokens.push(Cow::Borrowed("generator"));
}

match parent_node.kind() {
AstKind::MethodDefinition(method_definition) => match method_definition.kind {
MethodDefinitionKind::Constructor => tokens.push(Cow::Borrowed("constructor")),
MethodDefinitionKind::Get => tokens.push(Cow::Borrowed("getter")),
MethodDefinitionKind::Set => tokens.push(Cow::Borrowed("setter")),
MethodDefinitionKind::Method => tokens.push(Cow::Borrowed("method")),
},
AstKind::PropertyDefinition(_) => tokens.push(Cow::Borrowed("method")),
_ => tokens.push(Cow::Borrowed("function")),
}

match parent_node.kind() {
AstKind::MethodDefinition(method_definition)
if !method_definition.computed && method_definition.key.is_private_identifier() =>
{
if let Some(name) = method_definition.key.name() {
tokens.push(name);
}
}
AstKind::PropertyDefinition(definition) => {
if !definition.computed && definition.key.is_private_identifier() {
if let Some(name) = definition.key.name() {
tokens.push(name);
}
} else if let Some(static_name) = get_static_property_name(parent_node) {
tokens.push(static_name);
} else if let Some(name) = func.name() {
tokens.push(Cow::Borrowed(name.as_str()));
}
}
_ => {
if let Some(static_name) = get_static_property_name(parent_node) {
tokens.push(static_name);
} else if let Some(name) = func.name() {
tokens.push(Cow::Borrowed(name.as_str()));
}
}
}

Cow::Owned(tokens.join(" "))
}

impl Rule for FuncNames {
fn from_configuration(value: serde_json::Value) -> Self {
let Some(default_value) = value.get(0) else {
Expand All @@ -371,7 +250,7 @@ impl Rule for FuncNames {
}

fn run_once(&self, ctx: &LintContext<'_>) {
let mut invalid_funcs: Vec<(&Function, &AstNode)> = vec![];
let mut invalid_funcs: Vec<(&Function, &AstNode, &AstNode)> = vec![];

for node in ctx.nodes() {
match node.kind() {
Expand All @@ -384,7 +263,7 @@ impl Rule for FuncNames {
if func.generator { &self.generators_config } else { &self.default_config };

if config.is_invalid_function(func, parent_node) {
invalid_funcs.push((func, parent_node));
invalid_funcs.push((func, node, parent_node));
}
}

Expand All @@ -395,7 +274,7 @@ impl Rule for FuncNames {
// check at first if the callee calls an invalid function
if !invalid_funcs
.iter()
.filter_map(|(func, _)| func.name())
.filter_map(|(func, _, _)| func.name())
.any(|func_name| func_name == identifier.name)
{
continue;
Expand All @@ -418,16 +297,16 @@ impl Rule for FuncNames {

// we found a recursive function, remove it from the invalid list
if let Some(span) = ast_span {
invalid_funcs.retain(|(func, _)| func.span != span);
invalid_funcs.retain(|(func, _, _)| func.span != span);
}
}
}
_ => {}
}
}

for (func, parent_node) in &invalid_funcs {
let func_name_complete = get_function_name_with_kind(func, parent_node);
for (func, node, parent_node) in invalid_funcs {
let func_name_complete = get_function_name_with_kind(node, parent_node);

let report_span = Span::new(func.span.start, func.params.span.start);
let replace_span = Span::new(
Expand Down
Loading