UNPKG

makestatic-validate-html

Version:

Validates HTML documents

227 lines (201 loc) 6.64 kB
const when = require('when') const spawn = require('child_process').spawn const XML_FMT = 'xml' const JSON_FMT = 'json' const TEXT_FMT = 'text' const GNU_FMT = 'gnu' const formats = [XML_FMT, JSON_FMT, TEXT_FMT, GNU_FMT] /** * Validate HTML documents using the nu HTML validator jar file. * * Requires that java 1.8 is installed. * * This implementation can run in one of two modes either it will write the * file content to `stdin` of the java process as files are processed * which is compatible with the `validate` phase that executes before files * are written to disc. Or it can pass all the files as arguments to the * validator jar when `stdin` is `false` this implies the files have been * written to disc and is compatible with the `audit` phase. * * Writing to `stdin` is very slow as a new JVM is launched for each file * whilst passing all the files as arguments runs the risk of the dreaded * *argument list too long* shell error for very large sites. * * The default behaviour is to use the argument list as it is much faster. * * Generally it is recommended that `stdin` is disabled and this plugin is * used during the `audit` phase. * * @class ValidateHtml * * @see https://github.com/validator/validator HTML Validator */ class ValidateHtml { /** * Create a ValidateHtml plugin. * * If the `jar` option is given it overrides the `vnu-jar` module. * * When no `format` or a bad format is given the `json` format is used. * * Pretty printing only applies when the output format is `json`. * * When the `warn` option is disabled it is equivalent to the `--errors-only` * jar option so warnings are not displayed. * * @constructor ValidateHtml * @param {Object} context the processing context. * @param {Object} options plugin options. * * @option {String} [format=json] validator output format. * @option {Boolean=false} [stdin] write each file to stdin. * @option {Boolean=true} [pretty=true] pretty print result. * @option {Boolean=false} [bail=true] throw error on validation failure. * @option {Boolean=false} [warn=true] control validation warnings. * @option {Boolean=false} [detect=false] control language detection. * @option {String} [jar] path to the validator jar file. */ constructor (context, options = {}) { this.report = {} this.options = options this.stdin = options.stdin !== undefined ? options.stdin : false this.pretty = options.pretty !== undefined ? options.pretty : true this.warn = options.warn !== undefined ? options.warn : true this.bail = options.bail !== undefined ? options.bail : true /* istanbul ignore next: not mocking detect option */ this.detect = options.detect !== undefined ? options.detect : false // list of file matches when not writing to stdin this.files = [] this.cmd = 'java' this.format = options.format || JSON_FMT if (!~formats.indexOf(this.format)) { this.format = JSON_FMT } this.jar = options.jar || require('vnu-jar') } /** * Run validation on an individual HTML file when `stdin` is set otherwise * collect the list of files to be processed afterwards. * * @function sources * @member ValidateHtml * @param {File} file the current file. * @param {Object} context the processing context. */ sources (file, context) { const log = context.log if (this.stdin) { const args = this.getArguments(['-']) log.info('[validate-html] %s', file.path) log.info('[validate-html] %s %s', this.cmd, args.join(' ')) return this.run(context, args, file) } else { this.files.push(file) } } /** * @private */ getArguments (files) { let args = ['-jar', this.jar, '--format', this.format] if (!this.warn) { args.push('--errors-only') } /* istanbul ignore else: not mocking detect option */ if (!this.detect) { args.push('--no-langdetect') } args = args.concat.apply(args, files) return args } run (context, args, file) { const log = context.log return when.promise((resolve, reject) => { let report = new Buffer(0) const ps = spawn(this.cmd, args) ps.stderr.on('data', (buf) => { report = Buffer.concat([report, buf], report.length + buf.length) }) ps.stdout.pipe(process.stdout) // TODO: break content into chunks? if (file) { ps.stdin.write(file.content) } ps.once('close', (code) => { let result report = report.toString() if (this.format === JSON_FMT) { try { result = JSON.parse(report) if (file) { this.report[file.path] = result } } catch (e) { /* istanbul ignore next: tough to mock this error */ return when.reject( new Error(`failed to parse validator JSON output:\n\n${report}`)) } } if (result && result.messages && result.messages.length === 0) { if (file) { log.info('[validate-html] %s ✓', file.path) } else { log.info('[validate-html] ✓') } } else { try { this.summary(context, code, report, result) } catch (e) { return reject(e) } } resolve() }) if (file) { ps.stdin.end() } }) } summary (context, code, report, result) { const log = context.log const printer = require('./lib/print') if (this.format === JSON_FMT && this.pretty) { printer(log, result) } else { if (this.format === JSON_FMT && result) { log.info(JSON.stringify(result, undefined, 2)) } else { log.info(report) } } if (this.bail && code > 0) { throw new Error(`[validate-html] exit code: ${code}`) } } /** * Run validation on collected files when `stdin` is `false`. * * @function after * @member ValidateHtml * @param {Object} context the processing context. */ after (context) { const log = context.log if (!this.stdin) { let files = this.files.map((file) => { return file.output }) const args = this.getArguments(files) log.info('[validate-html] %s %s', this.cmd, args.join(' ')) return this.run(context, args) } } static get test () { return /\.(html|sgr)$/ } static get exclude () { return [/google[a-z0-9]+\.html$/i] } } module.exports = ValidateHtml