datum-focus
Version:
Data shape, model, metadata, JSON, JSON Schema, GraphQL, MongoDB query and aggregations, iterator generators
116 lines (97 loc) • 3.44 kB
text/typescript
export function cleanseAssertionOperators<T extends number | string | symbol = string>(
parsedName: string
): T {
return <T>parsedName.replace(/[?!]/g, "");
}
export interface NameofOptions {
/**
* Take only the last property of nested properties.
*/
lastProp?: boolean;
}
export function nameof<T extends Object>(
nameFunction: ((obj: T) => any) | { new (...params: any[]): T },
options?: NameofOptions
): string {
const fnStr = nameFunction.toString();
// ES6 class name:
// "class ClassName { ..."
if (
fnStr.startsWith("class ") &&
// Theoretically could, for some ill-advised reason, be "class => class.prop".
!fnStr.startsWith("class =>")
) {
return cleanseAssertionOperators(
fnStr.substring("class ".length, fnStr.indexOf(" {"))
);
}
// ES6 prop selector:
// "x => x.prop"
if (fnStr.includes("=>")) {
return cleanseAssertionOperators(fnStr.substring(fnStr.indexOf(".") + 1));
}
// ES5 prop selector:
// "function (x) { return x.prop; }"
// webpack production build excludes the spaces and optional trailing semicolon:
// "function(x){return x.prop}"
// FYI - during local dev testing i observed carriage returns after the curly brackets as well
// Note by maintainer: See https://github.com/IRCraziestTaxi/ts-simple-nameof/pull/13#issuecomment-567171802 for explanation of this regex.
const matchRegex = /function\s*\(\w+\)\s*\{[\r\n\s]*return\s+\w+\.((\w+\.)*(\w+))/i;
const es5Match = fnStr.match(matchRegex);
if (es5Match) {
return options && options.lastProp ? es5Match[3] : es5Match[1];
}
// ES5 class name:
// "function ClassName() { ..."
if (fnStr.startsWith("function ")) {
return cleanseAssertionOperators(
fnStr.substring("function ".length, fnStr.indexOf("("))
);
}
// Invalid function.
throw new Error("nameof: Invalid function.");
}
export function nameOf<T extends Object>(
nameFunction: (obj: T) => unknown
): keyof T {
const fnStr = nameFunction.toString();
// ES6 class name:
// "class ClassName { ..."
if (
fnStr.startsWith("class ") &&
// Theoretically could, for some ill-advised reason, be "class => class.prop".
!fnStr.startsWith("class =>")
) {
return cleanseAssertionOperators<keyof T>(
fnStr.substring("class ".length, fnStr.indexOf(" {"))
);
}
// ES6 prop selector:
// "x => x.prop"
if (fnStr.includes("=>")) {
return cleanseAssertionOperators<keyof T>(
fnStr.substring(fnStr.indexOf(".") + 1)
);
}
// ES5 prop selector:
// "function (x) { return x.prop; }"
// webpack production build excludes the spaces and optional trailing semicolon:
// "function(x){return x.prop}"
// FYI - during local dev testing i observed carriage returns after the curly brackets as well
// Note by maintainer: See https://github.com/IRCraziestTaxi/ts-simple-nameof/pull/13#issuecomment-567171802 for explanation of this regex.
const matchRegex = /function\s*\(\w+\)\s*\{[\r\n\s]*return\s+\w+\.((\w+\.)*(\w+))/i;
const es5Match = fnStr.match(matchRegex);
if (es5Match) {
return <keyof T>es5Match[1];
}
// ES5 class name:
// "function ClassName() { ..."
if (fnStr.startsWith("function ")) {
return cleanseAssertionOperators(
fnStr.substring("function ".length, fnStr.indexOf("("))
);
}
// Invalid function.
throw new Error("nameof: Invalid function.");
}
export default nameof;