Skip to content

Commit 2eb1328

Browse files
committed
Merge pull request #491 from sasstools/release/1.5.0
Release/1.5.0
2 parents da95f42 + aad6be6 commit 2eb1328

20 files changed

+461
-40
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Sass Lint Changelog
22

3+
## v1.5.0
4+
**January 28, 2016**
5+
6+
New year blues
7+
8+
**Changes**
9+
* AST parse errors will now be returned to the user as `Fatal` lint errors this prevents un-handled errors breaking builds [#459](https://github.com/sasstools/sass-lint/pull/459)
10+
* Sass-lint plugin for Brackets added to the README [#470](https://github.com/sasstools/sass-lint/issues/470)
11+
* Sass-lint plugin for IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm, added to the README [#484](https://github.com/sasstools/sass-lint/issues/484)
12+
13+
14+
**CLI**
15+
* Updated error codes, whenever errors are present even when cli is using the `--no-exit` flag a error code of 1 will be output [#221](https://github.com/sasstools/sass-lint/issues/221)
16+
17+
**Fixes**
18+
* Fixed an issue where an error of `next is undefined` would be thrown in the `space-after-colon` rule [#468](https://github.com/sasstools/sass-lint/issues/468)
19+
* Fixed an issue with negative z-index values in the `space-around-operator` rule [#454](https://github.com/sasstools/sass-lint/issues/454)
20+
* Fixed another minor issue with `space-around-operator` to prevent a possible crash [#483](https://github.com/sasstools/sass-lint/issues/483)
21+
322
## v1.4.0
423
**December 10, 2015**
524

@@ -39,6 +58,7 @@ The long overdue update!
3958
* [Dan Purdy](https://github.com/DanPurdy)
4059
* [Ben Rothman](https://github.com/benthemonkey)
4160
* [Don Abrams](https://github.com/donabrams)
61+
* [Andrew Hays](https://github.com/Dru89)
4262
* [Kaelig](https://github.com/kaelig)
4363

4464
**A big thankyou to everyone who reported issues or contributed to the discussion around issues**

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ Our AST is [Gonzales-PE](https://github.com/tonyganch/gonzales-pe/tree/dev). Eac
5454

5555
* [Atom](https://atom.io/packages/linter-sass-lint)
5656
* [Sublime Text](https://github.com/skovhus/SublimeLinter-contrib-sass-lint)
57+
* [Brackets](https://github.com/petetnt/brackets-sass-lint)
58+
* [IntelliJ IDEA, RubyMine, WebStorm, PhpStorm, PyCharm](https://github.com/idok/sass-lint-plugin)

bin/sass-lint.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ var program = require('commander'),
77

88
var configPath,
99
ignores,
10-
configOptions = {};
10+
configOptions = {},
11+
exitCode = 0;
1112

1213
var detectPattern = function (pattern) {
1314
var detects;
@@ -18,6 +19,10 @@ var detectPattern = function (pattern) {
1819
lint.outputResults(detects, configOptions, configPath);
1920
}
2021

22+
if (lint.errorCount(detects).count) {
23+
exitCode = 1;
24+
}
25+
2126
if (program.exit) {
2227
lint.failOnError(detects);
2328
}
@@ -86,3 +91,7 @@ else {
8691
detectPattern(path);
8792
});
8893
}
94+
95+
process.on('exit', function () {
96+
process.exit(exitCode); // eslint-disable-line no-process-exit
97+
});

index.js

Lines changed: 127 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,119 @@ var slConfig = require('./lib/config'),
66
slRules = require('./lib/rules'),
77
glob = require('glob'),
88
path = require('path'),
9-
jsonFormatter = require('eslint/lib/formatters/json'),
109
fs = require('fs-extra');
1110

12-
1311
var sassLint = function (config) {
1412
config = require('./lib/config')(config);
1513
return;
1614
};
1715

16+
/**
17+
* Takes any user specified options and a configPath
18+
* which returns a compiled config object
19+
*
20+
* @param {object} config user specified rules/options passed in
21+
* @param {string} configPath path to a config file
22+
* @returns {object} the compiled config object
23+
*/
1824
sassLint.getConfig = function (config, configPath) {
1925
return slConfig(config, configPath);
2026
};
2127

22-
sassLint.resultCount = function (results) {
23-
var flagCount = 0,
24-
jsonResults = JSON.parse(jsonFormatter(results));
28+
/**
29+
* Parses our results object to count errors and return
30+
* paths to files with detected errors.
31+
*
32+
* @param {object} results our results object
33+
* @returns {object} errors object containing the error count and paths for files incl. errors
34+
*/
35+
sassLint.errorCount = function (results) {
36+
var errors = {
37+
count: 0,
38+
files: []
39+
};
2540

41+
results.forEach(function (result) {
42+
if (result.errorCount) {
43+
errors.count += result.errorCount;
44+
errors.files.push(result.filePath);
45+
}
46+
});
2647

27-
for (var i = 0; i < jsonResults.length; i++) {
28-
flagCount += (jsonResults[i].warningCount + jsonResults[i].errorCount);
29-
}
48+
return errors;
49+
};
50+
51+
/**
52+
* Parses our results object to count warnings and return
53+
* paths to files with detected warnings.
54+
*
55+
* @param {object} results our results object
56+
* @returns {object} warnings object containing the error count and paths for files incl. warnings
57+
*/
58+
sassLint.warningCount = function (results) {
59+
var warnings = {
60+
count: 0,
61+
files: []
62+
};
63+
64+
results.forEach(function (result) {
65+
if (result.warningCount) {
66+
warnings.count += result.warningCount;
67+
warnings.files.push(result.filePath);
68+
}
69+
});
3070

31-
return flagCount;
71+
return warnings;
3272
};
3373

74+
/**
75+
* Parses our results object to count warnings and errors and return
76+
* a cumulative count of both
77+
*
78+
* @param {object} results our results object
79+
* @returns {int} the cumulative count of errors and warnings detected
80+
*/
81+
sassLint.resultCount = function (results) {
82+
var warnings = this.warningCount(results),
83+
errors = this.errorCount(results);
84+
85+
return warnings.count + errors.count;
86+
};
87+
88+
/**
89+
* Runs each rule against our AST tree and returns our main object of detected
90+
* errors, warnings, messages and filenames.
91+
*
92+
* @param {object} file file object from fs.readFileSync
93+
* @param {object} options user specified rules/options passed in
94+
* @param {string} configPath path to a config file
95+
* @returns {object} an object containing error & warning counts plus lint messages for each parsed file
96+
*/
3497
sassLint.lintText = function (file, options, configPath) {
3598
var rules = slRules(this.getConfig(options, configPath)),
36-
ast = groot(file.text, file.format, file.filename),
99+
ast = {},
37100
detects,
38101
results = [],
39102
errors = 0,
40103
warnings = 0;
41104

42-
if (ast.content.length > 0) {
105+
try {
106+
ast = groot(file.text, file.format, file.filename);
107+
}
108+
catch (e) {
109+
var line = e.line || 1;
110+
errors++;
111+
112+
results = [{
113+
ruleId: 'Fatal',
114+
line: line,
115+
column: 1,
116+
message: e.message,
117+
severity: 2
118+
}];
119+
}
120+
121+
if (ast.content && ast.content.length > 0) {
43122
rules.forEach(function (rule) {
44123
detects = rule.rule.detect(ast, rule);
45124
results = results.concat(detects);
@@ -54,7 +133,6 @@ sassLint.lintText = function (file, options, configPath) {
54133
});
55134
}
56135

57-
58136
results.sort(helpers.sortDetects);
59137

60138
return {
@@ -65,6 +143,16 @@ sassLint.lintText = function (file, options, configPath) {
65143
};
66144
};
67145

146+
/**
147+
* Takes a glob pattern or target string and creates an array of files as targets for
148+
* linting taking into account any user specified ignores. For each resulting file sassLint.lintText
149+
* is called which returns an object of results for that file which we push to our results object.
150+
*
151+
* @param {string} files a glob pattern or single file path as a lint target
152+
* @param {object} options user specified rules/options passed in
153+
* @param {string} configPath path to a config file
154+
* @returns {object} results object containing all results
155+
*/
68156
sassLint.lintFiles = function (files, options, configPath) {
69157
var that = this,
70158
results = [],
@@ -99,7 +187,14 @@ sassLint.lintFiles = function (files, options, configPath) {
99187
return results;
100188
};
101189

102-
190+
/**
191+
* Handles formatting of results using EsLint formatters
192+
*
193+
* @param {object} results our results object
194+
* @param {object} options user specified rules/options passed in
195+
* @param {string} configPath path to a config file
196+
* @returns {object} results our results object in the user specified format
197+
*/
103198
sassLint.format = function (results, options, configPath) {
104199
var config = this.getConfig(options, configPath),
105200
format = config.options.formatter.toLowerCase();
@@ -109,6 +204,15 @@ sassLint.format = function (results, options, configPath) {
109204
return formatted(results);
110205
};
111206

207+
/**
208+
* Handles outputting results whether this be straight to the console/stdout or to a file.
209+
* Passes results to the format function to ensure results are output in the chosen format
210+
*
211+
* @param {object} results our results object
212+
* @param {object} options user specified rules/options passed in
213+
* @param {string} configPath path to a config file
214+
* @returns {object} results our results object
215+
*/
112216
sassLint.outputResults = function (results, options, configPath) {
113217
var config = this.getConfig(options, configPath);
114218

@@ -132,16 +236,18 @@ sassLint.outputResults = function (results, options, configPath) {
132236
return results;
133237
};
134238

239+
/**
240+
* Throws an error if there are any errors detected. The error includes a count of all errors
241+
* and a list of all files that include errors.
242+
*
243+
* @param {object} results our results object
244+
* @returns {void}
245+
*/
135246
sassLint.failOnError = function (results) {
136-
var result,
137-
i;
247+
var errorCount = this.errorCount(results);
138248

139-
for (i = 0; i < results.length; i++) {
140-
result = results[i];
141-
142-
if (result.errorCount > 0) {
143-
throw new Error(result.errorCount + ' errors detected in ' + result.filePath);
144-
}
249+
if (errorCount.count > 0) {
250+
throw new Error(errorCount.count + ' errors were detected in \n- ' + errorCount.files.join('\n- '));
145251
}
146252
};
147253

lib/groot.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
var gonzales = require('gonzales-pe');
77

88
module.exports = function (text, syntax, filename) {
9-
var fileInfo = filename ? ' at ' + filename : '',
10-
tree;
9+
var tree;
1110

1211
// Run `.toString()` to allow Buffers to be passed in
1312
text = text.toString();
@@ -18,11 +17,20 @@ module.exports = function (text, syntax, filename) {
1817
});
1918
}
2019
catch (e) {
21-
throw new Error('Parsing error' + fileInfo + ': ' + e.message);
20+
throw {
21+
message: e.message,
22+
file: filename,
23+
line: e.line
24+
};
2225
}
2326

2427
if (typeof tree === 'undefined') {
25-
throw new Error('Undefined tree' + fileInfo + ': ' + text.toString() + ' => ' + tree.toString());
28+
throw {
29+
message: 'Undefined tree',
30+
file: filename,
31+
text: text.toString(),
32+
tree: tree.toString()
33+
};
2634
}
2735

2836
return tree;

lib/rules.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,30 @@ module.exports = function (config) {
2727
Object.keys(config.rules).forEach(function (rule) {
2828
var fullRule = config.rules[rule],
2929
loadRule,
30+
severity,
3031
options,
3132
ruleSearch;
33+
34+
if (typeof fullRule === 'number') {
35+
severity = fullRule;
36+
options = {};
37+
}
38+
else {
39+
severity = fullRule[0];
40+
options = fullRule[1];
41+
}
42+
3243
// Only seek out rules that are enabled
33-
if ((typeof fullRule === 'number' && fullRule !== 0) || (typeof fullRule === 'object' && fullRule[0] !== 0)) {
44+
if (severity !== 0) {
3445
ruleSearch = searchArray(rules, rule);
3546
if (ruleSearch >= 0) {
3647
loadRule = require(rules[ruleSearch]);
3748

38-
options = typeof fullRule === 'object' ? fullRule[1] : {};
39-
4049
options = merge.recursive(true, loadRule.defaults, options);
4150

4251
handlers.push({
4352
'rule': loadRule,
44-
'severity': typeof fullRule === 'number' ? fullRule : fullRule[0],
53+
'severity': severity,
4554
'options': options
4655
});
4756
}

lib/rules/extends-before-declarations.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ module.exports = {
1313
var lastDeclaration = null;
1414

1515
block.forEach(function (item, j) {
16-
if (item.type === 'include' || item.type === 'extend') {
16+
if ((item.type === 'include' || item.type === 'extend') &&
17+
item.first('atkeyword')) {
1718
if (item.first('atkeyword').first('ident').content === 'extend') {
1819
if (j > lastDeclaration && lastDeclaration !== null) {
1920
item.forEach('simpleSelector', function () {

lib/rules/space-after-colon.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ module.exports = {
1414
if (delimiter.content === ':') {
1515
var next = parent.content[i + 1];
1616

17-
if (next.is('space')) {
17+
if (next && next.is('space')) {
1818
if (!parser.options.include) {
1919
result = helpers.addUnique(result, {
2020
'ruleId': parser.rule.name,

0 commit comments

Comments
 (0)