custom-ability
Advanced tools
Comparing version 2.0.0-alpha.1 to 2.0.0-alpha.2
@@ -15,40 +15,55 @@ "use strict"; | ||
const injected_on_parent_1 = __importDefault(require("./injected-on-parent")); | ||
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; | ||
const skipStaticNames = ['name', 'arguments', 'prototype', 'super_', '__super__', '__proto__']; | ||
const skipProtoNames = ['constructor', '__proto__']; | ||
/** | ||
* Inject methods from NonEnum members of the aObject | ||
* Inject non-enumerable members of the aObject into aTargetClass | ||
* | ||
* @internal | ||
* @param aTargetClass the target class | ||
* @param aObject the NonEnum methods of the object will be injected into aTargetClass | ||
* @param filter | ||
* @param isStatic Whether the injected methods on the aObject is static | ||
* @returns already injected method name list | ||
* @param aObject the non-enumerable members of the object will be injected into aTargetClass | ||
* @param filter It'll be injected only when filter callback function return true, if exists | ||
* @param isStatic Whether is static members | ||
* @returns already injected members name list | ||
*/ | ||
function injectMethodsFromNonEnum(aTargetClass, aObject, filter, isStatic) { | ||
function injectMembersFromNonEnum(aTargetClass, aObject, filter, isStatic) { | ||
const nonEnumNames = (0, get_non_enumerable_names_1.getNonEnumerableNames)(aObject); | ||
const result = []; | ||
nonEnumNames.forEach(function (name) { | ||
let v, vName; | ||
if ((isStatic || name !== 'constructor') && (0, function_1.default)(v = aObject[name])) { | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
const vSkipNames = isStatic ? skipStaticNames : skipProtoNames; | ||
if (vSkipNames.includes(name)) { | ||
return; | ||
} | ||
const desc = getOwnPropertyDescriptor(aObject, name); | ||
const v = desc.value; | ||
const isFn = (0, function_1.default)(v); | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
} | ||
const vName = isStatic ? '@' + name : name; | ||
if (filter && !filter(vName)) { | ||
return; | ||
} | ||
if (desc.get === undefined && desc.set === undefined && v === undefined) { | ||
return; | ||
} | ||
if (!desc.get && isFn) { | ||
if ((0, function_1.default)(aTargetClass[name])) { | ||
(0, injectMethod_1.default)(aTargetClass, name, v); | ||
result.push(vName); | ||
return; | ||
} | ||
vName = isStatic ? '@' + name : name; | ||
if (!filter || filter(vName)) { | ||
if ((0, function_1.default)(aTargetClass[name])) { | ||
(0, injectMethod_1.default)(aTargetClass, name, v); | ||
else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} | ||
else { | ||
if (is$ && aObject[name]) { | ||
desc.value = aObject[name]; | ||
} | ||
else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} | ||
else { | ||
if (is$ && aObject[name]) { | ||
v = aObject[name]; | ||
} | ||
aTargetClass[name] = v; | ||
} | ||
result.push(name); | ||
} | ||
} | ||
(0, defineProperty_1.default)(aTargetClass, name, undefined, desc); | ||
result.push(name); | ||
}); | ||
@@ -111,3 +126,3 @@ return result; | ||
} | ||
vExcludes = injectMethodsFromNonEnum(aClass, AbilityClass, null, true); | ||
vExcludes = injectMembersFromNonEnum(aClass, AbilityClass, null, true); | ||
(0, extend_1.default)(aClass, AbilityClass, function (k) { | ||
@@ -120,3 +135,3 @@ return !(vExcludes.indexOf(k) >= 0); | ||
if (!vInjectedOnParent) { | ||
vExcludes = injectMethodsFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
vExcludes = injectMembersFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
(0, extend_1.default)(aClassPrototype, AbilityClass.prototype, function (k) { | ||
@@ -159,4 +174,4 @@ return !(vExcludes.indexOf(k) >= 0); | ||
}; | ||
let vAbilities = injectMethodsFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMethodsFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
let vAbilities = injectMembersFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMembersFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
vExcludes = vExcludes.concat(vAbilities); | ||
@@ -163,0 +178,0 @@ vAbilities = undefined; |
@@ -9,40 +9,55 @@ import isArray from 'util-ex/lib/is/type/array'; | ||
import isInjectedOnParent from './injected-on-parent'; | ||
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; | ||
const skipStaticNames = ['name', 'arguments', 'prototype', 'super_', '__super__', '__proto__']; | ||
const skipProtoNames = ['constructor', '__proto__']; | ||
/** | ||
* Inject methods from NonEnum members of the aObject | ||
* Inject non-enumerable members of the aObject into aTargetClass | ||
* | ||
* @internal | ||
* @param aTargetClass the target class | ||
* @param aObject the NonEnum methods of the object will be injected into aTargetClass | ||
* @param filter | ||
* @param isStatic Whether the injected methods on the aObject is static | ||
* @returns already injected method name list | ||
* @param aObject the non-enumerable members of the object will be injected into aTargetClass | ||
* @param filter It'll be injected only when filter callback function return true, if exists | ||
* @param isStatic Whether is static members | ||
* @returns already injected members name list | ||
*/ | ||
function injectMethodsFromNonEnum(aTargetClass, aObject, filter, isStatic) { | ||
function injectMembersFromNonEnum(aTargetClass, aObject, filter, isStatic) { | ||
const nonEnumNames = getNonEnumNames(aObject); | ||
const result = []; | ||
nonEnumNames.forEach(function (name) { | ||
let v, vName; | ||
if ((isStatic || name !== 'constructor') && isFunction(v = aObject[name])) { | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
const vSkipNames = isStatic ? skipStaticNames : skipProtoNames; | ||
if (vSkipNames.includes(name)) { | ||
return; | ||
} | ||
const desc = getOwnPropertyDescriptor(aObject, name); | ||
const v = desc.value; | ||
const isFn = isFunction(v); | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
} | ||
const vName = isStatic ? '@' + name : name; | ||
if (filter && !filter(vName)) { | ||
return; | ||
} | ||
if (desc.get === undefined && desc.set === undefined && v === undefined) { | ||
return; | ||
} | ||
if (!desc.get && isFn) { | ||
if (isFunction(aTargetClass[name])) { | ||
injectMethod(aTargetClass, name, v); | ||
result.push(vName); | ||
return; | ||
} | ||
vName = isStatic ? '@' + name : name; | ||
if (!filter || filter(vName)) { | ||
if (isFunction(aTargetClass[name])) { | ||
injectMethod(aTargetClass, name, v); | ||
else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} | ||
else { | ||
if (is$ && aObject[name]) { | ||
desc.value = aObject[name]; | ||
} | ||
else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} | ||
else { | ||
if (is$ && aObject[name]) { | ||
v = aObject[name]; | ||
} | ||
aTargetClass[name] = v; | ||
} | ||
result.push(name); | ||
} | ||
} | ||
defineProperty(aTargetClass, name, undefined, desc); | ||
result.push(name); | ||
}); | ||
@@ -105,3 +120,3 @@ return result; | ||
} | ||
vExcludes = injectMethodsFromNonEnum(aClass, AbilityClass, null, true); | ||
vExcludes = injectMembersFromNonEnum(aClass, AbilityClass, null, true); | ||
extendFilter(aClass, AbilityClass, function (k) { | ||
@@ -114,3 +129,3 @@ return !(vExcludes.indexOf(k) >= 0); | ||
if (!vInjectedOnParent) { | ||
vExcludes = injectMethodsFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
vExcludes = injectMembersFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
extendFilter(aClassPrototype, AbilityClass.prototype, function (k) { | ||
@@ -153,4 +168,4 @@ return !(vExcludes.indexOf(k) >= 0); | ||
}; | ||
let vAbilities = injectMethodsFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMethodsFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
let vAbilities = injectMembersFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMembersFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
vExcludes = vExcludes.concat(vAbilities); | ||
@@ -157,0 +172,0 @@ vAbilities = undefined; |
{ | ||
"name": "custom-ability", | ||
"version": "2.0.0-alpha.1", | ||
"version": "2.0.0-alpha.2", | ||
"description": "make custom ability more easy. generate the ability which can be added to any class directly.", | ||
@@ -39,14 +39,14 @@ "homepage": "https://github.com/snowyu/custom-ability.js", | ||
"dependencies": { | ||
"inherits-ex": "^2.1.0-alpha.5", | ||
"util-ex": "^2.0.0-alpha.3" | ||
"inherits-ex": "^2.1.0-alpha.9", | ||
"util-ex": "^2.0.0-alpha.8" | ||
}, | ||
"devDependencies": { | ||
"@antfu/eslint-config": "^0.38.4", | ||
"@antfu/eslint-config": "^0.38.5", | ||
"@types/chai": "^4.3.4", | ||
"@types/mocha": "^10.0.1", | ||
"@types/node": "^18.15.11", | ||
"@types/sinon": "^10.0.13", | ||
"@types/sinon": "^10.0.14", | ||
"@types/sinon-chai": "^3.2.9", | ||
"chai": "~4.3.7", | ||
"eslint": "^8.37.0", | ||
"eslint": "^8.38.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
@@ -58,4 +58,4 @@ "eslint-plugin-tsdoc": "^0.2.17", | ||
"ts-node": "^10.9.1", | ||
"typedoc": "^0.23.28", | ||
"typedoc-plugin-markdown": "^3.14.0", | ||
"typedoc": "^0.24.4", | ||
"typedoc-plugin-markdown": "^3.15.1", | ||
"typescript": "^5.0.4" | ||
@@ -62,0 +62,0 @@ }, |
@@ -25,2 +25,41 @@ # custom-ability [![Build Status](https://img.shields.io/travis/snowyu/custom-ability.js/master.png)](http://travis-ci.org/snowyu/custom-ability.js) [![npm](https://img.shields.io/npm/v/custom-ability.svg)](https://npmjs.org/package/custom-ability) [![downloads](https://img.shields.io/npm/dm/custom-ability.svg)](https://npmjs.org/package/custom-ability) [![license](https://img.shields.io/npm/l/custom-ability.svg)](https://npmjs.org/package/custom-ability) | ||
**Note**: The all non-enumerable members on the Ability class will be injected into the target class. | ||
**Replace Exists Methods** | ||
if you wanna “replace” and call the methods that already exist in a class, you can add the same method name prefixed with "`$`" on the ability class, add call the original method in this way: | ||
```javascript | ||
const makeAbility = require('custom-ability') | ||
class Feature { | ||
// the same method name prefixed with "`$`" | ||
$init() { | ||
// the original method in target class | ||
const Super = this.super | ||
const that = this.self || this | ||
if (Super) { | ||
if (Super.apply(that, arguments) === 'ok') return | ||
} | ||
that._init.apply(that, arguments) | ||
} | ||
_init() {console.log('feature init')} | ||
} | ||
// if the target class has no the init method, it(the enumerable method) will be injected | ||
Feature.prototype.init = function() {this._init.apply(this, arguments)} | ||
const addFeatureTo = makeAbility(Feature) | ||
class My { | ||
} | ||
addFeatureTo(My) | ||
expect(My.prototype.init).toStrictEqual(Feature.prototype.init) | ||
class My2 { | ||
init() {console.log('My2 init')} | ||
} | ||
addFeatureTo(My2) | ||
expect(My2.prototype.init).toStrictEqual(Feature.prototype.$init) | ||
``` | ||
## Examples | ||
@@ -160,5 +199,2 @@ | ||
**TODO: need to more explain:** | ||
The original `eventable('events-ex/eventable')` is no useful for AbstractObject. | ||
But we wanna the original `eventable('events-ex/eventable')` knows the changes | ||
@@ -165,0 +201,0 @@ and use it automatically. |
@@ -10,39 +10,51 @@ import isArray from 'util-ex/lib/is/type/array'; | ||
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor | ||
const skipStaticNames = ['name', 'arguments', 'prototype', 'super_', '__super__', '__proto__'] | ||
const skipProtoNames = ['constructor', '__proto__'] | ||
/** | ||
* Inject methods from NonEnum members of the aObject | ||
* Inject non-enumerable members of the aObject into aTargetClass | ||
* | ||
* @internal | ||
* @param aTargetClass the target class | ||
* @param aObject the NonEnum methods of the object will be injected into aTargetClass | ||
* @param filter | ||
* @param isStatic Whether the injected methods on the aObject is static | ||
* @returns already injected method name list | ||
* @param aObject the non-enumerable members of the object will be injected into aTargetClass | ||
* @param filter It'll be injected only when filter callback function return true, if exists | ||
* @param isStatic Whether is static members | ||
* @returns already injected members name list | ||
*/ | ||
function injectMethodsFromNonEnum(aTargetClass, aObject, filter?: (name:string)=>boolean, isStatic?: boolean) { | ||
function injectMembersFromNonEnum(aTargetClass, aObject, filter?: (name:string)=>boolean, isStatic?: boolean) { | ||
const nonEnumNames = getNonEnumNames(aObject); | ||
const result = []; | ||
nonEnumNames.forEach(function(name: string) { | ||
let v, vName: string; | ||
if ((isStatic || name !== 'constructor') && isFunction(v = aObject[name])) { | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
} | ||
vName = isStatic ? '@' + name : name; | ||
if (!filter || filter(vName)) { | ||
if (isFunction(aTargetClass[name])) { | ||
injectMethod(aTargetClass, name, v); | ||
} else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} else { | ||
if (is$ && aObject[name]) { | ||
v = aObject[name]; | ||
} | ||
aTargetClass[name] = v; | ||
const vSkipNames = isStatic ? skipStaticNames : skipProtoNames | ||
if (vSkipNames.includes(name)) {return} | ||
const desc = getOwnPropertyDescriptor(aObject, name) | ||
const v = desc.value | ||
const isFn = isFunction(v) | ||
const is$ = name[0] === '$'; | ||
// get rid of the first char '$' | ||
if (is$) { | ||
name = name.substring(1); | ||
} | ||
const vName = isStatic ? '@' + name : name; | ||
if (filter && !filter(vName)) {return} | ||
if (desc.get === undefined && desc.set === undefined && v === undefined) {return} | ||
if (!desc.get && isFn) { | ||
if (isFunction(aTargetClass[name])) { | ||
injectMethod(aTargetClass, name, v); | ||
result.push(vName); | ||
return; | ||
} else if (aTargetClass[name] != null) { | ||
throw new TypeError('the same non-null name is not function:' + name); | ||
} else { | ||
if (is$ && aObject[name]) { | ||
desc.value = aObject[name]; | ||
} | ||
result.push(name); | ||
} | ||
} | ||
}); | ||
defineProperty(aTargetClass, name, undefined, desc) | ||
result.push(name); | ||
}); | ||
return result; | ||
@@ -151,3 +163,3 @@ }; | ||
} | ||
vExcludes = injectMethodsFromNonEnum(aClass, AbilityClass, null, true); | ||
vExcludes = injectMembersFromNonEnum(aClass, AbilityClass, null, true); | ||
extendFilter(aClass, AbilityClass, function(k) { | ||
@@ -160,3 +172,3 @@ return !(vExcludes.indexOf(k) >= 0); | ||
if (!vInjectedOnParent) { | ||
vExcludes = injectMethodsFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
vExcludes = injectMembersFromNonEnum(aClassPrototype, AbilityClass.prototype); | ||
extendFilter(aClassPrototype, AbilityClass.prototype, function(k) { | ||
@@ -197,4 +209,4 @@ return !(vExcludes.indexOf(k) >= 0); | ||
let vAbilities = injectMethodsFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMethodsFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
let vAbilities = injectMembersFromNonEnum(aClass, AbilityClass, vGenFilter(true), true); | ||
vAbilities = vAbilities.concat(injectMembersFromNonEnum(aClass.prototype, AbilityClass.prototype, vGenFilter())); | ||
vExcludes = vExcludes.concat(vAbilities); | ||
@@ -201,0 +213,0 @@ vAbilities = undefined; |
@@ -1,2 +0,2 @@ | ||
import chai from 'chai'; | ||
import chai, { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
@@ -85,2 +85,31 @@ import sinonChai from 'sinon-chai'; | ||
}); | ||
it('should ignore getter attribute', function() { | ||
class MyFeature { | ||
static additionalClassMethod: () => void; | ||
static coreAbilityClassMethod(){}; | ||
static get getter(){return 1} | ||
static field = 1 | ||
get getter(){return 1} | ||
coreAbilityMethod(){}; | ||
additionalAbilityMethod(){}; | ||
} | ||
MyFeature.additionalClassMethod = function() {} | ||
const addFeatureTo = customAbility(MyFeature, ['coreAbilityMethod', '@coreAbilityClassMethod']); | ||
class MyClass { | ||
someMethod() {} | ||
} | ||
// inject the static and instance methods to the MyClass. | ||
addFeatureTo(MyClass); | ||
MyClass.should.have.ownProperty('coreAbilityClassMethod') | ||
let prop = Object.getOwnPropertyDescriptor(MyFeature, 'getter') | ||
prop!.get!.should.be.a('function'); | ||
Object.getOwnPropertyDescriptor(MyClass, 'getter')!.should.have.ownProperty('get', prop!.get) | ||
prop = Object.getOwnPropertyDescriptor(MyFeature.prototype, 'getter') | ||
Object.getOwnPropertyDescriptor(MyClass.prototype, 'getter')!.should.have.ownProperty('get', prop!.get) | ||
prop = Object.getOwnPropertyDescriptor(MyFeature, 'field') | ||
Object.getOwnPropertyDescriptor(MyClass, 'field')!.should.have.ownProperty('value', 1) | ||
}); | ||
it('could use getAbilityClass', function() { | ||
@@ -87,0 +116,0 @@ var My, getAbilityClass, result, testable1; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
118977
2714
575
Updatedinherits-ex@^2.1.0-alpha.9
Updatedutil-ex@^2.0.0-alpha.8