diff --git a/bin/cmd.js b/bin/cmd.js index 793e55b..a72a272 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -9,15 +9,18 @@ const url = require('url') const nopt = require('nopt') const pretty = require('../lib/format-pretty') +const tap = require('../lib/format-tap') const Validator = require('../lib') const knownOpts = { help: Boolean , version: Boolean , 'validate-metadata': Boolean + , tap: Boolean } const shortHand = { h: ['--help'] , v: ['--version'] , V: ['--validate-metadata'] + , t: ['--tap'] } const parsed = nopt(knownOpts, shortHand) @@ -73,10 +76,16 @@ function loadPatch(uri, cb) { const v = new Validator(parsed) -v.on('commit', (c) => { - pretty(c.commit, c.messages, v) - run() -}) +if (parsed.tap) { + v.on('commit', (c) => { + tap(c.commit, c.messages, v, run) + }) +} else { + v.on('commit', (c) => { + pretty(c.commit, c.messages, v) + run() + }) +} function run() { if (!args.length) { diff --git a/lib/format-pretty.js b/lib/format-pretty.js index de88e8e..99b1885 100644 --- a/lib/format-pretty.js +++ b/lib/format-pretty.js @@ -15,9 +15,9 @@ module.exports = function formatPretty(context, msgs, validator, opts) { return } - let level = 'warn' + let level = 'pass' for (const msg of msgs) { - if (msg.level === 'error') level = 'error' + if (msg.level === 'fail') level = 'fail' } msgs.sort((a, b) => { @@ -62,19 +62,22 @@ function formatLength(msg, opts) { const str = msg.string const l = str.length if (!opts.detailed) return out - const diff = str.slice(0, msg.column) + chalk.red(str.slice(msg.column, l)) + var col = msg.column || 0 + const diff = str.slice(0, col) + chalk.red(str.slice(col, l)) return `${out} ${diff}` } function formatMessage(msg) { - const pad = utils.rightPad(`${msg.line}:${msg.column}`, MAX_LINE_COL_LEN) + const l = msg.line || 0 + const col = msg.column || 0 + const pad = utils.rightPad(`${l}:${col}`, MAX_LINE_COL_LEN) const line = chalk.grey(pad) const id = formatId(msg.id) const m = msg.message - const icon = msg.level === 'error' + const icon = msg.level === 'fail' ? utils.X - : utils.WARN + : utils.CHECK return ` ${icon} ${line} ${utils.rightPad(m, 40)} ${id}` } diff --git a/lib/format-tap.js b/lib/format-tap.js new file mode 100644 index 0000000..579532f --- /dev/null +++ b/lib/format-tap.js @@ -0,0 +1,93 @@ +'use strict' + +const tap = require('tap') + +module.exports = function formatTap(context, msgs, validator, cb) { + tap.test(context.sha, (t) => { + for (const m of msgs) { + switch (m.level) { + case 'pass': + const a = m.string + ? ` [${m.string}]` + : '' + t.pass(`${m.id}: ${m.message}${a}`) + break + case 'skip': + t.pass(`${m.id}: ${m.message}`, { + skip: true + }) + break + case 'fail': + onFail(context, m, validator, t) + break + } + } + + t.end() + }) + + tap.on('end', cb) +} + +function onFail(context, m, validator, t) { + switch (m.id) { + case 'line-length': + case 'title-length': + lengthFail(context, m, validator, t) + break + case 'subsystem': + subsystemFail(context, m, validator, t) + break + default: + defaultFail(context, m, validator, t) + break + } +} + +function lengthFail(context, m, validator, t) { + const body = m.id === 'title-length' + ? context.title + : context.body + t.fail(`${m.id}: ${m.message}`, { + found: m.string.length + , compare: '<=' + , wanted: m.maxLength + , at: { + line: m.line || 0 + , column: m.column || 0 + , file: body + , function: '' + } + }) +} + +function subsystemFail(context, m, validator, t) { + t.fail(`${m.id}: ${m.message} (${m.string})`, { + found: m.string + , compare: 'indexOf() !== -1' + , wanted: m.wanted || '' + , at: { + line: m.line || 0 + , column: m.column || 0 + , file: context.title + , function: '' + } + }) +} + +function defaultFail(context, m, validator, t) { + const body = m.id === 'subsystem' + ? context.title + : context.body + t.fail(`${m.id}: ${m.message} (${m.string})`, { + found: m.string + , compare: Array.isArray(m.wanted) ? 'indexOf() !== -1' : '===' + , wanted: m.wanted || '' + , at: { + line: m.line || 0 + , column: m.column || 0 + , file: context.description + , function: '' + } + }) +} diff --git a/lib/index.js b/lib/index.js index 3724521..9af4383 100644 --- a/lib/index.js +++ b/lib/index.js @@ -67,7 +67,7 @@ module.exports = class ValidateCommit extends EE { throw new Error('Invalid report. Missing commit sha') } - if (opts.data.level === 'error') + if (opts.data.level === 'fail') this.errors++ const ar = this.messages.get(sha) || [] ar.push(opts.data) diff --git a/lib/rules/fixes-url.js b/lib/rules/fixes-url.js index 08a4a94..953c9bb 100644 --- a/lib/rules/fixes-url.js +++ b/lib/rules/fixes-url.js @@ -12,8 +12,15 @@ module.exports = { , options: {} , validate: (context, rule) => { const parsed = context.toJSON() - if (!Array.isArray(parsed.fixes)) return - if (!parsed.fixes.length) return + if (!Array.isArray(parsed.fixes) || !parsed.fixes.length) { + context.report({ + id: id + , message: 'skipping fixes-url' + , string: '' + , level: 'skip' + }) + return + } for (const url of parsed.fixes) { if (url[0] === '#') { // See nodejs/node#2aa376914b621018c5784104b82c13e78ee51307 @@ -25,7 +32,17 @@ module.exports = { , string: url , line: line , column: column - , level: 'error' + , level: 'fail' + }) + } else { + const { line, column } = findLineAndColumn(context.body, url) + context.report({ + id: id + , message: 'Valid fixes url' + , string: url + , line: line + , column: column + , level: 'pass' }) } } diff --git a/lib/rules/line-length.js b/lib/rules/line-length.js index 9257990..4e32f5b 100644 --- a/lib/rules/line-length.js +++ b/lib/rules/line-length.js @@ -19,10 +19,20 @@ module.exports = { const parsed = context.toJSON() // release commits include the notable changes from the changelog // in the commit message - if (parsed.release) return + if (parsed.release) { + context.report({ + id: id + , message: 'skipping line-length for release commit' + , string: '' + , level: 'skip' + }) + return + } + var failed = false for (let i = 0; i < parsed.body.length; i++) { const line = parsed.body[i] if (line.length > len) { + failed = true context.report({ id: id , message: `Line should be <= ${len} columns.` @@ -30,9 +40,18 @@ module.exports = { , maxLength: len , line: i , column: len - , level: 'error' + , level: 'fail' }) } } + + if (!failed) { + context.report({ + id: id + , message: 'line-lengths are valid' + , string: '' + , level: 'pass' + }) + } } } diff --git a/lib/rules/pr-url.js b/lib/rules/pr-url.js index cf1772d..88562a3 100644 --- a/lib/rules/pr-url.js +++ b/lib/rules/pr-url.js @@ -18,28 +18,38 @@ module.exports = { , string: context.prUrl , line: 0 , column: 0 - , level: 'error' + , level: 'fail' }) + return } - if (context.prUrl && context.prUrl[0] === '#') { - // see nodejs/node#7d3a7ea0d7df9b6f11df723dec370f49f4f87e99 - // for an example - var line = -1 - var column = -1 - for (var i = 0; i < context.body.length; i++) { - const l = context.body[i] - if (~l.indexOf('PR-URL') && ~l.indexOf(context.prUrl)) { - line = i - column = l.indexOf(context.prUrl) + if (context.prUrl) { + if (context.prUrl[0] === '#') { + // see nodejs/node#7d3a7ea0d7df9b6f11df723dec370f49f4f87e99 + // for an example + var line = -1 + var column = -1 + for (var i = 0; i < context.body.length; i++) { + const l = context.body[i] + if (~l.indexOf('PR-URL') && ~l.indexOf(context.prUrl)) { + line = i + column = l.indexOf(context.prUrl) + } } + context.report({ + id: id + , message: 'PR-URL must be a url, not a pr number.' + , string: context.prUrl + , line: line + , column: column + , level: 'fail' + }) } + } else { context.report({ id: id - , message: 'PR-URL must be a url, not a pr number.' + , message: 'PR-URL is valid' , string: context.prUrl - , line: line - , column: column - , level: 'error' + , level: 'pass' }) } } diff --git a/lib/rules/reviewers.js b/lib/rules/reviewers.js index bff9cd9..7450210 100644 --- a/lib/rules/reviewers.js +++ b/lib/rules/reviewers.js @@ -13,7 +13,15 @@ module.exports = { , validate: (context, rule) => { const parsed = context.toJSON() // release commits generally won't have any reviewers - if (parsed.release) return + if (parsed.release) { + context.report({ + id: id + , message: 'skipping reviewers for release commit' + , string: '' + , level: 'skip' + }) + return + } if (!Array.isArray(parsed.reviewers) || !parsed.reviewers.length) { // See nodejs/node#5aac4c42da104c30d8f701f1042d61c2f06b7e6c @@ -24,7 +32,7 @@ module.exports = { , string: null , line: 0 , column: 0 - , level: 'error' + , level: 'fail' }) } @@ -32,5 +40,12 @@ module.exports = { // This will probably be easier to do once we move gitlint-parser-node // over to using an array of objects with parsed reviewers vs an array // of strings + + context.report({ + id: id + , message: 'reviewers are valid' + , string: '' + , level: 'pass' + }) } } diff --git a/lib/rules/subsystem.js b/lib/rules/subsystem.js index 7007f6b..499334f 100644 --- a/lib/rules/subsystem.js +++ b/lib/rules/subsystem.js @@ -76,24 +76,44 @@ module.exports = { , string: parsed.title , line: 0 , column: 0 - , level: 'error' + , level: 'fail' + , wanted: subs + }) + } else { + context.report({ + id: id + , message: 'Release commits does not have subsystems' + , string: '' + , level: 'skip' }) } } else { + var failed = false for (const sub of parsed.subsystems) { if (!~subs.indexOf(sub)) { + failed = true // invalid subsystem const column = parsed.title.indexOf(sub) context.report({ id: id - , message: `Invalid subsystem: "${sub}".` + , message: `Invalid subsystem: "${sub}"` , string: parsed.title , line: 0 , column: column - , level: 'warning' + , level: 'fail' + , wanted: subs }) } } + + if (!failed) { + context.report({ + id: id + , message: 'valid subsystems' + , string: parsed.subsystems.join(',') + , level: 'pass' + }) + } } } } diff --git a/lib/rules/title-length.js b/lib/rules/title-length.js index 24c946a..97b7bb8 100644 --- a/lib/rules/title-length.js +++ b/lib/rules/title-length.js @@ -24,8 +24,16 @@ module.exports = { , maxLength: len , line: 0 , column: len - , level: 'error' + , level: 'fail' }) + return } + + context.report({ + id: id + , message: `Title is <= ${len} columns.` + , string: '' + , level: 'pass' + }) } } diff --git a/lib/utils.js b/lib/utils.js index 45b5177..9f766ad 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -19,11 +19,13 @@ exports.rightPad = function rightPad(str, max) { exports.header = (sha, status) => { switch (status) { + case 'skip': case 'pass': - return `${CHECK} ${chalk.underline(sha)}` - case 'warn': - return `${WARN} ${chalk.underline(sha)}` - case 'error': + const suffix = status === 'skip' + ? ' # SKIPPED' + : '' + return `${CHECK} ${chalk.underline(sha)}${suffix}` + case 'fail': return `${X} ${chalk.underline(sha)}` } } diff --git a/package.json b/package.json index 36107c3..1a46638 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,11 @@ "chalk": "~1.1.1", "gitlint-parser-node": "^1.0.1", "help": "^2.1.3", - "nopt": "^3.0.6" + "nopt": "^3.0.6", + "tap": "^6.3.0" }, "devDependencies": { - "lintit": "~1.0.1", - "tap": "~5.7.0" + "lintit": "~1.0.1" }, "license": "MIT", "bin": {