├── .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 | }
--------------------------------------------------------------------------------