Skip to content

Adding support for full method signatures in ForbiddenMethodCall#3505

Merged
BraisGabin merged 1 commit into
detekt:masterfrom
DcortezMeleth:feature/ForbiddenMethodCall_full_method_signature
Mar 2, 2021
Merged

Adding support for full method signatures in ForbiddenMethodCall#3505
BraisGabin merged 1 commit into
detekt:masterfrom
DcortezMeleth:feature/ForbiddenMethodCall_full_method_signature

Conversation

@DcortezMeleth

Copy link
Copy Markdown
Contributor

This PR is for now a proof of concept for #3498.

Namely it's adding support for full method signatures (like java.time.LocalDate.now(java.time.Clock) in ForbiddenMethodCall rule.

@cortinico cortinico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great work 💪 We need a bit of fine tuning and I think we should be able to merge this

Comment on lines +50 to +54
val groups = METHOD_PARAM_REGEX.find(methodSignature)?.groups
val methodName = groups?.get(METHOD_GROUP_NAME)?.value
val params = groups?.get(METHOD_PARAM_GROUP_NAME)?.value?.split(",")
?.map { it.trim() }
?.filter { it.isNotBlank() }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please replace with something like:

Suggested change
val groups = METHOD_PARAM_REGEX.find(methodSignature)?.groups
val methodName = groups?.get(METHOD_GROUP_NAME)?.value
val params = groups?.get(METHOD_PARAM_GROUP_NAME)?.value?.split(",")
?.map { it.trim() }
?.filter { it.isNotBlank() }
val groups = methodSignature.split('(',')')
val methodName = groups[0].trim()
val params = if (groups.size <= 1) {
null
} else {
groups[1].split(",")
.map(String::trim)
.filter(String::isNotBlank)
}

Regexes are hard to maintain in the long run, especially for shared codebases like Detekt.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you wish, you can also create a util function inside the detekt-psi-utils package and write a unit test (like a .parseFqNameAndParams() or so).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably, because it's not that easy because some functions can be called `like, this`(Int).

And, if we want to move out this code, it should be placed in tooling utils instead of psi utils, right? This code is to parse the configuration. It's not related with psi.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it should be placed in tooling utils instead of psi utils, right?

I checked and detekt-psi-utils is already imported inside the -rules modules as an api dependency. The detekt-tooling is not and I don't think it's worth to add a module dependency for a function.

Not to be done in this PR, but I think we should rename detekt-psi-utils to detekt-rules-utils (given that inside there is also not PSI related utils) or create a separate detekt-rules-utils just for those functions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. My first thought was to use regex and I was so fixed on this approach that I missed simpler one.

forbiddenMethods
.filter { fqName == it.first }
.forEach {
if (it.second == null || it.second == methodParameterFqNames) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To tackle the java.time.LocalDate.of(Int, Int, Int), you should make sure you run a param-by-param comparison of the two lists and check both the fully qualified and the simple name:

val configParams = it.second
val match = methodParameterFqNames.foldIndexed(true) { index: Int, acc: Boolean, param: String ->
    acc && (param == configParams[index] || param.split('.').last() == configParams[index])
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see one issue here. Won't we create inconsistency between how method should be defined (only fully qualified) and params (fully qualified or just class name)?
Other than that it seems ok, as it will allow you to shorten definition and only use qualified names when false-positives would be encountered otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see one issue here. Won't we create inconsistency between how method should be defined (only fully qualified) and params (fully qualified or just class name)?
Other than that it seems ok, as it will allow you to shorten definition and only use qualified names when false-positives would be encountered otherwise.

You're right. I think we could then revert to always be explicit. Maybe add a note example in the doc so users are aware on how to us it properly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

@DcortezMeleth
DcortezMeleth force-pushed the feature/ForbiddenMethodCall_full_method_signature branch from 83be499 to 12ab206 Compare February 28, 2021 13:01
@DcortezMeleth

Copy link
Copy Markdown
Contributor Author

I have updated PR following your instructions.
I didn't extract methods params parsing to util as I wasn't sure if it will be useful anywhere else but if you feel that we should go for it just tell me where is the best place to put it inside util module.

@codecov

codecov Bot commented Feb 28, 2021

Copy link
Copy Markdown

Codecov Report

Merging #3505 (f9c45fe) into master (c90eafe) will increase coverage by 0.01%.
The diff coverage is 87.50%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #3505      +/-   ##
============================================
+ Coverage     77.62%   77.64%   +0.01%     
- Complexity     2806     2810       +4     
============================================
  Files           461      461              
  Lines          8672     8691      +19     
  Branches       1676     1683       +7     
============================================
+ Hits           6732     6748      +16     
  Misses         1035     1035              
- Partials        905      908       +3     
Impacted Files Coverage Δ Complexity Δ
.../gitlab/arturbosch/detekt/rules/MethodSignature.kt 58.82% <71.42%> (+8.82%) 0.00 <0.00> (ø)
...turbosch/detekt/rules/style/ForbiddenMethodCall.kt 91.89% <94.11%> (-0.11%) 14.00 <0.00> (+4.00) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c90eafe...aab67c1. Read the comment docs.

@cortinico

Copy link
Copy Markdown
Member

I wasn't sure if it will be useful anywhere else but if you feel that we should go for it just tell me

Having the function extracted was more for testing reason. It's easier to write a couple of unit tests in a separate module for just that function. Anyway that's not a hard requirement for merging this PR.

@DcortezMeleth
DcortezMeleth force-pushed the feature/ForbiddenMethodCall_full_method_signature branch 2 times, most recently from 7c95ca9 to 527dede Compare February 28, 2021 20:14
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.internal.valueOrDefaultCommaSeparated
import io.gitlab.arturbosch.detekt.rules.extractMethodNameAndParams
import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not necessarily an issue for this PR, because getJetTypeFqName is used at several places. However, the package path looks very weird because js appears.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As I mentioned it is my first meeting with kotlin reflection and it was the solution I have found but if you have some idea how to achieve it with other method I will be happy to try that out.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can investigate further on why this is needed. I don't think this would block merging this PR

@picklebento

Copy link
Copy Markdown
Member

What do we think about methods with default arguments? If fun foo(bar: Int, baz: Int = 5) is in the configuration and we encounter foo(5), should we also report that?

(This PR looks good in general, this might be an edge case that we can follow up)

@DcortezMeleth
DcortezMeleth force-pushed the feature/ForbiddenMethodCall_full_method_signature branch from 527dede to aab67c1 Compare March 1, 2021 06:09
@DcortezMeleth

Copy link
Copy Markdown
Contributor Author

As for the methods with default parameters I think we should report them all, because otherwise if we would like to forbid method with more than one default parameter, we would have to cover or possible combinations in configuration.
Also typically we wan't to discourage usage of a method and not the certain way of calling it, and even if we did, I feel that this should be done be a separate rule then.

I have added test case with default parameter. It worked without changes in the rule logic.

@DcortezMeleth

Copy link
Copy Markdown
Contributor Author

Thanks for approval. If I can do anything else to help you with this PR please let me know!

@schalkms schalkms left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If @BraisGabin approves, I think we can merge this.
Thanks for this awesome PR. It's a nice improvement of this rule.

@BraisGabin

Copy link
Copy Markdown
Member

Great work!

@BraisGabin
BraisGabin merged commit c5f65a9 into detekt:master Mar 2, 2021
@DcortezMeleth

Copy link
Copy Markdown
Contributor Author

No problem. I'm happy that I could have helped.

This was referenced Mar 11, 2021
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.

5 participants