diff --git a/pom.xml b/pom.xml index 70324cf..4c97869 100644 --- a/pom.xml +++ b/pom.xml @@ -1,88 +1,83 @@ - + - 4.0.0 - org.lesscss - lesscss - 1.7.0.1.2-SNAPSHOT - jar - Official LESS CSS Compiler for Java - Official LESS CSS Compiler for Java - http://github.com/marceloverdijk/lesscss-java - - - org.sonatype.oss - oss-parent - 7 - - - - UTF-8 - UTF-8 - - - - - commons-io - commons-io - 2.4 - - - junit - junit - 4.10 - test - - - org.apache.commons - commons-lang3 - 3.1 - test - - - org.mockito - mockito-core - 1.9.0 - test - - - org.powermock - powermock-module-junit4 - 1.4.11 - test - - - org.powermock - powermock-api-mockito - 1.4.11 - test - - - org.jodah - concurrentunit - 0.3.0 - test - - - org.mozilla - rhino - 1.7R4 - - - org.slf4j - slf4j-api - 1.7.2 - true - - - org.slf4j - slf4j-simple - 1.7.2 - runtime - - - - - + 4.0.0 + org.extendedmind + lesscss + 1.7.4.1.0 + jar + Official LESS CSS Compiler for Java + Official LESS CSS Compiler for Java + http://github.com/ttiurani/lesscss-java + + + UTF-8 + UTF-8 + + + + + commons-io + commons-io + 2.4 + + + junit + junit + 4.10 + test + + + org.apache.commons + commons-lang3 + 3.1 + test + + + org.mockito + mockito-core + 1.9.0 + test + + + org.powermock + powermock-module-junit4 + 1.4.11 + test + + + org.powermock + powermock-api-mockito + 1.4.11 + test + + + org.jodah + concurrentunit + 0.3.0 + test + + + org.mozilla + rhino + 1.7R4 + + + org.slf4j + slf4j-api + 1.7.2 + true + + + org.slf4j + slf4j-simple + 1.7.2 + runtime + + + + + org.apache.maven.plugins maven-surefire-plugin @@ -95,162 +90,189 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - UTF-8 - 1.5 - 1.5 - - - - org.apache.maven.plugins - maven-failsafe-plugin - 2.16 - - - - integration-test - verify - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.8 - - UTF-8 - ${basedir}/src/main/javadoc/stylesheet.css - - - - sources-jar - package - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - javadoc-jar - package - - jar - - - - - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.8 - - - test - - check - - - - - - org.codehaus.mojo.signature - java15 - 1.0 - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.2 - - - package - - shade - - - - - org.mozilla:rhino - org.slf4j:slf4j-api - org.slf4j:slf4j-simple - - - - - org.apache.commons.io - org.lesscss.deps.org.apache.commons.io - - - true - - - - - - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - marceloverdijk - Marcel Overdijk - marcel@overdijk.me - - Lead Developer - - +2 - - - candrews - Craig Andrews - candrews@integralblue.com - http://candrews.integralblue.com - - Developer - - -5 - - - cpopov - Christophe Popov - chrpopov.gmail.com - http://uk.linkedin.com/in/hpopov/ - - Developer - - 0 - - - - - GitHub - http://github.com/marceloverdijk/lesscss-java/issues - - - - scm:git:git@github.com:marceloverdijk/lesscss-java.git - scm:git:git@github.com:marceloverdijk/lesscss-java.git - http://github.com/marceloverdijk/lesscss-java - - + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + UTF-8 + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.16 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.8 + + UTF-8 + ${basedir}/src/main/javadoc/stylesheet.css + + + + sources-jar + package + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + javadoc-jar + package + + jar + + + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.8 + + + test + + check + + + + + + org.codehaus.mojo.signature + java15 + 1.0 + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.2 + + + package + + shade + + + + + org.mozilla:rhino + org.slf4j:slf4j-api + org.slf4j:slf4j-simple + + + + + org.apache.commons.io + org.lesscss.deps.org.apache.commons.io + + + true + + + + + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + marceloverdijk + Marcel Overdijk + marcel@overdijk.me + + Lead Developer + + +2 + + + candrews + Craig Andrews + candrews@integralblue.com + http://candrews.integralblue.com + + Developer + + -5 + + + cpopov + Christophe Popov + chrpopov.gmail.com + http://uk.linkedin.com/in/hpopov/ + + Developer + + 0 + + + ttiurani + Timo Tiuraniemi + timo.tiuraniemi@iki.fi + http://ext.md + + Developer + + +2 + + + + + GitHub + http://github.com/extendedmind/lesscss-java/issues + + + + scm:git:git@github.com:extendedmind/lesscss-java.git + scm:git:git@github.com:extendedmind/lesscss-java.git + http://github.com/extendedmind/lesscss-java + + + + + false + extendedmind-releases + Extended Mind Releases + https://ci.ext.md/nexus/content/repositories/releases + default + + + true + extendedmind-snapshots + Extended Mind Snapshots + https://ci.ext.md/nexus/content/repositories/snapshots + default + + + diff --git a/src/main/java/org/lesscss/LessCompiler.java b/src/main/java/org/lesscss/LessCompiler.java index 1ded6fc..7eba2b4 100644 --- a/src/main/java/org/lesscss/LessCompiler.java +++ b/src/main/java/org/lesscss/LessCompiler.java @@ -65,8 +65,8 @@ public class LessCompiler { private static final LessLogger logger = LessLoggerFactory.getLogger(LessCompiler.class); - private URL lessJs = LessCompiler.class.getClassLoader().getResource("META-INF/less-rhino-1.7.0.js"); - private URL lesscJs = LessCompiler.class.getClassLoader().getResource("META-INF/lessc-rhino-1.7.0.js"); + private URL lessJs = LessCompiler.class.getClassLoader().getResource("META-INF/less-rhino-1.7.4.js"); + private URL lesscJs = LessCompiler.class.getClassLoader().getResource("META-INF/lessc-rhino-1.7.4.js"); private List customJs = Collections.emptyList(); private List options = Collections.emptyList(); private Boolean compress = null; diff --git a/src/main/java/org/lesscss/LessSource.java b/src/main/java/org/lesscss/LessSource.java index 30b6f98..5784c65 100644 --- a/src/main/java/org/lesscss/LessSource.java +++ b/src/main/java/org/lesscss/LessSource.java @@ -34,6 +34,7 @@ * Represents the metadata and content of a LESS source. * * @author Marcel Overdijk + * @author Timo Tiuraniemi */ public class LessSource { @@ -49,23 +50,6 @@ public class LessSource { private String normalizedContent; private Map imports = new LinkedHashMap(); - /** - * Constructs a new LessSource. - *

- * This will read the metadata and content of the LESS source, and will automatically resolve the imports. - *

- *

- * The resource is read using the default Charset of the platform - *

- * - * @param resource The File reference to the LESS source to read. - * @throws FileNotFoundException If the LESS source (or one of its imports) could not be found. - * @throws IOException If the LESS source cannot be read. - */ - public LessSource(Resource resource) throws IOException { - this(resource, Charset.defaultCharset()); - } - /** * Constructs a new LessSource. *

@@ -77,16 +61,18 @@ public LessSource(Resource resource) throws IOException { * @throws FileNotFoundException If the LESS resource (or one of its imports) could not be found. * @throws IOException If the LESS resource cannot be read. */ - public LessSource(Resource resource, Charset charset) throws IOException { + public LessSource(Resource resource, Charset charset, Map existingImports) throws IOException { if (resource == null) { throw new IllegalArgumentException("Resource must not be null."); } if (!resource.exists()) { throw new IOException("Resource " + resource + " not found."); } + logger.debug("Creating LESS source for resource %s with charset %s", resource.getName(), charset.displayName()); this.resource = resource; this.content = this.normalizedContent = loadResource(resource, charset); - resolveImports(); + resolveImports(existingImports); + logger.debug("Resource %s has %d imports", resource.getName(), this.imports.size()); } /** @@ -98,7 +84,8 @@ public LessSource(Resource resource, Charset charset) throws IOException { * @throws IOException */ public LessSource(File input) throws IOException { - this( new FileResource(input) ); + this( new FileResource(input) , Charset.defaultCharset(), null); + logger.debug("Creating LESS source for file %s", input.getName()); } private String loadResource(Resource resource, Charset charset) throws IOException { @@ -189,23 +176,24 @@ public Map getImports() { return imports; } - private void resolveImports() throws IOException { + private void resolveImports(Map existingImports) throws IOException { Matcher importMatcher = IMPORT_PATTERN.matcher(normalizedContent); while (importMatcher.find()) { String importedResource = importMatcher.group(5); importedResource = importedResource.matches(".*\\.(le?|c)ss$") ? importedResource : importedResource + ".less"; String importType = importMatcher.group(3)==null ? importedResource.substring(importedResource.lastIndexOf(".") + 1) : importMatcher.group(3); if (importType.equals("less")) { - logger.debug("Importing %s", importedResource); - - if( !imports.containsKey(importedResource) ) { - LessSource importedLessSource = new LessSource(getImportedResource(importedResource)); - imports.put(importedResource, importedLessSource); + logger.debug("Importing %s, %s existing imports", importedResource, existingImports == null ? "no" : String.valueOf(existingImports.size())); + if( !imports.containsKey(importedResource) && (existingImports == null || !existingImports.containsKey(importedResource))) { + logger.debug("Adding %s to imports", importedResource); + LessSource importedLessSource = new LessSource(getImportedResource(importedResource), Charset.defaultCharset(), this.imports); + this.imports.put(importedResource, importedLessSource); normalizedContent = includeImportedContent(importedLessSource, importMatcher); importMatcher = IMPORT_PATTERN.matcher(normalizedContent); } else { - normalizedContent = normalizedContent.substring(0, importMatcher.start(1)) + normalizedContent.substring(importMatcher.end(1)); + logger.debug("%s already in imports", importedResource); + normalizedContent = normalizedContent.substring(0, importMatcher.start(1)) + normalizedContent.substring(importMatcher.end(1)); importMatcher = IMPORT_PATTERN.matcher(normalizedContent); } } diff --git a/src/main/resources/META-INF/less-rhino-1.7.0.js b/src/main/resources/META-INF/less-rhino-1.7.4.js similarity index 98% rename from src/main/resources/META-INF/less-rhino-1.7.0.js rename to src/main/resources/META-INF/less-rhino-1.7.4.js index e95026f..1f2f17d 100644 --- a/src/main/resources/META-INF/less-rhino-1.7.0.js +++ b/src/main/resources/META-INF/less-rhino-1.7.4.js @@ -1,4 +1,4 @@ -/* LESS.js v1.7.0 RHINO | Copyright (c) 2009-2014, Alexis Sellier */ +/* Less.js v1.7.4 RHINO | Copyright (c) 2009-2014, Alexis Sellier */ // // Stub out `require` in rhino @@ -70,7 +70,7 @@ less.mode = 'rhino'; var result = []; for (i in parts) { var part = parts[i]; - if (part === '..' && result.length > 0) { + if (part === '..' && result.length > 0 && result[result.length-1] !== '..') { result.pop(); } else if (part === '' && result.length > 0) { // skip @@ -377,7 +377,7 @@ less.Parser = function Parser(env) { return oldi !== i || oldj !== j; } - function expect(arg, msg) { + function expect(arg, msg, index) { // some older browsers return typeof 'function' for RegExp var result = (Object.prototype.toString.call(arg) === '[object Function]') ? arg.call(parsers) : $(arg); if (result) { @@ -557,8 +557,8 @@ less.Parser = function Parser(env) { switch (cc) { case 40: // ( - parenLevel++; - lastOpeningParen = parserCurrentIndex; + parenLevel++; + lastOpeningParen = parserCurrentIndex; continue; case 41: // ) if (--parenLevel < 0) { @@ -569,8 +569,8 @@ less.Parser = function Parser(env) { if (!parenLevel) { emitChunk(); } continue; case 123: // { - level++; - lastOpening = parserCurrentIndex; + level++; + lastOpening = parserCurrentIndex; continue; case 125: // } if (--level < 0) { @@ -669,7 +669,7 @@ less.Parser = function Parser(env) { var evaldRoot, css, evalEnv = new tree.evalEnv(options); - + // // Allows setting variables with a hash, so: // @@ -837,7 +837,7 @@ less.Parser = function Parser(env) { // // Ruleset -> Rule -> Value -> Expression -> Entity // - // Here's some LESS code: + // Here's some Less code: // // .class { // color: #fff; @@ -1112,6 +1112,11 @@ less.Parser = function Parser(env) { var rgb; if (input.charAt(i) === '#' && (rgb = $re(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/))) { + var colorCandidateString = rgb.input.match(/^#([\w]+).*/); // strip colons, brackets, whitespaces and other characters that should not definitely be part of color string + colorCandidateString = colorCandidateString[1]; + if (!colorCandidateString.match(/^[A-Fa-f0-9]+$/)) { // verify if candidate consists only of allowed HEX characters + error("Invalid HEX color code"); + } return new(tree.Color)(rgb[1]); } }, @@ -1190,8 +1195,8 @@ less.Parser = function Parser(env) { rulesetCall: function () { var name; - if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) { - return new tree.RulesetCall(name[1]); + if (input.charAt(i) === '@' && (name = $re(/^(@[\w-]+)\s*\(\s*\)\s*;/))) { + return new tree.RulesetCall(name[1]); } }, @@ -1213,12 +1218,13 @@ less.Parser = function Parser(env) { } option = option && option[1]; - + if (!elements) + error("Missing target selector for :extend()."); extend = new(tree.Extend)(new(tree.Selector)(elements), option, index); if (extendList) { extendList.push(extend); } else { extendList = [ extend ]; } } while($char(",")); - + expect(/^\)/); if (isRule) { @@ -1234,7 +1240,7 @@ less.Parser = function Parser(env) { extendRule: function() { return this.extend(true); }, - + // // Mixins // @@ -1440,7 +1446,7 @@ less.Parser = function Parser(env) { variadic = argInfo.variadic; // .mixincall("@{a}"); - // looks a bit like a mixin definition.. + // looks a bit like a mixin definition.. // also // .mixincall(@a: {rule: set;}); // so we have to be nice and restore @@ -1449,7 +1455,7 @@ less.Parser = function Parser(env) { restore(); return; } - + parsers.comments(); if ($re(/^when/)) { // Guard @@ -1556,7 +1562,7 @@ less.Parser = function Parser(env) { // combinator: function () { var c = input.charAt(i); - + if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') { i++; if (input.charAt(i) === '^') { @@ -1649,7 +1655,7 @@ less.Parser = function Parser(env) { } return block; }, - + detachedRuleset: function() { var blockRuleset = this.blockRuleset(); if (blockRuleset) { @@ -1662,7 +1668,7 @@ less.Parser = function Parser(env) { // ruleset: function () { var selectors, s, rules, debugInfo; - + save(); if (env.dumpLineNumbers) { @@ -1709,20 +1715,20 @@ less.Parser = function Parser(env) { name = this.variable() || this.ruleProperty(); if (name) { isVariable = typeof name === "string"; - + if (isVariable) { value = this.detachedRuleset(); } - + if (!value) { // prefer to try to parse first if its a variable or we are compressing // but always fallback on the other one value = !tryAnonymous && (env.compress || isVariable) ? (this.value() || this.anonymousValue()) : (this.anonymousValue() || this.value()); - + important = this.important(); - + // a name returned by this.ruleProperty() is always an array of the form: // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] // where each item is a tree.Keyword or tree.Variable @@ -1757,7 +1763,7 @@ less.Parser = function Parser(env) { // // @import "lib"; // - // Depending on our environemnt, importing is done differently: + // Depending on our environment, importing is done differently: // In the browser, it's an XHR request, in Node, it would be a // file-system operation. The function used for importing is // stored in `import`, which we pass to the Import constructor. @@ -1765,22 +1771,27 @@ less.Parser = function Parser(env) { "import": function () { var path, features, index = i; - save(); - var dir = $re(/^@import?\s+/); - var options = (dir ? this.importOptions() : null) || {}; + if (dir) { + var options = (dir ? this.importOptions() : null) || {}; - if (dir && (path = this.entities.quoted() || this.entities.url())) { - features = this.mediaFeatures(); - if ($char(';')) { - forget(); + if ((path = this.entities.quoted() || this.entities.url())) { + features = this.mediaFeatures(); + + if (!$(';')) { + i = index; + error("missing semi-colon or unrecognised media features on import"); + } features = features && new(tree.Value)(features); return new(tree.Import)(path, features, options, index, env.currentFileInfo); } + else + { + i = index; + error("malformed import statement"); + } } - - restore(); }, importOptions: function() { @@ -1903,7 +1914,7 @@ less.Parser = function Parser(env) { save(); name = $re(/^@[a-z-]+/); - + if (!name) { return; } nonVendorSpecificName = name; @@ -1976,7 +1987,7 @@ less.Parser = function Parser(env) { if (rules || (!hasBlock && value && $char(';'))) { forget(); - return new(tree.Directive)(name, value, rules, index, env.currentFileInfo, + return new(tree.Directive)(name, value, rules, index, env.currentFileInfo, env.dumpLineNumbers ? getDebugInfo(index, input, env) : null); } @@ -2033,13 +2044,17 @@ less.Parser = function Parser(env) { if (peek(/^\/[*\/]/)) { break; } + + save(); + op = $char('/') || $char('*'); - if (!op) { break; } + if (!op) { forget(); break; } a = this.operand(); - if (!a) { break; } + if (!a) { restore(); break; } + forget(); m.parensInOp = true; a.parensInOp = true; @@ -2063,7 +2078,7 @@ less.Parser = function Parser(env) { if (!a) { break; } - + m.parensInOp = true; a.parensInOp = true; operation = new(tree.Operation)(op, [operation || m, a], isSpaced); @@ -2170,7 +2185,7 @@ less.Parser = function Parser(env) { }, ruleProperty: function () { var c = current, name = [], index = [], length = 0, s, k; - + function match(re) { var a = re.exec(c); if (a) { @@ -2184,7 +2199,7 @@ less.Parser = function Parser(env) { match(/^(\*?)/); while (match(/^((?:[\w-]+)|(?:@\{[\w-]+\}))/)); // ! if ((name.length > 1) && match(/^\s*((?:\+_|\+)?)\s*:/)) { - // at last, we have the complete match now. move forward, + // at last, we have the complete match now. move forward, // convert name particles to tree objects and return: skipWhitespace(length); if (name[0] === '') { @@ -2195,7 +2210,7 @@ less.Parser = function Parser(env) { s = name[k]; name[k] = (s.charAt(0) !== '@') ? new(tree.Keyword)(s) - : new(tree.Variable)('@' + s.slice(2, -1), + : new(tree.Variable)('@' + s.slice(2, -1), index[k], env.currentFileInfo); } return name; @@ -2284,22 +2299,22 @@ tree.functions = { }, hue: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().h)); + return new(tree.Dimension)(color.toHSL().h); }, saturation: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().s * 100), '%'); + return new(tree.Dimension)(color.toHSL().s * 100, '%'); }, lightness: function (color) { - return new(tree.Dimension)(Math.round(color.toHSL().l * 100), '%'); + return new(tree.Dimension)(color.toHSL().l * 100, '%'); }, hsvhue: function(color) { - return new(tree.Dimension)(Math.round(color.toHSV().h)); + return new(tree.Dimension)(color.toHSV().h); }, hsvsaturation: function (color) { - return new(tree.Dimension)(Math.round(color.toHSV().s * 100), '%'); + return new(tree.Dimension)(color.toHSV().s * 100, '%'); }, hsvvalue: function (color) { - return new(tree.Dimension)(Math.round(color.toHSV().v * 100), '%'); + return new(tree.Dimension)(color.toHSV().v * 100, '%'); }, red: function (color) { return new(tree.Dimension)(color.rgb[0]); @@ -2314,7 +2329,7 @@ tree.functions = { return new(tree.Dimension)(color.toHSL().a); }, luma: function (color) { - return new(tree.Dimension)(Math.round(color.luma() * color.alpha * 100), '%'); + return new(tree.Dimension)(color.luma() * color.alpha * 100, '%'); }, luminance: function (color) { var luminance = @@ -2322,7 +2337,7 @@ tree.functions = { + (0.7152 * color.rgb[1] / 255) + (0.0722 * color.rgb[2] / 255); - return new(tree.Dimension)(Math.round(luminance * color.alpha * 100), '%'); + return new(tree.Dimension)(luminance * color.alpha * 100, '%'); }, saturate: function (color, amount) { // filter: saturate(3.2); @@ -2442,7 +2457,7 @@ tree.functions = { } }, e: function (str) { - return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str); + return new(tree.Anonymous)(str instanceof tree.JavaScript ? str.evaluated : str.value); }, escape: function (str) { return new(tree.Anonymous)(encodeURI(str.value).replace(/=/g, "%3D").replace(/:/g, "%3A").replace(/#/g, "%23").replace(/;/g, "%3B").replace(/\(/g, "%28").replace(/\)/g, "%29")); @@ -2523,7 +2538,7 @@ tree.functions = { continue; } currentUnified = current.unit.toString() === "" && unitClone !== undefined ? new(tree.Dimension)(current.value, unitClone).unify() : current.unify(); - unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString(); + unit = currentUnified.unit.toString() === "" && unitStatic !== undefined ? unitStatic : currentUnified.unit.toString(); unitStatic = unit !== "" && unitStatic === undefined || unit !== "" && order[0].unify().unit.toString() === "" ? unit : unitStatic; unitClone = unit !== "" && unitClone === undefined ? current.unit.toString() : unitClone; j = values[""] !== undefined && unit !== "" && unit === unitStatic ? values[""] : values[unit]; @@ -2613,12 +2628,12 @@ tree.functions = { }, shade: function(color, amount) { return this.mix(this.rgb(0, 0, 0), color, amount); - }, + }, extract: function(values, index) { - index = index.value - 1; // (1-based index) + index = index.value - 1; // (1-based index) // handle non-array values as an array of length 1 // return 'undefined' if index is invalid - return Array.isArray(values.value) + return Array.isArray(values.value) ? values.value[index] : Array(values)[index]; }, length: function(values) { @@ -2635,7 +2650,7 @@ tree.functions = { var mimetype = mimetypeNode.value; var filePath = (filePathNode && filePathNode.value); - var fs = require('fs'), + var fs = require('./fs'), path = require('path'), useBase64 = false; @@ -2804,15 +2819,15 @@ tree._mime = { var mathFunctions = { // name, unit - ceil: null, - floor: null, - sqrt: null, + ceil: null, + floor: null, + sqrt: null, abs: null, - tan: "", - sin: "", + tan: "", + sin: "", cos: "", - atan: "rad", - asin: "rad", + atan: "rad", + asin: "rad", acos: "rad" }; @@ -2837,19 +2852,19 @@ function colorBlend(mode, color1, color2) { var ab = color1.alpha, cb, // backdrop as = color2.alpha, cs, // source ar, cr, r = []; // result - + ar = as + ab * (1 - as); for (var i = 0; i < 3; i++) { cb = color1.rgb[i] / 255; cs = color2.rgb[i] / 255; cr = mode(cb, cs); if (ar) { - cr = (as * cs + ab * (cb + cr = (as * cs + ab * (cb - as * (cb + cs - cr))) / ar; } r[i] = cr * 255; } - + return new(tree.Color)(r, ar); } @@ -2859,7 +2874,7 @@ var colorBlendMode = { }, screen: function(cb, cs) { return cb + cs - cb * cs; - }, + }, overlay: function(cb, cs) { cb *= 2; return (cb <= 1) @@ -2872,7 +2887,7 @@ var colorBlendMode = { e = 1; d = (cb > 0.25) ? Math.sqrt(cb) : ((16 * cb - 12) * cb + 4) * cb; - } + } return cb - (1 - 2 * cs) * e * (d - cb); }, hardlight: function(cb, cs) { @@ -2919,25 +2934,25 @@ tree.defaultFunc = { function initFunctions() { var f, tf = tree.functions; - + // math for (f in mathFunctions) { if (mathFunctions.hasOwnProperty(f)) { tf[f] = _math.bind(null, Math[f], mathFunctions[f]); } } - + // color blending for (f in colorBlendMode) { if (colorBlendMode.hasOwnProperty(f)) { tf[f] = colorBlend.bind(null, colorBlendMode[f]); } } - + // default f = tree.defaultFunc; tf["default"] = f.eval.bind(f); - + } initFunctions(); function hsla(color) { @@ -2970,13 +2985,9 @@ function clamp(val) { } tree.fround = function(env, value) { - var p; - if (env && (env.numPrecision != null)) { - p = Math.pow(10, env.numPrecision); - return Math.round(value * p) / p; - } else { - return value; - } + var p = env && env.numPrecision; + //add "epsilon" to ensure numbers like 1.000000005 (represented as 1.000000004999....) are properly rounded... + return (p == null) ? value : Number((value + 2e-16).toFixed(p)); }; tree.functionCall = function(env, currentFileInfo) { @@ -3185,9 +3196,9 @@ tree.find = function (obj, fun) { tree.jsify = function (obj) { if (Array.isArray(obj.value) && (obj.value.length > 1)) { - return '[' + obj.value.map(function (v) { return v.toCSS(false); }).join(', ') + ']'; + return '[' + obj.value.map(function (v) { return v.toCSS(); }).join(', ') + ']'; } else { - return obj.toCSS(false); + return obj.toCSS(); } }; @@ -3270,16 +3281,17 @@ tree.Alpha.prototype = { (function (tree) { -tree.Anonymous = function (string, index, currentFileInfo, mapLines) { - this.value = string.value || string; +tree.Anonymous = function (value, index, currentFileInfo, mapLines, rulesetLike) { + this.value = value; this.index = index; this.mapLines = mapLines; this.currentFileInfo = currentFileInfo; + this.rulesetLike = (typeof rulesetLike === 'undefined')? false : rulesetLike; }; tree.Anonymous.prototype = { type: "Anonymous", eval: function () { - return new tree.Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines); + return new tree.Anonymous(this.value, this.index, this.currentFileInfo, this.mapLines, this.rulesetLike); }, compare: function (x) { if (!x.toCSS) { @@ -3295,6 +3307,9 @@ tree.Anonymous.prototype = { return left < right ? -1 : 1; }, + isRulesetLike: function() { + return this.rulesetLike; + }, genCSS: function (env, output) { output.add(this.value, this.currentFileInfo, this.index, this.mapLines); }, @@ -4045,6 +4060,9 @@ tree.Directive.prototype = { value = visitor.visit(value); } }, + isRulesetLike: function() { + return "@charset" !== this.name; + }, genCSS: function (env, output) { var value = this.value, rules = this.rules; output.add(this.name, this.currentFileInfo, this.index); @@ -4413,7 +4431,7 @@ tree.Import.prototype = { if (this.options.inline) { //todo needs to reference css file not import - var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true); + var contents = new(tree.Anonymous)(this.root, 0, {filename: this.importedFilename}, true, true); return this.features ? new(tree.Media)([contents], this.features.value) : [contents]; } else if (this.css) { var newImport = new(tree.Import)(this.evalPath(env), features, this.options, this.index); @@ -4698,7 +4716,7 @@ tree.mixin.Call.prototype = { eval: function (env) { var mixins, mixin, args, rules = [], match = false, i, m, f, isRecursive, isOneFound, rule, candidates = [], candidate, conditionResult = [], defaultFunc = tree.defaultFunc, - defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count; + defaultResult, defNone = 0, defTrue = 1, defFalse = 2, count, originalRuleset; args = this.arguments && this.arguments.map(function (a) { return { name: a.name, value: a.value.eval(env) }; @@ -4776,8 +4794,9 @@ tree.mixin.Call.prototype = { try { mixin = candidates[m].mixin; if (!(mixin instanceof tree.mixin.Definition)) { + originalRuleset = mixin.originalRuleset || mixin; mixin = new tree.mixin.Definition("", [], mixin.rules, null, false); - mixin.originalRuleset = mixins[m].originalRuleset || mixins[m]; + mixin.originalRuleset = originalRuleset; } Array.prototype.push.apply( rules, mixin.evalCall(env, args, this.important).rules); @@ -4959,7 +4978,7 @@ tree.mixin.Definition.prototype = { matchCondition: function (args, env) { if (this.condition && !this.condition.eval( new(tree.evalEnv)(env, - [this.evalParams(env, new(tree.evalEnv)(env, this.frames.concat(env.frames)), args, [])] // the parameter variables + [this.evalParams(env, new(tree.evalEnv)(env, this.frames ? this.frames.concat(env.frames) : env.frames), args, [])] // the parameter variables .concat(this.frames) // the parent namespace/mixin frames .concat(env.frames)))) { // the current environment frames return false; @@ -5133,14 +5152,22 @@ tree.Quoted.prototype = { if (!x.toCSS) { return -1; } - - var left = this.toCSS(), + + var left, right; + + // when comparing quoted strings allow the quote to differ + if (x.type === "Quoted" && !this.escaped && !x.escaped) { + left = x.value; + right = this.value; + } else { + left = this.toCSS(); right = x.toCSS(); - + } + if (left === right) { return 0; } - + return left < right ? -1 : 1; } }; @@ -5540,9 +5567,27 @@ tree.Ruleset.prototype = { tabSetStr = env.compress ? '' : Array(env.tabLevel).join(" "), sep; + function isRulesetLikeNode(rule, root) { + // if it has nested rules, then it should be treated like a ruleset + if (rule.rules) + return true; + + // medias and comments do not have nested rules, but should be treated like rulesets anyway + if ( (rule instanceof tree.Media) || (root && rule instanceof tree.Comment)) + return true; + + // some directives and anonumoust nodes are ruleset like, others are not + if ((rule instanceof tree.Directive) || (rule instanceof tree.Anonymous)) { + return rule.isRulesetLike(); + } + + //anything else is assumed to be a rule + return false; + } + for (i = 0; i < this.rules.length; i++) { rule = this.rules[i]; - if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive || (this.root && rule instanceof tree.Comment)) { + if (isRulesetLikeNode(rule, this.root)) { rulesetNodes.push(rule); } else { ruleNodes.push(rule); @@ -6128,6 +6173,8 @@ tree.Variable.prototype = { if (!this.contentsIgnoredChars) { this.contentsIgnoredChars = {}; } if (!this.files) { this.files = {}; } + if (typeof this.paths === "string") { this.paths = [this.paths]; } + if (!this.currentFileInfo) { var filename = (options && options.filename) || "input"; var entryPath = filename.replace(/[^\/\\]*$/, ""); @@ -6413,7 +6460,7 @@ tree.Variable.prototype = { var importVisitor = this, evaldImportNode, inlineCSS = importNode.options.inline; - + if (!importNode.css || inlineCSS) { try { @@ -6436,10 +6483,13 @@ tree.Variable.prototype = { } this._importer.push(importNode.getPath(), importNode.currentFileInfo, importNode.options, function (e, root, importedAtRoot, fullPath) { - if (e && !e.filename) { e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; } + if (e && !e.filename) { + e.index = importNode.index; e.filename = importNode.currentFileInfo.filename; + } - if (!env.importMultiple) { - if (importedAtRoot) { + var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector; + if (!env.importMultiple) { + if (duplicateImport) { importNode.skip = true; } else { importNode.skip = function() { @@ -6448,7 +6498,7 @@ tree.Variable.prototype = { } importVisitor.onceFileDetectionMap[fullPath] = true; return false; - }; + }; } } @@ -6463,7 +6513,6 @@ tree.Variable.prototype = { if (root) { importNode.root = root; importNode.importedFilename = fullPath; - var duplicateImport = importedAtRoot || fullPath in importVisitor.recursionDetector; if (!inlineCSS && (env.importMultiple || !duplicateImport)) { importVisitor.recursionDetector[fullPath] = true; @@ -6515,6 +6564,7 @@ tree.Variable.prototype = { }; })(require('./tree')); + (function (tree) { tree.joinSelectorVisitor = function() { this.contexts = [[]]; @@ -6624,6 +6674,9 @@ tree.Variable.prototype = { } this.charset = true; } + if (directiveNode.rules && directiveNode.rules.rules) { + this._mergeRules(directiveNode.rules.rules); + } return directiveNode; }, @@ -7345,7 +7398,7 @@ tree.Variable.prototype = { if (this._writeSourceMap) { this._writeSourceMap(sourceMapContent); } else { - sourceMapURL = "data:application/json," + encodeURIComponent(sourceMapContent); + sourceMapURL = "data:application/json;base64," + require('./encoder.js').encodeBase64(sourceMapContent); } if (sourceMapURL) { diff --git a/src/main/resources/META-INF/lessc-rhino-1.7.0.js b/src/main/resources/META-INF/lessc-rhino-1.7.4.js similarity index 99% rename from src/main/resources/META-INF/lessc-rhino-1.7.0.js rename to src/main/resources/META-INF/lessc-rhino-1.7.4.js index d8c7187..e65bc35 100644 --- a/src/main/resources/META-INF/lessc-rhino-1.7.0.js +++ b/src/main/resources/META-INF/lessc-rhino-1.7.4.js @@ -1,4 +1,4 @@ -/* LESS.js v1.7.0 RHINO | Copyright (c) 2009-2014, Alexis Sellier */ +/* Less.js v1.7.4 RHINO | Copyright (c) 2009-2014, Alexis Sellier */ /*global name:true, less, loadStyleSheet, os */ @@ -205,7 +205,7 @@ function writeFile(filename, content) { switch (arg) { case 'v': case 'version': - console.log("lessc " + less.version.join('.') + " (LESS Compiler) [JavaScript]"); + console.log("lessc " + less.version.join('.') + " (Less Compiler) [JavaScript]"); continueProcessing = false; break; case 'verbose': @@ -446,5 +446,4 @@ function writeFile(filename, content) { writeError(e, options); quit(1); } - console.log("done"); -}(arguments)); +}(arguments)); \ No newline at end of file diff --git a/src/test/java/org/lesscss/LessCompilerTest.java b/src/test/java/org/lesscss/LessCompilerTest.java index 77f67df..987e84a 100644 --- a/src/test/java/org/lesscss/LessCompilerTest.java +++ b/src/test/java/org/lesscss/LessCompilerTest.java @@ -80,7 +80,7 @@ public class LessCompilerTest { @Mock private URL lessJsFile; @Mock private URLConnection lessJsURLConnection; - private static final String lessJsURLToString = "less-rhino-1.7.0.js"; + private static final String lessJsURLToString = "less-rhino-1.7.4.js"; @Mock private InputStream lessJsInputStream; @Mock private InputStreamReader lessJsInputStreamReader; @@ -106,8 +106,8 @@ public void setUp() throws Exception { @Test public void testNewLessCompiler() throws Exception { - assertEquals(LessCompiler.class.getClassLoader().getResource("META-INF/less-rhino-1.7.0.js"), FieldUtils.readField(lessCompiler, "lessJs", true)); - assertEquals(LessCompiler.class.getClassLoader().getResource("META-INF/lessc-rhino-1.7.0.js"), FieldUtils.readField(lessCompiler, "lesscJs", true)); + assertEquals(LessCompiler.class.getClassLoader().getResource("META-INF/less-rhino-1.7.4.js"), FieldUtils.readField(lessCompiler, "lessJs", true)); + assertEquals(LessCompiler.class.getClassLoader().getResource("META-INF/lessc-rhino-1.7.4.js"), FieldUtils.readField(lessCompiler, "lesscJs", true)); assertEquals(Collections.EMPTY_LIST, FieldUtils.readField(lessCompiler, "customJs", true)); } diff --git a/src/test/java/org/lesscss/LessSourceTest.java b/src/test/java/org/lesscss/LessSourceTest.java index 8d18a4d..f1dedb2 100644 --- a/src/test/java/org/lesscss/LessSourceTest.java +++ b/src/test/java/org/lesscss/LessSourceTest.java @@ -76,7 +76,7 @@ public void testNewLessSourceWithoutImports() throws Exception { FileResource fileResource = new FileResource(sourceFile); - lessSource = new LessSource(fileResource); + lessSource = new LessSource(fileResource, Charset.defaultCharset(), null); assertEquals(sourceFile.getAbsolutePath(), lessSource.getAbsolutePath()); assertEquals("content", lessSource.getContent()); @@ -91,13 +91,13 @@ public void testNewLessSourceWithoutImports() throws Exception { @Test(expected = IllegalArgumentException.class) public void testNewLessSourceFileNull() throws Exception { - lessSource = new LessSource((Resource)null); + lessSource = new LessSource((Resource)null, Charset.defaultCharset(), null); } @Test(expected = IOException.class) public void testNewLessSourceFileNotFound() throws Exception { when(file.exists()).thenReturn(false); - lessSource = new LessSource(new FileResource(file)); + lessSource = new LessSource(new FileResource(file), Charset.defaultCharset(), null); } @Test @@ -108,7 +108,7 @@ public void testLastModifiedIncludingImportsWhenNoImportModifiedLater() throws E when(import2.getLastModifiedIncludingImports()).thenReturn(0l); when(import3.getLastModifiedIncludingImports()).thenReturn(0l); - lessSource = new LessSource(new FileResource(file)); + lessSource = new LessSource(new FileResource(file), Charset.defaultCharset(), null); FieldUtils.writeField(lessSource, "imports", imports, true); assertEquals(1l, lessSource.getLastModifiedIncludingImports()); @@ -122,7 +122,7 @@ public void testLastModifiedIncludingImportsWhenImportModifiedLater() throws Exc when(import2.getLastModifiedIncludingImports()).thenReturn(2l); when(import3.getLastModifiedIncludingImports()).thenReturn(0l); - lessSource = new LessSource(new FileResource(file)); + lessSource = new LessSource(new FileResource(file), Charset.defaultCharset(), null); FieldUtils.writeField(lessSource, "imports", imports, true); assertEquals(2l, lessSource.getLastModifiedIncludingImports()); @@ -143,7 +143,7 @@ public void testWithBadEncodingLessFile() throws Exception { private String readLessSourceWithEncoding(String encoding) throws IOException, IllegalAccessException { URL sourceUrl = getClass().getResource("/compatibility/utf8-content.less"); File sourceFile = new File(sourceUrl.getFile()); - LessSource lessSource = new LessSource(new FileResource(sourceFile), Charset.forName(encoding)); + LessSource lessSource = new LessSource(new FileResource(sourceFile), Charset.forName(encoding), null); return (String) FieldUtils.readField(lessSource, "content", true); }