mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[ui] IJPL-229294 Inspection for unresolved color names in .theme.json files
GitOrigin-RevId: f2b7cc3794843e8a95927d92bacc10780de6ce10
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4564251081
commit
ab3eea633b
+34
-2
@@ -2,13 +2,17 @@
|
||||
package org.jetbrains.idea.devkit.inspections
|
||||
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import org.jetbrains.idea.devkit.themes.UnresolvedThemeJsonNamedColorInspection
|
||||
import org.jetbrains.idea.devkit.themes.UnresolvedIntelliJThemeKeyInspection
|
||||
|
||||
class UnresolvedThemeKeyInspectionTest : BasePlatformTestCase() {
|
||||
class JsonThemeInspectionTest : BasePlatformTestCase() {
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.enableInspections(UnresolvedIntelliJThemeKeyInspection::class.java)
|
||||
myFixture.enableInspections(
|
||||
UnresolvedIntelliJThemeKeyInspection::class.java,
|
||||
UnresolvedThemeJsonNamedColorInspection::class.java
|
||||
)
|
||||
}
|
||||
|
||||
fun testRegularKeys() {
|
||||
@@ -69,4 +73,32 @@ class UnresolvedThemeKeyInspectionTest : BasePlatformTestCase() {
|
||||
""".trimIndent())
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
|
||||
fun testColorNamesInThemeFiles() {
|
||||
myFixture.configureByText("test.theme.json", """
|
||||
{
|
||||
"name": "Test Theme",
|
||||
"colors": {
|
||||
"registered-color": "#000000"
|
||||
},
|
||||
"ui": {
|
||||
"ActionButton.focusedBorderColor": "registered-color",
|
||||
"ActionButton.separatorColor": "#ffffff",
|
||||
"ActionButton": {
|
||||
"pressedBackground": "<error descr="Cannot resolve symbol 'unregistered-color'">unregistered-color</error>",
|
||||
"hoverBackground": "registered-color"
|
||||
}
|
||||
},
|
||||
"ProjectGradients": {
|
||||
"Group1": {
|
||||
"DiagonalGradient": {
|
||||
"Color1": "registered-color",
|
||||
"Color2": "<error descr="Cannot resolve symbol 'unregistered-color'">unregistered-color</error>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
myFixture.checkHighlighting()
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports unresolved theme named colors in IntelliJ theme files (<code>*.theme.json</code>).
|
||||
<p>
|
||||
Named colors are defined in the <code>colors</code> section of the theme file and can be referenced
|
||||
in the <code>ui</code> section or gradient definitions.
|
||||
</p>
|
||||
<p>
|
||||
See <a href="https://plugins.jetbrains.com/docs/intellij/themes-metadata.html">Exposing Theme Metadata</a> for more information.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -20,6 +20,11 @@
|
||||
groupBundle="messages.DevKitBundle" groupPathKey="inspections.group.path" groupKey="inspections.group.code"
|
||||
implementationClass="org.jetbrains.idea.devkit.themes.UnresolvedIntelliJThemeKeyInspection"
|
||||
bundle="messages.DevKitThemesBundle" key="inspection.unresolved.theme.key.display.name"/>
|
||||
<localInspection language="JSON" shortName="UnresolvedIntelliJThemeJsonNamedColor"
|
||||
enabledByDefault="true" level="ERROR"
|
||||
groupBundle="messages.DevKitBundle" groupPathKey="inspections.group.path" groupKey="inspections.group.code"
|
||||
implementationClass="org.jetbrains.idea.devkit.themes.UnresolvedThemeJsonNamedColorInspection"
|
||||
bundle="messages.DevKitThemesBundle" key="inspection.unresolved.theme.json.named.color.display.name"/>
|
||||
<localInspection language="UAST" shortName="UnregisteredNamedColor"
|
||||
projectType="INTELLIJ_PLUGIN"
|
||||
groupBundle="messages.DevKitBundle" groupPathKey="inspections.group.path" groupKey="inspections.group.code"
|
||||
|
||||
@@ -19,6 +19,7 @@ theme.json.documentation.key.deprecated=Deprecated
|
||||
theme.json.documentation.key.no.description=(no description)
|
||||
theme.json.documentation.section.source.title=Source:
|
||||
theme.json.documentation.section.since.title=Since:
|
||||
inspection.unresolved.theme.json.named.color.display.name=Unresolved theme named color
|
||||
inspection.unregistered.named.color.display.name=Unregistered named color
|
||||
inspections.unregistered.named.color=<html>Named color key ''{0}'' is not registered in ''*.themeMetadata.json'' (<a href="https://plugins.jetbrains.com/docs/intellij/themes-metadata.html?from=DevkitUnregisteredNamedColorInspection">Documentation</a>)</html>
|
||||
inspections.unregistered.named.color.fix.navigate.theme.metadata.file=Navigate to Theme metadata file
|
||||
|
||||
@@ -52,7 +52,7 @@ final class ThemeColorAnnotator implements Annotator, DumbAware {
|
||||
|
||||
JsonStringLiteral literal = (JsonStringLiteral)element;
|
||||
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
|
||||
.gutterIconRenderer(new MyRenderer(literal.getValue(), literal)).create();
|
||||
.gutterIconRenderer(new MyRenderer(literal.getValue(), literal)).create();
|
||||
}
|
||||
|
||||
private static boolean isColorLineMarkerProviderEnabled() {
|
||||
@@ -72,18 +72,17 @@ final class ThemeColorAnnotator implements Annotator, DumbAware {
|
||||
return isColorCode(text) || isNamedColor(text);
|
||||
}
|
||||
|
||||
private static boolean isNamedColor(String text) {
|
||||
static boolean isNamedColor(String text) {
|
||||
return StringUtil.isLatinAlphanumeric(text);
|
||||
}
|
||||
|
||||
private static boolean isColorCode(@Nullable String text) {
|
||||
static boolean isColorCode(@Nullable String text) {
|
||||
if (!StringUtil.startsWithChar(text, '#')) return false;
|
||||
//noinspection ConstantConditions - StringUtil#startsWithChar checks for null
|
||||
if (text.length() != HEX_COLOR_LENGTH_RGB && text.length() != HEX_COLOR_LENGTH_RGBA) return false;
|
||||
return COLOR_HEX_PATTERN_RGB.matcher(text).matches() || COLOR_HEX_PATTERN_RGBA.matcher(text).matches();
|
||||
}
|
||||
|
||||
|
||||
private static final class MyRenderer extends GutterIconRenderer implements DumbAware {
|
||||
private static final int ICON_SIZE = 12;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class ThemeJsonNamedColorPsiReference extends JsonSchemaBaseReference<JsonLiteral> {
|
||||
private final String myName;
|
||||
|
||||
+42
-7
@@ -3,25 +3,60 @@ package org.jetbrains.idea.devkit.themes;
|
||||
|
||||
import com.intellij.json.psi.JsonLiteral;
|
||||
import com.intellij.json.psi.JsonProperty;
|
||||
import com.intellij.json.psi.JsonStringLiteral;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.PsiReferenceProvider;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Konstantin Bulenkov
|
||||
*/
|
||||
class ThemeJsonNamedColorPsiReferenceProvider extends PsiReferenceProvider {
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class ThemeJsonNamedColorPsiReferenceProvider extends PsiReferenceProvider {
|
||||
private static final Pattern COLOR_N_PATTERN = Pattern.compile("Color\\d+");
|
||||
|
||||
@Override
|
||||
public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
|
||||
if (!(element instanceof JsonLiteral literal)) return PsiReference.EMPTY_ARRAY;
|
||||
|
||||
if (element instanceof JsonStringLiteral
|
||||
&& ThemeColorAnnotator.isColorCode(((JsonStringLiteral)element).getValue())) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
PsiElement parent = literal.getParent();
|
||||
if (!(parent instanceof JsonProperty property)) return PsiReference.EMPTY_ARRAY;
|
||||
if (parent instanceof JsonProperty property) {
|
||||
String name = property.getName();
|
||||
if (property.getValue() == literal) { // inside value of property
|
||||
if (isKeyInteresting(name)) {
|
||||
return new PsiReference[]{new ThemeJsonNamedColorPsiReference(literal)};
|
||||
}
|
||||
|
||||
if (property.getValue() != literal) return PsiReference.EMPTY_ARRAY;
|
||||
PsiElement grandParent = property.getParent();
|
||||
if (grandParent != null) {
|
||||
PsiElement greatGrandParent = grandParent.getParent();
|
||||
if (greatGrandParent instanceof JsonProperty parentProperty) {
|
||||
String parentName = parentProperty.getName();
|
||||
if (COLOR_N_PATTERN.matcher(parentName).matches() || isKeyInteresting(parentName)) {
|
||||
return new PsiReference[]{new ThemeJsonNamedColorPsiReference(literal)};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PsiReference[]{new ThemeJsonNamedColorPsiReference(literal)};
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
private static boolean isKeyInteresting(String name) {
|
||||
return name.endsWith("Foreground")
|
||||
|| name.endsWith("Background")
|
||||
|| name.endsWith("Color")
|
||||
|| name.endsWith(".foreground")
|
||||
|| name.endsWith(".background")
|
||||
|| name.endsWith("color")
|
||||
|| "foreground".equals(name)
|
||||
|| "background".equals(name)
|
||||
|| COLOR_N_PATTERN.matcher(name).matches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ import com.intellij.psi.PsiReferenceRegistrar;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.patterns.PlatformPatterns.psiElement;
|
||||
|
||||
final class ThemeJsonPsiReferenceContributor extends PsiReferenceContributor {
|
||||
@Override
|
||||
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
|
||||
registrar.registerReferenceProvider(
|
||||
PlatformPatterns.psiElement(JsonStringLiteral.class)
|
||||
psiElement(JsonStringLiteral.class)
|
||||
.inVirtualFile(PlatformPatterns.virtualFile().with(new PatternCondition<>("theme.json") {
|
||||
@Override
|
||||
public boolean accepts(@NotNull VirtualFile file, ProcessingContext context) {
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.idea.devkit.themes;
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.json.psi.JsonElementVisitor;
|
||||
import com.intellij.json.psi.JsonLiteral;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class UnresolvedThemeJsonNamedColorInspection extends LocalInspectionTool {
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
if (!ThemeJsonUtil.isThemeFilename(holder.getFile().getName())) return PsiElementVisitor.EMPTY_VISITOR;
|
||||
|
||||
return new JsonElementVisitor() {
|
||||
@Override
|
||||
public void visitLiteral(@NotNull JsonLiteral literal) {
|
||||
if (literal.getTextLength() < 2) return;
|
||||
for (PsiReference reference : literal.getReferences()) {
|
||||
if (reference instanceof ThemeJsonNamedColorPsiReference) {
|
||||
if (reference.resolve() == null) {
|
||||
TextRange range = reference.getRangeInElement();
|
||||
if (range.isEmpty() && range.getStartOffset() == 1 && "\"\"".equals(literal.getText())) {
|
||||
String message = ProblemsHolder.unresolvedReferenceMessage(reference);
|
||||
holder.registerProblem(literal, message, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL, TextRange.create(0, 2));
|
||||
}
|
||||
else {
|
||||
holder.registerProblem(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user