com.google.template.soy.passes.ResolvePackageRelativeCssNamesPass.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.passes.ResolvePackageRelativeCssNamesPass.java

Source

/*
 * Copyright 2014 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.passes;

import com.google.common.base.CaseFormat;
import com.google.common.collect.Iterables;
import com.google.template.soy.base.internal.IdGenerator;
import com.google.template.soy.base.internal.QuoteStyle;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.StringNode;
import com.google.template.soy.shared.internal.BuiltinFunction;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyTreeUtils;
import com.google.template.soy.soytree.TemplateNode;
import javax.annotation.Nullable;

/** Converts package-relative CSS class names to absolute names. */
final class ResolvePackageRelativeCssNamesPass extends CompilerFilePass {

    private static final String RELATIVE_SELECTOR_PREFIX = "%";

    private static final SoyErrorKind PACKAGE_RELATIVE_CLASS_NAME_USED_WITH_COMPONENT_NAME = SoyErrorKind
            .of("Package-relative class name ''{0}'' cannot be used with component expression.");
    private static final SoyErrorKind NO_CSS_PACKAGE = SoyErrorKind
            .of("No CSS package defined for package-relative class name ''{0}''. "
                    + "CSS package prefixes are set via the ''cssbase'' attribute on the template, a "
                    + "''cssbase'' attribute on the namespace, or the first ''requirecss'' package on "
                    + "the namespace.{1}.");

    private final ErrorReporter errorReporter;

    ResolvePackageRelativeCssNamesPass(ErrorReporter errorReporter) {
        this.errorReporter = errorReporter;
    }

    @Override
    public void run(SoyFileNode file, IdGenerator nodeIdGen) {
        // Compute the CSS package prefix for this template. The search order is:
        // 1) cssbase on the template
        // 2) cssbase on the namespace
        // 3) first requirecss on the namespace
        String namespacePrefix = null;
        if (file.getCssBaseNamespace() != null) {
            namespacePrefix = toCamelCase(file.getCssBaseNamespace());
        } else if (!file.getRequiredCssNamespaces().isEmpty()) {
            namespacePrefix = toCamelCase(file.getRequiredCssNamespaces().get(0));
        }
        for (TemplateNode template : file.getChildren()) {
            String packagePrefix = namespacePrefix;
            if (template.getCssBaseNamespace() != null) {
                packagePrefix = toCamelCase(template.getCssBaseNamespace());
            }
            for (FunctionNode fn : SoyTreeUtils.getAllNodesOfType(template, FunctionNode.class)) {
                resolveSelector(template, fn, packagePrefix);
            }
        }
    }

    private void resolveSelector(TemplateNode template, FunctionNode node, @Nullable String packagePrefix) {
        if (node.getSoyFunction() != BuiltinFunction.CSS) {
            return;
        }

        ExprNode lastChild = Iterables.getLast(node.getChildren());
        if (!(lastChild instanceof StringNode)) {
            // this will generate an error in CheckFunctionCallsVisitor
            return;
        }

        StringNode selector = (StringNode) Iterables.getLast(node.getChildren());
        String selectorText = selector.getValue();
        if (!selectorText.startsWith(RELATIVE_SELECTOR_PREFIX)) {
            return;
        }

        if (node.numChildren() > 1) {
            errorReporter.report(selector.getSourceLocation(), PACKAGE_RELATIVE_CLASS_NAME_USED_WITH_COMPONENT_NAME,
                    selectorText);
        }

        if (packagePrefix == null) {
            errorReporter.report(selector.getSourceLocation(), NO_CSS_PACKAGE, selectorText,
                    template.getRequiredCssNamespaces().isEmpty() ? ""
                            : " NOTE: ''requirecss'' on a template is not used to infer the CSS package.");
        }

        // Replace the selector text with resolved selector text
        String prefixed = packagePrefix + selectorText.substring(RELATIVE_SELECTOR_PREFIX.length());
        StringNode newSelector = new StringNode(prefixed, QuoteStyle.SINGLE, selector.getSourceLocation());
        node.replaceChild(selector, newSelector);
    }

    private static String toCamelCase(String packageName) {
        String packageNameWithDashes = packageName.replace('.', '-');
        return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, packageNameWithDashes);
    }
}