diff --git a/packages/cactus-common/src/main/typescript/objects.ts b/packages/cactus-common/src/main/typescript/objects.ts new file mode 100644 index 0000000000..b55441f1aa --- /dev/null +++ b/packages/cactus-common/src/main/typescript/objects.ts @@ -0,0 +1,65 @@ +/** + * Utility class responsible for common and tedious tasks involving Javascript objects (instances of classes). + */ +export class Objects { + /** + * Returns a list of methods for an instance, including the inherited ones. + * Example: + * + * ```javascript + * class Base { + * constructor() { + * } + * getX() { + * return 'x'; + * } + * } + * + * class A extends Base { + * getA() { + * return 'a'; + * } + * } + * + * const a = new A(); + * const methodNames = Objects.getAllMethodNames(a); + * console.log(methodNames); + * // [ 'getA', 'getX' ] + * ``` + * + * @param anObject + */ + public static getAllMethodNames(anObject: any): string[] { + let properties: string[] = []; + do { + const symbols = Object.getOwnPropertySymbols(anObject); + const symbolPropertyNames = symbols.map((aSymbol) => aSymbol.toString()); + + const propertyNamesCurrent = Object.getOwnPropertyNames(anObject) + .concat(symbolPropertyNames) + .sort() + .filter((propertyName: string, index: number, arr) => { + return ( + typeof anObject[propertyName] === "function" && + propertyName !== "constructor" && + (index === 0 || propertyName !== arr[index - 1]) && + properties.indexOf(propertyName) === -1 + ); + }); + + properties = properties.concat(propertyNamesCurrent); + anObject = Object.getPrototypeOf(anObject); + } while (anObject && Object.getPrototypeOf(anObject)); + return properties; + } + + public static getAllFieldNames(anObject: any): string[] { + const allFieldNames = []; + for (const propertyKey in anObject) { + if (anObject.hasOwnProperty(propertyKey)) { + allFieldNames.push(propertyKey); + } + } + return allFieldNames; + } +} diff --git a/packages/cactus-common/src/main/typescript/public-api.ts b/packages/cactus-common/src/main/typescript/public-api.ts index 8f9a958b01..20a2a56ffe 100755 --- a/packages/cactus-common/src/main/typescript/public-api.ts +++ b/packages/cactus-common/src/main/typescript/public-api.ts @@ -1,3 +1,4 @@ export { LoggerProvider } from "./logging/logger-provider"; export { Logger, ILoggerOptions } from "./logging/logger"; export { LogLevelDesc } from "loglevel"; +export { Objects } from "./objects"; diff --git a/packages/cactus-common/src/test/typescript/unit/objects/get-all-method-names.ts b/packages/cactus-common/src/test/typescript/unit/objects/get-all-method-names.ts new file mode 100644 index 0000000000..a9ec0fa181 --- /dev/null +++ b/packages/cactus-common/src/test/typescript/unit/objects/get-all-method-names.ts @@ -0,0 +1,48 @@ +// tslint:disable: max-classes-per-file +// tslint:disable-next-line: no-var-requires +const tap = require("tap"); +import { Objects } from "../../../../main/typescript/public-api"; + +class Base { + private readonly y: string; + + constructor() { + this.y = "y"; + } + + getX() { + return "x"; + } +} + +class A extends Base { + private readonly b: string; + + constructor() { + super(); + this.b = "b"; + } + + getA() { + return "a"; + } +} + +tap.test("handles inheritance correctly", (assert: any) => { + const a = new A(); + const methodNames = Objects.getAllMethodNames(a); + assert.ok(Array.isArray(methodNames), "expect an arran of strings returned"); + assert.ok( + methodNames.length === 2, + "expect two items in said array of strings" + ); + assert.ok( + methodNames.includes("getX"), + 'expect "getX" in said array of strings' + ); + assert.ok( + methodNames.includes("getA"), + 'expect "getA" in said array of strings' + ); + assert.end(); +});