Repository: lesshint/lesshint
Pull request #233 (2016-08-01 08:00:52)

Added code examples of how to set "Custom configuration path" and "Ho…
…w to use the default reporter"

Basically what i wanted to do is adding simple code examples of what i think most people would need.
There was no documentation that there was a promise returned from .checkFile() etc. and that should be reflected in a simple example i believe.

Later on: Maybe we would like to add the possibility to pass a path to .configure() and not only just the config object, so we don't need to do the fs.readFileSync() stuff i show in the first code example.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4951804..316647b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
 # Changelog
+## 2.0.3 (2016-08-09)
+* Fixed an issue where `importPath` wouldn't handle [Less import options](http://lesscss.org/features/#import-options). ([dc099ee](https://github.com/lesshint/lesshint/commit/dc099eeb8bc11db07e487b01fd3f15245eb1bccc))
+* Fixed two issues where `urlFormat` and `urlQuotes` wouldn't report multiple URLs in the same declaration. ([ec5bd4a](https://github.com/lesshint/lesshint/commit/ec5bd4a46c0c9ee4f4ddb6779bc8bbe4e762f7cf))
+* Updated `gulp-mocha` to `3.x`. ([28c80b8](https://github.com/lesshint/lesshint/commit/28c80b86a3ad42888f019c20e3be0c14fd555747))
+
 ## 2.0.2 (2016-07-31)
 * Fixed an issue where the wrong line was reported in `decimalZero`. ([e0c8e94](https://github.com/lesshint/lesshint/commit/e0c8e942fcf7bd0a741e24e3b26aa1a953400d59))
 * Removed unnecessary `gulp-debug-finder` dependency. ([967968b](https://github.com/lesshint/lesshint/commit/967968bd75a916581517cf85fb97e7725560b317))
diff --git a/README.md b/README.md
index 1515d5b..0394b94 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,20 @@ Each option is then specified by its own JSON object, for example:
 }
 ```
 
+### Setting custom configuration path
+
+```js
+var path = require('path');
+var fs = require('fs');
+var LessHint = require('lesshint');
+var lesshint = new LessHint();
+
+var lesshintConfigPath = path.resolve('my-custom-path/.lesshintrc');
+var lesshintConfig = JSON.parse(fs.readFileSync(lesshintConfigurePath, 'utf8'));
+
+lesshint.configure(lesshintConfiguration);
+```
+
 ### Inline configuration
 It's also possible to configure rules using inline comments in your `.less` files. For example:
 
@@ -117,6 +131,7 @@ It's also possible to define your own linters to add to the built-in list. These
 ]
 ```
 
+
 ## Custom linters
 Since `2.0.0` it's possible to create your own linters when needed for something team/project specfic or something that's out of scope for `lesshint`.
 
@@ -221,13 +236,12 @@ If you're writing code which utilizes `lesshint`, for example a Gulp plugin you
 Pass the name of a module or a path to the `getReporter` method like this:
 
 ```js
-var Lesshint = require('lesshint');
-
-var lesshint = new Lesshint();
-var reporter = lesshint.getReporter('my-super-awesome-reporter');
-
-var errors = lesshint.checkFile('file.less');
-reporter.report(errors);
+var defaultReporter = lesshint.getReporter(); //Or pass path to your custom reporter in getReporter
+var promise = lesshint.checkFile('my-less-file.less');
+	
+promise.then(function(result){
+  defaultReporter.report(result);
+})
 ```
 
 ### Writing your own reporter
@@ -260,3 +274,5 @@ The reporter will be passed an array of objects representing each error:
 It's then up to the reporter to do something with the errors. No `return`s or anything is needed. If running from the CLI, `lesshint` will handle the setting of correct exit codes.
 
 Take a look at the [default reporter](https://github.com/lesshint/lesshint/blob/master/lib/reporters/default.js) for more information.
+
+
diff --git a/lib/linters/import_path.js b/lib/linters/import_path.js
index 902f218..029db07 100644
--- a/lib/linters/import_path.js
+++ b/lib/linters/import_path.js
@@ -15,15 +15,21 @@ module.exports = {
 
     lint: function importPathLinter (config, node) {
         var results = [];
+        var importOptsRx = /(\s+)?\((\s+)?(reference|inline|less|css|once|multiple|optional)(\s+)?\)(\s+)?/gi;
         var value;
         var file;
         var ast;
+        var params;
 
         if (node.name !== 'import') {
             return;
         }
 
-        ast = parser(node.params).parse();
+        // temporary fix for https://github.com/lesshint/lesshint/issues/236
+        // TODO: postcss-less really needs to be fixed to make this proper.
+        params = node.params.replace(importOptsRx, '');
+
+        ast = parser(params).parse();
         value = ast.first.first;
 
         // Extract the value if it's a url() call
diff --git a/lib/linters/url_format.js b/lib/linters/url_format.js
index a70b427..5636301 100644
--- a/lib/linters/url_format.js
+++ b/lib/linters/url_format.js
@@ -18,31 +18,44 @@ module.exports = {
                 return !helpers.isAbsoluteURL(url);
             }
         };
-        var uri = ast.first.first;
-        var column;
+        var self = this;
+        var results = [];
 
         if (config.style && !style[config.style]) {
             throw new Error('Invalid setting value for urlFormat: ' + config.style);
         }
 
-        if (uri.type !== 'func' || uri.value !== 'url') {
-            return;
-        }
+        // traverse all child value-nodes
+        // https://github.com/lesshint/lesshint/issues/225
+        ast.first.nodes.forEach(function (child) {
+            var func = child;
+            var column;
+            var uri;
+
+            if (func.type !== 'func' || func.value !== 'url') {
+                return;
+            }
 
-        uri = uri.first.next();
+            // skip over the 'paren' node; the following node is the value node.
+            uri = func.first.next();
 
-        if (!style[config.style](uri.value)) {
+            if (!style[config.style](uri.value)) {
 
-            column = (decl.raws.between ? decl.raws.between.length : 0) +
-                     decl.source.start.column +
-                     decl.prop.length +
-                     uri.source.start.column - 1;
+                column = (decl.raws.between ? decl.raws.between.length : 0) +
+                         decl.source.start.column +
+                         decl.prop.length +
+                         uri.source.start.column - 1;
+
+                results.push({
+                    column: column,
+                    line: decl.source.start.line,
+                    message: util.format(self.message, uri.value, config.style)
+                });
+            }
+        });
 
-            return [{
-                column: column,
-                line: decl.source.start.line,
-                message: util.format(this.message, uri.value, config.style)
-            }];
+        if (results.length) {
+            return results;
         }
     }
 };
diff --git a/lib/linters/url_quotes.js b/lib/linters/url_quotes.js
index 8d60fe3..76cf80f 100644
--- a/lib/linters/url_quotes.js
+++ b/lib/linters/url_quotes.js
@@ -8,12 +8,11 @@ module.exports = {
     message: 'URLs should be enclosed in quotes.',
 
     lint: function urlQuotesLinter (config, node) {
+        var results = [];
         var rSingle = /^\'(.+)\'$/;
         var rDouble = /^\"(.+)\"$/;
-        var column;
-        var value;
+        var self = this;
         var ast;
-        var uri;
 
         // Nothing to check, bail
         if (!node.params && !node.value) {
@@ -21,32 +20,45 @@ module.exports = {
         }
 
         ast = parser(node.params || node.value).parse();
-        uri = ast.first.first;
 
-        if (uri.type !== 'func' || uri.value !== 'url') {
-            return;
-        }
+        // traverse all child value-nodes
+        // https://github.com/lesshint/lesshint/issues/225
+        ast.first.nodes.forEach(function (child) {
+            var func = child;
+            var uri;
+            var column;
+            var value;
+
+            if (func.type !== 'func' || func.value !== 'url') {
+                return;
+            }
+
+            // skip over the 'paren' node; the following node is the value node.
+            uri = func.first.next();
+            value = uri.value.trim();
+
+            // postcss-values-parser has a bug with url string params surrounded by
+            // spaces. so account for that here.
+            // tracking: https://github.com/lesshint/postcss-values-parser/issues/1
+            if ((uri.type === 'word' &&
+                !(rSingle.test(value) || rDouble.test(value))) &&
+                uri.type !== 'string') {
+
+                column = (node.raws.between ? node.raws.between.length : 0) +
+                         node.source.start.column +
+                         (node.prop || node.name).length +
+                         uri.source.start.column - 1;
+
+                results.push({
+                    column: column,
+                    line: node.source.start.line,
+                    message: self.message
+                });
+            }
+        });
 
-        uri = uri.first.next();
-        value = uri.value.trim();
-
-        // postcss-values-parser has a bug with url string params surrounded by
-        // spaces. so account for that here.
-        // tracking: https://github.com/lesshint/postcss-values-parser/issues/1
-        if ((uri.type === 'word' &&
-            !(rSingle.test(value) || rDouble.test(value))) &&
-            uri.type !== 'string') {
-
-            column = (node.raws.between ? node.raws.between.length : 0) +
-                     node.source.start.column +
-                     (node.prop || node.name).length +
-                     uri.source.start.column - 1;
-
-            return [{
-                column: column,
-                line: node.source.start.line,
-                message: this.message
-            }];
+        if (results.length) {
+            return results;
         }
     }
 };
diff --git a/package.json b/package.json
index 1921612..9179ac6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "lesshint",
   "description": "A tool to aid you in writing clean and consistent Less.",
-  "version": "2.0.2",
+  "version": "2.0.3",
   "main": "./lib/lesshint.js",
   "author": {
     "name": "Jonathan Wilsson",
@@ -37,7 +37,7 @@
     "gulp-coveralls": "^0.1.4",
     "gulp-eslint": "^2.0.0",
     "gulp-istanbul": "^0.10.2",
-    "gulp-mocha": "^2.1.3",
+    "gulp-mocha": "^3.0.0",
     "rewire": "^2.3.3",
     "rimraf": "^2.4.2",
     "sinon": "^1.14.1"


Test patch:
diff --git a/test/specs/linters/import_path.js b/test/specs/linters/import_path.js
index 5dcf6ae..7f489d1 100644
--- a/test/specs/linters/import_path.js
+++ b/test/specs/linters/import_path.js
@@ -300,4 +300,89 @@ describe('lesshint', function () {
             });
         });
     });
+
+    describe('#importPath() with Import Option', function () {
+
+        it('should allow filename without extension when "filenameExtension" is "false"', function () {
+            var source = '@import (css) "foo";';
+            var options = {
+                filenameExtension: false,
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should allow filename with .css extension when "filenameExtension" is "false"', function () {
+            var source = '@import (inline) "foo.css";';
+            var options = {
+                filenameExtension: false,
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should not allow filename with extension when "filenameExtension" is "false"', function () {
+            var source = '@import (inline) "foo.less";';
+            var expected = [{
+                column: 9,
+                line: 1,
+                message: 'Imported file, "foo.less" should not include the file extension.'
+            }];
+
+            var options = {
+                filenameExtension: false,
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
+        it('should allow filename with extension when "filenameExtension" is "true"', function () {
+            var source = '@import (inline) "foo.less";';
+            var options = {
+                filenameExtension: true,
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should not allow filename without extension when "filenameExtension" is "true"', function () {
+            var source = '@import (inline) "foo";';
+            var expected = [{
+                column: 9,
+                line: 1,
+                message: 'Imported file, "foo" should include the file extension.'
+            }];
+
+            var options = {
+                filenameExtension: true,
+                exclude: []
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+    });
 });
diff --git a/test/specs/linters/url_format.js b/test/specs/linters/url_format.js
index b628f81..5499e29 100644
--- a/test/specs/linters/url_format.js
+++ b/test/specs/linters/url_format.js
@@ -26,6 +26,19 @@ describe('lesshint', function () {
             });
         });
 
+        it('should allow multiple relative URLs when "style" is "relative"', function () {
+            var source = '.foo { background-image: url(img/image.jpg), url(img/foo.jpg); }';
+            var options = {
+                style: 'relative'
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
         it('should not allow absolute URLs when "style" is "relative"', function () {
             var source = '.foo { background-image: url(http://example.com/img/image.jpg); }';
             var expected = [{
@@ -45,6 +58,32 @@ describe('lesshint', function () {
             });
         });
 
+        it('should not allow multiple absolute URLs when "style" is "relative"', function () {
+            var source = '.foo { background-image: url(http://example.com/img/image.jpg), url(http://example.com/img/foo.jpg); }';
+            var expected = [
+                {
+                    column: 30,
+                    line: 1,
+                    message: 'URL "http://example.com/img/image.jpg" should be relative.'
+                },
+                {
+                    column: 69,
+                    line: 1,
+                    message: 'URL "http://example.com/img/foo.jpg" should be relative.'
+                }
+            ];
+
+            var options = {
+                style: 'relative'
+            };
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint(options, ast.root.first.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
         it('should allow absolute URLs when "style" is "absolute"', function () {
             var source = '.foo { background-image: url(http://example.com/img/image.jpg); }';
             var options = {
diff --git a/test/specs/linters/url_quotes.js b/test/specs/linters/url_quotes.js
index 7e7fa5c..f9de57a 100644
--- a/test/specs/linters/url_quotes.js
+++ b/test/specs/linters/url_quotes.js
@@ -51,6 +51,16 @@ describe('lesshint', function () {
             });
         });
 
+        it('should allow single quotes with multiple urls', function () {
+            var source = '.foo { background-image: url(\'img/image.jpg\'), url(\'img/foo.jpg\'); }';
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint({}, ast.root.first.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
         it('should allow double quotes', function () {
             var source = '.foo { background-image: url("img/image.jpg"); }';
 
@@ -61,6 +71,16 @@ describe('lesshint', function () {
             });
         });
 
+        it('should allow double quotes with multiple urls', function () {
+            var source = '.foo { background-image: url("img/image.jpg"), url("img/foo.jpg"); }';
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint({}, ast.root.first.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
         it('should not allow missing quotes', function () {
             var source = '.foo { background-image: url(img/image.jpg); }';
             var expected = [{
@@ -76,6 +96,28 @@ describe('lesshint', function () {
             });
         });
 
+        it('should not allow missing quotes with multiple urls', function () {
+            var source = '.foo { background-image: url(img/image.jpg), url(img/foo.jpg); }';
+            var expected = [
+                {
+                    column: 30,
+                    line: 1,
+                    message: 'URLs should be enclosed in quotes.'
+                },
+                {
+                    column: 50,
+                    line: 1,
+                    message: 'URLs should be enclosed in quotes.'
+                }
+            ];
+
+            return spec.parse(source, function (ast) {
+                var result = spec.linter.lint({}, ast.root.first.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
         it('should not allow missing quotes in imports', function () {
             var source = '@import url(http://example.com)';
             var expected = [{