blob: 7ecdf14bad4382a4e72bca42467f4a393807ae06 [file] [log] [blame]
/*
* Copyright 2000-2014 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.lang.ASTNode;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.ParserDefinition;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.html.HtmlTag;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.psi.xml.XmlTag;
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.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author spleaner
*/
public class XmlWrongClosingTagNameInspection implements Annotator {
@Override
public void annotate(@NotNull final PsiElement psiElement, @NotNull final AnnotationHolder holder) {
if (psiElement instanceof XmlToken) {
final PsiElement parent = psiElement.getParent();
if (parent instanceof XmlTag) {
final XmlTag tag = (XmlTag)parent;
final XmlToken start = XmlTagUtil.getStartTagNameElement(tag);
XmlToken endTagName = XmlTagUtil.getEndTagNameElement(tag);
if (start == psiElement) {
if (endTagName != null && !(tag instanceof HtmlTag) && !tag.getName().equals(endTagName.getText())) {
registerProblemStart(holder, tag, start, endTagName);
}
else if (endTagName == null && !(tag instanceof HtmlTag && HtmlUtil.isSingleHtmlTag(tag.getName()))) {
final PsiErrorElement errorElement = PsiTreeUtil.getChildOfType(tag, PsiErrorElement.class);
endTagName = findEndTagName(errorElement);
if (endTagName != null) {
registerProblemStart(holder, tag, start, endTagName);
}
}
}
else if (endTagName == psiElement) {
if (!(tag instanceof HtmlTag) && !tag.getName().equals(endTagName.getText())) {
registerProblemEnd(holder, tag, endTagName);
}
}
}
else if (parent instanceof PsiErrorElement) {
if (XmlTokenType.XML_NAME == ((XmlToken)psiElement).getTokenType()) {
final PsiFile psiFile = psiElement.getContainingFile();
if (psiFile != null && (HTMLLanguage.INSTANCE == psiFile.getViewProvider().getBaseLanguage() || HTMLLanguage.INSTANCE == parent.getLanguage())) {
final String message = XmlErrorMessages.message("xml.parsing.closing.tag.matches.nothing");
if (message.equals(((PsiErrorElement)parent).getErrorDescription()) &&
psiFile.getContext() == null
) {
final Annotation annotation = holder.createWarningAnnotation(parent, message);
annotation.registerFix(new RemoveExtraClosingTagIntentionAction());
}
}
}
}
}
}
private static void registerProblemStart(@NotNull final AnnotationHolder holder,
@NotNull final XmlTag tag,
@NotNull final XmlToken start,
@NotNull final XmlToken end) {
PsiElement context = tag.getContainingFile().getContext();
if (context != null) {
ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(context.getLanguage());
if (parserDefinition != null) {
ASTNode contextNode = context.getNode();
if (contextNode != null && contextNode.getChildren(parserDefinition.getStringLiteralElements()) != null) {
// TODO: we should check for concatenations here
return;
}
}
}
final String tagName = tag instanceof HtmlTag ? tag.getName().toLowerCase() : tag.getName();
final String endTokenText = tag instanceof HtmlTag ? end.getText().toLowerCase() : end.getText();
final RenameTagBeginOrEndIntentionAction renameEndAction = new RenameTagBeginOrEndIntentionAction(tagName, endTokenText, false);
final RenameTagBeginOrEndIntentionAction renameStartAction = new RenameTagBeginOrEndIntentionAction(endTokenText, tagName, true);
final Annotation annotation = holder.createErrorAnnotation(start, XmlErrorMessages.message("tag.has.wrong.closing.tag.name"));
annotation.registerFix(renameEndAction);
annotation.registerFix(renameStartAction);
}
private static void registerProblemEnd(@NotNull final AnnotationHolder holder,
@NotNull final XmlTag tag,
@NotNull final XmlToken end) {
PsiElement context = tag.getContainingFile().getContext();
if (context != null) {
ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(context.getLanguage());
if (parserDefinition != null) {
ASTNode contextNode = context.getNode();
if (contextNode != null && contextNode.getChildren(parserDefinition.getStringLiteralElements()) != null) {
// TODO: we should check for concatenations here
return;
}
}
}
final String tagName = tag instanceof HtmlTag ? tag.getName().toLowerCase() : tag.getName();
final String endTokenText = tag instanceof HtmlTag ? end.getText().toLowerCase() : end.getText();
final RenameTagBeginOrEndIntentionAction renameEndAction = new RenameTagBeginOrEndIntentionAction(tagName, endTokenText, false);
final RenameTagBeginOrEndIntentionAction renameStartAction = new RenameTagBeginOrEndIntentionAction(endTokenText, tagName, true);
final Annotation annotation = holder.createErrorAnnotation(end, XmlErrorMessages.message("wrong.closing.tag.name"));
annotation.registerFix(new RemoveExtraClosingTagIntentionAction());
annotation.registerFix(renameEndAction);
annotation.registerFix(renameStartAction);
}
@Nullable
static XmlToken findEndTagName(@Nullable final PsiErrorElement element) {
if (element == null) return null;
final ASTNode astNode = element.getNode();
if (astNode == null) return null;
ASTNode current = astNode.getLastChildNode();
ASTNode prev = current;
while (current != null) {
final IElementType elementType = prev.getElementType();
if ((elementType == XmlTokenType.XML_NAME || elementType == XmlTokenType.XML_TAG_NAME) &&
current.getElementType() == XmlTokenType.XML_END_TAG_START) {
return (XmlToken)prev.getPsi();
}
prev = current;
current = current.getTreePrev();
}
return null;
}
}