From 28d48970617391726e0e91b1ee1db36ef885c2c0 Mon Sep 17 00:00:00 2001 From: jurcovicovam Date: Tue, 1 Jul 2014 13:11:37 +0200 Subject: [PATCH] Detached ruleset unlocked from mixin did not worked right (did not seen mixins parameters). Putting body scope joiner on one place to find out how to refactor them. #186 --- .../core/compiler/scopes/BasicScope.java | 12 ++ .../scopes/FullDetachedRulesetDefinition.java | 34 ---- .../less4j/core/compiler/scopes/IScope.java | 2 + .../compiler/scopes/ScopeManipulation.java | 83 ++++++++++ .../compiler/scopes/local/LocalScopeData.java | 8 - .../compiler/scopes/view/ScopesTreeView.java | 5 - .../core/compiler/stages/MixinsSolver.java | 156 ++---------------- .../core/problems/ProblemsCollector.java | 9 +- .../less4j/core/problems/ProblemsHandler.java | 5 + .../detached-ruleset-unlocked-from-inside.css | 3 + ...detached-ruleset-unlocked-from-inside.less | 14 ++ 11 files changed, 141 insertions(+), 190 deletions(-) delete mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/scopes/FullDetachedRulesetDefinition.java create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/scopes/ScopeManipulation.java create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.css create mode 100644 src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.less diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java index f0986926..be74e7ee 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/BasicScope.java @@ -62,6 +62,18 @@ public boolean seesLocalDataOf(IScope otherScope) { return getParent().seesLocalDataOf(otherScope); } + //FIXME ! this method might be wasteful, we are looking for the same tree, so there should be no need to restart search from top for each parent + public boolean seesAllDataOf(IScope otherScope) { + boolean isLocalImport = seesLocalDataOf(otherScope); + IScope parent = otherScope.getParent(); + while (isLocalImport && parent != null) { + isLocalImport = seesLocalDataOf(parent); + parent = parent.getParent(); + } + + return isLocalImport; + } + public IScope getRootScope() { if (!hasParent()) return this; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/FullDetachedRulesetDefinition.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/FullDetachedRulesetDefinition.java deleted file mode 100644 index 7b76ac0b..00000000 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/FullDetachedRulesetDefinition.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.github.sommeri.less4j.core.compiler.scopes; - -import com.github.sommeri.less4j.core.ast.DetachedRuleset; - -public class FullDetachedRulesetDefinition { - private final DetachedRuleset detached; - private final IScope bodyScope; - - public FullDetachedRulesetDefinition(DetachedRuleset detached, IScope mixinsBodyScope) { - super(); - this.detached = detached; - this.bodyScope = mixinsBodyScope; - } - - public DetachedRuleset getDetached() { - return detached; - } - - public IScope getScope() { - return bodyScope; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("FullDetachedDefinition [detached ["); - builder.append(detached.getSourceLine()).append(":").append(detached.getSourceColumn()); - builder.append("], bodyScope="); - builder.append(bodyScope); - builder.append("]"); - return builder.toString(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/IScope.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/IScope.java index 93b166c9..23c90ae3 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/IScope.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/IScope.java @@ -15,6 +15,8 @@ public interface IScope extends ILocalScope, IScopesTree { public boolean seesLocalDataOf(IScope otherScope); + public boolean seesAllDataOf(IScope otherScope); + //smart util methods public IScope firstChild(); diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/ScopeManipulation.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/ScopeManipulation.java new file mode 100644 index 00000000..0bf6c60a --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/ScopeManipulation.java @@ -0,0 +1,83 @@ +package com.github.sommeri.less4j.core.compiler.scopes; + +import java.util.ArrayList; +import java.util.List; + +import com.github.sommeri.less4j.core.compiler.scopes.view.ScopeView; + +public class ScopeManipulation { + + //FIXME !!!! refactor and clean, unify with references solver joiner + public ScopeView constructImportedBodyScope(IScope importTargetScope, IScope bodyToBeImportedScope) { + ScopeView newScope = null; + // locally defined mixin does not require any other action + boolean isLocalImport = bodyToBeImportedScope.seesAllDataOf(importTargetScope); + + if (isLocalImport) { + // we need to copy the whole tree, because this runs inside referenced mixin scope + // snapshot and imported mixin needs to remember the scope as it is now + newScope = ScopeFactory.createJoinedScopesView(null, bodyToBeImportedScope); + newScope.saveLocalDataForTheWholeWayUp(); + } else { + // since this is non-local import, we need to join reference scope and imported mixins scope + // imported mixin needs to have access to variables defined in caller + newScope = ScopeFactory.createJoinedScopesView(importTargetScope, bodyToBeImportedScope); + newScope.saveLocalDataForTheWholeWayUp(); + } + return newScope; + } + + public IScope calculateBodyWorkingScope(IScope callerScope, IScope bodyScope) { + //FIXME !!!!!! find the case where this is needed for mixins and create test case for it + // locally defined mixin does not require any other action + boolean isLocallyDefined = bodyScope.seesAllDataOf(callerScope); + + if (isLocallyDefined) { + return bodyScope; + } + + //join scopes + IScope result = ScopeFactory.createJoinedScopesView(callerScope, bodyScope); + return result; + } + + //FIXME: !!! evaluate need for this + public IScope calculateMixinsWorkingScope(IScope callerScope, IScope arguments, IScope mixinScope) { + // add arguments + IScope mixinDeclarationScope = mixinScope.getParent(); + mixinDeclarationScope.add(arguments); + + // locally defined mixin does not require any other action + boolean isLocallyDefined = mixinDeclarationScope.seesLocalDataOf(callerScope); + if (isLocallyDefined) { + return mixinScope; + } + + //join scopes + IScope result = ScopeFactory.createJoinedScopesView(callerScope, mixinScope); + return result; + } + + public List mixinsToImport(IScope referenceScope, IScope referencedMixinScope, List unmodifiedMixinsToImport) { + List result = new ArrayList(); + for (FullMixinDefinition mixinToImport : unmodifiedMixinsToImport) { + boolean isLocalImport = mixinToImport.getScope().seesLocalDataOf(referenceScope); + if (isLocalImport) { + // we need to copy the whole tree, because this runs inside referenced mixin scope + // snapshot and imported mixin needs to remember the scope as it is now + ScopeView newWay = ScopeFactory.createJoinedScopesView(null, mixinToImport.getScope()); + newWay.saveLocalDataForTheWholeWayUp(); + result.add(new FullMixinDefinition(mixinToImport.getMixin(), newWay)); + } else { + // since this is non-local import, we need to join reference scope and imported mixins scope + // imported mixin needs to have access to variables defined in caller + ScopeView newWay = ScopeFactory.createJoinedScopesView(referencedMixinScope, mixinToImport.getScope()); + newWay.saveLocalDataForTheWholeWayUp(); + result.add(new FullMixinDefinition(mixinToImport.getMixin(), newWay)); + } + + } + return result; + } + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/local/LocalScopeData.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/local/LocalScopeData.java index feed0429..2236b6ab 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/local/LocalScopeData.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/local/LocalScopeData.java @@ -1,17 +1,11 @@ package com.github.sommeri.less4j.core.compiler.scopes.local; -import java.util.HashMap; -import java.util.Map; - -import com.github.sommeri.less4j.core.ast.DetachedRuleset; -import com.github.sommeri.less4j.core.compiler.scopes.FullDetachedRulesetDefinition; public class LocalScopeData implements Cloneable { private VariablesDeclarationsStorage variables = new VariablesDeclarationsStorage(); private MixinsDefinitionsStorage mixins = new MixinsDefinitionsStorage(); - private Map detachedRulesetsScopes = new HashMap(); @Override public LocalScopeData clone() { @@ -19,7 +13,6 @@ public LocalScopeData clone() { LocalScopeData clone = (LocalScopeData) super.clone(); clone.variables = variables.clone(); clone.mixins = mixins.clone(); - clone.detachedRulesetsScopes = new HashMap(detachedRulesetsScopes); return clone; } catch (CloneNotSupportedException e) { @@ -40,7 +33,6 @@ public String toString() { StringBuilder result = new StringBuilder(getClass().getSimpleName()).append("\n"); result.append("**Variables storage: ").append(variables).append("\n\n"); result.append("**Mixins storage: ").append(mixins).append("\n\n"); - result.append("**DetachedRulesets size: ").append(detachedRulesetsScopes.size()).append("\n\n"); return result.toString(); } } diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopesTreeView.java b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopesTreeView.java index 36ef2cc0..1a430686 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopesTreeView.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/scopes/view/ScopesTreeView.java @@ -40,11 +40,6 @@ public void addChild(IScope child) { throw new IllegalStateException("Scopes view does not accept new childs."); } -// @Override -// public void setParent(IScope parent) { -// throw new IllegalStateException("Scopes view does not accept new parents."); -// } - @Override public void setParent(IScope parent) { throw new IllegalStateException("Scopes view does not accept new parents."); 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 7f9a7376..849ae6b8 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,8 +14,8 @@ 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.ExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.expressions.ExpressionFilter; +import com.github.sommeri.less4j.core.compiler.expressions.ExpressionsEvaluator; import com.github.sommeri.less4j.core.compiler.expressions.GuardValue; import com.github.sommeri.less4j.core.compiler.expressions.MixinsGuardsValidator; import com.github.sommeri.less4j.core.compiler.scopes.FullMixinDefinition; @@ -24,7 +24,7 @@ import com.github.sommeri.less4j.core.compiler.scopes.InScopeSnapshotRunner.IFunction; import com.github.sommeri.less4j.core.compiler.scopes.InScopeSnapshotRunner.ITask; import com.github.sommeri.less4j.core.compiler.scopes.ScopeFactory; -import com.github.sommeri.less4j.core.compiler.scopes.view.ScopeView; +import com.github.sommeri.less4j.core.compiler.scopes.ScopeManipulation; import com.github.sommeri.less4j.core.problems.ProblemsHandler; class MixinsSolver { @@ -34,6 +34,7 @@ class MixinsSolver { private final AstNodesStack semiCompiledNodes; private final Configuration configuration; private final DefaultGuardHelper defaultGuardHelper; + private final ScopeManipulation scopeManipulation = new ScopeManipulation(); public MixinsSolver(ReferencesSolver parentSolver, AstNodesStack semiCompiledNodes, ProblemsHandler problemsHandler, Configuration configuration) { this.parentSolver = parentSolver; @@ -43,60 +44,27 @@ public MixinsSolver(ReferencesSolver parentSolver, AstNodesStack semiCompiledNod this.defaultGuardHelper = new DefaultGuardHelper(problemsHandler); } - //FIXME !!!!!!!! unify with done one - @Deprecated - private BodyCompilationResult resolveMixinReference(final IScope callerScope, final FullMixinDefinition referencedMixin, final IScope mixinWorkingScope) { - final ExpressionsEvaluator expressionEvaluator = new ExpressionsEvaluator(mixinWorkingScope, problemsHandler, configuration); - final ReusableStructure mixin = referencedMixin.getMixin(); + private BodyCompilationResult resolveCalledBody(final IScope callerScope, final BodyOwner bodyOwner, final IScope bodyWorkingScope) { + final ExpressionsEvaluator expressionEvaluator = new ExpressionsEvaluator(bodyWorkingScope, problemsHandler, configuration); - final IScope referencedMixinScope = mixinWorkingScope; + final IScope referencedMixinScope = bodyWorkingScope; // ... and I'm starting to see the point of closures ... return InScopeSnapshotRunner.runInOriginalDataSnapshot(referencedMixinScope, new IFunction() { @Override public BodyCompilationResult run() { // compile referenced mixin - keep the original copy unchanged - List replacement = compileBody(mixin.getBody(), referencedMixinScope); - - // collect variables and mixins to be imported - IScope returnValues = expressionEvaluator.evaluateValues(referencedMixinScope); - List unmodifiedMixinsToImport = referencedMixinScope.getAllMixins(); - - List allMixinsToImport = mixinsToImport(callerScope, referencedMixinScope, unmodifiedMixinsToImport); - returnValues.addAllMixins(allMixinsToImport); - - return new BodyCompilationResult(mixin, replacement, returnValues); - } - - }); - } - - private BodyCompilationResult resolveReferencedBody(final IScope callerScope, final BodyOwner mixin, final IScope mixinWorkingScope) { - final ExpressionsEvaluator expressionEvaluator = new ExpressionsEvaluator(mixinWorkingScope, problemsHandler, configuration); - - final IScope referencedMixinScope = mixinWorkingScope; - // ... and I'm starting to see the point of closures ... - return InScopeSnapshotRunner.runInOriginalDataSnapshot(referencedMixinScope, new IFunction() { - - @Override - public BodyCompilationResult run() { - // compile referenced mixin - keep the original copy unchanged - List replacement = compileBody(mixin.getBody(), referencedMixinScope); - - // collect variables and mixins to be imported - // IScope returnValues = expressionEvaluator.evaluateValues(referencedMixinScope); + List replacement = compileBody(bodyOwner.getBody(), referencedMixinScope); // collect variables and mixins to be imported IScope returnValues = ScopeFactory.createDummyScope(); returnValues.addFilteredVariables(new ImportedScopeFilter(expressionEvaluator, callerScope), referencedMixinScope); List unmodifiedMixinsToImport = referencedMixinScope.getAllMixins(); - List allMixinsToImport = mixinsToImport(callerScope, referencedMixinScope, unmodifiedMixinsToImport); + List allMixinsToImport = scopeManipulation.mixinsToImport(callerScope, referencedMixinScope, unmodifiedMixinsToImport); returnValues.addAllMixins(allMixinsToImport); - //FIXME: !!!!!!!!!! clean up - return new BodyCompilationResult((ASTCssNode) mixin, replacement, returnValues); - + return new BodyCompilationResult((ASTCssNode) bodyOwner, replacement, returnValues); } }); @@ -113,28 +81,6 @@ private List compileBody(Body body, IScope scopeSnapshot) { } } - private List mixinsToImport(IScope referenceScope, IScope referencedMixinScope, List unmodifiedMixinsToImport) { - List result = new ArrayList(); - for (FullMixinDefinition mixinToImport : unmodifiedMixinsToImport) { - boolean isLocalImport = mixinToImport.getScope().seesLocalDataOf(referenceScope); - if (isLocalImport) { - // we need to copy the whole tree, because this runs inside referenced mixin scope - // snapshot and imported mixin needs to remember the scope as it is now - ScopeView newWay = ScopeFactory.createJoinedScopesView(null, mixinToImport.getScope()); - newWay.saveLocalDataForTheWholeWayUp(); - result.add(new FullMixinDefinition(mixinToImport.getMixin(), newWay)); - } else { - // since this is non-local import, we need to join reference scope and imported mixins scope - // imported mixin needs to have access to variables defined in caller - ScopeView newWay = ScopeFactory.createJoinedScopesView(referencedMixinScope, mixinToImport.getScope()); - newWay.saveLocalDataForTheWholeWayUp(); - result.add(new FullMixinDefinition(mixinToImport.getMixin(), newWay)); - } - - } - return result; - } - private void shiftComments(ASTCssNode reference, GeneralBody result) { List childs = result.getMembers(); if (!childs.isEmpty()) { @@ -166,7 +112,7 @@ public GeneralBody buildMixinReferenceReplacement(final MixinReference reference @Override public void run() { IScope mixinArguments = buildMixinsArguments(reference, callerScope, fullMixin); - IScope mixinWorkingScope = calculateMixinsWorkingScope(callerScope, mixinArguments, mixinScope); + IScope mixinWorkingScope = scopeManipulation.calculateMixinsWorkingScope(callerScope, mixinArguments, mixinScope); MixinsGuardsValidator guardsValidator = new MixinsGuardsValidator(mixinWorkingScope, problemsHandler, configuration); GuardValue guardValue = guardsValidator.evaluateGuards(mixin); @@ -174,7 +120,7 @@ public void run() { if (guardValue != GuardValue.DO_NOT_USE) { //OPTIMIZATION POSSIBLE: there is no need to compile mixins at this point, some of them are not going to be //used and create snapshot operation is cheap now. It should be done later on. - BodyCompilationResult compiled = resolveMixinReference(callerScope, fullMixin, mixinWorkingScope); + BodyCompilationResult compiled = resolveCalledBody(callerScope, fullMixin.getMixin(), mixinWorkingScope); //mark the mixin according to its default() function use compiled.setGuardValue(guardValue); //store the mixin as candidate @@ -202,8 +148,8 @@ public void run() { public GeneralBody buildDetachedRulesetReplacement(DetachedRulesetReference reference, IScope callerScope, DetachedRuleset detachedRuleset, IScope detachedRulesetScope) { //FIXME: !!!!!!!!!!!! this should run in detachedRulesetScope parent snapshot - check whether it is the case - IScope mixinWorkingScope = calculateBodyWorkingScope(callerScope, null, detachedRulesetScope); - BodyCompilationResult compiled = resolveReferencedBody(callerScope, detachedRuleset, mixinWorkingScope); + IScope mixinWorkingScope = scopeManipulation.calculateBodyWorkingScope(callerScope, detachedRulesetScope); + BodyCompilationResult compiled = resolveCalledBody(callerScope, detachedRuleset, mixinWorkingScope); GeneralBody result = new GeneralBody(reference.getUnderlyingStructure()); result.addMembers(compiled.getReplacement()); @@ -235,56 +181,12 @@ private void declarationsAreImportant(Body result) { } } - @Deprecated - //FIXME: !!! evaluate need for this - private static IScope calculateMixinsWorkingScope(IScope callerScope, IScope arguments, IScope mixinScope) { - // add arguments - IScope mixinDeclarationScope = mixinScope.getParent(); - mixinDeclarationScope.add(arguments); - - // locally defined mixin does not require any other action - boolean isLocallyDefined = mixinDeclarationScope.seesLocalDataOf(callerScope); - if (isLocallyDefined) { - return mixinScope; - } - - //join scopes - IScope result = ScopeFactory.createJoinedScopesView(callerScope, mixinScope); - return result; - } - - private static IScope calculateBodyWorkingScope(IScope callerScope, IScope arguments, IScope bodyScope) { - // add arguments - if (arguments != null) { - IScope mixinDeclarationScope = bodyScope.getParent(); - mixinDeclarationScope.add(arguments); - } - - //FIXME !!!!!!!!!!! make this a method in IScope - //FIXME !!!!!! find the case where this is needed for mixins and create test case for it - // locally defined mixin does not require any other action - boolean isLocallyDefined = bodyScope.seesLocalDataOf(callerScope); - IScope parent = callerScope.getParent(); - while (isLocallyDefined && parent != null) { - isLocallyDefined = bodyScope.seesLocalDataOf(parent); - ; - parent = parent.getParent(); - } - - if (isLocallyDefined) { - return bodyScope; - } - - //join scopes - IScope result = ScopeFactory.createJoinedScopesView(callerScope, bodyScope); - return result; - } - //FIXME !!!! refactor and clean, unify with references class ImportedScopeFilter implements ExpressionFilter { private final ExpressionsEvaluator expressionEvaluator; private final IScope importTargetScope; + private final ScopeManipulation scopeManipulation = new ScopeManipulation(); public ImportedScopeFilter(ExpressionsEvaluator expressionEvaluator, IScope importTargetScope) { super(); @@ -304,35 +206,7 @@ private IScope apply(IScope input) { if (input == null) return importTargetScope; - return constructImportedBodyScope(importTargetScope, input); - - } - - //FIXME !!!! refactor and clean, unify with references solver joiner - private ScopeView constructImportedBodyScope(IScope importTargetScope, IScope bodyToBeImportedScope) { - ScopeView newScope = null; - boolean isLocalImport = bodyToBeImportedScope.seesLocalDataOf(importTargetScope); - - //FIXME !!!! unify with the above scope joiners - // locally defined mixin does not require any other action - IScope parent = importTargetScope.getParent(); - while (isLocalImport && parent != null) { - isLocalImport = bodyToBeImportedScope.seesLocalDataOf(parent); - parent = parent.getParent(); - } - - if (isLocalImport) { - // we need to copy the whole tree, because this runs inside referenced mixin scope - // snapshot and imported mixin needs to remember the scope as it is now - newScope = ScopeFactory.createJoinedScopesView(null, bodyToBeImportedScope); - newScope.saveLocalDataForTheWholeWayUp(); - } else { - // since this is non-local import, we need to join reference scope and imported mixins scope - // imported mixin needs to have access to variables defined in caller - newScope = ScopeFactory.createJoinedScopesView(importTargetScope, bodyToBeImportedScope); - newScope.saveLocalDataForTheWholeWayUp(); - } - return newScope; + return scopeManipulation.constructImportedBodyScope(importTargetScope, input); } } diff --git a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsCollector.java b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsCollector.java index ab1865ba..5717962d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsCollector.java +++ b/src/main/java/com/github/sommeri/less4j/core/problems/ProblemsCollector.java @@ -7,10 +7,10 @@ import com.github.sommeri.less4j.LessCompiler.Problem; public class ProblemsCollector { - + private List warnings = new ArrayList(); private List errors = new ArrayList(); - + public void addErrors(Collection errors) { this.errors.addAll(errors); } @@ -39,4 +39,9 @@ public List getWarnings() { return warnings; } + @Override + public String toString() { + return "ProblemsCollector [errors:" + errors.size() + ", " + "warnings: " + warnings.size() + "]"; + } + } 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 8cfa092d..f770b775 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 @@ -341,4 +341,9 @@ public void stringInterpolationNotSupported(HiddenTokenAwareTree errorNode, Expr addError(errorNode, "String interpolation does not requeted expression type. Requested expression was defined at " + printer.toPosition(value)); } + @Override + public String toString() { + return collector.toString(); + } + } diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.css b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.css new file mode 100644 index 00000000..92512c49 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.css @@ -0,0 +1,3 @@ +#test { + width: 50px; +} diff --git a/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.less b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.less new file mode 100644 index 00000000..2ffd06c4 --- /dev/null +++ b/src/test/resources/compile-basic-features/detached-rulesets/detached-ruleset-unlocked-from-inside.less @@ -0,0 +1,14 @@ +#test { + // define mixin + .apples(@width: 100px) { + @bananas: { + width: @width; + }; + } + + // unlock + .apples(50px); + + // use imported mixin + @bananas(); +} \ No newline at end of file