From ddfb7d6e1fa6df65ad72595d7ac4e070ecf809cf Mon Sep 17 00:00:00 2001 From: jurcovicovam Date: Tue, 24 Jun 2014 14:42:43 +0200 Subject: [PATCH] Working detached rulesets draft with enabled unit tests. Most cases work, but the whole thing needs some more testing and clean up. #186 --- .../github/sommeri/less4j/core/parser/Less.g | 4 +- .../less4j/core/ast/MixinReference.java | 3 +- .../core/compiler/LessToCssCompiler.java | 4 +- ...a => IScopeAwareExpressionsEvaluator.java} | 350 +++++------------- .../expressions/MixinsGuardsValidator.java | 2 +- .../expressions/TypesConversionUtils.java | 2 +- .../strings/StringInterpolator.java | 6 +- .../core/compiler/scopes/NullScope.java | 186 ++++++++++ .../core/compiler/stages/ASTManipulator.java | 2 +- .../compiler/stages/ArgumentsBuilder.java | 6 +- .../less4j/core/compiler/stages/AstLogic.java | 4 + .../compiler/stages/DefaultGuardHelper.java | 1 - .../stages/MixinsReferenceMatcher.java | 6 +- .../core/compiler/stages/MixinsSolver.java | 12 +- .../compiler/stages/ReferencesSolver.java | 12 +- .../less4j/core/parser/ASTBuilderSwitch.java | 6 +- .../core/parser/MixinsParametersBuilder.java | 19 +- .../less4j/core/problems/ProblemsHandler.java | 13 + .../core/validators/LessAstValidator.java | 23 ++ .../validators/SupportedCSSBodyMembers.java | 10 +- .../less4j/compiler/DetachedRulesetsTest.java | 2 - ...tached-ruleset-in-lists-lists-scope-1.css} | 0 ...ached-ruleset-in-lists-lists-scope-1.less} | 0 ...etached-ruleset-in-lists-lists-scope-2.css | 3 + ...tached-ruleset-in-lists-lists-scope-2.less | 21 ++ ...etached-ruleset-in-lists-lists-scope-3.css | 3 + ...tached-ruleset-in-lists-lists-scope-3.less | 21 ++ .../detached-rulesets-as-argument.css | 6 + .../detached-rulesets-as-argument.err | 2 + .../detached-rulesets-errors-additional.css | 3 +- .../detached-rulesets-errors-suboptimal.css | 0 .../detached-rulesets-errors-suboptimal.err | 0 .../detached-rulesets-errors-suboptimal.less | 5 + .../detached-rulesets-errors.css | 11 +- .../detached-rulesets-errors.err | 8 +- .../detached-rulesets-errors.less | 10 +- .../detached-rulesets-lessjs.css | 10 +- .../detached-rulesets-lessjs.less | 10 +- src/test/resources/minitests/debug1.css | 28 +- src/test/resources/minitests/debug1.less | 29 +- 40 files changed, 475 insertions(+), 368 deletions(-) rename src/main/java/com/github/sommeri/less4j/core/compiler/expressions/{ExpressionEvaluator.java => IScopeAwareExpressionsEvaluator.java} (67%) create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/scopes/NullScope.java rename src/test/resources/compile-basic-features/detached-rulesets/{detached-ruleset-in-lists-lists-scope.css => detached-ruleset-in-lists-lists-scope-1.css} (100%) rename src/test/resources/compile-basic-features/detached-rulesets/{detached-ruleset-in-lists-lists-scope.less => detached-ruleset-in-lists-lists-scope-1.less} (100%) create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.css create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.less create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.css create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.less create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.err create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.css create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.err create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.less diff --git a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g index 77f9ff44..a65828ac 100644 --- a/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g +++ b/src/main/antlr3/com/github/sommeri/less4j/core/parser/Less.g @@ -806,8 +806,8 @@ finally { leaveRule(); } detachedRulesetReference @init {enterRule(retval, RULE_DETACHED_RULESET_REFERENCE);} - : a=AT_NAME (LPAREN RPAREN)? SEMI - -> ^(DETACHED_RULESET_REFERENCE $a) + : a+=AT_NAME (a+=LPAREN? a+=RPAREN?) SEMI + -> ^(DETACHED_RULESET_REFERENCE $a*) ; finally { leaveRule(); } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/MixinReference.java b/src/main/java/com/github/sommeri/less4j/core/ast/MixinReference.java index 169b2099..6d0467ef 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/MixinReference.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/MixinReference.java @@ -145,7 +145,8 @@ public MixinReference clone() { result.configureParentToAllChilds(); return result; } - + + @NotAstProperty public List getAllPositionalArgumentsFrom(int startIndx) { if (hasPositionalParameter(startIndx)) return positionalParameters.subList(startIndx, positionalParameters.size()); diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java index ddb4a5e6..223f0a45 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/LessToCssCompiler.java @@ -14,7 +14,7 @@ import com.github.sommeri.less4j.core.ast.FixedMediaExpression; import com.github.sommeri.less4j.core.ast.MediaExpressionFeature; import com.github.sommeri.less4j.core.ast.StyleSheet; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.scopes.IScope; import com.github.sommeri.less4j.core.compiler.selectors.ExtendsSolver; import com.github.sommeri.less4j.core.compiler.selectors.UselessLessElementsRemover; @@ -152,7 +152,7 @@ private int code(ASTCssNode node) { private void evaluateExpressions(ASTCssNode node) { ASTManipulator manipulator = new ASTManipulator(); //variables are not supposed to be there now - ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(problemsHandler, configuration); + IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(problemsHandler, configuration); if (node instanceof Expression) { Expression value = expressionEvaluator.evaluate((Expression) node); diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/IScopeAwareExpressionsEvaluator.java similarity index 67% rename from src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java rename to src/main/java/com/github/sommeri/less4j/core/compiler/expressions/IScopeAwareExpressionsEvaluator.java index 767f1185..8597d04d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/ExpressionEvaluator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/IScopeAwareExpressionsEvaluator.java @@ -1,21 +1,21 @@ package com.github.sommeri.less4j.core.compiler.expressions; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Stack; import com.github.sommeri.less4j.EmbeddedLessGenerator; import com.github.sommeri.less4j.EmbeddedScriptGenerator; import com.github.sommeri.less4j.LessCompiler.Configuration; import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; -import com.github.sommeri.less4j.core.ast.AbstractVariableDeclaration; import com.github.sommeri.less4j.core.ast.BinaryExpression; import com.github.sommeri.less4j.core.ast.BinaryExpressionOperator.Operator; import com.github.sommeri.less4j.core.ast.ComparisonExpression; import com.github.sommeri.less4j.core.ast.ComparisonExpressionOperator; import com.github.sommeri.less4j.core.ast.CssString; +import com.github.sommeri.less4j.core.ast.DetachedRuleset; import com.github.sommeri.less4j.core.ast.EmbeddedScript; import com.github.sommeri.less4j.core.ast.EscapedValue; import com.github.sommeri.less4j.core.ast.Expression; @@ -32,28 +32,27 @@ import com.github.sommeri.less4j.core.ast.NumberExpression.Dimension; import com.github.sommeri.less4j.core.ast.ParenthesesExpression; import com.github.sommeri.less4j.core.ast.ReusableStructure; -import com.github.sommeri.less4j.core.ast.ReusableStructureName; import com.github.sommeri.less4j.core.ast.RuleSet; import com.github.sommeri.less4j.core.ast.SignedExpression; import com.github.sommeri.less4j.core.ast.SignedExpression.Sign; import com.github.sommeri.less4j.core.ast.Variable; import com.github.sommeri.less4j.core.compiler.expressions.strings.StringInterpolator; -import com.github.sommeri.less4j.core.compiler.scopes.BasicScope; -import com.github.sommeri.less4j.core.compiler.scopes.FullMixinDefinition; import com.github.sommeri.less4j.core.compiler.scopes.IScope; +import com.github.sommeri.less4j.core.compiler.scopes.NullScope; import com.github.sommeri.less4j.core.compiler.scopes.ScopeFactory; -import com.github.sommeri.less4j.core.compiler.scopes.ScopesTree; -import com.github.sommeri.less4j.core.compiler.scopes.local.LocalScope; import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.core.problems.ProblemsHandler; import com.github.sommeri.less4j.utils.CssPrinter; import com.github.sommeri.less4j.utils.InStringCssPrinter; -public class ExpressionEvaluator { +//FIXME !!!!!!!!!!! rename back to ExpressionsEvaluator +public class IScopeAwareExpressionsEvaluator { private VariableCycleDetector cycleDetector = new VariableCycleDetector(); - private final IScope scope; + private final IScope lazyScope; + private final Stack eagerScopes = new Stack(); private final ProblemsHandler problemsHandler; + private ArithmeticCalculator arithmeticCalculator; private ColorsCalculator colorsCalculator; private ExpressionComparator comparator = new GuardsComparator(); @@ -62,13 +61,13 @@ public class ExpressionEvaluator { private StringInterpolator embeddedScriptInterpolator; private EmbeddedScriptGenerator embeddedScripting; - public ExpressionEvaluator(ProblemsHandler problemsHandler, Configuration configuration) { + public IScopeAwareExpressionsEvaluator(ProblemsHandler problemsHandler, Configuration configuration) { this(new NullScope(), problemsHandler, configuration); } - public ExpressionEvaluator(IScope scope, ProblemsHandler problemsHandler, Configuration configuration) { + public IScopeAwareExpressionsEvaluator(IScope scope, ProblemsHandler problemsHandler, Configuration configuration) { super(); - this.scope = scope == null ? new NullScope() : scope; + this.lazyScope = scope == null ? new NullScope() : scope; this.problemsHandler = problemsHandler; arithmeticCalculator = new ArithmeticCalculator(problemsHandler); colorsCalculator = new ColorsCalculator(problemsHandler); @@ -142,85 +141,65 @@ public Expression evaluate(Variable input) { return new FaultyExpression(input); } - Expression value = scope.getValue(input); - if (value == null) { + Expression expression = lazyScope.getValue(input); + if (expression == null) { problemsHandler.undefinedVariable(input); return new FaultyExpression(input); } + //FIXME!!!!!!!!!!! while it is clear what should this here do once this is done, this case might + //need some adjuctments while import is not fully solved + if (expression.getType() == ASTCssNodeType.DETACHED_RULESET && expression.getScope() == null) { + throw new BugHappened("Scope information is missing", expression); + } + IScope originalScope = enteringExpression(expression); cycleDetector.enteringVariableValue(input); - Expression result = evaluate(value); + Expression result = evaluate(expression); cycleDetector.leftVariableValue(); + leavingExpression(originalScope); return result; } + private void leavingExpression(IScope originalScope) { + if (originalScope != null) { + eagerScopes.pop(); + } + } + + private IScope enteringExpression(Expression value) { + IScope owningScope = value.getScope(); + if (owningScope != null) { + eagerScopes.push(owningScope); + } + return owningScope; + } + + //FIXME: !!!!!!!!!! try this on variable that depends on undefined public Expression evaluateIfPresent(Variable input) { - Expression value = scope.getValue(input); + Expression value = lazyScope.getValue(input); if (value == null) { return null; } + //FIXME !!!!!!!!!!!!!!!!! add to eager scopes return evaluate(value); } - // public Expression evaluate(IndirectVariable input) { - // Expression value = evaluate(scope.getValue(input)); - // - // if (!(value instanceof CssString)) { - // problemsHandler.nonStringIndirection(input); - // return new FaultyExpression(input); - // } - // - // CssString realName = (CssString) value; - // String realVariableName = "@" + realName.getValue(); - // value = scope.getValue(realVariableName); - // if (value == null) { - // problemsHandler.undefinedVariable(realVariableName, realName); - // return new FaultyExpression(realName.getUnderlyingStructure()); - // } - // return evaluate(value); - // } - -// public Expression evaluate(IndirectVariable input) { -// Expression value = evaluate(scope.getValue(input)); -// -// CssPrinter printer = new InStringCssPrinter(); -// printer.append(value); -// String realName = printer.toString(); -// -// if (!(value instanceof CssString)) { -// problemsHandler.nonStringIndirection(input); -// return new FaultyExpression(input); -// } -// -// CssString oldRealName = (CssString) value; -// if (!oldRealName.getValue().equals(realName)) -// System.out.println(oldRealName.getValue() + " -> " + realName); -// -// String oldRealVariableName = "@" + oldRealName.getValue(); -// value = scope.getValue(oldRealVariableName); -// if (value == null) { -// problemsHandler.undefinedVariable(oldRealVariableName, oldRealName); -// return new FaultyExpression(oldRealName.getUnderlyingStructure()); -// } -// return evaluate(value); -// } - - public Expression evaluate(IndirectVariable input) { - Expression reference = evaluate(scope.getValue(input)); - - CssPrinter printer = new InStringCssPrinter(); - printer.append(reference); - String realName = printer.toString(); - - String realVariableName = "@" + realName; - Expression value = scope.getValue(realVariableName); - if (value == null) { - problemsHandler.undefinedVariable(realVariableName, input); - return new FaultyExpression(input.getUnderlyingStructure()); - } - return evaluate(value); + public Expression evaluate(IndirectVariable input) { + Expression reference = evaluate(lazyScope.getValue(input)); + + CssPrinter printer = new InStringCssPrinter(); + printer.append(reference); + String realName = printer.toString(); + + String realVariableName = "@" + realName; + Expression value = lazyScope.getValue(realVariableName); + if (value == null) { + problemsHandler.undefinedVariable(realVariableName, input); + return new FaultyExpression(input.getUnderlyingStructure()); } + return evaluate(value); + } public Expression evaluate(Expression input) { switch (input.getType()) { @@ -257,15 +236,18 @@ public Expression evaluate(Expression input) { case EMBEDDED_SCRIPT: return evaluate((EmbeddedScript) input); - //the value is already there, nothing to evaluate + //FIXME: !!!!!!!!!!! now, this is probably wrong, just hotefixing prototype case DETACHED_RULESET: + return evaluate((DetachedRuleset) input); + + //the value is already there, nothing to evaluate -- TODO - probably bug, should create clone() case IDENTIFIER_EXPRESSION: case COLOR_EXPRESSION: case NUMBER: case FAULTY_EXPRESSION: case UNICODE_RANGE_EXPRESSION: case EMPTY_EXPRESSION: - return input; + return input.clone(); default: throw new BugHappened("Unknown expression type " + input.getType(), input); @@ -336,20 +318,30 @@ private boolean canCompareDimensions(Dimension left, Dimension right) { } public Expression evaluate(FunctionExpression input) { - Expression evaluatedParameter = evaluate(input.getParameter()); - List splitParameters = (evaluatedParameter.getType() == ASTCssNodeType.EMPTY_EXPRESSION) ? new ArrayList() : evaluatedParameter.splitByComma(); - - if (!input.isCssOnlyFunction()) { + // FIXME: !!!!!!!!!!! input parameter has null scope <- use closes parental scope if the current scope is null + Expression evaluatedParameter = evaluate(input.getParameter()); + List splitParameters = (evaluatedParameter.getType()==ASTCssNodeType.EMPTY_EXPRESSION)?new ArrayList() : evaluatedParameter.splitByComma(); + + if (!input.isCssOnlyFunction()) { for (FunctionsPackage pack : functions) { if (pack.canEvaluate(input, splitParameters)) return pack.evaluate(input, splitParameters, evaluatedParameter); } } - - UnknownFunction unknownFunction = new UnknownFunction(); + + UnknownFunction unknownFunction = new UnknownFunction(); return unknownFunction.evaluate(splitParameters, problemsHandler, input, evaluatedParameter); } + public Expression evaluate(ListExpression input) { + List evaluated = new ArrayList(); + for (Expression expression : input.getExpressions()) { + evaluated.add(evaluate(expression)); + } + //FIXME !!!!!!!!!!! ensure scope in all kinds of expressions + return new ListExpression(input.getUnderlyingStructure(), evaluated, input.getOperator().clone(), input.getScope()); + } + public Expression evaluate(NamedExpression input) { return new NamedExpression(input.getUnderlyingStructure(), input.getName(), evaluate(input.getExpression())); } @@ -387,14 +379,6 @@ public Expression evaluate(BinaryExpression input) { return new FaultyExpression(input); } - public Expression evaluate(ListExpression input) { - List evaluated = new ArrayList(); - for (Expression expression : input.getExpressions()) { - evaluated.add(evaluate(expression)); - } - return new ListExpression(input.getUnderlyingStructure(), evaluated, input.getOperator().clone()); - } - public boolean guardsSatisfied(ReusableStructure mixin) { return evaluate(mixin.getGuards()); } @@ -455,176 +439,34 @@ public boolean isRatioExpression(Expression expression) { return true; } -} - -class NullScope extends BasicScope { - - private static final String NULL = "#null#"; - - protected NullScope() { - super(new LocalScope(null, Arrays.asList(NULL), NULL), new ScopesTree()); - } - - @Override - public IScope getParent() { - return this; - } - - @Override - public List getChilds() { - return Collections.emptyList(); - } + public Expression evaluate(DetachedRuleset input) { + DetachedRuleset clone = input.clone(); - @Override - public List getNames() { - return Collections.emptyList(); - } + IScope owningScope = clone.getScope(); + if (owningScope == null) + throw new BugHappened("Detached ruleset with unknown scope.", input); - @Override - public boolean hasParent() { - return false; + clone.setScope(composedScope(owningScope)); + return clone; } - @Override - public void registerVariable(AbstractVariableDeclaration declaration) { - } + //FIXME !!!!!!! do something smarter, if they are local to each other they need special handling + //FIXME !!!!!!! make sure detached ruleset does not tie its own scope twice to itself + private IScope composedScope(IScope owningScope) { + //FIXME!!!!!!!!!!! nicer design, this whole method could be inside scope factory + Iterator nextEager = eagerScopes.iterator(); + if (!nextEager.hasNext()) + return owningScope; - @Override - public void registerVariable(AbstractVariableDeclaration node, Expression replacementValue) { - } - - @Override - public void registerVariableIfNotPresent(String name, Expression replacementValue) { - } - - @Override - public Expression getValue(Variable variable) { - return null; - } - - @Override - public Expression getValue(String name) { - return null; - } - - @Override - public void registerMixin(ReusableStructure mixin, IScope mixinsBodyScope) { - } - - // @Override - // public void setParent(IScope parent) { - // } - - @Override - public void removedFromAst() { - } - - @Override - public boolean isPresentInAst() { - return false; - } - - @Override - public IScope getRootScope() { - return this; - } - - @Override - public IScope getChildOwnerOf(ASTCssNode body) { - return null; - } - - @Override - public void addNames(List names) { - } - - @Override - public String toFullName() { - return toLongString(); - } - - @Override - public void registerVariable(String name, Expression replacementValue) { - } - - @Override - public void addFilteredVariables(ExpressionFilter filter, IScope source) { - } - - @Override - public void addAllMixins(List mixins) { - } - - @Override - public void add(IScope otherSope) { - } - - @Override - public DataPlaceholder createDataPlaceholder() { - return null; - } - - @Override - public void addToDataPlaceholder(IScope otherScope) { - } - - @Override - public void closeDataPlaceholder() { - } - - @Override - public List getAllMixins() { - return Collections.emptyList(); - } - - @Override - public List getMixinsByName(List nameChain, ReusableStructureName name) { - return Collections.emptyList(); - } - - @Override - public List getMixinsByName(ReusableStructureName name) { - return Collections.emptyList(); - } - - @Override - public List getMixinsByName(String name) { - return Collections.emptyList(); - } - - @Override - public IScope firstChild() { - return null; - } - - @Override - public boolean isBodyOwnerScope() { - return false; - } - - @Override - public IScope skipBodyOwner() { - return this; - } - - @Override - public String toLongString() { - return NULL; - } - - @Override - public boolean seesLocalDataOf(IScope otherScope) { - return false; - } - - @Override - public IScope childByOwners(ASTCssNode headNode, ASTCssNode... restNodes) { - return this; - } + IScope result = nextEager.next(); + while (nextEager.hasNext()) { + //FIXME!!!!!!!!!!! chech whether joining needs to happen - e.g. whehter they see each other + IScope next = nextEager.next(); + result = ScopeFactory.createJoinedScopesView(result, next); + } + result = ScopeFactory.createJoinedScopesView(result, owningScope); - @Override - public int getTreeSize() { - return 1; + return result; } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MixinsGuardsValidator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MixinsGuardsValidator.java index 70f732af..440547b5 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MixinsGuardsValidator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/MixinsGuardsValidator.java @@ -22,7 +22,7 @@ public boolean guardsSatisfied(ReusableStructure mixin, boolean assumeDefault) { return ifNotDefaultExpressionEvaluator.guardsSatisfied(mixin); } - private class DefaultPoweredExpressionEvaluator extends ExpressionEvaluator { + private class DefaultPoweredExpressionEvaluator extends IScopeAwareExpressionsEvaluator { public DefaultPoweredExpressionEvaluator(IScope scope, ProblemsHandler problemsHandler, Configuration configuration, boolean assumeDefault) { super(scope, problemsHandler, configuration); diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypesConversionUtils.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypesConversionUtils.java index 7f3fc677..15ef14c7 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypesConversionUtils.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/TypesConversionUtils.java @@ -37,7 +37,7 @@ public String contentToString(Expression input) { } public String extractFilename(Expression urlInput, ProblemsHandler problemsHandler, Configuration configuration) { - ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(problemsHandler, configuration); + IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(problemsHandler, configuration); Expression urlExpression = expressionEvaluator.evaluate(urlInput); if (urlExpression.getType() != ASTCssNodeType.FUNCTION) diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/strings/StringInterpolator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/strings/StringInterpolator.java index e0c71d83..ef91d4ed 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/strings/StringInterpolator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/expressions/strings/StringInterpolator.java @@ -6,11 +6,11 @@ import com.github.sommeri.less4j.EmbeddedLessGenerator; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.Variable; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; -public class StringInterpolator extends AbstractStringReplacer { +public class StringInterpolator extends AbstractStringReplacer { private static final Pattern STR_INTERPOLATION = Pattern.compile("@\\{([^\\{\\}@])*\\}"); private final EmbeddedScriptGenerator embeddedScriptEvaluator; @@ -36,7 +36,7 @@ protected String extractMatchName(String group) { } @Override - protected String replacementValue(ExpressionEvaluator expressionEvaluator, HiddenTokenAwareTree technicalUnderlying, MatchRange matchRange) { + protected String replacementValue(IScopeAwareExpressionsEvaluator expressionEvaluator, HiddenTokenAwareTree technicalUnderlying, MatchRange matchRange) { Expression value = expressionEvaluator.evaluateIfPresent(new Variable(technicalUnderlying, matchRange.getName())); if (value == null) { return matchRange.getFullMatch(); diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/NullScope.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/NullScope.java new file mode 100644 index 00000000..8b85feac --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/NullScope.java @@ -0,0 +1,186 @@ +package com.github.sommeri.less4j.core.compiler.scopes; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.AbstractVariableDeclaration; +import com.github.sommeri.less4j.core.ast.Expression; +import com.github.sommeri.less4j.core.ast.ReusableStructure; +import com.github.sommeri.less4j.core.ast.ReusableStructureName; +import com.github.sommeri.less4j.core.ast.Variable; +import com.github.sommeri.less4j.core.compiler.expressions.ExpressionFilter; +import com.github.sommeri.less4j.core.compiler.scopes.local.LocalScope; + +public class NullScope extends BasicScope { + + private static final String NULL = "#null#"; + + public NullScope() { + super(new LocalScope(null, Arrays.asList(NULL), NULL), new ScopesTree()); + } + + @Override + public IScope getParent() { + return this; + } + + @Override + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public List getNames() { + return Collections.emptyList(); + } + + @Override + public boolean hasParent() { + return false; + } + + @Override + public void registerVariable(AbstractVariableDeclaration declaration) { + } + + @Override + public void registerVariable(AbstractVariableDeclaration node, Expression replacementValue) { + } + + @Override + public void registerVariableIfNotPresent(String name, Expression replacementValue) { + } + + @Override + public Expression getValue(Variable variable) { + return null; + } + + @Override + public Expression getValue(String name) { + return null; + } + + @Override + public void registerMixin(ReusableStructure mixin, IScope mixinsBodyScope) { + } + + // @Override + // public void setParent(IScope parent) { + // } + + @Override + public void removedFromAst() { + } + + @Override + public boolean isPresentInAst() { + return false; + } + + @Override + public IScope getRootScope() { + return this; + } + + @Override + public IScope getChildOwnerOf(ASTCssNode body) { + return null; + } + + @Override + public void addNames(List names) { + } + + @Override + public String toFullName() { + return toLongString(); + } + + @Override + public void registerVariable(String name, Expression replacementValue) { + } + + @Override + public void addFilteredVariables(ExpressionFilter filter, IScope source) { + } + + @Override + public void addAllMixins(List mixins) { + } + + @Override + public void add(IScope otherSope) { + } + + @Override + public DataPlaceholder createDataPlaceholder() { + return null; + } + + @Override + public void addToDataPlaceholder(IScope otherScope) { + } + + @Override + public void closeDataPlaceholder() { + } + + @Override + public List getAllMixins() { + return Collections.emptyList(); + } + + @Override + public List getMixinsByName(List nameChain, ReusableStructureName name) { + return Collections.emptyList(); + } + + @Override + public List getMixinsByName(ReusableStructureName name) { + return Collections.emptyList(); + } + + @Override + public List getMixinsByName(String name) { + return Collections.emptyList(); + } + + @Override + public IScope firstChild() { + return null; + } + + @Override + public boolean isBodyOwnerScope() { + return false; + } + + @Override + public IScope skipBodyOwner() { + return this; + } + + @Override + public String toLongString() { + return NULL; + } + + @Override + public boolean seesLocalDataOf(IScope otherScope) { + return false; + } + + @Override + public IScope childByOwners(ASTCssNode headNode, ASTCssNode... restNodes) { + return this; + } + + @Override + public int getTreeSize() { + return 1; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ASTManipulator.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ASTManipulator.java index d6c095d2..4d341e09 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ASTManipulator.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ASTManipulator.java @@ -72,7 +72,7 @@ public void replace(ASTCssNode oldChild, ASTCssNode newChild) { } private boolean isAstProperty(PropertyDescriptor descriptor) { - return !descriptor.getReadMethod().isAnnotationPresent(NotAstProperty.class); + return descriptor.getReadMethod()!=null && !descriptor.getReadMethod().isAnnotationPresent(NotAstProperty.class); } private boolean isList(Class propertyType) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ArgumentsBuilder.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ArgumentsBuilder.java index aa48935e..a309925c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ArgumentsBuilder.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ArgumentsBuilder.java @@ -10,7 +10,7 @@ import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.ReusableStructure; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.scopes.IScope; import com.github.sommeri.less4j.core.compiler.scopes.ScopeFactory; import com.github.sommeri.less4j.core.problems.ProblemsHandler; @@ -20,7 +20,7 @@ class ArgumentsBuilder { // utils private final ProblemsHandler problemsHandler; - private final ExpressionEvaluator referenceEvaluator; + private final IScopeAwareExpressionsEvaluator referenceEvaluator; private final String ALL_ARGUMENTS = ReferencesSolver.ALL_ARGUMENTS; // input @@ -32,7 +32,7 @@ class ArgumentsBuilder { private List allValues = new ArrayList(); private IScope argumentsScope; - public ArgumentsBuilder(MixinReference reference, ReusableStructure pureMixin, ExpressionEvaluator referenceEvaluator, ProblemsHandler problemsHandler) { + public ArgumentsBuilder(MixinReference reference, ReusableStructure pureMixin, IScopeAwareExpressionsEvaluator referenceEvaluator, ProblemsHandler problemsHandler) { super(); this.referenceEvaluator = referenceEvaluator; this.problemsHandler = problemsHandler; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/AstLogic.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/AstLogic.java index 5554006c..0b567a28 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/AstLogic.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/AstLogic.java @@ -80,4 +80,8 @@ public static boolean isExpression(ASTCssNode kid) { return kid instanceof Expression; } + public static boolean isDetachedRuleset(Expression value) { + return value!=null && value.getType()==ASTCssNodeType.DETACHED_RULESET; + } + } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/DefaultGuardHelper.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/DefaultGuardHelper.java index 90bd32f0..23e7bc13 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/DefaultGuardHelper.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/DefaultGuardHelper.java @@ -8,7 +8,6 @@ import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.MixinReference; -import com.github.sommeri.less4j.core.ast.ReusableStructure; import com.github.sommeri.less4j.core.compiler.expressions.GuardValue; import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.core.problems.ProblemsHandler; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsReferenceMatcher.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsReferenceMatcher.java index d7187cde..5ed113db 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsReferenceMatcher.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsReferenceMatcher.java @@ -9,7 +9,7 @@ import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.ReusableStructure; import com.github.sommeri.less4j.core.compiler.expressions.ExpressionComparator; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.expressions.PatternsComparator; import com.github.sommeri.less4j.core.compiler.scopes.FullMixinDefinition; import com.github.sommeri.less4j.core.compiler.scopes.IScope; @@ -17,11 +17,11 @@ public class MixinsReferenceMatcher { - private ExpressionEvaluator evaluator; + private IScopeAwareExpressionsEvaluator evaluator; private ExpressionComparator comparator = new PatternsComparator(); public MixinsReferenceMatcher(IScope scope, ProblemsHandler problemsHandler, Configuration configuration) { - evaluator = new ExpressionEvaluator(scope, problemsHandler, configuration); + evaluator = new IScopeAwareExpressionsEvaluator(scope, problemsHandler, configuration); } public List filterByParametersNumber(MixinReference reference, List mixins) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsSolver.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsSolver.java index 36da88ef..a294b5f1 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsSolver.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MixinsSolver.java @@ -14,7 +14,7 @@ import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.ReusableStructure; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.expressions.ExpressionFilter; import com.github.sommeri.less4j.core.compiler.expressions.GuardValue; import com.github.sommeri.less4j.core.compiler.expressions.MixinsGuardsValidator; @@ -46,7 +46,7 @@ public MixinsSolver(ReferencesSolver parentSolver, AstNodesStack semiCompiledNod //FIXME !!!!!!!! unify with done one @Deprecated private BodyCompilationResult resolveMixinReference(final IScope callerScope, final FullMixinDefinition referencedMixin, final IScope mixinWorkingScope) { - final ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(mixinWorkingScope, problemsHandler, configuration); + final IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(mixinWorkingScope, problemsHandler, configuration); final ReusableStructure mixin = referencedMixin.getMixin(); final IScope referencedMixinScope = mixinWorkingScope; @@ -72,7 +72,7 @@ public BodyCompilationResult run() { } private BodyCompilationResult resolveReferencedBody(final IScope callerScope, final BodyOwner mixin, final IScope mixinWorkingScope) { - final ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(mixinWorkingScope, problemsHandler, configuration); + final IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(mixinWorkingScope, problemsHandler, configuration); final IScope referencedMixinScope = mixinWorkingScope; // ... and I'm starting to see the point of closures ... @@ -144,7 +144,7 @@ private void shiftComments(ASTCssNode reference, GeneralBody result) { } private IScope buildMixinsArguments(MixinReference reference, IScope referenceScope, FullMixinDefinition mixin) { - ArgumentsBuilder builder = new ArgumentsBuilder(reference, mixin.getMixin(), new ExpressionEvaluator(referenceScope, problemsHandler, configuration), problemsHandler); + ArgumentsBuilder builder = new ArgumentsBuilder(reference, mixin.getMixin(), new IScopeAwareExpressionsEvaluator(referenceScope, problemsHandler, configuration), problemsHandler); return builder.build(); } @@ -283,10 +283,10 @@ private static IScope calculateBodyWorkingScope(IScope callerScope, IScope argum //FIXME !!!! refactor and clean, unify with references class ImportedScopeFilter implements ExpressionFilter { - private final ExpressionEvaluator expressionEvaluator; + private final IScopeAwareExpressionsEvaluator expressionEvaluator; private final IScope importTargetScope; - public ImportedScopeFilter(ExpressionEvaluator expressionEvaluator, IScope importTargetScope) { + public ImportedScopeFilter(IScopeAwareExpressionsEvaluator expressionEvaluator, IScope importTargetScope) { super(); this.expressionEvaluator = expressionEvaluator; this.importTargetScope = importTargetScope; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ReferencesSolver.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ReferencesSolver.java index aef3a01a..4e8a8156 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ReferencesSolver.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/ReferencesSolver.java @@ -26,7 +26,7 @@ import com.github.sommeri.less4j.core.ast.SimpleSelector; import com.github.sommeri.less4j.core.ast.Variable; import com.github.sommeri.less4j.core.ast.VariableNamePart; -import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; +import com.github.sommeri.less4j.core.compiler.expressions.IScopeAwareExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.expressions.strings.StringInterpolator; import com.github.sommeri.less4j.core.compiler.scopes.FullMixinDefinition; import com.github.sommeri.less4j.core.compiler.scopes.IScope; @@ -103,7 +103,7 @@ private void unsafeDoSolveReferences(ASTCssNode node, IteratedScope iteratedScop } private void solveNonCalligReferences(List childs, IteratedScope iteratedScope) { - ExpressionEvaluator cssGuardsValidator = new ExpressionEvaluator(iteratedScope.getScope(), problemsHandler, configuration); + IScopeAwareExpressionsEvaluator cssGuardsValidator = new IScopeAwareExpressionsEvaluator(iteratedScope.getScope(), problemsHandler, configuration); for (ASTCssNode kid : childs) { if (isMixinReference(kid) || isDetachedRulesetReference(kid)) continue; @@ -175,7 +175,7 @@ private Map solveCalls(List childs, IScope if (fullNodeDefinition == null) { handleUnavailableDetachedRulesetReference(detachedRulesetReference, solvedMixinReferences); } else { - ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(referenceScope, problemsHandler, configuration); + IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(referenceScope, problemsHandler, configuration); Expression evaluatedDetachedRuleset = expressionEvaluator.evaluate(fullNodeDefinition); fullNodeDefinition = evaluatedDetachedRuleset; if (evaluatedDetachedRuleset.getType() != ASTCssNodeType.DETACHED_RULESET) { @@ -235,7 +235,7 @@ protected List findReferencedMixins(MixinReference mixinRef } private boolean solveIfVariableReference(ASTCssNode node, IScope scope) { - ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(scope, problemsHandler, configuration); + IScopeAwareExpressionsEvaluator expressionEvaluator = new IScopeAwareExpressionsEvaluator(scope, problemsHandler, configuration); switch (node.getType()) { case VARIABLE: { Expression replacement = expressionEvaluator.evaluate((Variable) node); @@ -294,14 +294,14 @@ private FixedNamePart toFixedName(Expression value, HiddenTokenAwareTree parent, return fixedName; } - private SimpleSelector interpolateEscapedSelector(EscapedSelector input, ExpressionEvaluator expressionEvaluator) { + private SimpleSelector interpolateEscapedSelector(EscapedSelector input, IScopeAwareExpressionsEvaluator expressionEvaluator) { HiddenTokenAwareTree underlying = input.getUnderlyingStructure(); String value = stringInterpolator.replaceIn(input.getValue(), expressionEvaluator, input.getUnderlyingStructure()); InterpolableName interpolableName = new InterpolableName(underlying, new FixedNamePart(underlying, value)); return new SimpleSelector(input.getUnderlyingStructure(), input.getLeadingCombinator(), interpolableName, false); } - private FixedNamePart interpolateFixedNamePart(FixedNamePart input, ExpressionEvaluator expressionEvaluator) { + private FixedNamePart interpolateFixedNamePart(FixedNamePart input, IScopeAwareExpressionsEvaluator expressionEvaluator) { String value = stringInterpolator.replaceIn(input.getName(), expressionEvaluator, input.getUnderlyingStructure()); return new FixedNamePart(input.getUnderlyingStructure(), value); } diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java b/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java index a8cd735d..a0a4db74 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/ASTBuilderSwitch.java @@ -433,7 +433,11 @@ public Expression handleMixinPattern(HiddenTokenAwareTree token) { } public DetachedRulesetReference handleDetachedRulesetReference(HiddenTokenAwareTree token) { - return new DetachedRulesetReference(token, termBuilder.buildFromVariable(token.getChild(0))); + DetachedRulesetReference result = new DetachedRulesetReference(token, termBuilder.buildFromVariable(token.getChild(0))); + if (token.getChildCount()<3) { + problemsHandler.detachedRulesetCallWithoutParentheses(result); + } + return result; } public DetachedRuleset handleDetachedRuleset(HiddenTokenAwareTree token) { diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/MixinsParametersBuilder.java b/src/main/java/com/github/sommeri/less4j/core/parser/MixinsParametersBuilder.java index 9cfb8da0..14302632 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/MixinsParametersBuilder.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/MixinsParametersBuilder.java @@ -11,12 +11,12 @@ import com.github.sommeri.less4j.core.ast.ReusableStructure; import com.github.sommeri.less4j.core.ast.Variable; import com.github.sommeri.less4j.core.ast.VariableDeclaration; +import com.github.sommeri.less4j.core.compiler.stages.AstLogic; import com.github.sommeri.less4j.core.problems.ProblemsHandler; public class MixinsParametersBuilder { private final ASTBuilderSwitch parentBuilder; - @SuppressWarnings("unused") private ProblemsHandler problemsHandler; public MixinsParametersBuilder(ASTBuilderSwitch astBuilderSwitch, ProblemsHandler problemsHandler) { @@ -100,7 +100,7 @@ private void handleCommaSplitMixinDeclarationArguments(HiddenTokenAwareTree toke if (argument.getType() == ASTCssNodeType.ARGUMENT_DECLARATION) { ArgumentDeclaration argumentDeclaration = (ArgumentDeclaration) argument; - declaration.addParameter(argumentDeclaration); + addParameter(declaration, argumentDeclaration); if (argumentDeclaration.getValue()!=null) { Iterator expressions = argumentDeclaration.getValue().splitByComma().iterator(); @@ -115,14 +115,23 @@ private void handleCommaSplitMixinDeclarationArguments(HiddenTokenAwareTree toke } } + private void addParameter(ReusableStructure declaration, ASTCssNode parameter) { + if (parameter.getType()==ASTCssNodeType.ARGUMENT_DECLARATION) { + Expression value = ((ArgumentDeclaration) parameter).getValue(); + if (AstLogic.isDetachedRuleset(value)) + problemsHandler.warnDetachedRulesetAsMixinParamDefault(value); + } + declaration.addParameter(parameter); + } + private void addParameters(ReusableStructure declaration, Iterator expressions) { while (expressions.hasNext()) { Expression next = expressions.next(); if (next.getType()==ASTCssNodeType.VARIABLE) { - declaration.addParameter(new ArgumentDeclaration((Variable)next, null)); + addParameter(declaration, new ArgumentDeclaration((Variable)next, null)); } else { - declaration.addParameter(next); + addParameter(declaration, next); } } } @@ -131,7 +140,7 @@ private void handleSemicolonSplitMixinDeclarationArguments(HiddenTokenAwareTree List children = token.getChildren(); for (HiddenTokenAwareTree kid : children) { if (kid.getType() != LessLexer.SEMI) { - declaration.addParameter(parentBuilder.switchOn(kid)); + addParameter(declaration, parentBuilder.switchOn(kid)); } } } diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java index b4f7548b..1b70bb31 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsHandler.java @@ -9,6 +9,7 @@ import com.github.sommeri.less4j.core.ast.ArgumentDeclaration; import com.github.sommeri.less4j.core.ast.Body; import com.github.sommeri.less4j.core.ast.ComparisonExpressionOperator; +import com.github.sommeri.less4j.core.ast.DetachedRuleset; import com.github.sommeri.less4j.core.ast.DetachedRulesetReference; import com.github.sommeri.less4j.core.ast.EscapedSelector; import com.github.sommeri.less4j.core.ast.Expression; @@ -327,4 +328,16 @@ public void errUnknownEncodingCharsetSourceMap(ASTCssNode nodeForErrorReport, St addError(nodeForErrorReport, "Source map link or data could not be created. Requested charset '" + encodingCharset+"' is not available."); } + public void detachedRulesetCallWithoutParentheses(DetachedRulesetReference reference) { + addError(reference, "Detached ruleset call is missing parentheses."); + } + + public void warnDetachedRulesetAsMixinParamDefault(Expression detached) { + addWarning(detached, "This works, but is incompatible with less.js. Less.js does not allow detached rulesets as default mixin parameter values."); + } + + public void wrongDetachedRulesetLocation(DetachedRuleset detachedRuleset) { + addError(detachedRuleset, "Detached ruleset is not allowed outside of variable declaration."); + } + } diff --git a/src/main/java/com/github/sommeri/less4j/core/validators/LessAstValidator.java b/src/main/java/com/github/sommeri/less4j/core/validators/LessAstValidator.java index 0c5d617a..600a7e30 100644 --- a/src/main/java/com/github/sommeri/less4j/core/validators/LessAstValidator.java +++ b/src/main/java/com/github/sommeri/less4j/core/validators/LessAstValidator.java @@ -7,8 +7,11 @@ import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.AbstractVariableDeclaration; import com.github.sommeri.less4j.core.ast.Body; +import com.github.sommeri.less4j.core.ast.DetachedRuleset; import com.github.sommeri.less4j.core.ast.EscapedSelector; +import com.github.sommeri.less4j.core.ast.FaultyExpression; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.NestedSelectorAppender; import com.github.sommeri.less4j.core.ast.PseudoClass; @@ -58,6 +61,10 @@ public void validate(ASTCssNode node) { checkForLogicalConditionConsistency((SupportsLogicalCondition) node); break; } + case DETACHED_RULESET: { + checkInapropriateLocation((DetachedRuleset) node); + break; + } default: //nothing is needed } @@ -69,6 +76,22 @@ public void validate(ASTCssNode node) { } + private void checkInapropriateLocation(DetachedRuleset detachedRuleset) { + ASTCssNode parent = detachedRuleset.getParent(); + if (!isVariableDeclaration(parent) && !isMixinReference(parent)) { + manipulator.replace(detachedRuleset, new FaultyExpression(detachedRuleset)); + problemsHandler.wrongDetachedRulesetLocation(detachedRuleset); + } + } + + private boolean isMixinReference(ASTCssNode parent) { + return parent.getType() == ASTCssNodeType.MIXIN_REFERENCE; + } + + private boolean isVariableDeclaration(ASTCssNode parent) { + return parent instanceof AbstractVariableDeclaration; + } + private void checkForLogicalConditionConsistency(SupportsLogicalCondition condition) { Iterator logicalOperators = condition.getLogicalOperators().iterator(); if (!logicalOperators.hasNext()) diff --git a/src/main/java/com/github/sommeri/less4j/core/validators/SupportedCSSBodyMembers.java b/src/main/java/com/github/sommeri/less4j/core/validators/SupportedCSSBodyMembers.java index ad56e9f2..5ab13a5c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/validators/SupportedCSSBodyMembers.java +++ b/src/main/java/com/github/sommeri/less4j/core/validators/SupportedCSSBodyMembers.java @@ -31,23 +31,23 @@ public Set getSupportedMembers(ASTCssNodeType bodyType, ASTCssNo switch (ownerType) { case VIEWPORT: - return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE); + return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.FAULTY_NODE); case RULE_SET: - return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE); + return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.FAULTY_NODE); case PAGE: - return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.PAGE_MARGIN_BOX); + return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.PAGE_MARGIN_BOX, ASTCssNodeType.FAULTY_NODE); case PAGE_MARGIN_BOX: - return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE); + return createSet(ASTCssNodeType.DECLARATION, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.FAULTY_NODE); case MEDIA: case DOCUMENT: return topLevelElements(); case KEYFRAMES: - return createSet(ASTCssNodeType.RULE_SET, ASTCssNodeType.UNKNOWN_AT_RULE); + return createSet(ASTCssNodeType.RULE_SET, ASTCssNodeType.UNKNOWN_AT_RULE, ASTCssNodeType.FAULTY_NODE); default: return allNodeTypes(); diff --git a/src/test/java/com/github/sommeri/less4j/compiler/DetachedRulesetsTest.java b/src/test/java/com/github/sommeri/less4j/compiler/DetachedRulesetsTest.java index 22a1bca1..1690d10c 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/DetachedRulesetsTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/DetachedRulesetsTest.java @@ -3,7 +3,6 @@ import java.io.File; import java.util.Collection; -import org.junit.Ignore; import org.junit.runners.Parameterized.Parameters; //FIXME: call on variable with wrong datatype in @@ -82,7 +81,6 @@ * test if @defaults works correctly -- e.g. including various callers scopes */ -@Ignore public class DetachedRulesetsTest extends BasicFeaturesTest { private static final String standardCases = "src/test/resources/compile-basic-features/detached-rulesets/"; diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-1.css similarity index 100% rename from src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope.css rename to src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-1.css diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-1.less similarity index 100% rename from src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope.less rename to src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-1.less diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.css new file mode 100644 index 00000000..9d1f3eec --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.css @@ -0,0 +1,3 @@ +selector { + property-1: in-passed-through-mixin; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.less new file mode 100644 index 00000000..17b92852 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-2.less @@ -0,0 +1,21 @@ +@detached-ruleset1: { property-1: @variable; }; +.list-namespacer { + .listUnlocker { + @list: @detached-ruleset1 something-else; + } +} + +.mixin(@list) { + @detached1: extract(@list, 1); + @detached1(); +} + +.indirect-mixin(@list) { + @variable: in-passed-through-mixin; + .mixin(@list); +} + +selector { + .list-namespacer > .listUnlocker(); + .indirect-mixin(@list) +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.css new file mode 100644 index 00000000..aed58ee3 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.css @@ -0,0 +1,3 @@ +selector { + property-1: in-passed-through-unlocker; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.less new file mode 100644 index 00000000..8414e9b2 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-in-lists-lists-scope-3.less @@ -0,0 +1,21 @@ +@detached-ruleset1: { property-1: @variable; }; +.list-namespacer { + .listUnlocker { + @list: @detached-ruleset1 something-else; + } +} + +.mixin(@list) { + @detached1: extract(@list, 1); + @detached1(); +} + +.indirect-unlocker() { + @variable: in-passed-through-unlocker; + .list-namespacer > .listUnlocker(); +} + +selector { + .indirect-unlocker(); + .mixin(@list); +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.css index e69de29b..4bfc3056 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.css +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.css @@ -0,0 +1,6 @@ +.default-mixin-argument { + default: default; +} +.custom-mixin-argument { + custom: custom; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.err b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.err new file mode 100644 index 00000000..13919355 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-as-argument.err @@ -0,0 +1,2 @@ +Warnings produced by compilation of testCase +WARNING 1:20 This works, but is incompatible with less.js. Less.js does not allow detached rulesets as default mixin parameter values. \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-additional.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-additional.css index 13971524..2b935310 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-additional.css +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-additional.css @@ -1,4 +1,3 @@ -//does not exists .does-not-exists { - !#error#! + !#error#! } \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.css new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.err b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.err new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.less new file mode 100644 index 00000000..16c008f9 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors-suboptimal.less @@ -0,0 +1,5 @@ +//less.js detached-ruleset-2.less +.detached-as-property-value { +// a: @a(); //uncomment this to give meaning to this test +} + diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.css index 30404ce4..5e125114 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.css +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.css @@ -1 +1,10 @@ -TODO \ No newline at end of file +.call-without-parentheses { + b: 1; +} +b: 1; +.selector { + b: 1; +} +.variable-declaration-symbol-missing { + b: !#error#!; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.err b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.err index 30404ce4..7efad5fa 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.err +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.err @@ -1 +1,7 @@ -TODO \ No newline at end of file +Warnings produced by compilation of testCase +WARNING 15:26 This works, but is incompatible with less.js. Less.js does not allow detached rulesets as default mixin parameter values. +WARNING 2:3 Compilation resulted in incorrect CSS. The declaration ended up inside a body of top level style sheet located at 1:1. +Errors produced by compilation of testCase +ERROR 6:2 Detached ruleset call is missing parentheses. +ERROR 31:6 Detached ruleset is not allowed outside of variable declaration. +ERROR 12:1 The reference brought declaration from 2:3 into top level style sheet which started at 1:1. Compilation produced an incorrect CSS. \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.less index d5c2d144..f25ed4fe 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.less +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-errors.less @@ -6,11 +6,7 @@ @a; } -//less.js detached-ruleset-2.less -.detached-as-property-value { - a: @a(); -} - +//less.js detached-ruleset-2.less - suboptimal //less.js detached-ruleset-3.less .brings-property-on-top {} @a(); @@ -24,7 +20,9 @@ //less.js detached-ruleset-5.less .mixin-variable-does-not-exists(@b) { - @a(); + .selector { + @a(); + } } .mixin-variable-does-not-exists({color: red;}); diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.css index 8d4be02b..0b0c2b2e 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.css +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.css @@ -2,7 +2,7 @@ color: black; one: 1px; four: magic-frame; - visible-one: visible; + visible-one: hidden and if you see this in the output its a bug; visible-two: visible; } .wrap-selector { @@ -46,22 +46,22 @@ html.lt-ie9 header { .without-mixins { b: 1; } -@media (orientation: portrait) and tv { +@media (orientation: portrait) and (smart-merging-not-yet-done: tv) { .my-selector { background-color: black; } } -@media (orientation: portrait) and widescreen and print and tv { +@media (orientation: portrait) and (smart-merging-not-yet-done: widescreen) and (smart-merging-not-yet-done: print) and (smart-merging-not-yet-done: tv) { .triple-wrapped-mq { triple: true; } } -@media (orientation: portrait) and widescreen and tv { +@media (orientation: portrait) and (smart-merging-not-yet-done: widescreen) and (smart-merging-not-yet-done: tv) { .triple-wrapped-mq { triple: true; } } -@media (orientation: portrait) and tv { +@media (orientation: portrait) and (smart-merging-not-yet-done: tv) { .triple-wrapped-mq { triple: true; } diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.less index 24f0fcfb..456a5f7a 100644 --- a/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.less +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-rulesets-lessjs.less @@ -11,7 +11,7 @@ .wrap-selector { @c: visible; @ruleset(); - visible-one: @b; + visible-one: @b; //CHANGE: different behaviour due to https://github.com/less/less.js/issues/2064 visible-two: @c; } } @@ -67,7 +67,7 @@ header { } @my-ruleset: { .my-selector { - @media tv { + @media (smart-merging-not-yet-done: tv) { //CHANGE: originally tv background-color: black; } } @@ -75,7 +75,7 @@ header { @media (orientation:portrait) { @my-ruleset(); .wrap-media-mixin({ - @media tv { + @media (smart-merging-not-yet-done: tv) { //CHANGE: originally tv .triple-wrapped-mq { triple: true; } @@ -83,8 +83,8 @@ header { }); } .wrap-media-mixin(@ruleset) { - @media widescreen { - @media print { + @media (smart-merging-not-yet-done: widescreen) { //CHANGE: originally widescreen + @media (smart-merging-not-yet-done: print) { //CHANGE: originally print @ruleset(); } @ruleset(); diff --git a/src/test/resources/minitests/debug1.css b/src/test/resources/minitests/debug1.css index dba97a6f..2c9cbadd 100644 --- a/src/test/resources/minitests/debug1.css +++ b/src/test/resources/minitests/debug1.css @@ -1,26 +1,2 @@ -@media (orientation: portrait) { -} -@media (orientation: portrait) and (min-size: 256) { - .my-selector { - background-color: black; - } -} -@media (orientation: portrait) and (max-size: 1028) { -} -@media (orientation: portrait) and (max-size: 1028) and (blah: 22) { -} -@media (orientation: portrait) and (max-size: 1028) and (blah: 22) and (min-size: 256) { - .triple-wrapped-mq { - triple: true; - } -} -@media (orientation: portrait) and (max-size: 1028) and (min-size: 256) { - .triple-wrapped-mq { - triple: true; - } -} -@media (orientation: portrait) and (min-size: 256) { - .triple-wrapped-mq { - triple: true; - } -} \ No newline at end of file + +/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6MywKImZpbGUiOiJkZWJ1ZzEuY3NzIiwKImxpbmVDb3VudCI6MSwKIm1hcHBpbmdzIjoiOyIsCiJzb3VyY2VzIjpbXSwKInNvdXJjZXNDb250ZW50IjpbXSwKIm5hbWVzIjpbXQp9Cg== */ diff --git a/src/test/resources/minitests/debug1.less b/src/test/resources/minitests/debug1.less index d35937b1..4c602e44 100644 --- a/src/test/resources/minitests/debug1.less +++ b/src/test/resources/minitests/debug1.less @@ -1,26 +1,5 @@ -@my-ruleset: { - .my-selector { - @media (min-size:256) { //tv - background-color: black; - } - } +.variable-declaration-symbol-missing { + b: { + color: red; }; -@media (orientation:portrait) { - @my-ruleset(); - .wrap-media-mixin({ - @media (min-size:256) { //tv - .triple-wrapped-mq { - triple: true; - } - } - }); -} -.wrap-media-mixin(@ruleset) { - @media (max-size:1028) { //widescreen - @media (blah: 22) { //print - @ruleset(); - } - @ruleset(); - } - @ruleset(); -} +} \ No newline at end of file