From bf7faba9cda9de038994e70a047f1063c54f20c0 Mon Sep 17 00:00:00 2001 From: meri Date: Thu, 24 Jan 2013 14:06:12 +0100 Subject: [PATCH] Media bubbling and mergin. Part of #42 --- .../github/sommeri/less4j/core/parser/Less.g | 54 ++-- .../less4j/core/ast/ASTCssNodeType.java | 2 +- .../github/sommeri/less4j/core/ast/Body.java | 81 ++++-- .../sommeri/less4j/core/ast/BodyOwner.java | 8 + .../sommeri/less4j/core/ast/FontFace.java | 18 +- .../sommeri/less4j/core/ast/GeneralBody.java | 2 +- .../sommeri/less4j/core/ast/Keyframes.java | 2 +- .../less4j/core/ast/KeyframesBody.java | 2 +- .../github/sommeri/less4j/core/ast/Media.java | 44 ++-- .../sommeri/less4j/core/ast/MediaQuery.java | 9 + .../github/sommeri/less4j/core/ast/Page.java | 2 +- .../less4j/core/ast/PageMarginBox.java | 2 +- .../less4j/core/ast/ReusableStructure.java | 8 +- .../sommeri/less4j/core/ast/RuleSet.java | 10 +- .../sommeri/less4j/core/ast/RuleSetsBody.java | 25 -- .../sommeri/less4j/core/ast/StyleSheet.java | 2 +- .../sommeri/less4j/core/ast/Viewport.java | 2 +- .../core/compiler/LessToCssCompiler.java | 14 +- .../core/compiler/stages/ASTManipulator.java | 35 ++- .../less4j/core/compiler/stages/AstLogic.java | 1 - .../stages/MediaBubblerAndMerger.java | 238 +++++++++++++++--- .../compiler/stages/NestedMediaCollector.java | 90 +++++++ .../compiler/stages/ReferencesSolver.java | 20 +- .../compiler/stages/SimpleImportsSolver.java | 7 +- .../less4j/core/parser/ASTBuilderSwitch.java | 48 ++-- .../less4j/core/parser/TokenTypeSwitch.java | 4 +- .../less4j/core/problems/ProblemsHandler.java | 5 + .../sommeri/less4j/utils/CssPrinter.java | 24 +- .../sommeri/less4j/utils/DebugUtils.java | 42 ++++ .../sommeri/less4j/compiler/MediaTest.java | 23 ++ src/test/resources/command-line/errors.css | 4 + .../command-line/errorsandwarnings.css | 4 + .../duplicate-declarations-removal.css | 1 - .../compile-basic-features/media/debug.css | 10 + .../compile-basic-features/media/debug.less | 22 ++ ...g.css => media-bubbling-lessjs-issues.css} | 61 ++--- ...less => media-bubbling-lessjs-issues.less} | 68 ++--- .../media/media-bubbling-scoping.less | 1 - .../media/media-merging-top-level.css | 40 +++ .../media/media-merging-top-level.less | 45 ++++ .../media/media-merging-with-bubbling.css | 17 ++ .../media/media-merging-with-bubbling.less | 30 +++ .../media/media-problems-and-todo.less | 46 ---- .../media/media-with-font-face.css | 6 + .../media/media-with-font-face.less | 7 + .../media/todo/media-query-as-variable.less | 24 ++ 46 files changed, 880 insertions(+), 330 deletions(-) create mode 100644 src/main/java/com/github/sommeri/less4j/core/ast/BodyOwner.java delete mode 100644 src/main/java/com/github/sommeri/less4j/core/ast/RuleSetsBody.java create mode 100644 src/main/java/com/github/sommeri/less4j/core/compiler/stages/NestedMediaCollector.java create mode 100644 src/main/java/com/github/sommeri/less4j/utils/DebugUtils.java create mode 100644 src/test/java/com/github/sommeri/less4j/compiler/MediaTest.java create mode 100644 src/test/resources/command-line/errors.css create mode 100644 src/test/resources/command-line/errorsandwarnings.css create mode 100644 src/test/resources/compile-basic-features/media/debug.css create mode 100644 src/test/resources/compile-basic-features/media/debug.less rename src/test/resources/compile-basic-features/media/{media-bubbling.css => media-bubbling-lessjs-issues.css} (56%) rename src/test/resources/compile-basic-features/media/{media-bubbling.less => media-bubbling-lessjs-issues.less} (64%) create mode 100644 src/test/resources/compile-basic-features/media/media-merging-top-level.css create mode 100644 src/test/resources/compile-basic-features/media/media-merging-top-level.less create mode 100644 src/test/resources/compile-basic-features/media/media-merging-with-bubbling.css create mode 100644 src/test/resources/compile-basic-features/media/media-merging-with-bubbling.less delete mode 100644 src/test/resources/compile-basic-features/media/media-problems-and-todo.less create mode 100644 src/test/resources/compile-basic-features/media/media-with-font-face.css create mode 100644 src/test/resources/compile-basic-features/media/media-with-font-face.less create mode 100644 src/test/resources/compile-basic-features/media/todo/media-query-as-variable.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 40bba7be..7ed2a140 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 @@ -170,7 +170,7 @@ tokens { styleSheet @init {enterRule(retval, RULE_STYLESHEET);} : ( a+=charSet* //the original ? was replaced by *, because it is possible (even if it makes no sense) and less.js is able to handle such situation - a+=bodylist + (a+=top_level_element)* EOF ) -> ^(STYLE_SHEET ($a)*) ; finally { leaveRule(); } @@ -200,14 +200,22 @@ finally { leaveRule(); } // Media can also have a ruleset in them. // //TODO media part of the grammar is throwing away tokens, so comments will not work correctly. fix that -media +media_queries_declaration: + m1+=mediaQuery (n+=COMMA m+=mediaQuery)* + -> ^(MEDIUM_DECLARATION $m1 ($n $m)*) +; + +media_top_level +@init {enterRule(retval, RULE_MEDIA);} + : MEDIA_SYM m1+=media_queries_declaration b+=top_level_body_with_declaration + -> ^(MEDIA_SYM $m1* $b*) + ; +finally { leaveRule(); } + +media_in_general_body @init {enterRule(retval, RULE_MEDIA);} - : MEDIA_SYM (m1+=mediaQuery (n+=COMMA m+=mediaQuery)*) - q1=LBRACE - ((declaration) => b+=declaration SEMI - | b+=bodyset )* - q2=RBRACE - -> ^(MEDIA_SYM ^(MEDIUM_DECLARATION $m1 ($n $m)*) $q1 $b* $q2) + : MEDIA_SYM m1+=media_queries_declaration b+=general_body + -> ^(MEDIA_SYM $m1* $b*) ; finally { leaveRule(); } @@ -245,17 +253,13 @@ mediaFeature : IDENT ; -bodylist - : bodyset* - ; - -bodyset +top_level_element : (mixinReferenceWithSemi)=>mixinReferenceWithSemi | (namespaceReferenceWithSemi)=>namespaceReferenceWithSemi | (reusableStructureName LPAREN)=>reusableStructure | (variabledeclaration)=>variabledeclaration | ruleSet - | media + | media_top_level | viewport | keyframes | page @@ -289,10 +293,7 @@ finally { leaveRule(); } fontface @init {enterRule(retval, RULE_FONT_FACE);} - : FONT_FACE_SYM^ - LBRACE! - declaration SEMI! (declaration SEMI!)* - RBRACE! + : FONT_FACE_SYM^ general_body ; finally { leaveRule(); } @@ -373,6 +374,22 @@ nestedAppender // this must be here because of special case & & <- the space bel ; //css does not require ; in last declaration + +top_level_body + : LBRACE + (a+=top_level_element)* + RBRACE + //If we remove LBRACE from the tree, a ruleset with an empty selector will report wrong line number in the warning. + -> ^(BODY LBRACE $a*); + +top_level_body_with_declaration + : LBRACE + ((declarationWithSemicolon)=> a+=declarationWithSemicolon + | a+=top_level_element)* + RBRACE + //If we remove LBRACE from the tree, a ruleset with an empty selector will report wrong line number in the warning. + -> ^(BODY LBRACE $a*); + general_body : LBRACE ( ((declarationWithSemicolon)=> (a+=declarationWithSemicolon) ) @@ -382,6 +399,7 @@ general_body | (reusableStructure)=>a+=reusableStructure | a+=pageMarginBox | a+=variabledeclaration + | a+=media_in_general_body //| a+=imports )* ( diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java index dc310645..a7741554 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ASTCssNodeType.java @@ -1,5 +1,5 @@ package com.github.sommeri.less4j.core.ast; public enum ASTCssNodeType { - UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, DECLARATIONS_BODY, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION + UNKNOWN, CSS_CLASS, DECLARATION, STYLE_SHEET, RULE_SET, SELECTOR, SIMPLE_SELECTOR, PSEUDO_CLASS, PSEUDO_ELEMENT, SELECTOR_ATTRIBUTE, ID_SELECTOR, CHARSET_DECLARATION, FONT_FACE, IDENTIFIER_EXPRESSION, COMPOSED_EXPRESSION, STRING_EXPRESSION, NUMBER, COLOR_EXPRESSION, FUNCTION, MEDIA, COMMENT, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, MEDIA_EXPRESSION, MEDIUM, MEDIUM_MODIFIER, MEDIUM_TYPE, MEDIUM_EX_FEATURE, VARIABLE_DECLARATION, VARIABLE, INDIRECT_VARIABLE, PARENTHESES_EXPRESSION, SIGNED_EXPRESSION, ARGUMENT_DECLARATION, MIXIN_REFERENCE, GUARD_CONDITION, COMPARISON_EXPRESSION, GUARD, NESTED_SELECTOR_APPENDER, REUSABLE_STRUCTURE, FAULTY_EXPRESSION, ESCAPED_SELECTOR, ESCAPED_VALUE, INTERPOLABLE_NAME, FIXED_NAME_PART, VARIABLE_NAME_PART, KEYFRAMES, KEYFRAMES_NAME, KEYFRAMES_BODY, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MA, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Body.java b/src/main/java/com/github/sommeri/less4j/core/ast/Body.java index ea2f581e..b2630c1e 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Body.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Body.java @@ -9,29 +9,29 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public abstract class Body extends ASTCssNode { +public abstract class Body extends ASTCssNode { - private List body = new ArrayList(); + private List body = new ArrayList(); public Body(HiddenTokenAwareTree underlyingStructure) { super(underlyingStructure); } - public Body(HiddenTokenAwareTree underlyingStructure, List declarations) { + public Body(HiddenTokenAwareTree underlyingStructure, List declarations) { this(underlyingStructure); body.addAll(declarations); } @Override - public List getChilds() { + public List getChilds() { return body; } - public List getMembers() { + public List getMembers() { return body; } - protected List getBody() { + protected List getBody() { return body; } @@ -39,44 +39,57 @@ public boolean isEmpty() { return body.isEmpty() && getOrphanComments().isEmpty(); } - public void addMembers(List members) { + public void removeAllMembers() { + body = new ArrayList(); + } + + public void addMembers(List members) { body.addAll(members); } - public void addMembersAfter(List nestedRulesets, ASTCssNode kid) { + public void addMembersAfter(List newMembers, ASTCssNode kid) { int index = body.indexOf(kid); if (index==-1) index = body.size(); else index++; - body.addAll(index, nestedRulesets); + body.addAll(index, newMembers); + } + public void addMemberAfter(ASTCssNode newMember, ASTCssNode kid) { + int index = body.indexOf(kid); + if (index==-1) + index = body.size(); + else + index++; - } + body.add(index, newMember); - public void addMember(T member) { + + } + public void addMember(ASTCssNode member) { body.add(member); } - public void replaceMember(T oldMember, List newMembers) { + public void replaceMember(ASTCssNode oldMember, List newMembers) { body.addAll(body.indexOf(oldMember), newMembers); body.remove(oldMember); oldMember.setParent(null); configureParentToAllChilds(); } - public void replaceMember(T oldMember, T newMember) { + public void replaceMember(ASTCssNode oldMember, ASTCssNode newMember) { body.add(body.indexOf(oldMember), newMember); body.remove(oldMember); oldMember.setParent(null); newMember.setParent(this); } - public List membersByType(ASTCssNodeType type) { - List result = new ArrayList(); - List body = getBody(); - for (T node : body) { + public List membersByType(ASTCssNodeType type) { + List result = new ArrayList(); + List body = getBody(); + for (ASTCssNode node : body) { if (node.getType()==type) { result.add(node); } @@ -84,10 +97,10 @@ public List membersByType(ASTCssNodeType type) { return result; } - public List membersByNotType(ASTCssNodeType type) { - List result = new ArrayList(); - List body = getBody(); - for (T node : body) { + public List membersByNotType(ASTCssNodeType type) { + List result = new ArrayList(); + List body = getBody(); + for (ASTCssNode node : body) { if (node.getType()!=type) { result.add(node); } @@ -95,7 +108,7 @@ public List membersByNotType(ASTCssNodeType type) { return result; } - public boolean removeMember(T node) { + public boolean removeMember(ASTCssNode node) { return body.remove(node); } @@ -103,20 +116,34 @@ public Set getSupportedMembers() { return new HashSet(Arrays.asList(ASTCssNodeType.values())); } - @SuppressWarnings("unchecked") - public Body clone() { - Body result = (Body) super.clone(); + public Body clone() { + Body result = (Body) super.clone(); result.body = ArraysUtils.deeplyClonedList(body); result.configureParentToAllChilds(); return result; } - public List getDeclarations() { + public Body emptyClone() { + Body result = (Body) super.clone(); + result.body = new ArrayList(); + return result; + } + + public List getDeclarations() { return membersByType(ASTCssNodeType.DECLARATION); } - public List getNotDeclarations() { + public List getNotDeclarations() { return membersByNotType(ASTCssNodeType.DECLARATION); } + + @Override + public void setParent(ASTCssNode parent) { + if (parent!=null && !(parent instanceof BodyOwner)) + throw new IllegalArgumentException("Body parent must be a BodyOwner: " + parent.getType()); + + super.setParent(parent); + } + } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/BodyOwner.java b/src/main/java/com/github/sommeri/less4j/core/ast/BodyOwner.java new file mode 100644 index 00000000..86547031 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/BodyOwner.java @@ -0,0 +1,8 @@ +package com.github.sommeri.less4j.core.ast; + +public interface BodyOwner { + + void setBody(T body); + T getBody(); + +} diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/FontFace.java b/src/main/java/com/github/sommeri/less4j/core/ast/FontFace.java index 7bd037cc..b0c59f7f 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/FontFace.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/FontFace.java @@ -3,15 +3,27 @@ import java.util.List; import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; +import com.github.sommeri.less4j.utils.ArraysUtils; -public class FontFace extends Body { +public class FontFace extends ASTCssNode implements BodyOwner { + + private GeneralBody body; public FontFace(HiddenTokenAwareTree underlyingStructure) { super(underlyingStructure); } - public FontFace(HiddenTokenAwareTree underlyingStructure, List declarations) { - super(underlyingStructure, declarations); + public GeneralBody getBody() { + return body; + } + + public void setBody(GeneralBody body) { + this.body = body; + } + + @Override + public List getChilds() { + return ArraysUtils.asNonNullList((ASTCssNode)body); } @Override diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/GeneralBody.java b/src/main/java/com/github/sommeri/less4j/core/ast/GeneralBody.java index f4e36793..96772c43 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/GeneralBody.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/GeneralBody.java @@ -5,7 +5,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; //FIXME: !validation! warning on viewport body with nested rulesets in output -public class GeneralBody extends Body { +public class GeneralBody extends Body { public GeneralBody(HiddenTokenAwareTree underlyingStructure) { super(underlyingStructure); diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Keyframes.java b/src/main/java/com/github/sommeri/less4j/core/ast/Keyframes.java index 2236b763..a1eb896c 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Keyframes.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Keyframes.java @@ -6,7 +6,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class Keyframes extends ASTCssNode { +public class Keyframes extends ASTCssNode implements BodyOwner { private String dialect; private List names = new ArrayList(); diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/KeyframesBody.java b/src/main/java/com/github/sommeri/less4j/core/ast/KeyframesBody.java index 2448cae4..6d0c620d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/KeyframesBody.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/KeyframesBody.java @@ -7,7 +7,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; -public class KeyframesBody extends Body { +public class KeyframesBody extends Body { private static final Set SUPPORTED_MEMBERS = new HashSet(); diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Media.java b/src/main/java/com/github/sommeri/less4j/core/ast/Media.java index a5a76803..fad9c814 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Media.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Media.java @@ -6,32 +6,15 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class Media extends Body { +public class Media extends ASTCssNode implements BodyOwner { private List mediums; + private GeneralBody body; public Media(HiddenTokenAwareTree token) { super(token); } - public void addDeclaration(Declaration declaration) { - addMember(declaration); - } - - public void addRuleSet(RuleSet ruleSet) { - addMember(ruleSet); - } - - public void addChild(ASTCssNode child) { - if (!(child.getType() == ASTCssNodeType.RULE_SET || child.getType() == ASTCssNodeType.VARIABLE_DECLARATION ||child.getType() == ASTCssNodeType.DECLARATION || child.getType() == ASTCssNodeType.MEDIUM || child.getType() == ASTCssNodeType.MEDIA_QUERY|| child.getType() == ASTCssNodeType.VIEWPORT|| child.getType() == ASTCssNodeType.PAGE)) - throw new IllegalArgumentException("Unexpected media child type: " + child.getType()); - - if (child.getType() == ASTCssNodeType.MEDIA_QUERY) - addMediaQuery((MediaQuery) child); - else - addMember(child); - } - public void addMediaQuery(MediaQuery medium) { if (mediums==null) { mediums = new ArrayList(); @@ -47,10 +30,19 @@ public void setMediums(List mediums) { this.mediums = mediums; } + public void replaceMediaQueries(List result) { + for (MediaQuery oldMediums : mediums) { + oldMediums.setParent(null); + } + mediums = new ArrayList(); + mediums.addAll(result); + } + @Override public List getChilds() { - List childs = new ArrayList(super.getChilds()); + List childs = new ArrayList(); childs.addAll(mediums); + childs.add(body); return childs; } @@ -63,7 +55,19 @@ public ASTCssNodeType getType() { public Media clone() { Media result = (Media) super.clone(); result.mediums = ArraysUtils.deeplyClonedList(mediums); + result.body = body==null?null:body.clone(); result.configureParentToAllChilds(); return result; } + + @Override + public void setBody(GeneralBody body) { + this.body = body; + } + + @Override + public GeneralBody getBody() { + return body; + } + } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/MediaQuery.java b/src/main/java/com/github/sommeri/less4j/core/ast/MediaQuery.java index 96679cbe..ed680dcf 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/MediaQuery.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/MediaQuery.java @@ -44,6 +44,13 @@ public void addExpression(MediaExpression expression) { this.expressions.add(expression); } + public void addExpressions(List expressions) { + if (expressions == null) + expressions = new ArrayList(); + + this.expressions.addAll(expressions); + } + /** * May throw class cast exception if the member in parameter is * does not have the right type. @@ -73,6 +80,8 @@ public MediaQuery clone() { MediaQuery result = (MediaQuery) super.clone(); result.medium = medium==null?null:medium.clone(); result.expressions = ArraysUtils.deeplyClonedList(expressions); + result.configureParentToAllChilds(); return result; } + } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Page.java b/src/main/java/com/github/sommeri/less4j/core/ast/Page.java index 8595bf99..1eaabc9f 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Page.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Page.java @@ -5,7 +5,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class Page extends ASTCssNode { +public class Page extends ASTCssNode implements BodyOwner{ private Name name; private boolean dockedPseudopage = true; diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/PageMarginBox.java b/src/main/java/com/github/sommeri/less4j/core/ast/PageMarginBox.java index aed5314d..ca4d746d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/PageMarginBox.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/PageMarginBox.java @@ -5,7 +5,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class PageMarginBox extends ASTCssNode { +public class PageMarginBox extends ASTCssNode implements BodyOwner { private Name name; private GeneralBody body; diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/ReusableStructure.java b/src/main/java/com/github/sommeri/less4j/core/ast/ReusableStructure.java index 49f08de1..dfdbd327 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/ReusableStructure.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/ReusableStructure.java @@ -6,13 +6,13 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class ReusableStructure extends ASTCssNode { +public class ReusableStructure extends ASTCssNode implements BodyOwner { private List names = new ArrayList(); //Allows: variable, argument declaration, pattern private List parameters = new ArrayList(); private List guards = new ArrayList(); - private RuleSetsBody body; + private GeneralBody body; public ReusableStructure(HiddenTokenAwareTree token) { super(token); @@ -43,7 +43,7 @@ public void setNames(List names) { this.names = names; } - public RuleSetsBody getBody() { + public GeneralBody getBody() { return body; } @@ -51,7 +51,7 @@ public boolean hasEmptyBody() { return body==null? true : body.isEmpty(); } - public void setBody(RuleSetsBody body) { + public void setBody(GeneralBody body) { this.body = body; } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/RuleSet.java b/src/main/java/com/github/sommeri/less4j/core/ast/RuleSet.java index 81a80458..435fb46b 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/RuleSet.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/RuleSet.java @@ -7,16 +7,16 @@ import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.utils.ArraysUtils; -public class RuleSet extends ASTCssNode { +public class RuleSet extends ASTCssNode implements BodyOwner { private List selectors = new ArrayList(); - private RuleSetsBody body; + private GeneralBody body; public RuleSet(HiddenTokenAwareTree token) { super(token); } - public RuleSet(HiddenTokenAwareTree token, RuleSetsBody body, List selectors) { + public RuleSet(HiddenTokenAwareTree token, GeneralBody body, List selectors) { super(token); this.body = body; addSelectors(selectors); @@ -27,7 +27,7 @@ public List getSelectors() { return selectors; } - public RuleSetsBody getBody() { + public GeneralBody getBody() { return body; } @@ -35,7 +35,7 @@ public boolean hasEmptyBody() { return body==null? true : body.isEmpty(); } - public void setBody(RuleSetsBody body) { + public void setBody(GeneralBody body) { this.body = body; } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/RuleSetsBody.java b/src/main/java/com/github/sommeri/less4j/core/ast/RuleSetsBody.java deleted file mode 100644 index c78c7040..00000000 --- a/src/main/java/com/github/sommeri/less4j/core/ast/RuleSetsBody.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.github.sommeri.less4j.core.ast; - -import java.util.List; - -import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; - -public class RuleSetsBody extends Body { - - public RuleSetsBody(HiddenTokenAwareTree underlyingStructure) { - super(underlyingStructure); - } - - public RuleSetsBody(HiddenTokenAwareTree underlyingStructure, List declarations) { - super(underlyingStructure, declarations); - } - - @Override - public ASTCssNodeType getType() { - return ASTCssNodeType.DECLARATIONS_BODY; - } - - public RuleSetsBody clone() { - return (RuleSetsBody) super.clone(); - } -} diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/StyleSheet.java b/src/main/java/com/github/sommeri/less4j/core/ast/StyleSheet.java index dc6cefaa..57751bc3 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/StyleSheet.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/StyleSheet.java @@ -6,7 +6,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; -public class StyleSheet extends Body { +public class StyleSheet extends Body { public StyleSheet(HiddenTokenAwareTree underlyingStructure) { super(underlyingStructure); diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Viewport.java b/src/main/java/com/github/sommeri/less4j/core/ast/Viewport.java index 5a3bc202..331efd34 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Viewport.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Viewport.java @@ -5,7 +5,7 @@ import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.utils.ArraysUtils; -public class Viewport extends ASTCssNode { +public class Viewport extends ASTCssNode implements BodyOwner { //I have to do this because of a comment in following less: `@viewport /*comment */ { ... }` private GeneralBody body; 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 b66349c7..dc8ec84e 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 @@ -31,6 +31,7 @@ public class LessToCssCompiler { private ProblemsHandler problemsHandler; + ASTManipulator astManipulator = new ASTManipulator(); public LessToCssCompiler(ProblemsHandler problemsHandler) { super(); @@ -43,7 +44,7 @@ public ASTCssNode compileToCss(StyleSheet less, LessSource source) { ScopeExtractor scopeBuilder = new ScopeExtractor(); Scope scope = scopeBuilder.extractScope(less); - + ReferencesSolver referencesSolver = new ReferencesSolver(problemsHandler); referencesSolver.solveReferences(less, scope); @@ -57,7 +58,7 @@ public ASTCssNode compileToCss(StyleSheet less, LessSource source) { } private void bubbleAndMergeMedia(StyleSheet less) { - MediaBubblerAndMerger bubblerAndMerger = new MediaBubblerAndMerger(); + MediaBubblerAndMerger bubblerAndMerger = new MediaBubblerAndMerger(problemsHandler); bubblerAndMerger.bubbleAndMergeMedia(less); } @@ -80,7 +81,7 @@ private int code(ASTCssNode node) { }); } - private void freeNestedRuleSets(Body body) { + private void freeNestedRuleSets(Body body) { NestedRulesCollector nestedRulesCollector = new NestedRulesCollector(); List childs = new ArrayList(body.getChilds()); @@ -88,14 +89,11 @@ private void freeNestedRuleSets(Body body) { switch (kid.getType()) { case RULE_SET: { List nestedRulesets = nestedRulesCollector.collectNestedRuleSets((RuleSet) kid); - body.addMembersAfter(nestedRulesets, kid); - for (RuleSet ruleSet : nestedRulesets) { - ruleSet.setParent(body); - } + astManipulator.addIntoBody(nestedRulesets, kid); break; } case MEDIA: { - freeNestedRuleSets((Media) kid); + freeNestedRuleSets(((Media) kid).getBody()); break; } case PAGE: { 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 a5497d4f..97cf4057 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 @@ -33,7 +33,6 @@ public void replace(ASTCssNode oldChild, ASTCssNode newChild) { } } - @SuppressWarnings({ "rawtypes", "unchecked" }) public void removeFromBody(ASTCssNode node) { ASTCssNode parent = node.getParent(); if (!(parent instanceof Body)) { @@ -45,7 +44,6 @@ public void removeFromBody(ASTCssNode node) { node.setParent(null); } - @SuppressWarnings({ "rawtypes", "unchecked" }) public void replaceInBody(ASTCssNode oldNode, ASTCssNode newNode) { ASTCssNode parent = oldNode.getParent(); if (!(parent instanceof Body)) { @@ -56,7 +54,6 @@ public void replaceInBody(ASTCssNode oldNode, ASTCssNode newNode) { pBody.replaceMember(oldNode, newNode); } - @SuppressWarnings({ "rawtypes", "unchecked" }) public void replaceInBody(ASTCssNode oldNode, List newNodes) { ASTCssNode parent = oldNode.getParent(); if (!(parent instanceof Body)) { @@ -67,6 +64,30 @@ public void replaceInBody(ASTCssNode oldNode, List newNodes) { pBody.replaceMember(oldNode, newNodes); } + public void addIntoBody(ASTCssNode newNode, ASTCssNode afterNode) { + ASTCssNode parent = afterNode.getParent(); + if (!(parent instanceof Body)) { + throw new BugHappened("Parent is not a body instance. " + parent, parent); + } + + Body pBody = (Body) parent; + pBody.addMemberAfter(newNode, afterNode); + newNode.setParent(parent); + } + + public void addIntoBody(List newNodes, ASTCssNode afterNode) { + ASTCssNode parent = afterNode.getParent(); + if (!(parent instanceof Body)) { + throw new BugHappened("Parent is not a body instance. " + parent, parent); + } + + Body pBody = (Body) parent; + pBody.addMembersAfter(newNodes, afterNode); + for (ASTCssNode newNode : newNodes) { + newNode.setParent(pBody); + } + } + private void setPropertyValue(ASTCssNode parent, ASTCssNode value, String name) { try { PropertyUtils.setProperty(parent, name, value); @@ -92,7 +113,7 @@ private void setPropertyValue(ASTCssNode parent, ASTCssNode value, PropertyDescr } - public Object getPropertyValue(ASTCssNode object, PropertyDescriptor descriptor) { + private Object getPropertyValue(ASTCssNode object, PropertyDescriptor descriptor) { try { Object result = PropertyUtils.getProperty(object, descriptor.getName()); return result; @@ -115,4 +136,10 @@ public void removeFromClosestBody(ASTCssNode node) { removeFromBody(removeNode); } + public void moveChildsBetweenBodies(Body from, Body to) { + to.addMembers(from.getChilds()); + to.configureParentToAllChilds(); + from.removeAllMembers(); + } + } 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 0a873e99..e892eb32 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 @@ -21,7 +21,6 @@ public static void validateCssBodyCompatibility(ASTCssNode reference, List supportedMembers = ((Body) parent).getSupportedMembers(); for (ASTCssNode member : members) { if (!supportedMembers.contains(member.getType())) { diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MediaBubblerAndMerger.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MediaBubblerAndMerger.java index a4ce6a14..d8eaefc0 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MediaBubblerAndMerger.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/MediaBubblerAndMerger.java @@ -1,54 +1,220 @@ package com.github.sommeri.less4j.core.compiler.stages; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import com.github.sommeri.less4j.core.ast.ASTCssNode; import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.Body; +import com.github.sommeri.less4j.core.ast.BodyOwner; +import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.ast.Media; import com.github.sommeri.less4j.core.ast.StyleSheet; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; public class MediaBubblerAndMerger { - - public void bubbleAndMergeMedia(ASTCssNode node) { + + private ASTManipulator astManipulator = new ASTManipulator(); + private ProblemsHandler problemsHandler; + + public MediaBubblerAndMerger(ProblemsHandler problemsHandler) { + super(); + this.problemsHandler = problemsHandler; + } + + public void bubbleAndMergeMedia(StyleSheet node) { bubbleUp(node); + mergeTopLevelMedias(node); + } + + private void mergeTopLevelMedias(StyleSheet node) { + NestedMediaCollector nestedMediaCollector = new NestedMediaCollector(problemsHandler); + + List childs = new ArrayList(node.getChilds()); + for (ASTCssNode kid : childs) { + switch (kid.getType()) { + case MEDIA: { + List nestedMedia = nestedMediaCollector.collectMedia((Media) kid); + astManipulator.addIntoBody(nestedMedia, kid); + break; + } + } + } + } private void bubbleUp(ASTCssNode node) { -// switch (node.getType()) { -// case MEDIA: { -// bubbleUp((Media) node); -// break; -// } -// } -// -// List childs = new ArrayList(less.getChilds()); -// for (ASTCssNode kid : childs) { -// bubbleUp(kid); -// } - } - - private void bubbleUp(Media node) { -// ASTCssNode parent = node.getParent(); -// while (parent!=null) { -// switch (parent.getType()) { -// case STYLE_SHEET: { -// return ; -// } -// case MEDIA: { -// return ; -// } -// } -// // TODO Auto-generated method stub -// blah blah -// -// } -// - } - - private void merge(Media node, Media parent) { - // TODO Auto-generated method stub - + switch (node.getType()) { + case MEDIA: { + bubbleUp((Media) node); + break; + } + default: + } + + List childs = new ArrayList(node.getChilds()); + for (ASTCssNode kid : childs) { + bubbleUp(kid); + } + } + + private void bubbleUp(Media media) { + ParentChainIterator parentChainIterator = new ParentChainIterator(media); + if (parentChainIterator.finished()) + return; + + astManipulator.removeFromBody(media); + BodiesStorage bodiesStorage = new BodiesStorage(); + + //move all kids of media into the empty clone. It is wasteful, they are going to be cloned but does not need to. + Body oldBody = parentChainIterator.getParentAsBody(); + ASTCssNode currentNode = parentChainIterator.getCurrentNode(); + parentChainIterator.moveUpToNextBody(); + + Body emptyClone = bodiesStorage.storeAndReplaceBySingleMemberClone(oldBody, null); + astManipulator.moveChildsBetweenBodies(media.getBody(), emptyClone); + + while (!parentChainIterator.finished()) { + //store current node and + oldBody = parentChainIterator.getParentAsBody(); + currentNode = parentChainIterator.getCurrentNode(); + //move up + parentChainIterator.moveUpToNextBody(); + + bodiesStorage.storeAndReplaceBySingleMemberClone(oldBody, currentNode); + } + + //clone whole parental chain + currentNode = parentChainIterator.getCurrentNode(); + ASTCssNode currentNodeClone = currentNode.clone(); + + //make it media child + media.getBody().addMember(currentNodeClone); + currentNodeClone.setParent(media.getBody()); + + //restore bodies and add media + bodiesStorage.restore(); + astManipulator.addIntoBody(media, currentNode); + } + +} + +class BodiesStorage { + private List originalBodies = new ArrayList(); + private List keepChilds = new ArrayList(); + private List> originalBodiesParents = new ArrayList>(); + + private void store(Body body, BodyOwner parent) { + originalBodies.add(body); + //TODO: this should be done in a clearer way + originalBodiesParents.add(parent); + } + + private void replaceBody(BodyOwner bodyOwner, Body body) { + bodyOwner.getBody().setParent(null); + bodyOwner.setBody(body); + body.setParent((ASTCssNode) bodyOwner); + } + + public Body storeAndReplaceBySingleMemberClone(Body body, ASTCssNode keepChild) { + Body newBody = body.emptyClone(); + @SuppressWarnings("unchecked") + BodyOwner bodyOwner = (BodyOwner) body.getParent(); + + store(body, bodyOwner); + replaceBody(bodyOwner, newBody); + + //add keep child node into faked body + keepChilds.add(keepChild); + moveToBody(newBody, keepChild); + + return newBody; + } + + private void moveToBody(Body body, ASTCssNode child) { + if (child != null) { + // we only reparented the child, we did not removed it from the previous parent + if (!body.getChilds().contains(child)) + body.addMember(child); + child.setParent(body); + } + } + + public void restore() { + Iterator bodiesIterator = originalBodies.iterator(); + Iterator> parentsIterator = originalBodiesParents.iterator(); + Iterator keepChildsIterator = keepChilds.iterator(); + + while (bodiesIterator.hasNext()) { + BodyOwner bodyOwner = parentsIterator.next(); + Body body = bodiesIterator.next(); + ASTCssNode keepChild = keepChildsIterator.next(); + + bodyOwner.getBody().setParent(null); + replaceBody(bodyOwner, body); + moveToBody(body, keepChild); + } + } } + +class ParentChainIterator { + + private ASTCssNode currentNode; + private ASTCssNode currentNodeParent; + + public ParentChainIterator(Media media) { + currentNode = media; + currentNodeParent = currentNode.getParent(); + } + + public ASTCssNode getCurrentNode() { + return currentNode; + } + + public Body getParentAsBody() { + return (Body) currentNodeParent; + } + + public void moveUpToNextBody() { + moveOnParent(); + + while (!finished() && !onNextBody()) { + moveOnParent(); + } + } + + private boolean onNextBody() { + return currentNodeParent instanceof Body; + } + + private void moveOnParent() { + currentNode = currentNodeParent; + currentNodeParent = currentNode.getParent(); + } + + public boolean finished() { + return isStopParent(currentNodeParent); + } + + private boolean isStopParent(ASTCssNode parent) { + if (parent == null) + return true; + + switch (parent.getType()) { + case STYLE_SHEET: { + return true; + } + case GENERAL_BODY: { + GeneralBody body = (GeneralBody) parent; + ASTCssNode bodyParent = body.getParent(); + return bodyParent == null || bodyParent.getType() == ASTCssNodeType.MEDIA; + } + } + + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/NestedMediaCollector.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/NestedMediaCollector.java new file mode 100644 index 00000000..0665a99e --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/NestedMediaCollector.java @@ -0,0 +1,90 @@ +package com.github.sommeri.less4j.core.compiler.stages; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; + +import com.github.sommeri.less4j.core.ast.ASTCssNode; +import com.github.sommeri.less4j.core.ast.ASTCssNodeType; +import com.github.sommeri.less4j.core.ast.Media; +import com.github.sommeri.less4j.core.ast.MediaQuery; +import com.github.sommeri.less4j.core.problems.ProblemsHandler; +import com.github.sommeri.less4j.utils.ArraysUtils; + +public class NestedMediaCollector { + + private final ASTManipulator manipulator = new ASTManipulator(); + private Stack> mediums; + private LinkedList finalMedia; + private ProblemsHandler problemsHandler; + + public NestedMediaCollector(ProblemsHandler problemsHandler) { + this.problemsHandler = problemsHandler; + } + + public List collectMedia(Media kid) { + mediums = new Stack>(); + finalMedia = new LinkedList(); + + pushMediums(kid); + collectChildMedia(kid); + popMediums(); + + return finalMedia; + } + + private void collectChildMedia(ASTCssNode node) { + List childs = new ArrayList(node.getChilds()); + for (ASTCssNode kid : childs) { + if (kid.getType() == ASTCssNodeType.MEDIA) { + Media nestedMedia = (Media) kid; + manipulator.removeFromBody(nestedMedia); + collect(nestedMedia); + pushMediums(nestedMedia); + } + //TODO: possible optimization: there is no reason to go inside rulesets and other childs types + collectChildMedia(kid); + if (kid.getType() == ASTCssNodeType.MEDIA) { + popMediums(); + } + } + } + + private void collect(Media media) { + combine(media, mediums.peek()); + finalMedia.add(media); + } + + public void combine(Media media, List previousMediaQueries) { + List result = new ArrayList(); + for (MediaQuery mediaQuery : media.getMediums()) { + for (MediaQuery previousMediaQuery : previousMediaQueries) { + result.add(combine(previousMediaQuery, mediaQuery)); + } + } + + media.replaceMediaQueries(result); + media.configureParentToAllChilds(); + } + + private MediaQuery combine(MediaQuery previousMediaQuery, MediaQuery mediaQuery) { + MediaQuery previousMediaQueryClone = previousMediaQuery.clone(); + if (mediaQuery.getMedium()!=null) { + problemsHandler.warnMerginMediaQueryWithMedium(mediaQuery); + } + + previousMediaQueryClone.addExpressions(ArraysUtils.deeplyClonedList(mediaQuery.getExpressions())); + previousMediaQueryClone.configureParentToAllChilds(); + return previousMediaQueryClone; + } + + private void pushMediums(Media kid) { + mediums.push(new ArrayList(kid.getMediums())); + } + + private void popMediums() { + mediums.pop(); + } + +} 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 1584a828..b3cf8659 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 @@ -13,11 +13,11 @@ import com.github.sommeri.less4j.core.ast.EscapedValue; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FixedNamePart; +import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.ast.IndirectVariable; import com.github.sommeri.less4j.core.ast.InterpolableName; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.ReusableStructure; -import com.github.sommeri.less4j.core.ast.RuleSetsBody; import com.github.sommeri.less4j.core.ast.SimpleSelector; import com.github.sommeri.less4j.core.ast.Variable; import com.github.sommeri.less4j.core.ast.VariableNamePart; @@ -52,7 +52,6 @@ private void doSolveReferences(ASTCssNode node, Scope scope) { private void doSolveReferences(ASTCssNode node, IteratedScope scope) { ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(scope.getScope(), problemsHandler); - switch (node.getType()) { case VARIABLE: { Expression replacement = expressionEvaluator.evaluate((Variable) node); @@ -76,7 +75,7 @@ private void doSolveReferences(ASTCssNode node, IteratedScope scope) { } case MIXIN_REFERENCE: { MixinReference mixinReference = (MixinReference) node; - RuleSetsBody replacement = resolveMixinReference(mixinReference, scope.getScope()); + GeneralBody replacement = resolveMixinReference(mixinReference, scope.getScope()); AstLogic.validateCssBodyCompatibility(mixinReference, replacement.getChilds(), problemsHandler); manipulator.replaceInBody(mixinReference, replacement.getChilds()); break; @@ -111,6 +110,7 @@ private void doSolveReferences(ASTCssNode node, IteratedScope scope) { } } } + } private FixedNamePart toFixedName(Expression value, HiddenTokenAwareTree parent) { @@ -133,12 +133,12 @@ private FixedNamePart interpolateFixedNamePart(FixedNamePart input, ExpressionEv return new FixedNamePart(input.getUnderlyingStructure(), value); } - private RuleSetsBody resolveMixinReference(MixinReference reference, Scope scope) { + private GeneralBody resolveMixinReference(MixinReference reference, Scope scope) { List sameNameMixins = scope.getNearestMixins(reference, problemsHandler); return resolveReferencedMixins(reference, scope, sameNameMixins); } - private RuleSetsBody resolveReferencedMixins(MixinReference reference, Scope referenceScope, List sameNameMixins) { + private GeneralBody resolveReferencedMixins(MixinReference reference, Scope referenceScope, List sameNameMixins) { if (sameNameMixins.isEmpty()) problemsHandler.undefinedMixin(reference); @@ -146,14 +146,14 @@ private RuleSetsBody resolveReferencedMixins(MixinReference reference, Scope ref if (mixins.isEmpty()) problemsHandler.unmatchedMixin(reference); - RuleSetsBody result = new RuleSetsBody(reference.getUnderlyingStructure()); + GeneralBody result = new GeneralBody(reference.getUnderlyingStructure()); for (FullMixinDefinition fullMixin : mixins) { Scope combinedScope = calculateMixinsOwnVariables(reference, referenceScope, fullMixin); ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(combinedScope, problemsHandler); ReusableStructure mixin = fullMixin.getMixin(); if (expressionEvaluator.evaluate(mixin.getGuards())) { - RuleSetsBody body = mixin.getBody().clone(); + GeneralBody body = mixin.getBody().clone(); doSolveReferences(body, combinedScope); result.addMembers(body.getChilds()); } @@ -165,7 +165,7 @@ private RuleSetsBody resolveReferencedMixins(MixinReference reference, Scope ref return result; } - private void shiftComments(MixinReference reference, RuleSetsBody result) { + private void shiftComments(MixinReference reference, GeneralBody result) { List childs = result.getChilds(); if (!childs.isEmpty()) { childs.get(0).addOpeningComments(reference.getOpeningComments()); @@ -173,13 +173,13 @@ private void shiftComments(MixinReference reference, RuleSetsBody result) { } } - private void resolveImportance(MixinReference reference, RuleSetsBody result) { + private void resolveImportance(MixinReference reference, GeneralBody result) { if (reference.isImportant()) { declarationsAreImportant(result); } } - private void declarationsAreImportant(RuleSetsBody result) { + private void declarationsAreImportant(GeneralBody result) { for (ASTCssNode kid : result.getChilds()) { if (kid instanceof Declaration) { Declaration declaration = (Declaration) kid; diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SimpleImportsSolver.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SimpleImportsSolver.java index 262eae29..b4fe38f0 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SimpleImportsSolver.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SimpleImportsSolver.java @@ -14,6 +14,7 @@ import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FaultyNode; import com.github.sommeri.less4j.core.ast.FunctionExpression; +import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.ast.Import; import com.github.sommeri.less4j.core.ast.Import.ImportKind; import com.github.sommeri.less4j.core.ast.Media; @@ -22,6 +23,7 @@ import com.github.sommeri.less4j.core.compiler.expressions.ExpressionEvaluator; import com.github.sommeri.less4j.core.parser.ANTLRParser; import com.github.sommeri.less4j.core.parser.ASTBuilder; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; import com.github.sommeri.less4j.core.problems.ProblemsHandler; import com.github.sommeri.less4j.platform.Constants; @@ -108,9 +110,10 @@ private void importEncountered(Import node, LessSource source) { // add media queries if needed if (node.hasMediums()) { - Media media = new Media(node.getUnderlyingStructure()); + HiddenTokenAwareTree underlyingStructure = node.getUnderlyingStructure(); + Media media = new Media(underlyingStructure); media.setMediums(node.getMediums()); - media.addMembers(importedAst.getMembers()); + media.setBody(new GeneralBody(underlyingStructure, importedAst.getMembers())); media.configureParentToAllChilds(); astManipulator.replaceInBody(node, media); } else { 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 5618ca04..5cb5a2da 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 @@ -22,6 +22,7 @@ import com.github.sommeri.less4j.core.ast.ExpressionOperator; import com.github.sommeri.less4j.core.ast.FixedNamePart; import com.github.sommeri.less4j.core.ast.FontFace; +import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.ast.Guard; import com.github.sommeri.less4j.core.ast.GuardCondition; import com.github.sommeri.less4j.core.ast.IdSelector; @@ -53,7 +54,6 @@ 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.RuleSetsBody; import com.github.sommeri.less4j.core.ast.Selector; import com.github.sommeri.less4j.core.ast.SelectorAttribute; import com.github.sommeri.less4j.core.ast.SelectorOperator; @@ -64,7 +64,6 @@ import com.github.sommeri.less4j.core.ast.VariableDeclaration; import com.github.sommeri.less4j.core.ast.VariableNamePart; import com.github.sommeri.less4j.core.ast.Viewport; -import com.github.sommeri.less4j.core.ast.GeneralBody; import com.github.sommeri.less4j.core.problems.BugHappened; import com.github.sommeri.less4j.core.problems.ProblemsHandler; @@ -210,13 +209,7 @@ public FontFace handleFontFace(HiddenTokenAwareTree token) { FontFace result = new FontFace(token); List children = token.getChildren(); - List declarations = new ArrayList(); - for (HiddenTokenAwareTree kid : children) { - if (kid.getType() == LessLexer.DECLARATION) - declarations.add(handleDeclaration(kid)); - } - - result.addMembers(declarations); + result.setBody(handleGeneralBody(children.get(0))); return result; } @@ -242,7 +235,7 @@ public RuleSet handleRuleSet(HiddenTokenAwareTree token) { selectors.add(selector); previousKid = selector; } else if (kid.getType() == LessLexer.BODY) { - RuleSetsBody body = handleRuleSetsBody(kid); + GeneralBody body = handleGeneralBody(kid); ruleSet.setBody(body); previousKid = body; } else if (kid.getType() == LessLexer.COMMA) { @@ -263,7 +256,7 @@ public ReusableStructure handleReusableStructureDeclaration(HiddenTokenAwareTree if (kid.getType() == LessLexer.REUSABLE_STRUCTURE_NAME) { result.addName(handleReusableStructureName(kid)); } else if (kid.getType() == LessLexer.BODY) { - result.setBody(handleRuleSetsBody(kid)); + result.setBody(handleGeneralBody(kid)); } else if (kid.getType() == LessLexer.GUARD) { result.addGuard(handleGuard(kid)); } else if (kid.getType() == LessLexer.DOT3) { @@ -403,9 +396,9 @@ public void validateGuardAnd(HiddenTokenAwareTree token) { throw new BugHappened(GRAMMAR_MISMATCH, token); } - public RuleSetsBody handleRuleSetsBody(HiddenTokenAwareTree token) { + public GeneralBody handleGeneralBody(HiddenTokenAwareTree token) { List members = handleBodyMembers(token); - return new RuleSetsBody(token, members); + return new GeneralBody(token, members); } public KeyframesBody handleKeyframesBody(HiddenTokenAwareTree token) { @@ -603,39 +596,26 @@ private String toFixedName(int typeCode, String text) { } public Media handleMedia(HiddenTokenAwareTree token) { - List originalChildren = token.getChildren(); - List children = new ArrayList(originalChildren); - HiddenTokenAwareTree lbrace = children.remove(1); - children.get(0).addFollowing(lbrace.getPreceding()); - children.get(1).addBeforePreceding(lbrace.getFollowing()); - - HiddenTokenAwareTree rbrace = children.remove(children.size() - 1); - children.get(children.size() - 1).addFollowing(rbrace.getPreceding()); - rbrace.getParent().addBeforeFollowing(rbrace.getFollowing()); + Iterator children = token.getChildren().iterator(); Media result = new Media(token); - for (HiddenTokenAwareTree kid : children) { - if (kid.getType() == LessLexer.MEDIUM_DECLARATION) { - kid.pushHiddenToKids(); - handleMediaDeclaration(result, kid); - } else { - result.addChild(switchOn(kid)); - } - - } + handleMediaDeclaration(result, children.next()); + result.setBody(handleGeneralBody(children.next())); + return result; } private void handleMediaDeclaration(Media result, HiddenTokenAwareTree declaration) { + declaration.pushHiddenToKids(); List children = declaration.getChildren(); - ASTCssNode previousKid = null; + MediaQuery previousKid = null; for (HiddenTokenAwareTree kid : children) { if (kid.getType() == LessLexer.COMMA) { previousKid.getUnderlyingStructure().addFollowing(kid.getPreceding()); } else { - previousKid = switchOn(kid); - result.addChild(previousKid); + previousKid = handleMediaQuery(kid); + result.addMediaQuery(previousKid); } } } diff --git a/src/main/java/com/github/sommeri/less4j/core/parser/TokenTypeSwitch.java b/src/main/java/com/github/sommeri/less4j/core/parser/TokenTypeSwitch.java index 92b9f699..4c5e401d 100644 --- a/src/main/java/com/github/sommeri/less4j/core/parser/TokenTypeSwitch.java +++ b/src/main/java/com/github/sommeri/less4j/core/parser/TokenTypeSwitch.java @@ -58,7 +58,7 @@ public T switchOn(HiddenTokenAwareTree token) { } if (type == LessLexer.BODY) { - return handleRuleSetsBody(token); + return handleGeneralBody(token); } if (type == LessLexer.EXPRESSION) { @@ -190,7 +190,7 @@ public T switchOn(HiddenTokenAwareTree token) { public abstract T handleNth(HiddenTokenAwareTree token); - public abstract T handleRuleSetsBody(HiddenTokenAwareTree token); + public abstract T handleGeneralBody(HiddenTokenAwareTree token); public abstract T handleMediaQuery(HiddenTokenAwareTree token); 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 7e6e1135..bc105876 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 @@ -10,6 +10,7 @@ import com.github.sommeri.less4j.core.ast.EscapedSelector; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.Import; +import com.github.sommeri.less4j.core.ast.MediaQuery; import com.github.sommeri.less4j.core.ast.MixinReference; import com.github.sommeri.less4j.core.ast.NestedSelectorAppender; import com.github.sommeri.less4j.core.ast.NumberExpression; @@ -29,6 +30,10 @@ public class ProblemsHandler { private LessPrinter printer = new LessPrinter(); + public void warnMerginMediaQueryWithMedium(MediaQuery mediaQuery) { + collector.addWarning(new CompilationWarning(mediaQuery, "Attempt to merge media query with a medium. Merge removed medium from inner media query, because the result CSS would be invalid otherwise.")); + } + public void warnLessImportNoBaseDirectory(Expression urlExpression) { collector.addWarning(new CompilationWarning(urlExpression, "Attempt to import less file with an unknown compiled file location. Import statement left unchanged.")); } diff --git a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java index b0487445..247738e5 100644 --- a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java +++ b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java @@ -49,7 +49,6 @@ import com.github.sommeri.less4j.core.ast.PseudoClass; import com.github.sommeri.less4j.core.ast.PseudoElement; import com.github.sommeri.less4j.core.ast.RuleSet; -import com.github.sommeri.less4j.core.ast.RuleSetsBody; import com.github.sommeri.less4j.core.ast.Selector; import com.github.sommeri.less4j.core.ast.SelectorAttribute; import com.github.sommeri.less4j.core.ast.SelectorCombinator; @@ -90,9 +89,6 @@ public boolean switchOnType(ASTCssNode node) { case RULE_SET: return appendRuleset((RuleSet) node); - case DECLARATIONS_BODY: - return appendBodyOptimizeDuplicates((RuleSetsBody) node); - case CSS_CLASS: return appendCssClass((CssClass) node); @@ -352,11 +348,8 @@ protected void appendComments(List comments, boolean ensureSeparator) { } public boolean appendFontFace(FontFace node) { - builder.append("@font-face {").newLine(); - builder.increaseIndentationLevel(); + builder.append("@font-face").ensureSeparator(); appendAllChilds(node); - builder.decreaseIndentationLevel(); - builder.append("}"); return true; } @@ -436,7 +429,7 @@ public boolean appendRuleset(RuleSet ruleSet) { return true; } - public boolean appendBodyOptimizeDuplicates(Body body) { + private boolean appendBodyOptimizeDuplicates(Body body) { if (body.isEmpty()) return false; @@ -454,7 +447,7 @@ public boolean appendBodyOptimizeDuplicates(Body body) { return true; } - private LinkedHashSet collectUniqueBodyMembersStrings(Body body) { + private LinkedHashSet collectUniqueBodyMembersStrings(Body body) { //the same declaration must be printed only once ExtendedStringBuilder storedBuilder = builder; LinkedHashSet declarationsStrings = new LinkedHashSet(); @@ -487,16 +480,17 @@ public boolean appendDeclaration(Declaration declaration) { return true; } - public boolean appendMedia(Media node) { + private boolean appendMedia(Media node) { builder.append("@media"); appendMediums(node.getMediums()); - appendBodySortDeclarations(node); + appendBodySortDeclarations(node.getBody()); return true; } - //TODO: the way this is used ignores comments in front of the body - private void appendBodySortDeclarations(Body node) { + private void appendBodySortDeclarations(Body node) { + //this is sort of hack, bypass the usual append method + appendComments(node.getOpeningComments(), true); builder.ensureSeparator().append("{").newLine(); builder.increaseIndentationLevel(); @@ -515,6 +509,8 @@ private void appendBodySortDeclarations(Body node) { } builder.decreaseIndentationLevel(); builder.append("}"); + //this is sort of hack, bypass the usual append method + appendComments(node.getTrailingComments(), false); } private void appendMediums(List mediums) { diff --git a/src/main/java/com/github/sommeri/less4j/utils/DebugUtils.java b/src/main/java/com/github/sommeri/less4j/utils/DebugUtils.java new file mode 100644 index 00000000..f06b427b --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/utils/DebugUtils.java @@ -0,0 +1,42 @@ +package com.github.sommeri.less4j.utils; + +import java.util.HashSet; +import java.util.Set; + +import com.github.sommeri.less4j.core.ast.ASTCssNode; + +public class DebugUtils { + + private Set duplicates = new HashSet(); + + public DebugUtils() { + } + + public void solveParentChildRelationShips(ASTCssNode node) { + for (ASTCssNode kid : node.getChilds()) { + kid.setParent(node); + solveParentChildRelationShips(kid); + } + } + + public void checkParentChildRelationshipsSanity(ASTCssNode node, String prefix) { + duplicates = new HashSet(); + doCheckParentChildRelationshipsSanity(node, prefix); + } + + private void doCheckParentChildRelationshipsSanity(ASTCssNode node, String prefix) { + for (ASTCssNode kid : node.getChilds()) { + if (duplicates.contains(kid)) + System.out.println("duplicate " + prefix + kid); + + duplicates.add(kid); + + if (kid.getParent() != node) + System.out.println("parent " + prefix + kid); + + doCheckParentChildRelationshipsSanity(kid, prefix); + } + } + + +} diff --git a/src/test/java/com/github/sommeri/less4j/compiler/MediaTest.java b/src/test/java/com/github/sommeri/less4j/compiler/MediaTest.java new file mode 100644 index 00000000..eb1d3b93 --- /dev/null +++ b/src/test/java/com/github/sommeri/less4j/compiler/MediaTest.java @@ -0,0 +1,23 @@ +package com.github.sommeri.less4j.compiler; + +import java.io.File; +import java.util.Collection; + +import org.junit.runners.Parameterized.Parameters; + +import com.github.sommeri.less4j.utils.TestFileUtils; + +public class MediaTest extends BasicFeaturesTest { + + private static final String standardCases = "src/test/resources/compile-basic-features/media/"; + + public MediaTest(File inputFile, File outputFile, String testName) { + super(inputFile, outputFile, testName); + } + + @Parameters() + public static Collection allTestsParameters() { + return (new TestFileUtils()).loadTestFiles(standardCases); + } + +} diff --git a/src/test/resources/command-line/errors.css b/src/test/resources/command-line/errors.css new file mode 100644 index 00000000..1b154af5 --- /dev/null +++ b/src/test/resources/command-line/errors.css @@ -0,0 +1,4 @@ +.test h4 { + declaration: !#error#!; + padding: 2 2 2 2; +} diff --git a/src/test/resources/command-line/errorsandwarnings.css b/src/test/resources/command-line/errorsandwarnings.css new file mode 100644 index 00000000..89106ce2 --- /dev/null +++ b/src/test/resources/command-line/errorsandwarnings.css @@ -0,0 +1,4 @@ +{ + declaration: !#error#!; + padding: 2 2 2 2; +} diff --git a/src/test/resources/compile-basic-features/css-optimizations/duplicate-declarations-removal.css b/src/test/resources/compile-basic-features/css-optimizations/duplicate-declarations-removal.css index 8b31ba07..053b159a 100644 --- a/src/test/resources/compile-basic-features/css-optimizations/duplicate-declarations-removal.css +++ b/src/test/resources/compile-basic-features/css-optimizations/duplicate-declarations-removal.css @@ -8,7 +8,6 @@ } @font-face { font-family: something; - font-family: something; } .colors { color: red; diff --git a/src/test/resources/compile-basic-features/media/debug.css b/src/test/resources/compile-basic-features/media/debug.css new file mode 100644 index 00000000..fbd0ad06 --- /dev/null +++ b/src/test/resources/compile-basic-features/media/debug.css @@ -0,0 +1,10 @@ +@media (max-width: 500px) { + h1 { + background: green; + } +} +@media (max-width: 500px) { + h2 { + background: green; + } +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/media/debug.less b/src/test/resources/compile-basic-features/media/debug.less new file mode 100644 index 00000000..03623a8c --- /dev/null +++ b/src/test/resources/compile-basic-features/media/debug.less @@ -0,0 +1,22 @@ +//https://github.com/cloudhead/less.js/issues/999 +.bg() { + @media (max-width: 500px) { + background: green; + } +} + +h1 { + .bg(); +} + +h2 { + .bg(); +} + +//@media (max-width: 1000px) { +// body { +// .bg(); +// background: blue; +// } +//} + diff --git a/src/test/resources/compile-basic-features/media/media-bubbling.css b/src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.css similarity index 56% rename from src/test/resources/compile-basic-features/media/media-bubbling.css rename to src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.css index d356dfc7..a83da06f 100644 --- a/src/test/resources/compile-basic-features/media/media-bubbling.css +++ b/src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.css @@ -9,32 +9,29 @@ padding: 2 2 2 2; } } -@media a, b and c { - +@media screen, (min-width: 117px) and (min-width: 117px) { } -@media a and x, b and c and x, a and y, b and c and y { - { - color: red; - } +@media screen and (max-device-aspect-ratio: 1367/768), (min-width: 117px) and (min-width: 117px) and (max-device-aspect-ratio: 1367/768), screen and (max-device-aspect-ratio: 2000/768), (min-width: 117px) and (min-width: 117px) and (max-device-aspect-ratio: 2000/768) { + color: red; } -@media a { +@media print { footer { - color: white; + color: #ffffff; } header { - color: white; + color: #ffffff; } } -@media a and b { +@media print and (min-width: 117px) { header { color: #000000; } footer { - color: black; + color: #000000; } } #namespace #foo { - color: black; + color: #000000; } @media (orientation: portrait) { #namespace #bar { @@ -45,7 +42,7 @@ color: blue; } @media print { - @page { + @page { margin: 0.5cm; } } @@ -57,7 +54,7 @@ color: white; } } -@media (orientation: portrait) and x and y { +@media (orientation: portrait) and (min-width: 22) and (max-width: 220) { #namespace #bar { padding: 2 2 2 2; } @@ -108,26 +105,32 @@ .classtwo { width: 50%; } -@media screen and max-width (600px) { - set { - padding: 1 1 1 1; +@media screen and (max-width: 600px) { + h1 { + declaration: value; } } -@media screen and max-width (600px) { - set { - padding: 2 2 2 2; +@media screen and (max-width: 300) { + h2 { + declaration: value; + } +} +body { + background: red; +} +@media (max-width: 500px) { + body { + background: green; } } -@media screen and max-width(600px) { - set { - padding: 3 3 3 3; +@media (max-width: 1000px) { + body { + background: red; + background: blue; } } -/* partial are not allowed -@partial: ~"max-width(600px)"; -@media screen and @partial { - set { - padding: 3 3 3 3; +@media (max-width: 1000px) and (max-width: 500px) { + body { + background: green; } } -*/ diff --git a/src/test/resources/compile-basic-features/media/media-bubbling.less b/src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.less similarity index 64% rename from src/test/resources/compile-basic-features/media/media-bubbling.less rename to src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.less index e5f36596..3ab18705 100644 --- a/src/test/resources/compile-basic-features/media/media-bubbling.less +++ b/src/test/resources/compile-basic-features/media/media-bubbling-lessjs-issues.less @@ -12,28 +12,28 @@ color: blue; } -@media a, b and c { - @media x, y { +@media screen, (min-width: 117px) and (min-width: 117px) { + @media (max-device-aspect-ratio: 1367/768), (max-device-aspect-ratio: 2000/768) { color: red; } } -@media a { - footer { color: white; } +@media print { + footer { color: #ffffff; } - @media b { - header { color: black } - footer { color: black; } + @media (min-width: 117px) { + header { color: #000000 } + footer { color: #000000; } } - header { color: white; } + header { color: #ffffff; } } // https://github.com/cloudhead/less.js/issues/286 #namespace { - #foo { color:black; } + #foo { color:#000000; } @media (orientation:portrait) { - #bar { color:white } + #bar { color:#ffffff } } } @@ -46,13 +46,13 @@ @page { margin: 0.5cm; } } -// *** TODO put to separate file, my invention **** +// my invention **** #namespace { #foo { color:black; } @media (orientation:portrait) { #bar { color:white; - @media x and y { + @media (min-width: 22) and (max-width: 220) { padding: 2 2 2 2; .innest { margin: 1 1 1 1; @@ -63,7 +63,6 @@ } // https://github.com/cloudhead/less.js/issues/528 - @media print { color: blue; } @@ -124,32 +123,37 @@ } // https://github.com/cloudhead/less.js/pull/643 -@media screen and max-width(600px) { - set { - padding: 1 1 1 1; +@media screen and (max-width: 600px) { + h1 { + declaration: value; } } -@tabletWidth: 600px; -@media screen and max-width(@tabletWidth) { - set { - padding: 2 2 2 2; +@tabletWidth: 300; +@media screen and (max-width: @tabletWidth) { + h2 { + declaration: value; } } -@tablet: ~"screen and max-width(600px)"; -@media @tablet { - set { - padding: 3 3 3 3; - } +//https://github.com/cloudhead/less.js/issues/999 +.bg() { + background: red; + + @media (max-width: 500px) { + background: green; + } } -/* partial are not allowed -@partial: ~"max-width(600px)"; -@media screen and @partial { - set { - padding: 3 3 3 3; - } +body { + .bg(); +} + +@bpMedium: 1000px; +@media (max-width: @bpMedium) { + body { + .bg(); + background: blue; + } } -*/ diff --git a/src/test/resources/compile-basic-features/media/media-bubbling-scoping.less b/src/test/resources/compile-basic-features/media/media-bubbling-scoping.less index 2ecf4939..6c151689 100644 --- a/src/test/resources/compile-basic-features/media/media-bubbling-scoping.less +++ b/src/test/resources/compile-basic-features/media/media-bubbling-scoping.less @@ -1,5 +1,4 @@ // https://github.com/cloudhead/less.js/issues/968 - .foo { .bar { background: #000; diff --git a/src/test/resources/compile-basic-features/media/media-merging-top-level.css b/src/test/resources/compile-basic-features/media/media-merging-top-level.css new file mode 100644 index 00000000..87252e37 --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-merging-top-level.css @@ -0,0 +1,40 @@ +@media screen, print { +} +@media screen and (max-device-aspect-ratio: 1367/768), print and (max-device-aspect-ratio: 1367/768) { + color: white; +} +@media screen and (min-width: 117px) { + padding: 1 1 1 1; + color: #008000; + background-color: white; +} +@media screen and (min-width: 117px) and (max-device-aspect-ratio: 1367/768) { + color: white; + background-color: #008000; +} +@media screen and (min-width: 117px), print { +} +@media screen and (min-width: 117px) and (max-device-aspect-ratio: 1367/768) and (max-width: 700px), print and (max-device-aspect-ratio: 1367/768) and (max-width: 700px), screen and (min-width: 117px) and (min-device-aspect-ratio: 10/10), print and (min-device-aspect-ratio: 10/10) { + pre { + color: white; + padding: 2 2 2 2; + } + pre #45 { + padding: 3 3 3 3; + } +} +@media screen, print { + pre { + content: "one level down"; + } +} +@media screen and (max-device-aspect-ratio: 1367/768), print and (max-device-aspect-ratio: 1367/768) { + pre { + content: "two levels down"; + } +} +@media screen and (max-device-aspect-ratio: 1367/768) and (max-width: 34), print and (max-device-aspect-ratio: 1367/768) and (max-width: 34) { + pre { + content: "three levels down"; + } +} diff --git a/src/test/resources/compile-basic-features/media/media-merging-top-level.less b/src/test/resources/compile-basic-features/media/media-merging-top-level.less new file mode 100644 index 00000000..8fb8df41 --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-merging-top-level.less @@ -0,0 +1,45 @@ +@media screen, print { + @media (max-device-aspect-ratio: 1367/768) { + color: white; + } +} + +@media screen and (min-width: 117px) { + @color: #008000; + padding: 1 1 1 1; + @media (max-device-aspect-ratio: 1367/768) { + color: white; + background-color: @color; + } + color: @color; + background-color: white; +} + +@media screen and (min-width: 117px), print { + @media (max-device-aspect-ratio: 1367/768) and (max-width: 700px), (min-device-aspect-ratio: 10/10){ + pre { + color: white; + padding: 2 2 2 2; + #45 { + padding: 3 3 3 3; + } + } + } +} + +@media screen, print { + pre { + content: "one level down"; + } + @media (max-device-aspect-ratio: 1367/768) { + pre { + content: "two levels down"; + } + @media (max-width: 34) { + pre { + content: "three levels down"; + } + } + } +} + diff --git a/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.css b/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.css new file mode 100644 index 00000000..52340c5e --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.css @@ -0,0 +1,17 @@ +@media screen, print { +} +@media screen and (max-device-aspect-ratio: 1367/768), print and (max-device-aspect-ratio: 1367/768) { + h2 h1 { + color: white; + } +} +@media screen, print { + pre td body #nested { + content: "string"; + } +} +@media screen and (max-device-aspect-ratio: 30/2), print and (max-device-aspect-ratio: 30/2) { + pre td body #nested h2 { + color: white; + } +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.less b/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.less new file mode 100644 index 00000000..2c7b7cbd --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-merging-with-bubbling.less @@ -0,0 +1,30 @@ +h2 { + h1 { + @media screen, print { + @media (max-device-aspect-ratio: 1367/768) { + color: white; + } + } + } +} + +#namespace { + .mixin() { + content: "string"; + @media (max-device-aspect-ratio: 30/2) { + h2 { + color: white; + } + } + } +} + +pre { + td { + @media screen, print { + body { #nested { + #namespace .mixin(); + } } + } + } +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/media/media-problems-and-todo.less b/src/test/resources/compile-basic-features/media/media-problems-and-todo.less deleted file mode 100644 index 7b30c4bf..00000000 --- a/src/test/resources/compile-basic-features/media/media-problems-and-todo.less +++ /dev/null @@ -1,46 +0,0 @@ -/* Syntax error - -// https://github.com/cloudhead/less.js/issues/1028 -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'Helvetica Neue'; - unicode-range: U+FF0C, U+3001, U+3002; - src: local('Heiti SC'), local(PMingLiU); - } -} -*/ - -//TODO: @media (orientation:portrait) with that and thing - -/* This will be fixed in 1.3.2 - -https://github.com/cloudhead/less.js/issues/999 -.bg() { - background: red; - - @media (max-width: 500px) { - background: green; - } -} - -body { - .bg(); -} - -@bpMedium: 1000px; -@media (max-width: @bpMedium) { - body { - .bg(); - background: blue; - } -} - - -*/ - -/* not implemented in less.js yet, but will be: -https://github.com/cloudhead/less.js/issues/950 -https://github.com/cloudhead/less.js/issues/654 -*/ - - diff --git a/src/test/resources/compile-basic-features/media/media-with-font-face.css b/src/test/resources/compile-basic-features/media/media-with-font-face.css new file mode 100644 index 00000000..aadeb5df --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-with-font-face.css @@ -0,0 +1,6 @@ +@media screen and (-webkit-min-device-pixel-ratio: 0) { + @font-face { + font-family: 'Helvetica Neue'; + src: local('Heiti SC'), local(PMingLiU); + } +} diff --git a/src/test/resources/compile-basic-features/media/media-with-font-face.less b/src/test/resources/compile-basic-features/media/media-with-font-face.less new file mode 100644 index 00000000..58cc4d65 --- /dev/null +++ b/src/test/resources/compile-basic-features/media/media-with-font-face.less @@ -0,0 +1,7 @@ +// https://github.com/cloudhead/less.js/issues/1028 +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'Helvetica Neue'; + src: local('Heiti SC'), local(PMingLiU); + } +} diff --git a/src/test/resources/compile-basic-features/media/todo/media-query-as-variable.less b/src/test/resources/compile-basic-features/media/todo/media-query-as-variable.less new file mode 100644 index 00000000..25422c4c --- /dev/null +++ b/src/test/resources/compile-basic-features/media/todo/media-query-as-variable.less @@ -0,0 +1,24 @@ +// https://github.com/cloudhead/less.js/pull/643 +@tablet: ~"screen and (max-width: 100px)"; +@media @tablet { + h3 { + declaration: value; + } +} + +@singleQuery: ~"(max-width: 500px)"; +@media screen, @singleQuery { + set { + padding: 3 3 3 3; + } +} + +/* partial are not allowed +@partial: ~"(max-width: 600px)"; +@media screen and @partial { + set { + padding: 3 3 3 3; + } +} +*/ +//TODO combine it with merging and bubbling !!!! \ No newline at end of file