1
1
'use babel' ;
2
2
3
- /* eslint-disable import/extensions, import/ no-extraneous-dependencies */
3
+ // eslint-disable-next-line import/no-extraneous-dependencies, import/extensions
4
4
import { CompositeDisposable } from 'atom' ;
5
- /* eslint-enable import/extensions, import/no-extraneous-dependencies */
5
+ import * as path from 'path' ;
6
6
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
+ } ;
9
22
10
23
export default {
11
24
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 ) ;
13
41
14
42
this . subscriptions = new CompositeDisposable ( ) ;
15
43
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 ;
18
46
} ) ,
19
47
) ;
20
48
} ,
21
49
22
50
deactivate ( ) {
51
+ this . idleCallbacks . forEach ( callbackID => window . cancelIdleCallback ( callbackID ) ) ;
52
+ this . idleCallbacks . clear ( ) ;
23
53
this . subscriptions . dispose ( ) ;
24
54
} ,
25
55
@@ -28,77 +58,126 @@ export default {
28
58
name : 'CSSLint' ,
29
59
grammarScopes : [ 'source.css' , 'source.html' ] ,
30
60
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 ( ) ;
39
64
const filePath = textEditor . getPath ( ) ;
40
65
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 [ ] ;
43
69
}
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
+
46
76
const projectPath = atom . project . relativizePath ( filePath ) [ 0 ] ;
47
77
let cwd = projectPath ;
48
- if ( ! ( cwd ) ) {
78
+ if ( ! cwd ) {
49
79
cwd = path . dirname ( filePath ) ;
50
80
}
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 ;
54
96
}
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
- }
60
97
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 ] ;
65
134
}
66
135
67
- const lintResult = JSON . parse ( output ) ;
136
+ const severity = data . type === 'error' ? 'error' : 'warning' ;
68
137
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 ;
72
151
}
73
152
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 ) ;
100
154
} ) ;
155
+
156
+ return toReturn ;
101
157
} ,
102
158
} ;
103
159
} ,
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
+ } ,
104
183
} ;
0 commit comments