From 04dec135e76946439f549c174c1ca8099fc60a8b Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 11:37:56 +0700 Subject: [PATCH 1/6] fix: Fix unrecognized type when using the string value Previously, if using the string value of `lsTypes` enum (e.g., 'LS_F') the `ls` function can't recognizes it correctly and ignore it, thus causing the returned value is the undesired entries result. This change is to address the issue and now the `ls` function correctly recognized the string value of `lsTypes` enum, improving flexibility to the function. As of this change, you can now call the `ls` function with less code and more flexible: const files = await ls('./foo', {}, 'LS_F'); --- src/lsfnd.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lsfnd.ts b/src/lsfnd.ts index 9824bfa..34f6008 100644 --- a/src/lsfnd.ts +++ b/src/lsfnd.ts @@ -205,9 +205,11 @@ export async function ls( switch (type) { case lsTypes.LS_D: + case 'LS_D': resultType = (!stats.isFile() && stats.isDirectory()); break; case lsTypes.LS_F: + case 'LS_F': resultType = (stats.isFile() && !stats.isDirectory()); break; // If set to `LS_A` or not known value, default to include all types From 827c2bb04b23605709faedeb7e4dec6a94d55650 Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 16:23:14 +0700 Subject: [PATCH 2/6] refactor: Improve the type argument checker In the last change we have configured the `ls` function to accepts the string value representing the `lsTypes` property names as value of `type` argument. This change brings the type argument checker to more strict and prevent from unintended values. In addition to this change, a new helper function called `checkType` has been defined to checks and verifies the type argument and compare it with the valid types. --- src/lsfnd.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/lsfnd.ts b/src/lsfnd.ts index 34f6008..72ef806 100644 --- a/src/lsfnd.ts +++ b/src/lsfnd.ts @@ -23,6 +23,8 @@ import type { LsTypesValues } from '../types'; +type Unpack = A extends Array<(infer U)> ? U : A; + /** * Converts a file URL to a file path. * @@ -63,6 +65,49 @@ function fileUrlToPath(url: URL | string): string { return (url instanceof URL) ? url.pathname : url.replace(/^file:/, ''); } +/** + * Checks if a provided type matches any of the allowed types. + * + * This function verifies if a provided `type` argument matches any of the + * allowed types specified in the `validTypes` array. It throws a `TypeError` + * if the `type` doesn't match any valid type. + * + * @param type - The type value to be checked. + * @param validTypes - An array containing the allowed types for the `type` parameter. + * + * @throws TypeError - If the provided `type` doesn't match any of the valid types. + * + * @since 1.0.0 + * @internal + */ +function checkType( + type: lsTypes | LsTypesKeys | LsTypesValues | N, + validTypes: Array<(string | number | N)> +): void { + function joinAll(arr: (typeof validTypes), delim: string): string { + let str: string = ''; + arr.forEach((e: Unpack<(typeof validTypes)>, i: number) => { + if (i > 0 && i <= arr.length - 1) str += delim; + str += (typeof e === 'string') ? `'${e}'` + : (e === null) ? 'null' + : (typeof e === 'undefined') ? 'undefined' : e; + }); + return str; + } + + let match: boolean = false; + validTypes.forEach((validType: Unpack<(typeof validTypes)>) => { + if (!match && type === validType) match = true; + }); + if (!match) { + throw new TypeError( + `Invalid 'type' value of ${type} ('${typeof type}'). Valid type is "${ + joinAll(validTypes.sort(), ' | ') + }"`); + } + return; +} + /** * Lists files and/or directories in a specified directory path, filtering by a * regular expression pattern. @@ -188,6 +233,9 @@ export async function ls( + (Array.isArray(options) ? 'array' : typeof options)); } + // Check the type argument + checkType(type!, [ ...Object.values(lsTypes), 0, null, undefined ]); + let result: LsResult = null; try { // Read the specified directory path recursively From d556337cac1707206ea9991dafe4db7d26f2b3ad Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 20:11:55 +0700 Subject: [PATCH 3/6] refactor(ls): Small refactor on default type resolution --- src/lsfnd.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lsfnd.ts b/src/lsfnd.ts index 72ef806..184362c 100644 --- a/src/lsfnd.ts +++ b/src/lsfnd.ts @@ -260,8 +260,7 @@ export async function ls( case 'LS_F': resultType = (stats.isFile() && !stats.isDirectory()); break; - // If set to `LS_A` or not known value, default to include all types - default: resultType = true; + default: resultType = (stats.isFile() || stats.isDirectory()); } return ( @@ -272,10 +271,12 @@ export async function ls( ) ? entry : null; }) ).then(function (results: Array): LsEntries { - return results.filter(function (entry: string | null): boolean { - // Remove any null entries - return !!entry!; - }); + return results.filter( + function (entry: Unpack<(typeof results)>): boolean { + // Remove any null entries + return !!entry!; + } + ); }); } catch (err: unknown) { if (err instanceof Error) throw err; From 084a48c47b91c27fe3d3503d58426f56b94f18fd Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 22:10:05 +0700 Subject: [PATCH 4/6] docs: Improve the APIs docs and add link references This change updated and improved the APIs docs and also added link references for error classes that might be thrown by the APIs. --- src/lsfnd.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/lsfnd.ts b/src/lsfnd.ts index 184362c..5dd7755 100644 --- a/src/lsfnd.ts +++ b/src/lsfnd.ts @@ -39,8 +39,8 @@ type Unpack = A extends Array<(infer U)> ? U : A; * or a string representing a file URL and must starts with `"file:"` * protocol. * @returns A string representing the corresponding file path. - * @throws {URIError} If the URL is not a valid file URL or if it contains - * unsupported formats. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/URIError **URIError**} - + * If the URL is not a valid file URL or if it contains unsupported formats. * * @example * // Convert a file URL to a file path @@ -75,7 +75,8 @@ function fileUrlToPath(url: URL | string): string { * @param type - The type value to be checked. * @param validTypes - An array containing the allowed types for the `type` parameter. * - * @throws TypeError - If the provided `type` doesn't match any of the valid types. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError **TypeError**} - + * If the provided `type` doesn't match any of the valid types. * * @since 1.0.0 * @internal @@ -158,9 +159,11 @@ function checkType( * @returns A promise that resolves with an array of string representing the * entries result excluding `'.'` and `'..'` or an empty array (`[]`) * if any files and directories does not match with the specified filter options. - * @throws {Error} If there is an error occurred while reading a directory. - * @throws {URIError} If the given URL path contains invalid file URL scheme or - * using unsupported protocols. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error **Error**} - + * If there is an error occurred while reading a directory. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/URIError **URIError**} - + * If the given URL path contains invalid file URL scheme or using + * unsupported protocols. * * @example * // List all installed packages in 'node_modules' directory @@ -330,9 +333,11 @@ export async function ls( * @returns A promise that resolves with an array of string representing the * entries result excluding `'.'` and `'..'` or an empty array (`[]`) * if any files and directories does not match with the specified filter options. - * @throws {Error} If there is an error occurred while reading a directory. - * @throws {URIError} If the given URL path contains invalid file URL scheme or - * using unsupported protocols. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error **Error**} - + * If there is an error occurred while reading a directory. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/URIError **URIError**} - + * If the given URL path contains invalid file URL scheme or using + * unsupported protocols. * * @example * // List all JavaScript files in current directory recursively, @@ -401,9 +406,11 @@ export async function lsFiles( * @returns A promise that resolves with an array of string representing the * entries result excluding `'.'` and `'..'` or an empty array (`[]`) * if any files and directories does not match with the specified filter options. - * @throws {Error} If there is an error occurred while reading a directory. - * @throws {URIError} If the given URL path contains invalid file URL scheme or - * using unsupported protocols. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error **Error**} - + * If there is an error occurred while reading a directory. + * @throws {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/URIError **URIError**} - + * If the given URL path contains invalid file URL scheme or using + * unsupported protocols. * * @example * // Search and list directory named 'foo' in 'src' directory From 87d9c54c06ab11ed4b0c3904008aef22425d8439 Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 22:52:21 +0700 Subject: [PATCH 5/6] test: Add several test cases - Added a test to check whether the `options` argument of APIs allows explicit `null` value - Added a test to check whether the `ls` function accepts a string value of `lsTypes` property names as value of `type` argument - Added a test to check the `ls` function correctly throws a `TypeError` if the given `type` argument is an unexpected value --- test/lsfnd.spec.cjs | 26 +++++++++++++++++++++----- test/lsfnd.spec.mjs | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/test/lsfnd.spec.cjs b/test/lsfnd.spec.cjs index 1cbeb72..61fa721 100644 --- a/test/lsfnd.spec.cjs +++ b/test/lsfnd.spec.cjs @@ -27,6 +27,12 @@ it('test `lsFiles` function by listing this file directory', async () => { deepEq(results, expected); }, false); +it('test `lsDirs` function by listing this file directory', async () => { + const results = await lsDirs(__dirname); + const expected = [ 'lib' ].map((e) => path.join(__dirname, e)); + deepEq(results, expected); +}, false); + it('list root directory using URL object', async () => { await doesNotReject(ls(pathToFileURL(rootDirPosix)), URIError); }, false); @@ -35,17 +41,27 @@ it('list root directory using file URL path', async () => { await doesNotReject(ls('file:'.concat(rootDirPosix)), URIError); }, false); -it('test `lsDirs` function by listing this file directory', async () => { - const results = await lsDirs(__dirname); - const expected = [ 'lib' ].map((e) => path.join(__dirname, e)); - deepEq(results, expected); +it('test if the options argument allows explicit null value', async () => { + await doesNotReject(lsFiles(__dirname, null), TypeError); +}, false); + +it('test if the type argument accepts a string value', async () => { + await doesNotReject(ls(__dirname, {}, 'LS_D'), TypeError); }, false); it('throws an error if the given directory path not exist', async () => { await rejects(ls('./this/is/not/exist/directory/path'), Error); }, false); -it('throws an URIError if the given file URL path using unsupported protocol', +it('throws a `URIError` if the given file URL path using unsupported protocol', async () => await rejects(ls('http:'.concat(rootDirPosix)), URIError), false ); + +it('throws a `TypeError` if the given type is an unexpected value', + async () => { + await rejects(ls(__dirname, {}, 'LS_FOO'), TypeError); // Invalid string value test + await rejects(ls(__dirname, {}, []), TypeError); // Array test + }, + false +); diff --git a/test/lsfnd.spec.mjs b/test/lsfnd.spec.mjs index a84abd1..c32c996 100644 --- a/test/lsfnd.spec.mjs +++ b/test/lsfnd.spec.mjs @@ -45,6 +45,14 @@ it('list root directory using file URL path', async () => { await doesNotReject(ls('file:'.concat(rootDirPosix)), URIError); }, false); +it('test if the options argument allows explicit null value', async () => { + await doesNotReject(lsFiles(__dirname, null), TypeError); +}, false); + +it('test if the type argument accepts a string value', async () => { + await doesNotReject(ls(__dirname, {}, 'LS_D'), TypeError); +}, false); + it('throws an error if the given directory path not exist', async () => { await rejects(ls('./this/is/not/exist/directory/path'), Error); }, false); @@ -53,3 +61,11 @@ it('throws an URIError if the given file URL path using unsupported protocol', async () => await rejects(ls('http:'.concat(rootDirPosix)), URIError), false ); + +it('throws a `TypeError` if the given type is an unexpected value', + async () => { + await rejects(ls(__dirname, {}, 'LS_FOO'), TypeError); // Invalid string value test + await rejects(ls(__dirname, {}, []), TypeError); // Array test + }, + false +); From 27c2c2c7dbda096a717d32da8180ea647fddaadc Mon Sep 17 00:00:00 2001 From: Ryuu Mitsuki Date: Sat, 20 Apr 2024 23:01:37 +0700 Subject: [PATCH 6/6] chore: Change the text color for better readability A small change to `simpletest` library, changed the text description color for success test cases to a bit darker, but keep the description color for failed test cases to default white. This allows developers and users to focus on where the failed tests have occurred (if any). --- test/lib/simpletest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lib/simpletest.js b/test/lib/simpletest.js index 035744c..c60f239 100644 --- a/test/lib/simpletest.js +++ b/test/lib/simpletest.js @@ -44,9 +44,9 @@ async function it(desc, func, continueOnErr=false) { const { isAsyncFunction } = require('node:util').types; try { isAsyncFunction(func) ? await func() : func(); - console.log(` \x1b[92m\u2714 \x1b[0m\x1b[1m${desc}\x1b[0m`); + console.log(` \x1b[92m\u2714 \x1b[0m\x1b[2m${desc}\x1b[0m`); } catch (err) { - console.error(` \x1b[91m\u2718 \x1b[0m\x1b[1m${desc}\x1b[0m\n`); + console.error(` \x1b[91m\u2718 \x1b[0m${desc}\n`); console.error(new TestError(err.message)); !!continueOnErr || process.exit(1); // Force terminate the process }