/* * Copyright 2015 Google Inc. * * 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.google.template.soy.html.passes; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.html.AbstractHtmlSoyNodeVisitor; import com.google.template.soy.html.HtmlDefinitions; import com.google.template.soy.html.IncrementalHtmlCloseTagNode; import com.google.template.soy.html.IncrementalHtmlOpenTagNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.SoyNode.ParentSoyNode; import com.google.template.soy.soytree.SoyNode.StandaloneNode; /** * Looks for void element closing tags that are not immediately preceded by an open tag of the same * type. This indicates an error on the developer's part as void elements may not contain any * content. While the spec prohibits void elements from having closing tags at all, we allow them * for people who like XML. */ public final class VoidElementVerifyingVisitor extends AbstractHtmlSoyNodeVisitor<Void> { private static final SoyErrorKind INVALID_CLOSE_TAG = SoyErrorKind.of( "Closing tag for a void HTML " + "Element was not immediately preceeded by an open tag for the same element. Void" + " HTML Elements are not allowed to have any content. See: " + "http://www.w3.org/TR/html-markup/syntax.html#void-element"); private final ErrorReporter errorReporter; public VoidElementVerifyingVisitor(ErrorReporter errorReporter) { this.errorReporter = errorReporter; } @Override protected void visitIncrementalHtmlCloseTagNode(IncrementalHtmlCloseTagNode node) { String tagName = node.getTagName(); if (!HtmlDefinitions.HTML5_VOID_ELEMENTS.contains(tagName)) { return; } ParentSoyNode<StandaloneNode> parent = node.getParent(); int previousIndex = parent.getChildIndex(node) - 1; // The close tag is the first node in its parent. In theory, the template could still be valid // (e.g. {if $foo}<input>{/if}{if $foo}</input>{/if}), but it is almost certainly always a // mistake. if (previousIndex < 0) { errorReporter.report(node.getSourceLocation(), INVALID_CLOSE_TAG); } else { StandaloneNode previousNode = parent.getChild(previousIndex); if (previousNode instanceof IncrementalHtmlOpenTagNode && ((IncrementalHtmlOpenTagNode) previousNode).getTagName().equals(tagName)) { return; } errorReporter.report(node.getSourceLocation(), INVALID_CLOSE_TAG); } } // ----------------------------------------------------------------------------------------------- // Fallback implementation. @Override protected void visitSoyNode(SoyNode node) { if (node instanceof ParentSoyNode<?>) { visitChildren((ParentSoyNode<?>) node); } } }