Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

lesshint

Package Overview
Dependencies
Maintainers
2
Versions
91
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lesshint - npm Package Compare versions

Comparing version 2.0.0-rc to 2.0.0

test/data/config/linters.json

2

.eslintrc.json

@@ -33,3 +33,3 @@ {

"no-caller": 2,
"no-console": 0,
"no-console": 2,
"no-else-return": 2,

@@ -36,0 +36,0 @@ "no-eval": 2,

# Changelog
## 2.0.0 (2016-07-13)
* Added the possibility to use custom linters. ([97e7268](https://github.com/lesshint/lesshint/commit/97e7268f639dfe956cf337885fdd44de2ac61982))
* Added a `allowNewline` option to `spaceAroundComma`. ([1cb009a](https://github.com/lesshint/lesshint/commit/1cb009a1487c3f609bac6bdc65a8b4ae2f502d32))
* Made a small behavioral change in decimalZero where it now only checks if there's always/never a leading/trailing decimal number.
([95e8037](https://github.com/lesshint/lesshint/commit/95e8037250bca1c09d74b282351ab8915fb0ea7b))
* Fixed an issue where `qualifyingElement` would report `&.classname`. ([af37172](https://github.com/lesshint/lesshint/commit/af371726ca47e073cfe38ab54e2e2b95fd606a10))
* Fixed an issue where `spaceBetweenParens` would fail on mulitiline definitions. ([846ebb0](https://github.com/lesshint/lesshint/commit/846ebb02c8f7310885e90c6e81dc117969de0a13))
* Fixed an issue where `decimalZero` would erroneously report whole numbers when `style` was `none`.
([b46be32](https://github.com/lesshint/lesshint/commit/b46be32dc5653915006e3e62ef924d29d84b5ecf))
* Fixed a ton of other bugs found after the parser back-end switch. ([Full diff from `2.0.0-rc1`](https://github.com/lesshint/lesshint/compare/v2.0.0-rc...v2.0.0))
## 2.0.0-rc1 (2016-05-16)

@@ -3,0 +14,0 @@ * Completely new parser back-end, using PostCSS. Please report any issues! ([1894408](https://github.com/lesshint/lesshint/commit/18944083bbd69dc0f3d607f24617732a15093e2e))

@@ -0,1 +1,3 @@

/*eslint no-console: 0*/
'use strict';

@@ -40,2 +42,3 @@

config.excludedFiles = config.excludedFiles || [];
config.linters = config.linters || [];

@@ -45,2 +48,6 @@ if (program.exclude) {

}
if (program.linters) {
config.linters.push.apply(config.linters, program.linters);
}
} catch (e) {

@@ -47,0 +54,0 @@ console.error("Something's wrong with the config file. Error: " + e.message);

'use strict';
var stripJsonComments = require('strip-json-comments');
var stripBom = require('strip-bom');
var RcFinder = require('rcfinder');

@@ -12,4 +11,8 @@ var fs = require('fs');

data = stripJsonComments(data);
data = stripBom(data);
// Strip BOM
if (data.charCodeAt(0) === 0xFEFF) {
data = data.slice(1);
}
return JSON.parse(data);

@@ -16,0 +19,0 @@ };

@@ -135,2 +135,3 @@ {

"spaceAroundComma": {
"allowNewline": false,
"enabled": true,

@@ -137,0 +138,0 @@ "style": "after"

@@ -9,3 +9,3 @@ 'use strict';

var linters = [
var defaultLinters = [
require('./linters/attribute_quotes'),

@@ -98,3 +98,7 @@ require('./linters/border_zero'),

var inlineOptions;
var linters;
// tests selectors for pseudo classes/selectors. eg. :not(), :active
var rPseudo = /::?[^ ,:.]+/g;
// Freeze the AST so linters won't accidentally change it

@@ -107,2 +111,5 @@ Object.freeze(ast);

// Load any additional linters
linters = mergeLintersFromConfig(defaultLinters, config);
// Fetch all single line options before we start the actual linting

@@ -135,3 +142,19 @@ ast.root.walkComments(function (node) {

// if we're dealing with a regular Rule (or other) node, which isn't
// an actual Mixin or AtRule, and its selector contains a pseudo
// class or selector, then clean up the raws and params properties.
// tracking: https://github.com/webschik/postcss-less/issues/56
// TODO: remove this when issue resolved
if (node.params && rPseudo.test(node.selector)) {
delete node.params;
// this just started showing up in postcss-less@0.14.0. not sure
// if it's sticking around, but making sure we're thorough.
if (node.raws.params) {
delete node.raws.params;
}
}
lint = linter.lint.call(linter, options, node);
if (lint) {

@@ -181,2 +204,22 @@ if (!Array.isArray(lint)) {

var mergeLintersFromConfig = function (defaultLinters, config) {
if (!config.linters) {
return defaultLinters;
}
if (!(config.linters instanceof Array)) {
throw new Error('linters should be an array of file paths and/or linters');
}
return defaultLinters.concat(config.linters.map(requireConfigLinter));
};
var requireConfigLinter = function (linter) {
if (typeof linter === 'string') {
return require(linter);
}
return linter;
};
exports.resultSeverity = {

@@ -183,0 +226,0 @@ error: 'error',

@@ -23,4 +23,4 @@ 'use strict';

if (selector.operator && !selector.quoted) {
column = selector.source.start.column + selector.attribute.length
+ selector.operator.length + '['.length;
column = node.source.start.column + selector.source.start.column
+ selector.attribute.length + selector.operator.length;

@@ -27,0 +27,0 @@ results.push({

@@ -26,8 +26,4 @@ 'use strict';

/*
* Bail if:
* - Not a floating point number
* - Float parsed as 0 (e.g. 0.0, .0, 0.)
*/
if (!(/^-?(\d*\.\d*)/.test(number)) || parseFloat(number) === 0) {
// If it's a float parsed as 0 (e.g. 0.0, .0, 0.), bail.
if (parseFloat(number) === 0) {
return;

@@ -43,21 +39,25 @@ }

case 'leading':
if (!/^-?(\d+)\.(\d*[1-9])$/.test(number)) {
if (!/^-?(\d+)\.?(\d*)$/.test(number)) {
output.type = 'leading';
}
break;
case 'trailing':
if (!/^-?([1-9]\d*)?\.(\d*0)$/.test(number)) {
if (!/^-?(\d*)?\.(\d*)$/.test(number)) {
output.type = 'trailing';
}
break;
case 'both':
if (!/^-?(\d+)\.(\d*0)$/.test(number)) {
if (!/^-?(\d+)\.(\d+)$/.test(number)) {
output.type = 'leading and trailing';
}
break;
case 'none':
if (!/^-?([1-9]\d*)?\.(\d*[1-9])$/.test(number)) {
if (/\./.test(number) && !/^-?([1-9]\d*)?\.(\d*[1-9])$/.test(number)) {
output.type = 'leading and trailing';
output.inclusion = 'without';
}
break;

@@ -70,3 +70,3 @@ default:

results.push({
column: node.prop.length + node.raws.between.length + child.source.start.column,
column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
line: child.source.start.line,

@@ -73,0 +73,0 @@ message: util.format(self.message, number, output.inclusion, output.type)

@@ -11,3 +11,4 @@ 'use strict';

if (node.ruleWithoutBody) {
// if it's a bodiless rule or a mixin function definition, bail.
if (node.ruleWithoutBody || node.params) {
return;

@@ -21,3 +22,3 @@ }

node.walk(function (child) {
if (child.type === 'decl' ||
if (child.type === 'decl' || child.type === 'atrule' ||
(child.type === 'rule' && child.ruleWithoutBody)) {

@@ -24,0 +25,0 @@ hasDeclarations = true;

@@ -23,3 +23,3 @@ 'use strict';

ast.first.walk(function (node) {
ast.first.walk(function (child) {
var color;

@@ -29,7 +29,7 @@ var canShorten = false;

if (node.type !== 'word' || !node.isColor) {
if (child.type !== 'word' || !child.isColor) {
return;
}
color = node.value;
color = child.value;

@@ -62,2 +62,3 @@ switch (config.style) {

results.push({
column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
message: util.format(self.message, color, config.style)

@@ -64,0 +65,0 @@ });

@@ -32,8 +32,8 @@ 'use strict';

ast.first.walk(function (node) {
if (node.type !== 'word' || !/^#/.test(node.value)) {
ast.first.walk(function (child) {
if (child.type !== 'word' || !/^#/.test(child.value)) {
return;
}
color = node.value;
color = child.value;

@@ -46,2 +46,3 @@ if (/^#\d+$/.test(color)) {

results.push({
column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
message: util.format(self.message, color, config.style)

@@ -48,0 +49,0 @@ });

@@ -24,10 +24,11 @@ 'use strict';

ast.first.walk(function (node) {
if (node.type !== 'word') {
ast.first.walk(function (child) {
if (child.type !== 'word') {
return;
}
if (rHex.test(node.value) && !rColor.test(node.value)) {
if (rHex.test(child.value) && !rColor.test(child.value)) {
results.push({
message: util.format(self.message, node.value)
column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
message: util.format(self.message, child.value)
});

@@ -34,0 +35,0 @@ }

@@ -34,4 +34,4 @@ 'use strict';

results.push({
column: id.source.start.column,
line: id.source.start.line,
column: node.source.start.column + id.source.start.column - 1,
line: node.source.start.line,
message: self.message

@@ -38,0 +38,0 @@ });

@@ -5,23 +5,16 @@ 'use strict';

name: 'importantRule',
nodeTypes: ['rule'],
nodeTypes: ['decl'],
message: '!important should not be used.',
lint: function importantRuleLinter (config, node) {
var results = [];
var self = this;
var pos = 1;
node.walkDecls(function (decl) {
if (decl.important) {
results.push({
column: decl.source.start.column,
line: decl.source.start.line,
message: self.message
});
}
});
if (results.length) {
return results;
if (node.important) {
return [{
column: node.source.start.column + node.prop.length + node.raws.between.length + node.value.length + pos,
line: node.source.start.line,
message: this.message
}];
}
}
};

@@ -18,13 +18,14 @@ 'use strict';

node.walkDecls(function (declaration) {
node.each(function (child) {
var currentProperty;
var property;
if (results.length) {
if (child.type !== 'decl' || results.length) {
return;
}
property = declaration.prop;
property = child.prop;
if (!property) {
// Ignore declarations without a property and variables
if (!property || (property && /^@/.test(property))) {
return;

@@ -38,4 +39,4 @@ }

results = [{
column: declaration.source.start.column,
line: declaration.source.start.line,
column: child.source.start.column,
line: child.source.start.line,
message: self.message

@@ -42,0 +43,0 @@ }];

@@ -54,3 +54,3 @@ 'use strict';

results.push({
column: node.raws.between.length + property.length + value.source.start.column,
column: node.source.start.column + node.raws.between.length + property.length + value.source.start.column - 1,
line: value.source.start.line,

@@ -57,0 +57,0 @@ message: util.format(self.message.unit, unit, property)

@@ -11,2 +11,33 @@ 'use strict';

inspectParent: function (node) {
var parent = node.parent;
var result = {
startsWith: '',
endsWith: '',
hasTag: false
};
if (!parent || !parent.selectorAst) {
return result;
}
parent.selectorAst.each(function (selector) {
if (!result.startsWith) {
result.startsWith = selector.first.type;
}
result.endsWith = selector.last.type;
if (!result.hasTag) {
selector.nodes.forEach(function (element) {
if (element.type === 'tag') {
result.hasTag = true;
}
});
}
});
return result;
},
lint: function qualifyingElementLinter (config, node) {

@@ -18,2 +49,4 @@ var selectorTypes = ['nesting', 'tag'];

parser(function (selectors) {
node.selectorAst = selectors;
selectors.each(function (selector) {

@@ -23,2 +56,5 @@ var result;

selector.nodes.forEach(function (element, index) {
var next;
var parent;
if (selectorTypes.indexOf(element.type) === -1) {

@@ -29,9 +65,11 @@ return;

// Fetch the next node to check it
element = selector.at(index + 1);
next = selector.at(index + 1);
if (!element) {
if (!next) {
return;
}
switch (element.type) {
parent = self.inspectParent(node);
switch (next.type) {
case 'attribute':

@@ -42,10 +80,11 @@ if (config.allowWithAttribute) {

result = element;
result = next;
break;
case 'class':
if (config.allowWithClass) {
if (config.allowWithClass ||
(parent.startsWith === 'class' && !parent.hasTag)) {
return;
}
result = element;
result = next;
break;

@@ -57,3 +96,3 @@ case 'id':

result = element;
result = next;
break;

@@ -64,4 +103,4 @@ }

results.push({
column: result.source.start.column,
line: result.source.start.line,
column: node.source.start.column + result.source.start.column - 1,
line: node.source.start.line + result.source.start.line - 1,
message: util.format(self.message, result.type.charAt(0).toUpperCase() + result.type.substring(1))

@@ -68,0 +107,0 @@ });

@@ -122,7 +122,5 @@ # Available linters

.foo {
font-size: 1.0em; // leading
font-size: .5em; // leading, trailing, both
font-size: 0.5em; // trailing, both, none
font-size: 1.5em; // trailing, both
font-size: 0.50em; // leading, trailing, none
font-size: .5em; // leading, both
font-size: 1em; // trailing, both
font-size: 1.0em; // none
}

@@ -134,8 +132,5 @@ ```

.foo {
font-size: 0.5em; // leading
font-size: 1.5em; // leading, none
font-size: .50em; // trailing
font-size: 1.0em; // trailing, both
font-size: 0.50em; // both
font-size: .5em; // none
font-size: 0.5em; // leading, both
font-size: 1.0em; // trailing, both
font-size: .5em; // none
}

@@ -616,5 +611,6 @@ ```

Option | Description
---------- | ----------
`style` | `after` (**default**), `before`, `both`, `none`
Option | Description
----------------| ----------
`allowNewline` | `false` (**default**), `boolean`
`style` | `after` (**default**), `before`, `both`, `none`

@@ -649,2 +645,12 @@ ### after

### allowNewline : true
```less
.foo {
font:
14px,
Roboto,
#000;
}
```
## spaceAroundOperator

@@ -651,0 +657,0 @@ Defines how operators (`+`, `-`, `*`, `/`, and `=`) should be formatted by a space to aid readability.

@@ -35,4 +35,4 @@ 'use strict';

results.push({
column: selector.source.start.column,
line: selector.source.start.line,
column: node.source.start.column + selector.source.start.column - 1,
line: node.source.start.line,
message: util.format(self.message, name)

@@ -39,0 +39,0 @@ });

'use strict';
var hasNewlineBefore = function (node) {
return /\n/.test(node.raws.before);
};
var isValidAfter = function (node, index) {
var next = node.parent.nodes[index + 1];
if (!next || (next && next.type === 'comment')) {
// Only valid if followed by comment
// Nothing after, valid
if (!next) {
return true;
}
// Followed by comment or next node is on a new line, valid
if (next.type === 'comment' || hasNewlineBefore(next)) {
return true;
}
return false;

@@ -35,4 +44,4 @@ };

validBefore = /\n/.test(child.raws.before);
validAfter = isValidAfter(node, index);
validBefore = hasNewlineBefore(child);
validAfter = isValidAfter(child, index);

@@ -39,0 +48,0 @@ // Check if the closing bracket is on the same line as the last property

@@ -39,4 +39,4 @@ 'use strict';

results.push({
column: thing.source.start.column,
line: thing.source.start.line,
column: node.source.start.column + thing.source.start.column - 1,
line: node.source.start.line,
message: self.message

@@ -43,0 +43,0 @@ });

@@ -18,9 +18,9 @@ 'use strict';

var styles = {
'no_space': /^:$/,
'one_space': /^:\s$/,
'at_least_one_space': /^:\s{1,}$/
'no_space': /^\s*:$/,
'one_space': /^\s*:\s$/,
'at_least_one_space': /^\s*:\s{1,}$/
};
if (config.style && !styles[config.style]) {
throw new Error('Invalid setting value for urlFormat: ' + config.style);
throw new Error('Invalid setting value for spaceAfterPropertyColon: ' + config.style);
}

@@ -30,3 +30,3 @@

return [{
column: node.source.start.column + node.prop.length + 1,
column: node.source.start.column + node.prop.length + node.raws.between.indexOf(':') + 1,
line: node.source.start.line,

@@ -33,0 +33,0 @@ message: util.format(this.message[config.style])

@@ -18,3 +18,3 @@ 'use strict';

results.push({
column: node.source.end.column - 1,
column: node.source.start.column + node.prop.length + node.raws.between.length + node.value.length,
line: node.source.end.line,

@@ -29,3 +29,3 @@ message: util.format(self.message, ' not', 'any')

results.push({
column: node.source.end.column,
column: node.source.start.column + node.prop.length + node.raws.between.length + node.value.length,
line: node.source.end.line,

@@ -32,0 +32,0 @@ message: util.format(self.message, '', 'one')

@@ -39,2 +39,26 @@ 'use strict';

// the user has opted to ignore decls, etc where newlines are used
// to separate comma-delimited lists.
if (config.allowNewline) {
if (config.style === 'after' && /\n/g.test(next.raws.before)) {
return;
}
if (/\n/g.test(child.raws.before)) {
return;
}
}
// newlines aren't accounted for. remove em cause we don't count
// em. (#209) users can choose to turn off trailing spaces and choose
// to have trailing whitespace after a comma, but before a newline.
// it's silly, but it's an available option.
if (next && next.raws && next.raws.before) {
next.raws.before = next.raws.before.replace(/\n/g, '');
}
if (child && child.raws && child.raws.before) {
child.raws.before = child.raws.before.replace(/\n/g, '');
}
switch (config.style) {

@@ -41,0 +65,0 @@ case 'after':

@@ -37,2 +37,8 @@ 'use strict';

// Ignore variables
if (child.value === '-' && (child.raws.before || node.raws.between) &&
nextElement.type === 'atword' && !nextElement.raws.before) {
return;
}
// Ignore font-size/line-height shorthand declaration

@@ -39,0 +45,0 @@ if (node.prop === 'font' && child.value === '/' &&

@@ -24,3 +24,8 @@ 'use strict';

if (node.ruleWithoutBody) {
// if the node is a bodiless rule, or it's an import statement, or we
// are dealing with an atrule without a body, bail.
if (node.ruleWithoutBody || node.name === 'import' ||
// hopefully node.ruleWithoutBody will be implemented on AtRule nodes
// in the future: https://github.com/webschik/postcss-less/issues/55
(node.type === 'atrule' && !node.nodes)) {
return;

@@ -27,0 +32,0 @@ }

@@ -43,2 +43,12 @@ 'use strict';

// issue #130: newlines aren't accounted for. remove em cause we
// don't count em.
if (next && next.raws && next.raws.before) {
next.raws.before = next.raws.before.replace(/\n/g, '');
}
if (prev && prev.raws && prev.raws.after) {
prev.raws.after = prev.raws.after.replace(/\n/g, '');
}
switch (config.style) {

@@ -45,0 +55,0 @@ case 'no_space':

@@ -43,3 +43,3 @@ 'use strict';

column: column,
line: decl.source.start.line,
line: node.source.start.line,
message: util.format(self.message, config.style)

@@ -55,8 +55,8 @@ });

if (selector.quoted && !quotes[config.style].test(selector.value)) {
column = selector.source.start.column + selector.attribute.length
+ selector.operator.length + '['.length;
column = node.source.start.column + selector.source.start.column
+ selector.attribute.length + selector.operator.length;
results.push({
column: column,
line: selector.source.start.line,
line: node.source.start.line,
message: util.format(self.message, config.style)

@@ -63,0 +63,0 @@ });

@@ -9,30 +9,23 @@ 'use strict';

lint: function trailingSemicolonLinter (config, node) {
var others = 0;
var results = [];
var self = this;
if (node.ruleWithoutBody || (node.nodes && !node.nodes.length)) {
return;
}
if (node.raws.semicolon === false) {
node.each(function (child, index) {
if (child.type !== 'decl' || index !== node.nodes.length - 1) {
return;
}
if (!node.raws.semicolon) {
node.walk(function (n) {
if (n.type !== 'decl') {
others++;
}
results.push({
column: child.source.end.column + 1,
line: child.source.start.line,
message: self.message
});
});
}
/**
* If the node contains child nodes that aren't Declarations,
* then PostCSS will report raws.semicolon: false. in that case
* we should wait until lesshint walks to that Rule/AtRule, and
* let the linter handle that one.
*/
if (others === 0) {
return [{
column: node.source.start.column,
line: node.source.start.line,
message: this.message
}];
}
if (results.length) {
return results;
}
}
};
'use strict';
var parser = require('postcss-values-parser');
module.exports = {

@@ -9,11 +11,17 @@ name: 'urlQuotes',

lint: function urlQuotesLinter (config, node) {
var parser = require('postcss-values-parser');
var ast = parser(node.params || node.value).parse();
var uri = ast.first.first;
var rSingle = /^\'(.+)\'$/;
var rDouble = /^\"(.+)\"$/;
var column;
var value;
var rSingle = /^\'(.+)\'$/;
var rDouble = /^\"(.+)\"$/;
var ast;
var uri;
// Nothing to check, bail
if (!node.params && !node.value) {
return;
}
ast = parser(node.params || node.value).parse();
uri = ast.first.first;
if (uri.type !== 'func' || uri.value !== 'url') {

@@ -20,0 +28,0 @@ return;

@@ -37,3 +37,3 @@ 'use strict';

ast.walk(function (child) {
ast.first.each(function (child) {
var unit = child.unit;

@@ -75,3 +75,3 @@ var valid = true;

results.push({
column: node.prop.length + node.raws.between.length + child.source.start.column,
column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
line: node.source.start.line,

@@ -78,0 +78,0 @@ message: util.format(self.message, config.style === 'keep_unit' ? 'not ' : '')

@@ -0,1 +1,3 @@

/*eslint no-console: 0*/
'use strict';

@@ -2,0 +4,0 @@

{
"name": "lesshint",
"description": "A tool to aid you in writing clean and consistent Less.",
"version": "2.0.0-rc",
"version": "2.0.0",
"main": "./lib/lesshint.js",

@@ -24,9 +24,8 @@ "author": {

"lodash.sortby": "^4.0.1",
"minimatch": "^3.0.0",
"minimatch": "^3.0.2",
"postcss": "^5.0.19",
"postcss-less": "^0.10.0",
"postcss-less": "^0.14.0",
"postcss-selector-parser": "^2.0.0",
"postcss-values-parser": "^0.1.0",
"rcfinder": "^0.1.8",
"strip-bom": "^2.0.0",
"strip-json-comments": "^2.0.0",

@@ -33,0 +32,0 @@ "vow": "^0.4.9",

# lesshint
[![npm](https://img.shields.io/npm/v/lesshint.svg)](https://www.npmjs.com/package/lesshint)
[![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/96cx40uc5t0842u4/branch/master?svg=true)](https://ci.appveyor.com/project/jwilsson/lesshint/branch/master)
[![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)

@@ -14,2 +14,3 @@ [![Dependency Status](https://david-dm.org/lesshint/lesshint.svg?theme=shields.io&style=flat)](https://david-dm.org/lesshint/lesshint)

* [Configuration](#configuration)
* [Custom linters](#custom-linters)
* [CLI usage](#cli-usage)

@@ -22,3 +23,3 @@ * [Reporters](#reporters)

## Installation
Run the following command from the command line (add -g to install globally):
Run the following command from the command line (add `-g` to install globally):

@@ -48,3 +49,3 @@ ```

### Inline configuration
Since `1.4.0` it's possible to configure rules using inline comments in your `.less` files. For example:
It's also possible to configure rules using inline comments in your `.less` files. For example:

@@ -110,2 +111,64 @@ ```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": [
"./plugins/linters/sampleLinter",
require("./plugins/linters/otherSampleLinter")
]
```
## 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.
## CLI usage

@@ -122,2 +185,3 @@ Run `lesshint` from the command-line by passing one or more files/directories to recursively scan.

`-e`/`--exclude` | A [minimatch glob pattern](https://github.com/isaacs/minimatch) or a file to exclude form 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.

@@ -133,3 +197,3 @@ `-V`/`--version` | Show version.

`1` | One or more linting errors with a severity of `warning` was found.
`2` | One or more linting errors with a severity of `error` was found (since `1.3.0`).
`2` | One or more linting errors with a severity of `error` was found.
`66` | No files to lint were supplied.

@@ -136,0 +200,0 @@ `70` | An unknown error occurred within `lesshint`, possibly a bug. [Please file an issue!](https://github.com/lesshint/lesshint/issues/new)

@@ -0,1 +1,3 @@

/*eslint no-console: 0*/
'use strict';

@@ -143,2 +145,51 @@

it('should load linters (command-line parameter only)', function () {
var result;
sinon.spy(console, 'log');
result = cli({
args: [path.dirname(__dirname) + '/data/files/file.less'],
linters: ['../test/plugins/sampleLinter']
});
return result.fail(function (status) {
console.log.restore();
expect(status).to.equal(1);
});
});
it('should load linters (config file only)', function () {
var result;
sinon.spy(console, 'log');
result = cli({
args: [path.dirname(__dirname) + '/data/files/file.less'],
config: path.resolve(process.cwd() + '/test/data/config/linters.json')
});
return result.fail(function (status) {
console.log.restore();
expect(status).to.equal(1);
});
});
it('should load linters (both command-line parameter and config file)', function () {
var result;
sinon.spy(console, 'log');
result = cli({
args: [path.dirname(__dirname) + '/data/files/file.less'],
config: path.resolve(process.cwd() + '/test/data/config/linters.json'),
linters: ['../test/plugins/otherSampleLinter']
});
return result.fail(function (status) {
console.log.restore();
expect(status).to.equal(1);
});
});
it('should exit without errors when passed a built-in reporter name', function () {

@@ -207,7 +258,6 @@ var result;

return result.fail(function (status) {
console.log.restore();
expect(status).to.equal(2);
console.log.restore();
});
});
});

@@ -7,3 +7,3 @@ 'use strict';

describe('lesshint', function () {
var testDir = path.dirname(__dirname) + '/data/files';
var testDir = path.join(path.dirname(__dirname), '/data/files');
var Lesshint = require('../../lib/lesshint');

@@ -24,3 +24,3 @@ var configLoader = require('../../lib/config-loader');

it('should strip trailing slashes from directory names', function () {
var testPath = path.dirname(__dirname) + '/data/files/sub/';
var testPath = path.join(path.dirname(__dirname), '/data/files/sub/');
var lesshint = new Lesshint();

@@ -36,3 +36,3 @@

it('should ignore dotfiles', function () {
var testPath = path.dirname(__dirname) + '/data/ignored-files';
var testPath = path.join(path.dirname(__dirname), '/data/ignored-files');
var lesshint = new Lesshint();

@@ -48,3 +48,3 @@

it('should ignore excluded files', function () {
var testPath = path.dirname(__dirname) + '/data/excluded-files';
var testPath = path.join(path.dirname(__dirname), '/data/excluded-files');
var lesshint = new Lesshint();

@@ -63,3 +63,3 @@ var config = {

it('should only check files with the correct extension and a leading dot', function () {
var testPath = path.dirname(__dirname) + '/data/excluded-files';
var testPath = path.join(path.dirname(__dirname), '/data/excluded-files');
var lesshint = new Lesshint();

@@ -78,3 +78,3 @@ var config = {

it('should only check files with the correct extension and without a leading dot', function () {
var testPath = path.dirname(__dirname) + '/data/excluded-files';
var testPath = path.join(path.dirname(__dirname), '/data/excluded-files');
var lesshint = new Lesshint();

@@ -93,3 +93,3 @@ var config = {

it('should allow all extensions when "*" is passed', function () {
var testPath = path.dirname(__dirname) + '/data/excluded-files';
var testPath = path.join(path.dirname(__dirname), '/data/excluded-files');
var lesshint = new Lesshint();

@@ -171,3 +171,3 @@ var config = {

it('should throw on non-parse related errors', function () {
var config = configLoader(path.dirname(__dirname) + '/data/config/bad.json');
var config = configLoader(path.join(path.dirname(__dirname), '/data/config/bad.json'));
var lesshint = new Lesshint();

@@ -174,0 +174,0 @@ var checker;

@@ -368,2 +368,68 @@ 'use strict';

});
it('should not report comma spaces for selectors that have pseudos', function () {
var source = '.foo,\n.bar:not(.foo){}';
var path = 'test.less';
var result;
result = linter.lint(source, path, {});
expect(result).to.have.length(0);
});
it('should load a custom linter (as a require path)', function () {
var source = '// boo!\n';
var path = 'test.less';
var result;
var config = {
linters: ['../test/plugins/sampleLinter'],
sample: {
enabled: true
}
};
var expected = [{
column: 1,
file: 'test.less',
fullPath: 'test.less',
line: 1,
linter: 'sample',
message: 'Sample error.',
severity: 'warning',
source: source.trim()
}];
result = linter.lint(source, path, config);
expect(result).to.deep.equal(expected);
});
it('should load a custom linter (as a passed linter)', function () {
var source = '// boo!\n';
var path = 'test.less';
var result;
var config = {
linters: [require('../plugins/sampleLinter')],
sample: {
enabled: true
}
};
var expected = [{
column: 1,
file: 'test.less',
fullPath: 'test.less',
line: 1,
linter: 'sample',
message: 'Sample error.',
severity: 'warning',
source: source.trim()
}];
result = linter.lint(source, path, config);
expect(result).to.deep.equal(expected);
});
});

@@ -370,0 +436,0 @@

@@ -16,2 +16,12 @@ 'use strict';

it('should bail if the node has no selector', function () {
var source = "input[type='text'] { width: 0; }";
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first.first);
expect(result).to.be.undefined;
});
});
it('should allow single quotes', function () {

@@ -18,0 +28,0 @@ var source = "input[type='text'] {}";

@@ -113,12 +113,2 @@ 'use strict';

it('should allow number without decimal', function () {
var source = 'font-size: 1em;';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should allow number with trailing decimal zero', function () {

@@ -135,7 +125,7 @@ var source = 'font-size: 1.0em;';

it('should not allow number without trailing decimal zero', function () {
var source = 'font-size: 1.5em;';
var source = 'font-size: 1em;';
var expected = [{
column: 12,
line: 1,
message: '1.5 should be written with trailing zero.'
message: '1 should be written with trailing zero.'
}];

@@ -149,17 +139,2 @@

});
it('should not allow number without trailing decimal zero in a function', function () {
var source = 'color: rgba(0, 0, 0, 0.1);';
var expected = [{
column: 22,
line: 1,
message: '0.1 should be written with trailing zero.'
}];
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.deep.equal(expected);
});
});
}); //"trailing"

@@ -184,4 +159,4 @@

it('should allow decimal number greater than 1 without leading zero', function () {
var source = 'font-size: 1.250em;';
it('should allow decimal number less than 1 without trailing zero', function () {
var source = 'font-size: 0.5em;';

@@ -195,9 +170,4 @@ return spec.parse(source, function (ast) {

it('should not allow number without trailing decimal zero', function () {
it('should allow decimal number greater than 1 without leading zero', function () {
var source = 'font-size: 1.5em;';
var expected = [{
column: 12,
line: 1,
message: '1.5 should be written with leading and trailing zero.'
}];

@@ -207,27 +177,12 @@ return spec.parse(source, function (ast) {

expect(result).to.deep.equal(expected);
expect(result).to.be.undefined;
});
});
it('should not allow number without trailing decimal zero in a function', function () {
var source = 'color: rgba(0, 0, 0, 1.5);';
var expected = [{
column: 22,
line: 1,
message: '1.5 should be written with leading and trailing zero.'
}];
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.deep.equal(expected);
});
});
it('should not allow number without leading decimal zero', function () {
var source = 'font-size: .50em;';
var source = 'font-size: .5em;';
var expected = [{
column: 12,
line: 1,
message: '.50 should be written with leading and trailing zero.'
message: '.5 should be written with leading and trailing zero.'
}];

@@ -242,8 +197,8 @@

it('should not allow number without leading decimal zero in a function', function () {
var source = 'color: rgba(0, 0, 0, .50);';
it('should not allow number without trailing decimal zero', function () {
var source = 'font-size: 1em;';
var expected = [{
column: 22,
column: 12,
line: 1,
message: '.50 should be written with leading and trailing zero.'
message: '1 should be written with leading and trailing zero.'
}];

@@ -276,8 +231,8 @@

it('should not allow number with trailing decimal zero', function () {
var source = 'font-size: .50em;';
it('should not allow number with leading decimal zero', function () {
var source = 'font-size: 0.5em;';
var expected = [{
column: 12,
line: 1,
message: '.50 should be written without leading and trailing zero.'
message: '0.5 should be written without leading and trailing zero.'
}];

@@ -292,23 +247,8 @@

it('should not allow number with trailing decimal zero in a function', function () {
var source = 'color: rgba(0, 0, 0, .50);';
it('should not allow number with trailing decimal zero', function () {
var source = 'font-size: 1.0em;';
var expected = [{
column: 22,
line: 1,
message: '.50 should be written without leading and trailing zero.'
}];
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.deep.equal(expected);
});
});
it('should not allow number with leading decimal zero', function () {
var source = 'font-size: 0.5em;';
var expected = [{
column: 12,
line: 1,
message: '0.5 should be written without leading and trailing zero.'
message: '1.0 should be written without leading and trailing zero.'
}];

@@ -323,9 +263,4 @@

it('should not allow number with trailing decimal zero in a function', function () {
var source = 'color: rgba(0, 0, 0, 0.5);';
var expected = [{
column: 22,
line: 1,
message: '0.5 should be written without leading and trailing zero.'
}];
it('should allow font-weights ending in zero', function () {
var source = 'font-weight: 300;';

@@ -335,3 +270,3 @@ return spec.parse(source, function (ast) {

expect(result).to.deep.equal(expected);
expect(result).to.be.undefined;
});

@@ -338,0 +273,0 @@ });

@@ -62,2 +62,12 @@ 'use strict';

it('should allow rules with only atrules (#200)', function () {
var source = '.foo { @atrule(); }';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not check mixin calls', function () {

@@ -72,3 +82,23 @@ var source = '.mixin();';

});
it('should not check atrule calls (#200)', function () {
var source = '@atrule();';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not check mixin definitions', function () {
var source = '.header-def(@rules) {h1,h2,h3,h4,h5,h6 {@rules();}}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
});
});

@@ -19,2 +19,3 @@ 'use strict';

var expected = [{
column: 8,
message: '#ABC should be written in the long-form format.'

@@ -50,2 +51,3 @@ }];

var expected = [{
column: 8,
message: '#AABBCC should be written in the short-form format.'

@@ -94,2 +96,3 @@ }];

var expected = [{
column: 37,
message: '#AABBCC should be written in the short-form format.'

@@ -112,2 +115,3 @@ }];

var expected = [{
column: 9,
message: '#ABC should be written in the long-form format.'

@@ -130,2 +134,3 @@ }];

var expected = [{
column: 9,
message: '#AABBCC should be written in the short-form format.'

@@ -132,0 +137,0 @@ }];

@@ -19,2 +19,3 @@ 'use strict';

var expected = [{
column: 8,
message: '#AABBCC should be written in lowercase.'

@@ -50,2 +51,3 @@ }];

var expected = [{
column: 8,
message: '#aabbcc should be written in uppercase.'

@@ -81,2 +83,3 @@ }];

var expected = [{
column: 37,
message: '#AABBCC should be written in lowercase.'

@@ -99,2 +102,3 @@ }];

var expected = [{
column: 9,
message: '#AABBCC should be written in lowercase.'

@@ -117,2 +121,3 @@ }];

var expected = [{
column: 9,
message: '#aabbcc should be written in uppercase.'

@@ -119,0 +124,0 @@ }];

@@ -29,2 +29,3 @@ 'use strict';

var expected = [{
column: 8,
message: 'Hexadecimal color "#AABBC" should be either three or six characters long.'

@@ -43,2 +44,3 @@ }];

var expected = [{
column: 37,
message: 'Hexadecimal color "#AABBC" should be either three or six characters long.'

@@ -57,2 +59,3 @@ }];

var expected = [{
column: 9,
message: 'Hexadecimal color "#AABBC" should be either three or six characters long.'

@@ -59,0 +62,0 @@ }];

@@ -9,3 +9,3 @@ 'use strict';

it('should have the proper node types', function () {
var source = '.foo { color: red; }';
var source = 'color: red;';

@@ -18,3 +18,3 @@ return spec.parse(source, function (ast) {

it('should not do anything when there is no !important present', function () {
var source = '.foo { color: red; }';
var source = 'color: red;';

@@ -29,5 +29,5 @@ return spec.parse(source, function (ast) {

it('should not allow !important', function () {
var source = '.foo { color: red !important; }';
var source = 'color: red !important;';
var expected = [{
column: 8,
column: 12,
line: 1,

@@ -34,0 +34,0 @@ message: '!important should not be used.'

@@ -74,4 +74,4 @@ 'use strict';

it('should not try to check variables', function () {
var source = '.foo { @var: auto; }';
it('should check each rule on its own', function () {
var source = '';
var options = {

@@ -81,2 +81,9 @@ style: 'alpha'

source += '.form-group {';
source += ' margin-bottom: 0;';
source += ' .form-control {';
source += ' height: auto;';
source += ' }';
source += '}';
return spec.parse(source, function (ast) {

@@ -90,3 +97,3 @@ var result = spec.linter.lint(options, ast.root.first);

it('should not try to check variables', function () {
var source = '.foo { @var: auto; }';
var source = '.foo { @b: auto; @a: inherit; }';
var options = {

@@ -93,0 +100,0 @@ style: 'alpha'

@@ -5,2 +5,3 @@ 'use strict';

var spec = require('../util.js').setup();
var parser = require('postcss-selector-parser');

@@ -126,6 +127,6 @@ describe('lesshint', function () {

it('should check parent selectors. #106', function () {
it('should not allow parent selectors when the parent is an element. #106', function () {
var source = 'a { &.active { color: red; } }';
var expected = [{
column: 2,
column: 6,
line: 1,

@@ -141,3 +142,64 @@ message: 'Class selectors should not include a qualifying element.'

});
it('should allow parent selectors when the parent is selector. #118', function () {
var source = '.a { &.active { color: red; } }';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should inspect the parent selector', function () {
var source = '.a { &.active { color: red; } }';
return spec.parse(source, function (ast) {
var node = ast.root.first;
var expected = { startsWith: 'class', endsWith: 'class', hasTag: false };
var result;
parser(function (selectors) {
node.selectorAst = selectors;
result = spec.linter.inspectParent(node.first);
expect(result).to.deep.equal(expected);
}).process(node.selector);
});
});
it('should inspect the parent selector, recognize tag', function () {
var source = 'a { &.active { color: red; } }';
return spec.parse(source, function (ast) {
var node = ast.root.first;
var expected = { startsWith: 'tag', endsWith: 'tag', hasTag: true };
var result;
parser(function (selectors) {
node.selectorAst = selectors;
result = spec.linter.inspectParent(node.first);
expect(result).to.deep.equal(expected);
}).process(node.selector);
});
});
it('should inspect the parent selector, recognize tag and class', function () {
var source = '.b a { &.active { color: red; } }';
return spec.parse(source, function (ast) {
var node = ast.root.first;
var expected = { startsWith: 'class', endsWith: 'tag', hasTag: true };
var result;
parser(function (selectors) {
node.selectorAst = selectors;
result = spec.linter.inspectParent(node.first);
expect(result).to.deep.equal(expected);
}).process(node.selector);
});
});
});
});

@@ -28,7 +28,14 @@ 'use strict';

var source = '.foo {\n color: red; margin-right: 10px; \n}';
var expected = [{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}];
var expected = [
{
column: 2,
line: 2,
message: 'Each property should be on its own line.'
},
{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}
];

@@ -219,7 +226,14 @@ return spec.parse(source, function (ast) {

var source = '.foo {\n color: red; margin-right: 10px; // inline comment\n}';
var expected = [{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}];
var expected = [
{
column: 2,
line: 2,
message: 'Each property should be on its own line.'
},
{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}
];

@@ -235,7 +249,14 @@ return spec.parse(source, function (ast) {

var source = '.foo {\n color: red; margin-right: 10px; /* inline comment */\n}';
var expected = [{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}];
var expected = [
{
column: 2,
line: 2,
message: 'Each property should be on its own line.'
},
{
column: 14,
line: 2,
message: 'Each property should be on its own line.'
}
];

@@ -298,3 +319,20 @@ return spec.parse(source, function (ast) {

});
it('should check each rule on its own', function () {
var source = '';
source += '.foo {\n';
source += ' margin-bottom: 0;\n';
source += '}\n';
source += '.bar {\n';
source += ' height: auto;\n';
source += '}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
});
});

@@ -163,2 +163,15 @@ 'use strict';

it('should not care about spaces before the colon', function () {
var source = '.foo { color : red; }';
var options = {
style: 'one_space'
};
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first.first);
expect(result).to.be.undefined;
});
});
it('should throw on invalid "style" value', function () {

@@ -165,0 +178,0 @@ var source = '.foo { color:red; }';

@@ -65,2 +65,22 @@ 'use strict';

it('should account for multiline rules with commas', function () {
var source = '.foo, \n.bar {}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should account for multiline rules with commas containing pseudo classes', function () {
var source = '.test1,\n.test2:not(.test3),\n.test3:not(.test2) {\n width: 100%;\n}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not allow missing space after comma in mixins', function () {

@@ -79,2 +99,37 @@ var source = '.mixin(@margin,@padding) {}';

});
it('should not allow multiline comma lists when allowNewline is false', function () {
var source = 'font: 14px,\n Roboto,\n #000000;';
var expected = [
{
column: 11,
message: 'Commas should be followed by one space.'
},
{
column: 17,
message: 'Commas should be followed by one space.'
}
];
options.allowNewline = false;
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.deep.equal(expected);
});
});
it('should allow multiline comma lists when allowNewline is true', function () {
var source = 'font: 14px,\n Roboto,\n #000000;';
options.allowNewline = true;
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
}); // "after"

@@ -142,2 +197,14 @@

});
it('should allow multiline comma lists when allowNewline is true', function () {
var source = 'font: 14px\n, Roboto\n, #000000;';
options.allowNewline = true;
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
}); // "before"

@@ -144,0 +211,0 @@

@@ -117,2 +117,12 @@ 'use strict';

it('should not report on negative variables. See #179', function () {
var source = 'margin-left: -@foo;';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not report on font-size/line-height shorthand declaration', function () {

@@ -119,0 +129,0 @@ var source = 'font: 12px/1.5 Arial;';

@@ -328,2 +328,28 @@ 'use strict';

it('should not throw on atrule use', function () {
var source = '.header-def(@rules) {h1,h2,h3,h4,h5,h6 {\n@rules();}}';
var options = {
style: 'one_space'
};
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not check imports', function () {
var source = '@import \'lib/colors\';';
var options = {
style: 'one_space'
};
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should ignore nodes without a following block', function () {

@@ -342,2 +368,15 @@ var source = '.foo();';

it('should ignore atrule nodes without a following block', function () {
var source = '@foo();';
var options = {
style: 'one_space'
};
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should throw on invalid "style" value', function () {

@@ -344,0 +383,0 @@ var source = '.foo{}';

@@ -199,2 +199,12 @@ 'use strict';

it('should allow multiline mixins', function () {
var source = '.mixin(\n@a,\n@b\n) {\n \n}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not allow one space after opening parenthesis in mixins', function () {

@@ -303,2 +313,12 @@ var source = '.mixin( @margin, @padding) {}';

});
it('should allow missing space before closing parenthesis in mixins', function () {
var source = '.mixin(@margin, @padding) {}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
}); // "no_space"

@@ -477,2 +497,12 @@

it('should allow one space before closing paren in multiline mixins', function () {
var source = '.mixin( \n@a,\n@b\n ) {\n \n}';
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should not allow missing space after opening parenthesis in mixins', function () {

@@ -479,0 +509,0 @@ var source = '.mixin(@margin, @padding ) {}';

@@ -19,3 +19,3 @@ 'use strict';

var expected = [{
column: 1,
column: 18,
line: 1,

@@ -91,3 +91,13 @@ message: 'All property declarations should end with a semicolon.'

});
it('should not check imports', function () {
var source = '@import \'lib/colors\';';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first);
expect(result).to.be.undefined;
});
});
});
});

@@ -24,4 +24,24 @@ 'use strict';

it('should not check nodes without params', function () {
var source = '.foo { @bar }';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first.first);
expect(result).to.be.undefined;
});
});
it('should not check nodes without values', function () {
var source = '.foo { @bar() }';
return spec.parse(source, function (ast) {
var result = spec.linter.lint({}, ast.root.first.first);
expect(result).to.be.undefined;
});
});
it('should allow single quotes', function () {
var source = ".foo { background-image: url('img/image.jpg'); }";
var source = '.foo { background-image: url(\'img/image.jpg\'); }';

@@ -28,0 +48,0 @@ return spec.parse(source, function (ast) {

@@ -147,2 +147,15 @@ 'use strict';

it('should not check function arguments', function () {
var source = 'color: rgb(0, 0, 0);';
var options = {
style: 'no_unit'
};
return spec.parse(source, function (ast) {
var result = spec.linter.lint(options, ast.root.first);
expect(result).to.be.undefined;
});
});
it('should throw on invalid "style" value', function () {

@@ -149,0 +162,0 @@ var source = 'margin-right: 0;';

@@ -0,1 +1,3 @@

/*eslint no-console: 0*/
'use strict';

@@ -2,0 +4,0 @@

@@ -11,2 +11,5 @@ 'use strict';

// tests selectors for pseudo classes/selectors. eg. :not(), :active
var rPseudo = /::?[^ ,:.]+/g;
// slightly evil, but it's OK since this is just for specs

@@ -31,2 +34,21 @@ // nodejs caches module.parent as the first module to require it,

return getParser(source).then(function (ast) {
// if we're dealing with a regular Rule (or other) node, which isn't
// an actual Mixin or AtRule, and its selector contains a pseudo
// class or selector, then clean up the raws and params properties.
// if we don't have this here, then the tests never get the same
// modified nodes.
// tracking: https://github.com/webschik/postcss-less/issues/56
// TODO: remove this when issue resolved
ast.root.walk(function (node) {
if (node.params && rPseudo.test(node.selector)) {
delete node.params;
// this just started showing up in postcss-less@0.14.0. not sure
// if it's sticking around, but making sure we're thorough.
if (node.raws.params) {
delete node.raws.params;
}
}
});
callback && callback(ast);

@@ -33,0 +55,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc