KEMBAR78
Add `InferNonNilReceiver` config to `Lint/RedundantSafeNavigation` by fatkodima · Pull Request #14345 · rubocop/rubocop · GitHub
Skip to content

Conversation

@fatkodima
Copy link
Contributor

Closes #13835.

This cop detects safe navigation as unnecessary if there was some method call on the same receiver in the previous code paths.

cc @vlad-pisanov

@fatkodima fatkodima force-pushed the new-SafeNavigationOnNonNil-cop branch from 1f0d4ed to a547769 Compare July 7, 2025 10:06
@koic
Copy link
Member

koic commented Jul 7, 2025

The removal of safe navigation needs to be done safely. If there is no guarantee that the logic remains safe, I think it shouldn't be added as a cop. Even if it is marked as unsafe, if removing a supposedly redundant safe navigation operator could break applications, users wouldn’t be able to use it with confidence.

@fatkodima fatkodima closed this Jul 7, 2025
@fatkodima fatkodima deleted the new-SafeNavigationOnNonNil-cop branch July 7, 2025 10:52
@fatkodima
Copy link
Contributor Author

I don't have time or desire to convince people about anything. It is already marked as unsafe and I detected 200 valid offenses in my app and only 1 false positive, so I think this is a valuable addition. I see people use this operator without thinking. I have it as a custom cop in my app.

@viralpraxis
Copy link
Contributor

Out of curiosity I tried it on bunch of my apps/gems and it actually found ~25 offenses in total, all of them were valid AFAICS.
Maybe it'd make sense to publish it as a tiny single-cop RuboCop extension?

@fatkodima Could you share what was your false positive?

@fatkodima
Copy link
Contributor Author

It was an example like this one https://github.com/rubocop/rubocop/pull/14345/files#diff-9d8778d3350f73d4e62eb6bf104febb5b6ba700ec50ea92c44ebca8200cda41eR33

I haven't added more logic to detect cases where a receiver is redefined to not make the implementation more complicated.

@bbatsov
Copy link
Collaborator

bbatsov commented Jul 7, 2025

I don't have time or desire to convince people about anything. It is already marked as unsafe and I detected 200 valid offenses in my app and only 1 false positive, so I think this is a valuable addition. I see people use this operator without thinking. I have it as a custom cop in my app.

We're all busy here, but there's little point in jumping to hasty conclusions.

I think the simplest path forward is to actually integrate your check in the existing cop and put it behind a flag there.

@fatkodima fatkodima changed the title Add new Lint/SafeNavigationOnNonNil cop Add CheckReceiverPreviousOccurrences config to Lint/RedundantSafeNavigation Jul 8, 2025
@fatkodima fatkodima restored the new-SafeNavigationOnNonNil-cop branch July 8, 2025 12:13
@fatkodima fatkodima reopened this Jul 8, 2025
@fatkodima fatkodima force-pushed the new-SafeNavigationOnNonNil-cop branch from a547769 to 4a8d464 Compare July 8, 2025 12:13
@fatkodima
Copy link
Contributor Author

Updated existing cop instead of creating a new one.

@fatkodima fatkodima force-pushed the new-SafeNavigationOnNonNil-cop branch from 4a8d464 to 968b39c Compare July 8, 2025 12:15
# In the example below, the safe navigation operator (`&.`) is unnecessary
# because `NilClass` has methods like `respond_to?` and `is_a?`.
#
# The `CheckReceiverPreviousOccurrences` option specifies whether to look into previous code
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps something like InferNonNilReceiver, InferReceiverValue or perhaps AnalyzeReceiverValue would be a slightly better name here. Might be good to expand a bit on that the inferences is limited to the current scope with a couple of examples illustrating it.

MSG = 'Redundant safe navigation detected, use `.` instead.'
MSG_LITERAL = 'Redundant safe navigation with default literal detected.'
MSG_NON_NIL =
'Redundant safe navigation on non nil receiver (detected by previous code paths).'
Copy link
Collaborator

Choose a reason for hiding this comment

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

non-nil

detected by analyzing previous code/method invocations


NIL_METHODS = (
nil.methods +
%i[present? blank? try try!] + # rails methods
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably non-core methods should be in a configuration option. I'm guessing some people will want to add more methods here anyways for whatever reasons.

@bbatsov
Copy link
Collaborator

bbatsov commented Jul 8, 2025

Overall the new version of the PR looks good to me. I left a few small remarks here and there. @rubocop/rubocop-core if someone has great naming suggestions for the config option - please share them!

Comment on lines +102 to +110
# # bad
# if foo.condition?
# foo&.bar
# end
Copy link
Contributor

Choose a reason for hiding this comment

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

would it be possible to handle this special case?

# bad
if foo
   foo&.bar
end

(in one of our projects I found things like log(user&.name) if user which are currently false negatives)

end

# rubocop:disable Metrics
def redundant?(node, receiver, checked_nodes)
Copy link
Contributor

Choose a reason for hiding this comment

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

should probably be renamed to be more specific (this entire cop is about redundancy checks, but this method is specifically for redundancy checks based on previous code paths)

this method might be more readable if packaged as a miniature service class (so you don't have to explicitly pass checked_nodes over and over and instead store it as an instance var)

node.each_condition do |condition|
return true if redundant?(condition, receiver, checked_nodes)
end
when :lvasgn
Copy link
Contributor

Choose a reason for hiding this comment

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

:ivasgn, :cvasgn, :gvasgn, :casgn?

otherwise stuff like this is a false negative:

NAME = user.name
user&.name

@fatkodima fatkodima force-pushed the new-SafeNavigationOnNonNil-cop branch from 968b39c to be8da84 Compare July 9, 2025 14:01
@fatkodima fatkodima changed the title Add CheckReceiverPreviousOccurrences config to Lint/RedundantSafeNavigation Add InferNonNilReceiver config to Lint/RedundantSafeNavigation Jul 9, 2025
@fatkodima
Copy link
Contributor Author

Thank you all for suggestions! Implemented all of them.

@bbatsov bbatsov merged commit d92357a into rubocop:master Jul 10, 2025
24 checks passed
@fatkodima fatkodima deleted the new-SafeNavigationOnNonNil-cop branch July 10, 2025 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect unnecessary safe navigation

5 participants