mirror of
https://github.com/FullScreenShenanigans/BabyIoC.git
synced 2026-08-12 11:18:25 -07:00
Initial commit
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
dist/
|
||||
docs/generated/
|
||||
test/
|
||||
node_modules/
|
||||
*.css
|
||||
*.d.ts
|
||||
*.js*
|
||||
!./*.js
|
||||
!*.json
|
||||
*.html
|
||||
npm-debug.log
|
||||
debug.log
|
||||
|
||||
# Added by shenanigans-manager for maps testing
|
||||
Maps.test.ts
|
||||
|
||||
# Local development typically uses npm install --link
|
||||
# Package lock files aren't updated by linked installs
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
test/
|
||||
*.test.*
|
||||
npm-debug.log
|
||||
@@ -0,0 +1,8 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "node"
|
||||
- "7"
|
||||
|
||||
script:
|
||||
npm run setup && npm run verify
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.tabSize": 4,
|
||||
"editor.trimAutoWhitespace": true,
|
||||
"tslint.alwaysShowRuleFailuresAsWarnings": true,
|
||||
"tslint.autoFixOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
'Software'), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,148 @@
|
||||
<!-- {{Top}} -->
|
||||
# BabyIoC
|
||||
[](https://greenkeeper.io/)
|
||||
[](https://travis-ci.org/FullScreenShenanigans/BabyIoC)
|
||||
[](http://badge.fury.io/js/babyioc)
|
||||
|
||||
Infantile IoC decorator with almost no features.
|
||||
<!-- {{/Top}} -->
|
||||
|
||||
BabyIoC is the smallest IoC container you'll ever see _(about 100 real lines of code!)_.
|
||||
It's also got the fewest toys - it's only targeted for use by [GameStartr](https://github.com/FullScreenShenanigans/GameStartr).
|
||||
|
||||
Key tenants:
|
||||
* Use TypeScript.
|
||||
* All `@components` are members of the parent `@container` container class instance.
|
||||
* Components and component `@dependency`s are stored as lazily evaluated getters: circular dependencies are fine!
|
||||
|
||||
## Usage
|
||||
|
||||
Each **@component** is a member of your root **@container** class.
|
||||
Declare your components with their classes to have them automagically created as members of your class.
|
||||
|
||||
```typescript
|
||||
import { component, container } from "babyioc";
|
||||
|
||||
class DependencyA { }
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
const { dependencyA } = new Container();
|
||||
```
|
||||
|
||||
Components can take each other in as **`@dependency`s.
|
||||
|
||||
```typescript
|
||||
class DependencyA { }
|
||||
class DependencyB {
|
||||
@dependency(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
|
||||
@component(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
```
|
||||
|
||||
### Factories
|
||||
|
||||
Your components don't have to be direct classes with dependencies.
|
||||
Pass functions that take in your container as an argument.
|
||||
|
||||
If you're using anonymous `() =>` arrow lambdas, because they don't have names, pass in the class name or a string identifier to store them under internally.
|
||||
|
||||
```typescript
|
||||
class DependencyA {
|
||||
public constructor(
|
||||
public readonly member: string,
|
||||
) { }
|
||||
}
|
||||
const memberValue = "memberValue";
|
||||
const createDependencyA = () => new DependencyA(memberValue);
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(createDependencyA, DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
const { dependencyA } = new Container();
|
||||
```
|
||||
|
||||
These factory functions have access to all the values on the container, including computed getters.
|
||||
|
||||
```typescript
|
||||
class DependencyA {
|
||||
public constructor(
|
||||
public readonly memberA: string,
|
||||
) { }
|
||||
}
|
||||
class DependencyB {
|
||||
public constructor(
|
||||
public readonly referenceA: DependencyA,
|
||||
public readonly valueC: string,
|
||||
) { }
|
||||
}
|
||||
const memberValueA = "memberValueA";
|
||||
const createDependencyA = () => new DependencyA(memberValueA);
|
||||
const createDependencyB = (instance: Container) => new DependencyB(dependencyA, container.valueC);
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(createDependencyA, DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
|
||||
@component(createDependencyB, DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
|
||||
public readonly valueC = "valueC";
|
||||
}
|
||||
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
```
|
||||
|
||||
<!-- {{Development}} -->
|
||||
## Development
|
||||
|
||||
```
|
||||
git clone https://github.com/FullScreenShenanigans/BabyIoC
|
||||
cd BabyIoC
|
||||
npm run setup
|
||||
npm run verify
|
||||
```
|
||||
|
||||
* `npm run setup` creates a few auto-generated setup files locally.
|
||||
* `npm run verify` builds, lints, and runs tests.
|
||||
|
||||
### Building
|
||||
|
||||
```shell
|
||||
npm run watch
|
||||
```
|
||||
|
||||
Source files are written under `src/` in TypeScript and compile in-place to JavaScript files.
|
||||
`npm run watch` will directly run the TypeScript compiler on source files in watch mode.
|
||||
Use it in the background while developing to keep the compiled files up-to-date.
|
||||
|
||||
### Running Tests
|
||||
|
||||
```shell
|
||||
npm run test
|
||||
```
|
||||
|
||||
Test files are alongside source files under `src/` and named `*.test.ts?`.
|
||||
Whenever you add, remove, or rename a `*.test.ts?` file under `src/`, re-run `npm run test:setup` to regenerate the list of static test files in `test/index.html`.
|
||||
You can open that file in a browser to debug through the tests.
|
||||
`npm run test` will run that setup and execute tests using [Puppeteer](https://github.com/GoogleChrome/puppeteer).
|
||||
<!-- {{/Development}} -->
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"author": {
|
||||
"email": "joshuakgoldberg@outlook.com",
|
||||
"name": "Josh Goldberg"
|
||||
},
|
||||
"browser": "./src/index.js",
|
||||
"bugs": {
|
||||
"url": "https://github.com/FullScreenShenanigans/BabyIoC/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"description": "Infantile IoC decorator with almost no features.",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.0.4",
|
||||
"@types/lolex": "^2.1.1",
|
||||
"@types/mocha": "^2.2.44",
|
||||
"@types/sinon": "^4.0.0",
|
||||
"@types/sinon-chai": "^2.7.29",
|
||||
"chai": "^4.1.2",
|
||||
"glob": "^7.1.2",
|
||||
"lolex": "^2.3.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mocha": "^4.0.1",
|
||||
"mocha-headless-chrome": "^1.7.1",
|
||||
"requirejs": "^2.3.5",
|
||||
"run-for-every-file": "^1.1.0",
|
||||
"shenanigans-manager": "^0.2.7",
|
||||
"sinon": "^4.1.2",
|
||||
"sinon-chai": "^2.14.0",
|
||||
"tslint": "5.8.0",
|
||||
"tsutils": "^2.14.0",
|
||||
"typedoc": "^0.9.0",
|
||||
"typescript": "^2.6.2",
|
||||
"webpack": "^3.10.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"name": "babyioc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@github.com:FullScreenShenanigans/BabyIoC.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dist": "npm run dist:webpack",
|
||||
"dist:webpack": "webpack",
|
||||
"docs": "npm run docs:typedoc",
|
||||
"docs:typedoc": "typedoc src/ --exclude **/*.d.ts --ignoreCompilerErrors --out docs/generated",
|
||||
"init": "npm install && npm run setup && npm run verify",
|
||||
"setup": "npm run setup:copy && npm run setup:package && npm run setup:readme",
|
||||
"setup:copy": "npm run setup:copy:default",
|
||||
"setup:copy:default": "run-for-every-file --dot --src \"node_modules/shenanigans-manager/setup/default/\" --file \"**/*\" --run \"mustache package.json {{src-file}} {{file}}\" --dest \".\" --only-files",
|
||||
"setup:package": "shenanigans-manager hydrate-package-json",
|
||||
"setup:readme": "shenanigans-manager hydrate-readme",
|
||||
"src": "npm run src:tsc && npm run src:tslint",
|
||||
"src:tsc": "tsc -p .",
|
||||
"src:tslint": "tslint -c tslint.json -e ./node_modules/**/*.ts* -p tsconfig.json -t stylish",
|
||||
"test": "npm run test:setup && npm run test:run",
|
||||
"test:run": "mocha-headless-chrome --file test/index.html",
|
||||
"test:setup": "npm run test:setup:dir && npm run test:setup:copy && npm run test:setup:html && npm run test:setup:tsc",
|
||||
"test:setup:copy": "npm run test:setup:copy:default",
|
||||
"test:setup:copy:default": "run-for-every-file --dot --src \"node_modules/shenanigans-manager/setup/test/\" --file \"**/*\" --run \"mustache package.json {{src-file}} ./test/{{file}}\" --dest \".\" --only-files",
|
||||
"test:setup:dir": "mkdirp test",
|
||||
"test:setup:html": "shenanigans-manager generate-test-html",
|
||||
"test:setup:tsc": "tsc -p test",
|
||||
"verify": "npm run src && npm run test && npm run dist && npm run docs",
|
||||
"watch": "tsc -p . -w"
|
||||
},
|
||||
"shenanigans": {
|
||||
"name": "BabyIoC"
|
||||
},
|
||||
"types": "./src/index.d.ts",
|
||||
"version": "0.7.0"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { getFunctionName } from "./Reading";
|
||||
|
||||
export type IContainerClass = IClass & {
|
||||
__listings__?: { [i: string]: IComponentListing };
|
||||
};
|
||||
|
||||
export type IClass = IClassWithArgs | IClassWithoutArgs;
|
||||
|
||||
export interface IClassWithArgs {
|
||||
new(...args: any[]): any;
|
||||
}
|
||||
|
||||
export interface IClassWithoutArgs {
|
||||
new(): any;
|
||||
}
|
||||
|
||||
export type IComponentClassOrFunction = IComponentClass | IComponentFunction;
|
||||
|
||||
export interface IComponentClass {
|
||||
new(container: IClass): any;
|
||||
}
|
||||
|
||||
export type IComponentFunction = (container: IClass) => any;
|
||||
|
||||
/**
|
||||
* Describes how to create and store a component.
|
||||
*/
|
||||
export interface IComponentListing {
|
||||
/**
|
||||
* Class or function to create the component.
|
||||
*/
|
||||
componentFunction: IComponentClassOrFunction;
|
||||
|
||||
/**
|
||||
* Unique name for the class or function to create this component.
|
||||
*/
|
||||
listingName: string;
|
||||
|
||||
/**
|
||||
* Member property name the component will be stored under.
|
||||
*/
|
||||
memberName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a member component to a parent container.
|
||||
*
|
||||
* @param componentFunction Class or function that creates the component.
|
||||
* @param name Name to store the component under, if not the class' or function's .name.
|
||||
*/
|
||||
export const component = (componentFunction: any /* IComponentClassOrFunction */, name?: string | Function) => {
|
||||
if (typeof name === "function") {
|
||||
name = getFunctionName(name);
|
||||
}
|
||||
|
||||
const listingName = name === undefined
|
||||
? getFunctionName(componentFunction)
|
||||
: name;
|
||||
|
||||
return (parentClass: any /* IContainerClass */, memberName: string) => {
|
||||
const listing: IComponentListing = { componentFunction, listingName, memberName };
|
||||
|
||||
if (parentClass.__listings__ === undefined) {
|
||||
parentClass.__listings__ = {};
|
||||
}
|
||||
|
||||
parentClass.__listings__[listing.listingName] = listing;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { component } from "./Component";
|
||||
import { container } from "./Container";
|
||||
import { dependency } from "./Dependency";
|
||||
|
||||
describe("container", () => {
|
||||
it("resolves a component dependency", () => {
|
||||
// Arrange
|
||||
class DependencyA { }
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyA).to.be.instanceOf(DependencyA);
|
||||
});
|
||||
|
||||
it("resolves two component dependencies out of alphabetical order", () => {
|
||||
// Arrange
|
||||
class DependencyA { }
|
||||
class DependencyB { }
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
|
||||
@component(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyA).to.be.instanceOf(DependencyA);
|
||||
expect(dependencyB).to.be.instanceOf(DependencyB);
|
||||
});
|
||||
|
||||
it("adds a dependency to a component", () => {
|
||||
// Arrange
|
||||
class DependencyA { }
|
||||
class DependencyB {
|
||||
@dependency(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
|
||||
@component(DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyB.dependencyA).to.be.equal(dependencyA);
|
||||
});
|
||||
|
||||
it("creates a component using a factory", () => {
|
||||
// Arrange
|
||||
class DependencyA {
|
||||
public constructor(
|
||||
public readonly member: string,
|
||||
) { }
|
||||
}
|
||||
const memberValue = "memberValue";
|
||||
const createDependencyA = () => new DependencyA(memberValue);
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(createDependencyA, DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyA.member).to.be.equal(memberValue);
|
||||
});
|
||||
|
||||
it("creates different components using factories and their naming classes", () => {
|
||||
// Arrange
|
||||
class DependencyA {
|
||||
public constructor(
|
||||
public readonly memberA: string,
|
||||
) { }
|
||||
}
|
||||
class DependencyB {
|
||||
public constructor(
|
||||
public readonly memberB: string,
|
||||
) { }
|
||||
}
|
||||
const memberValueA = "memberValueA";
|
||||
const memberValueB = "memberValueB";
|
||||
const createDependencyA = () => new DependencyA(memberValueA);
|
||||
const createDependencyB = () => new DependencyB(memberValueB);
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(createDependencyA, DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
|
||||
@component(createDependencyB, DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyA.memberA).to.be.equal(memberValueA);
|
||||
expect(dependencyB.memberB).to.be.equal(memberValueB);
|
||||
});
|
||||
|
||||
it("passes the container after creating getters to factories", () => {
|
||||
// Arrange
|
||||
class DependencyA {
|
||||
public constructor(
|
||||
public readonly memberA: string,
|
||||
) { }
|
||||
}
|
||||
class DependencyB {
|
||||
public constructor(
|
||||
public readonly referenceA: DependencyA,
|
||||
public readonly valueC: string,
|
||||
) { }
|
||||
}
|
||||
const memberValueA = "memberValueA";
|
||||
const createDependencyA = () => new DependencyA(memberValueA);
|
||||
const createDependencyB = (instance: Container) => new DependencyB(dependencyA, instance.valueC);
|
||||
|
||||
@container
|
||||
class Container {
|
||||
@component(createDependencyA, DependencyA)
|
||||
public readonly dependencyA: DependencyA;
|
||||
|
||||
@component(createDependencyB, DependencyB)
|
||||
public readonly dependencyB: DependencyB;
|
||||
|
||||
public readonly valueC: string;
|
||||
}
|
||||
|
||||
// Act
|
||||
const { dependencyA, dependencyB } = new Container();
|
||||
|
||||
// Assert
|
||||
expect(dependencyA.memberA).to.be.equal(memberValueA);
|
||||
expect(dependencyB.referenceA).to.be.equal(dependencyA);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import { IClassWithArgs, IComponentListing } from "./Component";
|
||||
|
||||
/**
|
||||
* Creates a getter method for a lazily computed instance.
|
||||
*
|
||||
* @param resolve Resolves the value of the instance.
|
||||
* @returns A getter method for a lazilyi computed instance.
|
||||
*/
|
||||
const createLazyInstance = <TInstance>(resolve: () => any) => {
|
||||
let instance: TInstance | undefined;
|
||||
|
||||
return () => {
|
||||
if (instance === undefined) {
|
||||
instance = resolve();
|
||||
}
|
||||
|
||||
return instance;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves the value of a lazily instantiated component.
|
||||
*
|
||||
* @param parentContainerInstance Parent container creating this component.
|
||||
*
|
||||
*/
|
||||
const resolveComponent = (parentContainerInstance: any, listing: IComponentListing): any => {
|
||||
const { componentFunction } = listing;
|
||||
const componentInstance = new (componentFunction as IClassWithArgs)(parentContainerInstance);
|
||||
|
||||
const dependencies = componentFunction.prototype.__dependencies__;
|
||||
if (dependencies !== undefined) {
|
||||
for (const dependency of dependencies) {
|
||||
Object.defineProperty(componentInstance, dependency.memberName, {
|
||||
configurable: true,
|
||||
get: createLazyInstance(() =>
|
||||
parentContainerInstance[parentContainerInstance.__listings__[dependency.dependencyName].memberName]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return componentInstance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a class that creates getters to resolve its components.
|
||||
*/
|
||||
export const container = (containerClass: { new(...args: any[]): any }): any => {
|
||||
const createdComponents: any = {};
|
||||
|
||||
return class extends containerClass {
|
||||
public constructor(...args: any[]) {
|
||||
super(...args);
|
||||
|
||||
const listings = containerClass.prototype.__listings__;
|
||||
for (const listingName in listings) {
|
||||
if (!{}.hasOwnProperty.call(listings, listingName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const listing = listings[listingName];
|
||||
|
||||
Object.defineProperty(this, listing.memberName, {
|
||||
configurable: true,
|
||||
get: createLazyInstance(() => resolveComponent(this, listing)),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { getFunctionName } from "./Reading";
|
||||
|
||||
/**
|
||||
* Describes a dependency of a class.
|
||||
*/
|
||||
export interface IDependencyListing {
|
||||
/**
|
||||
* Class or function name of the dependency.
|
||||
*/
|
||||
dependencyName: string;
|
||||
|
||||
/**
|
||||
* Member property name the instance will be stored under.
|
||||
*/
|
||||
memberName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dependency to a component class.
|
||||
*
|
||||
* @param componentFunction Class or function creates the component, unique string name thereof.
|
||||
*/
|
||||
export const dependency = (dependencyName: string | Function): any => {
|
||||
if (typeof dependencyName === "function") {
|
||||
dependencyName = getFunctionName(dependencyName);
|
||||
}
|
||||
|
||||
return (dependingClass: any, memberName: string) => {
|
||||
const listing = { dependencyName, memberName };
|
||||
|
||||
if (dependingClass.__dependencies__) {
|
||||
dependingClass.__dependencies__.push(listing);
|
||||
} else {
|
||||
dependingClass.__dependencies__ = [listing];
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { getFunctionName } from "./Reading";
|
||||
|
||||
describe("Reading", () => {
|
||||
describe("getFunctionName", () => {
|
||||
it("returns the name of a function when there is a .name", () => {
|
||||
// Arrange
|
||||
const name = "test";
|
||||
const object = { name };
|
||||
|
||||
// Act
|
||||
const result = getFunctionName(object);
|
||||
|
||||
// Assert
|
||||
expect(result).to.be.equal(name);
|
||||
});
|
||||
|
||||
it("returns the name of a function when there is no .name and .toString results in a function", () => {
|
||||
// Arrange
|
||||
const name = "test";
|
||||
const object = {
|
||||
toString() {
|
||||
return `function ${name}() { /* ... */ }`;
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = getFunctionName(object as any);
|
||||
|
||||
// Assert
|
||||
expect(result).to.be.equal(name);
|
||||
});
|
||||
|
||||
it("returns the name of a function when there is no .name and .toString results in a class", () => {
|
||||
// Arrange
|
||||
const name = "test";
|
||||
const object = {
|
||||
toString() {
|
||||
return `class ${name} { /* ... */ }`;
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = getFunctionName(object as any);
|
||||
|
||||
// Assert
|
||||
expect(result).to.be.equal(name);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Object with a name.
|
||||
*/
|
||||
export interface IObjectWithName {
|
||||
/**
|
||||
* Name of the object.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the friendly name of a class or function.
|
||||
*
|
||||
* @param method Class or function which may have a name.
|
||||
* @returns Friendly name of the class or function.
|
||||
*/
|
||||
export const getFunctionName = (method: IObjectWithName | Function): string => {
|
||||
if ((method as IObjectWithName).name !== undefined) {
|
||||
return (method as IObjectWithName).name;
|
||||
}
|
||||
|
||||
const stringified = method.toString();
|
||||
const typeDescriptorMatch = stringified.match(/class|function/)!;
|
||||
const indexOfNameSpace = typeDescriptorMatch.index! + typeDescriptorMatch[0].length;
|
||||
const indexOfNameAfterSpace = stringified.search(/\(|\{/);
|
||||
|
||||
return stringified.substring(indexOfNameSpace, indexOfNameAfterSpace).trim();
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { component } from "./Component";
|
||||
export { container } from "./Container";
|
||||
export { dependency } from "./Dependency";
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "react",
|
||||
"lib": ["dom", "es2015.collection", "es2015.promise", "es5"],
|
||||
"module": "amd",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"pretty": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es5"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./node_modules/shenanigans-manager/setup/tslint.json",
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"./node_modules/**/*"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"no-any": false,
|
||||
"no-non-null-assertion": false,
|
||||
"no-parameter-properties": false,
|
||||
"no-unsafe-any": false,
|
||||
"ban-types": false,
|
||||
"max-classes-per-file": [true, "exclude-class-expressions"]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user