com.google.common.css.compiler.passes.CheckMissingRequire Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of closure-stylesheets Show documentation
Show all versions of closure-stylesheets Show documentation
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.
/*
* 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);
}
}