Skip to content

Commit

Permalink
fix(ls): Allow string types for type arg & improve type safety (#4)
Browse files Browse the repository at this point in the history
This changes addresses the issue where the `ls` function can't recognized a string value representing the `lsTypes` enum properties, focusing on function flexibility.

Signed-off-by: Ryuu Mitsuki <dhefam31@gmail.com>
  • Loading branch information
mitsuki31 committed Apr 20, 2024
2 parents 6c8a65e + 27c2c2c commit 8c64dc7
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 24 deletions.
92 changes: 75 additions & 17 deletions src/lsfnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import type {
LsTypesValues
} from '../types';

type Unpack<A> = A extends Array<(infer U)> ? U : A;

/**
* Converts a file URL to a file path.
*
Expand All @@ -37,8 +39,8 @@ import type {
* 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
Expand All @@ -63,6 +65,50 @@ 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 {@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
*/
function checkType<N extends null | undefined>(
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.
Expand Down Expand Up @@ -113,9 +159,11 @@ function fileUrlToPath(url: URL | string): string {
* @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
Expand Down Expand Up @@ -188,6 +236,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
Expand All @@ -205,13 +256,14 @@ 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
default: resultType = true;
default: resultType = (stats.isFile() || stats.isDirectory());
}

return (
Expand All @@ -222,10 +274,12 @@ export async function ls(
) ? entry : null;
})
).then(function (results: Array<string | null>): LsEntries {
return <LsEntries> results.filter(function (entry: string | null): boolean {
// Remove any null entries
return !!entry!;
});
return <LsEntries> results.filter(
function (entry: Unpack<(typeof results)>): boolean {
// Remove any null entries
return !!entry!;
}
);
});
} catch (err: unknown) {
if (err instanceof Error) throw err;
Expand Down Expand Up @@ -279,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,
Expand Down Expand Up @@ -350,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
Expand Down
4 changes: 2 additions & 2 deletions test/lib/simpletest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
26 changes: 21 additions & 5 deletions test/lsfnd.spec.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
);
16 changes: 16 additions & 0 deletions test/lsfnd.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
);

0 comments on commit 8c64dc7

Please sign in to comment.