From 824c4bd10656b9c4f36656bc80beb2f75bb83574 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Tue, 31 Jan 2023 18:58:21 +0100 Subject: [PATCH] Add checking of types in scripts --- .gitignore | 1 + package.json | 6 +- script/list-of-humans.js | 147 +++++++++++++++++++++++++++++---------- tsconfig.json | 17 +++++ 4 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 7cfd02b..46c478d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store +*.d.ts *.log node_modules/ diff --git a/package.json b/package.json index 67e1aa1..b13962e 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,20 @@ ], "type": "module", "devDependencies": { + "@types/mdast": "^3.0.0", "mdast-comment-marker": "^2.0.0", "mdast-zone": "^5.0.0", "prettier": "^2.0.0", "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", - "unist-builder": "^3.0.0", + "typescript": "^4.0.0", "xo": "^0.53.0", "yaml": "^2.0.0" }, "scripts": { + "build": "tsc --build --clean && tsc --build", "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", - "test": "npm run format" + "test": "npm run build && npm run format" }, "prettier": { "tabWidth": 2, diff --git a/script/list-of-humans.js b/script/list-of-humans.js index e6506e1..f05d53a 100644 --- a/script/list-of-humans.js +++ b/script/list-of-humans.js @@ -1,27 +1,51 @@ +/** + * @typedef {import('mdast').BlockContent} BlockContent + * @typedef {import('mdast').List} List + * @typedef {import('mdast').ListItem} ListItem + * @typedef {import('mdast').PhrasingContent} PhrasingContent + * @typedef {import('mdast').Root} Root + */ + +/** + * @typedef {'contributor' | 'merger' | 'maintainer' | 'releaser'} Role + * + * @typedef Team + * @property {string} name + * @property {boolean | undefined} [collective] + * @property {string | undefined} [lead] + * @property {Record} humans + * + * @typedef Human + * @property {string} name + * @property {string} email + * @property {string} url + * @property {string} github + * @property {string | undefined} [npm] + */ + +import assert from 'node:assert/strict' import fs from 'node:fs/promises' import yaml from 'yaml' import {zone} from 'mdast-zone' import {commentMarker} from 'mdast-comment-marker' -import {u} from 'unist-builder' +/** @type {Array} */ const humans = yaml.parse( String(await fs.readFile(new URL('../data/humans.yml', import.meta.url))) ) +/** @type {Array} */ const teams = yaml.parse( String(await fs.readFile(new URL('../data/teams.yml', import.meta.url))) ) const own = {}.hasOwnProperty +/** @type {import('unified').Plugin<[], Root>} */ export default function listOfHumans() { - return transform - - function transform(tree, file) { - zone(tree, 'humans', onzone) - - function onzone(start, nodes, end) { - const parameters = commentMarker(start).parameters - const shift = parameters.shift || 0 + return function (tree, file) { + zone(tree, 'humans', function (start, nodes, end) { + const parameters = commentMarker(start)?.parameters || {} + const shift = Number(parameters.shift) || 0 const name = parameters.team if (!name) { @@ -32,17 +56,31 @@ export default function listOfHumans() { if (!team) { file.fail('Missing definition for team `' + name + '`', start) + return } - const content = [u('heading', {depth: 1 + shift}, [u('text', 'Members')])] + /** @type {Array} */ + const content = [ + { + type: 'heading', + // @ts-expect-error: fine. + depth: 1 + shift, + children: [{type: 'text', value: 'Members'}] + } + ] + /** @type {Partial>>} */ const byRole = {} let human for (human in team.humans) { if (own.call(team.humans, human)) { - byRole[team.humans[human]] = ( - byRole[team.humans[human]] || [] - ).concat(human) + const role = team.humans[human] + const list = byRole[role] + if (Array.isArray(list)) { + list.push(human) + } else { + byRole[role] = [human] + } } } @@ -50,64 +88,101 @@ export default function listOfHumans() { content.push( byRole.maintainer ? list(team, byRole.maintainer) - : u('paragraph', [u('text', 'None.')]) + : {type: 'paragraph', children: [{type: 'text', value: 'None.'}]} ) } else { content.push( - u('heading', {depth: 2 + shift}, [u('text', 'Releasers')]), + { + type: 'heading', + // @ts-expect-error: fine. + depth: 2 + shift, + children: [{type: 'text', value: 'Releasers'}] + }, byRole.releaser ? list(team, byRole.releaser) - : u('paragraph', [u('text', 'None.')]), - u('heading', {depth: 2 + shift}, [u('text', 'Mergers')]), + : {type: 'paragraph', children: [{type: 'text', value: 'None.'}]}, + { + type: 'heading', + depth: 2 + shift, + children: [{type: 'text', value: 'Mergers'}] + }, byRole.merger ? list(team, byRole.merger) - : u('paragraph', [u('text', 'None.')]) + : {type: 'paragraph', children: [{type: 'text', value: 'None.'}]} ) } if (byRole.contributor) { content.push( - u('heading', {depth: 1 + shift}, [u('text', 'Contributors')]), + { + type: 'heading', + // @ts-expect-error: fine. + depth: 1 + shift, + children: [{type: 'text', value: 'Contributors'}] + }, list(team, byRole.contributor) ) } return [start].concat(content, end) - } + }) } } +/** + * @param {Team} team + * @param {Array} users + * @returns {List} + */ function list(team, users) { - return u( - 'list', - {ordered: false}, - users - .map((github) => humans.find((p) => p.github === github)) + return { + type: 'list', + ordered: false, + children: users + .map((github) => { + const human = humans.find((p) => p.github === github) + assert(human, 'expected human') + return human + }) .sort((a, b) => { return a.name.localeCompare(b.name) }) .map((human) => { + /** @type {Array} */ const content = [ - u('text', human.name + '\n('), - u('link', {url: 'https://github.com/' + human.github}, [ - u('strong', [u('text', '@' + human.github)]) - ]), - u('text', ')') + {type: 'text', value: human.name + '\n('}, + { + type: 'link', + url: 'https://github.com/' + human.github, + children: [ + { + type: 'strong', + children: [{type: 'text', value: '@' + human.github}] + } + ] + }, + {type: 'text', value: ')'} ] if (human.email) { - content.push(u('text', '\n<'), u('text', human.email), u('text', '>')) + content.push({type: 'text', value: '\n<' + human.email + '>'}) } if (human.github === team.lead) { content.push( - u('text', '\n('), - u('strong', [u('text', 'lead')]), - u('text', ')') + {type: 'text', value: '\n('}, + {type: 'strong', children: [{type: 'text', value: 'lead'}]}, + {type: 'text', value: ')'} ) } - return u('listItem', [u('paragraph', content)]) + /** @type {ListItem} */ + const result = { + type: 'listItem', + children: [{type: 'paragraph', children: content}] + } + + return result }) - ) + } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ebe8889 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["**/*.js"], + "exclude": ["coverage/", "node_modules/"], + "compilerOptions": { + "checkJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "lib": ["es2020"], + "module": "node16", + "newLine": "lf", + "skipLibCheck": true, + "strict": true, + "target": "es2020" + } +}