From 4ca9b677e59f149df17999569587a3a36c0ba4e7 Mon Sep 17 00:00:00 2001 From: Alexander Zolotov Date: Tue, 23 Dec 2014 20:35:24 +0300 Subject: [PATCH] HTML: add unknown boolean attribute inspection --- .../src/messages/XmlBundle.properties | 4 + .../src/META-INF/XmlPlugin.xml | 3 + .../XmlQuickFixFactoryImpl.java | 8 ++ .../AddAttributeValueIntentionFix.java | 84 ++++++++++++++ ...HtmlUnknownBooleanAttributeInspection.java | 34 ++++++ .../codeInspection/XmlQuickFixFactory.java | 4 + .../HtmlUnknownBooleanAttribute.html | 6 + .../EmptyXmlQuickFixFactory.java | 7 ++ ...UnknownBooleanAttributeInspectionBase.java | 103 ++++++++++++++++++ .../messages/XmlErrorMessages.properties | 2 + .../XmlEntitiesInspection.java | 1 + .../src/com/intellij/xml/util/HtmlUtil.java | 32 ++++-- 12 files changed, 278 insertions(+), 10 deletions(-) create mode 100644 xml/impl/src/com/intellij/codeInspection/htmlInspections/AddAttributeValueIntentionFix.java create mode 100644 xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspection.java create mode 100644 xml/xml-analysis-impl/resources/inspectionDescriptions/HtmlUnknownBooleanAttribute.html create mode 100644 xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java diff --git a/platform/platform-resources-en/src/messages/XmlBundle.properties b/platform/platform-resources-en/src/messages/XmlBundle.properties index 53c4fc55aa1f7..139f86b8521b7 100644 --- a/platform/platform-resources-en/src/messages/XmlBundle.properties +++ b/platform/platform-resources-en/src/messages/XmlBundle.properties @@ -71,10 +71,13 @@ html.inspections.non.existent.internet.resource.name=Non-existent web resource html.inspections.unknown.tag=Unknown HTML tag html.inspections.unknown.attribute=Unknown HTML tag attribute +html.inspections.unknown.boolean.attribute=Unknown HTML boolean tag attribute html.inspections.unknown.tag.checkbox.title=Custom HTML tags: html.inspections.unknown.tag.title=Edit custom tags html.inspections.unknown.tag.attribute.checkbox.title=Custom HTML tag attributes: +html.inspections.unknown.tag.boolean.attribute.checkbox.title=Custom HTML boolean tag attributes: html.inspections.unknown.tag.attribute.title=Edit custom attributes +html.inspections.unknown.tag.boolean.attribute.title=Edit custom boolean attributes xml.schema.create.complex.type.intention.name=Create Complex Type {0} xml.schema.create.attribute.intention.name=Create Attribute {0} xml.schema.create.element.intention.name=Create Element {0} @@ -130,6 +133,7 @@ no.ignored.resources=No ignored resources custom.html.tag=Custom Html Tag add.custom.html.tag=Add {0} to custom html tags add.custom.html.attribute=Add {0} to custom html attributes +add.custom.html.boolean.attribute=Add {0} to custom html boolean attributes add.optional.html.attribute=Add {0} to not required html attributes fix.html.family=Fix Html diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index cf0e8e7b97c8c..3ada69d17e891 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -421,6 +421,9 @@ + diff --git a/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java b/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java index c095bd1cf49fe..4c647c6e0c018 100644 --- a/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java +++ b/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java @@ -17,7 +17,9 @@ import com.intellij.codeInsight.daemon.impl.analysis.CreateNSDeclarationIntentionFix; import com.intellij.codeInsight.daemon.impl.analysis.InsertRequiredAttributeFix; +import com.intellij.codeInspection.htmlInspections.AddAttributeValueIntentionFix; import com.intellij.psi.PsiElement; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; import org.jetbrains.annotations.NotNull; @@ -35,4 +37,10 @@ public LocalQuickFixAndIntentionActionOnPsiElement insertRequiredAttributeFix(@N public LocalQuickFix createNSDeclarationIntentionFix(@NotNull PsiElement element, @NotNull String namespacePrefix, @Nullable XmlToken token) { return new CreateNSDeclarationIntentionFix(element, namespacePrefix, token); } + + @NotNull + @Override + public LocalQuickFixAndIntentionActionOnPsiElement addAttributeValueFix(@NotNull XmlAttribute attribute) { + return new AddAttributeValueIntentionFix(attribute); + } } diff --git a/xml/impl/src/com/intellij/codeInspection/htmlInspections/AddAttributeValueIntentionFix.java b/xml/impl/src/com/intellij/codeInspection/htmlInspections/AddAttributeValueIntentionFix.java new file mode 100644 index 0000000000000..500fdc4f4c8b3 --- /dev/null +++ b/xml/impl/src/com/intellij/codeInspection/htmlInspections/AddAttributeValueIntentionFix.java @@ -0,0 +1,84 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.codeInspection.htmlInspections; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.daemon.XmlErrorMessages; +import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.XmlElementFactory; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlAttributeValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class AddAttributeValueIntentionFix extends LocalQuickFixAndIntentionActionOnPsiElement { + public AddAttributeValueIntentionFix(@Nullable PsiElement element) { + super(element); + } + + @NotNull + @Override + public String getText() { + return XmlErrorMessages.message("add.attribute.value.quickfix.text"); + } + + @Override + @NotNull + public String getFamilyName() { + return getName(); + } + + @Override + public void invoke(@NotNull Project project, + @NotNull PsiFile file, + @Nullable("is null when called from inspection") final Editor editor, + @NotNull PsiElement startElement, + @NotNull PsiElement endElement) { + final XmlAttribute attribute = PsiTreeUtil.getNonStrictParentOfType(startElement, XmlAttribute.class); + if (attribute == null || attribute.getValue() != null) { + return; + } + + if (!FileModificationService.getInstance().prepareFileForWrite(attribute.getContainingFile())) { + return; + } + + new WriteCommandAction(project) { + @Override + protected void run(@NotNull final Result result) { + final XmlAttribute attributeWithValue = XmlElementFactory.getInstance(getProject()).createXmlAttribute(attribute.getName(), ""); + final PsiElement newAttribute = attribute.replace(attributeWithValue); + + if (editor != null && newAttribute != null && newAttribute instanceof XmlAttribute && newAttribute.isValid()) { + final XmlAttributeValue valueElement = ((XmlAttribute)newAttribute).getValueElement(); + if (valueElement != null) { + editor.getCaretModel().moveToOffset(valueElement.getTextOffset()); + AutoPopupController.getInstance(newAttribute.getProject()).scheduleAutoPopup(editor); + } + } + } + }.execute(); + } +} diff --git a/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspection.java b/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspection.java new file mode 100644 index 0000000000000..478669ea418fb --- /dev/null +++ b/xml/impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspection.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.intellij.codeInspection.htmlInspections; + +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +public class HtmlUnknownBooleanAttributeInspection extends HtmlUnknownBooleanAttributeInspectionBase { + + public HtmlUnknownBooleanAttributeInspection() { + super(""); + } + + @Nullable + @Override + public JComponent createOptionsPanel() { + return HtmlUnknownTagInspection.createOptionsPanel(this); + } +} diff --git a/xml/xml-analysis-api/src/com/intellij/codeInspection/XmlQuickFixFactory.java b/xml/xml-analysis-api/src/com/intellij/codeInspection/XmlQuickFixFactory.java index c0c5b10817854..3037760dc98ac 100644 --- a/xml/xml-analysis-api/src/com/intellij/codeInspection/XmlQuickFixFactory.java +++ b/xml/xml-analysis-api/src/com/intellij/codeInspection/XmlQuickFixFactory.java @@ -17,6 +17,7 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.psi.PsiElement; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; import org.jetbrains.annotations.NotNull; @@ -34,4 +35,7 @@ public static XmlQuickFixFactory getInstance() { public abstract LocalQuickFix createNSDeclarationIntentionFix(@NotNull final PsiElement element, @NotNull String namespacePrefix, @Nullable final XmlToken token); + + @NotNull + public abstract LocalQuickFixAndIntentionActionOnPsiElement addAttributeValueFix(@NotNull XmlAttribute attribute); } diff --git a/xml/xml-analysis-impl/resources/inspectionDescriptions/HtmlUnknownBooleanAttribute.html b/xml/xml-analysis-impl/resources/inspectionDescriptions/HtmlUnknownBooleanAttribute.html new file mode 100644 index 0000000000000..d926ffab1cc08 --- /dev/null +++ b/xml/xml-analysis-impl/resources/inspectionDescriptions/HtmlUnknownBooleanAttribute.html @@ -0,0 +1,6 @@ + + +This inspection highlights HTML none-boolean tag attributes without value as invalid, and lets mark such attributes as Custom to avoid highlighting them as +invalid.
+ + \ No newline at end of file diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/EmptyXmlQuickFixFactory.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/EmptyXmlQuickFixFactory.java index ce56f3923abc4..3c019d890e3ce 100644 --- a/xml/xml-analysis-impl/src/com/intellij/codeInspection/EmptyXmlQuickFixFactory.java +++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/EmptyXmlQuickFixFactory.java @@ -17,6 +17,7 @@ import com.intellij.codeInsight.intention.QuickFixes; import com.intellij.psi.PsiElement; +import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlTag; import com.intellij.psi.xml.XmlToken; import org.jetbrains.annotations.NotNull; @@ -38,4 +39,10 @@ public LocalQuickFix createNSDeclarationIntentionFix(@NotNull PsiElement element @Nullable XmlToken token) { return QuickFixes.EMPTY_ACTION; } + + @NotNull + @Override + public LocalQuickFixAndIntentionActionOnPsiElement addAttributeValueFix(@NotNull XmlAttribute attribute) { + return QuickFixes.EMPTY_FIX; + } } diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java new file mode 100644 index 0000000000000..b7fb7f2a08705 --- /dev/null +++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.htmlInspections; + +import com.intellij.codeInsight.daemon.XmlErrorMessages; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.XmlQuickFixFactory; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Key; +import com.intellij.psi.html.HtmlTag; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlTag; +import com.intellij.xml.XmlAttributeDescriptor; +import com.intellij.xml.XmlBundle; +import com.intellij.xml.XmlElementDescriptor; +import com.intellij.xml.impl.schema.AnyXmlElementDescriptor; +import com.intellij.xml.util.HtmlUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public abstract class HtmlUnknownBooleanAttributeInspectionBase extends HtmlUnknownElementInspection { + private static final Key BOOLEAN_ATTRIBUTE_KEY = Key.create(BOOLEAN_ATTRIBUTE_SHORT_NAME); + private static final Logger LOG = Logger.getInstance(HtmlUnknownBooleanAttributeInspectionBase.class); + + public HtmlUnknownBooleanAttributeInspectionBase(String defaultValues) { + super(defaultValues); + } + + @Override + @Nls + @NotNull + public String getDisplayName() { + return XmlBundle.message("html.inspections.unknown.boolean.attribute"); + } + + @Override + @NonNls + @NotNull + public String getShortName() { + return BOOLEAN_ATTRIBUTE_SHORT_NAME; + } + + @Override + protected String getCheckboxTitle() { + return XmlBundle.message("html.inspections.unknown.tag.boolean.attribute.checkbox.title"); + } + + @NotNull + @Override + protected String getPanelTitle() { + return XmlBundle.message("html.inspections.unknown.tag.boolean.attribute.title"); + } + + @Override + @NotNull + protected Logger getLogger() { + return LOG; + } + + @Override + protected void checkAttribute(@NotNull final XmlAttribute attribute, @NotNull final ProblemsHolder holder, final boolean isOnTheFly) { + if (attribute.getValueElement() == null) { + final XmlTag tag = attribute.getParent(); + + if (tag instanceof HtmlTag) { + XmlElementDescriptor elementDescriptor = tag.getDescriptor(); + if (elementDescriptor == null || elementDescriptor instanceof AnyXmlElementDescriptor) { + return; + } + + XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute); + if (attributeDescriptor != null) { + String name = attribute.getName(); + if (!HtmlUtil.isBooleanAttribute(attributeDescriptor) && (!isCustomValuesEnabled() || !isCustomValue(name))) { + LocalQuickFix[] quickFixes = new LocalQuickFix[]{ + new AddCustomHtmlElementIntentionAction(BOOLEAN_ATTRIBUTE_KEY, name, XmlBundle.message("add.custom.html.boolean.attribute", name)), + XmlQuickFixFactory.getInstance().addAttributeValueFix(attribute), + new RemoveAttributeIntentionAction(name), + }; + + registerProblemOnAttributeName(attribute, XmlErrorMessages.message("attribute.is.not.boolean", attribute.getName()), holder, + quickFixes); + } + } + } + } + } +} diff --git a/xml/xml-psi-impl/resources/messages/XmlErrorMessages.properties b/xml/xml-psi-impl/resources/messages/XmlErrorMessages.properties index 1eaa0cecc989c..aad82b2ed52b2 100644 --- a/xml/xml-psi-impl/resources/messages/XmlErrorMessages.properties +++ b/xml/xml-psi-impl/resources/messages/XmlErrorMessages.properties @@ -14,6 +14,7 @@ wrong.root.element=Wrong root element unbound.namespace=Namespace ''{0}'' is not bound unbound.namespace.no.param=Namespace is not bound attribute.is.not.allowed.here=Attribute {0} is not allowed here +attribute.is.not.boolean=Attribute {0} is not boolean empty.attribute.is.not.allowed=Empty attribute {0} is not allowed duplicate.attribute=Duplicate attribute {0} duplicate.id.reference=Duplicate id reference @@ -27,6 +28,7 @@ insert.required.attribute.quickfix.text=Insert required attribute {0} insert.required.attribute.quickfix.family=Insert required attribute remove.attribute.quickfix.text=Remove attribute {0} remove.attribute.quickfix.family=Remove attribute +add.attribute.value.quickfix.text=Add attribute value remove.extra.closing.tag.quickfix=Remove extra closing tag create.namespace.declaration.quickfix=Create {0} declaration select.namespace.title={0} To Import diff --git a/xml/xml-psi-impl/src/com/intellij/codeInspection/htmlInspections/XmlEntitiesInspection.java b/xml/xml-psi-impl/src/com/intellij/codeInspection/htmlInspections/XmlEntitiesInspection.java index 1143b7bd05dc4..109dd2cab802d 100644 --- a/xml/xml-psi-impl/src/com/intellij/codeInspection/htmlInspections/XmlEntitiesInspection.java +++ b/xml/xml-psi-impl/src/com/intellij/codeInspection/htmlInspections/XmlEntitiesInspection.java @@ -7,6 +7,7 @@ * Date: 16-Dec-2005 */ public interface XmlEntitiesInspection { + @NonNls String BOOLEAN_ATTRIBUTE_SHORT_NAME = "HtmlUnknownBooleanAttribute"; @NonNls String ATTRIBUTE_SHORT_NAME = "HtmlUnknownAttribute"; @NonNls String TAG_SHORT_NAME = "HtmlUnknownTag"; @NonNls String REQUIRED_ATTRIBUTES_SHORT_NAME = "RequiredAttributes"; diff --git a/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java b/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java index 3c3221b92b9e7..1e90360c24a4d 100644 --- a/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java +++ b/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java @@ -57,10 +57,7 @@ import org.jetbrains.annotations.Nullable; import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; +import java.util.*; /** * @author Maxim.Mossienko @@ -140,7 +137,7 @@ private HtmlUtil() { static { for (HTMLControls.Control control : HTMLControls.getControls()) { - final String tagName = control.name.toLowerCase(); + final String tagName = control.name.toLowerCase(Locale.US); if (control.endTag == HTMLControls.TagState.FORBIDDEN) EMPTY_TAGS_MAP.add(tagName); AUTO_CLOSE_BY_MAP.put(tagName, new THashSet(control.autoClosedBy)); } @@ -153,7 +150,7 @@ private HtmlUtil() { } public static boolean isSingleHtmlTag(String tagName) { - return EMPTY_TAGS_MAP.contains(tagName.toLowerCase()); + return EMPTY_TAGS_MAP.contains(tagName.toLowerCase(Locale.US)); } public static boolean isSingleHtmlTagL(String tagName) { @@ -161,7 +158,7 @@ public static boolean isSingleHtmlTagL(String tagName) { } public static boolean isOptionalEndForHtmlTag(String tagName) { - return OPTIONAL_END_TAGS_MAP.contains(tagName.toLowerCase()); + return OPTIONAL_END_TAGS_MAP.contains(tagName.toLowerCase(Locale.US)); } public static boolean isOptionalEndForHtmlTagL(String tagName) { @@ -174,11 +171,11 @@ public static boolean canTerminate(final String childTagName, final String tagNa } public static boolean isSingleHtmlAttribute(String attrName) { - return EMPTY_ATTRS_MAP.contains(attrName.toLowerCase()); + return EMPTY_ATTRS_MAP.contains(attrName.toLowerCase(Locale.US)); } public static boolean isHtmlBlockTag(String tagName) { - return BLOCK_TAGS_MAP.contains(tagName.toLowerCase()); + return BLOCK_TAGS_MAP.contains(tagName.toLowerCase(Locale.US)); } public static boolean isPossiblyInlineTag(String tagName) { @@ -190,7 +187,7 @@ public static boolean isHtmlBlockTagL(String tagName) { } public static boolean isInlineTagContainer(String tagName) { - return INLINE_ELEMENTS_CONTAINER_MAP.contains(tagName.toLowerCase()); + return INLINE_ELEMENTS_CONTAINER_MAP.contains(tagName.toLowerCase(Locale.US)); } public static boolean isInlineTagContainerL(String tagName) { @@ -241,6 +238,21 @@ public static String[] getHtmlTagNames() { return HtmlDescriptorsTable.getHtmlTagNames(); } + public static boolean isBooleanAttribute(@NotNull XmlAttributeDescriptor descriptor) { + final String[] values = descriptor.getEnumeratedValues(); + if (values == null) { + return false; + } + if (values.length == 2) { + return values[0].isEmpty() && values[1].equals(descriptor.getName()) + || values[1].isEmpty() && values[0].equals(descriptor.getName()); + } + else if (values.length == 1) { + return descriptor.getName().equals(values[0]); + } + return false; + } + public static XmlAttributeDescriptor[] getCustomAttributeDescriptors(XmlElement context) { String entitiesString = getEntitiesString(context, XmlEntitiesInspection.ATTRIBUTE_SHORT_NAME); if (entitiesString == null) return XmlAttributeDescriptor.EMPTY;