7
7
/// <reference path="typings/htmlhint/htmlhint.d.ts" />
8
8
9
9
import * as path from 'path' ;
10
-
11
10
import * as server from 'vscode-languageserver' ;
12
-
13
11
import htmlhint = require( 'htmlhint' ) ;
12
+ import fs = require( 'fs' ) ;
13
+ let stripJsonComments : any = require ( 'strip-json-comments' ) ;
14
14
15
15
interface Settings {
16
16
htmlhint : {
17
17
enable : boolean ;
18
- rulesDirectory : string ;
19
- formatterDirectory : string
18
+ options : any ;
20
19
}
20
+ [ key : string ] : any ;
21
21
}
22
22
23
23
let settings : Settings = null ;
24
- let rulesDirectory : string = null ;
25
- let formatterDirectory : string = null ;
26
24
let linter : any = null ;
27
25
28
- let configCache = {
29
- filePath : < string > null ,
30
- configuration : < any > null
31
- }
26
+ /**
27
+ * This variable is used to cache loaded htmlhintrc objects. It is a dictionary from path -> config object.
28
+ * A value of null means a .htmlhintrc object didn't exist at the given path.
29
+ * A value of undefined means the file at this path hasn't been loaded yet, or should be reloaded because it changed
30
+ */
31
+ let htmlhintrcOptions : any = { } ;
32
32
33
+ /**
34
+ * Given an htmlhint Error object, approximate the text range highlight
35
+ */
33
36
function getRange ( error : htmlhint . Error , lines : string [ ] ) : any {
34
37
35
- // approximate way to find the range of the element where the error is being reported.
36
38
let line = lines [ error . line - 1 ] ;
37
39
var isWhitespace = false ;
38
40
var curr = error . col ;
@@ -58,6 +60,9 @@ function getRange(error: htmlhint.Error, lines: string[]): any {
58
60
} ;
59
61
}
60
62
63
+ /**
64
+ * Given an htmlhint.Error type return a VS Code server Diagnostic object
65
+ */
61
66
function makeDiagnostic ( problem : htmlhint . Error , lines : string [ ] ) : server . Diagnostic {
62
67
63
68
return {
@@ -68,26 +73,70 @@ function makeDiagnostic(problem: htmlhint.Error, lines: string[]): server.Diagno
68
73
} ;
69
74
}
70
75
76
+ /**
77
+ * Get the html-hint configuration settings for the given html file. This method will take care of whether to use
78
+ * VS Code settings, or to use a .htmlhintrc file.
79
+ */
71
80
function getConfiguration ( filePath : string ) : any {
72
- // TODO
73
- return { } ;
74
- /*
75
- if (configCache.configuration && configCache.filePath === filePath) {
76
- return configCache.configuration;
77
- }
78
- configCache = {
79
- filePath: filePath,
80
- configuration: linter.findConfiguration(null, filePath)
81
- }
82
- return configCache.configuration;
83
- */
81
+ var options : any ;
82
+ if ( settings . htmlhint && settings . htmlhint . options && Object . keys ( settings . htmlhint . options ) . length > 0 ) {
83
+ options = settings . htmlhint . options ;
84
+ }
85
+ else {
86
+ options = findConfigForHtmlFile ( filePath ) ;
87
+ }
88
+
89
+ options = options || { } ;
90
+ return options ;
91
+ }
92
+
93
+ /**
94
+ * Given the path of an html file, this function will look in current directory & parent directories
95
+ * to find a .htmlhintrc file to use as the linter configuration. The settings are
96
+ */
97
+ function findConfigForHtmlFile ( base : string ) {
98
+ var options : any ;
99
+
100
+ if ( fs . existsSync ( base ) ) {
101
+
102
+ // find default config file in parent directory
103
+ if ( fs . statSync ( base ) . isDirectory ( ) === false ) {
104
+ base = path . dirname ( base ) ;
105
+ }
106
+
107
+ while ( base && ! options ) {
108
+ var tmpConfigFile = path . resolve ( base + path . sep , '.htmlhintrc' ) ;
109
+
110
+ // undefined means we haven't tried to load the config file at this path, so try to load it.
111
+ if ( htmlhintrcOptions [ tmpConfigFile ] === undefined ) {
112
+ htmlhintrcOptions [ tmpConfigFile ] = loadConfigurationFile ( tmpConfigFile ) ;
113
+ }
114
+
115
+ // defined, non-null value means we found a config file at the given path, so use it.
116
+ if ( htmlhintrcOptions [ tmpConfigFile ] ) {
117
+ options = htmlhintrcOptions [ tmpConfigFile ] ;
118
+ break ;
119
+ }
120
+
121
+ base = base . substring ( 0 , base . lastIndexOf ( path . sep ) ) ;
122
+ }
123
+ }
124
+ return options ;
84
125
}
85
126
86
- function flushConfigCache ( ) {
87
- configCache = {
88
- filePath : null ,
89
- configuration : null
127
+ /**
128
+ * Given a path to a .htmlhintrc file, load it into a javascript object and return it.
129
+ */
130
+ function loadConfigurationFile ( configFile ) : any {
131
+ var ruleset : any = null ;
132
+ if ( fs . existsSync ( configFile ) ) {
133
+ var config = fs . readFileSync ( configFile , 'utf-8' ) ;
134
+ try {
135
+ ruleset = JSON . parse ( stripJsonComments ( config ) ) ;
136
+ }
137
+ catch ( e ) { }
90
138
}
139
+ return ruleset ;
91
140
}
92
141
93
142
function getErrorMessage ( err : any , document : server . ITextDocument ) : string {
@@ -138,10 +187,9 @@ function doValidate(connection: server.IConnection, document: server.ITextDocume
138
187
let contents = document . getText ( ) ;
139
188
let lines = contents . split ( '\n' ) ;
140
189
141
- // TODO
142
- //options.configuration = getConfiguration(fsPath);
190
+ let config = getConfiguration ( fsPath ) ;
143
191
144
- let errors : htmlhint . Error [ ] = linter . verify ( contents ) ;
192
+ let errors : htmlhint . Error [ ] = linter . verify ( contents , config ) ;
145
193
146
194
let diagnostics : server . Diagnostic [ ] = [ ] ;
147
195
if ( errors . length > 0 ) {
@@ -168,19 +216,17 @@ documents.onDidChangeContent((event) => {
168
216
169
217
// The VS Code htmlhint settings have changed. Revalidate all documents.
170
218
connection . onDidChangeConfiguration ( ( params ) => {
171
- flushConfigCache ( ) ;
172
219
settings = params . settings ;
173
220
174
- if ( settings . htmlhint ) {
175
- rulesDirectory = settings . htmlhint . rulesDirectory ;
176
- formatterDirectory = settings . htmlhint . formatterDirectory ;
177
- }
221
+
178
222
validateAllTextDocuments ( connection , documents . all ( ) ) ;
179
223
} ) ;
180
224
181
- // The watched htmlhint.json has changed. Revalidate all documents.
225
+ // The watched .htmlhintrc has changed. Clear out the last loaded config, and revalidate all documents.
182
226
connection . onDidChangeWatchedFiles ( ( params ) => {
183
- flushConfigCache ( ) ;
227
+ for ( var i = 0 ; i < params . changes . length ; i ++ ) {
228
+ htmlhintrcOptions [ server . Files . uriToFilePath ( params . changes [ i ] . uri ) ] = undefined ;
229
+ }
184
230
validateAllTextDocuments ( connection , documents . all ( ) ) ;
185
231
} ) ;
186
232
0 commit comments