UNPKG

5.6 kBJavaScriptView Raw
1/*
2 * grunt
3 * http://gruntjs.com/
4 *
5 * Copyright (c) 2012 "Cowboy" Ben Alman
6 * Licensed under the MIT license.
7 * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
8 */
9
10module.exports = function(grunt) {
11
12 // External libs.
13 var jshint = require('jshint').JSHINT;
14
15 // ==========================================================================
16 // TASKS
17 // ==========================================================================
18
19 grunt.registerMultiTask('lint', 'Validate files with JSHint.', function() {
20 // Get flags and globals, allowing target-specific options and globals to
21 // override the default options and globals.
22 var options, globals, tmp;
23
24 tmp = grunt.config(['jshint', this.target, 'options']);
25 if (typeof tmp === 'object') {
26 grunt.verbose.writeln('Using "' + this.target + '" JSHint options.');
27 options = tmp;
28 } else {
29 grunt.verbose.writeln('Using master JSHint options.');
30 options = grunt.config('jshint.options');
31 }
32 grunt.verbose.writeflags(options, 'Options');
33
34 tmp = grunt.config(['jshint', this.target, 'globals']);
35 if (typeof tmp === 'object') {
36 grunt.verbose.writeln('Using "' + this.target + '" JSHint globals.');
37 globals = tmp;
38 } else {
39 grunt.verbose.writeln('Using master JSHint globals.');
40 globals = grunt.config('jshint.globals');
41 }
42 grunt.verbose.writeflags(globals, 'Globals');
43
44 // Lint specified files.
45 grunt.file.expandFiles(this.file.src).forEach(function(filepath) {
46 grunt.helper('lint', grunt.file.read(filepath), options, globals, filepath);
47 });
48
49 // Fail task if errors were logged.
50 if (this.errorCount) { return false; }
51
52 // Otherwise, print a success message.
53 grunt.log.writeln('Lint free.');
54 });
55
56 // ==========================================================================
57 // HELPERS
58 // ==========================================================================
59
60 // No idea why JSHint treats tabs as options.indent # characters wide, but it
61 // does. See issue: https://github.com/jshint/jshint/issues/430
62 function getTabStr(options) {
63 // Do something that's going to error.
64 jshint('\tx', options || {});
65 // If an error occurred, figure out what character JSHint reported and
66 // subtract one.
67 var character = jshint.errors && jshint.errors[0] && jshint.errors[0].character - 1;
68 // If character is actually a number, use it. Otherwise use 1.
69 var tabsize = isNaN(character) ? 1 : character;
70 // If tabsize > 1, return something that should be safe to use as a
71 // placeholder. \uFFFF repeated 2+ times.
72 return tabsize > 1 && grunt.utils.repeat(tabsize, '\uFFFF');
73 }
74
75 var tabregex = /\t/g;
76
77 // Lint source code with JSHint.
78 grunt.registerHelper('lint', function(src, options, globals, extraMsg) {
79 // JSHint sometimes modifies objects you pass in, so clone them.
80 options = grunt.utils._.clone(options);
81 globals = grunt.utils._.clone(globals);
82 // Enable/disable debugging if option explicitly set.
83 if (grunt.option('debug') !== undefined) {
84 options.devel = options.debug = grunt.option('debug');
85 // Tweak a few things.
86 if (grunt.option('debug')) {
87 options.maxerr = Infinity;
88 }
89 }
90 var msg = 'Linting' + (extraMsg ? ' ' + extraMsg : '') + '...';
91 grunt.verbose.write(msg);
92 // Tab size as reported by JSHint.
93 var tabstr = getTabStr(options);
94 var placeholderregex = new RegExp(tabstr, 'g');
95 // Lint.
96 var result = jshint(src, options || {}, globals || {});
97 // Attempt to work around JSHint erroneously reporting bugs.
98 // if (!result) {
99 // // Filter out errors that shouldn't be reported.
100 // jshint.errors = jshint.errors.filter(function(o) {
101 // return o && o.something === 'something';
102 // });
103 // // If no errors are left, JSHint actually succeeded.
104 // result = jshint.errors.length === 0;
105 // }
106 if (result) {
107 // Success!
108 grunt.verbose.ok();
109 } else {
110 // Something went wrong.
111 grunt.verbose.or.write(msg);
112 grunt.log.error();
113 // Iterate over all errors.
114 jshint.errors.forEach(function(e) {
115 // Sometimes there's no error object.
116 if (!e) { return; }
117 var pos;
118 var evidence = e.evidence;
119 var character = e.character;
120 if (evidence) {
121 // Manually increment errorcount since we're not using grunt.log.error().
122 grunt.fail.errorcount++;
123 // Descriptive code error.
124 pos = '['.red + ('L' + e.line).yellow + ':'.red + ('C' + character).yellow + ']'.red;
125 grunt.log.writeln(pos + ' ' + e.reason.yellow);
126 // If necessary, eplace each tab char with something that can be
127 // swapped out later.
128 if (tabstr) {
129 evidence = evidence.replace(tabregex, tabstr);
130 }
131 if (character > evidence.length) {
132 // End of line.
133 evidence = evidence + ' '.inverse.red;
134 } else {
135 // Middle of line.
136 evidence = evidence.slice(0, character - 1) + evidence[character - 1].inverse.red +
137 evidence.slice(character);
138 }
139 // Replace tab placeholder (or tabs) but with a 2-space soft tab.
140 evidence = evidence.replace(tabstr ? placeholderregex : tabregex, ' ');
141 grunt.log.writeln(evidence);
142 } else {
143 // Generic "Whoops, too many errors" error.
144 grunt.log.error(e.reason);
145 }
146 });
147 grunt.log.writeln();
148 }
149 });
150
151};