From b2127c7eb3f917b165ee4cc6cf0c793eb1dc95bb Mon Sep 17 00:00:00 2001 From: Axel Navarro Date: Tue, 25 May 2021 00:50:30 -0300 Subject: [PATCH 1/2] add jsdocs to tldr and cache --- lib/cache.js | 17 +++++++++++ lib/index.js | 50 +++++++++++++++++++++++++------- lib/remote.js | 10 +++++-- lib/tldr.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 141 insertions(+), 16 deletions(-) diff --git a/lib/cache.js b/lib/cache.js index 8b227f5c..da416b46 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -14,10 +14,19 @@ class Cache { this.cacheFolder = path.join(config.cache, 'cache'); } + /** + * Fetch stats from the cache folder. + * @returns {Promise} A promise with the stats of the cache folder. + */ lastUpdated() { return fs.stat(this.cacheFolder); } + /** + * Fetch a page from cache using preferred language and preferred platform. + * @param page {} A + * @returns {Promise} + */ getPage(page) { let preferredPlatform = platforms.getPreferredPlatformFolder(this.config); const preferredLanguage = process.env.LANG || 'en'; @@ -34,10 +43,18 @@ class Cache { }); } + /** + * Clean the cache folder. + * @returns {Promise} A promise when the remove is completed. + */ clear() { return fs.remove(this.cacheFolder); } + /** + * Update the cache folder and returns the English entries. + * @returns {Promise} A promise with the index. + */ update() { // Temporary folder path: /tmp/tldr/{randomName} const tempFolder = path.join(os.tmpdir(), 'tldr', utils.uniqueId()); diff --git a/lib/index.js b/lib/index.js index 74c9faca..1977861d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -91,17 +91,24 @@ function hasLang(targets, preferredLanguage) { }); } -// hasPage is always called after the index is created, -// hence just return the variable in memory. -// There is no need to re-read the index file again. +/** + * Check if a page is in the index. + * @returns {boolean} A boolean that indicates if the page is present. + */ function hasPage(page) { + // hasPage is always called after the index is created, + // hence just return the variable in memory. + // There is no need to re-read the index file again. if (!shortIndex) { return false; } return page in shortIndex; } -// Return all commands available in the local cache. +/** + * Return all commands available in the local cache. + * @returns {Promise} A promise with the commands from cache. + */ function commands() { return getShortIndex().then((idx) => { return Object.keys(idx).sort(); @@ -110,6 +117,13 @@ function commands() { // Return all commands for a given platform. // P.S. - The platform 'common' is always included. +/** + * Return all commands for a given platform. + * + * The 'common' platform is always included. + * @param platform {string} The platform. + * @returns {Promise} A promise with the commands for a given platform. + */ function commandsFor(platform) { return getShortIndex() .then((idx) => { @@ -124,7 +138,11 @@ function commandsFor(platform) { }); } -// Delete the index file. +/** + * Delete the index file. + * + * @returns {Promise} A promise when the remove is completed. + */ function clearPagesIndex() { return fs.unlink(shortIndexFile) .then(() => { @@ -139,7 +157,9 @@ function clearPagesIndex() { }); } -// Set the shortIndex variable to null. +/** + * Set the shortIndex variable to null. + */ function clearRuntimeIndex() { shortIndex = null; } @@ -150,8 +170,12 @@ function rebuildPagesIndex() { }); } -// If the variable is not set, read the file and set it. -// Else, just return the variable. +/** + * Return the index. + * + * If the index is not loaded, read the file and load it. + * @returns {Promise} The index entries. + */ function getShortIndex() { if (shortIndex) { return Promise.resolve(shortIndex); @@ -159,9 +183,13 @@ function getShortIndex() { return readShortPagesIndex(); } -// Read the index file, and load it into memory. -// If the file does not exist, create the data structure, write the file, -// and load it into memory. +/** + * Read the index file, and load it into memory. + * + * If the file does not exist, create the data structure, write the file, + * and load it into memory. + * @returns {Promise} The index entries. + */ function readShortPagesIndex() { return fs.readJson(shortIndexFile) .then((idx) => { diff --git a/lib/remote.js b/lib/remote.js index f1da5b3a..43a908ca 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -6,7 +6,11 @@ const unzip = require('adm-zip'); const config = require('./config'); const axios = require('axios'); -// Downloads the zip file from github and extracts it to folder +/** + * Download the zip file from GitHub and extract it to folder. + * @param path {string} Path to destination folder. + * @returns {Promise} A promise when the operation is completed. + */ exports.download = (loc, lang) => { // If the lang is english then keep the url simple, otherwise add language. const suffix = (lang === 'en' ? '' : '.' + lang); @@ -26,7 +30,7 @@ exports.download = (loc, lang) => { const writer = fs.createWriteStream(fileName); response.data.pipe(writer); - + writer.on('finish', () => { writer.end(); const zip = new unzip(fileName); @@ -41,4 +45,4 @@ exports.download = (loc, lang) => { }).catch((err) => { return Promise.reject(err); }); -}; \ No newline at end of file +}; diff --git a/lib/tldr.js b/lib/tldr.js index 5b06ed36..dd8fda20 100644 --- a/lib/tldr.js +++ b/lib/tldr.js @@ -19,6 +19,11 @@ class Tldr { this.cache = new Cache(this.config); } + /** + * Print pages for a given platform.. + * @param singleColumn {boolean} A boolean to print one command per line. + * @returns {Promise} A promise when the operation is completed. + */ list(singleColumn) { let platform = platforms.getPreferredPlatformFolder(this.config); return index.commandsFor(platform) @@ -27,6 +32,11 @@ class Tldr { }); } + /** + * Print all pages in the cache. + * @param singleColumn {boolean} A boolean to print one command per line. + * @returns {Promise} A promise when the operation is completed. + */ listAll(singleColumn) { return index.commands() .then((commands) => { @@ -34,10 +44,21 @@ class Tldr { }); } + /** + * Print a command page. + * @param commands {string[]} A given command to be printed. + * @param options {object} The options for the render. + * @returns {Promise} A promise when the operation is completed. + */ get(commands, options) { return this.printBestPage(commands.join('-'), options); } + /** + * Print a random page for the current platform. + * @param options {object} The options for the render. + * @returns {Promise} A promise when the operation is completed. + */ random(options) { let platform = platforms.getPreferredPlatformFolder(this.config); return index.commandsFor(platform) @@ -54,6 +75,10 @@ class Tldr { }); } + /** + * Print a random page. + * @returns {Promise} A promise when the opration is completed. + */ randomExample() { let platform = platforms.getPreferredPlatformFolder(this.config); return index.commandsFor(platform) @@ -70,6 +95,11 @@ class Tldr { }); } + /** + * Print a markdown file. + * @param file {string} The path to the file. + * @returns {Promise} A promise when the operation is completed. + */ render(file) { if (typeof file !== 'string') { throw new MissingRenderPathError(); @@ -83,24 +113,41 @@ class Tldr { }); } + /** + * Clear the cache folder. + * @returns {Promise} A promise when the cache is deleted. + */ clearCache() { return this.cache.clear().then(() => { console.log('Done'); }); } + /** + * Update the cache. + * @returns {Promise} A promise with the index. + */ updateCache() { return spinningPromise('Updating...', () => { return this.cache.update(); }); } + /** + * Update the index. + * @returns {Promise} A promise with the index. + */ updateIndex() { return spinningPromise('Creating index...', () => { return search.createIndex(); }); } + /** + * Search some keywords in the index and print the results. + * @param keywords {string[]} The given keywords. + * @returns {Promise} A promise when the operation is completed. + */ search(keywords) { return search.getResults(keywords.join(' ')) .then((results) => { @@ -109,6 +156,12 @@ class Tldr { }); } + /** + * Print all pages. + * @param pages {string[]} A list of pages to be printed. + * @param singleColumn {boolean} A boolean to print one command per line. + * @returns {Promise} A promise when the operation is completed. + */ printPages(pages, singleColumn) { if (pages.length === 0) { throw new EmptyCacheError(); @@ -121,7 +174,15 @@ class Tldr { }); } - printBestPage(command, options={}) { + /** + * Print a page from the cache. + * + * If the page is not present, the cache is updated. + * @param command {string} The given command to be printed. + * @param options {object} The options for the render. + * @returns {Promise} A promise when the operation is completed. + */ + printBestPage(command, options= {}) { // Trying to get the page from cache first return this.cache.getPage(command) .then((content) => { @@ -157,6 +218,10 @@ class Tldr { }); } + /** + * Print a warning message if the cache is 30 days old. + * @returns {Promise} A promise when the operation is completed. + */ checkStale() { return this.cache.lastUpdated() .then((stats) => { @@ -166,7 +231,12 @@ class Tldr { }); } - renderContent(content, options={}) { + /** + * Print the page content. + * @param content {string} The content of a page. + * @param options {object<{markdown: boolean, randomExample: boolean}>} The options for the render. + */ + renderContent(content, options= {}) { if (options.markdown) { return console.log(content); } @@ -181,6 +251,12 @@ class Tldr { } } +/** + * Display a spinner while a task is running. + * @param text {string} The text of the spinner. + * @param factory {Function} A task to be run. + * @returns {Promise} A promise with the result of the task. + */ function spinningPromise(text, factory) { const spinner = ora(); spinner.start(text); From c93060f571aef4192d177f4f50d25706e4848227 Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Thu, 30 Oct 2025 23:38:34 +0000 Subject: [PATCH 2/2] chore: add jsdoc types and typechecking Co-authored-by: Vitor Henrique --- .github/workflows/test.yml | 1 + lib/cache.js | 15 ++-- lib/completion.js | 4 +- lib/config.js | 4 +- lib/index.js | 29 +++++--- lib/parser.js | 1 + lib/remote.js | 5 +- lib/render.js | 10 ++- lib/search.js | 69 ++++++++++++----- lib/tldr.js | 62 +++++++++------- lib/utils.js | 13 +--- package-lock.json | 147 +++++++++++++++++++++++++------------ package.json | 7 +- test/cache.spec.js | 28 +++---- test/config.spec.js | 18 ++--- test/index.spec.js | 4 +- test/parser.spec.js | 2 +- test/platform.spec.js | 21 +++--- test/remote.spec.js | 4 +- test/render.spec.js | 25 ++++--- test/search.spec.js | 10 +-- test/tldr.spec.js | 3 +- tsconfig.json | 19 +++++ 23 files changed, 315 insertions(+), 186 deletions(-) create mode 100644 tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 489a2e7e..c682c4d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,4 +37,5 @@ jobs: node-version: ${{ matrix.node-version }} - run: npm ci + - run: npm run typecheck - run: npm run test:all diff --git a/lib/cache.js b/lib/cache.js index da416b46..119fc4ec 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -15,7 +15,9 @@ class Cache { } /** - * Fetch stats from the cache folder. + * Fetch stats from the cache folder for getting its last modified time + * (mtime). + * * @returns {Promise} A promise with the stats of the cache folder. */ lastUpdated() { @@ -24,8 +26,8 @@ class Cache { /** * Fetch a page from cache using preferred language and preferred platform. - * @param page {} A - * @returns {Promise} + * @param {string} page + * @returns {Promise} */ getPage(page) { let preferredPlatform = platforms.getPreferredPlatformFolder(this.config); @@ -52,8 +54,10 @@ class Cache { } /** - * Update the cache folder and returns the English entries. - * @returns {Promise} A promise with the index. + * Update the cache folder using a temporary directory, update the index and + * return it. + * + * @returns {Promise} The index. */ update() { // Temporary folder path: /tmp/tldr/{randomName} @@ -82,7 +86,6 @@ class Cache { index.rebuildPagesIndex(), ]); }) - .then(([_, shortIndex]) => { return shortIndex; }); diff --git a/lib/completion.js b/lib/completion.js index d8c9eaf4..88dec93c 100644 --- a/lib/completion.js +++ b/lib/completion.js @@ -22,7 +22,7 @@ class Completion { appendScript(script) { const rcFilePath = this.getFilePath(); - return new Promise((resolve, reject) => { + return new Promise((/** @type {(v?: never) => void} */ resolve, reject) => { fs.appendFile(rcFilePath, `\n${script}\n`, (err) => { if (err) { reject((new CompletionScriptError(`Error appending to ${rcFilePath}: ${err.message}`))); @@ -79,4 +79,4 @@ fi } } -module.exports = Completion; \ No newline at end of file +module.exports = Completion; diff --git a/lib/config.js b/lib/config.js index 749d8c0b..771566c8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -11,12 +11,12 @@ exports.get = () => { const DEFAULT = path.join(__dirname, '..', 'config.json'); const CUSTOM = path.join(osHomedir(), '.tldrrc'); - let defaultConfig = JSON.parse(fs.readFileSync(DEFAULT)); + let defaultConfig = JSON.parse(fs.readFileSync(DEFAULT, 'utf-8')); defaultConfig.cache = path.join(osHomedir(), '.tldr'); let customConfig = {}; try { - customConfig = JSON.parse(fs.readFileSync(CUSTOM)); + customConfig = JSON.parse(fs.readFileSync(CUSTOM, 'utf-8')); } catch (ex) { if (ex instanceof SyntaxError) { throw new Error('The content of .tldrrc is not a valid JSON object:\n' + ex); diff --git a/lib/index.js b/lib/index.js index 1977861d..999727ed 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,6 +10,12 @@ let shortIndex = null; const pagesPath = path.join(config.get().cache, 'cache'); const shortIndexFile = path.join(pagesPath, 'shortIndex.json'); +/** + * @param {string} page + * @param {string|undefined} preferredPlatform + * @param {string} preferredLanguage + * @returns {Promise} + */ function findPage(page, preferredPlatform, preferredLanguage) { // Load the index return getShortIndex() @@ -33,7 +39,7 @@ function findPage(page, preferredPlatform, preferredLanguage) { ll = preferredLanguage.substring(0, preferredLanguage.indexOf('_')); } if (!hasLang(targets, preferredLanguage)) { - preferredLanguage = ll; + preferredLanguage = /** @type {string} */ (ll); } // Page resolution logic: @@ -93,7 +99,8 @@ function hasLang(targets, preferredLanguage) { /** * Check if a page is in the index. - * @returns {boolean} A boolean that indicates if the page is present. + * + * @returns {boolean} The presence of the page in the index. */ function hasPage(page) { // hasPage is always called after the index is created, @@ -106,8 +113,8 @@ function hasPage(page) { } /** - * Return all commands available in the local cache. - * @returns {Promise} A promise with the commands from cache. + * Return all commands available in the local index. + * @returns {Promise} A promise with the commands from the index. */ function commands() { return getShortIndex().then((idx) => { @@ -115,14 +122,12 @@ function commands() { }); } -// Return all commands for a given platform. -// P.S. - The platform 'common' is always included. /** - * Return all commands for a given platform. + * Return all commands for a given platform. The 'common' platform is always + * included. * - * The 'common' platform is always included. - * @param platform {string} The platform. - * @returns {Promise} A promise with the commands for a given platform. + * @param {string} platform The desired platform. + * @returns {Promise} The commands for a given platform. */ function commandsFor(platform) { return getShortIndex() @@ -171,9 +176,9 @@ function rebuildPagesIndex() { } /** - * Return the index. + * Return the index, that contains all available commands with their target os + * and platform. If the index is not loaded, read the file and load it. * - * If the index is not loaded, read the file and load it. * @returns {Promise} The index entries. */ function getShortIndex() { diff --git a/lib/parser.js b/lib/parser.js index 6a1088ca..60f41086 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -17,6 +17,7 @@ function unhtml(text){ exports.parse = (markdown) => { // Creating the page structure + /** @type {Required & { examples: any[] }} */ let page = { name: '', description: '', diff --git a/lib/remote.js b/lib/remote.js index 43a908ca..a1384cb7 100644 --- a/lib/remote.js +++ b/lib/remote.js @@ -8,7 +8,8 @@ const axios = require('axios'); /** * Download the zip file from GitHub and extract it to folder. - * @param path {string} Path to destination folder. + * @param {string} loc Path to a directory on disk. + * @param {string} lang Language/locale code. * @returns {Promise} A promise when the operation is completed. */ exports.download = (loc, lang) => { @@ -25,7 +26,7 @@ exports.download = (loc, lang) => { headers: { 'User-Agent' : 'tldr-node-client' }, timeout: REQUEST_TIMEOUT, }).then((response) => { - return new Promise((resolve, reject) => { + return new Promise((/** @type {(v?: never) => void} */ resolve, reject) => { let fileName = path.join(loc, 'download_' + lang + '.zip'); const writer = fs.createWriteStream(fileName); diff --git a/lib/render.js b/lib/render.js index aa0dd852..2a20c1ac 100644 --- a/lib/render.js +++ b/lib/render.js @@ -3,8 +3,14 @@ const Theme = require('./theme'); const he = require('he'); // Import the 'he' library -// The page structure is passed to this function, and then the theme is applied -// to different parts of the page and rendered to the console +/** + * Page structure is passed to this function, and then the theme is applied to + * different parts of the page and rendered to the console. + * + * @param {import('./tldr').TldrPage} page + * @param {any} config + * @returns {string|void} + */ exports.toANSI = (page, config) => { // Creating the theme object let themeOptions = config.themes[config.theme]; diff --git a/lib/search.js b/lib/search.js index ba399a76..0cc75248 100644 --- a/lib/search.js +++ b/lib/search.js @@ -9,26 +9,53 @@ const utils = require('./utils'); const index = require('./index'); const platforms = require('./platforms'); +/** + * @typedef {object} Corpus + * @property {Record>} fileWords + * @property {Record} fileLengths + * @property {Record} invertedIndex + * @property {any} allTokens + * @property {Record>} tfidf + * + * @typedef {object} Query + * @property {?string} raw + * @property {?string[]} tokens + * @property {Record} frequency + * @property {Record} score + * @property {QueryRank[]} ranks + * + * @typedef {object} QueryRank + * @property {string} file + * @property {number} score + */ + const CACHE_FOLDER = path.join(config.get().cache, 'cache'); const filepath = CACHE_FOLDER + '/search-corpus.json'; -let corpus = {}; - -corpus.fileWords = {}; -corpus.fileLengths = {}; -corpus.invertedIndex = {}; -corpus.allTokens = new Set(); -corpus.tfidf = {}; -let query = {}; +/** @type {Corpus} */ +let corpus = { + fileWords: {}, + fileLengths: {}, + invertedIndex: {}, + allTokens: new Set(), + tfidf: {}, +}; -query.raw = null; -query.tokens = null; -query.frequency = {}; -query.score = {}; -query.ranks = []; +/** @type {Query} */ +let query = { + raw: null, + tokens: null, + frequency: {}, + score: {}, + ranks: [], +}; +/** + * @param {string} data + * @returns {string[]} + */ let getTokens = (data) => { let tokenizer = new natural.WordTokenizer(); let tokens = tokenizer.tokenize(data); @@ -139,6 +166,9 @@ let readCorpus = () => { }); }; +/** + * @param {string} rawquery + */ let processQuery = (rawquery) => { query.raw = rawquery; query.tokens = getTokens(rawquery); @@ -156,10 +186,9 @@ let processQuery = (rawquery) => { query.score = {}; query.tokens.forEach((word) => { if (corpus.invertedIndex[word]) { - let logbase = 10; let df = corpus.invertedIndex[word].length; - let idf = Math.log(numberOfFiles / df, logbase); - let wordWeight = idf * (1 + Math.log(query.frequency[word], logbase)); + let idf = Math.log10(numberOfFiles / df); + let wordWeight = idf * (1 + Math.log10(query.frequency[word])); corpus.invertedIndex[word].forEach((file) => { let fileWeight = corpus.tfidf[file][word]; if (query.score[file]) { @@ -209,7 +238,7 @@ exports.printResults = (results, config) => { outputs.add(output); }); - console.log('Searching for:', query.raw.trim()); + console.log('Searching for:', /** @type {string} */ (query.raw).trim()); console.log(); Array.from(outputs).forEach((elem) => { console.log(elem); @@ -220,7 +249,7 @@ exports.printResults = (results, config) => { }; exports.createIndex = () => { - return utils.glob(CACHE_FOLDER + '/pages/**/*.md', {}) + return utils.glob(CACHE_FOLDER + '/pages/**/*.md') .then((files) => { let promises = []; files.forEach((file) => { @@ -246,6 +275,10 @@ exports.createIndex = () => { }); }; +/** + * @param {string} rawquery + * @returns + */ exports.getResults = (rawquery) => { query.ranks = []; return readCorpus() diff --git a/lib/tldr.js b/lib/tldr.js index dd8fda20..8ad8297c 100644 --- a/lib/tldr.js +++ b/lib/tldr.js @@ -12,6 +12,17 @@ const parser = require('./parser'); const render = require('./render'); const index = require('./index'); +/** + * @typedef {object} TldrPage + * @property {string} name + * @property {string} description + * @property {TldrExample[]} examples + * @property {string[]=} seeAlso + * + * @typedef {object} TldrExample + * @property {string} code + * @property {string} description + */ class Tldr { constructor(config) { @@ -20,8 +31,8 @@ class Tldr { } /** - * Print pages for a given platform.. - * @param singleColumn {boolean} A boolean to print one command per line. + * Print pages for a given platform. + * @param {boolean} singleColumn Print one command per line. * @returns {Promise} A promise when the operation is completed. */ list(singleColumn) { @@ -33,8 +44,8 @@ class Tldr { } /** - * Print all pages in the cache. - * @param singleColumn {boolean} A boolean to print one command per line. + * Print the name of all pages in the index. + * @param {boolean} singleColumn Print one command per line. * @returns {Promise} A promise when the operation is completed. */ listAll(singleColumn) { @@ -46,8 +57,8 @@ class Tldr { /** * Print a command page. - * @param commands {string[]} A given command to be printed. - * @param options {object} The options for the render. + * @param {string[]} commands Given commands to be printed. + * @param {object} options The options for the render. * @returns {Promise} A promise when the operation is completed. */ get(commands, options) { @@ -56,7 +67,7 @@ class Tldr { /** * Print a random page for the current platform. - * @param options {object} The options for the render. + * @param {object} options The options for the render. * @returns {Promise} A promise when the operation is completed. */ random(options) { @@ -76,8 +87,8 @@ class Tldr { } /** - * Print a random page. - * @returns {Promise} A promise when the opration is completed. + * Print a random page example. + * @returns {Promise} A promise when the operation is completed. */ randomExample() { let platform = platforms.getPreferredPlatformFolder(this.config); @@ -96,8 +107,9 @@ class Tldr { } /** - * Print a markdown file. - * @param file {string} The path to the file. + * Render a markdown file. + * + * @param {string} file The path to the file. * @returns {Promise} A promise when the operation is completed. */ render(file) { @@ -125,7 +137,7 @@ class Tldr { /** * Update the cache. - * @returns {Promise} A promise with the index. + * @returns {Promise} The index. */ updateCache() { return spinningPromise('Updating...', () => { @@ -135,7 +147,7 @@ class Tldr { /** * Update the index. - * @returns {Promise} A promise with the index. + * @returns {Promise} The index. */ updateIndex() { return spinningPromise('Creating index...', () => { @@ -145,7 +157,7 @@ class Tldr { /** * Search some keywords in the index and print the results. - * @param keywords {string[]} The given keywords. + * @param {string[]} keywords The given keywords. * @returns {Promise} A promise when the operation is completed. */ search(keywords) { @@ -157,9 +169,9 @@ class Tldr { } /** - * Print all pages. - * @param pages {string[]} A list of pages to be printed. - * @param singleColumn {boolean} A boolean to print one command per line. + * Print the name of all pages. + * @param {string[]} pages A list of pages to be printed. + * @param {boolean} singleColumn Print one command per line. * @returns {Promise} A promise when the operation is completed. */ printPages(pages, singleColumn) { @@ -178,11 +190,11 @@ class Tldr { * Print a page from the cache. * * If the page is not present, the cache is updated. - * @param command {string} The given command to be printed. - * @param options {object} The options for the render. + * @param {string} command The given command to be printed. + * @param {object} options The options for the render. * @returns {Promise} A promise when the operation is completed. */ - printBestPage(command, options= {}) { + printBestPage(command, options = {}) { // Trying to get the page from cache first return this.cache.getPage(command) .then((content) => { @@ -233,8 +245,8 @@ class Tldr { /** * Print the page content. - * @param content {string} The content of a page. - * @param options {object<{markdown: boolean, randomExample: boolean}>} The options for the render. + * @param {string} content The content of a page. + * @param {{markdown?: boolean, randomExample?: boolean}} options The options for the render. */ renderContent(content, options= {}) { if (options.markdown) { @@ -253,9 +265,9 @@ class Tldr { /** * Display a spinner while a task is running. - * @param text {string} The text of the spinner. - * @param factory {Function} A task to be run. - * @returns {Promise} A promise with the result of the task. + * @param {string} text The text of the spinner. + * @param {Function} factory A task to be run. + * @returns {Promise} A promise with the result of the task. */ function spinningPromise(text, factory) { const spinner = ora(); diff --git a/lib/utils.js b/lib/utils.js index f27b49a0..b4d44d7f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -79,19 +79,10 @@ module.exports = { }); }, - glob(string,options) { - return new Promise((resolve, reject) => { - glob(string, options, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); + glob(string, options) { + return glob(string, options); }, - uniqueId(length = 32) { const size = Math.ceil(length / 2); return crypto.randomBytes(size).toString('hex').slice(0, length); diff --git a/package-lock.json b/package-lock.json index e7666ed4..26080e1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,13 +26,17 @@ "tldr": "bin/tldr" }, "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "^24.9.2", + "@types/sinon": "^17.0.4", "eslint": "^9.14.0", "eslint-config-eslint": "^11.0.0", "husky": "^9.1.6", "mocha": "^11.0.1", "nyc": "^17.1.0", "should": "^13.2.3", - "sinon": "^19.0.2" + "sinon": "^19.0.2", + "typescript": "^5.9.3" }, "engines": { "node": ">=22" @@ -87,6 +91,7 @@ "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -837,6 +842,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -951,6 +957,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -958,6 +981,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/sinon": { + "version": "17.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz", + "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-15.0.1.tgz", + "integrity": "sha512-Ko2tjWJq8oozHzHV+reuvS5KYIRAokHnGbDwGh/J64LntgpbuylF74ipEL24HCyRjf9FOlBiBHWBR1RlVKsI1w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -974,14 +1014,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", "debug": "^4.3.4" }, "engines": { @@ -992,18 +1032,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1014,9 +1054,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", "dev": true, "license": "MIT", "engines": { @@ -1027,13 +1067,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, "license": "MIT", "engines": { @@ -1045,16 +1085,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1070,13 +1110,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1100,16 +1140,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1120,18 +1160,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.46.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1147,6 +1187,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1423,6 +1464,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -2114,6 +2156,7 @@ "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -2453,9 +2496,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4806,6 +4849,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.9.0", "pg-pool": "^3.10.0", @@ -6239,9 +6283,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -6259,6 +6303,13 @@ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "license": "MIT" }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/union": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", diff --git a/package.json b/package.json index 22280653..efa538fa 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "test:functional": "bash test/functional-test.sh", "test:coverage": "nyc mocha", "test:all": "npm run lint && npm test && npm run test:functional", + "typecheck": "tsc --noEmit", "prepare": "husky" }, "dependencies": { @@ -71,13 +72,17 @@ "ora": "^5.1.0" }, "devDependencies": { + "@types/mocha": "^10.0.10", + "@types/node": "^24.9.2", + "@types/sinon": "^17.0.4", "eslint": "^9.14.0", "eslint-config-eslint": "^11.0.0", "husky": "^9.1.6", "mocha": "^11.0.1", "nyc": "^17.1.0", "should": "^13.2.3", - "sinon": "^19.0.2" + "sinon": "^19.0.2", + "typescript": "^5.9.3" }, "funding": { "type": "liberapay", diff --git a/test/cache.spec.js b/test/cache.spec.js index 77eff24c..c3322a6f 100644 --- a/test/cache.spec.js +++ b/test/cache.spec.js @@ -54,14 +54,14 @@ describe('Cache', () => { fs.ensureDir.restore(); fs.remove.restore(); fs.copy.restore(); - remote.download.restore(); - index.rebuildPagesIndex.restore(); + /** @type {sinon.SinonSpy} */ (remote.download).restore(); + /** @type {sinon.SinonSpy} */ (index.rebuildPagesIndex).restore(); }); }); describe('getPage()', () => { beforeEach(() => { - sinon.stub(index, 'getShortIndex').returns({ + sinon.stub(index, 'getShortIndex').returns(Promise.resolve({ cp: ['common'], git: ['common'], ln: ['common'], @@ -72,11 +72,11 @@ describe('Cache', () => { svcs: ['sunos'], pkg: ['android', 'freebsd', 'openbsd'], pkgin: ['netbsd'] - }); + })); }); afterEach(() => { - index.getShortIndex.restore(); + /** @type {sinon.SinonSpy} */ (index.getShortIndex).restore(); }); it('should return page contents for ls', () => { @@ -89,12 +89,12 @@ describe('Cache', () => { should.exist(content); content.should.startWith('# ls'); fs.readFile.restore(); - platforms.getPreferredPlatformFolder.restore(); - index.findPage.restore(); + /** @type {sinon.SinonSpy} */ (platforms.getPreferredPlatformFolder).restore(); + /** @type {sinon.SinonSpy} */ (index.findPage).restore(); }); }); - it('should return empty contents for svcs on OSX', () =>{ + it('should return empty contents for svcs on OSX', () => { sinon.stub(fs, 'readFile').resolves('# svcs\n> svcs'); sinon.stub(platforms, 'getPreferredPlatformFolder').returns('osx'); sinon.stub(index, 'findPage').resolves(null); @@ -103,8 +103,8 @@ describe('Cache', () => { .then((content) => { should.not.exist(content); fs.readFile.restore(); - platforms.getPreferredPlatformFolder.restore(); - index.findPage.restore(); + /** @type {sinon.SinonSpy} */ (platforms.getPreferredPlatformFolder).restore(); + /** @type {sinon.SinonSpy} */ (index.findPage).restore(); }); }); @@ -118,8 +118,8 @@ describe('Cache', () => { should.exist(content); content.should.startWith('# svcs'); fs.readFile.restore(); - platforms.getPreferredPlatformFolder.restore(); - index.findPage.restore(); + /** @type {sinon.SinonSpy} */ (platforms.getPreferredPlatformFolder).restore(); + /** @type {sinon.SinonSpy} */ (index.findPage).restore(); }); }); @@ -133,8 +133,8 @@ describe('Cache', () => { should.exist(content); content.should.startWith('# pkg'); fs.readFile.restore(); - platforms.getPreferredPlatformFolder.restore(); - index.findPage.restore(); + /** @type {sinon.SinonSpy} */ (platforms.getPreferredPlatformFolder).restore(); + /** @type {sinon.SinonSpy} */ (index.findPage).restore(); }); }); diff --git a/test/config.spec.js b/test/config.spec.js index 7f4d4307..eae7eaee 100644 --- a/test/config.spec.js +++ b/test/config.spec.js @@ -42,30 +42,30 @@ describe('Config', () => { }); afterEach(() => { - fs.readFileSync.restore(); + /** @type {sinon.SinonSpy} */ (fs.readFileSync).restore(); }); it('should load the default config', () => { - fs.readFileSync.onCall(0).returns(DEFAULT); - fs.readFileSync.onCall(1).throws('Not found'); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(0).returns(DEFAULT); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(1).throws('Not found'); config.get().repository.should.eql('http://tldr-pages.github.io/assets/tldr.zip'); }); it('should override the defaults with content from .tldrrc', () => { - fs.readFileSync.onCall(0).returns(DEFAULT); - fs.readFileSync.onCall(1).returns(CUSTOM); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(0).returns(DEFAULT); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(1).returns(CUSTOM); config.get().repository.should.eql('http://myrepo/assets/tldr.zip'); }); it('should validate the custom config JSON', () => { - fs.readFileSync.onCall(0).returns(DEFAULT); - fs.readFileSync.onCall(1).returns(CUSTOM_INVALID_JSON); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(0).returns(DEFAULT); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(1).returns(CUSTOM_INVALID_JSON); config.get.should.throw(/not a valid JSON object/); }); it('should validate the custom config schema', () => { - fs.readFileSync.onCall(0).returns(DEFAULT); - fs.readFileSync.onCall(1).returns(CUSTOM_INVALID_SCHEMA); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(0).returns(DEFAULT); + /** @type {sinon.SinonStub} */ (fs.readFileSync).onCall(1).returns(CUSTOM_INVALID_SCHEMA); config.get.should.throw(/Invalid theme value/); }); diff --git a/test/index.spec.js b/test/index.spec.js index 5961783c..6b36708c 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -64,7 +64,7 @@ describe('Index building', () => { }); afterEach(() => { - utils.walk.restore(); + /** @type {sinon.SinonSpy} */ (utils.walk).restore(); fs.readJson.restore(); fs.writeJson.restore(); }); @@ -79,7 +79,7 @@ describe('Index', () => { }); afterEach(() => { - utils.walk.restore(); + /** @type {sinon.SinonSpy} */ (utils.walk).restore(); fs.readJson.restore(); fs.writeJson.restore(); }); diff --git a/test/parser.spec.js b/test/parser.spec.js index ebe9d009..847c96fe 100644 --- a/test/parser.spec.js +++ b/test/parser.spec.js @@ -160,7 +160,7 @@ describe('Parser', () => { }); afterEach(() => { - index.hasPage.restore(); + /** @type {sinon.SinonSpy} */ (index.hasPage).restore(); }); it('should parse seeAlso commands when mentioned in description', () => { diff --git a/test/platform.spec.js b/test/platform.spec.js index 0ed77231..317aa514 100644 --- a/test/platform.spec.js +++ b/test/platform.spec.js @@ -10,33 +10,32 @@ describe('Platform', () => { describe('getPreferredPlatform', () => { beforeEach(() => { sinon.stub(os, 'platform'); - this.config = config.get(); }); afterEach(() => { - os.platform.restore(); + /** @type {sinon.SinonStub} */ (os.platform).restore(); }); it('should return the running platform with no configuration', () => { - os.platform.onCall(0).returns('darwin'); - this.config = {}; - platforms.getPreferredPlatform(this.config).should.eql('darwin'); + /** @type {sinon.SinonStub} */ (os.platform).onCall(0).returns('darwin'); + const config = {}; + platforms.getPreferredPlatform(config).should.eql('darwin'); }); it('should overwrite the running platform if configured', () => { - os.platform.onCall(0).returns('darwin'); - this.config = { + /** @type {sinon.SinonStub} */ (os.platform).onCall(0).returns('darwin'); + const config = { platform: 'linux' }; - platforms.getPreferredPlatform(this.config).should.eql('linux'); + platforms.getPreferredPlatform(config).should.eql('linux'); }); it('should return current system platform if configuration is wrong', () => { - os.platform.onCall(0).returns('darwin'); - this.config = { + /** @type {sinon.SinonStub} */ (os.platform).onCall(0).returns('darwin'); + const config = { platform: 'there_is_no_such_platform' }; - platforms.getPreferredPlatform(this.config).should.eql('darwin'); + platforms.getPreferredPlatform(config).should.eql('darwin'); }); }); diff --git a/test/remote.spec.js b/test/remote.spec.js index 21ae058c..25b49b70 100644 --- a/test/remote.spec.js +++ b/test/remote.spec.js @@ -81,8 +81,8 @@ describe('Remote', () => { fs.copy.restore(); fs.remove.restore(); fs.ensureDir.restore(); - utils.localeToLang.restore(); - index.rebuildPagesIndex.restore(); + /** @type {sinon.SinonSpy} */ (utils.localeToLang).restore(); + /** @type {sinon.SinonSpy} */ (index.rebuildPagesIndex).restore(); await fs.remove(tempFolder); }).timeout(TIMEOUT_INTERVAL); diff --git a/test/render.spec.js b/test/render.spec.js index a3bae75f..fca4e151 100644 --- a/test/render.spec.js +++ b/test/render.spec.js @@ -31,8 +31,8 @@ describe('Render', () => { description: 'archive utility', examples: [] }, this.config); - text.should.startWith('\n'); - text.should.endWith('\n'); + should(text).startWith('\n'); + should(text).endWith('\n'); }); it('contains the command name', () => { @@ -41,8 +41,8 @@ describe('Render', () => { description: 'archive utility', examples: [] }, this.config); - text.should.containEql('tar'); - text.should.containEql('archive utility'); + should(text).containEql('tar'); + should(text).containEql('archive utility'); }); it('contains the description name', () => { @@ -51,8 +51,8 @@ describe('Render', () => { description: 'archive utility\nwith support for compression', examples: [] }, this.config); - text.should.containEql('archive utility'); - text.should.containEql('with support for compression'); + should(text).containEql('archive utility'); + should(text).containEql('with support for compression'); }); it('highlights replaceable {{tokens}}', () => { @@ -64,16 +64,16 @@ describe('Render', () => { code: 'hello {{token}} bye' }] }, this.config); - text.should.containEql('hello '); - text.should.containEql('token'); - text.should.containEql(' bye'); + should(text).containEql('hello '); + should(text).containEql('token'); + should(text).containEql(' bye'); }); it('should correctly render see also section', () => { let text = render.toANSI({ name: 'uname', description: 'Description for `uname`.\n' + - 'See also `lsb_release`.', + 'See also `lsb_release`.', examples: [{ description: '1st example. You need `sudo` to run this', code: 'uname {{token}}' @@ -83,7 +83,7 @@ describe('Render', () => { 'sudo' ] }, this.config); - text.should.containEql('See also: lsb_release, sudo'); + should(text).containEql('See also: lsb_release, sudo'); }); it('should return an error for invalid theme', () => { @@ -103,8 +103,9 @@ describe('Render', () => { let text = render.toANSI({ name: 'tar', description: 'archive utility', + examples: [], }, config); should.not.exist(text); - console.error.getCall(0).args[0].should.equal('invalid theme: bad'); + /** @type {sinon.SinonSpy} */ (console.error).getCall(0).args[0].should.equal('invalid theme: bad'); }); }); diff --git a/test/search.spec.js b/test/search.spec.js index ab2e8226..2fbfcb4b 100644 --- a/test/search.spec.js +++ b/test/search.spec.js @@ -119,7 +119,7 @@ let fakes = { }, fs: { writeFile: (writepath, content) => { - return new Promise((resolve, reject) => { + return new Promise((/** @type {(v?: never) => void} */ resolve, reject) => { if (writepath !== filepath) { return reject('Incorrect File Path'); } @@ -160,8 +160,8 @@ describe('Search', () => { stubs.push(sinon.stub(fs, 'readFile').callsFake(fakes.fs.readFile)); stubs.push(sinon.stub(fs, 'writeFile').callsFake(fakes.fs.writeFile)); search.createIndex().then((data) => { - Object.keys(data.tfidf).length.should.equal(20); - Object.keys(data.invertedIndex).length.should.equal(56); + Object.keys(data.tfidf).length.should.equal(20); + Object.keys(data.invertedIndex).length.should.equal(56); data.invertedIndex['roxi'][0].should.equal('/path/to/file-11.md'); testData.corpus = data; done(); @@ -178,7 +178,7 @@ describe('Search', () => { stubs.push(sinon.stub(fs, 'writeFile').callsFake(fakes.fs.writeFile)); stubs.push(sinon.stub(index, 'getShortIndex').callsFake(fakes.index.getShortIndex)); search.getResults('Anthony').then((data) => { - data.length.should.equal(4); + data.length.should.equal(4); data[0].file.should.equal('/path/to/file-09.md'); return Promise.resolve(); }).then(() => { @@ -188,7 +188,7 @@ describe('Search', () => { }); }).then(() => { return search.getResults('Joe and Roxie').then((data) => { - data.length.should.equal(8); + data.length.should.equal(8); data[1].file.should.equal('/path/to/file-16.md'); done(); return Promise.resolve(); diff --git a/test/tldr.spec.js b/test/tldr.spec.js index 78959426..79802aab 100644 --- a/test/tldr.spec.js +++ b/test/tldr.spec.js @@ -1,10 +1,11 @@ 'use strict'; +const should = require('should'); const TLDR = require('../lib/tldr'); describe('TLDR class', () => { it('should construct', () => { const tldr = new TLDR({ cache: 'some-random-string' }); - tldr.should.exist; + should(tldr).instanceOf(TLDR); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f224893b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "noEmit": true, + "module": "preserve", + "target": "es2021", + "lib": [ + "es2021" + ], + "allowJs": true, + "checkJs": true, + "strict": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "noImplicitAny": false + }, + "exclude": [ + "node_modules/" + ] +}