diff --git a/plugins/htmltools/src/com/intellij/htmltools/xml/util/HtmlReferenceProvider.java b/plugins/htmltools/src/com/intellij/htmltools/xml/util/HtmlReferenceProvider.java index 7d8d0a1752c3..1fcab9b179b3 100644 --- a/plugins/htmltools/src/com/intellij/htmltools/xml/util/HtmlReferenceProvider.java +++ b/plugins/htmltools/src/com/intellij/htmltools/xml/util/HtmlReferenceProvider.java @@ -3,6 +3,7 @@ package com.intellij.htmltools.xml.util; import com.intellij.codeInsight.lookup.AutoCompletionPolicy; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.html.impl.providers.HtmlAttributeValueProvider; import com.intellij.html.impl.util.MicrodataUtil; import com.intellij.lang.Language; import com.intellij.openapi.fileTypes.FileType; @@ -31,6 +32,7 @@ import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.JBIterable; import com.intellij.util.io.URLUtil; import com.intellij.xml.util.HtmlUtil; import org.intellij.images.fileTypes.ImageFileTypeManager; @@ -44,6 +46,8 @@ import org.jetbrains.annotations.Nullable; import java.util.*; +import static com.intellij.util.ObjectUtils.doIfNotNull; + public class HtmlReferenceProvider extends PsiReferenceProvider { @NonNls private static final String NAME_ATTR_LOCAL_NAME = "name"; @NonNls private static final String USEMAP_ATTR_NAME = "usemap"; @@ -52,7 +56,7 @@ public class HtmlReferenceProvider extends PsiReferenceProvider { @NonNls private static final String SRC_ATTR_NAME = "src"; @NonNls private static final String JAVASCRIPT_PREFIX = "javascript:"; - private static final FileType[] IMAGE_FILE_TYPES = new FileType[]{ImageFileTypeManager.getInstance().getImageFileType()}; + public static final FileType[] IMAGE_FILE_TYPES = new FileType[]{ImageFileTypeManager.getInstance().getImageFileType()}; public static final String LABELLEDBY = "aria-labelledby"; public static ElementFilter getFilter() { @@ -284,7 +288,6 @@ public class HtmlReferenceProvider extends PsiReferenceProvider { return fileType == null ? null : new FileType[]{fileType}; } - @SuppressWarnings({"HardCodedStringLiteral"}) public static String[] getAttributeValues() { return new String[] {SRC_ATTR_NAME, HREF_ATTRIBUTE_NAME, USEMAP_ATTR_NAME, "action", "background", "width", "height", "type", "bgcolor", "color", "vlink", "link", "alink", "text", "name", HtmlUtil.ID_ATTRIBUTE_NAME, HtmlUtil.CLASS_ATTRIBUTE_NAME, FOR_ATTR_NAME, MicrodataUtil.ITEM_REF, "data", "poster", "srcset", @@ -388,9 +391,15 @@ public class HtmlReferenceProvider extends PsiReferenceProvider { @Nullable public static ImageInfoReader.Info getImageInfo(@NotNull final XmlTag tag) { return CachedValuesManager.getCachedValue(tag, () -> { - final XmlAttribute src = tag.getAttribute(SRC_ATTR_NAME, null); - if (src != null) { - final PsiFile psiFile = FileReferenceUtil.findFile(src.getValueElement()); + PsiElement srcValue = JBIterable.from(HtmlAttributeValueProvider.EP_NAME.getExtensionList()) + .filterMap(it -> it.getCustomAttributeValue(tag, SRC_ATTR_NAME)) + .first(); + if (srcValue == null) { + var attr = tag.getAttribute(SRC_ATTR_NAME, null); + srcValue = attr != null ? attr.getValueElement() : null; + } + if (srcValue != null) { + final PsiFile psiFile = FileReferenceUtil.findFile(srcValue); if (psiFile != null) { final VirtualFile virtualFile = psiFile.getVirtualFile(); if (virtualFile instanceof VirtualFileWithId) { @@ -398,19 +407,19 @@ public class HtmlReferenceProvider extends PsiReferenceProvider { if (value == null) return null; return CachedValueProvider.Result.create( new ImageInfoReader.Info(value.width, value.height, value.bpp, IfsUtil.isSVG(virtualFile)), - src, + srcValue, virtualFile ); } } - final String srcValue = src.getValue(); - if (srcValue != null && URLUtil.isDataUri(srcValue)) { - final byte[] bytesFromDataUri = URLUtil.getBytesFromDataUri(srcValue); + final String srcValueText = doIfNotNull(srcValue.getText(), StringUtil::unquoteString); + if (srcValueText != null && URLUtil.isDataUri(srcValueText)) { + final byte[] bytesFromDataUri = URLUtil.getBytesFromDataUri(srcValueText); if (bytesFromDataUri != null) { try { ImageInfoReader.Info info = ImageInfoReader.getInfo(bytesFromDataUri); if (info != null) { - return CachedValueProvider.Result.create(info, src); + return CachedValueProvider.Result.create(info, srcValue); } } catch (Exception ignored) { diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RequiredAttributesInspectionBase.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RequiredAttributesInspectionBase.java index 3c85d2c56395..c08dc9c86439 100644 --- a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RequiredAttributesInspectionBase.java +++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/RequiredAttributesInspectionBase.java @@ -18,6 +18,7 @@ package com.intellij.codeInspection.htmlInspections; import com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightingAwareElementDescriptor; import com.intellij.codeInspection.*; import com.intellij.codeInspection.util.InspectionMessage; +import com.intellij.html.impl.providers.HtmlAttributeValueProvider; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.NlsSafe; @@ -26,6 +27,7 @@ import com.intellij.psi.html.HtmlTag; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; +import com.intellij.util.containers.JBIterable; import com.intellij.xml.XmlAttributeDescriptor; import com.intellij.xml.XmlElementDescriptor; import com.intellij.xml.XmlExtension; @@ -113,8 +115,10 @@ public class RequiredAttributesInspectionBase extends HtmlLocalInspectionTool im if (!hasAttribute(tag, attrName) && !XmlExtension.getExtension(tag.getContainingFile()).isRequiredAttributeImplicitlyPresent(tag, attrName)) { - LocalQuickFix insertRequiredAttributeIntention = isOnTheFly ? XmlQuickFixFactory.getInstance().insertRequiredAttributeFix(tag, attrName) : null; - final String localizedMessage = XmlAnalysisBundle.message("xml.inspections.element.doesnt.have.required.attribute", name, attrName); + LocalQuickFix insertRequiredAttributeIntention = + isOnTheFly ? XmlQuickFixFactory.getInstance().insertRequiredAttributeFix(tag, attrName) : null; + final String localizedMessage = + XmlAnalysisBundle.message("xml.inspections.element.doesnt.have.required.attribute", name, attrName); reportOneTagProblem( tag, attrName, @@ -130,6 +134,10 @@ public class RequiredAttributesInspectionBase extends HtmlLocalInspectionTool im } private static boolean hasAttribute(XmlTag tag, String attrName) { + if (JBIterable.from(HtmlAttributeValueProvider.EP_NAME.getExtensionList()) + .filterMap(it -> it.getCustomAttributeValue(tag, attrName)).first() != null) { + return true; + } final XmlAttribute attribute = tag.getAttribute(attrName); if (attribute == null) return false; if (attribute.getValueElement() != null) return true; @@ -149,17 +157,17 @@ public class RequiredAttributesInspectionBase extends HtmlLocalInspectionTool im if (tag instanceof HtmlTag) { htmlTag = true; - if(isAdditionallyDeclared(getAdditionalEntries(), name)) return; + if (isAdditionallyDeclared(getAdditionalEntries(), name)) return; } LocalQuickFix[] fixes; ProblemHighlightType highlightType; if (htmlTag) { - fixes = basicIntention == null ? new LocalQuickFix[] {addAttributeFix} : new LocalQuickFix[]{addAttributeFix, basicIntention}; + fixes = basicIntention == null ? new LocalQuickFix[]{addAttributeFix} : new LocalQuickFix[]{addAttributeFix, basicIntention}; highlightType = isInjectedWithoutValidation(tag) ? ProblemHighlightType.INFORMATION : ProblemHighlightType.GENERIC_ERROR_OR_WARNING; } else { - fixes = basicIntention == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[] {basicIntention}; + fixes = basicIntention == null ? LocalQuickFix.EMPTY_ARRAY : new LocalQuickFix[]{basicIntention}; highlightType = ProblemHighlightType.ERROR; } if (isOnTheFly || highlightType != ProblemHighlightType.INFORMATION) { diff --git a/xml/impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java b/xml/xml-psi-impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java similarity index 64% rename from xml/impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java rename to xml/xml-psi-impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java index 622bcaa6c172..c7f559a6f31b 100644 --- a/xml/impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java +++ b/xml/xml-psi-impl/src/com/intellij/html/impl/providers/HtmlAttributeValueProvider.java @@ -16,7 +16,9 @@ package com.intellij.html.impl.providers; import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiElement; import com.intellij.psi.xml.XmlTag; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class HtmlAttributeValueProvider { @@ -27,9 +29,18 @@ public class HtmlAttributeValueProvider { * Calculates attribute value. Used when it is impossible to get attribute value as text of PSI element * @return calculated value */ - @Nullable - public String getCustomAttributeValues(final XmlTag tag, final String attributeName) { + public @Nullable String getCustomAttributeValues(@NotNull XmlTag tag, @NotNull String attributeName) { return null; } + + /** + * Allows to provide an attribute value from a different non-standard attribute to work with in place of a regular one. + * It is useful in frameworks, e.g. to provide a directive attribute value instead of {@code src} attribute of {@code } tag. + * @return a custom attribute to use instead of a standard one + */ + public @Nullable PsiElement getCustomAttributeValue(@NotNull XmlTag tag, @NotNull String attributeName) { + return null; + } + }