diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index a1aede1c557..a2ddf6bedfb 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -66,6 +66,7 @@ comments: active: false matchTypeParameters: true matchDeclarationsOrder: true + allowParamOnConstructorProperties: false UndocumentedPublicClass: active: false excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] diff --git a/detekt-rules-documentation/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentation.kt b/detekt-rules-documentation/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentation.kt index bfe760f4a01..894600cba28 100644 --- a/detekt-rules-documentation/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentation.kt +++ b/detekt-rules-documentation/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentation.kt @@ -76,6 +76,9 @@ class OutdatedDocumentation(config: Config = Config.empty) : Rule(config) { @Configuration("if the order of declarations should be preserved") private val matchDeclarationsOrder: Boolean by config(true) + @Configuration("if we allow constructor parameters to be marked as @param instead of @property") + private val allowParamOnConstructorProperties: Boolean by config(false) + override fun visitClass(klass: KtClass) { super.visitClass(klass) reportIfDocumentationIsOutdated(klass) { getClassDeclarations(klass) } @@ -119,7 +122,15 @@ class OutdatedDocumentation(config: Config = Config.empty) : Rule(config) { private fun getDeclarationsForValueParameters(valueParameters: List): List { return valueParameters.mapNotNull { it.name?.let { name -> - val type = if (it.isPropertyParameter()) DeclarationType.PROPERTY else DeclarationType.PARAM + val type = if (it.isPropertyParameter()) { + if (allowParamOnConstructorProperties) { + DeclarationType.ANY + } else { + DeclarationType.PROPERTY + } + } else { + DeclarationType.PARAM + } Declaration(name, type) } } @@ -166,11 +177,21 @@ class OutdatedDocumentation(config: Config = Config.empty) : Rule(config) { } private fun declarationsMatch(doc: List, element: List): Boolean { - return if (matchDeclarationsOrder) { - doc == element + if (doc.size != element.size) { + return false + } + + val zippedElements = if (matchDeclarationsOrder) { + doc.zip(element) } else { - doc.sortedBy { it.name } == element.sortedBy { it.name } + doc.sortedBy { it.name }.zip(element.sortedBy { it.name }) } + + return zippedElements.all { (doc, element) -> declarationMatches(doc, element) } + } + + private fun declarationMatches(doc: Declaration, element: Declaration): Boolean { + return element.name == doc.name && (element.type == DeclarationType.ANY || element.type == doc.type) } private fun reportCodeSmell(element: KtNamedDeclaration) { @@ -193,6 +214,6 @@ class OutdatedDocumentation(config: Config = Config.empty) : Rule(config) { ) enum class DeclarationType { - PARAM, PROPERTY + PARAM, PROPERTY, ANY } } diff --git a/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentationSpec.kt b/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentationSpec.kt index bbf03b58c88..da7d72e7228 100644 --- a/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentationSpec.kt +++ b/detekt-rules-documentation/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/documentation/OutdatedDocumentationSpec.kt @@ -364,5 +364,67 @@ class OutdatedDocumentationSpec : Spek({ assertThat(configuredSubject.compileAndLint(incorrectDeclarationsOrderWithType)).isEmpty() } } + describe("configuration allowParamOnConstructorProperties") { + val configuredSubject by memoized { + OutdatedDocumentation(TestConfig(mapOf("allowParamOnConstructorProperties" to "true"))) + } + + it("should not report when property is documented as param") { + val propertyAsParam = """ + /** + * @param someParam Description of param + * @param someProp Description of property + */ + class MyClass(someParam: String, val someProp: String) + """ + assertThat(configuredSubject.compileAndLint(propertyAsParam)).isEmpty() + } + + it("should not report when property is documented as property") { + val propertyAsParam = """ + /** + * @param someParam Description of param + * @property someProp Description of property + */ + class MyClass(someParam: String, val someProp: String) + """ + assertThat(configuredSubject.compileAndLint(propertyAsParam)).isEmpty() + } + } + + describe("configuration matchDeclarationsOrder and allowParamOnConstructorProperties") { + val configuredSubject by memoized { + OutdatedDocumentation( + TestConfig( + mapOf( + "matchDeclarationsOrder" to "false", + "allowParamOnConstructorProperties" to "true" + ) + ) + ) + } + + it("should not report when property is documented as param") { + val propertyAsParam = """ + /** + * @param someParam Description of param + * @param someProp Description of property + */ + class MyClass(someParam: String, val someProp: String) + """ + assertThat(configuredSubject.compileAndLint(propertyAsParam)).isEmpty() + } + + it("should not report when property is documented as property") { + val propertyAsParam = """ + /** + * @param someParam Description of param + * @property someProp Description of property + */ + class MyClass(someParam: String, val someProp: String) + """ + assertThat(configuredSubject.compileAndLint(propertyAsParam)).isEmpty() + } + } } })