Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override Required Attributes Inspection for x-bind #46

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package com.github.inxilpro.intellijalpine

import com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor
import com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightingAwareElementDescriptor
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.XmlQuickFixFactory
import com.intellij.codeInspection.htmlInspections.RequiredAttributesInspection
import com.intellij.codeInspection.util.InspectionMessage
import com.intellij.html.impl.providers.HtmlAttributeValueProvider
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.html.HtmlTag
import com.intellij.psi.xml.XmlTag
import com.intellij.util.containers.JBIterable
import com.intellij.xml.XmlExtension
import com.intellij.xml.analysis.XmlAnalysisBundle
import com.intellij.xml.impl.schema.AnyXmlElementDescriptor
import com.intellij.xml.util.HtmlUtil
import com.intellij.xml.util.XmlTagUtil
import com.intellij.xml.util.XmlUtil
import java.util.*

/**
* Essentially a copy-paste from RequiredAttributesInspection,
* but with the addition of a check for any bounded required attributes, like ":src" in <img/> tags.
*/
class AlpineRequiredAttributesInspection : RequiredAttributesInspection() {
override fun checkTag(tag: XmlTag, holder: ProblemsHolder, isOnTheFly: Boolean) {
val name = tag.name
var elementDescriptor = XmlUtil.getDescriptorFromContext(tag)
if (elementDescriptor is AnyXmlElementDescriptor || elementDescriptor == null) {
elementDescriptor = tag.descriptor
}

if (elementDescriptor == null) return
if (elementDescriptor is XmlHighlightingAwareElementDescriptor &&
!(elementDescriptor as XmlHighlightingAwareElementDescriptor).shouldCheckRequiredAttributes()) {
return
}

val attributeDescriptors = elementDescriptor.getAttributesDescriptors(tag)
var requiredAttributes: MutableSet<String>? = null

for (attribute in attributeDescriptors) {
if (attribute != null && attribute.isRequired) {
if (requiredAttributes == null) {
requiredAttributes = HashSet()
}
requiredAttributes.add(attribute.getName(tag))
}
}

if (requiredAttributes != null) {
for (attrName in requiredAttributes) {
if (!hasAttribute(tag, attrName) && AttributeUtil.bindPrefixes.all { !hasAttribute(tag, it + attrName) } &&
!XmlExtension.getExtension(tag.containingFile).isRequiredAttributeImplicitlyPresent(tag, attrName)) {
val insertRequiredAttributeIntention: LocalQuickFix? = if (isOnTheFly) XmlQuickFixFactory.getInstance().insertRequiredAttributeFix(tag, attrName) else null
val localizedMessage = XmlAnalysisBundle.message("xml.inspections.element.doesnt.have.required.attribute", name, attrName)
reportOneTagProblem(
tag,
attrName,
localizedMessage,
insertRequiredAttributeIntention,
holder,
getIntentionAction(attrName),
isOnTheFly
)
}
}
}
}

private fun hasAttribute(tag: XmlTag, attrName: String): Boolean {
if (JBIterable.from(HtmlAttributeValueProvider.EP_NAME.extensionList)
.filterMap { it: HtmlAttributeValueProvider -> it.getCustomAttributeValue(tag, attrName) }.first() != null) {
return true
}
val attribute = tag.getAttribute(attrName) ?: return false
if (attribute.valueElement != null) return true
if (tag !is HtmlTag) return false
val descriptor = attribute.descriptor
return descriptor != null && HtmlUtil.isBooleanAttribute(descriptor, tag)
}

private fun reportOneTagProblem(
tag: XmlTag,
name: String,
localizedMessage: @InspectionMessage String,
basicIntention: LocalQuickFix?,
holder: ProblemsHolder,
addAttributeFix: LocalQuickFix,
isOnTheFly: Boolean,
) {
var htmlTag = false
if (tag is HtmlTag) {
htmlTag = true
if (isAdditionallyDeclared(additionalEntries, name)) return
}
val fixes: Array<LocalQuickFix>
val highlightType: ProblemHighlightType
if (htmlTag) {
fixes = if (basicIntention == null) arrayOf(addAttributeFix) else arrayOf(addAttributeFix, basicIntention)
highlightType = if (XmlHighlightVisitor.isInjectedWithoutValidation(tag)) ProblemHighlightType.INFORMATION else ProblemHighlightType.GENERIC_ERROR_OR_WARNING
} else {
fixes = if (basicIntention == null) LocalQuickFix.EMPTY_ARRAY else arrayOf(basicIntention)
highlightType = ProblemHighlightType.ERROR
}
if (isOnTheFly || highlightType != ProblemHighlightType.INFORMATION) {
addElementsForTag(tag, localizedMessage, highlightType, holder, isOnTheFly, *fixes)
}
}

private fun addElementsForTag(
tag: XmlTag,
message: String,
error: ProblemHighlightType,
holder: ProblemsHolder,
isOnTheFly: Boolean,
vararg fixes: LocalQuickFix,
) {
val start = XmlTagUtil.getStartTagNameElement(tag) as PsiElement? ?: return
holder.registerProblem(start, message, error, *fixes)

if (isOnTheFly) {
val end = XmlTagUtil.getEndTagNameElement(tag) as PsiElement? ?: return
holder.registerProblem(end, message, error, *fixes)
}
}

private fun isAdditionallyDeclared(additional: String, name: String): Boolean {
val newName = StringUtil.toLowerCase(name)
if (!additional.contains(newName)) return false
val tokenizer = StringTokenizer(additional, ", ")
while (tokenizer.hasMoreTokens()) {
if (newName == tokenizer.nextToken()) {
return true
}
}
return false
}
}
4 changes: 4 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@
displayName="Alpine.js"/>
<gotoDeclarationHandler implementation="com.github.inxilpro.intellijalpine.AlpineGotoDeclarationHandler"
order="first"/>
<localInspection language="XML" shortName="RequiredAttributes" enabledByDefault="true" level="WARNING"
bundle="messages.XmlBundle" key="xml.inspections.required.attributes.display.name"
groupBundle="messages.XmlBundle" groupKey="html.inspections.group.name"
implementationClass="com.github.inxilpro.intellijalpine.AlpineRequiredAttributesInspection"/>
</extensions>
</idea-plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
Reports a missing mandatory attribute in an XML/HTML tag. Suggests configuring attributes that should not be reported.
</body>
</html>
Loading