Repository: lesshint/lesshint
Pull request #337 (2017-02-04 17:19:05)

Fix a bunch of line reporting bugs
<!--
If you're submitting a PR for a new feature, please open an issue about it first so we can discuss it.
If you're submitting a PR for something else, for example a documentation fix, go right ahead!
-->

**Which issue, if any, does this resolve?**
#308 (and other bugs without issues) 

**Is there anything in this PR that needs extra explaining or should something specific be focused on?**
Nothing in particular.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98c0e59..aa1f73c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,26 @@
 # Changelog
+## 3.0.1 (2017-02-10)
+* Lockdown `postcss-values-parser` to `~` range. ([7e03dbc](https://github.com/lesshint/lesshint/commit/7e03dbcd7db5a61498bc9f3bad1c23f78067877c))
+
+## 3.0.0 (2017-02-08)
+* **Breaking** Dropped support for Node < 4. ([7a142ee](https://github.com/lesshint/lesshint/commit/7a142eec6a51c124270a4baa865cba7d8a700a7d))
+* **Breaking** The public Node API now exposes multiple classes instead of just the `Lesshint` class. ([5013aae](https://github.com/lesshint/lesshint/commit/5013aaee0d551923f3dba63d3ee832a0d4bee543))
+* **Breaking** All `Promise`s returned from `Lesshint` methods are now native ones instead of `Vow`. ([afda593](https://github.com/lesshint/lesshint/commit/afda593da9c5a3b0e27127757c29ccd75c5dda8d))
+* **Breaking** Whenever an inaccessible file is encountered, `lesshint` will now throw an error. ([63d258](https://github.com/lesshint/lesshint/commit/63d25894fef5bf2c1c9e1530c7e12482fd2ceb32))
+* Added a `Lesshint.getConfig()` method to search for config files using `lesshint`'s logic. ([7850cbf](https://github.com/lesshint/lesshint/commit/7850cbfaea6cec7410d48ec8674cc96f322ad992))
+* A complete ES6 rewrite was performed. ([879160f](https://github.com/lesshint/lesshint/commit/879160fedccdf14ff86affb6700f44c6dee10354))
+* A new `Runner` class has been added, exposing the full CLI API to Node consumers. ([7c9e76a](https://github.com/lesshint/lesshint/commit/7c9e76abd18c21dcb2e7a9f668e0cbb78c0f9443))
+* `excludedFiles` will now be checked on all paths before `lesshint` tries to access them. ([7bc3ab2](https://github.com/lesshint/lesshint/commit/7bc3ab20d60ea1cc15473db3b3b1eca157e15d53))
+* The `fullPath` property in linting results will now always be a full, absolute path. ([dab57d4](https://github.com/lesshint/lesshint/commit/dab57d4c14acab3663f985aa1ca91197115d37ef))
+* Relative custom linter paths will now be resolved against `process.cwd()`. ([6f2b5d9](https://github.com/lesshint/lesshint/commit/6f2b5d97917609a29b451dca36152c2c56b14cd5))
+* The default reporter can now be explicitly required. ([587c215](https://github.com/lesshint/lesshint/commit/587c2151e1cc1749b0adad60be5a903ec76236a9))
+* Replaced use of the `exit` module with `process.exitCode`. ([004ae71](https://github.com/lesshint/lesshint/commit/004ae715dd22620501126a046863192ef3b932c9))
+* Passing `-1` to the `--max-warnings` flag is no longer supported. Simply omit the flag for the same behavior. ([f8473d1](https://github.com/lesshint/lesshint/commit/f8473d18a7db6815ec1c4738624ed207bdf5cd6a))
+* `postcss-values-parser` was updated to `1.1.0`. ([a91aabb](https://github.com/lesshint/lesshint/commit/a91aabb786fd939f3cb8f5ab72cb32c2e39a1b30))
+* A huge documentation refresh. ([3addaf0](https://github.com/lesshint/lesshint/commit/3addaf02c7503438802443a9af225e76c56fd3f1))
+* Added the `engines` field to `package.json`. ([a62da86](https://github.com/lesshint/lesshint/commit/a62da86c83a0ad87980b14b4ecb2a3566b43507d))
+* Added tests for the `lesshint` binary. ([2a16a17](https://github.com/lesshint/lesshint/commit/2a16a171e3e65578c35801b96e2998a8a19f2704))
+
 ## 2.4.0 (2016-12-14)
 * Added a `allowSingleLineRules` option to `singleLinePerProperty`. ([52879a7](https://github.com/lesshint/lesshint/commit/52879a7acfe4bba6d532a8a231b338616a5ce42c))
 * Added a `position` field to results. ([6292387](https://github.com/lesshint/lesshint/commit/62923873b287ed1a347532eac233e8347a5213af))
diff --git a/README.md b/README.md
index 0db1a5e..60bf779 100644
--- a/README.md
+++ b/README.md
@@ -3,196 +3,29 @@
 [![Build Status](https://travis-ci.org/lesshint/lesshint.svg?branch=master)](https://travis-ci.org/lesshint/lesshint)
 [![Build status](https://ci.appveyor.com/api/projects/status/d1u4477uxtv6dygk/branch/master?svg=true)](https://ci.appveyor.com/project/lesshint/lesshint/branch/master)
 [![Coverage Status](https://coveralls.io/repos/lesshint/lesshint/badge.svg?branch=master)](https://coveralls.io/r/lesshint/lesshint?branch=master)
-[![Dependency Status](https://david-dm.org/lesshint/lesshint.svg?theme=shields.io&style=flat)](https://david-dm.org/lesshint/lesshint)
-[![devDependency Status](https://david-dm.org/lesshint/lesshint/dev-status.svg?theme=shields.io&style=flat)](https://david-dm.org/lesshint/lesshint#info=devDependencies)
 
 `lesshint` is a tool to aid you in writing clean and consistent [Less](http://lesscss.org/).
 
-* [Requirements](#requirements)
 * [Installation](#installation)
 * [Configuration](#configuration)
-* [Custom linters](#custom-linters)
 * [CLI usage](#cli-usage)
+* [Linters](#linters)
 * [Reporters](#reporters)
-
-## Requirements
-[Node.js](https://nodejs.org/) 0.12 (or later) or [io.js](https://iojs.org/) 1.0 (or later).
+* [Developer resources](#developer-resources)
 
 ## Installation
-Run the following command from the command line (add `-g` to install globally):
+_[Node.js](https://nodejs.org/) 4 (or later) is required._
+
+Run the following from the command line to install `lesshint` (add `-g` to install globally):
 
 ```
 npm install lesshint
 ```
 
 ## Configuration
-`lesshint` is customizable and we highly recommend you to look at the [available options](lib/linters/README.md) to tailor it to your needs.
-
-Start by creating a `.lesshintrc` file in your project root and add your settings to it. It will be automatically loaded and merged with the default values.
-
-Each option is then specified by its own JSON object, for example:
-
-```js
-{
-    "fileExtensions": [".less", ".css"],
-
-    "excludedFiles": ["vendor.less"],
-
-    "spaceAfterPropertyColon": {
-        "enabled": true,
-        "style": "one_space" // Comments are allowed
-    },
-
-    "emptyRule": true // If there are no options for a rule, you can simply enable it by setting it to true
-}
-```
-
-### 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:
-
-```less
-// lesshint spaceBeforeBrace: false
-.foo{ // This line won't be reported
-    color: red;
-}
-```
-
-It's also possible to disable rules on a single line using a trailing comment:
-
-```less
-.bar {
-    color:red; // lesshint spaceAfterPropertyColon: false
-}
-```
-
-If you wish to enable a rule that's disabled in your `.lesshintrc` you need to specify any other options too. But rules without options can be enabled by just setting it to `true`. For example:
-
-`.lesshintrc`:
-```json
-{
-    "emptyRule": false,
-    "spaceAfterPropertyName": false
-}
-```
-
-`file.less`
-```less
-// lesshint spaceAfterPropertyName: { enabled: true, style: "one_space" }, emptyRule: true
-.foo {
-    color : red; // Won't report the extra space before ":"
-}
-
-.bar {
-
-}
-```
-
-The options format is a less strict form of JSON. Keys doesn't need any quotes but string values need double quotes.
-
-### Options
-
-#### fileExtensions
-Array of file extensions to check. Either an array of extensions or `"*"` to allow all files. For example:
-
-```js
-"fileExtensions": [".less", ".css"] // Allow ".less" and ".css" files. Can be passed with or without a dot.
-
-"fileExtensions": "*" // Allow all files
-```
-
-#### excludedFiles
-Array of [minimatch glob patterns](https://github.com/isaacs/minimatch) or a file to exclude. For example:
-
-```js
-"excludedFiles": ["vendor/*.less"] // Ignore all files in "vendor/"
+For information on how to configure `lesshint` and other available options, see the [user guide](/docs/user-guide/configuration.md).
 
-"excludedFiles": ["vendor.less"] // Ignore a file named "vendor.less"
-```
-
-#### linters
-It's also possible to define your own linters to add to the built-in list. These can be the linters themselves or require paths relative to your current working directory. For example:
-
-```js
-"linters": [
-    "/path/to/sample-linter.js"
-],
-
-"sample": {
-    "enabled": true
-},
-
-"otherSample": {
-    "enabled": true
-}
-```
-
-
-## 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`.
-
-To work properly, all linters are required to expose a few things.
-* `name` - The name of the linter. While we don't enforce namespaces, we recommend it to prevent naming collisions.
-* `nodeTypes` - An array of [PostCSS node types](http://api.postcss.org/postcss.html) that the linter wants to check.
-* `lint` - The main lint method which will be called with the following arguments.
-    * `config` - The config object for this linter.
-    * `node` - The current node to lint.
-
-If the linter doesn't find any errors or doesn't need/want to check the passed node for some reason it should return `undefined`. If it finds something it should return an array of result objects which looks like this:
-
-```js
-{
-    column: 5,
-    file: 'file.less',
-    fullPath: 'path/to/file.less',
-    line: 1,
-    linter: 'spaceBeforeBrace',
-    message: 'Opening curly brace should be preceded by one space.',
-    severity: 'warning',
-    source: '.foo{'
-}
-```
-
-If a linter doesn't set a value for a property, `lesshint` will set it. Most of the time, you'll only want to set `column`, `line`, and `message` while leaving the rest to `lesshint`.
-
-A simple linter example:
-```js
-module.exports = {
-    name: 'my-namespace/my-super-awesome-linter',
-    nodeTypes: ['decl'],
-    lint: function (config, node) {
-        var results = [];
-
-        if (true) { // Nothing to lint, return early
-            return;
-        }
-
-        // Check some things...
-
-        // Return the results
-        return results;
-    }
-};
-```
-
-We highly recommend the following resources which are all included with `lesshint`.
-* [postcss](http://api.postcss.org/postcss.html) - Main PostCSS docs.
-* [postcss-less](https://github.com/webschik/postcss-less) - PostCSS Less plugin docs.
-* [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser) - PostCSS plugin for working with selectors.
-* [postcss-values-parser](https://github.com/lesshint/postcss-values-parser) - PostCSS plugin for working with values.
+Since `lesshint` is highly customizable we recommend you to also take a look at the [available rule options](/lib/linters/README.md) to tailor it to your needs.
 
 ## CLI usage
 Run `lesshint` from the command-line by passing one or more files/directories to recursively scan.
@@ -201,14 +34,14 @@ Run `lesshint` from the command-line by passing one or more files/directories to
 lesshint src/less/ lib/style.less
 ```
 
-Available Flags       | Description
-----------------------|----------------------------------------------
+Available Flags         | Description
+----------------------|---------------------
 `-c`/`--config`       | Specify the configuration file to use (will be merged with defaults).
 `-e`/`--exclude`      | A [minimatch glob pattern](https://github.com/isaacs/minimatch) or a file to exclude from being linted.
-`-l`/`--linters`      | Require paths of custom linters to add to the built-in list.
-`-r`/`--reporter`     | The reporter to use. See "Reporters" below for possible values.
-`-V`/`--version`      | Show version.
-`-x`/`--max-warnings` | Number of warnings to allow before exiting with a non-zero code. Passing `-1` will allow any number of warnings to pass.
+`-l`/`--linters`      | Paths to custom linters to add to the built-in list. See "Linters" below for more information.
+`-r`/`--reporter`     | The reporter to use. See "Reporters" below for more information.
+`-V`/`--version`      | Show the version.
+`-x`/`--max-warnings` | Number of warnings to allow before exiting with a non-zero code.
 
 ### Exit status codes
 Depending on the linter results and options supplied, the exit status code returned by the CLI will differ.
@@ -224,64 +57,17 @@ Exit status code   | Description
 
 These codes were chosen with regards to the [preferable exit codes](http://www.gsp.com/cgi-bin/man.cgi?section=3&topic=sysexits).
 
-## Reporters
-Reporters can be used to perform actions with the lint results, for example printing something to the terminal or generate custom reports.
-
-### The reporter loading steps
-
-1. If nothing is passed, a simple, default reporter will be used. This will just print all the warnings/errors found.
-2. Pass the name of a Node module. If `lesshint` is installed globally only globally installed reporters are available (the normal Node module loading rules apply).
-3. Pass a absolute or relative path to a custom reporter anywhere on the disk. Relative paths will be resolved against [`process.cwd()`](https://nodejs.org/api/process.html#process_process_cwd).
-
-These steps always apply, no matter whether you're using the CLI or calling `lesshint` from code.
-
-### Using reporters from the CLI
-```bash
-lesshint --reporter my-super-awesome-reporter file.less
-lesshint --reporter /path/to/my/super/awesome/reporter.js file.less
-```
-
-### Using reporters from code
-If you're writing code which utilizes `lesshint`, for example a Gulp plugin you can use the `getReporter` method on the `lesshint` object to load a reporter using the same logic as `lesshint` does.
-
-Pass the name of a module or a path to the `getReporter` method like this:
+## Linters
+In addition to the linters included with `lesshint`, it's also possible to include custom ones. For example to check something team or project specific.
 
-```js
-var defaultReporter = lesshint.getReporter(); //Or pass path to your custom reporter in getReporter
-var promise = lesshint.checkFile('my-less-file.less');
+For more information on using custom linters, see the [user guide](/docs/user-guide/linters.md).
 
-promise.then(function(result){
-  defaultReporter.report(result);
-})
-```
-
-### Writing your own reporter
-In its simplest form, a reporter is just a function accepting some input. The most basic reporter possible:
-
-```js
-module.exports = {
-    name: 'my-super-awesome-reporter', // Not required, but recommended
-    report: function (errors) {
-        console.log(errors.length ? 'Errors found' : 'No errors');
-    }
-};
-```
-
-The reporter will be passed an array of objects representing each error:
-
-```js
-{
-    column: 5,
-    file: 'file.less',
-    fullPath: 'path/to/file.less',
-    line: 1,
-    linter: 'spaceBeforeBrace',
-    message: 'Opening curly brace should be preceded by one space.',
-    severity: 'warning',
-    source: '.foo{'
-}
-```
+## Reporters
+Reporters are small modules that can be used to perform actions with the lint results, for example printing something to the terminal or generate custom reports.
 
-It's then up to the reporter to do something with the errors. No `return`s or anything are needed. If running from the CLI, `lesshint` will handle the setting of correct exit codes.
+For more information on using reporters, see the [user guide](/docs/user-guide/reporters.md).
 
-Take a look at the [default reporter](https://github.com/lesshint/lesshint/blob/master/lib/reporters/default.js) for more information.
+## Developer resources
+* [Node API](/docs/developer-guide/API.md) - `lesshint`'s public Node API reference.
+* [Developing linters](/docs/developer-guide/linters.md) - Guide for hacking on linters.
+* [Developing reporters](/docs/developer-guide/reporters.md) - Guide for hacking on reporters.
diff --git a/docs/developer-guide/API.md b/docs/developer-guide/API.md
new file mode 100644
index 0000000..f4bab60
--- /dev/null
+++ b/docs/developer-guide/API.md
@@ -0,0 +1,6 @@
+# Node API
+
+* [Lesshint class](/docs/developer-guide/api/lesshint.md)
+* [Runner class](/docs/developer-guide/api/runner.md)
+
+_Note: Anything not documented here should be considered a internal API which may change at any time._
diff --git a/docs/developer-guide/api/lesshint.md b/docs/developer-guide/api/lesshint.md
new file mode 100644
index 0000000..9db4d91
--- /dev/null
+++ b/docs/developer-guide/api/lesshint.md
@@ -0,0 +1,104 @@
+# Lesshint class
+
+The `Lesshint` class is the core of `lesshint`, from here all operations can be performed. To use it, start by initalizing a `lesshint` instance.
+
+```js
+const Lesshint = require('lesshint').Lesshint;
+
+const lesshint = new Lesshint();
+
+lesshint.configure();
+
+// Do your thing using the methods below
+```
+
+## `Lesshint.checkDirectory(checkPath)`
+Check a directory recursively. Will respect `fileExtensions` and `excludedFiles` options.
+
+A `Promise` will be returned.
+
+```js
+const result = lesshint.checkDirectory('/path/to/my/directory');
+
+result.then((results) => {
+
+});
+```
+
+## `Lesshint.checkFile(checkPath)`
+Check a single file asynchronously. Will not check `fileExtensions` and `excludedFiles` options.
+
+A `Promise` will be returned.
+
+```js
+const result = lesshint.checkFile('/path/to/my/file.less');
+
+result.then((results) => {
+
+});
+```
+
+## `Lesshint.checkPath(checkPath)`
+Check a path asynchronously. If a file is passed it will check that, if a directory is passed it will check that recursively. Will respect `fileExtensions` and `excludedFiles` options.
+
+A `Promise` will be returned.
+
+```js
+const result = lesshint.checkPath('/path/to/my/directory');
+
+result.then((results) => {
+
+});
+```
+
+## `Lesshint.checkString(input, checkPath)`
+Check a Less string synchronously and return an array of linter results.
+
+The `checkPath` argument can be used to include a file name in lint results.
+
+If a error occurs which isn't a Less parse error, an exception will be thrown.
+
+```js
+try {
+    const results = lesshint.checkString('<my less code>', '/path/to/file');
+} catch (e) {
+    console.error('Something bad happened.');
+}
+```
+
+## `Lesshint.configure(config)`
+Setup the options to use. This method must always be called before doing anything else, otherwise no options will be specified. Not even defaults.
+
+The `config` argument is optional, but if a object is passed it'll be merged with the defaults.
+
+```js
+const config = {
+    emptyRule: false
+};
+
+lesshint.configure(config);
+```
+
+## `Lesshint.getConfig(path)`
+Load a config file using `lesshint`'s logic.
+
+If `path` is a file, it'll be loaded and its contents returned. If `path` is a directory, it will start looking for a `.lesshintrc` file in that directory, traversing up the directory structure until it finds one. If no config file is found, `undefined` will be returned.
+
+```js
+const config = lesshint.getConfig('/path/to/my/config.json');
+```
+
+
+## `Lesshint.getReporter(reporter)`
+Load a reporter using `lesshint`'s logic.
+
+The `reporter` argument can be one of the following.
+* The name of a module. The normal Node.js loading rules for global/local modules apply.
+* Path to a reporter. Relative paths will be resolved against [`process.cwd()`](https://nodejs.org/api/process.html#process_process_cwd).
+* A already initialized reporter object which will just be returned again.
+
+If nothing is passed, the default reporter will be returned. If the reporter loading fails for some reason, `false` will be returned.
+
+```js
+const reporter = lesshint.getReporter('/path/to/my/reporter.js');
+```
diff --git a/docs/developer-guide/api/runner.md b/docs/developer-guide/api/runner.md
new file mode 100644
index 0000000..59cf71a
--- /dev/null
+++ b/docs/developer-guide/api/runner.md
@@ -0,0 +1,37 @@
+# Runner class
+
+The `Runner` class can be used to access the backbone of the CLI. To use it, start by initializing a `runner` instance.
+
+```js
+const Runner = require('lesshint').Runner;
+
+// Any CLI option is valid
+const options = {
+    maxWarnings: 10, // Dashes in CLI flags are converted to camel case
+    paths: ['file.less'] // An array of path(s) to check
+};
+
+const runner = new Runner(options);
+
+const result = runner.run();
+```
+
+## `Runner.constructor()`
+Set the options for a `Runner` instance.
+
+```js
+const runner = new Runner(options);
+```
+
+## `Runner.run()`
+Run `lesshint` as if used through the CLI. All rule checks will be performed and reporters called.
+
+A `Promise` will be returned. On success, it'll be resolved with a plain object. On failure, it'll be rejected with a `Runner` error object with a `status` property corresponding to a [CLI exit status code](/README.md#exit-status-codes).
+
+```js
+const result = runner.run();
+
+result.then((output) => {
+
+});
+```
diff --git a/docs/developer-guide/linters.md b/docs/developer-guide/linters.md
new file mode 100644
index 0000000..e816b85
--- /dev/null
+++ b/docs/developer-guide/linters.md
@@ -0,0 +1,54 @@
+# Developing linters
+If you wish to create your own linter or work on one of the core ones, this is the place for you!
+
+First of all, in order to work properly, all linters are required to expose a few things.
+* `name` - The name of the linter. While we don't enforce namespaces, we recommend it when creating custom linters, to prevent naming collisions.
+* `nodeTypes` - An array of [PostCSS node types](http://api.postcss.org/postcss.html) that the linter wants to check.
+* `lint` - The main lint method which will be called with the following arguments.
+    * `config` - The config object for this linter.
+    * `node` - The current node to lint.
+
+If the linter doesn't find any errors or doesn't need/want to check the passed node for some reason it should return `undefined`. The linter doesn't need to check whether is's enabled or not, this will be checked beforehand by `lesshint`. If it's disabled, it'll never be called.
+
+If the linter finds something it should return an array of result objects which looks like this:
+
+```js
+{
+    column: 5,
+    file: 'file.less',
+    fullPath: '/path/to/file.less',
+    line: 1,
+    linter: 'spaceBeforeBrace',
+    message: 'Opening curly brace should be preceded by one space.',
+    position: 4,
+    severity: 'warning',
+    source: '.foo{'
+}
+```
+
+If a linter doesn't set a value for a property, `lesshint` will set it. Most of the time, you'll only want to set `column`, `line`, and `message` while leaving the rest to `lesshint`.
+
+A simple linter example:
+```js
+module.exports = {
+    name: 'my-namespace/my-super-awesome-linter',
+    nodeTypes: ['decl'],
+    lint: function (config, node) {
+        const results = [];
+
+        if (true) { // Nothing to lint, return early
+            return;
+        }
+
+        // Check some things...
+
+        // Return the results
+        return results;
+    }
+```
+
+When working with linters, we highly recommend the following resources which are all included with `lesshint`.
+* [postcss](http://api.postcss.org/postcss.html) - Main PostCSS docs.
+* [postcss-less](https://github.com/webschik/postcss-less) - PostCSS Less plugin docs.
+* [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser) - PostCSS plugin for working with selectors.
+* [postcss-values-parser](https://github.com/lesshint/postcss-values-parser) - PostCSS plugin for working with values.
diff --git a/docs/developer-guide/reporters.md b/docs/developer-guide/reporters.md
new file mode 100644
index 0000000..4c3f415
--- /dev/null
+++ b/docs/developer-guide/reporters.md
@@ -0,0 +1,51 @@
+# Developing reporters
+If you wish to create your own reporter or work on one of the core ones, this is the place for you!
+
+First of all, in order to work properly, all reporters are required to expose a few things.
+* `name` - The name of the reporter. While we don't enforce namespaces, we recommend it when creating custom reporters, to prevent naming collisions.
+* `report` - The main report method which will be called with the following arguments.
+    * `results` - An array of all linting results. See a full example below.
+
+Example results array passed to reporters:
+```js
+[{
+    column: 5,
+    file: 'file.less',
+    fullPath: '/path/to/file.less',
+    line: 1,
+    linter: 'spaceBeforeBrace',
+    message: 'Opening curly brace should be preceded by one space.',
+    position: 4,
+    severity: 'warning',
+    source: '.foo{'
+}]
+```
+
+Now, in its simplest form, a reporter is just a function accepting some input. The most basic reporter possible:
+```js
+module.exports = {
+    name: 'my-super-awesome-reporter',
+    report: function (results) {
+        console.log(results.length ? 'Errors found' : 'No errors');
+    }
+};
+```
+
+It's now up to the reporter to do something with the errors. No `return`s or anything are 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.
+
+## Using reporters from code
+If you're writing code which utilizes `lesshint`, for example a Gulp plugin, you can use the `getReporter` method on the `lesshint` object to load a reporter using the same logic as `lesshint` does.
+
+Pass the name of a module, a path, or a already initialized reporter object to the `getReporter` method like this:
+
+```js
+// Passing nothing will load the default reporter but a module name, path, or reporter object could be passed instead.
+const defaultReporter = lesshint.getReporter();
+const result = lesshint.checkFile('my-less-file.less');
+
+result.then((result) => {
+    defaultReporter.report(result);
+});
+```
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
new file mode 100644
index 0000000..d003d2e
--- /dev/null
+++ b/docs/user-guide/configuration.md
@@ -0,0 +1,102 @@
+# Configuring lesshint
+Start by creating a `.lesshintrc` file in your project root and add your settings to it. It will be automatically loaded and merged with the default values. If no `.lesshintrc` file is found in the current working directory, `lesshint` will traverse up the directory structure looking for one, stopping once it finds one or reaches the root directory.
+
+In the `.lesshintrc` file, each option is specified by its own JSON object, for example:
+
+```js
+{
+    "fileExtensions": [".less", ".css"],
+
+    "excludedFiles": ["vendor.less"],
+
+    "spaceAfterPropertyColon": {
+        "enabled": true,
+        "style": "one_space" // Comments are allowed
+    },
+
+    "emptyRule": true, // If there are no options for a rule, you can simply enable it by setting it to true
+
+    "importantRule": false // To disable a rule completely, set it to false
+}
+```
+
+Now, take a look at the available [linter options](/lib/linters/README.md).
+
+## Inline configuration
+It's possible to configure rules using inline comments in your `.less` files. For example:
+
+```less
+// lesshint spaceBeforeBrace: false
+.foo{ // This line won't be reported
+    color: red;
+}
+```
+
+It's also possible to disable rules on a single line using a trailing comment:
+
+```less
+.bar {
+    color:red; // lesshint spaceAfterPropertyColon: false
+}
+```
+
+If you wish to enable a rule that's disabled in your `.lesshintrc` you need to specify all the other options for it too. For example:
+
+`.lesshintrc`:
+```json
+{
+    "emptyRule": false,
+    "spaceAfterPropertyName": false
+}
+```
+
+`file.less`
+```less
+// lesshint spaceAfterPropertyName: { enabled: true, style: "one_space" }, emptyRule: true
+.foo {
+    color : red; // Won't report the extra space before ":"
+}
+
+.bar { // But this empty rule will be reported
+
+}
+```
+
+The inline configuration options format is a less strict form of JSON. Keys doesn't need any quotes but string values need double quotes.
+
+## Other available options
+
+### fileExtensions
+Array of file extensions to check. Either an array of extensions or `"*"` to allow all files. For example:
+
+```js
+"fileExtensions": [".less", ".css"] // Allow ".less" and ".css" files. Can be passed with or without a dot.
+
+"fileExtensions": "*" // Allow all files
+```
+
+### excludedFiles
+Array of [minimatch glob patterns](https://github.com/isaacs/minimatch) or a file to exclude. For example:
+
+```js
+"excludedFiles": ["vendor/*.less"] // Ignore all files in "vendor/"
+
+"excludedFiles": ["vendor.less"] // Ignore a file named "vendor.less"
+```
+
+### linters
+It's also possible to define your own linters to add to the built-in list. These can be the linters themselves or require paths relative to your current working directory. Note that any custom linters will need to be enabled before they can be used. For example:
+
+```js
+"linters": [
+    "/path/to/sample-linter.js"
+]
+
+"sample": {
+    "enabled": true
+},
+
+"otherSample": {
+    "enabled": true
+}
+```
diff --git a/docs/user-guide/linters.md b/docs/user-guide/linters.md
new file mode 100644
index 0000000..1a249d0
--- /dev/null
+++ b/docs/user-guide/linters.md
@@ -0,0 +1,17 @@
+# Using linters
+In addition to the linters provided by `lesshint`, it's also possible to include custom ones. These can either be npm modules or paths to local linters on disk.
+
+## Using reporters from the CLI
+```bash
+lesshint --linters my-super-awesome-linter file.less
+lesshint --linters /path/to/my/super/awesome/linter.js file.less
+```
+
+Please note that each custom linter will need to be enabled in your `.lesshintrc` file before they can be used.
+
+```js
+"awesomeLinter": {
+    "enabled": true,
+    "someOption": false
+}
+```
diff --git a/docs/user-guide/reporters.md b/docs/user-guide/reporters.md
new file mode 100644
index 0000000..cb5c825
--- /dev/null
+++ b/docs/user-guide/reporters.md
@@ -0,0 +1,15 @@
+# Using reporters
+Reporters are small modules that can be used to perform actions with the lint results, for example printing something to the terminal or generate custom reports.
+
+### The reporter loading steps
+1. If nothing is passed, a simple, default reporter will be used. This will just print all the warnings/errors found.
+2. Pass the name of a Node module. If `lesshint` is installed globally only globally installed reporters are available (the normal Node module loading rules apply).
+3. Pass a absolute or relative path to a custom reporter anywhere on the disk. Relative paths will be resolved against [`process.cwd()`](https://nodejs.org/api/process.html#process_process_cwd).
+
+These steps always apply, no matter whether you're using the CLI or calling `lesshint` from code.
+
+## Using reporters from the CLI
+```bash
+lesshint --reporter my-super-awesome-reporter file.less
+lesshint --reporter /path/to/my/super/awesome/reporter.js file.less
+```
diff --git a/lib/errors/runner-error.js b/lib/errors/runner-error.js
new file mode 100644
index 0000000..e31df8c
--- /dev/null
+++ b/lib/errors/runner-error.js
@@ -0,0 +1,14 @@
+'use strict';
+
+class RunnerError extends Error {
+    constructor (error, status) {
+        super(error);
+
+        this.message = error.message;
+        this.name = this.constructor.name;
+        this.status = status;
+        this.stack = error.stack;
+    }
+}
+
+module.exports = RunnerError;
diff --git a/lib/linters/attribute_quotes.js b/lib/linters/attribute_quotes.js
index 5d52d80..852530c 100644
--- a/lib/linters/attribute_quotes.js
+++ b/lib/linters/attribute_quotes.js
@@ -22,7 +22,7 @@ module.exports = {
 
                     results.push({
                         column: column,
-                        line: selector.source.start.line,
+                        line: node.source.start.line + selector.source.start.line - 1,
                         message: this.message
                     });
                 }
diff --git a/lib/linters/id_selector.js b/lib/linters/id_selector.js
index 81bba31..3f2b1f8 100644
--- a/lib/linters/id_selector.js
+++ b/lib/linters/id_selector.js
@@ -30,7 +30,7 @@ module.exports = {
 
                 results.push({
                     column: node.source.start.column + id.source.start.column - 1,
-                    line: node.source.start.line,
+                    line: node.source.start.line + id.source.start.line - 1,
                     message: this.message
                 });
             });
diff --git a/lib/linters/property_units.js b/lib/linters/property_units.js
index a543f43..bbeb5b9 100644
--- a/lib/linters/property_units.js
+++ b/lib/linters/property_units.js
@@ -52,7 +52,7 @@ module.exports = {
 
             results.push({
                 column: node.source.start.column + node.raws.between.length + property.length + value.source.start.column - 1,
-                line: value.source.start.line,
+                line: node.source.start.line + value.source.start.line - 1,
                 message: util.format(this.message.unit, unit, property)
             });
         });
diff --git a/lib/linters/selector_naming.js b/lib/linters/selector_naming.js
index fc4f101..a5ab7c4 100644
--- a/lib/linters/selector_naming.js
+++ b/lib/linters/selector_naming.js
@@ -32,7 +32,7 @@ module.exports = {
                 ) {
                     results.push({
                         column: node.source.start.column + selector.source.start.column - 1,
-                        line: node.source.start.line,
+                        line: node.source.start.line + selector.source.start.line - 1,
                         message: util.format(this.message, name)
                     });
                 }
diff --git a/lib/runner.js b/lib/runner.js
index 5adb7cd..f9c2732 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -1,5 +1,6 @@
 'use strict';
 
+const RunnerError = require('./errors/runner-error');
 const configLoader = require('./config-loader');
 const Lesshint = require('./lesshint');
 
@@ -20,10 +21,10 @@ class Runner {
     run () {
         return new Promise((resolve, reject) => {
             if (!this.options.args.length) {
-                return reject({
-                    message: 'No files to lint were passed. See lesshint -h',
-                    status: EXIT_CODES.NOINPUT
-                });
+                return reject(new RunnerError(
+                    'No files to lint were passed. See lesshint -h',
+                    EXIT_CODES.NOINPUT
+                ));
             }
 
             let config;
@@ -40,13 +41,13 @@ class Runner {
                 }
 
                 if (this.options.linters) {
-                    config.linters.push(config.linters, this.options.linters);
+                    config.linters.push(this.options.linters);
                 }
             } catch (e) {
-                return reject({
-                    message: `Something's wrong with the config file. Error: ${ e.message }`,
-                    status: EXIT_CODES.CONFIG
-                });
+                return reject(new RunnerError(
+                    `Something's wrong with the config file. Error: ${ e.message }`,
+                    EXIT_CODES.CONFIG
+                ));
             }
 
             const lesshint = new Lesshint();
@@ -57,10 +58,10 @@ class Runner {
             const reporter = lesshint.getReporter(this.options.reporter);
 
             if (!reporter) {
-                return reject({
-                    message: `Could not load reporter "${ this.options.reporter }".`,
-                    status: EXIT_CODES.CONFIG
-                });
+                return reject(new RunnerError(
+                    `Could not load reporter "${ this.options.reporter }".`,
+                    EXIT_CODES.CONFIG
+                ));
             }
 
             Promise.all(promises).then((results) => {
@@ -79,13 +80,19 @@ class Runner {
                 });
 
                 if (hasError) {
-                    return reject({
-                        message: '',
-                        status: EXIT_CODES.ERROR
-                    });
+                    return reject(new RunnerError(
+                        '',
+                        EXIT_CODES.ERROR
+                    ));
                 }
 
-                const maxWarnings = parseInt(this.options.maxWarnings, 10) || 0;
+                let maxWarnings;
+
+                if (isFinite(this.options.maxWarnings)) {
+                    maxWarnings = parseInt(this.options.maxWarnings, 10) || 0;
+                } else {
+                    maxWarnings = new Boolean(this.options.maxWarnings) ? 0 : -1;
+                }
 
                 if (maxWarnings === -1) {
                     return resolve({
@@ -98,10 +105,10 @@ class Runner {
                 }, 0);
 
                 if (warnings > maxWarnings) {
-                    return reject({
-                        message: '',
-                        status: EXIT_CODES.WARNING
-                    });
+                    return reject(new RunnerError(
+                        '',
+                        EXIT_CODES.WARNING
+                    ));
                 }
 
                 return resolve({
@@ -113,10 +120,10 @@ class Runner {
                     error.stack
                 ].join('\n');
 
-                reject({
-                    message: message,
-                    status: EXIT_CODES.SOFTWARE
-                });
+                reject(new RunnerError(
+                    message,
+                    EXIT_CODES.SOFTWARE
+                ));
             });
         });
     }
diff --git a/package.json b/package.json
index 3132937..237d434 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.4.0",
+  "version": "3.0.1",
   "main": "./lib/index.js",
   "author": {
     "name": "Jonathan Wilsson",
@@ -27,7 +27,7 @@
     "postcss": "^5.0.19",
     "postcss-less": "^0.14.0",
     "postcss-selector-parser": "^2.0.0",
-    "postcss-values-parser": "^1.1.0",
+    "postcss-values-parser": "~1.1.0",
     "rcfinder": "^0.1.8",
     "strip-json-comments": "^2.0.0"
   },


Test patch:
diff --git a/test/specs/linters/attribute_quotes.js b/test/specs/linters/attribute_quotes.js
index 4686e21..f556dee 100644
--- a/test/specs/linters/attribute_quotes.js
+++ b/test/specs/linters/attribute_quotes.js
@@ -79,7 +79,11 @@ describe('lesshint', function () {
         });
 
         it('should check all selectors in a selector group', function () {
-            const source = 'input[type=text], input[type=text] {}';
+            const source = [
+                'input[type=text],',
+                'input[type=text] {}'
+            ].join('\n');
+
             const expected = [
                 {
                     column: 12,
@@ -87,8 +91,8 @@ describe('lesshint', function () {
                     message: 'Attribute selectors should use quotes.'
                 },
                 {
-                    column: 30,
-                    line: 1,
+                    column: 12,
+                    line: 2,
                     message: 'Attribute selectors should use quotes.'
                 }
             ];
diff --git a/test/specs/linters/id_selector.js b/test/specs/linters/id_selector.js
index 727e374..a511894 100644
--- a/test/specs/linters/id_selector.js
+++ b/test/specs/linters/id_selector.js
@@ -46,7 +46,11 @@ describe('lesshint', function () {
         });
 
         it('should not allow IDs in multiple selectors for a rule', function () {
-            const source = '.foo #bar, .baz #qux {}';
+            const source = [
+                '.foo #bar,',
+                '.baz #qux {}'
+            ].join('\n');
+
             const expected = [
                 {
                     column: 6,
@@ -54,8 +58,8 @@ describe('lesshint', function () {
                     message: 'Selectors should not use IDs.'
                 },
                 {
-                    column: 17,
-                    line: 1,
+                    column: 6,
+                    line: 2,
                     message: 'Selectors should not use IDs.'
                 }
             ];
diff --git a/test/specs/linters/selector_naming.js b/test/specs/linters/selector_naming.js
index dfaee2a..c1eb57c 100644
--- a/test/specs/linters/selector_naming.js
+++ b/test/specs/linters/selector_naming.js
@@ -132,5 +132,33 @@ describe('lesshint', function () {
                 expect(result).to.deep.equal(expected);
             });
         });
+
+        it('should report the correct line', function () {
+            const source = [
+                '.Foo,',
+                '.Bar {}'
+            ].join('\n');
+
+            const expected = [{
+                column: 1,
+                line: 1,
+                message: 'Selector "Foo" should follow naming conventions.'
+            },
+            {
+                column: 1,
+                line: 2,
+                message: 'Selector "Bar" should follow naming conventions.'
+            }];
+
+            const options = {
+                disallowUppercase: true
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
     });
 });