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

Commit 9c0a140

Browse files
committed
Rewrite to use mainline CSSLint
Rewrite the provider to use mainline csslint instead of the custom fork. Includes: * BREAKING: Disabling of linting on changes * Updating to Linter v2 API * Allowing users to specify the path to CSSLint. Uses project local first, bundled second if not specified.
1 parent 40f69df commit 9c0a140

File tree

6 files changed

+208
-87
lines changed

6 files changed

+208
-87
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.DS_Store
22
.idea
33
npm-debug.log
4-
node_modules
4+
node_modules/*
55
.github_changelog_generator

lib/main.js

Lines changed: 141 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,55 @@
11
'use babel';
22

3-
/* eslint-disable import/extensions, import/no-extraneous-dependencies */
3+
// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions
44
import { CompositeDisposable } from 'atom';
5-
/* eslint-enable import/extensions, import/no-extraneous-dependencies */
5+
import * as path from 'path';
66

7-
let helpers = null;
8-
let path = null;
7+
// Dependencies
8+
let helpers;
9+
let fs;
10+
11+
// Internal Variables
12+
let bundledCsslintPath;
13+
14+
const loadDeps = () => {
15+
if (!helpers) {
16+
helpers = require('atom-linter');
17+
}
18+
if (!fs) {
19+
fs = require('fs');
20+
}
21+
};
922

1023
export default {
1124
activate() {
12-
require('atom-package-deps').install('linter-csslint');
25+
this.idleCallbacks = new Set();
26+
let depsCallbackID;
27+
const installLinterCsslintDeps = () => {
28+
this.idleCallbacks.delete(depsCallbackID);
29+
if (!atom.inSpecMode()) {
30+
require('atom-package-deps').install('linter-csslint');
31+
}
32+
loadDeps();
33+
34+
// FIXME: Remove this after a few versions
35+
if (atom.config.get('linter-csslint.disableTimeout')) {
36+
atom.config.unset('linter-csslint.disableTimeout');
37+
}
38+
};
39+
depsCallbackID = window.requestIdleCallback(installLinterCsslintDeps);
40+
this.idleCallbacks.add(depsCallbackID);
1341

1442
this.subscriptions = new CompositeDisposable();
1543
this.subscriptions.add(
16-
atom.config.observe('linter-csslint.disableTimeout', (value) => {
17-
this.disableTimeout = value;
44+
atom.config.observe('linter-csslint.executablePath', (value) => {
45+
this.executablePath = value;
1846
}),
1947
);
2048
},
2149

2250
deactivate() {
51+
this.idleCallbacks.forEach(callbackID => window.cancelIdleCallback(callbackID));
52+
this.idleCallbacks.clear();
2353
this.subscriptions.dispose();
2454
},
2555

@@ -28,77 +58,126 @@ export default {
2858
name: 'CSSLint',
2959
grammarScopes: ['source.css', 'source.html'],
3060
scope: 'file',
31-
lintOnFly: true,
32-
lint(textEditor) {
33-
if (!helpers) {
34-
helpers = require('atom-linter');
35-
}
36-
if (!path) {
37-
path = require('path');
38-
}
61+
lintsOnChange: false,
62+
lint: async (textEditor) => {
63+
loadDeps();
3964
const filePath = textEditor.getPath();
4065
const text = textEditor.getText();
41-
if (text.length === 0) {
42-
return Promise.resolve([]);
66+
if (!filePath || text.length === 0) {
67+
// Empty or unsaved file
68+
return [];
4369
}
44-
const parameters = ['--format=json', '-'];
45-
const exec = path.join(__dirname, '..', 'node_modules', 'atomlinter-csslint', 'cli.js');
70+
71+
const parameters = [
72+
'--format=json',
73+
filePath,
74+
];
75+
4676
const projectPath = atom.project.relativizePath(filePath)[0];
4777
let cwd = projectPath;
48-
if (!(cwd)) {
78+
if (!cwd) {
4979
cwd = path.dirname(filePath);
5080
}
51-
const options = { stdin: text, cwd };
52-
if (this.disableTimeout) {
53-
options.timeout = Infinity;
81+
82+
const execOptions = {
83+
cwd,
84+
uniqueKey: `linter-csslint::${filePath}`,
85+
timeout: 1000 * 30, // 30 seconds
86+
ignoreExitCode: true,
87+
};
88+
89+
const execPath = this.determineExecPath(this.executablePath, projectPath);
90+
91+
const output = await helpers.exec(execPath, parameters, execOptions);
92+
93+
if (textEditor.getText() !== text) {
94+
// The editor contents have changed, tell Linter not to update
95+
return null;
5496
}
55-
return helpers.execNode(exec, parameters, options).then((output) => {
56-
if (textEditor.getText() !== text) {
57-
// The editor contents have changed, tell Linter not to update
58-
return null;
59-
}
6097

61-
const toReturn = [];
62-
if (output.length < 1) {
63-
// No output, no errors
64-
return toReturn;
98+
const toReturn = [];
99+
100+
if (output.length < 1) {
101+
// No output, no errors
102+
return toReturn;
103+
}
104+
105+
let lintResult;
106+
try {
107+
lintResult = JSON.parse(output);
108+
} catch (e) {
109+
const excerpt = 'Invalid response received from CSSLint, check ' +
110+
'your console for more details.';
111+
return [{
112+
severity: 'error',
113+
excerpt,
114+
location: {
115+
file: filePath,
116+
position: helpers.generateRange(textEditor, 0),
117+
},
118+
}];
119+
}
120+
121+
if (lintResult.messages.length < 1) {
122+
// Output, but no errors found
123+
return toReturn;
124+
}
125+
126+
lintResult.messages.forEach((data) => {
127+
let line;
128+
let col;
129+
if (!(data.line && data.col)) {
130+
// Use the file start if a location wasn't defined
131+
[line, col] = [0, 0];
132+
} else {
133+
[line, col] = [data.line - 1, data.col - 1];
65134
}
66135

67-
const lintResult = JSON.parse(output);
136+
const severity = data.type === 'error' ? 'error' : 'warning';
68137

69-
if (lintResult.messages.length < 1) {
70-
// Output, but no errors found
71-
return toReturn;
138+
const msg = {
139+
severity,
140+
excerpt: data.message,
141+
location: {
142+
file: filePath,
143+
position: helpers.generateRange(textEditor, line, col),
144+
},
145+
};
146+
if (data.rule.id && data.rule.desc) {
147+
msg.details = `${data.rule.desc} (${data.rule.id})`;
148+
}
149+
if (data.rule.url) {
150+
msg.url = data.rule.url;
72151
}
73152

74-
lintResult.messages.forEach((data) => {
75-
let line;
76-
let col;
77-
if (!(data.line && data.col)) {
78-
// Use the file start if a location wasn't defined
79-
[line, col] = [0, 0];
80-
} else {
81-
[line, col] = [data.line - 1, data.col - 1];
82-
}
83-
84-
const msg = {
85-
type: data.type.charAt(0).toUpperCase() + data.type.slice(1),
86-
text: data.message,
87-
filePath,
88-
range: helpers.generateRange(textEditor, line, col),
89-
};
90-
91-
if (data.rule.id && data.rule.desc) {
92-
msg.trace = [{
93-
type: 'Trace',
94-
text: `[${data.rule.id}] ${data.rule.desc}`,
95-
}];
96-
}
97-
toReturn.push(msg);
98-
});
99-
return toReturn;
153+
toReturn.push(msg);
100154
});
155+
156+
return toReturn;
101157
},
102158
};
103159
},
160+
161+
determineExecPath(givenPath, projectPath) {
162+
let execPath = givenPath;
163+
if (execPath === '') {
164+
// Use the bundled copy of CSSLint
165+
let relativeBinPath = path.join('node_modules', '.bin', 'csslint');
166+
if (process.platform === 'win32') {
167+
relativeBinPath += '.cmd';
168+
}
169+
if (!bundledCsslintPath) {
170+
const packagePath = atom.packages.resolvePackagePath('linter-csslint');
171+
bundledCsslintPath = path.join(packagePath, relativeBinPath);
172+
}
173+
execPath = bundledCsslintPath;
174+
if (projectPath) {
175+
const localCssLintPath = path.join(projectPath, relativeBinPath);
176+
if (fs.existsSync(localCssLintPath)) {
177+
execPath = localCssLintPath;
178+
}
179+
}
180+
}
181+
return execPath;
182+
},
104183
};

package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@
1313
"license": "MIT",
1414
"private": true,
1515
"configSchema": {
16-
"disableTimeout": {
17-
"type": "boolean",
18-
"description": "Disable the 10 second execution timeout",
19-
"default": false
16+
"executablePath": {
17+
"type": "string",
18+
"default": "",
19+
"description": "If unset a project local install of CSSLint is attempted to be used first, falling back to the bundled version. Requires a full path to `csslint` (e.g.: `/usr/bin/csslint` or `C:\\foo\\bar\\csslint.cmd`)."
2020
}
2121
},
2222
"engines": {
2323
"atom": ">=1.4.0 <2.0.0"
2424
},
2525
"dependencies": {
2626
"atom-linter": "^10.0.0",
27-
"atom-package-deps": "^4.0.1",
28-
"atomlinter-csslint": "0.10.1"
27+
"atom-package-deps": "^4.6.0",
28+
"csslint": "^1.0.5"
2929
},
3030
"devDependencies": {
3131
"eslint": "^4.3.0",
@@ -34,7 +34,7 @@
3434
"jasmine-fix": "^1.3.0"
3535
},
3636
"package-deps": [
37-
"linter"
37+
"linter:2.0.0"
3838
],
3939
"scripts": {
4040
"lint": "eslint .",
@@ -64,7 +64,7 @@
6464
"providedServices": {
6565
"linter": {
6666
"versions": {
67-
"1.0.0": "provideLinter"
67+
"2.0.0": "provideLinter"
6868
}
6969
}
7070
}

spec/fixtures/execProject/node_modules/.bin/csslint

Whitespace-only changes.

spec/fixtures/execProject/node_modules/.bin/csslint.cmd

Whitespace-only changes.

0 commit comments

Comments
 (0)