Skip to content

Commit 3e5979d

Browse files
author
Mike Kaufman
committed
Updating extension to find and load htmlhintrc configuration files.
1 parent a6a5f4f commit 3e5979d

File tree

10 files changed

+123
-42
lines changed

10 files changed

+123
-42
lines changed

htmlhint-server/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
},
88
"private": true,
99
"dependencies": {
10+
"fs": "0.0.2",
1011
"htmlhint": "^0.9.12",
12+
"strip-json-comments": "^2.0.0",
1113
"vscode-languageserver": "0.10.x"
1214
},
1315
"devDependencies": {

htmlhint-server/src/server.ts

+83-37
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,34 @@
77
/// <reference path="typings/htmlhint/htmlhint.d.ts" />
88

99
import * as path from 'path';
10-
1110
import * as server from 'vscode-languageserver';
12-
1311
import htmlhint = require('htmlhint');
12+
import fs = require('fs');
13+
let stripJsonComments: any = require('strip-json-comments');
1414

1515
interface Settings {
1616
htmlhint: {
1717
enable: boolean;
18-
rulesDirectory: string;
19-
formatterDirectory: string
18+
options: any;
2019
}
20+
[key: string]: any;
2121
}
2222

2323
let settings: Settings = null;
24-
let rulesDirectory: string = null;
25-
let formatterDirectory: string = null;
2624
let linter: any = null;
2725

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 = {};
3232

33+
/**
34+
* Given an htmlhint Error object, approximate the text range highlight
35+
*/
3336
function getRange(error: htmlhint.Error, lines: string[]): any {
3437

35-
// approximate way to find the range of the element where the error is being reported.
3638
let line = lines[error.line - 1];
3739
var isWhitespace = false;
3840
var curr = error.col;
@@ -58,6 +60,9 @@ function getRange(error: htmlhint.Error, lines: string[]): any {
5860
};
5961
}
6062

63+
/**
64+
* Given an htmlhint.Error type return a VS Code server Diagnostic object
65+
*/
6166
function makeDiagnostic(problem: htmlhint.Error, lines: string[]): server.Diagnostic {
6267

6368
return {
@@ -68,26 +73,70 @@ function makeDiagnostic(problem: htmlhint.Error, lines: string[]): server.Diagno
6873
};
6974
}
7075

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+
*/
7180
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;
84125
}
85126

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) { }
90138
}
139+
return ruleset;
91140
}
92141

93142
function getErrorMessage(err: any, document: server.ITextDocument): string {
@@ -138,10 +187,9 @@ function doValidate(connection: server.IConnection, document: server.ITextDocume
138187
let contents = document.getText();
139188
let lines = contents.split('\n');
140189

141-
// TODO
142-
//options.configuration = getConfiguration(fsPath);
190+
let config = getConfiguration(fsPath);
143191

144-
let errors: htmlhint.Error[] = linter.verify(contents);
192+
let errors: htmlhint.Error[] = linter.verify(contents, config);
145193

146194
let diagnostics: server.Diagnostic[] = [];
147195
if (errors.length > 0) {
@@ -168,19 +216,17 @@ documents.onDidChangeContent((event) => {
168216

169217
// The VS Code htmlhint settings have changed. Revalidate all documents.
170218
connection.onDidChangeConfiguration((params) => {
171-
flushConfigCache();
172219
settings = params.settings;
173220

174-
if (settings.htmlhint) {
175-
rulesDirectory = settings.htmlhint.rulesDirectory;
176-
formatterDirectory = settings.htmlhint.formatterDirectory;
177-
}
221+
178222
validateAllTextDocuments(connection, documents.all());
179223
});
180224

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.
182226
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+
}
184230
validateAllTextDocuments(connection, documents.all());
185231
});
186232

htmlhint/extension.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function activate(context: ExtensionContext) {
1717
documentSelector: ['html', 'htm'],
1818
synchronize: {
1919
configurationSection: 'htmlhint',
20-
fileEvents: workspace.createFileSystemWatcher('**/httmlhint.json')
20+
fileEvents: workspace.createFileSystemWatcher('**/.htmlhintrc')
2121
}
2222
}
2323

htmlhint/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"name": "HTMLHint",
33
"displayName": "HTMLHint",
4-
"description": "HTMLHint - A Static Code Analysis Tool for HTML",
4+
"description": "VS Code integration for HTMLHint - A Static Code Analysis Tool for HTML",
55
"icon": "HTMLHint.svg",
6-
"version": "0.0.2",
6+
"version": "0.0.3",
77
"publisher": "mkaufman",
88
"galleryBanner": {
99
"color": "#0000FF",

test/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

test/.htmlhintrc

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"tagname-lowercase": true,
3+
"attr-lowercase": true,
4+
"attr-value-double-quotes": true,
5+
"doctype-first": true,
6+
"tag-pair": true,
7+
"spec-char-escape": true,
8+
"id-unique": true,
9+
"src-not-empty": true,
10+
"attr-no-duplication": true,
11+
"title-require": true
12+
}

test/A/.htmlhintrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"tagname-lowercase": true,
3+
"attr-lowercase": true,
4+
"attr-value-double-quotes": true,
5+
"doctype-first": true
6+
}

test/A/B/test.html

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
<!--<!DOCTYPE html>-->
3+
<ul>
4+
<li>
5+
<a href='http://www.github.com'>github</a>
6+
</ul>

test/A/test.html

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
<!--<!DOCTYPE html>-->
3+
<ul>
4+
<li>
5+
<a href="http://www.github.com">github</a>
6+
</ul>

test/test.html

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2-
<!DOCTYPE html>
3-
<ul> <li>
2+
<!--<!DOCTYPE html>-->
3+
<ul>
4+
<li>
5+
<a href='http://www.github.com'>github</a>
46
</ul>

0 commit comments

Comments
 (0)