com.google.common.css.compiler.passes.CreateForLoopNodes 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 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.common.css.compiler.passes;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.css.compiler.ast.CssAtRuleNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssForLoopRuleNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssLoopVariableNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssNumericNode;
import com.google.common.css.compiler.ast.CssUnknownAtRuleNode;
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.MutatingVisitController;
import java.util.Stack;
import java.util.regex.Pattern;
/**
* A compiler pass that replaces each {@code @for} with a {@link CssForLoopRuleNode}.
*/
public class CreateForLoopNodes extends DefaultTreeVisitor implements CssCompilerPass {
@VisibleForTesting
static final String SYNTAX_ERROR = "Invalid syntax for @for rule. Expected: "
+ "@for from to ) [step ]";
@VisibleForTesting
static final String ILLEGAL_VARIABLE_NAME = "Illegal variable name.";
@VisibleForTesting
static final String OVERRIDE_VARIABLE_NAME = "Overriding existing variable name.";
private static final String FOR_NAME = CssAtRuleNode.Type.FOR.getCanonicalName();
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$[a-zA-Z_]\\w*");
private static final String FROM_KEYWORD = "from";
private static final String TO_KEYWORD = "to";
private static final String STEP_KEYWORD = "step";
private static final int VARIABLE_INDEX = 0;
private static final int FROM_KEYWORD_INDEX = 1;
private static final int FROM_VALUE_INDEX = 2;
private static final int TO_KEYOWRD_INDEX = 3;
private static final int TO_VALUE_INDEX = 4;
private static final int STEP_KEYOWRD_INDEX = 5;
private static final int STEP_VALUE_INDEX = 6;
private static final int ARGUMENT_COUNT_WITHOUT_STEP = 5;
private static final int ARGUMENT_COUNT_WITH_STEP = 7;
private final MutatingVisitController visitController;
private final ErrorManager errorManager;
private final Stack variables = new Stack<>();
private int uniqueLoopId = 0;
public CreateForLoopNodes(MutatingVisitController visitController, ErrorManager errorManager) {
this.visitController = visitController;
this.errorManager = errorManager;
}
@Override
public boolean enterUnknownAtRule(CssUnknownAtRuleNode node) {
if (!node.getName().getValue().equals(FOR_NAME)) {
return true;
}
if (!node.getType().hasBlock()) {
reportError("@" + FOR_NAME + " with no block", node);
return false;
}
if (node.getChildren().size() != ARGUMENT_COUNT_WITHOUT_STEP
&& node.getChildren().size() != ARGUMENT_COUNT_WITH_STEP) {
reportError(SYNTAX_ERROR, node);
return false;
}
if (!(node.getChildAt(VARIABLE_INDEX) instanceof CssLoopVariableNode)
|| !(node.getChildAt(FROM_KEYWORD_INDEX) instanceof CssLiteralNode)
|| !FROM_KEYWORD.equals(node.getChildAt(FROM_KEYWORD_INDEX).getValue())
|| !isValidValueNode(node.getChildAt(FROM_VALUE_INDEX))
|| !(node.getChildAt(TO_KEYOWRD_INDEX) instanceof CssLiteralNode)
|| !TO_KEYWORD.equals(node.getChildAt(TO_KEYOWRD_INDEX).getValue())
|| !isValidValueNode(node.getChildAt(TO_VALUE_INDEX))) {
reportError(SYNTAX_ERROR, node);
return false;
}
String variableName = node.getChildAt(VARIABLE_INDEX).getValue();
if (!VARIABLE_PATTERN.matcher(variableName).matches()) {
reportError(ILLEGAL_VARIABLE_NAME, node.getChildAt(VARIABLE_INDEX));
return false;
}
if (variables.contains(variableName)) {
reportError(OVERRIDE_VARIABLE_NAME , node.getChildAt(VARIABLE_INDEX));
return false;
}
CssValueNode from = node.getChildAt(FROM_VALUE_INDEX);
CssValueNode to = node.getChildAt(TO_VALUE_INDEX);
CssValueNode step = new CssNumericNode("1", CssNumericNode.NO_UNITS);
if (node.getChildren().size() == ARGUMENT_COUNT_WITH_STEP) {
if (!(node.getChildAt(STEP_KEYOWRD_INDEX) instanceof CssLiteralNode)
|| !STEP_KEYWORD.equals(node.getChildAt(STEP_KEYOWRD_INDEX).getValue())
|| !isValidValueNode(node.getChildAt(STEP_VALUE_INDEX))) {
reportError(SYNTAX_ERROR, node);
return false;
}
step = node.getChildAt(STEP_VALUE_INDEX);
}
CssForLoopRuleNode loopNode = new CssForLoopRuleNode(node.getName(),
node.getBlock(),
node.getComments(),
from,
to,
step,
variableName,
nextLoopId(),
node.getSourceCodeLocation());
loopNode.setParameters(node.getChildren());
visitController.replaceCurrentBlockChildWith(Lists.newArrayList(loopNode), true);
variables.push(variableName);
return true;
}
@Override
public void leaveForLoop(CssForLoopRuleNode node) {
variables.pop();
}
private boolean isValidValueNode(CssValueNode node) {
return node instanceof CssNumericNode || node instanceof CssLiteralNode;
}
private void reportError(String message, CssNode node) {
errorManager.report(new GssError(message, node.getSourceCodeLocation()));
visitController.removeCurrentNode();
}
private int nextLoopId() {
return uniqueLoopId++;
}
@Override
public void runPass() {
visitController.startVisit(this);
}
}