Adding linter for checking license (and copyright) header in java/kotlin files
Fixes: 302679200
Test: MissingApacheLicenseDetectorTest
Test: run `m SYSTEM_UI_CORE_PATH/lint-report.html` and see legit license warnings/errors
Change-Id: I8c79e74f6a171964b30b93e029eeef0ef72a045c
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index ee05f2d..0284117 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -212,6 +212,7 @@
lint: {
extra_check_modules: ["SystemUILintChecker"],
+ warning_checks: ["MissingApacheLicenseDetector"],
},
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 0000000..46125be
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ val firstComment = node.allCommentsInFile.firstOrNull()
+ // Normally we don't need to explicitly handle suppressing case and just return
+ // error as usual with indicating node and lint will ignore it for us. But here
+ // suppressing will be applied on top of comment that doesn't exist so we don't have
+ // node to return - it's a bit of corner case
+ if (firstComment != null && firstComment.isSuppressingComment()) {
+ return
+ }
+ if (firstComment == null || !firstComment.isLicenseComment()) {
+ val firstLineOfFile =
+ Location.create(
+ context.file,
+ SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+ )
+ context.report(
+ issue = ISSUE,
+ location = firstLineOfFile,
+ message =
+ "License header is missing\n" +
+ "Please add the following copyright and license header to the" +
+ " beginning of the file:\n\n" +
+ copyrightHeader
+ )
+ }
+ }
+ }
+ }
+
+ private fun UComment.isSuppressingComment(): Boolean {
+ val suppressingComment =
+ "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+ return text.contains(suppressingComment)
+ }
+
+ private fun UComment.isLicenseComment(): Boolean {
+ // We probably don't want to compare full copyright header in case there are some small
+ // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+ // good enough if this detector deals with missing
+ // license header instead of incorrect license header
+ return text.contains("Apache License")
+ }
+
+ private val copyrightHeader: String
+ get() =
+ """
+ /*
+ * Copyright (C) ${Year.now().value} The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ """
+ .trimIndent()
+ .trim()
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "MissingApacheLicenseDetector",
+ briefDescription = "File is missing Apache license information",
+ explanation =
+ """
+ Every source code file should have copyright and license information \
+ attached at the beginning.""",
+ category = Category.COMPLIANCE,
+ priority = 8,
+ // ignored by default and then explicitly overridden in SysUI's soong configuration
+ severity = Severity.IGNORE,
+ implementation =
+ Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 520c888..e09aa42 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -42,6 +42,7 @@
StaticSettingsProviderDetector.ISSUE,
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
+ MissingApacheLicenseDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 0000000..78e133f
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return MissingApacheLicenseDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ MissingApacheLicenseDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun testHasCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ /*
+ * Copyright (C) ${Year.now().value} The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testDoesntHaveCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+ // probably doesn't make much sense for license header (which is far above it) and for
+ // kotlin files that can have several classes. If someone really wants to omit header
+ // they can do it with //noinspection
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectContains("License header is missing")
+ }
+}