Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Commit

Permalink
Migrate to Linter v2 API
Browse files Browse the repository at this point in the history
Move to the v2 Linter API, allowing for cleaner messages and no need for
translation of the messages on Linter's part.
  • Loading branch information
Arcanemagus committed Jul 17, 2017
1 parent 98febbc commit 125a2cc
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 109 deletions.
108 changes: 54 additions & 54 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { dirname } from 'path';
import stylelint from 'stylelint';
import assignDeep from 'assign-deep';
import escapeHTML from 'escape-html';
import { generateRange } from 'atom-linter';
import presetConfig from 'stylelint-config-standard';

Expand All @@ -30,48 +29,19 @@ export function endMeasure(baseName) {
}

export function createRange(editor, data) {
if (!Object.hasOwnProperty.call(data, 'line') && !Object.hasOwnProperty.call(data, 'column')) {
if (!data ||
(!Object.hasOwnProperty.call(data, 'line') && !Object.hasOwnProperty.call(data, 'column'))
) {
// data.line & data.column might be undefined for non-fatal invalid rules,
// e.g.: "block-no-empty": "foo"
// Return `false` so Linter will ignore the range
return false;
// Return a range encompassing the first line of the file
return generateRange(editor);
}

return generateRange(editor, data.line - 1, data.column - 1);
}

export function generateHTMLMessage(message) {
if (!message.rule || message.rule === 'CssSyntaxError') {
return escapeHTML(message.text);
}

const ruleParts = message.rule.split('/');
let url;

if (ruleParts.length === 1) {
// Core rule
url = `http://stylelint.io/user-guide/rules/${ruleParts[0]}`;
} else {
// Plugin rule
const pluginName = ruleParts[0];
// const ruleName = ruleParts[1];

switch (pluginName) {
case 'plugin':
url = 'https://github.com/AtomLinter/linter-stylelint/tree/master/docs/noRuleNamespace.md';
break;
default:
url = 'https://github.com/AtomLinter/linter-stylelint/tree/master/docs/linkingNewRule.md';
}
}

// Escape any HTML in the message, and replace the rule ID with a link
return escapeHTML(message.text).replace(
`(${message.rule})`, `(<a href="${url}">${message.rule}</a>)`
);
}

export const parseResults = (editor, results, filePath, showIgnored) => {
const parseResults = (editor, results, filePath, showIgnored) => {
startMeasure('linter-stylelint: Parsing results');
if (!results) {
endMeasure('linter-stylelint: Parsing results');
Expand All @@ -80,38 +50,67 @@ export const parseResults = (editor, results, filePath, showIgnored) => {
}

const invalidOptions = results.invalidOptionWarnings.map(msg => ({
type: 'Error',
severity: 'error',
text: msg.text,
filePath
excerpt: msg.text,
location: {
file: filePath,
position: createRange(editor)
}
}));

const warnings = results.warnings.map((warning) => {
// Stylelint only allows 'error' and 'warning' as severity values
const severity = !warning.severity || warning.severity === 'error' ? 'Error' : 'Warning';
return {
type: severity,
const message = {
severity: severity.toLowerCase(),
html: generateHTMLMessage(warning),
filePath,
range: createRange(editor, warning)
excerpt: warning.text,
location: {
file: filePath,
position: createRange(editor, warning)
}
};

const ruleParts = warning.rule.split('/');
if (ruleParts.length === 1) {
// Core rule
message.url = `http://stylelint.io/user-guide/rules/${ruleParts[0]}`;
} else {
// Plugin rule
const pluginName = ruleParts[0];
// const ruleName = ruleParts[1];

const linterStylelintURL = 'https://github.com/AtomLinter/linter-stylelint/tree/master/docs';
switch (pluginName) {
case 'plugin':
message.url = `${linterStylelintURL}/noRuleNamespace.md`;
break;
default:
message.url = `${linterStylelintURL}/linkingNewRule.md`;
}
}

return message;
});

const deprecations = results.deprecations.map(deprecation => ({
type: 'Warning',
severity: 'warning',
html: `${escapeHTML(deprecation.text)} (<a href="${deprecation.reference}">reference</a>)`,
filePath
excerpt: deprecation.text,
url: deprecation.reference,
location: {
file: filePath,
position: createRange(editor)
}
}));

const ignored = [];
if (showIgnored && results.ignored) {
ignored.push({
type: 'Warning',
severity: 'warning',
text: 'This file is ignored',
filePath
excerpt: 'This file is ignored',
location: {
file: filePath,
position: createRange(editor)
}
});
}

Expand All @@ -137,11 +136,12 @@ export const runStylelint = async (editor, stylelintOptions, filePath, settings)
if (error.line) {
endMeasure('linter-stylelint: Lint');
return [{
type: 'Error',
severity: 'error',
text: error.reason || error.message,
filePath,
range: createRange(editor, error)
excerpt: error.reason || error.message,
location: {
file: filePath,
position: createRange(editor, error)
}
}];
}

Expand Down
10 changes: 6 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export default {
name: 'stylelint',
grammarScopes: this.baseScopes,
scope: 'file',
lintOnFly: true,
lintsOnChange: true,
lint: async (editor) => {
// Force the dependencies to load if they haven't already
loadDeps();
Expand Down Expand Up @@ -178,10 +178,12 @@ export default {
helpers.endMeasure('linter-stylelint: Lint');
if (this.showIgnored) {
return [{
type: 'Warning',
severity: 'warning',
text: 'This file is ignored',
filePath
excerpt: 'This file is ignored',
location: {
file: filePath,
position: helpers.createRange(editor)
}
}];
}
return [];
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@
"dependencies": {
"assign-deep": "^0.4.5",
"atom-linter": "^10.0.0",
"atom-package-deps": "^4.0.1",
"escape-html": "^1.0.3",
"atom-package-deps": "^4.6.0",
"stylelint": "8.0.0",
"stylelint-config-standard": "^17.0.0"
},
Expand Down Expand Up @@ -90,12 +89,12 @@
}
},
"package-deps": [
"linter"
"linter:2.0.0"
],
"providedServices": {
"linter": {
"versions": {
"1.0.0": "provideLinter"
"2.0.0": "provideLinter"
}
}
}
Expand Down
83 changes: 36 additions & 47 deletions spec/linter-stylelint-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const configStandardPath = path.join(fixtures, 'bad', 'stylelint-config-standard
const warningPath = path.join(fixtures, 'warn', 'warn.css');
const invalidRulePath = path.join(fixtures, 'invalid-rule', 'styles.css');

const blockNoEmpty = 'Unexpected empty block (<a href="http://stylelint.io/user-guide/rules/block-no-empty">block-no-empty</a>)';
const blockNoEmpty = 'Unexpected empty block (block-no-empty)';
const blockNoEmptyUrl = 'http://stylelint.io/user-guide/rules/block-no-empty';

describe('The stylelint provider for Linter', () => {
const lint = require('../lib/index.js').provideLinter().lint;
Expand All @@ -28,12 +29,11 @@ describe('The stylelint provider for Linter', () => {
expect(messages.length).toBeGreaterThan(0);

// test only the first error
expect(messages[0].type).toBe('Error');
expect(messages[0].severity).toBe('error');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(blockNoEmpty);
expect(messages[0].filePath).toBe(configStandardPath);
expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
expect(messages[0].excerpt).toBe(blockNoEmpty);
expect(messages[0].url).toBe(blockNoEmptyUrl);
expect(messages[0].location.file).toBe(configStandardPath);
expect(messages[0].location.position).toEqual([[0, 5], [0, 7]]);
});

it('reports rules set as warnings as a Warning', async () => {
Expand All @@ -43,12 +43,11 @@ describe('The stylelint provider for Linter', () => {
expect(messages.length).toBeGreaterThan(0);

// test only the first error
expect(messages[0].type).toBe('Warning');
expect(messages[0].severity).toBe('warning');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(blockNoEmpty);
expect(messages[0].filePath).toMatch(/.+warn\.css$/);
expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
expect(messages[0].excerpt).toBe(blockNoEmpty);
expect(messages[0].url).toBe(blockNoEmptyUrl);
expect(messages[0].location.file).toMatch(/.+warn\.css$/);
expect(messages[0].location.position).toEqual([[0, 5], [0, 7]]);
});

it('finds nothing wrong with a valid file', async () => {
Expand All @@ -64,12 +63,10 @@ describe('The stylelint provider for Linter', () => {
const messages = await lint(editor);
expect(messages.length).toBe(1);

expect(messages[0].type).toBe('Error');
expect(messages[0].severity).toBe('error');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe('Unknown word (CssSyntaxError)');
expect(messages[0].filePath).toBe(invalidPath);
expect(messages[0].range).toEqual([[0, 0], [0, 3]]);
expect(messages[0].excerpt).toBe('Unknown word (CssSyntaxError)');
expect(messages[0].location.file).toBe(invalidPath);
expect(messages[0].location.position).toEqual([[0, 0], [0, 3]]);
});

it('shows an error on non-fatal stylelint runtime error', async () => {
Expand All @@ -78,12 +75,10 @@ describe('The stylelint provider for Linter', () => {
const messages = await lint(editor);
expect(messages.length).toBe(1);

expect(messages[0].type).toBe('Error');
expect(messages[0].severity).toBe('error');
expect(messages[0].text).toBe(text);
expect(messages[0].html).not.toBeDefined();
expect(messages[0].filePath).toBe(invalidRulePath);
expect(messages[0].range).not.toBeDefined();
expect(messages[0].excerpt).toBe(text);
expect(messages[0].location.file).toBe(invalidRulePath);
expect(messages[0].location.position).toEqual([[0, 0], [0, 6]]);
});

it('shows an error notification for a fatal stylelint runtime error', async () => {
Expand Down Expand Up @@ -138,12 +133,10 @@ describe('The stylelint provider for Linter', () => {
const messages = await lint(editor);
expect(messages.length).toBe(1);

expect(messages[0].type).toBe('Warning');
expect(messages[0].severity).toBe('warning');
expect(messages[0].text).toBe('This file is ignored');
expect(messages[0].html).not.toBeDefined();
expect(messages[0].filePath).toBe(ignorePath);
expect(messages[0].range).not.toBeDefined();
expect(messages[0].excerpt).toBe('This file is ignored');
expect(messages[0].location.file).toBe(ignorePath);
expect(messages[0].location.position).toEqual([[0, 0], [0, 7]]);
});

it("doesn't show a message when not asked to", async () => {
Expand All @@ -165,12 +158,11 @@ describe('The stylelint provider for Linter', () => {
expect(messages.length).toBeGreaterThan(0);

// test only the first error
expect(messages[0].type).toBe('Warning');
expect(messages[0].severity).toBe('warning');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(blockNoEmpty);
expect(messages[0].filePath).toBe(warningPath);
expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
expect(messages[0].excerpt).toBe(blockNoEmpty);
expect(messages[0].url).toBe(blockNoEmptyUrl);
expect(messages[0].location.file).toBe(warningPath);
expect(messages[0].location.position).toEqual([[0, 5], [0, 7]]);
});

describe('works with Less files and', () => {
Expand All @@ -188,12 +180,11 @@ describe('The stylelint provider for Linter', () => {
expect(messages.length).toBeGreaterThan(0);

// test only the first error
expect(messages[0].type).toBe('Error');
expect(messages[0].severity).toBe('error');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(blockNoEmpty);
expect(messages[0].filePath).toBe(badLess);
expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
expect(messages[0].excerpt).toBe(blockNoEmpty);
expect(messages[0].url).toBe(blockNoEmptyUrl);
expect(messages[0].location.file).toBe(badLess);
expect(messages[0].location.position).toEqual([[0, 5], [0, 7]]);
});

it('finds nothing wrong with a valid file', async () => {
Expand All @@ -217,12 +208,11 @@ describe('The stylelint provider for Linter', () => {
expect(messages.length).toBeGreaterThan(0);

// test only the first error
expect(messages[0].type).toBe('Error');
expect(messages[0].severity).toBe('error');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(blockNoEmpty);
expect(messages[0].filePath).toBe(issuesPostCSS);
expect(messages[0].range).toEqual([[0, 5], [0, 7]]);
expect(messages[0].excerpt).toBe(blockNoEmpty);
expect(messages[0].url).toBe(blockNoEmptyUrl);
expect(messages[0].location.file).toBe(issuesPostCSS);
expect(messages[0].location.position).toEqual([[0, 5], [0, 7]]);
});

it('finds nothing wrong with a valid file', async () => {
Expand All @@ -241,15 +231,14 @@ describe('The stylelint provider for Linter', () => {
});

it('shows lint messages when found', async () => {
const nlzMessage = 'Expected a leading zero (<a href="http://stylelint.io/user-guide/rules/number-leading-zero">number-leading-zero</a>)';
const editor = await atom.workspace.open(badSugarSS);
const messages = await lint(editor);
expect(messages[0].type).toBe('Error');

expect(messages[0].severity).toBe('error');
expect(messages[0].text).not.toBeDefined();
expect(messages[0].html).toBe(nlzMessage);
expect(messages[0].filePath).toBe(badSugarSS);
expect(messages[0].range).toEqual([[1, 38], [1, 40]]);
expect(messages[0].excerpt).toBe('Expected a leading zero (number-leading-zero)');
expect(messages[0].url).toBe('http://stylelint.io/user-guide/rules/number-leading-zero');
expect(messages[0].location.file).toBe(badSugarSS);
expect(messages[0].location.position).toEqual([[1, 38], [1, 40]]);
});

it('finds nothing wrong with a valid file', async () => {
Expand Down

0 comments on commit 125a2cc

Please sign in to comment.