Permalink
Browse files

Better error handling.

- When cosmiconfig throws
- When stylelint throws
- When stylelint return errors of invalid rules.
  • Loading branch information...
1 parent ef284cf commit 9bafa6dd04b3b93e4f91d1b83f1e64180a363868 @satazor satazor committed Dec 31, 2015
View
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
View
@@ -0,0 +1,2 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text eol=lf
View
@@ -1,7 +1,7 @@
'use babel';
import path from 'path';
-import { Range } from 'atom';
+import { rangeFromLineNumber } from 'atom-linter';
import stylelint from 'stylelint';
import assign from 'deep-assign';
import cosmiconfig from 'cosmiconfig';
@@ -25,11 +25,19 @@ export const config = {
const usePreset = () => atom.config.get('linter-stylelint.usePreset');
const presetConfig = () => atom.config.get('linter-stylelint.presetConfig');
+function createRange(editor, data) {
+ // data.line & data.column might be undefined for non-fatal invalid rules,
+ // e.g.: "block-no-empty": "foo"
+ // so we convert undefineds to 1, which will pass 0 to the underlying rangeFromLineNumber
+ // which selects the first line of the file
+ return rangeFromLineNumber(editor, (data.line || 1) - 1, (data.column || 1) - 1);
+}
+
export const activate = () => {
require('atom-package-deps').install('linter-stylelint');
};
-const runStylelint = (options, filePath) => {
+const runStylelint = (editor, options, filePath) => {
return stylelint.lint(options).then(data => {
const result = data.results.shift();
@@ -38,32 +46,30 @@ const runStylelint = (options, filePath) => {
}
return result.warnings.map(warning => {
- const range = new Range(
- [warning.line - 1, warning.column - 1],
- [warning.line - 1, warning.column + 1000]
- );
-
return {
- type: (warning.severity === 'error') ? 'Error' : 'Warning',
+ type: (!warning.severity || warning.severity === 'error') ? 'Error' : 'Warning',
text: warning.text,
filePath,
- range
+ range: createRange(editor, warning)
};
});
- }).catch(error => {
- if (error.line && error.reason) {
- const range = new Range(
- [error.line - 1, 0],
- [error.line - 1, 1000]
- );
-
+ }, error => {
+ // was it a code parsing error?
+ if (error.line) {
return [{
type: 'Error',
- text: error.reason,
+ text: error.reason || error.message,
filePath,
- range
+ range: createRange(editor, error)
}];
}
+
+ // if we got here, stylelint found something really wrong with the configuration,
+ // such as extending an invalid configuration
+ atom.notifications.addError('Unable to run stylelint', {
+ detail: error.reason || error.message,
+ dismissable: true
+ });
});
};
@@ -95,29 +101,24 @@ export const provideLinter = () => {
options.syntax = 'scss';
}
- return new Promise((resolve) => {
- cosmiconfig('stylelint', {
- cwd: path.dirname(filePath)
- }).then(result => {
- if (result) {
- options.config = assign(rules, result.config);
- options.configBasedir = path.dirname(result.filepath);
- }
-
- resolve(runStylelint(options, filePath));
- }).catch(error => {
- atom.notifications.addWarning(`Invalid config file`, {
- detail: error.message || `Failed to parse config file`,
- dismissable: true
- });
-
- if (usePreset()) {
- resolve(runStylelint(options, filePath));
- } else {
- resolve([]);
- }
+ return cosmiconfig('stylelint', {
+ cwd: path.dirname(filePath)
+ }).then(result => {
+ if (result) {
+ options.config = assign(rules, result.config);
+ options.configBasedir = path.dirname(result.filepath);
+ }
+
+ return runStylelint(editor, options, filePath);
+ }, error => {
+ // if we got here, cosmiconfig failed to parse the configuration
+ // there's no point of re-linting if usePreset is true, because the user does not have
+ // the complete set of desired rules parsed
+ atom.notifications.addError('Unable to parse stylelint configuration', {
+ detail: error.message,
+ dismissable: true
});
- });
+ }).then(result => result || []);
}
};
};
No changes.
@@ -0,0 +1 @@
+>>>
@@ -0,0 +1,3 @@
+.div {
+ color: blue;
+}
@@ -0,0 +1,3 @@
+{
+ "extends": "some-module-that-will-never-exist"
+}
@@ -0,0 +1,3 @@
+.div {
+ color: blue;
+}
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "block-no-empty": "foo"
+ }
+}
@@ -0,0 +1,3 @@
+.div {
+ color: blue;
+}
@@ -7,117 +7,123 @@ describe('The stylelint provider for Linter', () => {
beforeEach(() => {
atom.workspace.destroyActivePaneItem();
- waitsForPromise(() => {
- atom.packages.activatePackage('linter-stylelint');
- return atom.packages.activatePackage('language-css').then(() =>
- atom.workspace.open(path.join(__dirname, 'fixtures', 'good.css'))
- );
- });
atom.config.set('linter-stylelint.usePreset', true);
- });
- describe('checks bad.css and', () => {
- let editor = null;
- beforeEach(() => {
- waitsForPromise(() => {
- return atom.workspace.open(path.join(__dirname, 'fixtures', 'bad', 'bad.css')).then(openEditor => {
- editor = openEditor;
- });
+ waitsForPromise(() => {
+ return atom.packages.activatePackage('linter-stylelint').then(() => {
+ return atom.packages.activatePackage('language-css');
});
});
+ });
+
+ it('detects invalid coding style in bad.css and report as error', () => {
+ waitsForPromise(() => {
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'bad', 'bad.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toBeGreaterThan(0);
- it('finds at least one message', () => {
- waitsForPromise(() => {
- return lint(editor).then(messages => {
- expect(messages.length).toBeGreaterThan(0);
- });
+ // test only the first error
+ expect(messages[0].type).toEqual('Error');
+ expect(messages[0].text).toEqual('Unexpected empty block (block-no-empty)');
+ expect(messages[0].filePath).toMatch(/.+bad\.css$/);
+ expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
});
});
+ });
+
+ it('detects invalid coding style in warn.css and report as warning', () => {
+ atom.config.set('linter-stylelint.usePreset', false);
+
+ waitsForPromise(() => {
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'warn', 'warn.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toBeGreaterThan(0);
- it('verifies the first message', () => {
- waitsForPromise(() => {
- return lint(editor).then(messages => {
- expect(messages[0].type).toBeDefined();
- expect(messages[0].type).toEqual('Error');
- expect(messages[0].text).toBeDefined();
- expect(messages[0].text).toEqual('Unexpected empty block (block-no-empty)');
- expect(messages[0].filePath).toBeDefined();
- expect(messages[0].filePath).toMatch(/.+bad\.css$/);
- expect(messages[0].range).toBeDefined();
- expect(messages[0].range).toEqual({
- start: { row: 0, column: 5 },
- end: { row: 0, column: 1006 }
- });
- });
+ // test only the first error
+ expect(messages[0].type).toEqual('Warning');
+ expect(messages[0].text).toEqual('Unexpected empty block (block-no-empty)');
+ expect(messages[0].filePath).toMatch(/.+warn\.css$/);
+ expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
});
});
});
- describe('checks warn.css and', () => {
- let editor = null;
- beforeEach(() => {
- waitsForPromise(() => {
- return atom.workspace.open(path.join(__dirname, 'fixtures', 'warn', 'warn.css')).then(openEditor => {
- editor = openEditor;
- });
+ it('finds nothing wrong with a valid file (good.css)', () => {
+ waitsForPromise(() => {
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'good', 'good.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toEqual(0);
});
-
- atom.config.set('linter-stylelint.usePreset', false);
});
+ });
+
+ it('show CSS syntax error with an invalid file (invalid.css)', () => {
+ waitsForPromise(() => {
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'invalid', 'invalid.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toEqual(1);
- it('finds at least one message', () => {
- waitsForPromise(() => {
- return lint(editor).then(messages => {
- expect(messages.length).toBeGreaterThan(0);
- });
+ expect(messages[0].type).toEqual('Error');
+ expect(messages[0].text).toEqual('Unknown word');
+ expect(messages[0].filePath).toMatch(/.+invalid\.css$/);
+ expect(messages[0].range).toEqual([[0, 0], [0, 3]]);
});
});
+ });
+
+ it('show error on non-fatal stylelint runtime error', () => {
+ atom.config.set('linter-stylelint.usePreset', false);
+
+ waitsForPromise(() => {
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'invalid-rule', 'styles.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toEqual(1);
- it('verifies the first message', () => {
- waitsForPromise(() => {
- return lint(editor).then(messages => {
- expect(messages[0].type).toBeDefined();
- expect(messages[0].type).toEqual('Warning');
- expect(messages[0].text).toBeDefined();
- expect(messages[0].text).toEqual('Unexpected empty block (block-no-empty)');
- expect(messages[0].filePath).toBeDefined();
- expect(messages[0].filePath).toMatch(/.+warn\.css$/);
- expect(messages[0].range).toBeDefined();
- expect(messages[0].range).toEqual({
- start: { row: 0, column: 5 },
- end: { row: 0, column: 1006 }
- });
- });
+ expect(messages[0].type).toEqual('Error');
+ expect(messages[0].text).toEqual('Unexpected option value "foo" for rule "block-no-empty"');
+ expect(messages[0].filePath).toMatch(/.+styles\.css$/);
+ expect(messages[0].range).toEqual([[0, 0], [0, 6]]);
});
});
});
- it('finds nothing wrong with a valid file', () => {
+ it('show error notification on fatal stylelint runtime error', () => {
+ atom.config.set('linter-stylelint.usePreset', false);
+ spyOn(atom.notifications, 'addError').andCallFake(() => {});
+
waitsForPromise(() => {
- return atom.workspace.open(path.join(__dirname, 'fixtures', 'good', 'good.css')).then(editor => {
- return lint(editor).then(messages => {
- expect(messages.length).toEqual(0);
- });
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'invalid-extends', 'styles.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toEqual(0);
+
+ expect(atom.notifications.addError.calls.length).toEqual(1);
+ expect(atom.notifications.addError.mostRecentCall.args[0]).toEqual('Unable to run stylelint');
+ expect(atom.notifications.addError.mostRecentCall.args[1].detail).toContain('Could not find "some-module-that-will-never-exist". Do you need a `configBasedir`?');
+ expect(atom.notifications.addError.mostRecentCall.args[1].dismissable).toEqual(true);
});
});
});
- it('show CSS syntax error with an invalid file', () => {
+ it('show error notification on an broken syntax configuration', () => {
+ atom.config.set('linter-stylelint.usePreset', false);
+ spyOn(atom.notifications, 'addError').andCallFake(() => {});
+
waitsForPromise(() => {
- return atom.workspace.open(path.join(__dirname, 'fixtures', 'invalid', 'invalid.css')).then(editor => {
- return lint(editor).then(messages => {
- expect(messages[0].type).toBeDefined();
- expect(messages[0].type).toEqual('Error');
- expect(messages[0].text).toBeDefined();
- expect(messages[0].text).toEqual('Unknown word');
- expect(messages[0].filePath).toBeDefined();
- expect(messages[0].filePath).toMatch(/.+invalid\.css$/);
- expect(messages[0].range).toBeDefined();
- expect(messages[0].range).toEqual({
- start: { row: 0, column: 0 },
- end: { row: 0, column: 1000 }
- });
- });
+ return atom.workspace.open(path.join(__dirname, 'fixtures', 'invalid-config', 'styles.css')).then(editor => {
+ return lint(editor);
+ }).then(messages => {
+ expect(messages.length).toEqual(0);
+
+ expect(atom.notifications.addError.calls.length).toEqual(1);
+ expect(atom.notifications.addError.mostRecentCall.args[0]).toEqual('Unable to parse stylelint configuration');
+ expect(atom.notifications.addError.mostRecentCall.args[1].detail).toContain('>>>');
+ expect(atom.notifications.addError.mostRecentCall.args[1].dismissable).toEqual(true);
});
});
});

0 comments on commit 9bafa6d

Please sign in to comment.