diff --git a/platform/platform-resources-en/src/messages/XmlBundle.properties b/platform/platform-resources-en/src/messages/XmlBundle.properties
index 53c4fc55aa1f..139f86b8521b 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 cf0e8e7b97c8..3ada69d17e89 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 c095bd1cf49f..4c647c6e0c01 100644
--- a/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java
+++ b/xml/impl/src/com/intellij/codeInspection/XmlQuickFixFactoryImpl.java
@@ -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);
+ }
}
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 000000000000..500fdc4f4c8b
--- /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 000000000000..478669ea418f
--- /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 c0c5b1081785..3037760dc98a 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 @@ 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);
}
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 000000000000..d926ffab1cc0
--- /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 ce56f3923abc..3c019d890e3c 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 @@ 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;
+ }
}
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 000000000000..b7fb7f2a0870
--- /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 1eaa0cecc989..aad82b2ed52b 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 1143b7bd05dc..109dd2cab802 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 @@ 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";
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 3c3221b92b9e..1e90360c24a4 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.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(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;