From 3cce0b22a7268f4269628b101b749b69637c7ba9 Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 27 Oct 2022 12:18:07 +0200 Subject: [PATCH 1/2] Add types --- .gitignore | 2 ++ index.js | 29 ++++++++++++++++++++++++----- package.json | 21 ++++++++++++++------- script/generate-fixtures.js | 10 ++++++++-- script/generate-regex.js | 3 ++- test/index.js | 2 ++ tsconfig.json | 15 +++++++++++++++ 7 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 82f8ca5..c1f1961 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ build/Release # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules package-lock.json + +*.d.ts diff --git a/index.js b/index.js index eb6acf5..28f0c5d 100644 --- a/index.js +++ b/index.js @@ -10,11 +10,16 @@ export default class BananaSlug { * Create a new slug class. */ constructor () { - this.reset() + /** @type {Record} */ + this.occurrences = Object.create(null) } /** * Generate a unique slug. + * + * Track previously generated slugs: repeated calls with the same value + * will result in different slugs. + * Use the `slug` function to get same slugs. * * @param {string} value * String of text to slugify @@ -48,8 +53,22 @@ export default class BananaSlug { } } -export function slug (string, maintainCase) { - if (typeof string !== 'string') return '' - if (!maintainCase) string = string.toLowerCase() - return string.replace(regex, '').replace(/ /g, '-') +/** + * Generate a slug. + * + * Does not track previously generated slugs: repeated calls with the same value + * will result in the exact same slug. + * Use the `GithubSlugger` class to get unique slugs. + * + * @param {string} value + * String of text to slugify + * @param {boolean} [maintainCase=false] + * Keep the current case, otherwise make all lowercase + * @return {string} + * A unique slug string + */ +export function slug (value, maintainCase) { + if (typeof value !== 'string') return '' + if (!maintainCase) value = value.toLowerCase() + return value.replace(regex, '').replace(/ /g, '-') } diff --git a/package.json b/package.json index 8109fc4..ad0826b 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,17 @@ "url": "https://github.com/Flet/github-slugger/issues" }, "type": "module", + "main": "index.js", + "types": "index.d.ts", "files": [ + "index.d.ts", "index.js", + "regex.d.ts", "regex.js" ], "devDependencies": { "@octokit/rest": "^19.0.0", + "@types/tape": "^4.0.0", "@unicode/unicode-13.0.0": "^1.0.0", "c8": "^7.0.0", "hast-util-select": "^5.0.0", @@ -25,9 +30,12 @@ "node-fetch": "^3.0.0", "regenerate": "^1.0.0", "rehype-parse": "^8.0.0", + "rimraf": "^3.0.0", "standard": "*", "tap-spec": "^5.0.0", "tape": "^5.0.0", + "type-coverage": "^2.0.0", + "typescript": "^4.0.0", "unified": "^10.0.0" }, "homepage": "https://github.com/Flet/github-slugger", @@ -42,21 +50,20 @@ "url" ], "license": "ISC", - "main": "index.js", "repository": { "type": "git", "url": "https://github.com/Flet/github-slugger.git" }, "scripts": { + "prepack": "npm run build && npm run format", + "build": "rimraf \"{script,test}/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage", "format": "standard --fix", "test-api": "tape test | tap-spec", "test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", - "test": "npm run format && npm run test-coverage" + "test": "npm run build && npm run format && npm run test-coverage" }, - "nyc": { - "check-coverage": true, - "lines": 100, - "functions": 100, - "branches": 100 + "typeCoverage": { + "atLeast": 100, + "detail": true } } diff --git a/script/generate-fixtures.js b/script/generate-fixtures.js index 0f9a88f..7f5b672 100644 --- a/script/generate-fixtures.js +++ b/script/generate-fixtures.js @@ -70,6 +70,7 @@ main() async function main () { const files = await fs.readdir(categoryBase) + /** @type {Array<{name: string, input: string, markdownOverwrite?: string, expected?: string}>} */ const tests = [...otherTests] let index = -1 @@ -86,7 +87,9 @@ async function main () { if (name === 'Other') continue const fp = `./${name}/code-points.js` - const { default: codePoints } = await import(new URL(fp, categoryBase)) + + /** @type {{default: Array}} */ + const { default: codePoints } = await import(new URL(fp, categoryBase).href) const subs = [] let n = -1 @@ -138,7 +141,10 @@ async function main () { const anchors = selectAll('h1 .anchor', markdownBody) anchors.forEach((node, i) => { - tests[i].expected = node.properties.href.slice(1) + const href = node.properties.href; + if (typeof href === 'string') { + tests[i].expected = href.slice(1) + } }) await fs.writeFile(new URL('../test/fixtures.json', import.meta.url), JSON.stringify(tests, null, 2) + '\n') diff --git a/script/generate-regex.js b/script/generate-regex.js index eb7ce10..f6614a0 100644 --- a/script/generate-regex.js +++ b/script/generate-regex.js @@ -40,7 +40,8 @@ async function main () { while (++index < ranges.length) { const name = ranges[index] const fp = `./${name}/code-points.js` - const { default: codePoints } = await import(new URL(fp, categoryBase)) + /** @type {{default: Array}} */ + const { default: codePoints } = await import(new URL(fp, categoryBase).href) generator.add(codePoints) } diff --git a/test/index.js b/test/index.js index 29c17ca..8f83473 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,7 @@ import fs from 'node:fs' import test from 'tape' import GithubSlugger, { slug } from '../index.js' +/** @type {Array<{name: string, input: string, markdownOverwrite?: string, expected: string}>} */ const fixtures = JSON.parse( String(fs.readFileSync( new URL('fixtures.json', import.meta.url) @@ -11,6 +12,7 @@ const fixtures = JSON.parse( test('simple stuff', function (t) { const slugger = new GithubSlugger() + // @ts-expect-error: not allowed by types but handled gracefully in the code. t.equals(slugger.slug(1), '', 'should return empty string for non-strings') // Note: GH doesn’t support `maintaincase`, so the actual values are commented below. diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d8750aa --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["script/**/*.js", "test/**/*.js", "*.js"], + "compilerOptions": { + "target": "es2022", + "lib": ["es2022"], + "module": "esnext", + "moduleResolution": "node", + "allowJs": true, + "checkJs": true, + "declaration": true, + "emitDeclarationOnly": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true + } +} From 8cafeaad25f8a3edea785e7171f4a2b2408ebb8b Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 27 Oct 2022 12:25:29 +0200 Subject: [PATCH 2/2] Add strict types --- index.js | 5 ++++- package.json | 4 +++- script/generate-fixtures.js | 9 +++++++-- script/generate-regex.js | 1 + tsconfig.json | 3 ++- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 28f0c5d..984f27f 100644 --- a/index.js +++ b/index.js @@ -11,7 +11,10 @@ export default class BananaSlug { */ constructor () { /** @type {Record} */ - this.occurrences = Object.create(null) + // eslint-disable-next-line no-unused-expressions + this.occurrences + + this.reset() } /** diff --git a/package.json b/package.json index ad0826b..2db4993 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ ], "devDependencies": { "@octokit/rest": "^19.0.0", + "@types/regenerate": "^1.0.0", "@types/tape": "^4.0.0", "@unicode/unicode-13.0.0": "^1.0.0", "c8": "^7.0.0", @@ -64,6 +65,7 @@ }, "typeCoverage": { "atLeast": 100, - "detail": true + "detail": true, + "strict": true } } diff --git a/script/generate-fixtures.js b/script/generate-fixtures.js index 7f5b672..047c325 100644 --- a/script/generate-fixtures.js +++ b/script/generate-fixtures.js @@ -90,6 +90,7 @@ async function main () { /** @type {{default: Array}} */ const { default: codePoints } = await import(new URL(fp, categoryBase).href) + /** @type {Array} */ const subs = [] let n = -1 @@ -115,7 +116,11 @@ async function main () { } }) - const file = gistResult.data.files[filename] + const file = (gistResult.data.files || {})[filename] + + if (!file || !gistResult.data.html_url || !gistResult.data.id) { + throw new Error('Something weird happened contacting GitHub') + } if (!file.language) { throw new Error('The generated markdown was seen as binary data instead of text by GitHub. This is likely because there are weird characters (such as control characters or lone surrogates) in it') @@ -141,7 +146,7 @@ async function main () { const anchors = selectAll('h1 .anchor', markdownBody) anchors.forEach((node, i) => { - const href = node.properties.href; + const href = (node.properties || {}).href if (typeof href === 'string') { tests[i].expected = href.slice(1) } diff --git a/script/generate-regex.js b/script/generate-regex.js index f6614a0..edd0333 100644 --- a/script/generate-regex.js +++ b/script/generate-regex.js @@ -1,5 +1,6 @@ import { promises as fs } from 'node:fs' import regenerate from 'regenerate' +// @ts-expect-error: untyped import alphabetics from '@unicode/unicode-13.0.0/Binary_Property/Alphabetic/code-points.js' const categoryBase = new URL('../node_modules/@unicode/unicode-13.0.0/General_Category/', import.meta.url) diff --git a/tsconfig.json b/tsconfig.json index d8750aa..a352348 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "declaration": true, "emitDeclarationOnly": true, "allowSyntheticDefaultImports": true, - "skipLibCheck": true + "skipLibCheck": true, + "strict": true } }