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

Create CondFinalCondition readability check #935

Closed
wants to merge 8 commits into from
Closed

Create CondFinalCondition readability check #935

wants to merge 8 commits into from

Conversation

Stratus3D
Copy link
Contributor

@Stratus3D Stratus3D commented Jan 5, 2022

This PR adds a check named Credo.Check.Readability.CondCatchallTrue that checks for conds that end in a catchall clause that use a literal value other than true. I created this check to catch violations of the rule about catchall cond clauses specified in the Christopher Adam's style guide (https://github.com/christopheradams/elixir_style_guide#true-as-last-condition). See the tests for examples.

Context:

@@ -0,0 +1,78 @@
defmodule Credo.Check.Readability.CondCatchallTrue do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Open to suggestions on what to name this check. I had a hard time picking a name and I'm not sure this name is best.

defp catchall_other_than_true?({:->, _meta, [[{:{}, _meta2, _values}], _args]}), do: true
# Atom literal catch-all clause
defp catchall_other_than_true?({:->, _meta, [[name], _args]}) when is_atom(name), do: true
defp catchall_other_than_true?(_), do: false
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Returns true of the clause condition is a catchall that isn't the literal value true.

@rrrene
Copy link
Owner

rrrene commented Jan 9, 2022

Great 👍

Since the Elixir website & documentation call this the "final condition", we should name this CondFinalCondition.

Also, I do think the true should be a config parameter for this check. I can't think of a reason why we would exclude people using :else from the ability to check for it (although true should be the default to match the linked styleguide). 👍

@Stratus3D
Copy link
Contributor Author

Great 👍

Since the Elixir website & documentation call this the "final condition", we should name this CondFinalCondition.

Also, I do think the true should be a config parameter for this check. I can't think of a reason why we would exclude people using :else from the ability to check for it (although true should be the default to match the linked styleguide). 👍

PR updated!

@Stratus3D Stratus3D changed the title Create CondCatchallTrue readability check Create CondFinalCondition readability check Jan 13, 2022
@Stratus3D
Copy link
Contributor Author

@rrrene this is ready for review again!

@Stratus3D
Copy link
Contributor Author

@rrrene any chance you can take a look at this PR again?

Copy link
Owner

@rrrene rrrene left a comment

Choose a reason for hiding this comment

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

Apologies, I'm having the first cold in 2.5 years and am no longer used to that *%&/!.

Did you use a formatter on the code?

lib/credo/check/readability/cond_final_condition.ex Outdated Show resolved Hide resolved
lib/credo/check/readability/cond_final_condition.ex Outdated Show resolved Hide resolved
lib/credo/check/readability/cond_final_condition.ex Outdated Show resolved Hide resolved
defp catchall_other_than_value?({:->, _meta, [[{:{}, _meta2, _values}], _args]}, _value), do: true
# Atom literal catch-all clause
defp catchall_other_than_value?({:->, _meta, [[name], _args]}, _value) when is_atom(name), do: true
defp catchall_other_than_value?(_clause, _value), do: false
Copy link
Owner

Choose a reason for hiding this comment

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

If the clauses "above" this one are "catch alls" for all cases, what are examples where we need this clause?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is for a non catch-all clause. A contrived example:

 cond do
   n > 5 ->
     "greater than 5"
   n >= 0 ->
     "positive"
   n < 0 ->
     "negative"
end

The last clause is not a catchall and is not composed of a single literal. I will add a comment for this final clause. Let me know if you think I should rename this function.

{ast, issues}
end

defp catchall_other_than_value?({:->, _meta, [[value], _args]}, value), do: false
Copy link
Owner

Choose a reason for hiding this comment

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

Please rename this function to something that gives a hint at what it does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PR updated. Let me know if the new name is acceptable.


defp catchall_other_than_value?({:->, _meta, [[value], _args]}, value), do: false
# Integer literal catch-all clause
defp catchall_other_than_value?({:->, _meta, ['{', _args]}, _value), do: true
Copy link
Owner

Choose a reason for hiding this comment

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

Why does this always return true, no matter what I specified in the config?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function returns false when the value variable bound by the match of the first argument ({:->, _meta, [[value], _args]}) matches the second argument value. The second argument passed to this function is value specified in the config.

test/credo/check/readability/cond_final_condition_test.exs Outdated Show resolved Hide resolved
if conditions
|> List.last()
|> catchall_other_than_value?(final_condition_value) do
{ast, issues ++ [issue_for(issue_meta, meta[:line], :cond)]}
Copy link
Owner

Choose a reason for hiding this comment

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

Why is the trigger for the issue always "cond"? The trigger is the word in the code that "triggers" the issue and that should be the "wrong" final condition, e.g. using :else when using true is expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PR updated. The trigger is now the literal catchall value, or in the case of maps, tuples, and integers, the atom :map, :tuple or :integer. Let me know if this is acceptable.

Copy link
Owner

Choose a reason for hiding this comment

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

As I mentioned before

The trigger is the word in the code that "triggers" the issue

which means the actual string that should get squiggly lines in your editor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm sorry about that. I don't know how to do that but I'll see if I can figure it out. Are there any other checks that I could refer to that do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PR updated. I have added assertions to the tests for this check to verify that the clause expression that triggers issue is what is set for the issue's trigger parameter.

@Stratus3D
Copy link
Contributor Author

Apologies, I'm having the first cold in 2.5 years and am no longer used to that *%&/!.

Did you use a formatter on the code?

Sorry to hear you have been sick. Thanks for the review and thank you for all your work on credo!

I forgot to run the formatter when I opened the PR. I ran it and pushed the reformatted code. Everything is ready to review again.

@Stratus3D
Copy link
Contributor Author

I have updated the PR again based on your reply to one of my comments about the issue trigger value. The trigger is now set to the string of code that triggered the issue. The PR is ready to review again.

@Stratus3D
Copy link
Contributor Author

Any chance this can get merged soon?

@rrrene
Copy link
Owner

rrrene commented Feb 5, 2022

I have added assertions to the tests for this check to verify that the clause expression that triggers issue is what is set for the issue's trigger parameter.

This is good in principle, but it does not work if I modify your tests to something that might very well happen in real life (a dangling space somewhere):

*..................................................................................................................................................................................................................................................................................................................................

  1) test it should report conds with a last condition that is a list literal (Credo.Check.Readability.CondFinalConditionTest)
     test/credo/check/readability/cond_final_condition_test.exs:141
     Assertion with == failed
     code:  assert "[:else ]" == trigger
     left:  "[:else ]"
     right: "[:else]"
     stacktrace:
       (credo 1.6.2) lib/credo/test/assertions.ex:30: Credo.Test.Assertions.assert_issue/2
       test/credo/check/readability/cond_final_condition_test.exs:160: (test)

.

  2) test it should report conds with a last condition that is a tuple literal (Credo.Check.Readability.CondFinalConditionTest)
     test/credo/check/readability/cond_final_condition_test.exs:165
     Assertion with == failed
     code:  assert "{ :else }" == trigger
     left:  "{ :else }"
     right: "{:else}"
     stacktrace:
       (credo 1.6.2) lib/credo/test/assertions.ex:30: Credo.Test.Assertions.assert_issue/2
       test/credo/check/readability/cond_final_condition_test.exs:184: (test)

.......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................

@Stratus3D
Copy link
Contributor Author

@rrrene I copied what was done in the existing checks. I used Macro.to_string(ast) to convert the AST back into the original expression and passed that into the issue as the trigger value.

Can you provide a suggestion on how to improve this? Happy to change things but not sure how.

@Stratus3D
Copy link
Contributor Author

@rrrene, I'd like to get this merged, can you show me how to address this in credo? I'm not sure I can get the cond clause with whitespace in credo.

This is good in principle, but it does not work if I modify your tests to something that might very well happen in real life (a dangling space somewhere):

Can you provide a suggestion on how to improve this? Happy to change things but not sure how.

Thanks!

@Stratus3D Stratus3D closed this by deleting the head repository Oct 24, 2022
@Stratus3D Stratus3D reopened this Nov 30, 2022
@Stratus3D
Copy link
Contributor Author

Re-opening this PR as I closed it in error. @rrrene happy to make any changes to get this merged!

@Stratus3D
Copy link
Contributor Author

@rrrene you've merged several of my checks but not this one. If you can provide any info as to why this hasn't been accepted yet I will be happy to make any necessary changes (or close it permanently if that is needed).

@rrrene
Copy link
Owner

rrrene commented Dec 30, 2022

If you can provide any info as to why this hasn't been accepted yet

Hi, sorry for being a slow responder. This check won't get merged as long as it assumes that people write code the same way that Macro.to_string/1 translates ASTs. This is just an unrealistic expectation.

I do not have an "obvious" solution for your specific problem here (a.k.a. I'm not trolling you).

@rrrene
Copy link
Owner

rrrene commented Feb 3, 2023

I am closing this for age/inactivity.

If you find a way to make the final changes, please feel free to re-open this issue at your discretion.

@rrrene rrrene closed this Feb 3, 2023
@Stratus3D
Copy link
Contributor Author

Hi @rrrene , sorry for the late reply here.

Hi, sorry for being a slow responder. This check won't get merged as long as it assumes that people write code the same way that Macro.to_string/1 translates ASTs. This is just an unrealistic expectation.

Are you referring to this line? https://github.com/rrrene/credo/pull/935/files#diff-cbac4e2c2b102d2eba670658d03634fb4b21022007e98f39f5b7bccd674e52daR57 The Credo.Check.Warning.ExpensiveEmptyEnumCheck already uses Macro.to_string/1 to translate AST into code for the error (see https://github.com/rrrene/credo/blob/master/lib/credo/check/warning/expensive_empty_enum_check.ex#L66).

Is there a reason this is problematic? I understand the code may not be formatted exactly as the user typed it in, but I'm not sure this will be a big problem as we do output the line number as well, so the user should be able to locate the problem without issue. Remember, this AST in this case is only ever going to be a catchall clause literal value. So for example, :else, %{random: value}, [], [1,2,3], etc... it'll never be an anything other than a literal. While it's true the user may have formatted the literal differently, it I don't think it'll be a big problem in practice. The only alternative to I see to this would be to remove the trigger from reported issues entirely, which probably is not desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants