All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.css.compiler.passes.CheckMissingRequire Maven / Gradle / Ivy

Go to download

Closure Stylesheets is an extension to CSS that adds variables, functions, conditionals, and mixins to standard CSS. The tool also supports minification, linting, RTL flipping, and CSS class renaming.

There is a newer version: 20160212
Show newest version
/*
 * Copyright 2013 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.common.css.compiler.passes;

import com.google.common.collect.ListMultimap;
import com.google.common.collect.Sets;
import com.google.common.css.compiler.ast.CssCommentNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssConstantReferenceNode;
import com.google.common.css.compiler.ast.CssDefinitionNode;
import com.google.common.css.compiler.ast.CssMixinNode;
import com.google.common.css.compiler.ast.CssRefinerNode;
import com.google.common.css.compiler.ast.CssSelectorNode;
import com.google.common.css.compiler.ast.CssValueNode;
import com.google.common.css.compiler.ast.DefaultTreeVisitor;
import com.google.common.css.compiler.ast.ErrorManager;
import com.google.common.css.compiler.ast.GssError;
import com.google.common.css.compiler.ast.VisitController;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A compiler pass that checks for missing {@code @require} lines for def constant references
 * and mixins. This pass is used in conjunction with CollectProvideNamespaces, which provides
 * namespaces for constant definitions and mixins.
 * Example for def references:
 * file foo/gss/button.gss provides namespace {@code @provide 'foo.gss.button';} and has
 *  the def: {@code @def FONT_SIZE 10px;}.
 * File foo/gss/item.gss references the above def as follows:
 * {@code @def ITEM_FONT_SIZE FONT_SIZE;}
 * This pass enforces that file foo/gss/item.gss contains {@code @require 'foo.gss.button';}
 *
 */
public final class CheckMissingRequire extends DefaultTreeVisitor implements CssCompilerPass {
  private static final Logger logger = Logger.getLogger(CheckMissingRequire.class.getName());

  private static final Pattern OVERRIDE_SELECTOR_REGEX = Pattern.compile(
      "/\\*\\*?\\s+@overrideSelector\\s+\\{(.*)\\}\\s+\\*/");

  private static final Pattern OVERRIDE_DEF_REGEX = Pattern.compile(
      "/\\*\\*?\\s+@overrideDef\\s+\\{(.*)\\}\\s+\\*/");

  private final VisitController visitController;
  private final ErrorManager errorManager;

  // Key: filename; Value: provide namespace
  private final Map filenameProvideMap;
  // Key: filename; Value: require namespace
  private final ListMultimap filenameRequireMap;

  // Multiple namespaces can contain the same defs due to duplicate defs (or mods).
  // Key: def name; Value: provide namespace
  private final ListMultimap defProvideMap;
  // Key: defmixin name; Value: provide namespace
  private final ListMultimap defmixinProvideMap;

  public CheckMissingRequire(VisitController visitController,
      ErrorManager errorManager,
      Map filenameProvideMap,
      ListMultimap filenameRequireMap,
      ListMultimap defProvideMap,
      ListMultimap defmixinProvideMap) {
    this.visitController = visitController;
    this.errorManager = errorManager;
    this.filenameProvideMap = filenameProvideMap;
    this.filenameRequireMap = filenameRequireMap;
    this.defProvideMap = defProvideMap;
    this.defmixinProvideMap = defmixinProvideMap;
  }

  @Override
  public boolean enterValueNode(CssValueNode node) {
    if (node instanceof CssConstantReferenceNode) {
      CssConstantReferenceNode reference = (CssConstantReferenceNode) node;
      String filename = reference.getSourceCodeLocation().getSourceCode().getFileName();
      List provides = defProvideMap.get(reference.getValue());
      // Remove this after switching to the new syntax.
      if (provides == null || provides.size() == 0) {  // ignore old format @provide
        return true;
      }
      if (hasMissingRequire(provides, filenameProvideMap.get(filename),
          filenameRequireMap.get(filename))) {
        StringBuilder error = new StringBuilder("Missing @require for constant " +
            reference.getValue() + ". Please @require namespace from:\n");
        for (String namespace : defProvideMap.get(reference.getValue())) {
          error.append("\t");
          error.append(namespace);
          error.append("\n");
        }
        errorManager.report(new GssError(error.toString(), reference.getSourceCodeLocation()));
      }
    }
    return true;
  }

  @Override
  public boolean enterMixin(CssMixinNode node) {
    String filename = node.getSourceCodeLocation().getSourceCode().getFileName();
    List provides = defmixinProvideMap.get(node.getDefinitionName());
    // Remove this after switching to the new syntax.
    if (provides == null || provides.size() == 0) {  // ignore old format @provide
      return true;
    }
    if (hasMissingRequire(provides, filenameProvideMap.get(filename),
        filenameRequireMap.get(filename))) {
      StringBuilder error = new StringBuilder("Missing @require for mixin " +
          node.getDefinitionName() + ". Please @require namespace from:\n");
      for (String namespace : defmixinProvideMap.get(node.getDefinitionName())) {
        error.append("\t");
        error.append(namespace);
        error.append("\n");
      }
      errorManager.report(new GssError(error.toString(), node.getSourceCodeLocation()));
    }
    return true;
  }

  private boolean hasMissingRequire(List provides, String currentNamespace,
      List requires) {
    // Either the namespace should be provided in this very file or it should be @require'd here.
    Set defNamespaceSet = Sets.newHashSet(provides);
    Set requireNamespaceSet = Sets.newHashSet(requires);
    requireNamespaceSet.retainAll(defNamespaceSet);
    if (requireNamespaceSet.size() > 0 || defNamespaceSet.contains(currentNamespace)) {
      return false;
    }
    return true;
  }

  /*
   * Check whether @overrideSelector namespaces are @require'd.
   */
  @Override
  public boolean enterSelector(CssSelectorNode node) {
    String filename = node.getSourceCodeLocation().getSourceCode().getFileName();
    for (CssRefinerNode refiner : node.getRefiners().getChildren()) {
      for (CssCommentNode comment : refiner.getComments()) {
        Matcher matcher = OVERRIDE_SELECTOR_REGEX.matcher(comment.getValue());
        if (matcher.find()) {
          String overrideNamespace = matcher.group(1);
          List requires = filenameRequireMap.get(filename);
          // Remove this after switching to the new syntax.
          if (requires == null || requires.size() == 0) {  // ignore old format @require
            continue;
          }
          Set requireNamespaceSet = Sets.newHashSet(requires);
          if (!requireNamespaceSet.contains(overrideNamespace)) {
            String error = "Missing @require for @overrideSelector {" +
                overrideNamespace + "}. Please @require this namespace in file: " +
                filename + ".\n";
            errorManager.report(new GssError(error, node.getSourceCodeLocation()));
            return true;
          }
        }
      }
    }
    return true;
  }

  /*
   * Check whether @overrideDef namespaces are @require'd.
   */
  @Override
  public boolean enterDefinition(CssDefinitionNode node) {
    String filename = node.getSourceCodeLocation().getSourceCode().getFileName();
    for (CssCommentNode comment : node.getComments()) {
      Matcher matcher = OVERRIDE_DEF_REGEX.matcher(comment.getValue());
      if (matcher.find()) {
        String overrideNamespace = matcher.group(1);
        List requires = filenameRequireMap.get(filename);
        if (requires == null || requires.size() == 0) {  // ignore old format @require
          continue;
        }
        Set requireNamespaceSet = Sets.newHashSet(requires);
        if (!requireNamespaceSet.contains(overrideNamespace)) {
          String error = "Missing @require for @overrideDef {"
              + overrideNamespace + "}. Please @require this namespace in file: "
              + filename + ".\n";
          errorManager.report(new GssError(error, node.getSourceCodeLocation()));
          return true;
        }
      }
    }
    return true;
  }

  @Override
  public void runPass() {
    visitController.startVisit(this);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy