HTML: add unknown boolean attribute inspection

This commit is contained in:
Alexander Zolotov
2014-12-24 19:44:36 +03:00
parent c488d3e992
commit 4ca9b677e5
12 changed files with 278 additions and 10 deletions
@@ -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
@@ -421,6 +421,9 @@
<localInspection language="XML" shortName="HtmlUnknownAttribute" bundle="messages.XmlBundle" key="html.inspections.unknown.attribute" groupName="HTML"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.htmlInspections.HtmlUnknownAttributeInspection"/>
<localInspection language="XML" bundle="messages.XmlBundle" key="html.inspections.unknown.boolean.attribute" groupName="HTML"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.htmlInspections.HtmlUnknownBooleanAttributeInspection"/>
<localInspection language="XML" shortName="XmlUnboundNsPrefix" bundle="messages.XmlBundle" key="xml.inspections.unbound.prefix"
groupKey="xml.inspections.group.name" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInsight.daemon.impl.analysis.XmlUnboundNsPrefixInspection"/>
@@ -17,7 +17,9 @@ package com.intellij.codeInspection;
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 class XmlQuickFixFactoryImpl extends XmlQuickFixFactory {
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);
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -17,6 +17,7 @@ package com.intellij.codeInspection;
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 abstract class XmlQuickFixFactory {
public abstract LocalQuickFix createNSDeclarationIntentionFix(@NotNull final PsiElement element,
@NotNull String namespacePrefix,
@Nullable final XmlToken token);
@NotNull
public abstract LocalQuickFixAndIntentionActionOnPsiElement addAttributeValueFix(@NotNull XmlAttribute attribute);
}
@@ -0,0 +1,6 @@
<html>
<body>
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.<br>
</body>
</html>
@@ -17,6 +17,7 @@ package com.intellij.codeInspection;
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 class EmptyXmlQuickFixFactory extends XmlQuickFixFactory {
@Nullable XmlToken token) {
return QuickFixes.EMPTY_ACTION;
}
@NotNull
@Override
public LocalQuickFixAndIntentionActionOnPsiElement addAttributeValueFix(@NotNull XmlAttribute attribute) {
return QuickFixes.EMPTY_FIX;
}
}
@@ -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<HtmlUnknownElementInspection> 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);
}
}
}
}
}
}
@@ -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
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.NonNls;
* 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";
@@ -57,10 +57,7 @@ import org.jetbrains.annotations.NotNull;
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 @@ public class 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<String>(control.autoClosedBy));
}
@@ -153,7 +150,7 @@ public class 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 class HtmlUtil {
}
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 class HtmlUtil {
}
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 class HtmlUtil {
}
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 class HtmlUtil {
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;