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 */
6
5
7
- let helpers = null ;
8
- let path = null ;
6
+ // Dependencies
7
+ let fs ;
8
+ let path ;
9
+ let helpers ;
10
+
11
+ // Internal Variables
12
+ let bundledCsslintPath ;
13
+
14
+ const loadDeps = ( ) => {
15
+ if ( ! fs ) {
16
+ fs = require ( 'fs-plus' ) ;
17
+ }
18
+ if ( ! path ) {
19
+ path = require ( 'path' ) ;
20
+ }
21
+ if ( ! helpers ) {
22
+ helpers = require ( 'atom-linter' ) ;
23
+ }
24
+ } ;
9
25
10
26
export default {
11
27
activate ( ) {
12
- require ( 'atom-package-deps' ) . install ( 'linter-csslint' ) ;
28
+ this . idleCallbacks = new Set ( ) ;
29
+ let depsCallbackID ;
30
+ const installLinterCsslintDeps = ( ) => {
31
+ this . idleCallbacks . delete ( depsCallbackID ) ;
32
+ if ( ! atom . inSpecMode ( ) ) {
33
+ require ( 'atom-package-deps' ) . install ( 'linter-csslint' ) ;
34
+ }
35
+ loadDeps ( ) ;
36
+
37
+ // FIXME: Remove this after a few versions
38
+ if ( atom . config . get ( 'linter-csslint.disableTimeout' ) ) {
39
+ atom . config . unset ( 'linter-csslint.disableTimeout' ) ;
40
+ }
41
+ } ;
42
+ depsCallbackID = window . requestIdleCallback ( installLinterCsslintDeps ) ;
43
+ this . idleCallbacks . add ( depsCallbackID ) ;
13
44
14
45
this . subscriptions = new CompositeDisposable ( ) ;
15
46
this . subscriptions . add (
16
- atom . config . observe ( 'linter-csslint.disableTimeout ' , ( value ) => {
17
- this . disableTimeout = value ;
47
+ atom . config . observe ( 'linter-csslint.executablePath ' , ( value ) => {
48
+ this . executablePath = value ;
18
49
} ) ,
19
50
) ;
20
51
} ,
21
52
22
53
deactivate ( ) {
54
+ this . idleCallbacks . forEach ( callbackID => window . cancelIdleCallback ( callbackID ) ) ;
55
+ this . idleCallbacks . clear ( ) ;
23
56
this . subscriptions . dispose ( ) ;
24
57
} ,
25
58
@@ -28,77 +61,129 @@ export default {
28
61
name : 'CSSLint' ,
29
62
grammarScopes : [ 'source.css' , 'source.html' ] ,
30
63
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
- }
64
+ lintsOnChange : false ,
65
+ lint : async ( textEditor ) => {
66
+ loadDeps ( ) ;
39
67
const filePath = textEditor . getPath ( ) ;
40
68
const text = textEditor . getText ( ) ;
41
- if ( text . length === 0 ) {
42
- return Promise . resolve ( [ ] ) ;
69
+ if ( ! filePath || text . length === 0 ) {
70
+ // Empty or unsaved file
71
+ return [ ] ;
43
72
}
44
- const parameters = [ '--format=json' , '-' ] ;
45
- const exec = path . join ( __dirname , '..' , 'node_modules' , 'atomlinter-csslint' , 'cli.js' ) ;
73
+
74
+ const parameters = [
75
+ '--format=json' ,
76
+ filePath ,
77
+ ] ;
78
+
46
79
const projectPath = atom . project . relativizePath ( filePath ) [ 0 ] ;
47
80
let cwd = projectPath ;
48
- if ( ! ( cwd ) ) {
81
+ if ( ! cwd ) {
49
82
cwd = path . dirname ( filePath ) ;
50
83
}
51
- const options = { stdin : text , cwd } ;
52
- if ( this . disableTimeout ) {
53
- options . timeout = Infinity ;
84
+
85
+ const execOptions = {
86
+ cwd,
87
+ uniqueKey : `linter-csslint::${ filePath } ` ,
88
+ timeout : 1000 * 30 , // 30 seconds
89
+ ignoreExitCode : true ,
90
+ } ;
91
+
92
+ const execPath = this . determineExecPath ( this . executablePath , projectPath ) ;
93
+
94
+ const output = await helpers . exec ( execPath , parameters , execOptions ) ;
95
+
96
+ if ( textEditor . getText ( ) !== text ) {
97
+ // The editor contents have changed, tell Linter not to update
98
+ return null ;
54
99
}
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
100
61
- const toReturn = [ ] ;
62
- if ( output . length < 1 ) {
63
- // No output, no errors
64
- return toReturn ;
101
+ const toReturn = [ ] ;
102
+
103
+ if ( output . length < 1 ) {
104
+ // No output, no errors
105
+ return toReturn ;
106
+ }
107
+
108
+ let lintResult ;
109
+ try {
110
+ lintResult = JSON . parse ( output ) ;
111
+ } catch ( e ) {
112
+ const excerpt = 'Invalid response received from CSSLint, check ' +
113
+ 'your console for more details.' ;
114
+ return [ {
115
+ severity : 'error' ,
116
+ excerpt,
117
+ location : {
118
+ file : filePath ,
119
+ position : helpers . generateRange ( textEditor , 0 ) ,
120
+ } ,
121
+ } ] ;
122
+ }
123
+
124
+ if ( lintResult . messages . length < 1 ) {
125
+ // Output, but no errors found
126
+ return toReturn ;
127
+ }
128
+
129
+ lintResult . messages . forEach ( ( data ) => {
130
+ let line ;
131
+ let col ;
132
+ if ( ! ( data . line && data . col ) ) {
133
+ // Use the file start if a location wasn't defined
134
+ [ line , col ] = [ 0 , 0 ] ;
135
+ } else {
136
+ [ line , col ] = [ data . line - 1 , data . col - 1 ] ;
65
137
}
66
138
67
- const lintResult = JSON . parse ( output ) ;
139
+ const severity = data . type === 'error' ? 'error' : 'warning' ;
68
140
69
- if ( lintResult . messages . length < 1 ) {
70
- // Output, but no errors found
71
- return toReturn ;
141
+ const msg = {
142
+ severity,
143
+ excerpt : data . message ,
144
+ location : {
145
+ file : filePath ,
146
+ position : helpers . generateRange ( textEditor , line , col ) ,
147
+ } ,
148
+ } ;
149
+ if ( data . rule . id && data . rule . desc ) {
150
+ msg . details = `${ data . rule . desc } (${ data . rule . id } )` ;
151
+ }
152
+ if ( data . rule . url ) {
153
+ msg . url = data . rule . url ;
72
154
}
73
155
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 ;
156
+ toReturn . push ( msg ) ;
100
157
} ) ;
158
+
159
+ return toReturn ;
101
160
} ,
102
161
} ;
103
162
} ,
163
+
164
+ determineExecPath ( givenPath , projectPath ) {
165
+ let execPath = givenPath ;
166
+ if ( execPath === '' ) {
167
+ // Use the bundled copy of CSSLint
168
+ let relativeBinPath = path . join ( 'node_modules' , '.bin' , 'csslint' ) ;
169
+ if ( process . platform === 'win32' ) {
170
+ relativeBinPath += '.cmd' ;
171
+ }
172
+ if ( ! bundledCsslintPath ) {
173
+ const packagePath = atom . packages . resolvePackagePath ( 'linter-csslint' ) ;
174
+ bundledCsslintPath = path . join ( packagePath , relativeBinPath ) ;
175
+ }
176
+ execPath = bundledCsslintPath ;
177
+ if ( projectPath ) {
178
+ const localCssLintPath = path . join ( projectPath , relativeBinPath ) ;
179
+ if ( fs . existsSync ( localCssLintPath ) ) {
180
+ execPath = localCssLintPath ;
181
+ }
182
+ }
183
+ } else {
184
+ // Normalize any usage of ~
185
+ fs . normalize ( execPath ) ;
186
+ }
187
+ return execPath ;
188
+ } ,
104
189
} ;
0 commit comments