mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
new "Missing closing tag" HTML inspection
This commit is contained in:
@@ -82,6 +82,7 @@ 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
|
||||
html.inspections.missing.closing.tag=Missing closing tag
|
||||
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}
|
||||
|
||||
@@ -460,6 +460,10 @@
|
||||
bundle="messages.XmlBundle" key="html.inspections.unknown.boolean.attribute"
|
||||
groupBundle="messages.XmlBundle" groupKey="html.inspections.group.name"
|
||||
implementationClass="com.intellij.codeInspection.htmlInspections.HtmlUnknownBooleanAttributeInspection"/>
|
||||
<localInspection language="HTML" enabledByDefault="true" level="INFORMATION"
|
||||
bundle="messages.XmlBundle" key="html.inspections.missing.closing.tag"
|
||||
groupBundle="messages.XmlBundle" groupKey="html.inspections.group.name"
|
||||
implementationClass="com.intellij.codeInspection.htmlInspections.HtmlMissingClosingTagInspection"/>
|
||||
<localInspection language="XML" shortName="HtmlUnknownTarget" enabledByDefault="true" level="WARNING"
|
||||
bundle="messages.XmlBundle" key="html.inspections.unknown.target"
|
||||
groupBundle="messages.XmlBundle" groupKey="html.inspections.group.name"
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.codeHighlighting.HighlightDisplayLevel;
|
||||
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.ide.highlighter.HtmlFileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
|
||||
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class HtmlMissingClosingTagInspectionTest extends LightPlatformCodeInsightFixtureTestCase {
|
||||
|
||||
public void testImgElement() {
|
||||
highlightTest("<html><body><img></body></html>");
|
||||
}
|
||||
|
||||
public void testIncompleteElement() {
|
||||
highlightTest("<html><table><error descr=\"Element table is not closed\"><</error>/html>");
|
||||
}
|
||||
|
||||
public void testPElement() {
|
||||
quickfixTest("<html><<warning descr=\"Element <p> is missing an end tag\">p</warning><caret>>Behold!</html>", "<html><p>Behold!</p></html>", "Add </p>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@NotNull
|
||||
protected LocalInspectionTool getInspection() {
|
||||
return new HtmlMissingClosingTagInspection();
|
||||
}
|
||||
|
||||
protected void highlightTest(@Language("HTML") String code) {
|
||||
final LocalInspectionTool inspection = getInspection();
|
||||
myFixture.enableInspections(inspection);
|
||||
final HighlightDisplayKey displayKey = HighlightDisplayKey.find(inspection.getShortName());
|
||||
final Project project = myFixture.getProject();
|
||||
final InspectionProfileImpl currentProfile = ProjectInspectionProfileManager.getInstance(project).getCurrentProfile();
|
||||
final HighlightDisplayLevel errorLevel = currentProfile.getErrorLevel(displayKey, null);
|
||||
if (errorLevel == HighlightDisplayLevel.DO_NOT_SHOW) {
|
||||
currentProfile.setErrorLevel(displayKey, HighlightDisplayLevel.WARNING, project);
|
||||
}
|
||||
myFixture.configureByText(HtmlFileType.INSTANCE, code);
|
||||
myFixture.testHighlighting();
|
||||
}
|
||||
|
||||
protected void quickfixTest(@Language("HTML") String before, @Language("HTML") String after, String hint) {
|
||||
highlightTest(before);
|
||||
final IntentionAction intention = findIntention(hint);
|
||||
assertNotNull(intention);
|
||||
myFixture.launchAction(intention);
|
||||
myFixture.checkResult(after);
|
||||
}
|
||||
|
||||
protected IntentionAction findIntention(@NotNull final String hint) {
|
||||
final List<IntentionAction> allIntentions = myFixture.getAvailableIntentions();
|
||||
final List<IntentionAction> intentions =
|
||||
allIntentions.stream().filter(action -> action.getText().startsWith(hint)).limit(2).collect(Collectors.toList());
|
||||
Assert.assertFalse("\"" + hint + "\" not in " + intentions, intentions.isEmpty());
|
||||
Assert.assertFalse("Too many quickfixes found for \"" + hint + "\": " + intentions + "]", intentions.size() > 1);
|
||||
return intentions.get(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports HTML elements without a closing tag.
|
||||
Some coding styles require a closing tag on HTML elements even where this is optional.
|
||||
<!-- tooltip end -->
|
||||
<p><small>New in 2017.2</small>
|
||||
</body>
|
||||
</html>
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.codeInsight.daemon.impl.analysis.XmlHighlightVisitor;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiErrorElement;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.XmlElementFactory;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.html.HtmlTag;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.psi.xml.XmlText;
|
||||
import com.intellij.psi.xml.XmlToken;
|
||||
import com.intellij.psi.xml.XmlTokenType;
|
||||
import com.intellij.xml.util.HtmlUtil;
|
||||
import com.intellij.xml.util.XmlTagUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class HtmlMissingClosingTagInspection extends HtmlLocalInspectionTool {
|
||||
|
||||
@Override
|
||||
protected void checkTag(@NotNull XmlTag tag, @NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
if (!(tag instanceof HtmlTag) || !XmlHighlightVisitor.shouldBeValidated(tag)) {
|
||||
return;
|
||||
}
|
||||
PsiElement child = tag.getLastChild();
|
||||
if (child instanceof PsiErrorElement) {
|
||||
return;
|
||||
}
|
||||
final XmlToken tagNameElement = XmlTagUtil.getStartTagNameElement(tag);
|
||||
if (tagNameElement == null) {
|
||||
return;
|
||||
}
|
||||
final String tagName = tagNameElement.getText();
|
||||
if (HtmlUtil.isSingleHtmlTag(tagName)) {
|
||||
return;
|
||||
}
|
||||
if (isToken(child, XmlTokenType.XML_TAG_END)) {
|
||||
child = child.getPrevSibling();
|
||||
if (isToken(child, XmlTokenType.XML_NAME) && tagName.equals(child.getText())) {
|
||||
child = child.getPrevSibling();
|
||||
if (isToken(child, XmlTokenType.XML_END_TAG_START)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.registerProblem(tagNameElement, XmlErrorMessages.message("element.missing.end.tag"),
|
||||
new MissingClosingTagFix(tagName));
|
||||
}
|
||||
|
||||
private static boolean isToken(PsiElement element, IElementType tokenType) {
|
||||
if (!(element instanceof XmlToken)) {
|
||||
return false;
|
||||
}
|
||||
final XmlToken token = (XmlToken)element;
|
||||
return token.getTokenType() == tokenType;
|
||||
}
|
||||
|
||||
private static class MissingClosingTagFix implements LocalQuickFix {
|
||||
|
||||
private final String myName;
|
||||
|
||||
public MissingClosingTagFix(String name) {
|
||||
myName = name;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return XmlErrorMessages.message("add.named.closing.tag", myName);
|
||||
}
|
||||
|
||||
@Nls
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
return XmlErrorMessages.message("add.closing.tag");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement element = descriptor.getPsiElement();
|
||||
if (!(element instanceof XmlToken)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement parent = element.getParent();
|
||||
if (!(parent instanceof XmlTag)) {
|
||||
return;
|
||||
}
|
||||
String text = parent.getText();
|
||||
if (text.contains("\n")) {
|
||||
int cutoff = -1;
|
||||
for (int i = text.length() - 1; i > 0; i--) {
|
||||
final char c = text.charAt(i);
|
||||
if (c == ' ' || c == '\t') continue;
|
||||
if (c == '\n') cutoff = i;
|
||||
else break;
|
||||
}
|
||||
if (cutoff > 0) {
|
||||
text = text.substring(0, cutoff);
|
||||
}
|
||||
}
|
||||
final String replacementText = text + "</" + element.getText() + ">";
|
||||
final XmlElementFactory factory = XmlElementFactory.getInstance(project);
|
||||
final XmlTag newTag = factory.createHTMLTagFromText(replacementText);
|
||||
final PsiElement child = parent.getLastChild().copy();
|
||||
CodeStyleManager.getInstance(project).performActionWithFormatterDisabled((Runnable)() -> {
|
||||
final PsiElement replacement = parent.replace(newTag);
|
||||
if (child instanceof XmlText) {
|
||||
final PsiElement grandChild = child.getLastChild();
|
||||
if (grandChild instanceof PsiWhiteSpace) {
|
||||
final XmlTag dummyTag = factory.createHTMLTagFromText("<dummy>" + grandChild.getText() + "</dummy>");
|
||||
final XmlText whitespace = PsiTreeUtil.getChildOfType(dummyTag, XmlText.class);
|
||||
assert whitespace != null;
|
||||
replacement.getParent().addAfter(whitespace, replacement);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ invalid.id.reference=Invalid id reference
|
||||
uri.is.not.registered=URI is not registered (Settings | Languages \\& Frameworks | Schemas and DTDs)
|
||||
registered.resource.is.not.recognized=Resource registered by this uri is not recognized (Settings | Languages \\& Frameworks | Schemas and DTDs)
|
||||
should.have.fixed.value={0} should have fixed value {1}
|
||||
element.missing.end.tag=Element \\<#ref\\> is missing an end tag
|
||||
|
||||
#quickfixes
|
||||
insert.required.attribute.quickfix.text=Insert required attribute {0}
|
||||
@@ -37,6 +38,8 @@ select.namespace.title={0} To Import
|
||||
select.namespace.location.title=Select Namespace Location
|
||||
declare.id.in.comment.quickfix=Declare invalid id in comment annotation
|
||||
switch.to.html5.quickfix.text=Switch to HTML5 language level
|
||||
add.closing.tag=Add closing tag
|
||||
add.named.closing.tag=Add </{0}>
|
||||
|
||||
element.is.not.closed=Element is not closed
|
||||
named.element.is.not.closed=Element {0} is not closed
|
||||
|
||||
Reference in New Issue
Block a user