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
Add rules to suggest usage of check(), require() and error(). #1570
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
efde663
Add rules to suggest usage of check(), require() and error().
0907042
Add static import for Assertions.
84420af
Replace tab with spaces.
26433b9
Do not report if exception is created with a message and a cause.
aa9ca5d
Do not report if exception is thrown as the only action in a lambda.
6a49018
Refactor to remove duplicate code.
cb7e0c3
Merge branch 'master' into do-not-throw
arturbosch 88075eb
Regenerate docu with fixed ForbiddenVoid parameter
arturbosch File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseCheckOrError.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.CodeSmell | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Entity | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import org.jetbrains.kotlin.psi.KtCallExpression | ||
import org.jetbrains.kotlin.psi.KtThrowExpression | ||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType | ||
|
||
/** | ||
* Kotlin provides a much more concise way to check invariants as well as pre- and post conditions than to manually throw | ||
* an IllegalStateException. | ||
* | ||
* <noncompliant> | ||
* if (value == null) throw new IllegalStateException("value should not be null") | ||
* if (value < 0) throw new IllegalStateException("value is $value but should be at least 0") | ||
* when(a) { | ||
* 1 -> doSomething() | ||
* else -> throw IllegalStateException("Unexpected value") | ||
* } | ||
* </noncompliant> | ||
* | ||
* <compliant> | ||
* checkNotNull(value) {"value should not be null"} | ||
* check(value >= 0) { "value is $value but should be at least 0" } | ||
* when(a) { | ||
* 1 -> doSomething() | ||
* else -> error("Unexpected value") | ||
* } | ||
* </compliant> | ||
* | ||
* @author Markus Schwarz | ||
*/ | ||
class UseCheckOrError(config: Config = Config.empty) : Rule(config) { | ||
|
||
override val issue = Issue( | ||
"UseRequire", Severity.Style, | ||
"Use check() or error() instead of throwing an IllegalStateException.", | ||
Debt.FIVE_MINS | ||
) | ||
|
||
override fun visitThrowExpression(expression: KtThrowExpression) { | ||
if (expression.isIllegalStateExceptionWithoutCause()) { | ||
report(CodeSmell(issue, Entity.from(expression), issue.description)) | ||
} | ||
} | ||
|
||
private fun KtThrowExpression.isIllegalStateExceptionWithoutCause(): Boolean { | ||
val callExpression = findDescendantOfType<KtCallExpression>() | ||
val argumentCount = callExpression?.valueArgumentList?.children?.size ?: 0 | ||
|
||
return callExpression?.firstChild?.text == "IllegalStateException" && argumentCount < 2 | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
detekt-rules/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseRequire.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.CodeSmell | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Debt | ||
import io.gitlab.arturbosch.detekt.api.Entity | ||
import io.gitlab.arturbosch.detekt.api.Issue | ||
import io.gitlab.arturbosch.detekt.api.Rule | ||
import io.gitlab.arturbosch.detekt.api.Severity | ||
import org.jetbrains.kotlin.psi.KtCallExpression | ||
import org.jetbrains.kotlin.psi.KtThrowExpression | ||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType | ||
|
||
/** | ||
* Kotlin provides a much more concise way to check preconditions than to manually throw an | ||
* IllegalArgumentException. | ||
* | ||
* <noncompliant> | ||
* if (value == null) throw new IllegalArgumentException("value should not be null") | ||
* if (value < 0) throw new IllegalArgumentException("value is $value but should be at least 0") | ||
* </noncompliant> | ||
* | ||
* <compliant> | ||
* requireNotNull(value) {"value should not be null"} | ||
* require(value >= 0) { "value is $value but should be at least 0" } | ||
* </compliant> | ||
* | ||
* @author Markus Schwarz | ||
*/ | ||
class UseRequire(config: Config = Config.empty) : Rule(config) { | ||
|
||
override val issue = Issue( | ||
"UseRequire", Severity.Style, | ||
"Use require() instead of throwing an IllegalArgumentException.", | ||
Debt.FIVE_MINS | ||
) | ||
|
||
override fun visitThrowExpression(expression: KtThrowExpression) { | ||
if (expression.isIllegalArgumentException()) { | ||
report(CodeSmell(issue, Entity.from(expression), issue.description)) | ||
} | ||
} | ||
|
||
private fun KtThrowExpression.isIllegalArgumentException(): Boolean { | ||
val callExpression = findDescendantOfType<KtCallExpression>() | ||
val argumentCount = callExpression?.valueArgumentList?.children?.size ?: 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code duplication here |
||
|
||
return callExpression?.firstChild?.text == "IllegalArgumentException" && argumentCount < 2 | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseCheckOrErrorSpec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.test.lint | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.spekframework.spek2.Spek | ||
import org.spekframework.spek2.style.specification.describe | ||
|
||
class UseCheckOrErrorSpec : Spek({ | ||
|
||
val subject by memoized { UseCheckOrError(Config.empty) } | ||
|
||
describe("UseCheckOrError rule") { | ||
|
||
it("reports if a an IllegalStateException is thrown") { | ||
val code = """ | ||
fun x() { | ||
doSomething() | ||
if (a < 0) throw IllegalStateException() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if a an IllegalStateException is thrown with an error message") { | ||
val code = """ | ||
fun x() { | ||
doSomething() | ||
if (a < 0) throw IllegalStateException("More details") | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if a an IllegalStateException is thrown as default case of a when expression") { | ||
val code = """ | ||
fun x(a: Int) = | ||
when (a) { | ||
1 -> doSomething() | ||
else -> throw IllegalStateException() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if an IllegalStateException is thrown by its fully qualified name") { | ||
val code = """ | ||
fun x() { | ||
doSomething() | ||
if (a < 0) throw java.lang.IllegalStateException() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if an IllegalStateException is thrown by its fully qualified name using the kotlin type alias") { | ||
val code = """ | ||
fun x() { | ||
doSomething() | ||
if (a < 0) throw kotlin.IllegalStateException() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("does not report if any other kind of exception is thrown") { | ||
val code = """ | ||
fun x() { | ||
doSomething() | ||
if (a < 0) throw SomeBusinessException() | ||
}""" | ||
assertThat(subject.lint(code)).isEmpty() | ||
} | ||
|
||
it("does not report an issue if the exception thrown has a message and a cause") { | ||
val code = """ | ||
private fun missing(): Nothing { | ||
if (cause != null) { | ||
throw IllegalStateException("message", cause) | ||
} | ||
}""" | ||
assertThat(subject.lint(code)).isEmpty() | ||
} | ||
} | ||
}) |
68 changes: 68 additions & 0 deletions
68
detekt-rules/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseRequireSpec.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package io.gitlab.arturbosch.detekt.rules.style | ||
|
||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.test.lint | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.spekframework.spek2.Spek | ||
import org.spekframework.spek2.style.specification.describe | ||
|
||
class UseRequireSpec : Spek({ | ||
|
||
val subject by memoized { UseRequire(Config.empty) } | ||
|
||
describe("UseRequire rule") { | ||
|
||
it("reports if a precondition throws an IllegalArgumentException") { | ||
val code = """ | ||
fun x(a: Int) { | ||
if (a < 0) throw IllegalArgumentException() | ||
doSomething() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if a precondition throws an IllegalArgumentException with more details") { | ||
val code = """ | ||
fun x(a: Int) { | ||
if (a < 0) throw IllegalArgumentException("More details") | ||
doSomething() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if a precondition throws a fully qualified IllegalArgumentException") { | ||
val code = """ | ||
fun x(a: Int) { | ||
if (a < 0) throw java.lang.IllegalArgumentException() | ||
doSomething() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("reports if a precondition throws a fully qualified IllegalArgumentException using the kotlin type alias") { | ||
val code = """ | ||
fun x(a: Int) { | ||
if (a < 0) throw kotlin.IllegalArgumentException() | ||
doSomething() | ||
}""" | ||
assertThat(subject.lint(code)).hasSize(1) | ||
} | ||
|
||
it("does not report if a precondition throws a different kind of exception") { | ||
val code = """ | ||
fun x(a: Int) { | ||
if (a < 0) throw SomeBusinessException() | ||
doSomething() | ||
}""" | ||
assertThat(subject.lint(code)).isEmpty() | ||
} | ||
|
||
it("does not report an issue if the exception thrown has a message and a cause") { | ||
val code = """ | ||
private fun x(a: Int): Nothing { | ||
throw IllegalArgumentException("message", cause) | ||
}""" | ||
assertThat(subject.lint(code)).isEmpty() | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code duplication here
Can we group this together and pass the type of the exception?