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 15cc60d1..2312b2b0 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 @@ -64,6 +64,7 @@ tokens { MEDIUM_DECLARATION; MEDIA_EXPRESSION; INTERPOLATED_MEDIA_EXPRESSION; + IMPORT_OPTIONS; MEDIA_QUERY; MEDIUM_TYPE; BODY; @@ -204,10 +205,14 @@ finally { leaveRule(); } // imports @init {enterRule(retval, RULE_IMPORTS);} - : (IMPORT_SYM | IMPORT_ONCE_SYM | IMPORT_MULTIPLE_SYM)^ (term) (mediaQuery (COMMA mediaQuery)*)? SEMI! + : (IMPORT_SYM | IMPORT_ONCE_SYM | IMPORT_MULTIPLE_SYM)^ (importoptions)? term (mediaQuery (COMMA mediaQuery)*)? SEMI! ; finally { leaveRule(); } +importoptions: + LPAREN a+=IDENT+ RPAREN + -> ^(IMPORT_OPTIONS $a*) + ; // --------- // Media. Introduce a set of rules that are to be used if the consumer indicates // it belongs to the signified medium. 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 8e63a3a1..8a77fb17 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, SELECTOR_OPERATOR, SELECTOR_COMBINATOR, EXPRESSION_OPERATOR, NTH, NAMED_EXPRESSION, MEDIA_QUERY, FIXED_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, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION, SYNTAX_ONLY_ELEMENT, UNICODE_RANGE_EXPRESSION, INTERPOLATED_MEDIA_EXPRESSION, DOCUMENT, SUPPORTS, SUPPORTS_QUERY, SUPPORTS_CONDITION_NEGATION, SUPPORTS_CONDITION_PARENTHESES, SUPPORTS_CONDITION_LOGICAL, SUPPORTS_LOGICAL_OPERATOR, EXTEND + 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, FIXED_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, REUSABLE_STRUCTURE_NAME, VIEWPORT, GENERAL_BODY, PAGE, NAME, PAGE_MARGIN_BOX, IMPORT, FAULTY_NODE, ANONYMOUS, EMPTY_EXPRESSION, SYNTAX_ONLY_ELEMENT, UNICODE_RANGE_EXPRESSION, INTERPOLATED_MEDIA_EXPRESSION, DOCUMENT, SUPPORTS, SUPPORTS_QUERY, SUPPORTS_CONDITION_NEGATION, SUPPORTS_CONDITION_PARENTHESES, SUPPORTS_CONDITION_LOGICAL, SUPPORTS_LOGICAL_OPERATOR, EXTEND, INLINE_CONTENT } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/Import.java b/src/main/java/com/github/sommeri/less4j/core/ast/Import.java index 32f01999..d34d0e23 100644 --- a/src/main/java/com/github/sommeri/less4j/core/ast/Import.java +++ b/src/main/java/com/github/sommeri/less4j/core/ast/Import.java @@ -9,7 +9,8 @@ public class Import extends ASTCssNode { - private ImportKind kind = ImportKind.IMPORT; + private ImportMultiplicity multiplicity = ImportMultiplicity.IMPORT; + private boolean isInline = false; private Expression urlExpression; private List mediums = new ArrayList(); @@ -17,12 +18,20 @@ public Import(HiddenTokenAwareTree underlyingStructure) { super(underlyingStructure); } - public ImportKind getKind() { - return kind; + public boolean isInline() { + return isInline; } - public void setKind(ImportKind kind) { - this.kind = kind; + public void setInline(boolean isInline) { + this.isInline = isInline; + } + + public ImportMultiplicity getMultiplicity() { + return multiplicity; + } + + public void setMultiplicity(ImportMultiplicity multiplicity) { + this.multiplicity = multiplicity; } public Expression getUrlExpression() { @@ -71,7 +80,7 @@ public ASTCssNodeType getType() { return ASTCssNodeType.IMPORT; } - public enum ImportKind { + public enum ImportMultiplicity { IMPORT, IMPORT_ONCE, IMPORT_MULTIPLE } diff --git a/src/main/java/com/github/sommeri/less4j/core/ast/InlineContent.java b/src/main/java/com/github/sommeri/less4j/core/ast/InlineContent.java new file mode 100644 index 00000000..9ca66164 --- /dev/null +++ b/src/main/java/com/github/sommeri/less4j/core/ast/InlineContent.java @@ -0,0 +1,46 @@ +package com.github.sommeri.less4j.core.ast; + +import java.util.Collections; +import java.util.List; + +import com.github.sommeri.less4j.core.ast.annotations.NotAstProperty; +import com.github.sommeri.less4j.core.parser.HiddenTokenAwareTree; + +public class InlineContent extends ASTCssNode { + + private String value; + + public InlineContent(HiddenTokenAwareTree token, String value) { + super(token); + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public ASTCssNodeType getType() { + return ASTCssNodeType.INLINE_CONTENT; + } + + @Override + @NotAstProperty + public List getChilds() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "" + value; + } + + @Override + public InlineContent clone() { + return (InlineContent) super.clone(); + } +} diff --git a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SingleImportsSolver.java b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SingleImportsSolver.java index 8fe69006..f968b2a3 100644 --- a/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SingleImportsSolver.java +++ b/src/main/java/com/github/sommeri/less4j/core/compiler/stages/SingleImportsSolver.java @@ -15,7 +15,8 @@ import com.github.sommeri.less4j.core.ast.FaultyNode; 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.InlineContent; +import com.github.sommeri.less4j.core.ast.Import.ImportMultiplicity; import com.github.sommeri.less4j.core.ast.Media; import com.github.sommeri.less4j.core.ast.StyleSheet; import com.github.sommeri.less4j.core.compiler.expressions.TypesConversionUtils; @@ -101,6 +102,17 @@ public ASTCssNode importEncountered(Import node, LessSource source) { return null; } + if (node.isInline()) { + HiddenTokenAwareTree underlyingStructure = node.getUnderlyingStructure(); + StyleSheet result = new StyleSheet(underlyingStructure); + InlineContent content = new InlineContent(underlyingStructure, importedContent); + result.addMember(content); + result.configureParentToAllChilds(); + + astManipulator.replaceInBody(node, content); + return result; + } + // parse imported file StyleSheet importedAst = parseContent(node, importedContent, importedSource); @@ -127,7 +139,7 @@ public ASTCssNode importEncountered(Import node, LessSource source) { } private boolean isImportOnce(Import node) { - return node.getKind() == ImportKind.IMPORT_ONCE; + return node.getMultiplicity() == ImportMultiplicity.IMPORT_ONCE; } private boolean alreadyVisited(LessSource importedSource) { 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 c8d0a920..6c447327 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 @@ -104,6 +104,13 @@ class ASTBuilderSwitch extends TokenTypeSwitch { } private final static String EXTEND_ALL_KEYWORD = "all"; + + private final static String IMPORT_OPTION_REFERENCE = "reference"; + private final static String IMPORT_OPTION_INLINE = "inline"; + private final static String IMPORT_OPTION_LESS = "less"; + private final static String IMPORT_OPTION_CSS = "css"; + private final static String IMPORT_OPTION_ONCE = "once"; + private final static String IMPORT_OPTION_MULTIPLE = "multiple"; public ASTBuilderSwitch(ProblemsHandler problemsHandler) { super(); @@ -1060,20 +1067,25 @@ public Import handleImport(HiddenTokenAwareTree token) { Import result = new Import(token); switch (token.getType()) { case LessLexer.IMPORT_SYM: - result.setKind(Import.ImportKind.IMPORT); + result.setMultiplicity(Import.ImportMultiplicity.IMPORT); break; case LessLexer.IMPORT_ONCE_SYM: - result.setKind(Import.ImportKind.IMPORT_ONCE); + result.setMultiplicity(Import.ImportMultiplicity.IMPORT_ONCE); break; case LessLexer.IMPORT_MULTIPLE_SYM: - result.setKind(Import.ImportKind.IMPORT_MULTIPLE); + result.setMultiplicity(Import.ImportMultiplicity.IMPORT_MULTIPLE); break; default: throw new BugHappened(GRAMMAR_MISMATCH, token); } - Iterator children = token.getChildren().iterator(); - result.setUrlExpression(handleTerm(children.next())); + HiddenTokenAwareTree nextChild = children.next(); + if (nextChild.getType() == LessLexer.IMPORT_OPTIONS) { + configureImportOptions(result, nextChild.getChildren()); + nextChild = children.next(); + } + + result.setUrlExpression(handleTerm(nextChild)); while (children.hasNext()) { HiddenTokenAwareTree kid = children.next(); if (kid.getType() == LessLexer.COMMA) { @@ -1088,6 +1100,17 @@ public Import handleImport(HiddenTokenAwareTree token) { return result; } + private void configureImportOptions(Import node, List options) { + for (HiddenTokenAwareTree token : options) { + String text = token.getText(); + if (IMPORT_OPTION_INLINE.equals(text)) { + node.setInline(true); + } else { + problemsHandler.unknownImportOption(node, text); + } + } + } + public NamedExpression handleNamedExpression(HiddenTokenAwareTree token) { HiddenTokenAwareTree nameToken = token.getChild(0); HiddenTokenAwareTree valueToken = token.getChild(1); 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 5accc59b..ac7a32eb 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 @@ -11,6 +11,7 @@ import com.github.sommeri.less4j.core.ast.EscapedSelector; import com.github.sommeri.less4j.core.ast.Expression; import com.github.sommeri.less4j.core.ast.FunctionExpression; +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; @@ -59,6 +60,10 @@ 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 unknownImportOption(Import node, String text) { + collector.addError(new CompilationError(node, "Unknown import option \"" + text)); + } + public void warnInconsistentSupportsLogicalConditionOperators(SupportsLogicalOperator faulty, SupportsLogicalOperator masterOperator) { String faultySymbol = faulty.getOperator().getSymbol(); String masterSymbol = masterOperator.getOperator().getSymbol(); 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 56a9ff3a..8b4c1a3c 100644 --- a/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java +++ b/src/main/java/com/github/sommeri/less4j/utils/CssPrinter.java @@ -30,6 +30,7 @@ import com.github.sommeri.less4j.core.ast.IdSelector; import com.github.sommeri.less4j.core.ast.IdentifierExpression; import com.github.sommeri.less4j.core.ast.Import; +import com.github.sommeri.less4j.core.ast.InlineContent; import com.github.sommeri.less4j.core.ast.InterpolableName; import com.github.sommeri.less4j.core.ast.InterpolatedMediaExpression; import com.github.sommeri.less4j.core.ast.Keyframes; @@ -278,6 +279,9 @@ public boolean switchOnType(ASTCssNode node) { case SUPPORTS_LOGICAL_OPERATOR: return appendSupportsLogicalOperator((SupportsLogicalOperator) node); // TODOsm: source map + case INLINE_CONTENT: + return appendInlineContent((InlineContent) node); // TODOsm: source map + case ESCAPED_SELECTOR: case PARENTHESES_EXPRESSION: case SIGNED_EXPRESSION: @@ -291,6 +295,12 @@ public boolean switchOnType(ASTCssNode node) { } } + //TODO: what about source maps? + private boolean appendInlineContent(InlineContent node) { + cssOnly.appendAsIs(node.getValue()); + return false; + } + private boolean appendSyntaxOnlyElement(SyntaxOnlyElement node) { cssOnly.append(node.getSymbol()); return true; diff --git a/src/test/java/com/github/sommeri/less4j/compiler/TokenNamesForErrorReportingTest.java b/src/test/java/com/github/sommeri/less4j/compiler/TokenNamesForErrorReportingTest.java index cb6c3bab..44ee8db2 100644 --- a/src/test/java/com/github/sommeri/less4j/compiler/TokenNamesForErrorReportingTest.java +++ b/src/test/java/com/github/sommeri/less4j/compiler/TokenNamesForErrorReportingTest.java @@ -14,7 +14,7 @@ public class TokenNamesForErrorReportingTest { private static final List TECHNICAL_ANTLR = Arrays.asList("", "", "", ""); - private static final List FICTIONAL = Arrays.asList("ARGUMENT_DECLARATION", "VARIABLE_DECLARATION", "ARGUMENT_COLLECTOR", "EXPRESSION", "DECLARATION", "VARIABLE_REFERENCE", "RULESET", "NESTED_APPENDER", "EXTENDED_SELECTOR", "SELECTOR", "SIMPLE_SELECTOR", "ESCAPED_SELECTOR", "EXPRESSION_PARENTHESES", "ESCAPED_VALUE", "STYLE_SHEET", "EMPTY_SEPARATOR", "ELEMENT_NAME", "CSS_CLASS", "NTH", "PSEUDO", "ATTRIBUTE", "ID_SELECTOR", "ELEMENT_SUBSEQUENT", "CHARSET_DECLARATION", "TERM_FUNCTION", "TERM", "MEDIUM_DECLARATION", "FIXED_MEDIA_EXPRESSION", "MEDIA_QUERY", "MEDIUM_TYPE", "BODY", "MIXIN_REFERENCE", "NAMESPACE_REFERENCE", "REUSABLE_STRUCTURE", "MIXIN_PATTERN", "GUARD_CONDITION", "GUARD", "DUMMY_MEANINGFULL_WHITESPACE", "KEYFRAMES", "DOCUMENT_DECLARATION", "REUSABLE_STRUCTURE_NAME", "VIEWPORT"); + private static final List FICTIONAL = Arrays.asList("IMPORT_OPTIONS", "ARGUMENT_DECLARATION", "VARIABLE_DECLARATION", "ARGUMENT_COLLECTOR", "EXPRESSION", "DECLARATION", "VARIABLE_REFERENCE", "RULESET", "NESTED_APPENDER", "EXTENDED_SELECTOR", "SELECTOR", "SIMPLE_SELECTOR", "ESCAPED_SELECTOR", "EXPRESSION_PARENTHESES", "ESCAPED_VALUE", "STYLE_SHEET", "EMPTY_SEPARATOR", "ELEMENT_NAME", "CSS_CLASS", "NTH", "PSEUDO", "ATTRIBUTE", "ID_SELECTOR", "ELEMENT_SUBSEQUENT", "CHARSET_DECLARATION", "TERM_FUNCTION", "TERM", "MEDIUM_DECLARATION", "FIXED_MEDIA_EXPRESSION", "MEDIA_QUERY", "MEDIUM_TYPE", "BODY", "MIXIN_REFERENCE", "NAMESPACE_REFERENCE", "REUSABLE_STRUCTURE", "MIXIN_PATTERN", "GUARD_CONDITION", "GUARD", "DUMMY_MEANINGFULL_WHITESPACE", "KEYFRAMES", "DOCUMENT_DECLARATION", "REUSABLE_STRUCTURE_NAME", "VIEWPORT"); private static final List TECHNICAL_LESS4J = Arrays.asList("SEMI_SPLIT_MIXIN_DECLARATION_ARGUMENTS", "SEMI_SPLIT_MIXIN_REFERENCE_ARGUMENTS", "UNICODE_RANGE_HEX","TERM_FUNCTION_NAME", "NMSTART", "NMCHAR", "NAME", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"); @Test diff --git a/src/test/resources/compile-basic-features/import/import-options-inline.css b/src/test/resources/compile-basic-features/import/import-options-inline.css new file mode 100644 index 00000000..08b8f7b0 --- /dev/null +++ b/src/test/resources/compile-basic-features/import/import-options-inline.css @@ -0,0 +1,18 @@ +first { + first: first; +} +@variable: @fromImporter; + +.mixin() { + content: ~"@variable value is" @variable; +} + +#space { + .mixin() { + content: ~"mixin in space"; + } +} +div { + location: main; + variable: main; +} \ No newline at end of file diff --git a/src/test/resources/compile-basic-features/import/import-options-inline.less b/src/test/resources/compile-basic-features/import/import-options-inline.less new file mode 100644 index 00000000..ffd9cf29 --- /dev/null +++ b/src/test/resources/compile-basic-features/import/import-options-inline.less @@ -0,0 +1,14 @@ +first { + first: first; +} +@variable: main; +@import (inline) "import/namespaces-and-mixins.less"; + +.mixin() { + location: main; +} +@fromImporter: value; +div { + .mixin(); + variable: @variable; +}