From d787b8fcd1b3aded36a401ec6bfbb8aea2ec05f6 Mon Sep 17 00:00:00 2001 From: notaphplover Date: Tue, 3 Dec 2024 17:05:51 +0100 Subject: [PATCH] Fix null property access at getBaseClassDependencyCount (#1665) * refactor: add getBaseType * fix: update getBaseClassDependencyCount to rely on getBaseType * fix: update rollup config to patch wrong sourcemap links on ESM build * fix: update Prototype with mandatory constructor --- CHANGELOG.md | 1 + rollup.config.mjs | 8 ++++ src/planning/reflection_utils.ts | 62 ++++++++++++++-------------- src/test/utils/get_base_type.test.ts | 50 ++++++++++++++++++++++ src/utils/get_base_type.ts | 16 +++++++ 5 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 src/test/utils/get_base_type.test.ts create mode 100644 src/utils/get_base_type.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a51bc368..5ec3f563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Fixed +- Fixed unexpected property access while running planning checks on injected base types. ## [6.1.5] diff --git a/rollup.config.mjs b/rollup.config.mjs index 6c8117b7..5acac763 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -36,6 +36,14 @@ export default [ dir: './lib/esm', format: 'esm', sourcemap: true, + sourcemapPathTransform: (relativeSourcePath) => { + // Rollup seems to generate source maps pointing to the wrong directory. Ugly patch to fix it + if (relativeSourcePath.startsWith('../')) { + return relativeSourcePath.slice(3); + } else { + return relativeSourcePath; + } + }, }, ], plugins: [ diff --git a/src/planning/reflection_utils.ts b/src/planning/reflection_utils.ts index 5614e54e..3f50c2ce 100644 --- a/src/planning/reflection_utils.ts +++ b/src/planning/reflection_utils.ts @@ -3,6 +3,7 @@ import { getTargets } from '@inversifyjs/core'; import * as METADATA_KEY from '../constants/metadata_keys'; import { interfaces } from '../interfaces/interfaces'; +import { getBaseType } from '../utils/get_base_type'; import { getFunctionName } from '../utils/serialization'; import { Metadata } from './metadata'; @@ -17,40 +18,37 @@ function getBaseClassDependencyCount( metadataReader: interfaces.MetadataReader, func: NewableFunction, ): number { - const baseConstructor: NewableFunction = - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - Object.getPrototypeOf(func.prototype).constructor as NewableFunction; - if (baseConstructor !== (Object as unknown as NewableFunction)) { - // get targets for base class - - const targets: interfaces.Target[] = getTargets(metadataReader)( - baseConstructor as Newable, - ); - - // get unmanaged metadata - const metadata: interfaces.Metadata[][] = targets.map( - (t: interfaces.Target) => - t.metadata.filter( - (m: interfaces.Metadata) => m.key === METADATA_KEY.UNMANAGED_TAG, - ), - ); - - // Compare the number of constructor arguments with the number of - // unmanaged dependencies unmanaged dependencies are not required - const unmanagedCount: number = ([] as Metadata[]).concat.apply( - [], - metadata, - ).length; - const dependencyCount: number = targets.length - unmanagedCount; - - if (dependencyCount > 0) { - return dependencyCount; - } else { - return getBaseClassDependencyCount(metadataReader, baseConstructor); - } - } else { + const baseConstructor: Newable | undefined = getBaseType(func); + + if (baseConstructor === undefined || baseConstructor === Object) { return 0; } + + // get targets for base class + const targets: interfaces.Target[] = + getTargets(metadataReader)(baseConstructor); + + // get unmanaged metadata + const metadata: interfaces.Metadata[][] = targets.map( + (t: interfaces.Target) => + t.metadata.filter( + (m: interfaces.Metadata) => m.key === METADATA_KEY.UNMANAGED_TAG, + ), + ); + + // Compare the number of constructor arguments with the number of + // unmanaged dependencies unmanaged dependencies are not required + const unmanagedCount: number = ([] as Metadata[]).concat.apply( + [], + metadata, + ).length; + const dependencyCount: number = targets.length - unmanagedCount; + + if (dependencyCount > 0) { + return dependencyCount; + } else { + return getBaseClassDependencyCount(metadataReader, baseConstructor); + } } export { getDependencies, getBaseClassDependencyCount, getFunctionName }; diff --git a/src/test/utils/get_base_type.test.ts b/src/test/utils/get_base_type.test.ts new file mode 100644 index 00000000..73feacae --- /dev/null +++ b/src/test/utils/get_base_type.test.ts @@ -0,0 +1,50 @@ +import { Newable } from '@inversifyjs/common'; +import { expect } from 'chai'; + +import { getBaseType } from '../../utils/get_base_type'; + +describe(getBaseType.name, () => { + describe('having a type with base type', () => { + let baseTypeFixture: Newable; + let typeFixture: Newable; + + before(() => { + class BaseType {} + + baseTypeFixture = BaseType; + typeFixture = class extends BaseType {}; + }); + + describe('when called', () => { + let result: unknown; + + before(() => { + result = getBaseType(typeFixture); + }); + + it('should return base type', () => { + expect(result).to.eq(baseTypeFixture); + }); + }); + }); + + describe('having a type with no base type', () => { + let typeFixture: Newable; + + before(() => { + typeFixture = Object; + }); + + describe('when called', () => { + let result: unknown; + + before(() => { + result = getBaseType(typeFixture); + }); + + it('should return undefined', () => { + expect(result).to.eq(undefined); + }); + }); + }); +}); diff --git a/src/utils/get_base_type.ts b/src/utils/get_base_type.ts new file mode 100644 index 00000000..2024eb92 --- /dev/null +++ b/src/utils/get_base_type.ts @@ -0,0 +1,16 @@ +import { Newable } from '@inversifyjs/common'; + +interface Prototype { + constructor: Newable; +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type +export function getBaseType(type: Function): Newable | undefined { + const prototype: Prototype | null = Object.getPrototypeOf( + type.prototype, + ) as Prototype | null; + + const baseType: Newable | undefined = prototype?.constructor; + + return baseType; +}