From 4504a5e547bad10d3289905d1d22976af0d3d505 Mon Sep 17 00:00:00 2001 From: Mauro Gadaleta <580627+zazoomauro@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:00:45 +0200 Subject: [PATCH] feat: :sparkles: Ability to override services from autowire (#216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: ✨ Ability to override services from autowire #213 --- .github/workflows/build.yml | 30 +++++------ .gitignore | 3 +- lib/Autowire.js | 6 +++ lib/CompilerPass/AutowireOverridePass.js | 52 +++++++++++++++++++ lib/Definition.js | 17 +++++- lib/Exception/CannotAutowireOverrideSearch.js | 7 +++ lib/InstanceManager.js | 29 +++++++++++ lib/Loader/FileLoader.js | 11 ++++ .../config/services-not-exists.yaml | 9 ++++ .../Autowire-Override/config/services.yaml | 9 ++++ .../Autowire-Override/src/Adapter.ts | 3 ++ .../src/Adapters/BarAdapter.ts | 7 +++ .../src/Adapters/CiAdapter.ts | 7 +++ .../src/Adapters/FooAdapter.ts | 7 +++ .../src/FooBarAutowireOverride.ts | 13 +++++ .../lib-ts/Autowire.spec.js | 47 ++++++++++++++++- 16 files changed, 239 insertions(+), 18 deletions(-) create mode 100644 lib/CompilerPass/AutowireOverridePass.js create mode 100644 lib/Exception/CannotAutowireOverrideSearch.js create mode 100644 test/Resources-ts/Autowire-Override/config/services-not-exists.yaml create mode 100644 test/Resources-ts/Autowire-Override/config/services.yaml create mode 100644 test/Resources-ts/Autowire-Override/src/Adapter.ts create mode 100644 test/Resources-ts/Autowire-Override/src/Adapters/BarAdapter.ts create mode 100644 test/Resources-ts/Autowire-Override/src/Adapters/CiAdapter.ts create mode 100644 test/Resources-ts/Autowire-Override/src/Adapters/FooAdapter.ts create mode 100644 test/Resources-ts/Autowire-Override/src/FooBarAutowireOverride.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fdf079a..5696917 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,16 +2,16 @@ name: Build on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x, 15.x, 16.x, 17.x] + node-version: [14.x, 15.x, 16.x, 17.x, 18.x, 19.x, 20.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} @@ -24,18 +24,18 @@ jobs: - run: npm test coverage: - needs: [ build ] + needs: [build] name: coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - uses: actions/setup-node@master - with: - node-version: '17' - - run: npm ci - - uses: paambaati/codeclimate-action@v3.2.0 - env: - CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} - with: - coverageCommand: npm run test:coverage - coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov + - uses: actions/checkout@master + - uses: actions/setup-node@master + with: + node-version: "17" + - run: npm ci + - uses: paambaati/codeclimate-action@v3.2.0 + env: + CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} + with: + coverageCommand: npm run test:coverage + coverageLocations: ${{github.workspace}}/coverage/lcov.info:lcov diff --git a/.gitignore b/.gitignore index a9c67e9..7bf9c6a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ node_modules/ coverage/ coverage.lcov npm-debug.log -.DS_STORE \ No newline at end of file +.DS_STORE +.vscode diff --git a/lib/Autowire.js b/lib/Autowire.js index f161dde..18c356a 100644 --- a/lib/Autowire.js +++ b/lib/Autowire.js @@ -7,6 +7,8 @@ import Reference from './Reference' import ServiceFile from './ServiceFile' import AutowireIdentifier from './AutowireIdentifier' import ContainerDefaultDirMustBeSet from './Exception/ContainerDefaultDirMustBeSet' +import PassConfig from './PassConfig' +import AutowireOverridePass from './CompilerPass/AutowireOverridePass' export default class Autowire { /** @@ -121,6 +123,10 @@ export default class Autowire { if (this._serviceFile instanceof ServiceFile) { await this._serviceFile.generateFromContainer(this._container) } + this._container.addCompilerPass( + new AutowireOverridePass(), + PassConfig.TYPE_BEFORE_OPTIMIZATION + ) } /** diff --git a/lib/CompilerPass/AutowireOverridePass.js b/lib/CompilerPass/AutowireOverridePass.js new file mode 100644 index 0000000..dc0a27a --- /dev/null +++ b/lib/CompilerPass/AutowireOverridePass.js @@ -0,0 +1,52 @@ +import Reference from '../Reference' + +export default class AutowireOverridePass { + /** + * @param {ContainerBuilder} container + */ + process (container) { + this._definitions = container.instanceManager.definitions + const overrideDefinitions = container.instanceManager.searchDefinitionsToOverrideArgs() + const toDelete = [] + + for (const [key, definitionToOverride] of overrideDefinitions) { + toDelete.push(key) + this._processOverride(definitionToOverride, container) + } + + this._removeDefinitions(toDelete) + } + + _processOverride (definitionToOverride, container) { + const definitionsToOverride = container.instanceManager.searchNotOverrideDefinitionsByObject(definitionToOverride.Object) + + for (const overrideArg of definitionToOverride.overrideArgs) { + const references = this._getReferencesForOverrideArg(overrideArg.id) + + for (const [, definitionFromOverride] of definitionsToOverride) { + definitionFromOverride.args = [...references] + } + } + } + + _getReferencesForOverrideArg (overrideArgId) { + const argumentsToOverride = this._searchDefinitionsByClassName(overrideArgId) + return argumentsToOverride.map(arg => new Reference(arg.key)) + } + + _searchDefinitionsByClassName (className) { + const result = [] + for (const [key, definition] of this._definitions) { + if (definition.Object?.name === className) { + result.push({ key, definition }) + } + } + return result + } + + _removeDefinitions (keysToDelete) { + for (const key of keysToDelete) { + this._definitions.delete(key) + } + } +} diff --git a/lib/Definition.js b/lib/Definition.js index 02caf7f..3667cff 100644 --- a/lib/Definition.js +++ b/lib/Definition.js @@ -6,9 +6,10 @@ class Definition { * @param {*|null} Object * @param {Array} args */ - constructor (Object = null, args = []) { + constructor (Object = null, args = [], overrideArgs = []) { this._Object = Object this._args = args + this._overrideArgs = overrideArgs this._calls = [] this._tags = [] this._properties = new Map() @@ -221,6 +222,20 @@ class Definition { this._parent = value } + /** + * @param {Array} value + */ + set overrideArgs (value) { + this._overrideArgs = value + } + + /** + * @returns {Array} + */ + get overrideArgs () { + return this._overrideArgs + } + /** * @param {Object|Reference} Object * @param {string} method diff --git a/lib/Exception/CannotAutowireOverrideSearch.js b/lib/Exception/CannotAutowireOverrideSearch.js new file mode 100644 index 0000000..bd7e288 --- /dev/null +++ b/lib/Exception/CannotAutowireOverrideSearch.js @@ -0,0 +1,7 @@ +export default class CannotAutowireOverrideSearch extends Error { + constructor (className = null) { + super(`Cannot Autowire Override ${className}`) + this.name = 'CannotAutowireOverrideSearch' + this.stack = (new Error()).stack + } +} diff --git a/lib/InstanceManager.js b/lib/InstanceManager.js index adc9ca2..7897009 100644 --- a/lib/InstanceManager.js +++ b/lib/InstanceManager.js @@ -20,6 +20,13 @@ export default class InstanceManager { this._alias = alias } + /** + * @returns {Map} + */ + get definitions () { + return this._definitions + } + /** * @private * @param {string} id @@ -338,4 +345,26 @@ export default class InstanceManager { service[call.method](...args) } + + /** + * @returns {Map} + */ + searchDefinitionsToOverrideArgs () { + return new Map( + [...this._definitions] + .filter(([key, definition]) => definition.overrideArgs.length > 0) + ) + } + + /** + * @param {Function} Object + * + * @returns {Map} + */ + searchNotOverrideDefinitionsByObject (Object) { + return new Map( + [...this._definitions] + .filter(([key, definition]) => definition.Object?.name === Object.name && definition.overrideArgs.length === 0) + ) + } } diff --git a/lib/Loader/FileLoader.js b/lib/Loader/FileLoader.js index 746b3fa..bdd1d98 100644 --- a/lib/Loader/FileLoader.js +++ b/lib/Loader/FileLoader.js @@ -126,6 +126,7 @@ class FileLoader { definition.shared = service.shared this._parseArguments(definition, service.arguments) + this._parseOverrideArguments(definition, service.override_arguments) this._parseProperties(definition, service.properties) this._parseCalls(definition, service.calls) this._parseTags(definition, service.tags) @@ -244,6 +245,16 @@ class FileLoader { definition[argument] = this._getParsedArguments(args) } + /** + * @param {Definition} definition + * @param {Array} args + * + * @private + */ + _parseOverrideArguments (definition, args = []) { + definition.overrideArgs = this._getParsedArguments(args) + } + /** * @param {string} classObject * @param {string} mainClassName diff --git a/test/Resources-ts/Autowire-Override/config/services-not-exists.yaml b/test/Resources-ts/Autowire-Override/config/services-not-exists.yaml new file mode 100644 index 0000000..92dc64e --- /dev/null +++ b/test/Resources-ts/Autowire-Override/config/services-not-exists.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + rootDir: ../src + + App.NoExists: + class: './FooBarAutowireOverride' + override_arguments: + - '@Caca' diff --git a/test/Resources-ts/Autowire-Override/config/services.yaml b/test/Resources-ts/Autowire-Override/config/services.yaml new file mode 100644 index 0000000..d8673cf --- /dev/null +++ b/test/Resources-ts/Autowire-Override/config/services.yaml @@ -0,0 +1,9 @@ +services: + _defaults: + autowire: true + rootDir: ../src + + App.FooBarAutowireOverride: + class: './FooBarAutowireOverride' + override_arguments: + - '@CiAdapter' diff --git a/test/Resources-ts/Autowire-Override/src/Adapter.ts b/test/Resources-ts/Autowire-Override/src/Adapter.ts new file mode 100644 index 0000000..e7e762d --- /dev/null +++ b/test/Resources-ts/Autowire-Override/src/Adapter.ts @@ -0,0 +1,3 @@ +export default interface Adapter { + toString(): string; +} diff --git a/test/Resources-ts/Autowire-Override/src/Adapters/BarAdapter.ts b/test/Resources-ts/Autowire-Override/src/Adapters/BarAdapter.ts new file mode 100644 index 0000000..0347e97 --- /dev/null +++ b/test/Resources-ts/Autowire-Override/src/Adapters/BarAdapter.ts @@ -0,0 +1,7 @@ +import Adapter from "../Adapter"; + +export default class BarAdapter implements Adapter { + toString(): string { + return "bar"; + } +} diff --git a/test/Resources-ts/Autowire-Override/src/Adapters/CiAdapter.ts b/test/Resources-ts/Autowire-Override/src/Adapters/CiAdapter.ts new file mode 100644 index 0000000..5c26a86 --- /dev/null +++ b/test/Resources-ts/Autowire-Override/src/Adapters/CiAdapter.ts @@ -0,0 +1,7 @@ +import Adapter from "../Adapter"; + +export default class CiAdapter implements Adapter { + toString(): string { + return 'ci'; + } +} \ No newline at end of file diff --git a/test/Resources-ts/Autowire-Override/src/Adapters/FooAdapter.ts b/test/Resources-ts/Autowire-Override/src/Adapters/FooAdapter.ts new file mode 100644 index 0000000..cbef0f7 --- /dev/null +++ b/test/Resources-ts/Autowire-Override/src/Adapters/FooAdapter.ts @@ -0,0 +1,7 @@ +import Adapter from "../Adapter"; + +export default class FooAdapter implements Adapter { + toString(): string { + return "foo"; + } +} diff --git a/test/Resources-ts/Autowire-Override/src/FooBarAutowireOverride.ts b/test/Resources-ts/Autowire-Override/src/FooBarAutowireOverride.ts new file mode 100644 index 0000000..c93e5e8 --- /dev/null +++ b/test/Resources-ts/Autowire-Override/src/FooBarAutowireOverride.ts @@ -0,0 +1,13 @@ +import Adapter from "./Adapter"; + +export default class FooBarAutowireOverride { + constructor(private readonly _adapter: Adapter) {} + + getString(): string { + return this._adapter.toString(); + } + + get adapter() { + return this._adapter + } +} diff --git a/test/node-dependency-injection/lib-ts/Autowire.spec.js b/test/node-dependency-injection/lib-ts/Autowire.spec.js index 99a323f..a6abf08 100644 --- a/test/node-dependency-injection/lib-ts/Autowire.spec.js +++ b/test/node-dependency-injection/lib-ts/Autowire.spec.js @@ -1,5 +1,5 @@ import { describe, it } from 'mocha' -import chai from 'chai' +import chai, { config } from 'chai' import chaiAsPromised from 'chai-as-promised'; chai.use(chaiAsPromised); import path from 'path' @@ -25,6 +25,7 @@ import ImplementsOnePath from '../../Resources-ts/AutowireModulePath/src/Service import ImplementsTwoPath from '../../Resources-ts/AutowireModulePath/src/Service/ImplementsTwo' import PathExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/ExcludedService' import PathInFolderExcludedService from '../../Resources-ts/AutowireModulePath/src/ToExclude/InFolderExclude/InFolderExcludedService' +import FooBarAutowireOverride from '../../Resources-ts/Autowire-Override/src/FooBarAutowireOverride' import ServiceFile from '../../../lib/ServiceFile'; import RootDirectoryNotFound from '../../../lib/Exception/RootDirectoryNotFound'; @@ -36,6 +37,50 @@ describe('AutowireTS', () => { const excludedServiceMessage = 'The service ExcludedService is not registered' const inFolderExcludedMessage = 'The service InFolderExcludedService is not registered' + it("should not override single class with autowiring if not exists", async () => { + const configFile = path.join( + __dirname, + '..', + '..', + resourcesTsFolder, + 'Autowire-Override', + 'config', + 'services-not-exists.yaml' + ) + const cb = new ContainerBuilder() + const loader = new YamlFileLoader(cb) + await loader.load(configFile) + await cb.compile() + + // Act. + const actual = cb.get(FooBarAutowireOverride) + + // Assert. + assert.isUndefined(actual.adapter) + }); + + it("should override single class with autowiring", async () => { + const configFile = path.join( + __dirname, + '..', + '..', + resourcesTsFolder, + 'Autowire-Override', + 'config', + 'services.yaml' + ) + const cb = new ContainerBuilder() + const loader = new YamlFileLoader(cb) + await loader.load(configFile) + await cb.compile() + + // Act. + const actual = cb.get(FooBarAutowireOverride) + + // Assert. + assert.equal(actual.getString(), "ci") + }); + it('should get service file when was properly set', () => { // Arrange. const dir = path.join(__dirname, '..', '..', resourcesTsFolder, 'Autowire', 'src')