/* * 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.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.getName(); final String endTokenText = 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.getName(); final String endTokenText = 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; } }