For #8947 - Lint check for licenses in Kotlin/Java source files (#15639)

upstream-sync
Juan C. Goncalves 4 years ago committed by GitHub
parent d208ffe002
commit 6d699105ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,9 @@ dependencies {
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}"
compileOnly "com.android.tools.lint:lint-api:${Versions.android_lint_api}"
compileOnly "com.android.tools.lint:lint-checks:${Versions.android_lint_api}"
testImplementation "com.android.tools.lint:lint:${Versions.android_lint_api}"
testImplementation "com.android.tools.lint:lint-tests:${Versions.android_lint_api}"
}
jar {

@ -0,0 +1,84 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.lintrules
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import org.jetbrains.kotlin.psi.psiUtil.siblings
import org.jetbrains.kotlin.psi.psiUtil.startsWithComment
import org.jetbrains.uast.UComment
import org.jetbrains.uast.UFile
class LicenseCommentChecker(private val context: JavaContext) : UElementHandler() {
companion object {
val ValidLicenseForKotlinFiles = """
|/* This Source Code Form is subject to the terms of the Mozilla Public
| * License, v. 2.0. If a copy of the MPL was not distributed with this
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""".trimMargin()
}
override fun visitFile(node: UFile) {
if (!node.sourcePsi.startsWithComment()) {
reportMissingLicense(node)
} else {
val firstComment = node.allCommentsInFile.first()
if (firstComment.text != ValidLicenseForKotlinFiles) {
reportInvalidLicenseFormat(firstComment)
} else {
val nextSibling =
firstComment.sourcePsi.siblings(withItself = false).firstOrNull()
if (nextSibling?.text != "\n\n") {
reportMissingLeadingNewLineCharacter(firstComment)
}
}
}
}
private fun reportMissingLicense(node: UFile) = context.report(
LicenseDetector.ISSUE_MISSING_LICENSE,
context.getLocation(node.sourcePsi.firstChild),
"The file must start with a comment containing the license",
addLicenseQuickFix()
)
private fun reportInvalidLicenseFormat(comment: UComment) = context.report(
LicenseDetector.ISSUE_INVALID_LICENSE_FORMAT,
context.getLocation(comment),
"The license comment doesn't have the appropriate format",
replaceCommentWithValidLicenseFix(comment)
)
private fun reportMissingLeadingNewLineCharacter(licenseComment: UComment) = context.report(
LicenseDetector.ISSUE_INVALID_LICENSE_FORMAT,
context.getRangeLocation(licenseComment, licenseComment.text.lastIndex, 1),
"The license comment must be followed by a newline character",
addLeadingNewLineQuickFix(licenseComment)
)
private fun addLicenseQuickFix() = LintFix.create()
.name("Insert license at the top of the file")
.replace()
.beginning()
.with(ValidLicenseForKotlinFiles + "\n\n")
.autoFix()
.build()
private fun replaceCommentWithValidLicenseFix(comment: UComment) = LintFix.create()
.name("Replace with correctly formatted license")
.replace()
.range(context.getLocation(comment))
.with(ValidLicenseForKotlinFiles)
.build()
private fun addLeadingNewLineQuickFix(licenseComment: UComment) = LintFix.create()
.name("Insert newline character after the license")
.replace()
.range(context.getLocation(licenseComment))
.with(ValidLicenseForKotlinFiles + "\n")
.autoFix()
.build()
}

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.lintrules
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.*
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UFile
class LicenseDetector : Detector(), SourceCodeScanner {
companion object {
private val Implementation = Implementation(
LicenseDetector::class.java,
Scope.JAVA_FILE_SCOPE
)
val ISSUE_MISSING_LICENSE = Issue.create(
id = "MissingLicense",
briefDescription = "File doesn't start with the license comment",
explanation = "Every file must start with the license comment:\n" +
LicenseCommentChecker.ValidLicenseForKotlinFiles,
category = Category.CORRECTNESS,
severity = Severity.WARNING,
implementation = Implementation
)
val ISSUE_INVALID_LICENSE_FORMAT = Issue.create(
id = "InvalidLicenseFormat",
briefDescription = "License isn't formatted correctly",
explanation = "The license must be:\n${LicenseCommentChecker.ValidLicenseForKotlinFiles}",
category = Category.CORRECTNESS,
severity = Severity.WARNING,
implementation = Implementation
)
}
override fun getApplicableUastTypes(): List<Class<out UElement>>? = listOf(UFile::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler? =
LicenseCommentChecker(context)
}

@ -17,6 +17,8 @@ class LintIssueRegistry : IssueRegistry() {
ButtonStyleXmlDetector.ISSUE_XML_STYLE,
AndroidSrcXmlDetector.ISSUE_XML_SRC_USAGE,
TextViewAndroidSrcXmlDetector.ISSUE_XML_SRC_USAGE,
ImageViewAndroidTintXmlDetector.ISSUE_XML_SRC_USAGE
ImageViewAndroidTintXmlDetector.ISSUE_XML_SRC_USAGE,
LicenseDetector.ISSUE_MISSING_LICENSE,
LicenseDetector.ISSUE_INVALID_LICENSE_FORMAT
) + ConstraintLayoutPerfDetector.ISSUES
}

@ -0,0 +1,140 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.lintrules
import com.android.tools.lint.checks.infrastructure.TestFiles
import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
import org.junit.Test
import org.mozilla.fenix.lintrules.LicenseCommentChecker.Companion.ValidLicenseForKotlinFiles
import org.mozilla.fenix.lintrules.LicenseDetector.Companion.ISSUE_INVALID_LICENSE_FORMAT
import org.mozilla.fenix.lintrules.LicenseDetector.Companion.ISSUE_MISSING_LICENSE
class LicenseDetectorTest {
@Test
fun `report missing license if it isn't at the top of the file`() {
val code = """
|package example
|
|class SomeExample {
| fun test() {
| val x = 10
| println("Hello")
| }
|}""".trimMargin()
val expectedReport = """
|src/example/SomeExample.kt:1: Warning: The file must start with a comment containing the license [MissingLicense]
|package example
|~~~~~~~~~~~~~~~
|0 errors, 1 warnings""".trimMargin()
val expectedFixOutput = """
|Fix for src/example/SomeExample.kt line 1: Insert license at the top of the file:
|@@ -1 +1
|+ /* This Source Code Form is subject to the terms of the Mozilla Public
|+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|+""".trimMargin()
lint()
.files(TestFiles.kt(code))
.issues(ISSUE_MISSING_LICENSE, ISSUE_INVALID_LICENSE_FORMAT)
.run()
.expect(expectedReport)
.expectFixDiffs(expectedFixOutput)
}
@Test
fun `report invalid license format if it doesn't have a leading newline character`() {
val code = """
|$ValidLicenseForKotlinFiles
|package example
|
|class SomeExample {
| fun test() {
| val x = 10
| println("Hello")
| }
|}""".trimMargin()
val expectedReport = """
|src/example/SomeExample.kt:3: Warning: The license comment must be followed by a newline character [InvalidLicenseFormat]
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
| ~
|0 errors, 1 warnings""".trimMargin()
val expectedFixOutput = """
|Fix for src/example/SomeExample.kt line 3: Insert newline character after the license:
|@@ -4 +4
|+""".trimMargin()
lint()
.files(TestFiles.kt(code))
.issues(ISSUE_MISSING_LICENSE, ISSUE_INVALID_LICENSE_FORMAT)
.run()
.expect(expectedReport)
.expectFixDiffs(expectedFixOutput)
}
@Test
fun `valid license does not generate a warning`() {
val code = """
|$ValidLicenseForKotlinFiles
|
|package example
|
|class SomeExample {
| fun test() {
| val x = 10
| println("Hello")
| }
|}""".trimMargin()
lint()
.files(TestFiles.kt(code))
.issues(ISSUE_MISSING_LICENSE, ISSUE_INVALID_LICENSE_FORMAT)
.run()
.expectClean()
}
@Test
fun `report incorrectly formatted license`() {
val code = """
|/* This Source Code Form is subject to the terms of the Mozilla Public
| License, v. 2.0. If a copy of the MPL was not distributed with this
| file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|package example
|
|class SomeExample {
| fun test() {
| val x = 10
| println("Hello")
| }
|}""".trimMargin()
val expectedReport = """
|src/example/SomeExample.kt:1: Warning: The license comment doesn't have the appropriate format [InvalidLicenseFormat]
|/* This Source Code Form is subject to the terms of the Mozilla Public
|^
|0 errors, 1 warnings""".trimMargin()
val expectedFixOutput = """
|Fix for src/example/SomeExample.kt line 1: Replace with correctly formatted license:
|@@ -2 +2
|- License, v. 2.0. If a copy of the MPL was not distributed with this
|- file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */""".trimMargin()
lint()
.files(TestFiles.kt(code))
.issues(ISSUE_MISSING_LICENSE, ISSUE_INVALID_LICENSE_FORMAT)
.run()
.expect(expectedReport)
.expectFixDiffs(expectedFixOutput)
}
}
Loading…
Cancel
Save