From 44673d5eaeb8f4e850fe85ded10909cbdc1a9e55 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Wed, 15 Apr 2026 18:25:32 +0200 Subject: [PATCH] feat!: parse trailers using `git` if available Signed-off-by: Antoine du Hamel --- lib/gitlint-parser.js | 171 +++++++++++++ lib/rules/line-length.js | 33 ++- lib/rules/signed-off-by.js | 10 +- lib/validator.js | 2 +- npm-shrinkwrap.json | 36 +-- package.json | 2 +- test/gitlint-parser.js | 321 ++++++++++++++++++++++++ test/rules/assisted-by-is-trailer.js | 2 +- test/rules/co-authored-by-is-trailer.js | 2 +- test/rules/fixes-url.js | 2 +- test/rules/line-after-title.js | 2 +- test/rules/line-length.js | 66 ++++- test/rules/reviewers.js | 2 +- test/rules/signed-off-by.js | 2 +- test/rules/subsystem.js | 2 +- test/rules/title-format.js | 2 +- test/validator.js | 54 +--- 17 files changed, 590 insertions(+), 121 deletions(-) create mode 100644 lib/gitlint-parser.js create mode 100644 test/gitlint-parser.js diff --git a/lib/gitlint-parser.js b/lib/gitlint-parser.js new file mode 100644 index 0000000..b87c95f --- /dev/null +++ b/lib/gitlint-parser.js @@ -0,0 +1,171 @@ +import { spawnSync } from 'node:child_process' +import Base from 'gitlint-parser-base' + +const revertRE = /Revert "(.*)"$/ +const workingRE = /Working on v([\d]+)\.([\d]+).([\d]+)$/ +const releaseRE = /([\d]{4})-([\d]{2})-([\d]{2}),? Version/ +const reviewedByRE = /^Reviewed-By: (.*)$/ +const fixesRE = /^Fixes: (.*)$/ +const prUrlRE = /^PR-URL: (.*)$/ +const refsRE = /^Refs?: (.*)$/ + +export default class Parser extends Base { + constructor (str, validator) { + super(str, validator) + this.subsystems = [] + this.fixes = [] + this.prUrl = null + this.refs = [] + this.reviewers = [] + this._metaStart = 0 + this._metaEnd = 0 + this._parse() + } + + _setMetaStart (n) { + if (this._metaStart) return + this._metaStart = n + } + + _setMetaEnd (n) { + if (n < this._metaEnd) return + this._metaEnd = n + } + + _parseTrailers (body) { + const interpretTrailers = commitMessage => spawnSync('git', [ + 'interpret-trailers', '--only-trailers', '--only-input', '--no-divider' + ], { + encoding: 'utf-8', + input: `${commitMessage}\n` + }).stdout + + let originalTrailers + try { + originalTrailers = interpretTrailers(body.join('\n')).trim() + } catch (err) { + console.warn('git is not available, trailers detection might be a bit ' + + 'off which is acceptable in most cases', err) + return body + } + const trailerFreeBody = body.slice(1) // clone, and remove the first empty line + const stillInTrailers = () => { + const result = interpretTrailers(trailerFreeBody.join('\n')) + return result.length && originalTrailers.startsWith(result.trim()) + } + for (let i = trailerFreeBody.length - 1; stillInTrailers(); i--) { + // Remove last line until git no longer detects any trailers + trailerFreeBody.pop() + } + this._metaStart = trailerFreeBody.length + 1 + for (let i = trailerFreeBody.length - 1; trailerFreeBody[i] === ''; i--) { + // Remove additional empty line(s) + trailerFreeBody.pop() + } + this._metaEnd = body.length - 1 + this.trailerFreeBody = trailerFreeBody + return (this.trailers = originalTrailers.split('\n')) + } + + _parse () { + const revert = this.isRevert() + if (!revert) { + this.subsystems = getSubsystems(this.title || '') + } else { + const matches = this.title.match(revertRE) + if (matches) { + const title = matches[1] + this.subsystems = getSubsystems(title) + } + } + + const trailers = this._parseTrailers(this.body) + + for (let i = 0; i < trailers.length; i++) { + const line = trailers[i] + const reviewedBy = reviewedByRE.exec(line) + if (reviewedBy) { + this._setMetaStart(i) + this._setMetaEnd(i) + this.reviewers.push(reviewedBy[1]) + continue + } + + const fixes = fixesRE.exec(line) + if (fixes) { + this._setMetaStart(i) + this._setMetaEnd(i) + this.fixes.push(fixes[1]) + continue + } + + const prUrl = prUrlRE.exec(line) + if (prUrl) { + this._setMetaStart(i) + this._setMetaEnd(i) + this.prUrl = prUrl[1] + continue + } + + const refs = refsRE.exec(line) + if (refs) { + this._setMetaStart(i) + this._setMetaEnd(i) + this.refs.push(refs[1]) + continue + } + + if (this._metaStart && !this._metaEnd) { this._setMetaEnd(i) } + } + } + + isRevert () { + return revertRE.test(this.title) + } + + isWorkingCommit () { + return workingRE.test(this.title) + } + + isReleaseCommit () { + return releaseRE.test(this.title) + } + + toJSON () { + return { + sha: this.sha, + title: this.title, + subsystems: this.subsystems, + author: this.author, + date: this.date, + fixes: this.fixes, + refs: this.refs, + prUrl: this.prUrl, + reviewers: this.reviewers, + body: this.body, + trailers: this.trailers, + trailerFreeBody: this.trailerFreeBody, + revert: this.isRevert(), + release: this.isReleaseCommit(), + working: this.isWorkingCommit(), + metadata: { + start: this._metaStart, + end: this._metaEnd + } + } + } +} + +function getSubsystems (str) { + str = str || '' + const colon = str.indexOf(':') + if (colon === -1) { + return [] + } + + const subStr = str.slice(0, colon) + const subs = subStr.split(',') + return subs.map((item) => { + return item.trim() + }) +} diff --git a/lib/rules/line-length.js b/lib/rules/line-length.js index 9a3be31..a3aa52d 100644 --- a/lib/rules/line-length.js +++ b/lib/rules/line-length.js @@ -7,10 +7,12 @@ export default { recommended: true }, defaults: { - length: 72 + length: 72, + trailerLength: 120 }, options: { - length: 72 + length: 72, + trailerLength: 120 }, validate: (context, rule) => { const len = rule.options.length @@ -27,19 +29,14 @@ export default { return } let failed = false - for (let i = 0; i < parsed.body.length; i++) { - const line = parsed.body[i] + const body = parsed.trailerFreeBody ?? parsed.body + for (let i = 0; i < body.length; i++) { + const line = body[i] // Skip quoted lines, e.g. for original commit messages of V8 backports. if (line.startsWith(' ')) { continue } // Skip lines with URLs. if (/https?:\/\//.test(line)) { continue } - // Skip co-authorship. - if (/^co-authored-by:/i.test(line)) { continue } - // Skip DCO sign-offs. - if (/^signed-off-by:/i.test(line)) { continue } - // Skip agentic assistants. - if (/^assisted-by:/i.test(line)) { continue } if (line.length > len) { failed = true @@ -54,6 +51,22 @@ export default { }) } } + for (let i = 0; i < (parsed.trailers?.length ?? 0); i++) { + const line = parsed.trailers[i] + const len = rule.options.trailerLength + if (line.length > len) { + failed = true + context.report({ + id, + message: `Trailer should be <= ${len} columns.`, + string: line, + maxLength: len, + line: i + parsed.body.length - parsed.trailers.length, + column: len, + level: 'fail' + }) + } + } if (!failed) { context.report({ diff --git a/lib/rules/signed-off-by.js b/lib/rules/signed-off-by.js index b92d91d..3bdf6ce 100644 --- a/lib/rules/signed-off-by.js +++ b/lib/rules/signed-off-by.js @@ -67,10 +67,12 @@ export default { return } + const body = parsed.trailers ?? parsed.body + // Backport commits (identified by a Backport-PR-URL trailer) are // cherry-picks of existing commits into release branches. The // original commit was already validated. - if (parsed.body.some((line) => backportPattern.test(line))) { + if (body.some((line) => backportPattern.test(line))) { context.report({ id, message: 'skipping sign-off for backport commit', @@ -80,9 +82,9 @@ export default { return } - const signoffs = parsed.body - .map((line, i) => [line, i]) - .filter(([line]) => signoffPattern.test(line)) + const signoffs = body + .filter(line => signoffPattern.test(line)) + .map((line, i) => [line, i + parsed.body.length - body.length]) // Bot-authored commits don't need a sign-off. // If they have one, warn; otherwise pass. diff --git a/lib/validator.js b/lib/validator.js index dfa2a94..c28cc94 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -1,5 +1,5 @@ import EE from 'node:events' -import Parser from 'gitlint-parser-node' +import Parser from './gitlint-parser.js' import BaseRule from './rule.js' // Rules diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9a25b80..87e41c9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -9,7 +9,7 @@ "version": "5.0.1", "license": "MIT", "dependencies": { - "gitlint-parser-node": "^1.1.0" + "gitlint-parser-base": "^2.0.0" }, "bin": { "core-validate-commit": "bin/cmd.js" @@ -55,7 +55,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1031,7 +1030,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -1223,19 +1221,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "extraneous": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/check-pkg": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-pkg/-/check-pkg-2.1.1.tgz", @@ -1918,7 +1903,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2126,7 +2110,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -2194,7 +2177,6 @@ "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "builtins": "^5.0.1", "eslint-plugin-es": "^4.1.0", @@ -2221,7 +2203,6 @@ "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2238,7 +2219,6 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -2407,7 +2387,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2883,15 +2862,6 @@ "integrity": "sha512-kWkbGGH55AigPQgTz9ZEomv8uQn2GRZiFgAg+SbTIV+8ineU8f3QDeAQHzS7h9yKVuE7cjcgQm0ENmHPM0bxMQ==", "license": "MIT" }, - "node_modules/gitlint-parser-node": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/gitlint-parser-node/-/gitlint-parser-node-1.1.0.tgz", - "integrity": "sha512-h99xAvajhfkTtwVo8q9oBlL8+QrljJltguykpszQWyu0D9SVE5+5CqfEn9qn5IJ254Q2mr1ByjrFu9Rs2niSyA==", - "license": "MIT", - "dependencies": { - "gitlint-parser-base": "^2.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6008,7 +5978,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -6465,7 +6434,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -6594,7 +6562,6 @@ ], "inBundle": true, "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001565", "electron-to-chromium": "^1.4.601", @@ -7365,7 +7332,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" diff --git a/package.json b/package.json index a63502e..dbe4224 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test-ci": "npm run test && c8 report --reporter=lcov" }, "dependencies": { - "gitlint-parser-node": "^1.1.0" + "gitlint-parser-base": "^2.0.0" }, "devDependencies": { "c8": "^7.13.0", diff --git a/test/gitlint-parser.js b/test/gitlint-parser.js new file mode 100644 index 0000000..e00d5ab --- /dev/null +++ b/test/gitlint-parser.js @@ -0,0 +1,321 @@ +import { test } from 'tap' +import Parser from '../lib/gitlint-parser.js' + +test('Parser', (t) => { + t.test('basic commit with PR-URL and reviewers', (tt) => { + tt.plan(10) + const input = `commit e7c077c610afa371430180fbd447bfef60ebc5ea +Author: Calvin Metcalf +Date: Tue Apr 12 15:42:23 2016 -0400 + + stream: make null an invalid chunk to write in object mode + + this harmonizes behavior between readable, writable, and transform + streams so that they all handle nulls in object mode the same way by + considering them invalid chunks. + + PR-URL: https://github.com/nodejs/node/pull/6170 + Reviewed-By: James M Snell + Reviewed-By: Matteo Collina ` + + const data = { name: 'biscuits' } + + const v = { + report: (obj) => { + tt.pass('called report') + tt.equal(obj.data, data, 'obj') + } + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, 'e7c077c610afa371430180fbd447bfef60ebc5ea', 'sha') + tt.equal(c.author, 'Calvin Metcalf ', 'author') + tt.equal(c.date, 'Tue Apr 12 15:42:23 2016 -0400', 'date') + tt.deepEqual(c.subsystems, ['stream'], 'subsystems') + tt.deepEqual(c.fixes, [], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/6170', 'prUrl') + tt.deepEqual(c.reviewers, [ + 'James M Snell ', + 'Matteo Collina ' + ], 'reviewers') + tt.deepEqual(c.metadata, { + start: 5, + end: 7 + }, 'metadata') + p.report(data) + }) + + t.test('basic commit using format=fuller', (tt) => { + tt.plan(9) + const input = `commit e7c077c610afa371430180fbd447bfef60ebc5ea +Author: Calvin Metcalf +AuthorDate: Tue Apr 12 15:42:23 2016 -0400 +Commit: James M Snell +CommitDate: Tue Apr 12 15:42:23 2016 -0400 + + stream: make null an invalid chunk to write in object mode + + this harmonizes behavior between readable, writable, and transform + streams so that they all handle nulls in object mode the same way by + considering them invalid chunks. + + PR-URL: https://github.com/nodejs/node/pull/6170 + Reviewed-By: James M Snell + Reviewed-By: Matteo Collina ` + + const data = { name: 'biscuits' } + + const v = { + report: (obj) => { + tt.pass('called report') + tt.equal(obj.data, data, 'obj') + } + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, 'e7c077c610afa371430180fbd447bfef60ebc5ea', 'sha') + tt.equal(c.author, 'Calvin Metcalf ', 'author') + tt.equal(c.date, 'Tue Apr 12 15:42:23 2016 -0400', 'date') + tt.deepEqual(c.subsystems, ['stream'], 'subsystems') + tt.deepEqual(c.fixes, [], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/6170', 'prUrl') + tt.deepEqual(c.reviewers, [ + 'James M Snell ', + 'Matteo Collina ' + ], 'reviewers') + p.report(data) + }) + + t.test('revert commit', (tt) => { + tt.plan(13) + const input = `commit 1d4c7993a9c6dcacaca5074a80b1043e977c43fb +Author: Rod Vagg +Date: Wed Jun 8 16:32:10 2016 +1000 + + Revert "test: change duration_ms to duration" + + This reverts commit d413378e513c47a952f7979c74f4a4d9f144ca7d. + + PR-URL: https://github.com/nodejs/node/pull/7216 + Reviewed-By: Colin Ihrig + Reviewed-By: James M Snell + Reviewed-By: Michaël Zasso + Reviewed-By: Johan Bergström ` + const data = { name: 'biscuits' } + + const v = { + report: (obj) => { + tt.pass('called report') + tt.equal(obj.data, data, 'obj') + } + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, '1d4c7993a9c6dcacaca5074a80b1043e977c43fb', 'sha') + tt.equal(c.author, 'Rod Vagg ', 'author') + tt.equal(c.date, 'Wed Jun 8 16:32:10 2016 +1000', 'date') + tt.deepEqual(c.subsystems, ['test'], 'subsystems') + tt.deepEqual(c.fixes, [], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/7216', 'prUrl') + tt.deepEqual(c.reviewers, [ + 'Colin Ihrig ', + 'James M Snell ', + 'Michaël Zasso ', + 'Johan Bergström ' + ], 'reviewers') + + tt.equal(c.revert, true, 'revert') + tt.equal(c.release, false, 'release') + tt.equal(c.working, false, 'working') + tt.deepEqual(c.metadata, { + start: 3, + end: 7 + }, 'metadata') + p.report(data) + }) + + t.test('backport commit', (tt) => { + const input = `commit 5cdbbdf94d6f656230293ddd2a71dedf11cab17d +Author: Ben Noordhuis +Date: Thu Jul 7 14:29:32 2016 -0700 + + deps: cherry-pick 1f53e42 from v8 upstream + + Original commit message: + + Handle symbols in FrameMirror#invocationText(). + + Fix a TypeError when putting together the invocationText for a + symbol method's stack frame. + + See https://github.com/nodejs/node/issues/7536. + + Review-Url: https://codereview.chromium.org/2122793003 + Cr-Commit-Position: refs/heads/master@{#37597} + + Fixes: https://github.com/nodejs/node/issues/7536 + PR-URL: https://github.com/nodejs/node/pull/7612 + Reviewed-By: Colin Ihrig + Reviewed-By: Michaël Zasso ` + const data = { name: 'biscuits' } + + const v = { + report: (obj) => { + tt.pass('called report') + tt.equal(obj.data, data, 'obj') + } + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, '5cdbbdf94d6f656230293ddd2a71dedf11cab17d', 'sha') + tt.equal(c.author, 'Ben Noordhuis ', 'author') + tt.equal(c.date, 'Thu Jul 7 14:29:32 2016 -0700', 'date') + tt.deepEqual(c.subsystems, ['deps'], 'subsystems') + tt.deepEqual(c.fixes, [ + 'https://github.com/nodejs/node/issues/7536' + ], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/7612', 'prUrl') + tt.deepEqual(c.reviewers, [ + 'Colin Ihrig ', + 'Michaël Zasso ' + ], 'reviewers') + + tt.equal(c.revert, false, 'revert') + tt.equal(c.release, false, 'release') + tt.equal(c.working, false, 'working') + tt.deepEqual(c.metadata, { + start: 13, + end: 16 + }, 'metadata') + p.report(data) + tt.end() + }) + + t.test('release commit', (tt) => { + /* eslint-disable */ + const input = `commit 6a9438343bb63e2c1fc028f2e9387e6ae41b9fc8 +Author: Evan Lucas +Date: Thu Jun 23 07:27:44 2016 -0500 + + 2016-06-23, Version 5.12.0 (Stable) + + Notable changes: + + This is a security release. All Node.js users should consult the security + release summary at https://nodejs.org/en/blog/vulnerability/june-2016-security-releases + for details on patched vulnerabilities. + + * **buffer** + * backport allocUnsafeSlow (Сковорода Никита Андреевич) [#7169](https://github.com/nodejs/node/pull/7169) + * ignore negative allocation lengths (Anna Henningsen) [#7221](https://github.com/nodejs/node/pull/7221) + * **deps**: backport 3a9bfec from v8 upstream (Ben Noordhuis) [nodejs/node-private#40](https://github.com/nodejs/node-private/pull/40) + * Fixes a Buffer overflow vulnerability discovered in v8. More details + can be found in the CVE (CVE-2016-1699). + + PR-URL: https://github.com/nodejs/node-private/pull/51` + /* eslint-enable */ + const v = { + report: () => {} + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, '6a9438343bb63e2c1fc028f2e9387e6ae41b9fc8', 'sha') + tt.equal(c.author, 'Evan Lucas ', 'author') + tt.equal(c.date, 'Thu Jun 23 07:27:44 2016 -0500', 'date') + tt.deepEqual(c.subsystems, [], 'subsystems') + tt.deepEqual(c.fixes, [], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node-private/pull/51', 'prUrl') + tt.deepEqual(c.reviewers, [], 'reviewers') + tt.deepEqual(c.metadata, { + start: 14, + end: 14 + }, 'metadata') + + tt.equal(c.revert, false, 'revert') + tt.equal(c.release, true, 'release') + tt.equal(c.working, false, 'working') + tt.end() + }) + + t.test('extra meta', (tt) => { + /* eslint-disable */ + const input = { + "sha": "c5545f2c63fe30b0cfcdafab18c26df8286881d0", + "url": "https://api.github.com/repos/nodejs/node/git/commits/c5545f2c63fe30b0cfcdafab18c26df8286881d0", + "html_url": "https://github.com/nodejs/node/commit/c5545f2c63fe30b0cfcdafab18c26df8286881d0", + "author": { + "name": "Anna Henningsen", + "email": "anna@addaleax.net", + "date": "2016-09-13T10:57:49Z" + }, + "committer": { + "name": "Anna Henningsen", + "email": "anna@addaleax.net", + "date": "2016-09-19T12:50:57Z" + }, + "tree": { + "sha": "b505c0ffa0555730e9f4cdb391d1ebeb48bb2f59", + "url": "https://api.github.com/repos/nodejs/node/git/trees/b505c0ffa0555730e9f4cdb391d1ebeb48bb2f59" + }, + "message": "fs: fix handling of `uv_stat_t` fields\n\n`FChown` and `Chown` test that the `uid` and `gid` parameters\nthey receive are unsigned integers, but `Stat()` and `FStat()`\nwould return the corresponding fields of `uv_stat_t` as signed\nintegers. Applications which pass those these values directly\nto `Chown` may fail\n(e.g. for `nobody` on OS X, who has an `uid` of `-2`, see e.g.\nhttps://github.com/nodejs/node-v0.x-archive/issues/5890).\n\nThis patch changes the `Integer::New()` call for `uid` and `gid`\nto `Integer::NewFromUnsigned()`.\n\nAll other fields are kept as they are, for performance, but\nstrictly speaking the respective sizes of those\nfields aren’t specified, either.\n\nRef: https://github.com/npm/npm/issues/13918\nPR-URL: https://github.com/nodejs/node/pull/8515\nReviewed-By: Ben Noordhuis \nReviewed-By: Sakthipriyan Vairamani \nReviewed-By: James M Snell ", + "parents": [ + { + "sha": "4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4", + "url": "https://api.github.com/repos/nodejs/node/git/commits/4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4", + "html_url": "https://github.com/nodejs/node/commit/4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4" + } + ] + } + /* eslint-enable */ + const v = { + report: () => {} + } + const p = new Parser(input, v) + const c = p.toJSON() + tt.equal(c.sha, 'c5545f2c63fe30b0cfcdafab18c26df8286881d0', 'sha') + tt.equal(c.author, 'Anna Henningsen ', 'author') + tt.equal(c.date, '2016-09-13T10:57:49Z', 'date') + tt.deepEqual(c.subsystems, ['fs'], 'subsystems') + tt.deepEqual(c.fixes, [], 'fixes') + tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/8515', 'prUrl') + tt.deepEqual(c.reviewers, [ + 'Ben Noordhuis ', + 'Sakthipriyan Vairamani ', + 'James M Snell ' + ], 'reviewers') + tt.deepEqual(c.metadata, { + start: 16, + end: 20 + }, 'metadata') + tt.deepEqual(c.trailers, [ + 'Ref: https://github.com/npm/npm/issues/13918', + 'PR-URL: https://github.com/nodejs/node/pull/8515', + 'Reviewed-By: Ben Noordhuis ', + 'Reviewed-By: Sakthipriyan Vairamani ', + 'Reviewed-By: James M Snell ' + ], 'c.trailers') + tt.deepEqual(c.trailerFreeBody, [ + '`FChown` and `Chown` test that the `uid` and `gid` parameters', + 'they receive are unsigned integers, but `Stat()` and `FStat()`', + 'would return the corresponding fields of `uv_stat_t` as signed', + 'integers. Applications which pass those these values directly', + 'to `Chown` may fail', + '(e.g. for `nobody` on OS X, who has an `uid` of `-2`, see e.g.', + 'https://github.com/nodejs/node-v0.x-archive/issues/5890).', + '', + 'This patch changes the `Integer::New()` call for `uid` and `gid`', + 'to `Integer::NewFromUnsigned()`.', + '', + 'All other fields are kept as they are, for performance, but', + 'strictly speaking the respective sizes of those', + 'fields aren’t specified, either.' + ], 'c.trailerFreeBody') + + tt.equal(c.revert, false, 'revert') + tt.equal(c.release, false, 'release') + tt.equal(c.working, false, 'working') + tt.end() + }) + + t.end() +}) diff --git a/test/rules/assisted-by-is-trailer.js b/test/rules/assisted-by-is-trailer.js index 01d2a2d..c4dd6b0 100644 --- a/test/rules/assisted-by-is-trailer.js +++ b/test/rules/assisted-by-is-trailer.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/assisted-by-is-trailer.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: assisted-by-is-trailer', (t) => { diff --git a/test/rules/co-authored-by-is-trailer.js b/test/rules/co-authored-by-is-trailer.js index 3dc3b8d..c7a9cd9 100644 --- a/test/rules/co-authored-by-is-trailer.js +++ b/test/rules/co-authored-by-is-trailer.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/co-authored-by-is-trailer.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: co-authored-by-is-trailer', (t) => { diff --git a/test/rules/fixes-url.js b/test/rules/fixes-url.js index 1ed7744..4f468b6 100644 --- a/test/rules/fixes-url.js +++ b/test/rules/fixes-url.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/fixes-url.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' const INVALID_PRURL = 'Pull request URL must reference a comment or discussion.' diff --git a/test/rules/line-after-title.js b/test/rules/line-after-title.js index a7e7256..ab9fabb 100644 --- a/test/rules/line-after-title.js +++ b/test/rules/line-after-title.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/line-after-title.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: line-after-title', (t) => { diff --git a/test/rules/line-length.js b/test/rules/line-length.js index 78e8f33..6ad7f32 100644 --- a/test/rules/line-length.js +++ b/test/rules/line-length.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/line-length.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: line-length', (t) => { @@ -31,7 +31,8 @@ ${'aaa'.repeat(30)}` Rule.validate(context, { options: { - length: 72 + length: 72, + trailerLength: 120 } }) }) @@ -59,7 +60,8 @@ ${'aaa'.repeat(30)}` Rule.validate(context, { options: { - length: 72 + length: 72, + trailerLength: 120 } }) tt.end() @@ -93,7 +95,8 @@ That was the original code. Rule.validate(context, { options: { - length: 72 + length: 72, + trailerLength: 120 } }) tt.end() @@ -111,6 +114,8 @@ That was the original code. message: `src: make foo mor foo-ey https://${'very-'.repeat(80)}-long-url.org/ + +Trailer: value ` }, v) @@ -123,13 +128,14 @@ https://${'very-'.repeat(80)}-long-url.org/ Rule.validate(context, { options: { - length: 72 + length: 72, + trailerLength: 120 } }) tt.end() }) - t.test('Co-author lines', (tt) => { + t.test('Co-author trailers', (tt) => { const v = new Validator() const good = new Commit({ @@ -141,6 +147,7 @@ https://${'very-'.repeat(80)}-long-url.org/ }, message: [ 'fixup!: apply case-insensitive suggestion', + '', 'Co-authored-by: Michaël Zasso <37011812+targos@users.noreply.github.com>' ].join('\n') }, v) @@ -154,14 +161,15 @@ https://${'very-'.repeat(80)}-long-url.org/ Rule.validate(good, { options: { - length: 72 + length: 72, + trailerLength: 120 } }) tt.end() }) - t.test('Signed-off-by and Assisted-by lines', (tt) => { + t.test('Signed-off-by and Assisted-by trailers', (tt) => { const v = new Validator() const good = new Commit({ @@ -172,6 +180,8 @@ https://${'very-'.repeat(80)}-long-url.org/ date: '2026-04-10T16:38:01Z' }, message: [ + 'subsystem: foobar', + '', 'Signed-off-by: John Connor <9092381+JConnor1985@users.noreply.github.com>', 'Assisted-by: The Longest-Named Code Agent In The World ' ].join('\n') @@ -186,7 +196,45 @@ https://${'very-'.repeat(80)}-long-url.org/ Rule.validate(good, { options: { - length: 72 + length: 72, + trailerLength: 120 + } + }) + + tt.end() + }) + + t.test('Signed-off-by and Assisted-by non-trailers', (tt) => { + const v = new Validator() + + const context = new Commit({ + sha: '016b3921626b58d9b595c90141e65c6fbe0c78e2', + author: { + name: 'John Connor', + email: '9092381+JConnor1985@users.noreply.github.com', + date: '2026-04-10T16:38:01Z' + }, + message: [ + 'subsystem: foobar', + '', + 'Signed-off-by: John Connor <9092381+JConnor1985@users.noreply.github.com>', + 'Assisted-by: The Longest-Named Code Agent In The World ', + '', + 'Actual-trailer: Value' + ].join('\n') + }, v) + + context.report = (opts) => { + tt.pass('called report') + tt.equal(opts.id, 'line-length', 'id') + tt.equal(opts.string, 'Assisted-by: The Longest-Named Code Agent In The World ', 'string') + tt.equal(opts.level, 'fail', 'level') + } + + Rule.validate(context, { + options: { + length: 72, + trailerLength: 120 } }) diff --git a/test/rules/reviewers.js b/test/rules/reviewers.js index d76865a..3639936 100644 --- a/test/rules/reviewers.js +++ b/test/rules/reviewers.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/reviewers.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' const MSG = 'Commit must have at least 1 reviewer.' diff --git a/test/rules/signed-off-by.js b/test/rules/signed-off-by.js index acff632..859d9e5 100644 --- a/test/rules/signed-off-by.js +++ b/test/rules/signed-off-by.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/signed-off-by.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: signed-off-by', (t) => { diff --git a/test/rules/subsystem.js b/test/rules/subsystem.js index 5e47c01..ccb9bb4 100644 --- a/test/rules/subsystem.js +++ b/test/rules/subsystem.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/subsystem.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' test('rule: subsystem', (t) => { diff --git a/test/rules/title-format.js b/test/rules/title-format.js index 405f37b..c22384b 100644 --- a/test/rules/title-format.js +++ b/test/rules/title-format.js @@ -1,6 +1,6 @@ import { test } from 'tap' import Rule from '../../lib/rules/title-format.js' -import Commit from 'gitlint-parser-node' +import Commit from '../../lib/gitlint-parser.js' import Validator from '../../index.js' function makeCommit (title) { diff --git a/test/validator.js b/test/validator.js index 27dcf5d..2dee88b 100644 --- a/test/validator.js +++ b/test/validator.js @@ -81,34 +81,6 @@ Date: Thu Mar 3 10:10:46 2016 -0600 Signed-off-by: Wyatt Preul ` -/* eslint-disable */ -const str6 = { - "sha": "c5545f2c63fe30b0cfcdafab18c26df8286881d0", - "url": "https://api.github.com/repos/nodejs/node/git/commits/c5545f2c63fe30b0cfcdafab18c26df8286881d0", - "html_url": "https://github.com/nodejs/node/commit/c5545f2c63fe30b0cfcdafab18c26df8286881d0", - "author": { - "name": "Anna Henningsen", - "email": "anna@addaleax.net", - "date": "2016-09-13T10:57:49Z" - }, - "committer": { - "name": "Anna Henningsen", - "email": "anna@addaleax.net", - "date": "2016-09-19T12:50:57Z" - }, - "tree": { - "sha": "b505c0ffa0555730e9f4cdb391d1ebeb48bb2f59", - "url": "https://api.github.com/repos/nodejs/node/git/trees/b505c0ffa0555730e9f4cdb391d1ebeb48bb2f59" - }, - "message": "fs: fix handling of `uv_stat_t` fields\n\n`FChown` and `Chown` test that the `uid` and `gid` parameters\nthey receive are unsigned integers, but `Stat()` and `FStat()`\nwould return the corresponding fields of `uv_stat_t` as signed\nintegers. Applications which pass those these values directly\nto `Chown` may fail\n(e.g. for `nobody` on OS X, who has an `uid` of `-2`, see e.g.\nhttps://github.com/nodejs/node-v0.x-archive/issues/5890).\n\nThis patch changes the `Integer::New()` call for `uid` and `gid`\nto `Integer::NewFromUnsigned()`.\n\nAll other fields are kept as they are, for performance, but\nstrictly speaking the respective sizes of those\nfields aren’t specified, either.\n\nSigned-off-by: Anna Henningsen \nRef: https://github.com/npm/npm/issues/13918\nPR-URL: https://github.com/nodejs/node/pull/8515\nReviewed-By: Ben Noordhuis \nReviewed-By: Sakthipriyan Vairamani \nReviewed-By: James M Snell \n\nundo accidental change to other fields of uv_fs_stat", - "parents": [ - { - "sha": "4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4", - "url": "https://api.github.com/repos/nodejs/node/git/commits/4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4", - "html_url": "https://github.com/nodejs/node/commit/4e76bffc0c7076a5901179e70c7b8a8f9fcd22e4" - } - ] -} /* eslint-enable */ const str7 = `commit 7d3a7ea0d7df9b6f11df723dec370f49f4f87e99 @@ -289,7 +261,7 @@ test('Validator - real commits', (t) => { const filtered = msgs.filter((item) => { return item.level === 'fail' }) - tt.equal(filtered.length, 0, 'messages.length') + tt.same(filtered, [], 'messages.length') tt.end() }) }) @@ -391,30 +363,6 @@ test('Validator - real commits', (t) => { }) }) - t.test('non empty lines after metadata', (tt) => { - const v = new Validator() - v.lint(str6) - v.on('commit', (data) => { - const c = data.commit.toJSON() - tt.equal(c.sha, 'c5545f2c63fe30b0cfcdafab18c26df8286881d0', 'sha') - tt.equal(c.date, '2016-09-13T10:57:49Z', 'date') - tt.same(c.subsystems, ['fs'], 'subsystems') - tt.equal(c.prUrl, 'https://github.com/nodejs/node/pull/8515', 'pr') - tt.equal(c.revert, false, 'revert') - const msgs = data.messages - const filtered = msgs.filter((item) => { - return item.level === 'fail' - }) - tt.equal(filtered.length, 1, 'messages.length') - const item = filtered[0] - tt.equal(item.id, 'metadata-end', 'id') - tt.equal(item.message, 'commit metadata at end of message', 'message') - tt.equal(item.line, 23, 'line') - tt.equal(item.column, 0, 'column') - tt.end() - }) - }) - t.test('trailing punctuation in title line', (tt) => { const v = new Validator({ 'validate-metadata': false