From f05fbdac68dd150856b546ba3d47f62ccff2c16c Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Mon, 23 Sep 2024 14:34:56 +0200 Subject: [PATCH] Add `filter` option Closes GH-32. --- index.js | 3 +- lib/index.js | 79 ++++++++++++++++++++++++++++++++++----------------- readme.md | 33 ++++++++++++++++++++- test/index.js | 37 ++++++++++++++++++++++-- 4 files changed, 122 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index df50d01..db0f607 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ /** * @typedef {import('remark-contributors').Contributor} Contributor + * @typedef {import('./lib/index.js').Filter} Filter * @typedef {import('./lib/index.js').Options} Options */ -export {default} from './lib/index.js' +export {defaultFilter, default} from './lib/index.js' diff --git a/lib/index.js b/lib/index.js index c3beab6..9f52bbb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,15 +19,6 @@ * @property {Social | undefined} social * Social profile. * - * @typedef RawContributor - * Contributor found by `contributorsFromGit`. - * @property {number} commits - * Number of commits. - * @property {string} name - * Name. - * @property {string} email - * Email. - * * @typedef ContributorsModule * Contributors module. * @property {Array | null | undefined} [contributors] @@ -35,6 +26,15 @@ * @property {Array | null | undefined} [default] * Default export. * + * @callback Filter + * Filter contributors. + * @param {RawContributor} contributor + * Contributor found by `contributorsFromGit`. + * @param {Record} metadata + * Associated metadata found in `package.json` or `options.contributors`. + * @returns {boolean} + * Whether to include the contributor. + * * @typedef Options * Configuration. * @property {boolean | null | undefined} [appendIfMissing=false] @@ -48,12 +48,23 @@ * @property {string | null | undefined} [cwd] * Working directory from which to resolve a `contributors` module, if any * (default: `file.cwd`). + * @property {Filter | null | undefined} [filter=defaultFilter] + * Filter contributors (default: `defaultFilter`). * @property {number | null | undefined} [limit=0] * Limit the rendered contributors (default: `0`); * `0` (or lower) includes all contributors; * if `limit` is given, only the top `` contributors, sorted by commit * count, are rendered. * + * @typedef RawContributor + * Contributor found by `contributorsFromGit`. + * @property {number} commits + * Number of commits. + * @property {string} email + * Email. + * @property {string} name + * Name. + * * @typedef Social * Social profile. * @property {string} text @@ -81,6 +92,27 @@ const noreply = '@users.noreply.github.com' const headingExpression = /^contributors$/i const idFields = ['email', 'name', 'github', 'social.url'] +/** + * Default filter for contributors; + * currently filters out Greenkeeper. + * + * @param {RawContributor} contributor + * Contributor found by `contributorsFromGit`. + * @param {Record} metadata + * Associated metadata found in `package.json` or `options.contributors`. + * @returns {boolean} + * Whether to include the contributor. + * @satisfies {Filter} + */ +export function defaultFilter(contributor, metadata) { + return ( + !contributor.email.endsWith('@greenkeeper.io') && + contributor.name.toLowerCase() !== 'greenkeeper' && + metadata.github !== 'greenkeeper[bot]' && + metadata.github !== 'greenkeeperio-bot' + ) +} + /** * Generate a list of Git contributors. * @@ -100,6 +132,7 @@ const idFields = ['email', 'name', 'github', 'social.url'] export default function remarkGitContributors(options) { const settings = typeof options === 'string' ? {contributors: options} : options || {} + const filter = settings.filter || defaultFilter /** * Transform. @@ -140,9 +173,8 @@ export default function remarkGitContributors(options) { indexContributor(indices, /** @type {Contributor} */ (packageData.author)) if (Array.isArray(packageData.contributors)) { - let index = -1 - while (++index < packageData.contributors.length) { - indexContributor(indices, packageData.contributors[index]) + for (const contributor of packageData.contributors) { + indexContributor(indices, contributor) } } @@ -183,10 +215,9 @@ export default function remarkGitContributors(options) { /** @type {Array} */ let contributors = [] - let index = -1 - while (++index < gitContributors.length) { - const {name, email, commits} = gitContributors[index] + for (const gitContributor of gitContributors) { + const {name, email, commits} = gitContributor if (!email) { file.message( @@ -199,8 +230,11 @@ export default function remarkGitContributors(options) { continue } - const metadata = - indices.email[email] || indices.name[name.toLowerCase()] || {} + const nameLower = name.toLowerCase() + + const metadata = { + ...(indices.email[email] || indices.name[nameLower]) + } if (email.endsWith(noreply)) { metadata.github = email @@ -209,12 +243,7 @@ export default function remarkGitContributors(options) { indexValue(indices.github, metadata.github, metadata) } - if ( - email.endsWith('@greenkeeper.io') || - name.toLowerCase() === 'greenkeeper' || - metadata.github === 'greenkeeper[bot]' || - metadata.github === 'greenkeeperio-bot' - ) { + if (!filter(gitContributor, metadata)) { continue } @@ -289,10 +318,8 @@ export default function remarkGitContributors(options) { } // Whether an existing contributor was found that matched an id field. let found = false - let idIndex = -1 - while (++idIndex < idFields.length) { - const idField = idFields[idIndex] + for (const idField of idFields) { /** @type {unknown} */ const id = dlv(contributor, idField) diff --git a/readme.md b/readme.md index 68a516a..12b2e1c 100644 --- a/readme.md +++ b/readme.md @@ -17,8 +17,10 @@ * [Install](#install) * [Use](#use) * [API](#api) + * [`defaultFilter(contributor, metadata)`](#defaultfiltercontributor-metadata) * [`unified().use(remarkGitContributors[, options])`](#unifieduseremarkgitcontributors-options) * [`Contributor`](#contributor) + * [`Filter`](#filter) * [`Options`](#options) * [Examples](#examples) * [Example: CLI](#example-cli) @@ -126,6 +128,11 @@ MIT This package exports no identifiers. The default export is [`remarkGitContributors`][api-remark-git-contributors]. +### `defaultFilter(contributor, metadata)` + +Default filter for contributors ([`Filter`][api-filter]); +currently filters out Greenkeeper. + ### `unified().use(remarkGitContributors[, options])` Generate a list of Git contributors. @@ -158,6 +165,21 @@ type). type Contributor = Record | string ``` +### `Filter` + +Filter contributors (TypeScript type). + +###### Parameters + +* `contributor` (`Contributor`) + — contributor found by `contributorsFromGit` +* `metadata` (`Record`) + — associated metadata found in `package.json` or `options.contributors` + +###### Returns + +Whether to include the contributor (`boolean`). + ### `Options` Configuration (TypeScript type). @@ -175,6 +197,9 @@ Configuration (TypeScript type). throws if no contributors are found or given * `cwd` (`string`, default: `file.cwd`) — working directory from which to resolve a `contributors` module, if any +* `filter` ([`Filter`][api-filter], default: + [`defaultFilter`][api-default-filter]) + — filter contributors * `limit` (`number`, default: `0`) — limit the rendered contributors; `0` (or lower) includes all contributors; @@ -384,7 +409,9 @@ export default contributors ## Types This package is fully typed with [TypeScript][]. -It exports the additional types [`Contributor`][api-contributor] and +It exports the additional types +[`Contributor`][api-contributor], +[`Filter`][api-filter], and [`Options`][api-options]. ## Compatibility @@ -511,6 +538,10 @@ abide by its terms. [api-contributor]: #contributor +[api-default-filter]: #defaultfiltercontributor-metadata + +[api-filter]: #filter + [api-options]: #options [api-remark-git-contributors]: #unifieduseremarkgitcontributors-options diff --git a/test/index.js b/test/index.js index b97337e..d8d4d8b 100644 --- a/test/index.js +++ b/test/index.js @@ -25,7 +25,7 @@ * @property {boolean | null | undefined} [broken] * Break the `package.json` (default: `false`). * - * @typedef {[string, string]} User + * @typedef {[name: string, email: string]} User * User name and email. */ @@ -59,7 +59,7 @@ test('remark-git-contributors', async function (t) { await t.test('should expose the public api', async function () { assert.deepEqual( Object.keys(await import('remark-git-contributors')).sort(), - ['default'] + ['default', 'defaultFilter'] ) }) @@ -356,6 +356,39 @@ test('remark-git-contributors', async function (t) { } }) + await t.test('should support a custom `filter`', async function () { + const cwd = temporary() + const [input] = await getFixtures('00') + + await createPackage(cwd) + await createCommits(cwd, { + users: [ + ['beep', 'beep@boop.io'], + ['bea', 'äkta@människor.io'] + ] + }) + + const file = await remark() + .use(remarkGfm) + .use(remarkGitContributors, { + filter(d) { + return d.name !== 'beep' + } + }) + .process(new VFile({cwd, path: 'input.md', value: input})) + + assert.equal( + String(file), + '# Contributors\n\n| Name |\n| :------ |\n| **bea** |\n' + ) + assert.deepEqual( + file.messages.map(function (d) { + return d.reason + }), + ['Unexpected missing social handle for contributor `äkta@människor.io`'] + ) + }) + await t.test('should work w/ invalid twitter', async function () { const cwd = temporary() const [input, expected] = await getFixtures('00')