Comparing version 2.0.0-rc to 2.0.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
364347
13
111
7828
1
261
8
+ Addedpostcss-less@0.14.0(transitive)
- Removedstrip-bom@^2.0.0
- Removedis-utf8@0.2.1(transitive)
- Removedpostcss-less@0.10.0(transitive)
- Removedstrip-bom@2.0.0(transitive)
Updatedminimatch@^3.0.2
Updatedpostcss-less@^0.14.0