1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.ui.text.lint 18 19 import com.android.tools.lint.detector.api.Category 20 import com.android.tools.lint.detector.api.Detector 21 import com.android.tools.lint.detector.api.Implementation 22 import com.android.tools.lint.detector.api.Issue 23 import com.android.tools.lint.detector.api.JavaContext 24 import com.android.tools.lint.detector.api.LintFix 25 import com.android.tools.lint.detector.api.Scope 26 import com.android.tools.lint.detector.api.Severity 27 import com.android.tools.lint.detector.api.SourceCodeScanner 28 import com.intellij.psi.PsiMethod 29 import java.util.EnumSet 30 import org.jetbrains.uast.UCallExpression 31 import org.jetbrains.uast.getParameterForArgument 32 import org.jetbrains.uast.skipParenthesizedExprDown 33 34 class LocaleInvalidLanguageTagDetector : Detector(), SourceCodeScanner { getApplicableConstructorTypesnull35 override fun getApplicableConstructorTypes() = 36 listOf("androidx.compose.ui.text.intl.LocaleList", "androidx.compose.ui.text.intl.Locale") 37 38 override fun visitConstructor( 39 context: JavaContext, 40 node: UCallExpression, 41 constructor: PsiMethod 42 ) { 43 val languageTag = 44 node.valueArguments 45 .find { 46 val name = node.getParameterForArgument(it)?.name 47 name == "languageTag" || name == "languageTags" 48 } 49 ?.skipParenthesizedExprDown() 50 51 val localeValue = languageTag?.evaluate() as? String ?: return 52 val localeInvalid = localeValue.contains('_') 53 54 if (localeInvalid) { 55 val fixedLocaleValue = localeValue.replace('_', '-') 56 context.report( 57 InvalidLanguageTagDelimiter, 58 context.getLocation(languageTag), 59 "A hyphen (-), not an underscore (_) delimiter should be used in a language tag", 60 LintFix.create() 61 .replace() 62 .name("Change $localeValue to $fixedLocaleValue") 63 .text(localeValue) 64 .with(fixedLocaleValue) 65 .autoFix() 66 .build() 67 ) 68 } 69 } 70 71 companion object { 72 val InvalidLanguageTagDelimiter = 73 Issue.create( 74 id = "InvalidLanguageTagDelimiter", 75 briefDescription = "Undercore (_) is an unsupported delimiter for subtags", 76 explanation = 77 "A language tag must be compliant with IETF BCP47, specifically a " + 78 "sequence of subtags must be separated by hyphens (-) instead of underscores (_)", 79 category = Category.CORRECTNESS, 80 priority = 3, 81 severity = Severity.ERROR, 82 implementation = 83 Implementation( 84 LocaleInvalidLanguageTagDetector::class.java, 85 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) 86 ) 87 ) 88 } 89 } 90