Skip to content
Open
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
43 changes: 43 additions & 0 deletions packages/context/src/__tests__/unit/inject.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {inherits} from 'util';
import {
Constructor,
describeInjectedArguments,
Expand Down Expand Up @@ -105,6 +106,48 @@ describe('function argument injection', () => {
expect(meta.map(m => m.bindingSelector)).to.deepEqual([]);
});

it('does not read the source of a class without a base class', () => {
let sourceRead = false;
class TestClass {
static toString() {
sourceRead = true;
return '';
}

constructor(@inject('foo') foo: string) {}
}

const meta = describeInjectedArguments(TestClass);
expect(meta.map(m => m.bindingSelector)).to.deepEqual(['foo']);
expect(sourceRead).to.be.false();
});

it('does not inherit ctor args through a linked prototype chain', () => {
class BaseClass {
constructor(@inject('foo') foo: string) {}
}

class TestClass {
constructor(foo: string) {}
}
Object.setPrototypeOf(TestClass.prototype, BaseClass.prototype);

const meta = describeInjectedArguments(TestClass);
expect(meta).to.deepEqual([]);
});

it('inherits ctor args for a function linked with util.inherits', () => {
class BaseClass {
constructor(@inject('foo') foo: string) {}
}

function testFunction() {}
inherits(testFunction, BaseClass);

const meta = describeInjectedArguments(testFunction);
expect(meta.map(m => m.bindingSelector)).to.deepEqual(['foo']);
});

// https://github.com/loopbackio/loopback-next/issues/2946
it('allows custom decorator that returns a new constructor', () => {
class HelloController {
Expand Down
16 changes: 15 additions & 1 deletion packages/context/src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,20 @@ function findOrCreateBindingForInjection(
return binding;
}

/**
* Check if the given class can inherit metadata from a base class. Besides
* `extends`, `Reflect.getMetadata` also follows a base class that is linked
* only through the `prototype` of the class.
*
* @param targetClass - Target class
*/
function hasBaseClass(targetClass: Object) {
if (Object.getPrototypeOf(targetClass) !== Function.prototype) return true;
const proto = (targetClass as {prototype?: object}).prototype;
const baseProto = proto && Object.getPrototypeOf(proto);
return baseProto != null && baseProto !== Object.prototype;
}

/**
* Check if constructor injection should be applied to the base class
* of the given target class
Expand Down Expand Up @@ -589,7 +603,7 @@ export function describeInjectedArguments(
// Build the description
const options: InspectionOptions = {};
if (method === '') {
if (shouldSkipBaseConstructorInjection(target)) {
if (hasBaseClass(target) && shouldSkipBaseConstructorInjection(target)) {
options.ownMetadataOnly = true;
}
} else if (Object.prototype.hasOwnProperty.call(target, method)) {
Expand Down