Linting JavaScript inside HTML

Use jshint to statically check JavaScript inside HTML pages.

jshint is an awesome tool. It finds undeclared variables, missing semicolons, functions that are too complex and other potential problems in your JavaScript. It is extremely easy to setup jshint to run on file changes, pre-commit or inside your text editor as your program.

With the rise of server-side templating, a lot of projects include JavaScript code inside html files. The js code inside HTML or any other template language is usually NOT linted. At some point I even created a tool called dirty-water to measure number of JS lines inside EJS templates. Once one has a number, one can drive it down, for example by switching to AngularJs ;)

jshint CLI tool

Turns out, the current stable version of jshint (>2.4 released in December of 2013) has a command-line option --extract that allows to strip HTML and lint the remaining JavaScript code. You can read the official announcement, and here is an example index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
<head>
<script type="text/javascript">
foo = 'foo';
</script>
</head>
<body>
<script>
bar = 'bar'
</script>
</body>
</html>
1
2
3
4
5
$ jshint --extract=auto index.html
index.html: line 10, col 16, Missing semicolon.
index.html: line 5, col 5, 'foo' is not defined.
index.html: line 10, col 5, 'bar' is not defined.
3 errors

The parser replaced HTML with blank lines, the linting errors thus have correct line numbers.

The --extract option has 3 values:

  • default: no extraction, tries to parse each file as JavaScript code
  • always: always strips HTML tags
  • auto: looks at the start of the file, checking if it starts with an opening angular bracket <.

grunt-contrib-jshint

I usually do not run jshint directly from command line, using grunt-contrib-jshint instead. Recently, this grunt plugin added support for extract option, passing it to the jshint cli, see source fragment. Unfortunately this version (0.9.0-pre) has not been published to NPM yet, if you would like to lint js inside your html files today, you could use the latest plugin directly from github. In your package.json specify the repo url instead of the version

"devDependencies": {
  "grunt": "~0.4.2",
  "grunt-contrib-jshint": "git://github.com/gruntjs/grunt-contrib-jshint.git"
}

I suggest using separate sets of settings to lint your server-side (Node) and browser code. A sample Gruntfile.js can easily set this up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* global module, require */
module.exports = function (grunt) {
grunt.initConfig({
jshint: {
options: {
reporter: require('jshint-stylish')
},
js: {
options: {
jshintrc: '.jshintrc'
},
src: ['Gruntfile.js']
},
html: {
options: {
extract: 'always',
undef: true,
browser: true,
globals: {
foo: true
}
},
files: {
src: ['*.html']
}
}
}
});
var plugins = require('matchdep').filterDev('grunt-*');
plugins.forEach(grunt.loadNpmTasks);
grunt.registerTask('default', ['jshint']);
};
1
2
3
4
5
6
7
8
9
10
11
$ grunt
Running "jshint:js" (jshint) task
✔ No problems

Running "jshint:html" (jshint) task
index.html
line 10 col 16 Missing semicolon.
line 10 col 5 'bar' is not defined.
✖ 2 problems
Warning: Task "jshint:html" failed. Use --force to continue.
Aborted due to warnings.

As always I am using jshint-stylish as a reporter.

Indentation problem

If you set jshint to check the indentation, the indentation is confused by the default script code offset, for example:

1
2
3
4
5
6
7
8
9
10
11
<!doctype html>
<html>
<head>
<script type="text/javascript">
/*jshint indent:2 */
var foo = 'foo';
</script>
</head>
<body>
</body>
</html>

generates errors:

$ jshint --extract=auto index.html
index.html: line 6, col 5, Expected 'var' to have an indentation at 1 instead at 5.

This is due to the HTML tag stripping: it literally removes everything but JavaScript, leaving the block of code offset in the otherwise empty file. If we print the intermediate result, the linter is looking at this code:

|
|    /*jshint indent:2 */
|    var foo = 'foo';

I forked jshint and added stripping leading offset from the JavaScript code extracted from HTML. Each js fragment will be offset by the amount of leading whitespace from the first line. In the previous example, the output js fragment would be

|
|/*jshint indent:2 */
|var foo = 'foo';

which passes jshint.

Update

My pull request made its way into jshint 2.4.4. Now you can check the white space in JavaScript inside HTML!