Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(linter): add unicorn/no-accessor-recursion rule #9971

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

huangtiandi1999
Copy link
Contributor

Copy link

graphite-app bot commented Mar 22, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

@github-actions github-actions bot added A-linter Area - Linter C-enhancement Category - New feature or request labels Mar 22, 2025
@huangtiandi1999 huangtiandi1999 changed the title feat(linter): add no-accessor-recursion rule feat(linter): add unicorn/no-accessor-recursion rule Mar 22, 2025
@huangtiandi1999 huangtiandi1999 marked this pull request as draft March 22, 2025 15:50
Copy link

codspeed-hq bot commented Mar 22, 2025

CodSpeed Instrumentation Performance Report

Merging #9971 will not alter performance

Comparing huangtiandi1999:feat/linter/no-accessor-recursion (9ae9cfb) with main (b1a30d6)

Summary

✅ 33 untouched benchmarks

@huangtiandi1999 huangtiandi1999 marked this pull request as ready for review March 25, 2025 09:29
@huangtiandi1999
Copy link
Contributor Author

hi @Sysix, this job is finish, please help to review

fn get_closest_function<'a>(node: &AstNode, ctx: &'a LintContext) -> Option<&'a AstNode<'a>> {
let mut parent = ctx.nodes().parent_node(node.id())?;

loop {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you rewrite it, to not use a loop and instead use something like while Some(parent) = ctx.nodes()...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's possible, I'd even recommend using ctx.nodes().ancestor_kinds(). here is an example elsewhere, where we do something similar: https://github.com/oxc-project/oxc/blob/main/crates/oxc_linter/src/rules/eslint/no_new_func.rs#L66-L72

Comment on lines +69 to +74
if !is_parent_property_or_method_def(func, ctx) {
return;
}
let Some(key_name) = get_property_or_method_def_name(func, ctx) else {
return;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_parent_property_or_method_def and get_property_or_method_def_name lookup up the parent from ctx.
I would prefer to get the parent in this method and avoiding a ctx passtrough :)

impl Rule for NoAccessorRecursion {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::VariableDeclarator(decl) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VariableDeclarator and MemberExpression are the core structure of any JS program.
Maybe we can get more performance when we check for ThisExpression and its possible valid parent.
What do you think @camchenry

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea. @huangtiandi1999 to put it more concretely: try restructuring the rule so that it only searches for AstKind::ThisExpression. Then, look up/down the tree as needed to match the cases. It might require a bit of restructuring, but it should be a lot faster, as we'll be able to skip evaluating lots of nodes that are common like @Sysix pointed out.

Comment on lines +15 to +16
OxcDiagnostic::warn(format!("Disallow recursive access to `this` within {kind}."))
.with_label(span)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every rule should have at least a short help message, to help users understand how to fix it the issue. How about something like this:

Suggested change
OxcDiagnostic::warn(format!("Disallow recursive access to `this` within {kind}."))
.with_label(span)
let method_kind = match kind {
"setters" => "set",
_ => "get",
};
OxcDiagnostic::warn(format!("Disallow recursive access to `this` within {kind}."))
.with_help("Remove this property access, or remove `{method_kind}` from the method")
.with_label(span)

impl Rule for NoAccessorRecursion {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
match node.kind() {
AstKind::VariableDeclarator(decl) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a good idea. @huangtiandi1999 to put it more concretely: try restructuring the rule so that it only searches for AstKind::ThisExpression. Then, look up/down the tree as needed to match the cases. It might require a bit of restructuring, but it should be a lot faster, as we'll be able to skip evaluating lots of nodes that are common like @Sysix pointed out.

fn get_closest_function<'a>(node: &AstNode, ctx: &'a LintContext) -> Option<&'a AstNode<'a>> {
let mut parent = ctx.nodes().parent_node(node.id())?;

loop {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's possible, I'd even recommend using ctx.nodes().ancestor_kinds(). here is an example elsewhere, where we do something similar: https://github.com/oxc-project/oxc/blob/main/crates/oxc_linter/src/rules/eslint/no_new_func.rs#L66-L72

Comment on lines +76 to +80
let exist = obj_pattern
.properties
.iter()
.find(|ident| ident.key.name().is_some_and(|name| name == key_name));
if exist.is_some() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let exist = obj_pattern
.properties
.iter()
.find(|ident| ident.key.name().is_some_and(|name| name == key_name));
if exist.is_some() {
let exists = obj_pattern
.properties
.iter()
.any(|ident| ident.key.name().is_some_and(|name| name == key_name));
if exists {

AstKind::VariableDeclarator(decl) => {
// Here we deal with deconstructive access to this inside getter
// e.g. "const { baz } = this"
if let Some(init) = &decl.init {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest doing an early return here, to remove a level of indentation:

Suggested change
if let Some(init) = &decl.init {
let Some(init) = &decl.init else {
return;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linter Area - Linter C-enhancement Category - New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants