Skip to content

Commit

Permalink
Add get parent(s) of target class util functions
Browse files Browse the repository at this point in the history
  • Loading branch information
aedart committed Feb 15, 2024
1 parent 9f89c85 commit 0ffcdf4
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 0 deletions.
36 changes: 36 additions & 0 deletions packages/support/src/reflections/getAllParentsOfClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ConstructorOrAbstractConstructor } from "@aedart/contracts";
import { getParentOfClass } from "./getParentOfClass";
import { isset } from "@aedart/support/misc";

/**
* Returns all parent classes of given target
*
* @see {getParentOfClass}
*
* @param {ConstructorOrAbstractConstructor} target The target class.
* @param {boolean} [includeTarget=false] If `true`, then given target is included in the output as the first element.
*
* @returns {ConstructorOrAbstractConstructor[]} List of parent classes, ordered by the top-most parent class first.
*
* @throws {TypeError}
*/
export function getAllParentsOfClass(target: ConstructorOrAbstractConstructor, includeTarget: boolean = false): ConstructorOrAbstractConstructor[]
{
if (!isset(target)) {
throw new TypeError('getAllParentsOfClass() expects a target class as argument, undefined given');
}

const output: ConstructorOrAbstractConstructor[] = [];
if (includeTarget) {
output.push(target);
}

let parent: ConstructorOrAbstractConstructor | null = getParentOfClass(target);
while (parent !== null) {
output.push(parent);

parent = getParentOfClass(parent);
}

return output;
}
29 changes: 29 additions & 0 deletions packages/support/src/reflections/getParentOfClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ConstructorOrAbstractConstructor } from "@aedart/contracts";
import { FUNCTION_PROTOTYPE } from "@aedart/contracts/support/reflections";
import {isset} from "@aedart/support/misc";

/**
* Returns the parent class of given target class
*
* **Note**: _If target has a parent that matches
* [FUNCTION_PROTOTYPE]{@link import('@aedart/contracts/support/reflections').FUNCTION_PROTOTYPE}, then `null` is returned!_
*
* @param {ConstructorOrAbstractConstructor} target The target class
*
* @returns {ConstructorOrAbstractConstructor | null} Parent class or `null`, if target has no parent class.
*
* @throws {TypeError}
*/
export function getParentOfClass(target: ConstructorOrAbstractConstructor): ConstructorOrAbstractConstructor | null
{
if (!isset(target)) {
throw new TypeError('getParentOfClass() expects a target class as argument, undefined given');
}

const parent: object | null = Reflect.getPrototypeOf(target);
if (parent === FUNCTION_PROTOTYPE) {
return null;
}

return parent as ConstructorOrAbstractConstructor;
}
2 changes: 2 additions & 0 deletions packages/support/src/reflections/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './assertHasPrototypeProperty';
export * from './getAllParentsOfClass';
export * from './getClassPropertyDescriptor';
export * from './getClassPropertyDescriptors';
export * from './getParentOfClass';
export * from './hasPrototypeProperty';
export * from './isCallable';
export * from './isClassConstructor';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { getAllParentsOfClass } from "@aedart/support/reflections";

describe('@aedart/support/reflections', () => {
describe('getAllParentsOfClass()', () => {

it('fails when no arguments given', () => {
const callback = () => {
return getAllParentsOfClass();
}

expect(callback)
.toThrowError(TypeError);
});

it('returns all parent classes', () => {

class A {}
class B extends A {}
class C extends B {}

const parents = getAllParentsOfClass(C);

// Debug
// console.log('parents of C', parents);

expect(parents.length)
.withContext('Incorrect amount of parents returned')
.toEqual(2);

expect(parents[0])
.withContext('Incorrect parent of C')
.toEqual(B);

expect(parents[1])
.withContext('Incorrect parent of B')
.toEqual(A);
});

it('includes target in output', () => {

class A {}
class B extends A {}
class C extends B {}

const parents = getAllParentsOfClass(C, true);

// Debug
// console.log('parents', parents);

expect(parents.length)
.withContext('Incorrect amount of parents returned')
.toEqual(3);

expect(parents[0])
.withContext('First element should be C')
.toEqual(C);

expect(parents[1])
.withContext('Incorrect parent of C')
.toEqual(B);

expect(parents[2])
.withContext('Incorrect parent of B')
.toEqual(A);
});

it('returns empty array when target has no parents', () => {

class A {}

const parents = getAllParentsOfClass(A);

expect(parents.length)
.withContext('A should not have any parents')
.toEqual(0);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getParentOfClass } from "@aedart/support/reflections";

describe('@aedart/support/reflections', () => {
describe('getParentOfClass()', () => {

it('fails when no arguments given', () => {
const callback = () => {
return getParentOfClass();
}

expect(callback)
.toThrowError(TypeError);
});

it('can return parent class', () => {

class A {}
class B extends A {}
class C extends B {}

const parentOfC = getParentOfClass(C);
const parentOfB = getParentOfClass(B);
const parentOfA = getParentOfClass(A);

// Debug
// console.log('Parent of C', parentOfC);
// console.log('Parent of B', parentOfB);
// console.log('Parent of A', parentOfA);

expect(parentOfC)
.withContext('Incorrect parent of C')
.toEqual(B);

expect(parentOfB)
.withContext('Incorrect parent of B')
.toEqual(A);

expect(parentOfA)
.withContext('A should not have a parent')
.toBeNull()
});
});
});

0 comments on commit 0ffcdf4

Please sign in to comment.