├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .travis.yml ├── index.js ├── license ├── package.json ├── readme.md ├── reporters ├── fail.js ├── index.js └── load-reporter.js ├── screenshot.png └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowMultipleVarDecl": true, 3 | "disallowSpacesInsideObjectBrackets": true, 4 | "excludeFiles": ["excluded.js"] 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'stable' 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var PluginError = require('plugin-error'); 4 | var through = require('through2'); 5 | var tildify = require('tildify'); 6 | var Checker = require('jscs'); 7 | var loadConfigFile = require('jscs/lib/cli-config'); 8 | 9 | module.exports = function (opts) { 10 | opts = opts || {}; 11 | 12 | var config; 13 | var checker = new Checker(); 14 | 15 | try { 16 | config = loadConfigFile.load(opts.configPath); 17 | } catch (err) { 18 | err.message = 'Unable to load JSCS config file'; 19 | 20 | if (opts.configPath) { 21 | err.message += ' at ' + tildify(path.resolve(opts.configPath)); 22 | } 23 | 24 | err.message += '\n' + err.message; 25 | 26 | throw err; 27 | } 28 | 29 | // run autofix over as many errors as possible 30 | if (opts.fix) { 31 | config.maxErrors = Infinity; 32 | } 33 | 34 | checker.registerDefaultRules(); 35 | checker.configure(config); 36 | 37 | return through.obj(function (file, enc, cb) { 38 | if (file.isNull()) { 39 | cb(null, file); 40 | return; 41 | } 42 | 43 | if (file.isStream()) { 44 | cb(new PluginError('gulp-jscs', 'Streaming not supported')); 45 | return; 46 | } 47 | 48 | if (checker.getConfiguration().isFileExcluded(file.path)) { 49 | cb(null, file); 50 | return; 51 | } 52 | 53 | var fixResults; 54 | var errors; 55 | var contents = file.contents.toString(); 56 | 57 | if (opts.fix) { 58 | fixResults = checker.fixString(contents, file.path); 59 | errors = fixResults.errors; 60 | file.contents = new Buffer(fixResults.output); 61 | } else { 62 | errors = checker.checkString(contents, file.path); 63 | } 64 | 65 | var errorList = errors.getErrorList(); 66 | 67 | file.jscs = { 68 | success: true, 69 | errorCount: 0, 70 | errors: [] 71 | }; 72 | 73 | if (errorList.length > 0) { 74 | file.jscs.success = false; 75 | file.jscs.errorCount = errorList.length; 76 | file.jscs.errors = errors; 77 | } 78 | 79 | cb(null, file); 80 | }); 81 | }; 82 | 83 | module.exports.reporter = require('./reporters'); 84 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sindre Sorhus (sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-jscs", 3 | "version": "4.1.0", 4 | "description": "Check JavaScript code style with jscs", 5 | "license": "MIT", 6 | "repository": "jscs-dev/gulp-jscs", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "xo && mocha" 17 | }, 18 | "files": [ 19 | "index.js", 20 | "reporters" 21 | ], 22 | "keywords": [ 23 | "gulpplugin", 24 | "jscs", 25 | "javascript", 26 | "ecmascript", 27 | "js", 28 | "code", 29 | "style", 30 | "validate", 31 | "lint", 32 | "ast", 33 | "check", 34 | "checker" 35 | ], 36 | "dependencies": { 37 | "jscs": "^3.0.4", 38 | "plugin-error": "^0.1.2", 39 | "through2": "^2.0.0", 40 | "tildify": "^1.0.0" 41 | }, 42 | "devDependencies": { 43 | "mocha": "*", 44 | "stream-assert": "^2.0.2", 45 | "temp-write": "^2.1.0", 46 | "vinyl": "^2.1.0", 47 | "xo": "*" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gulp-jscs [![Build Status](https://travis-ci.org/jscs-dev/gulp-jscs.svg?branch=master)](https://travis-ci.org/jscs-dev/gulp-jscs) 2 | 3 | > Check JavaScript code style with [JSCS](http://jscs.info) 4 | 5 | ![](screenshot.png) 6 | 7 | *Issues with the output should be reported on the JSCS [issue tracker](https://github.com/jscs-dev/node-jscs/issues).* 8 | 9 | 10 | ## Install 11 | 12 | ``` 13 | $ npm install --save-dev gulp-jscs 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | ### Reporting 20 | 21 | ```js 22 | const gulp = require('gulp'); 23 | const jscs = require('gulp-jscs'); 24 | 25 | gulp.task('default', () => { 26 | return gulp.src('src/app.js') 27 | .pipe(jscs()) 28 | .pipe(jscs.reporter()); 29 | }); 30 | ``` 31 | 32 | ### Fixing 33 | 34 | ```js 35 | const gulp = require('gulp'); 36 | const jscs = require('gulp-jscs'); 37 | 38 | gulp.task('default', () => { 39 | return gulp.src('src/app.js') 40 | .pipe(jscs({fix: true})) 41 | .pipe(gulp.dest('src')); 42 | }); 43 | ``` 44 | 45 | ### Reporting & fixing & failing on lint error 46 | 47 | ```js 48 | const gulp = require('gulp'); 49 | const jscs = require('gulp-jscs'); 50 | 51 | gulp.task('default', () => { 52 | return gulp.src('src/app.js') 53 | .pipe(jscs({fix: true})) 54 | .pipe(jscs.reporter()) 55 | .pipe(jscs.reporter('fail')) 56 | .pipe(gulp.dest('src')); 57 | }); 58 | ``` 59 | 60 | 61 | ## Results 62 | 63 | A `jscs` object will be attached to the file object. 64 | An example with one error might look like this: 65 | 66 | ```js 67 | { 68 | success: false, // or true if no errors 69 | errorCount: 1, // number of errors in the errors array 70 | errors: [{ // an array of jscs error objects 71 | filename: 'index.js', // basename of the file 72 | rule: 'requireCamelCaseOrUpperCaseIdentifiers', // the rule which triggered the error 73 | message: 'All identifiers must be camelCase or UPPER_CASE', // error message 74 | line: 32, // error line number 75 | column: 7 // error column 76 | }] 77 | }; 78 | ``` 79 | 80 | 81 | ## API 82 | 83 | JSCS [config](http://jscs.info/overview.html#options) should be placed in a `.jscsrc` file. 84 | 85 | ### jscs([options]) 86 | 87 | #### options 88 | 89 | ##### fix 90 | 91 | Type: `boolean` 92 | Default: `false` 93 | 94 | Make JSCS attempt to auto-fix your files. 95 | Be sure to pipe to `gulp.dest` if you use this option. 96 | 97 | ##### configPath 98 | 99 | Type: `string` 100 | Default: JSCS will search for the config file up to your home directory. 101 | 102 | Set the path to the JSCS config file. 103 | Only use this option when it can't be found automatically. 104 | 105 | ### jscs.reporter([reporter]) 106 | 107 | #### reporter 108 | 109 | Type: `string` 110 | Default: `console` 111 | 112 | See the JSCS [reporter docs](http://jscs.info/overview#-reporter-r) for supported input. 113 | 114 | Can be used multiple times in the same pipeline. 115 | 116 | This plugin also ships with some custom reporters: 117 | 118 | - `fail` - Emits an error at the end of the stream if there are lint errors. 119 | - `failImmediately` - Emits an error immediately if there are lint errors. 120 | 121 | 122 | ## License 123 | 124 | MIT © [Sindre Sorhus](http://sindresorhus.com) 125 | -------------------------------------------------------------------------------- /reporters/fail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var PluginError = require('plugin-error'); 3 | var through = require('through2'); 4 | 5 | module.exports = function (failImmediately) { 6 | // paths to files that failed JSCS 7 | var fails = false; 8 | 9 | return through.obj(function (file, enc, cb) { 10 | var error = null; 11 | 12 | // check for failure 13 | if (file.jscs && !file.jscs.success) { 14 | if (failImmediately) { 15 | error = new PluginError('gulp-jscs', { 16 | message: 'JSCS failed for: ' + file.path, 17 | showStack: false 18 | }); 19 | } else { 20 | (fails = fails || []).push(file.path); 21 | } 22 | } 23 | 24 | cb(error, file); 25 | }, function (cb) { 26 | if (!failImmediately && fails) { 27 | // calling `cb(err)` would not emit the `end` event, 28 | // so emit the error explicitly and call `cb()` afterwards. 29 | this.emit('error', new PluginError('gulp-jscs', { 30 | message: 'JSCS failed for: ' + fails.join(', '), 31 | showStack: false 32 | })); 33 | } 34 | 35 | cb(); 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /reporters/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var PluginError = require('plugin-error'); 3 | var through = require('through2'); 4 | var loadReporter = require('./load-reporter'); 5 | var failReporter = require('./fail'); 6 | 7 | module.exports = function (reporter) { 8 | if (reporter === 'fail' || reporter === 'failImmediately') { 9 | return failReporter(reporter === 'failImmediately'); 10 | } 11 | 12 | var rpt = loadReporter(reporter); 13 | 14 | if (typeof rpt !== 'function') { 15 | throw new PluginError('gulp-jscs', 'Invalid reporter'); 16 | } 17 | 18 | // return stream that reports stuff 19 | return through.obj(function (file, enc, cb) { 20 | if (file.jscs && !file.jscs.success) { 21 | rpt([file.jscs.errors]); 22 | } 23 | 24 | cb(null, file); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /reporters/load-reporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var getReporter = require('jscs/lib/cli-config').getReporter; 3 | 4 | module.exports = function (reporter) { 5 | // we want the function 6 | if (typeof reporter === 'function') { 7 | return reporter; 8 | } 9 | 10 | // load JSCS built-in or absolute path or module reporters 11 | return getReporter(reporter).writer; 12 | }; 13 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscs-dev/gulp-jscs/16f8afef518b3cb65b3241cc82381323a493dc58/screenshot.png -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict'; 3 | var path = require('path'); 4 | var assert = require('assert'); 5 | var Vinyl = require('vinyl'); 6 | var streamAssert = require('stream-assert'); 7 | var tempWrite = require('temp-write'); 8 | var jscs = require('./'); 9 | 10 | var stdoutWrite = process.stdout.write; 11 | var stdoutStub; 12 | 13 | function stubStdout() { 14 | stdoutStub = ''; 15 | process.stdout.write = function (str) { 16 | stdoutStub += str; 17 | }; 18 | } 19 | 20 | function teardown() { 21 | process.stdout.write = stdoutWrite; 22 | } 23 | 24 | // in case test fails due to timeout 25 | afterEach(teardown); 26 | 27 | it('should check code style of JS files', function (cb) { 28 | var stream = jscs(); 29 | 30 | stream 31 | .pipe(streamAssert.first(function (file) { 32 | var errors = file.jscs.errors; 33 | assert(/Multiple var declaration/.test(errors.explainError(errors.getErrorList()[0], false))); 34 | })) 35 | .pipe(streamAssert.second(function (file) { 36 | var errors = file.jscs.errors; 37 | assert(/Illegal space before/.test(errors.explainError(errors.getErrorList()[1], false))); 38 | })) 39 | .pipe(streamAssert.end(cb)); 40 | 41 | stream.write(new Vinyl({ 42 | base: __dirname, 43 | path: path.join(__dirname, 'fixture.js'), 44 | contents: new Buffer('var x = 1,y = 2;') 45 | })); 46 | 47 | stream.write(new Vinyl({ 48 | base: __dirname, 49 | path: path.join(__dirname, 'fixture2.js'), 50 | contents: new Buffer('var x = { a: 1 };') 51 | })); 52 | 53 | stream.end(); 54 | }); 55 | 56 | it('should check code style of JS files using a preset', function (cb) { 57 | var stream = jscs({ 58 | configPath: tempWrite.sync(JSON.stringify({preset: 'google'})) 59 | }); 60 | 61 | stream 62 | .pipe(streamAssert.first(function (file) { 63 | var errors = file.jscs.errors; 64 | assert(/Missing line feed at file end/.test(errors.explainError(errors.getErrorList()[1], false))); 65 | })) 66 | .pipe(streamAssert.end(cb)); 67 | 68 | stream.write(new Vinyl({ 69 | base: __dirname, 70 | path: path.join(__dirname, 'fixture.js'), 71 | contents: new Buffer('var x = 1,y = 2;') 72 | })); 73 | 74 | stream.end(); 75 | }); 76 | 77 | it('should pass valid files', function (cb) { 78 | var stream = jscs(); 79 | 80 | stream.pipe(jscs.reporter('fail')).on('error', function (err) { 81 | assert(false, err); 82 | }).on('end', cb).resume(); 83 | 84 | stream.write(new Vinyl({ 85 | path: path.join(__dirname, 'fixture.js'), 86 | contents: new Buffer('var x = 1; var y = 2;') 87 | })); 88 | 89 | stream.end(); 90 | }); 91 | 92 | it('should respect "excludeFiles" from config', function (cb) { 93 | var stream = jscs(); 94 | 95 | stream.pipe(jscs.reporter('fail')).on('error', function (err) { 96 | assert(false, err); 97 | }).on('end', cb).resume(); 98 | 99 | stream.write(new Vinyl({ 100 | base: __dirname, 101 | path: path.join(__dirname, 'excluded.js'), 102 | contents: new Buffer('var x = { a: 1 };') 103 | })); 104 | 105 | stream.end(); 106 | }); 107 | 108 | it('should accept configPath options', function (cb) { 109 | var stream = jscs({ 110 | configPath: '.jscsrc' 111 | }); 112 | 113 | stream 114 | .pipe(streamAssert.first(function (file) { 115 | var errors = file.jscs.errors; 116 | var errorList = errors.getErrorList(); 117 | assert(errorList.length === 1 && /Multiple var declaration/.test(errors.explainError(errorList[0], false))); 118 | })) 119 | .pipe(streamAssert.end(cb)); 120 | 121 | stream.write(new Vinyl({ 122 | base: __dirname, 123 | path: path.join(__dirname, 'fixture.js'), 124 | contents: new Buffer('import x from \'x\'; var x = 1, y = 2;') 125 | })); 126 | 127 | stream.end(); 128 | }); 129 | 130 | it('should accept the fix option', function (cb) { 131 | var stream = jscs({ 132 | fix: true 133 | }); 134 | 135 | stream.on('data', function (file) { 136 | assert.equal(file.contents.toString(), 'var x = {a: 1, b: 2}'); 137 | }); 138 | 139 | stream.on('end', cb); 140 | 141 | stream.write(new Vinyl({ 142 | base: __dirname, 143 | path: path.join(__dirname, 'fixture.js'), 144 | contents: new Buffer('var x = { a: 1, b: 2 }') 145 | })); 146 | 147 | stream.end(); 148 | }); 149 | 150 | it('should run autofix over as many errors as possible', function (done) { 151 | var config = { 152 | maxErrors: 1, 153 | requireSpaceBeforeBinaryOperators: ['='] 154 | }; 155 | var validJS = 'var foo =1;\nvar bar =2;'; 156 | var invalidJS = 'var foo=1;\nvar bar=2;'; 157 | 158 | var stream = jscs({ 159 | fix: true, 160 | configPath: tempWrite.sync(JSON.stringify(config)) 161 | }); 162 | 163 | stream 164 | .pipe(streamAssert.first(function (file) { 165 | assert.equal(file.contents.toString(), validJS); 166 | })) 167 | .pipe(streamAssert.second(function (file) { 168 | assert.equal(file.contents.toString(), validJS); 169 | })) 170 | .pipe(streamAssert.end(done)); 171 | 172 | stream.write(new Vinyl({ 173 | base: __dirname, 174 | path: path.join(__dirname, 'fixture.js'), 175 | contents: new Buffer(invalidJS) 176 | })); 177 | 178 | stream.write(new Vinyl({ 179 | base: __dirname, 180 | path: path.join(__dirname, 'fixture2.js'), 181 | contents: new Buffer(invalidJS) 182 | })); 183 | 184 | stream.end(); 185 | }); 186 | 187 | it('should not mutate the options object passed as argument', function () { 188 | var options = {foo: true}; 189 | jscs(options); 190 | assert.equal(options.foo, true); 191 | }); 192 | 193 | describe('Reporter', function () { 194 | it('`.reporter()` called with no arguments should use the default reporter', function (cb) { 195 | stubStdout(); 196 | var stream = jscs(); 197 | 198 | stream.pipe(jscs.reporter()).on('end', function () { 199 | assert(/Multiple var declaration[^]*---\^/.test(stdoutStub)); 200 | teardown(); 201 | cb(); 202 | }).resume(); 203 | 204 | stream.write(new Vinyl({ 205 | base: __dirname, 206 | path: path.join(__dirname, 'fixture.js'), 207 | contents: new Buffer('var x = 1,y = 2;') 208 | })); 209 | 210 | stream.end(); 211 | }); 212 | 213 | it('`.reporter()` called with a non-function argument should delegate reporter loading to JSCS', function (cb) { 214 | stubStdout(); 215 | var stream = jscs(); 216 | 217 | stream.pipe(jscs.reporter('inlinesingle')).on('end', function () { 218 | assert(/line 1, col 8, disallowMultipleVarDecl: Multiple var declaration/.test(stdoutStub)); 219 | teardown(); 220 | cb(); 221 | }).resume(); 222 | 223 | stream.write(new Vinyl({ 224 | base: __dirname, 225 | path: path.join(__dirname, 'fixture.js'), 226 | contents: new Buffer('var x = 1,y = 2;') 227 | })); 228 | 229 | stream.end(); 230 | }); 231 | 232 | it('`.reporter()` should accept a function', function (cb) { 233 | function reporterFn(errors) { 234 | assert(/Multiple var declaration/.test(errors[0].explainError(errors[0].getErrorList()[0], false))); 235 | cb(); 236 | } 237 | 238 | var stream = jscs(); 239 | 240 | stream.pipe(jscs.reporter(reporterFn)).resume(); 241 | 242 | stream.write(new Vinyl({ 243 | base: __dirname, 244 | path: path.join(__dirname, 'fixture.js'), 245 | contents: new Buffer('var x = 1,y = 2;') 246 | })); 247 | 248 | stream.end(); 249 | }); 250 | 251 | it('`fail` reporter should emit an error at the end of the stream', function (cb) { 252 | var stream = jscs(); 253 | var passedErrorAssertion = false; 254 | 255 | stream 256 | .pipe(jscs.reporter('fail')) 257 | .on('error', function (err) { 258 | assert(err instanceof Error && /JSCS/.test(err.message)); 259 | passedErrorAssertion = true; 260 | }) 261 | .pipe(streamAssert.length(2)) 262 | .pipe(streamAssert.first(function (file) { 263 | var errors = file.jscs.errors; 264 | assert(/Multiple var declaration/.test(errors.explainError(errors.getErrorList()[0], false))); 265 | })) 266 | .pipe(streamAssert.second(function (file) { 267 | assert(file.jscs.success); 268 | })) 269 | .pipe(streamAssert.end(function (err) { 270 | if (err) { 271 | cb(err); 272 | return; 273 | } 274 | 275 | assert(passedErrorAssertion, 'Did not emit an error'); 276 | cb(); 277 | })); 278 | 279 | stream.write(new Vinyl({ 280 | base: __dirname, 281 | path: path.join(__dirname, 'fixture.js'), 282 | contents: new Buffer('var x = 1,y = 2;') 283 | })); 284 | 285 | stream.write(new Vinyl({ 286 | base: __dirname, 287 | path: path.join(__dirname, 'passing.js'), 288 | contents: new Buffer('var x = 1; var y = 2;') 289 | })); 290 | 291 | stream.end(); 292 | }); 293 | 294 | it('`failImmediately` reporter should emit an error immediately', function (cb) { 295 | var stream = jscs(); 296 | 297 | stream 298 | .pipe(jscs.reporter('failImmediately')) 299 | .on('error', function (err) { 300 | assert(err instanceof Error && /JSCS/.test(err.message)); 301 | cb(); 302 | }) 303 | .pipe(streamAssert.second(function () { 304 | cb(new Error('Did not emit an error immediately')); 305 | })) 306 | .pipe(streamAssert.end()); 307 | 308 | stream.write(new Vinyl({ 309 | base: __dirname, 310 | path: path.join(__dirname, 'fixture.js'), 311 | contents: new Buffer('var x = 1,y = 2;') 312 | })); 313 | 314 | stream.write(new Vinyl({ 315 | base: __dirname, 316 | path: path.join(__dirname, 'passing.js'), 317 | contents: new Buffer('var x = 1; var y = 2;') 318 | })); 319 | 320 | stream.end(); 321 | }); 322 | }); 323 | --------------------------------------------------------------------------------