Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ls): Allow string types for type arg & improve type safety #4

Merged
merged 6 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
);
Loading