Skip to content

Commit 1cd8e2e

Browse files
SethFalcovitorhclnavarroaxelsebastiaanspeck
authored
chore: add jsdoc types and typechecking (#515)
Co-authored-by: Vitor Henrique <[email protected]> Co-authored-by: Axel Navarro <[email protected]> Co-authored-by: Sebastiaan Speck <[email protected]>
1 parent 45d1227 commit 1cd8e2e

23 files changed

+413
-159
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ jobs:
3737
node-version: ${{ matrix.node-version }}
3838

3939
- run: npm ci
40+
- run: npm run typecheck
4041
- run: npm run test:all

lib/cache.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,21 @@ class Cache {
1414
this.cacheFolder = path.join(config.cache, 'cache');
1515
}
1616

17+
/**
18+
* Fetch stats from the cache folder for getting its last modified time
19+
* (mtime).
20+
*
21+
* @returns {Promise<any>} A promise with the stats of the cache folder.
22+
*/
1723
lastUpdated() {
1824
return fs.stat(this.cacheFolder);
1925
}
2026

27+
/**
28+
* Fetch a page from cache using preferred language and preferred platform.
29+
* @param {string} page
30+
* @returns {Promise<string>}
31+
*/
2132
getPage(page) {
2233
let preferredPlatform = platforms.getPreferredPlatformFolder(this.config);
2334
const preferredLanguage = process.env.LANG || 'en';
@@ -34,10 +45,20 @@ class Cache {
3445
});
3546
}
3647

48+
/**
49+
* Clean the cache folder.
50+
* @returns {Promise<any>} A promise when the remove is completed.
51+
*/
3752
clear() {
3853
return fs.remove(this.cacheFolder);
3954
}
4055

56+
/**
57+
* Update the cache folder using a temporary directory, update the index and
58+
* return it.
59+
*
60+
* @returns {Promise<any>} The index.
61+
*/
4162
update() {
4263
// Temporary folder path: /tmp/tldr/{randomName}
4364
const tempFolder = path.join(os.tmpdir(), 'tldr', utils.uniqueId());
@@ -65,7 +86,6 @@ class Cache {
6586
index.rebuildPagesIndex(),
6687
]);
6788
})
68-
6989
.then(([_, shortIndex]) => {
7090
return shortIndex;
7191
});

lib/completion.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Completion {
2222

2323
appendScript(script) {
2424
const rcFilePath = this.getFilePath();
25-
return new Promise((resolve, reject) => {
25+
return new Promise((/** @type {(v?: never) => void} */ resolve, reject) => {
2626
fs.appendFile(rcFilePath, `\n${script}\n`, (err) => {
2727
if (err) {
2828
reject((new CompletionScriptError(`Error appending to ${rcFilePath}: ${err.message}`)));
@@ -79,4 +79,4 @@ fi
7979
}
8080
}
8181

82-
module.exports = Completion;
82+
module.exports = Completion;

lib/config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ exports.get = () => {
1111
const DEFAULT = path.join(__dirname, '..', 'config.json');
1212
const CUSTOM = path.join(osHomedir(), '.tldrrc');
1313

14-
let defaultConfig = JSON.parse(fs.readFileSync(DEFAULT));
14+
let defaultConfig = JSON.parse(fs.readFileSync(DEFAULT, 'utf-8'));
1515
defaultConfig.cache = path.join(osHomedir(), '.tldr');
1616

1717
let customConfig = {};
1818
try {
19-
customConfig = JSON.parse(fs.readFileSync(CUSTOM));
19+
customConfig = JSON.parse(fs.readFileSync(CUSTOM, 'utf-8'));
2020
} catch (ex) {
2121
if (ex instanceof SyntaxError) {
2222
throw new Error('The content of .tldrrc is not a valid JSON object:\n' + ex);

lib/index.js

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ let shortIndex = null;
1010
const pagesPath = path.join(config.get().cache, 'cache');
1111
const shortIndexFile = path.join(pagesPath, 'shortIndex.json');
1212

13+
/**
14+
* @param {string} page
15+
* @param {string|undefined} preferredPlatform
16+
* @param {string} preferredLanguage
17+
* @returns {Promise<?string>}
18+
*/
1319
function findPage(page, preferredPlatform, preferredLanguage) {
1420
// Load the index
1521
return getShortIndex()
@@ -33,7 +39,7 @@ function findPage(page, preferredPlatform, preferredLanguage) {
3339
ll = preferredLanguage.substring(0, preferredLanguage.indexOf('_'));
3440
}
3541
if (!hasLang(targets, preferredLanguage)) {
36-
preferredLanguage = ll;
42+
preferredLanguage = /** @type {string} */ (ll);
3743
}
3844

3945
// Page resolution logic:
@@ -91,25 +97,38 @@ function hasLang(targets, preferredLanguage) {
9197
});
9298
}
9399

94-
// hasPage is always called after the index is created,
95-
// hence just return the variable in memory.
96-
// There is no need to re-read the index file again.
100+
/**
101+
* Check if a page is in the index.
102+
*
103+
* @returns {boolean} The presence of the page in the index.
104+
*/
97105
function hasPage(page) {
106+
// hasPage is always called after the index is created,
107+
// hence just return the variable in memory.
108+
// There is no need to re-read the index file again.
98109
if (!shortIndex) {
99110
return false;
100111
}
101112
return page in shortIndex;
102113
}
103114

104-
// Return all commands available in the local cache.
115+
/**
116+
* Return all commands available in the local index.
117+
* @returns {Promise<string[]>} A promise with the commands from the index.
118+
*/
105119
function commands() {
106120
return getShortIndex().then((idx) => {
107121
return Object.keys(idx).sort();
108122
});
109123
}
110124

111-
// Return all commands for a given platform.
112-
// P.S. - The platform 'common' is always included.
125+
/**
126+
* Return all commands for a given platform. The 'common' platform is always
127+
* included.
128+
*
129+
* @param {string} platform The desired platform.
130+
* @returns {Promise<string[]>} The commands for a given platform.
131+
*/
113132
function commandsFor(platform) {
114133
return getShortIndex()
115134
.then((idx) => {
@@ -124,7 +143,11 @@ function commandsFor(platform) {
124143
});
125144
}
126145

127-
// Delete the index file.
146+
/**
147+
* Delete the index file.
148+
*
149+
* @returns {Promise<any>} A promise when the remove is completed.
150+
*/
128151
function clearPagesIndex() {
129152
return fs.unlink(shortIndexFile)
130153
.then(() => {
@@ -139,7 +162,9 @@ function clearPagesIndex() {
139162
});
140163
}
141164

142-
// Set the shortIndex variable to null.
165+
/**
166+
* Set the shortIndex variable to null.
167+
*/
143168
function clearRuntimeIndex() {
144169
shortIndex = null;
145170
}
@@ -150,18 +175,26 @@ function rebuildPagesIndex() {
150175
});
151176
}
152177

153-
// If the variable is not set, read the file and set it.
154-
// Else, just return the variable.
178+
/**
179+
* Return the index, that contains all available commands with their target os
180+
* and platform. If the index is not loaded, read the file and load it.
181+
*
182+
* @returns {Promise<any>} The index entries.
183+
*/
155184
function getShortIndex() {
156185
if (shortIndex) {
157186
return Promise.resolve(shortIndex);
158187
}
159188
return readShortPagesIndex();
160189
}
161190

162-
// Read the index file, and load it into memory.
163-
// If the file does not exist, create the data structure, write the file,
164-
// and load it into memory.
191+
/**
192+
* Read the index file, and load it into memory.
193+
*
194+
* If the file does not exist, create the data structure, write the file,
195+
* and load it into memory.
196+
* @returns {Promise<any>} The index entries.
197+
*/
165198
function readShortPagesIndex() {
166199
return fs.readJson(shortIndexFile)
167200
.then((idx) => {

lib/parser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ function unhtml(text){
1717

1818
exports.parse = (markdown) => {
1919
// Creating the page structure
20+
/** @type {Required<import('./tldr').TldrPage> & { examples: any[] }} */
2021
let page = {
2122
name: '',
2223
description: '',

lib/remote.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ const unzip = require('adm-zip');
66
const config = require('./config');
77
const axios = require('axios');
88

9-
// Downloads the zip file from github and extracts it to folder
9+
/**
10+
* Download the zip file from GitHub and extract it to folder.
11+
* @param {string} loc Path to a directory on disk.
12+
* @param {string} lang Language/locale code.
13+
* @returns {Promise<void>} A promise when the operation is completed.
14+
*/
1015
exports.download = (loc, lang) => {
1116
// If the lang is english then keep the url simple, otherwise add language.
1217
const suffix = (lang === 'en' ? '' : '.' + lang);
@@ -21,12 +26,12 @@ exports.download = (loc, lang) => {
2126
headers: { 'User-Agent' : 'tldr-node-client' },
2227
timeout: REQUEST_TIMEOUT,
2328
}).then((response) => {
24-
return new Promise((resolve, reject) => {
29+
return new Promise((/** @type {(v?: never) => void} */ resolve, reject) => {
2530
let fileName = path.join(loc, 'download_' + lang + '.zip');
2631

2732
const writer = fs.createWriteStream(fileName);
2833
response.data.pipe(writer);
29-
34+
3035
writer.on('finish', () => {
3136
writer.end();
3237
const zip = new unzip(fileName);
@@ -41,4 +46,4 @@ exports.download = (loc, lang) => {
4146
}).catch((err) => {
4247
return Promise.reject(err);
4348
});
44-
};
49+
};

lib/render.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
const Theme = require('./theme');
44
const he = require('he'); // Import the 'he' library
55

6-
// The page structure is passed to this function, and then the theme is applied
7-
// to different parts of the page and rendered to the console
6+
/**
7+
* Page structure is passed to this function, and then the theme is applied to
8+
* different parts of the page and rendered to the console.
9+
*
10+
* @param {import('./tldr').TldrPage} page
11+
* @param {any} config
12+
* @returns {string|void}
13+
*/
814
exports.toANSI = (page, config) => {
915
// Creating the theme object
1016
let themeOptions = config.themes[config.theme];

lib/search.js

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,53 @@ const utils = require('./utils');
99
const index = require('./index');
1010
const platforms = require('./platforms');
1111

12+
/**
13+
* @typedef {object} Corpus
14+
* @property {Record<string, Record<string, number>>} fileWords
15+
* @property {Record<string, number>} fileLengths
16+
* @property {Record<string, string[]>} invertedIndex
17+
* @property {any} allTokens
18+
* @property {Record<string, Record<string, number>>} tfidf
19+
*
20+
* @typedef {object} Query
21+
* @property {?string} raw
22+
* @property {?string[]} tokens
23+
* @property {Record<string, number>} frequency
24+
* @property {Record<string, number>} score
25+
* @property {QueryRank[]} ranks
26+
*
27+
* @typedef {object} QueryRank
28+
* @property {string} file
29+
* @property {number} score
30+
*/
31+
1232
const CACHE_FOLDER = path.join(config.get().cache, 'cache');
1333

1434
const filepath = CACHE_FOLDER + '/search-corpus.json';
1535

16-
let corpus = {};
17-
18-
corpus.fileWords = {};
19-
corpus.fileLengths = {};
20-
corpus.invertedIndex = {};
21-
corpus.allTokens = new Set();
22-
corpus.tfidf = {};
2336

24-
let query = {};
37+
/** @type {Corpus} */
38+
let corpus = {
39+
fileWords: {},
40+
fileLengths: {},
41+
invertedIndex: {},
42+
allTokens: new Set(),
43+
tfidf: {},
44+
};
2545

26-
query.raw = null;
27-
query.tokens = null;
28-
query.frequency = {};
29-
query.score = {};
30-
query.ranks = [];
46+
/** @type {Query} */
47+
let query = {
48+
raw: null,
49+
tokens: null,
50+
frequency: {},
51+
score: {},
52+
ranks: [],
53+
};
3154

55+
/**
56+
* @param {string} data
57+
* @returns {string[]}
58+
*/
3259
let getTokens = (data) => {
3360
let tokenizer = new natural.WordTokenizer();
3461
let tokens = tokenizer.tokenize(data);
@@ -139,6 +166,9 @@ let readCorpus = () => {
139166
});
140167
};
141168

169+
/**
170+
* @param {string} rawquery
171+
*/
142172
let processQuery = (rawquery) => {
143173
query.raw = rawquery;
144174
query.tokens = getTokens(rawquery);
@@ -156,10 +186,9 @@ let processQuery = (rawquery) => {
156186
query.score = {};
157187
query.tokens.forEach((word) => {
158188
if (corpus.invertedIndex[word]) {
159-
let logbase = 10;
160189
let df = corpus.invertedIndex[word].length;
161-
let idf = Math.log(numberOfFiles / df, logbase);
162-
let wordWeight = idf * (1 + Math.log(query.frequency[word], logbase));
190+
let idf = Math.log10(numberOfFiles / df);
191+
let wordWeight = idf * (1 + Math.log10(query.frequency[word]));
163192
corpus.invertedIndex[word].forEach((file) => {
164193
let fileWeight = corpus.tfidf[file][word];
165194
if (query.score[file]) {
@@ -209,7 +238,7 @@ exports.printResults = (results, config) => {
209238
outputs.add(output);
210239
});
211240

212-
console.log('Searching for:', query.raw.trim());
241+
console.log('Searching for:', /** @type {string} */ (query.raw).trim());
213242
console.log();
214243
Array.from(outputs).forEach((elem) => {
215244
console.log(elem);
@@ -220,7 +249,7 @@ exports.printResults = (results, config) => {
220249
};
221250

222251
exports.createIndex = () => {
223-
return utils.glob(CACHE_FOLDER + '/pages/**/*.md', {})
252+
return utils.glob(CACHE_FOLDER + '/pages/**/*.md')
224253
.then((files) => {
225254
let promises = [];
226255
files.forEach((file) => {
@@ -246,6 +275,10 @@ exports.createIndex = () => {
246275
});
247276
};
248277

278+
/**
279+
* @param {string} rawquery
280+
* @returns
281+
*/
249282
exports.getResults = (rawquery) => {
250283
query.ranks = [];
251284
return readCorpus()

0 commit comments

Comments
 (0)