Repository: lesshint/lesshint
Pull request #333 (2017-02-01 20:21:45)

Variable Value linter
<!--
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!
-->

New variable value linter.

**Which issue, if any, does this resolve?**
#331 

shellscape: man that linter name rubs me wrong 😆 
justtal: :)

I don't care changing it name at all.

I just didn't understand the need of "never a variable" option.
jwilsson: Thanks for working on this!

I left a few comments, but overall it looks good.

I think that @shellscape wants (and please correct me if I'm wrong) to have a "never" option because we usually allow linters to be really flexible (even if it might seem a bit crazy). And as I said earlier, I'm fine on either name.
justtal: @jwilsson  Thanks for the review!

So do you want me to change the config to be:

```
{
  "enabled": true,
  "properties": {
    "color": "always",
    "font-size": "never"
  }
}
```

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/config/defaults.json b/lib/config/defaults.json
index 01ef585..eee960d 100644
--- a/lib/config/defaults.json
+++ b/lib/config/defaults.json
@@ -187,5 +187,11 @@
     "zeroUnit": {
         "enabled": true,
         "style": "keep_unit"
+    },
+
+    "variableValue": {
+        "enabled": true,
+        "always": [],
+        "never": []
     }
 }
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/linter.js b/lib/linter.js
index be74e12..c6f104a 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -41,7 +41,8 @@ const defaultLinters = [
     require('./linters/url_format'),
     require('./linters/url_quotes'),
     require('./linters/zero_unit'),
-    require('./linters/max_char_per_line')
+    require('./linters/max_char_per_line'),
+    require('./linters/variable_value')
 ];
 
 const parseRules = function (node) {
diff --git a/lib/linters/README.md b/lib/linters/README.md
index f156a77..728d79f 100644
--- a/lib/linters/README.md
+++ b/lib/linters/README.md
@@ -34,6 +34,7 @@
 * [trailingWhitespace](#trailingwhitespace)
 * [urlFormat](#urlformat)
 * [urlQuotes](#urlquotes)
+* [variableValue](#variablevalue)
 * [zeroUnit](#zerounit)
 
 In addition to the linter specific options outlined below, each linter also accepts these options:
@@ -854,6 +855,42 @@ The [CSS spec](http://dev.w3.org/csswg/css-values/#url-value) also recommends th
 }
 ```
 
+## variableValue
+Force variable or non variable value in property.
+
+Option     | Description
+---------- | ----------
+`always`    | Array of property names
+`never`    | Array of property names
+
+### always ['color'] valid
+```less
+.foo {
+    color: @success;
+}
+```
+
+### always ['color'] invalid
+```less
+.foo {
+    color: #333;
+}
+```
+
+### never ['color'] valid
+```less
+.foo {
+    color: #333;
+}
+```
+
+### never ['color'] invalid
+```less
+.foo {
+    color: @success;
+}
+```
+
 ## zeroUnit
 Zero values should include a unit for consistency with other values.
 
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/decimal_zero.js b/lib/linters/decimal_zero.js
index 3ae2814..0d6c9c3 100644
--- a/lib/linters/decimal_zero.js
+++ b/lib/linters/decimal_zero.js
@@ -9,7 +9,10 @@ module.exports = {
     message: '%s should be written %s %s zero.',
 
     lint: function decimalZeroLinter (config, node) {
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.walk((child) => {
diff --git a/lib/linters/hex_length.js b/lib/linters/hex_length.js
index 967ac91..e3bebe5 100644
--- a/lib/linters/hex_length.js
+++ b/lib/linters/hex_length.js
@@ -15,7 +15,10 @@ module.exports = {
          */
         node.value = node.value.replace(/;$/, '');
 
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.first.walk((child) => {
diff --git a/lib/linters/hex_notation.js b/lib/linters/hex_notation.js
index e53ef63..8157cb5 100644
--- a/lib/linters/hex_notation.js
+++ b/lib/linters/hex_notation.js
@@ -20,7 +20,9 @@ module.exports = {
          */
         node.value = node.value.replace(/;$/, '');
 
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
 
         if (config.style && !styles[config.style]) {
             throw new Error(`Invalid setting value for hexNotation: ${ config.style }`);
diff --git a/lib/linters/hex_validation.js b/lib/linters/hex_validation.js
index d616222..df6758a 100644
--- a/lib/linters/hex_validation.js
+++ b/lib/linters/hex_validation.js
@@ -18,7 +18,10 @@ module.exports = {
          */
         node.value = node.value.replace(/;$/, '');
 
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.first.walk((child) => {
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/import_path.js b/lib/linters/import_path.js
index 1f2c4a2..64954ef 100644
--- a/lib/linters/import_path.js
+++ b/lib/linters/import_path.js
@@ -24,7 +24,9 @@ module.exports = {
          */
         const importOptsRx = /(\s+)?\((\s+)?(reference|inline|less|css|once|multiple|optional)(\s+)?\)(\s+)?/gi;
         const params = node.params.replace(importOptsRx, '');
-        const ast = parser(params).parse();
+        const ast = parser(params, {
+            loose: true
+        }).parse();
 
         let value = ast.first.first;
 
diff --git a/lib/linters/property_units.js b/lib/linters/property_units.js
index a65a508..bbeb5b9 100644
--- a/lib/linters/property_units.js
+++ b/lib/linters/property_units.js
@@ -23,8 +23,11 @@ module.exports = {
         config.invalid = Array.isArray(config.invalid) ? config.invalid : [];
         config.properties = config.properties || {};
 
-        const ast = parser(node.value).parse();
         const allowed = config.properties[property];
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.first.walk((value) => {
@@ -49,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/linters/space_around_comma.js b/lib/linters/space_around_comma.js
index e0ca76a..25daeee 100644
--- a/lib/linters/space_around_comma.js
+++ b/lib/linters/space_around_comma.js
@@ -10,18 +10,22 @@ module.exports = {
 
     lint: function spaceAroundCommaLinter (config, node) {
         let column = node.source.start.column;
-        let ast;
+        let value;
 
         if (node.params) {
             // A bit hacky, we're abusing the values parser for mixins etc.
-            ast = parser(node.selector).parse();
+            value = node.selector;
         } else if (node.type === 'decl') {
-            ast = parser(node.value).parse();
+            value = node.value;
             column += node.prop.length + node.raws.between.length;
         } else {
             return;
         }
 
+        const ast = parser(value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.walk((child) => {
@@ -32,6 +36,10 @@ module.exports = {
             const index = child.parent.index(child);
             const next = child.parent.nodes[index + 1];
 
+            if (next === undefined) {
+                return;
+            }
+
             /**
              * The user has opted to ignore decls, etc where newlines are used
              * to separate comma-delimited lists.
diff --git a/lib/linters/space_around_operator.js b/lib/linters/space_around_operator.js
index d36415d..830763e 100644
--- a/lib/linters/space_around_operator.js
+++ b/lib/linters/space_around_operator.js
@@ -9,7 +9,10 @@ module.exports = {
     message: 'Operators should%s be %s by %s space.',
 
     lint: function spaceAroundOperatorLinter (config, node) {
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.walk((child) => {
diff --git a/lib/linters/space_between_parens.js b/lib/linters/space_between_parens.js
index 00a9138..5faf547 100644
--- a/lib/linters/space_between_parens.js
+++ b/lib/linters/space_between_parens.js
@@ -13,18 +13,22 @@ module.exports = {
 
     lint: function spaceBetweenParensLinter (config, node) {
         let column = node.source.start.column;
-        let ast;
+        let value;
 
         if (node.params) {
             // A bit hacky, we're abusing the values parser for mixins etc.
-            ast = parser(node.selector).parse();
+            value = node.selector;
         } else if (node.type === 'decl') {
-            ast = parser(node.value).parse();
+            value = node.value;
             column += node.prop.length + node.raws.between.length;
         } else {
             return;
         }
 
+        const ast = parser(value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.walk((child) => {
diff --git a/lib/linters/string_quotes.js b/lib/linters/string_quotes.js
index 9071619..537ed01 100644
--- a/lib/linters/string_quotes.js
+++ b/lib/linters/string_quotes.js
@@ -47,7 +47,9 @@ module.exports = {
                 });
             }
         } else if (node.type === 'decl') {
-            const ast = valuesParser(node.value).parse();
+            const ast = valuesParser(node.value, {
+                loose: true
+            }).parse();
 
             ast.first.walk((decl) => {
                 if (decl.type !== 'string') {
diff --git a/lib/linters/url_format.js b/lib/linters/url_format.js
index 751ae6a..2d1bf9f 100644
--- a/lib/linters/url_format.js
+++ b/lib/linters/url_format.js
@@ -25,7 +25,10 @@ module.exports = {
          * Traverse all child value-nodes
          * https://github.com/lesshint/lesshint/issues/225
          */
-        const ast = parser(decl.params || decl.value).parse();
+        const ast = parser(decl.params || decl.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.first.nodes.forEach((child) => {
diff --git a/lib/linters/url_quotes.js b/lib/linters/url_quotes.js
index 6a9b23e..17518e5 100644
--- a/lib/linters/url_quotes.js
+++ b/lib/linters/url_quotes.js
@@ -21,7 +21,9 @@ module.exports = {
          * Traverse all child value-nodes
          * https://github.com/lesshint/lesshint/issues/225
          */
-        const ast = parser(node.params || node.value).parse();
+        const ast = parser(node.params || node.value, {
+            loose: true
+        }).parse();
 
         ast.first.nodes.forEach((child) => {
             if (child.type !== 'func' || child.value !== 'url') {
diff --git a/lib/linters/variable_value.js b/lib/linters/variable_value.js
new file mode 100644
index 0000000..6e2fe4e
--- /dev/null
+++ b/lib/linters/variable_value.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const util = require('util');
+
+module.exports = {
+    name: 'variableValue',
+    nodeTypes: ['decl'],
+    message: '%s is not allowed in "%s" property.',
+
+    lint: function variableValueLinter (config, node) {
+        const variableRegex = /@/i;
+
+        if (config.never && config.never.indexOf(node.prop) !== -1) {
+            if (variableRegex.test(node.value)) {
+                return [{
+                    column: node.source.start.column +
+                            node.prop.length +
+                            node.raws.between.length,
+                    line: node.source.start.line,
+                    message: util.format(this.message, 'Variable', node.prop)
+                }];
+            }
+        }
+
+        if (config.always && config.always.indexOf(node.prop) !== -1) {
+            if (!variableRegex.test(node.value)) {
+                return [{
+                    column: node.source.start.column +
+                            node.prop.length +
+                            node.raws.between.length,
+                    line: node.source.start.line,
+                    message: util.format(this.message, 'Non variable', node.prop)
+                }];
+            }
+        }
+    }
+};
diff --git a/lib/linters/zero_unit.js b/lib/linters/zero_unit.js
index b829f0a..65116bc 100644
--- a/lib/linters/zero_unit.js
+++ b/lib/linters/zero_unit.js
@@ -29,7 +29,10 @@ module.exports = {
             return;
         }
 
-        const ast = parser(node.value).parse();
+        const ast = parser(node.value, {
+            loose: true
+        }).parse();
+
         const results = [];
 
         ast.first.each((child) => {
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 dc527b2..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": "^0.1.7",
+    "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);
+            });
+        });
     });
 });
diff --git a/test/specs/linters/space_around_comma.js b/test/specs/linters/space_around_comma.js
index cb0bc75..782aeda 100644
--- a/test/specs/linters/space_around_comma.js
+++ b/test/specs/linters/space_around_comma.js
@@ -457,4 +457,17 @@ describe('lesshint', function () {
             });
         }); // "invalid"
     });
+
+    it('should ignore comma in the wrong place', function () {
+        const source = 'color: rgb(255, 255, 255),';
+        const options = {
+            style: 'after'
+        };
+
+        return spec.parse(source, function (ast) {
+            const result = spec.linter.lint(options, ast.root.first);
+            expect(result).to.be.undefined;
+        });
+    });
+
 });
diff --git a/test/specs/linters/variable_value.js b/test/specs/linters/variable_value.js
new file mode 100644
index 0000000..22a037b
--- /dev/null
+++ b/test/specs/linters/variable_value.js
@@ -0,0 +1,104 @@
+'use strict';
+
+const expect = require('chai').expect;
+const spec = require('../util.js').setup();
+
+describe('lesshint', function () {
+    describe('#variableValue()', function () {
+        it('should have the proper node types', function () {
+            const source = 'color: #ABC;';
+
+            return spec.parse(source, function (ast) {
+                expect(spec.linter.nodeTypes).to.include(ast.root.first.type);
+            });
+        });
+
+        it('should ignore if property not in lists', function () {
+            const source = 'font-weight: 700;';
+            const options = {
+                always: ['font-size'],
+                never: ['color']
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should ignore if no lists', function () {
+            const source = 'font-size: #333;';
+            const options = {
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should not allow variable if in never list', function () {
+            const source = 'color: @app-color;';
+            const options = {
+                never: ['color']
+            };
+            const expected = [{
+                line: 1,
+                column: 8,
+                message: 'Variable is not allowed in "color" property.'
+            }];
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
+        it('should allow non variable if in never list', function () {
+            const source = 'color: #333;';
+            const options = {
+                never: ['color']
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+
+        it('should not allow non variable if in always list', function () {
+            const source = 'color: #333;';
+            const options = {
+                always: ['color']
+            };
+            const expected = [{
+                line: 1,
+                column: 8,
+                message: 'Non variable is not allowed in "color" property.'
+            }];
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.deep.equal(expected);
+            });
+        });
+
+        it('should allow variable if in always list', function () {
+            const source = 'color: @app-color;';
+            const options = {
+                always: ['color']
+            };
+
+            return spec.parse(source, function (ast) {
+                const result = spec.linter.lint(options, ast.root.first);
+
+                expect(result).to.be.undefined;
+            });
+        });
+    });
+});