├── .gitattributes ├── .gitignore ├── .travis.yml ├── test ├── html │ ├── invalid.html │ └── valid.html └── main.js ├── .editorconfig ├── LICENSE ├── package.json ├── README.md ├── index.js └── .jshintrc /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | temp/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '8' 5 | - '10' 6 | -------------------------------------------------------------------------------- /test/html/invalid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 | 8 |

Test page 9 | 10 | -------------------------------------------------------------------------------- /test/html/valid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 | 8 |

Test page

9 | 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [test/fixtures/*] 16 | insert_final_newline = false 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Callum Macrae 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-w3cjs", 3 | "version": "1.3.2", 4 | "description": "A Gulp wrapper for w3cjs to validate your HTML", 5 | "keywords": [ 6 | "gulpplugin", 7 | "html", 8 | "validate", 9 | "validation", 10 | "w3c" 11 | ], 12 | "homepage": "https://github.com/callumacrae/gulp-w3cjs", 13 | "bugs": "https://github.com/callumacrae/gulp-w3cjs/issues", 14 | "author": { 15 | "name": "Callum Macrae", 16 | "email": "callum@macr.ae", 17 | "url": "https://github.com/callumacrae" 18 | }, 19 | "main": "./index.js", 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/callumacrae/gulp-w3cjs.git" 23 | }, 24 | "scripts": { 25 | "test": "mocha" 26 | }, 27 | "dependencies": { 28 | "ansi-colors": "^3.2.1", 29 | "fancy-log": "^1.3.2", 30 | "plugin-error": "^1.0.1", 31 | "through2": "^3.0.1", 32 | "w3cjs": "^0.4.0" 33 | }, 34 | "devDependencies": { 35 | "mocha": "^5.2.0", 36 | "should": "^13.2.3", 37 | "vinyl": "^2.1.0" 38 | }, 39 | "engines": { 40 | "node": ">=0.10.0", 41 | "npm": ">=1.2.10" 42 | }, 43 | "licenses": [ 44 | { 45 | "type": "MIT" 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-w3cjs [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][depstat-image]][depstat-url] 2 | 3 | > [w3cjs](https://github.com/thomasdavis/w3cjs) wrapper for [gulp](https://github.com/wearefractal/gulp) to validate your HTML 4 | 5 | ## Usage 6 | 7 | First, install `gulp-w3cjs` as a development dependency: 8 | 9 | ```shell 10 | npm install --save-dev gulp-w3cjs 11 | ``` 12 | 13 | Then, add it to your `gulpfile.js`: 14 | 15 | ```javascript 16 | var w3cjs = require('gulp-w3cjs'); 17 | 18 | gulp.task('w3cjs', function () { 19 | return gulp.src('src/*.html') 20 | .pipe(w3cjs()) 21 | .pipe(w3cjs.reporter()); 22 | }); 23 | ``` 24 | 25 | ### Custom Reporting 26 | 27 | The results are also added onto each file object under `w3cjs`, containing `success` (Boolean) and `messages` (Array). 28 | 29 | **Example usage** 30 | 31 | ```javascript 32 | var w3cjs = require('gulp-w3cjs'); 33 | var through2 = require('through2'); 34 | 35 | gulp.task('example', function () { 36 | return gulp.src('src/*.html') 37 | .pipe(w3cjs()) 38 | .pipe(through2.obj(function(file, enc, cb){ 39 | cb(null, file); 40 | if (!file.w3cjs.success){ 41 | throw new Error('HTML validation error(s) found'); 42 | } 43 | })); 44 | }); 45 | ``` 46 | 47 | **Example output** 48 | 49 | ```shell 50 | HTML Error: index.html Line 5, Column 19: Element title must not be empty. 51 | 52 | 53 | .../gulpfile.js:11 54 | throw new Error('HTML validation error(s) found'); 55 | ^ 56 | Error: HTML validation error(s) found 57 | ``` 58 | 59 | ## API 60 | 61 | ### w3cjs(options) 62 | 63 | #### options.url 64 | 65 | URL to the w3c validator. Use if you want to use a local validator. This is the 66 | same thing as `w3cjs.setW3cCheckUrl()`. 67 | 68 | #### options.proxy 69 | 70 | Http address of the proxy server if you are running behind a firewall, e.g. `http://proxy:8080` 71 | 72 | _`options.doctype` and `options.charset` were dropped in 1.0.0. Use 0.3.0 if you need them._ 73 | 74 | #### options.showInfo 75 | 76 | Default: `false` 77 | 78 | Show `info` type messages in the output. 79 | 80 | #### options.verifyMessage 81 | 82 | Allows you to intercept info, warnings or errors, using `options.verifyMessage` methed, returning false will skip the log output. Example usage: 83 | ```js 84 | return gulp.src('index.html') 85 | .pipe(w3cjs({ 86 | verifyMessage: function(type, message) { 87 | 88 | // prevent logging error message 89 | if(message.indexOf('Element “style” not allowed as child of element') === 0) return false; 90 | 91 | // allow message to pass through 92 | return true; 93 | } 94 | })) 95 | .pipe(w3cjs.reporter()) 96 | ``` 97 | 98 | ### w3cjs.setW3cCheckUrl(url) 99 | 100 | Same as options.url. SEt's the URL to the w3c validator. 101 | 102 | ## License 103 | 104 | [MIT License](http://en.wikipedia.org/wiki/MIT_License) 105 | 106 | [npm-url]: https://npmjs.org/package/gulp-w3cjs 107 | [npm-image]: https://badge.fury.io/js/gulp-w3cjs.png 108 | 109 | [travis-url]: http://travis-ci.org/callumacrae/gulp-w3cjs 110 | [travis-image]: https://secure.travis-ci.org/callumacrae/gulp-w3cjs.png?branch=master 111 | 112 | [depstat-url]: https://david-dm.org/callumacrae/gulp-w3cjs 113 | [depstat-image]: https://david-dm.org/callumacrae/gulp-w3cjs.png 114 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through2'); 4 | var w3cjs = require('w3cjs'); 5 | var fancyLog = require('fancy-log'); 6 | var PluginError = require('plugin-error'); 7 | var colors = require('ansi-colors'); 8 | 9 | /** 10 | * Handles messages. 11 | * 12 | * @param file The file array. 13 | * @param messages Array of messages returned by w3cjs. 14 | * @return boolean Return false if errors have occurred. 15 | */ 16 | function handleMessages(file, messages, options) { 17 | var success = true; 18 | var errorText = colors.bold(colors.red('HTML Error:')); 19 | var warningText = colors.bold(colors.yellow('HTML Warning:')); 20 | var infoText = colors.bold(colors.green('HTML Info:')); 21 | var lines = file.contents.toString().split(/\r\n|\r|\n/g); 22 | 23 | if (!Array.isArray(messages)) { 24 | fancyLog(warningText, 'Failed to run validation on', file.relative); 25 | 26 | // Not sure whether this should be true or false 27 | return true; 28 | } 29 | 30 | messages.forEach(function (message) { 31 | 32 | // allows you to intercept info, warnings or errors, using `options.verifyMessage` methed, returning false will skip the log output 33 | if(options.verifyMessage && !options.verifyMessage(message.type, message.message)) return; 34 | 35 | if (message.type === 'info' && !options.showInfo) { 36 | return; 37 | } 38 | 39 | if (message.type === 'error') { 40 | success = false; 41 | } 42 | 43 | var type = (message.type === 'error') ? errorText : ((message.type === 'info') ? infoText : warningText); 44 | 45 | var location = 'Line ' + (message.lastLine || 0) + ', Column ' + (message.lastColumn || 0) + ':'; 46 | 47 | var erroredLine = lines[message.lastLine - 1]; 48 | 49 | // If this is false, stream was changed since validation 50 | if (erroredLine) { 51 | var errorColumn = message.lastColumn; 52 | 53 | // Trim before if the error is too late in the line 54 | if (errorColumn > 60) { 55 | erroredLine = erroredLine.slice(errorColumn - 50); 56 | errorColumn = 50; 57 | } 58 | 59 | // Trim after so the line is not too long 60 | erroredLine = erroredLine.slice(0, 60); 61 | 62 | // Highlight character with error 63 | erroredLine = 64 | colors.grey(erroredLine.substring(0, errorColumn - 1)) + 65 | colors.bold(colors.red(erroredLine[ errorColumn - 1 ])) + 66 | colors.grey(erroredLine.substring(errorColumn)); 67 | } 68 | 69 | if (typeof(message.lastLine) !== 'undefined' || typeof(lastColumn) !== 'undefined') { 70 | fancyLog(type, file.relative, location, message.message); 71 | } else { 72 | fancyLog(type, file.relative, message.message); 73 | } 74 | 75 | if (erroredLine) { 76 | fancyLog(erroredLine); 77 | } 78 | }); 79 | 80 | return success; 81 | } 82 | 83 | function reporter() { 84 | return through.obj(function(file, enc, cb) { 85 | cb(null, file); 86 | if (file.w3cjs && !file.w3cjs.success) { 87 | throw new PluginError('gulp-w3cjs', 'HTML validation error(s) found'); 88 | } 89 | }); 90 | } 91 | 92 | module.exports = function (options) { 93 | options = options || {}; 94 | 95 | // I typo'd this and didn't want to break BC 96 | if (typeof options.uri === 'string') { 97 | options.url = options.uri; 98 | } 99 | 100 | if (typeof options.url === 'string') { 101 | w3cjs.setW3cCheckUrl(options.url); 102 | } 103 | 104 | return through.obj(function (file, enc, callback) { 105 | if (file.isNull()) { 106 | return callback(null, file); 107 | } 108 | 109 | if (file.isStream()) { 110 | return callback(new PluginError('gulp-w3cjs', 'Streaming not supported')); 111 | } 112 | 113 | w3cjs.validate({ 114 | proxy: options.proxy ? options.proxy : undefined, 115 | input: file.contents, 116 | callback: function (error, res) { 117 | if(error){ 118 | return callback(new PluginError('gulp-w3cjs', error)) 119 | } 120 | file.w3cjs = { 121 | success: handleMessages(file, res.messages, options), 122 | messages: res.messages 123 | }; 124 | 125 | callback(null, file); 126 | } 127 | }); 128 | }); 129 | }; 130 | 131 | module.exports.reporter = reporter; 132 | module.exports.setW3cCheckUrl = w3cjs.setW3cCheckUrl; 133 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | var should = require('should'); 6 | var path = require('path'); 7 | 8 | var Vinyl = require('vinyl'); 9 | var w3cjs = require('../'); 10 | 11 | describe('gulp-w3cjs', function () { 12 | describe('w3cjs()', function () { 13 | it('should pass valid files', function (done) { 14 | var a = 0; 15 | 16 | var fakeFile = new Vinyl({ 17 | path: './test/html/valid.html', 18 | cwd: './test/', 19 | base: './test/html/', 20 | contents: fs.readFileSync('./test/html/valid.html') 21 | }); 22 | 23 | var stream = w3cjs({showInfo: true}); 24 | stream.on('data', function (newFile) { 25 | should.exist(newFile); 26 | newFile.w3cjs.success.should.equal(true); 27 | newFile.w3cjs.messages.filter(function(m) { return m.type!=="info"; }).length.should.equal(0); 28 | should.exist(newFile.path); 29 | should.exist(newFile.relative); 30 | should.exist(newFile.contents); 31 | newFile.path.should.equal(path.normalize('./test/html/valid.html')); 32 | newFile.relative.should.equal('valid.html'); 33 | ++a; 34 | }); 35 | 36 | stream.once('end', function () { 37 | a.should.equal(1); 38 | done(); 39 | }); 40 | 41 | stream.write(fakeFile); 42 | stream.end(); 43 | }); 44 | 45 | it('should fail invalid files', function (done) { 46 | var a = 0; 47 | 48 | var fakeFile = new Vinyl({ 49 | path: './test/html/invalid.html', 50 | cwd: './test/', 51 | base: './test/html/', 52 | contents: fs.readFileSync('./test/html/invalid.html') 53 | }); 54 | 55 | var stream = w3cjs(); 56 | stream.on('data', function (newFile) { 57 | should.exist(newFile); 58 | newFile.w3cjs.success.should.equal(false); 59 | newFile.w3cjs.messages.filter(function(m) { return m.type!=="info"; }).length.should.equal(2); 60 | should.exist(newFile.path); 61 | should.exist(newFile.relative); 62 | should.exist(newFile.contents); 63 | newFile.path.should.equal(path.normalize('./test/html/invalid.html')); 64 | newFile.relative.should.equal('invalid.html'); 65 | ++a; 66 | }); 67 | 68 | stream.once('end', function () { 69 | a.should.equal(1); 70 | done(); 71 | }); 72 | 73 | stream.write(fakeFile); 74 | stream.end(); 75 | }); 76 | 77 | it('should allow a custom error to be ignored when `options.verifyMessage` used', function(done) { 78 | var a = 0; 79 | 80 | var fakeFile = new Vinyl({ 81 | path: './test/html/invalid.html', 82 | cwd: './test/', 83 | base: './test/html/', 84 | contents: fs.readFileSync('./test/html/invalid.html') 85 | }); 86 | 87 | var stream = w3cjs({ 88 | verifyMessage: function(type, message) { 89 | 90 | // prevent logging error message 91 | if(message.indexOf('End tag for “body” seen, but') === 0) return false; 92 | if(message.indexOf('Unclosed element “h1”.') === 0) return false; 93 | 94 | // allow message to pass through 95 | return true; 96 | } 97 | }); 98 | stream.on('data', function (newFile) { 99 | should.exist(newFile); 100 | newFile.w3cjs.success.should.equal(true); 101 | ++a; 102 | }); 103 | 104 | stream.once('end', function () { 105 | a.should.equal(1); 106 | done(); 107 | }); 108 | 109 | stream.write(fakeFile); 110 | stream.end(); 111 | }) 112 | }); 113 | 114 | describe('w3cjs.setW3cCheckUrl()', function () { 115 | it('should be possible to set a new checkUrl', function () { 116 | w3cjs.setW3cCheckUrl('http://localhost'); 117 | }); 118 | }); 119 | 120 | describe('w3cjs.reporter()', function () { 121 | it('should pass files through', function () { 122 | var fakeFile = new Vinyl({ 123 | path: './test/html/valid.html', 124 | cwd: './test/', 125 | base: './test/html/', 126 | contents: fs.readFileSync('./test/html/valid.html') 127 | }); 128 | 129 | var stream = w3cjs.reporter(); 130 | stream.write(fakeFile); 131 | stream.end(); 132 | 133 | return stream; 134 | }); 135 | 136 | it('should contain a reporter by default', function () { 137 | var fakeFile = new Vinyl({ 138 | path: './test/html/invalid.html', 139 | cwd: './test/', 140 | base: './test/html/', 141 | contents: fs.readFileSync('./test/html/invalid.html') 142 | }); 143 | 144 | fakeFile.w3cjs = { 145 | success: false, 146 | messages: ['ur html is valid'] 147 | }; 148 | 149 | var stream = w3cjs.reporter(); 150 | 151 | (function () { 152 | stream.write(fakeFile); 153 | }).should.throw(/HTML validation error\(s\) found/); 154 | 155 | stream.end(); 156 | 157 | return stream; 158 | }); 159 | }); 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 50, // {int} Maximum error before stopping 3 | 4 | // Enforcing 5 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 6 | "camelcase" : true, // true: Identifiers must be in camelCase 7 | "curly" : true, // true: Require {} for every new block or scope 8 | "eqeqeq" : false, // true: Require triple equals (===) for comparison 9 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 10 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 11 | "indent" : 4, // {int} Number of spaces to use for indentation 12 | "latedef" : false, // true: Require variables/functions to be defined before being used 13 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 14 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 15 | "noempty" : true, // true: Prohibit use of empty blocks 16 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 17 | "plusplus" : false, // true: Prohibit use of `++` & `--` 18 | "quotmark" : true, // Quotation mark consistency: 19 | // false : do nothing (default) 20 | // true : ensure whatever is used is consistent 21 | // "single" : require single quotes 22 | // "double" : require double quotes 23 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 24 | "unused" : true, // true: Require all defined variables be used 25 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 26 | "trailing" : true, // true: Prohibit trailing whitespaces 27 | "maxparams" : false, // {int} Max number of formal params allowed per function 28 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 29 | "maxstatements" : false, // {int} Max number statements per function 30 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 31 | "maxlen" : 120, // {int} Max number of characters per line 32 | 33 | // Relaxing 34 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 35 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 36 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 37 | "eqnull" : false, // true: Tolerate use of `== null` 38 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 39 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 40 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 41 | // (ex: `for each`, multiple try/catch, function expression…) 42 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 43 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 44 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 45 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 46 | "iterator" : false, // true: Tolerate using the `__iterator__` property 47 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 48 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 49 | "laxcomma" : false, // true: Tolerate comma-first style coding 50 | "loopfunc" : false, // true: Tolerate functions being defined in loops 51 | "multistr" : false, // true: Tolerate multi-line strings 52 | "proto" : false, // true: Tolerate using the `__proto__` property 53 | "scripturl" : false, // true: Tolerate script-targeted URLs 54 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 55 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 56 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 57 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 58 | "validthis" : false, // true: Tolerate using this in a non-constructor function 59 | 60 | // Environments 61 | "browser" : true, // Web Browser (window, document, etc) 62 | "couch" : false, // CouchDB 63 | "devel" : true, // Development/debugging (alert, confirm, etc) 64 | "dojo" : false, // Dojo Toolkit 65 | "jquery" : true, // jQuery 66 | "mootools" : false, // MooTools 67 | "node" : true, // Node.js 68 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 69 | "prototypejs" : false, // Prototype and Scriptaculous 70 | "rhino" : false, // Rhino 71 | "worker" : false, // Web Workers 72 | "wsh" : false, // Windows Scripting Host 73 | "yui" : false, // Yahoo User Interface 74 | 75 | // Legacy 76 | "nomen" : false, // true: Prohibit dangling `_` in variables 77 | "onevar" : false, // true: Allow only one `var` statement per function 78 | "passfail" : false, // true: Stop on first error 79 | "white" : false, // true: Check against strict whitespace and indentation rules 80 | 81 | // Custom Globals 82 | "globals" : { 83 | "DEBUG": true, 84 | "define": true 85 | } 86 | } --------------------------------------------------------------------------------