mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
I18nInspection: Kotlin interpolated strings support (IDEA-234780)
GitOrigin-RevId: 1f9e01a0740fe1ba969db9af40a04651ebcfb8fe
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6a6f45b597
commit
3c6c5ad37a
@@ -33,6 +33,17 @@ class PartiallyKnownString(val segments: List<StringEntry>) {
|
||||
return stringBuffer.toString()
|
||||
}
|
||||
|
||||
val concatenationOfKnown: String
|
||||
get() {
|
||||
(segments.singleOrNull() as? StringEntry.Known)?.let { return it.value }
|
||||
|
||||
val stringBuffer = StringBuffer()
|
||||
for (segment in segments) {
|
||||
if (segment is StringEntry.Known) stringBuffer.append(segment.value)
|
||||
}
|
||||
return stringBuffer.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String = segments.joinToString { segment ->
|
||||
when (segment) {
|
||||
is StringEntry.Known -> segment.value
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
<orderEntry type="library" scope="RUNTIME" name="KotlinPlugin" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="kotlin-stdlib-jdk8" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.devkit.java.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.java.i18n" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.idea.devkit.kotlin.inspections
|
||||
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
|
||||
|
||||
class KtI18NInspectionTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(com.intellij.codeInspection.i18n.I18nInspection())
|
||||
}
|
||||
|
||||
fun testFunctionParameters() {
|
||||
myFixture.configureByText("Foo.kt", """
|
||||
class Foo {
|
||||
fun foo(s: String) {
|
||||
foo(<warning descr="Hardcoded string literal: \"text\"">"text"</warning>)
|
||||
foo(<warning descr="Hardcoded string literal: \"templated ${"\$"}s end\"">"templated ${"\$"}s end"</warning>)
|
||||
foo(<warning descr="Hardcoded string literal: \"concatenated \"">"concatenated "</warning> + s + <warning descr="Hardcoded string literal: \" end\"">" end"</warning>)
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
myFixture.testHighlighting()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.expressions.UInjectionHost;
|
||||
import org.jetbrains.uast.expressions.UStringConcatenationsFacade;
|
||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -497,16 +499,16 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
public void visitElement(@NotNull PsiElement element) {
|
||||
super.visitElement(element);
|
||||
|
||||
if (element instanceof PsiMember && ((PsiMember)element).getName() != null ||
|
||||
if (element instanceof PsiMember && ((PsiMember)element).getName() != null ||
|
||||
element instanceof PsiClassInitializer) {
|
||||
return;
|
||||
}
|
||||
|
||||
UElement uElement =
|
||||
UastContextKt.toUElementOfExpectedTypes(element, ULiteralExpression.class, UAnnotation.class);
|
||||
UastContextKt.toUElementOfExpectedTypes(element, UInjectionHost.class, UAnnotation.class);
|
||||
|
||||
if (uElement instanceof ULiteralExpression) {
|
||||
visitLiteralExpression(element, (ULiteralExpression)uElement);
|
||||
if (uElement instanceof UInjectionHost) {
|
||||
visitLiteralExpression(element, (UInjectionHost)uElement);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -519,13 +521,11 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
|
||||
element.acceptChildren(this);
|
||||
}
|
||||
|
||||
|
||||
private void visitLiteralExpression(@NotNull PsiElement sourcePsi,
|
||||
@NotNull ULiteralExpression expression) {
|
||||
Object value = expression.getValue();
|
||||
if (!(value instanceof String)) return;
|
||||
String stringValue = (String)value;
|
||||
if (stringValue.trim().isEmpty()) {
|
||||
@NotNull UInjectionHost expression) {
|
||||
String stringValue = getStringValueOfKnownPart(expression);
|
||||
if (StringUtil.isEmptyOrSpaces(stringValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -589,8 +589,18 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
}
|
||||
}
|
||||
|
||||
private static String getStringValueOfKnownPart(@NotNull UInjectionHost expression) {
|
||||
UStringConcatenationsFacade concatenationsFacade = UStringConcatenationsFacade.createFromUExpression(expression);
|
||||
if (concatenationsFacade != null) {
|
||||
return concatenationsFacade.asPartiallyKnownString().getConcatenationOfKnown();
|
||||
}
|
||||
else {
|
||||
return expression.evaluateToString();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canBeI18ned(@NotNull Project project,
|
||||
@NotNull ULiteralExpression expression,
|
||||
@NotNull UInjectionHost expression,
|
||||
@NotNull String value,
|
||||
@NotNull Set<? super PsiModifierListOwner> nonNlsTargets) {
|
||||
if (ignoreForNonAlpha && !StringUtil.containsAlphaCharacters(value)) {
|
||||
@@ -672,7 +682,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isArgOfEnumConstant(ULiteralExpression expression) {
|
||||
private static boolean isArgOfEnumConstant(UInjectionHost expression) {
|
||||
return expression.getUastParent() instanceof UEnumConstant;
|
||||
}
|
||||
|
||||
@@ -680,8 +690,8 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
myCachedNonNlsPattern = nonNlsCommentPattern.trim().isEmpty() ? null : Pattern.compile(nonNlsCommentPattern);
|
||||
}
|
||||
|
||||
private static boolean isClassRef(final ULiteralExpression expression, String value) {
|
||||
if (StringUtil.startsWithChar(value,'#')) {
|
||||
private static boolean isClassRef(final UInjectionHost expression, String value) {
|
||||
if (StringUtil.startsWithChar(value, '#')) {
|
||||
value = value.substring(1); // A favor for JetBrains team to catch common Logger usage practice.
|
||||
}
|
||||
|
||||
@@ -705,7 +715,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
|| isPackageNonNls(psiPackage.getParentPackage());
|
||||
}
|
||||
|
||||
private boolean isPassedToNonNlsVariable(@NotNull ULiteralExpression expression,
|
||||
private boolean isPassedToNonNlsVariable(@NotNull UInjectionHost expression,
|
||||
final Set<? super PsiModifierListOwner> nonNlsTargets) {
|
||||
UExpression toplevel = JavaI18nUtil.getTopLevelExpression(expression);
|
||||
PsiModifierListOwner var = null;
|
||||
@@ -773,7 +783,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
return AnnotationUtil.isAnnotated(parent, AnnotationUtil.NON_NLS, CHECK_EXTERNAL);
|
||||
}
|
||||
|
||||
private static boolean isInNonNlsEquals(ULiteralExpression expression, final Set<? super PsiModifierListOwner> nonNlsTargets) {
|
||||
private static boolean isInNonNlsEquals(UInjectionHost expression, final Set<? super PsiModifierListOwner> nonNlsTargets) {
|
||||
UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent());
|
||||
if (!(parent instanceof UQualifiedReferenceExpression)) return false;
|
||||
UExpression selector = ((UQualifiedReferenceExpression)parent).getSelector();
|
||||
@@ -845,7 +855,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isReturnedFromAnnotatedMethod(final ULiteralExpression expression,
|
||||
private static boolean isReturnedFromAnnotatedMethod(final UInjectionHost expression,
|
||||
final String fqn,
|
||||
final @Nullable Set<? super PsiModifierListOwner> nonNlsTargets) {
|
||||
PsiMethod method;
|
||||
@@ -902,7 +912,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isToString(final ULiteralExpression expression) {
|
||||
private static boolean isToString(final UInjectionHost expression) {
|
||||
final UMethod method = UastUtils.getParentOfType(expression, UMethod.class);
|
||||
if (method == null) return false;
|
||||
final PsiType returnType = method.getReturnType();
|
||||
@@ -912,7 +922,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
&& "java.lang.String".equals(returnType.getCanonicalText());
|
||||
}
|
||||
|
||||
private static boolean isArgOfJUnitAssertion(ULiteralExpression expression) {
|
||||
private static boolean isArgOfJUnitAssertion(UInjectionHost expression) {
|
||||
final UElement parent = UastUtils.skipParenthesizedExprUp(expression.getUastParent());
|
||||
if (parent == null || !UastExpressionUtils.isMethodCall(parent)) {
|
||||
return false;
|
||||
@@ -938,7 +948,7 @@ public class I18nInspection extends AbstractBaseUastLocalInspectionTool implemen
|
||||
InheritanceUtil.isInheritor(containingClass, "junit.framework.Assert");
|
||||
}
|
||||
|
||||
private static boolean isArgOfSpecifiedExceptionConstructor(ULiteralExpression expression,
|
||||
private static boolean isArgOfSpecifiedExceptionConstructor(UInjectionHost expression,
|
||||
String[] specifiedExceptions) {
|
||||
if (specifiedExceptions.length == 0) return false;
|
||||
|
||||
|
||||
@@ -16,18 +16,16 @@ import org.jetbrains.uast.*
|
||||
*
|
||||
* @see PartiallyKnownString
|
||||
*/
|
||||
class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext: UExpression) {
|
||||
class UStringConcatenationsFacade private constructor(private val uContext: UExpression, val uastOperands: Sequence<UExpression>) {
|
||||
|
||||
val uastOperands: Sequence<UExpression> = run {
|
||||
when {
|
||||
uContext is UPolyadicExpression -> uContext.operands.asSequence()
|
||||
uContext is ULiteralExpression && uContext.uastParent !is UPolyadicExpression -> {
|
||||
val host = uContext.sourceInjectionHost
|
||||
if (host == null || !host.isValidHost) emptySequence() else sequenceOf(uContext)
|
||||
}
|
||||
else -> emptySequence()
|
||||
}
|
||||
}
|
||||
@Deprecated("use factory method `UStringConcatenationsFacade.createFromUExpression`",
|
||||
ReplaceWith("UStringConcatenationsFacade.createFromUExpression"))
|
||||
@ApiStatus.Experimental
|
||||
constructor(uContext: UExpression) : this(uContext, buildLazyUastOperands(uContext) ?: emptySequence())
|
||||
|
||||
@get:ApiStatus.Experimental
|
||||
val rootUExpression: UExpression
|
||||
get() = uContext
|
||||
|
||||
val psiLanguageInjectionHosts: Sequence<PsiLanguageInjectionHost> =
|
||||
uastOperands.mapNotNull { (it as? ULiteralExpression)?.psiLanguageInjectionHost }.distinct()
|
||||
@@ -101,13 +99,29 @@ class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext:
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun buildLazyUastOperands(uContext: UExpression?): Sequence<UExpression>? = when {
|
||||
uContext is UPolyadicExpression && isConcatenation(uContext) -> uContext.operands.asSequence()
|
||||
uContext is ULiteralExpression && !isConcatenation(uContext.uastParent) -> {
|
||||
val host = uContext.sourceInjectionHost
|
||||
if (host == null || !host.isValidHost) emptySequence() else sequenceOf(uContext)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun create(context: PsiElement): UStringConcatenationsFacade? {
|
||||
if (context !is PsiLanguageInjectionHost && context.firstChild !is PsiLanguageInjectionHost) {
|
||||
fun createFromUExpression(uContext: UExpression?): UStringConcatenationsFacade? {
|
||||
val operands = buildLazyUastOperands(uContext) ?: return null
|
||||
return UStringConcatenationsFacade(uContext!!, operands)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun create(context: PsiElement?): UStringConcatenationsFacade? {
|
||||
if (context == null || context !is PsiLanguageInjectionHost && context.firstChild !is PsiLanguageInjectionHost) {
|
||||
return null
|
||||
}
|
||||
val uElement = context.toUElement(UExpression::class.java) ?: return null
|
||||
return UStringConcatenationsFacade(uElement)
|
||||
val uElement = context.toUElementOfType<UExpression>() ?: return null
|
||||
return createFromUExpression(uElement)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,13 @@ val UExpression.allPsiLanguageInjectionHosts: List<PsiLanguageInjectionHost>
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
fun isConcatenation(uExpression: UElement?): Boolean {
|
||||
if (uExpression !is UPolyadicExpression) return false
|
||||
if (uExpression.operator != UastBinaryOperator.PLUS) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a non-strict parent [PsiLanguageInjectionHost] for [sourcePsi] of given literal expression if it exists.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user