feature(@jsdoc/core): add simple inversion of control (IoC) tool

Currently unused. Intended to be used for JSDoc core dependencies that must be available everywhere, such as the config and the event bus.
This commit is contained in:
Jeff Williams 2021-09-26 09:54:42 -07:00
parent 470d3c801e
commit b850fa14b9
6 changed files with 801 additions and 465 deletions

View File

@ -5,9 +5,11 @@
*/
const config = require('./lib/config');
const dependencies = require('./lib/dependencies');
const name = require('./lib/name');
module.exports = {
config,
dependencies,
name,
};

View File

@ -0,0 +1,42 @@
const yaioc = require('yaioc');
let dependencies;
/**
* Container for JSDoc classes, objects, and values that can be injected into other modules.
*
* @alias module:@jsdoc/core.deps
*/
class Dependencies {
constructor() {
// This class provides a lightweight facade for the `yaioc` package.
this._container = yaioc.container();
}
get(name) {
const dep = this._container.get(name);
if (dep === undefined) {
throw new Error(`No dependency registered for the name "${name}"`);
}
return dep;
}
registerClass(klass, opts = {}) {
if (opts.singleton) {
this._container.cache().register(klass);
} else {
this._container.register(klass);
}
}
registerValue(name, value) {
this._container.register(name, value);
}
}
dependencies = new Dependencies();
dependencies.Dependencies = Dependencies;
module.exports = dependencies;

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,8 @@
"escape-string-regexp": "^4.0.0",
"lodash": "^4.17.21",
"strip-bom": "^4.0.0",
"strip-json-comments": "^3.1.1"
"strip-json-comments": "^3.1.1",
"yaioc": "^1.10.0"
},
"engines": {
"node": ">=v14.17.6"

View File

@ -13,6 +13,14 @@ describe('@jsdoc/core', () => {
});
});
describe('dependencies', () => {
it('is lib/dependencies', () => {
const dependencies = require('../../lib/dependencies');
expect(core.dependencies).toBe(dependencies);
});
});
describe('name', () => {
it('is lib/name', () => {
const name = require('../../lib/name');

View File

@ -0,0 +1,100 @@
const dependencies = require('../../../lib/dependencies');
describe('@jsdoc/core/lib/dependencies', () => {
let container;
beforeEach(() => {
container = new dependencies.Dependencies();
});
it('is an object', () => {
expect(dependencies).toBeObject();
});
describe('Dependencies', () => {
it('is the constructor of the dependencies object', () => {
expect(dependencies.Dependencies).toBe(dependencies.constructor);
});
});
describe('get', () => {
it('throws an error if the name is missing', () => {
expect(() => container.get()).toThrowError();
});
it('throws an error if the name is unrecognized', () => {
expect(() => container.get('foo')).toThrowError();
});
it('returns an instance of classes', () => {
class Foo {}
let instance;
container.registerClass(Foo);
instance = container.get('foo');
expect(instance).toBeInstanceOf(Foo);
});
it('passes dependencies to instance constructors', () => {
class Foo {
constructor(bar) {
this.bar = bar;
}
}
class Bar {}
let instance;
container.registerClass(Foo);
container.registerClass(Bar);
instance = container.get('foo');
expect(instance.bar).toBeInstanceOf(Bar);
});
it('returns the same instance every time for singletons', () => {
class Foo {}
let instance1;
let instance2;
container.registerClass(Foo, { singleton: true });
instance1 = container.get('foo');
instance2 = container.get('foo');
expect(instance2).toBe(instance1);
});
it('returns static values', () => {
const value = new Set();
container.registerValue('foo', value);
expect(container.get('foo')).toBe(value);
});
});
describe('registerClass', () => {
// The tests for `get()` also test the behavior of these methods more extensively.
it('accepts a constructor', () => {
class Foo {}
expect(() => container.registerClass(Foo)).not.toThrow();
});
it('accepts a `singleton` option', () => {
class Foo {}
expect(() => container.registerClass(Foo, { singleton: true })).not.toThrow();
});
});
describe('registerValue', () => {
it('accepts a name and value', () => {
expect(() => container.registerValue('name', new Set())).not.toThrow();
});
});
});