diff --git a/package.json b/package.json index 4039f2a..9c2b9c3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "document": "typedoc --out docs --excludeNotExported --excludePrivate --disableOutputCheck --gitRevision master --exclude \"**/+(as-proto|as-static)/**/*ts\"", "prepare-github-pages": "npm run document && echo \"\" > ./docs/.nojekyll", "test": "nyc mocha --recursive --compilers ts:ts-node/register src/*.spec.ts src/**/*.spec.ts", - "cover": "nyc report --reporter=text-lcov | coveralls" + "cover": "nyc report --reporter=text-lcov | codecov" }, "author": "alisahinozcelik@gmail.com", "license": "MIT", @@ -43,7 +43,7 @@ "@types/chai": "^4.1.6", "@types/mocha": "^5.2.5", "chai": "^4.2.0", - "coveralls": "^3.0.2", + "codecov.io": "^0.1.6", "mocha": "^5.2.0", "nyc": "^13.1.0", "ts-node": "^7.0.1", diff --git a/src/smart-map.spec.ts b/src/smart-map.spec.ts new file mode 100644 index 0000000..01ca950 --- /dev/null +++ b/src/smart-map.spec.ts @@ -0,0 +1,82 @@ +import { expect, assert } from 'chai'; +import 'mocha'; + +import { SmartMap } from "./smart-map"; + +describe('SmartMap Class', () => { + let map: SmartMap; + + beforeEach(() => { + map = new SmartMap(); + }); + + it('should construct properly', () => { + const mapWithValues = new SmartMap([['key', 'value'], ['key2', 'value2']]); + + assert.isDefined(map); + assert.isDefined(mapWithValues); + expect([...mapWithValues.entries()]).to.be.eql([...new Map([['key', 'value'], ['key2', 'value2']]).entries()]); + }); + + it('should set a value by primitive type keys', () => { + map.set(1, "number"); + map.set("string", "string"); + map.set(true, "boolean"); + map.set(Symbol(), "symbol"); + map.set(undefined, "undefined"); + + expect(map.size).to.eq(5); + }); + + it('should set a value by object type keys', () => { + const anObj = {}; + + map.set(anObj, "foo"); + expect(map.has(anObj)).to.be.true; + }); + + it('should store object keys in weakmap storage', () => { + const anObj = {}; + + map.set(anObj, "foo"); + + expect(map.size).to.eq(0); + expect(map.has(anObj)).to.be.true; + }); + + it('should check correctly when has method called via both primitive and object keys', () => { + const anObj = {}; + + map.set(1, "1"); + map.set(anObj, "an object"); + + expect(map.has(1)).to.be.true; + expect(map.has(anObj)).to.be.true; + }); + + it('should get value when get method called via both primitive and object keys', () => { + const anObj = {}; + + map.set(1, "1"); + map.set(anObj, "an object"); + + expect(map.get(1)).to.eq("1"); + expect(map.get(anObj)).to.eq("an object"); + }); + + it('should delete value when delete method called via both primitive and object key', () => { + const anObj = {}; + + map.set(1, "1"); + map.set(anObj, "an object"); + + const primitiveResult = map.delete(1); + const objectResult = map.delete(anObj); + + expect(primitiveResult).to.be.true; + expect(objectResult).to.be.true; + + expect(map.size).to.eq(0); + expect(map.has(anObj)).to.be.false; + }) +}); diff --git a/src/smart-map.ts b/src/smart-map.ts new file mode 100644 index 0000000..62d5281 --- /dev/null +++ b/src/smart-map.ts @@ -0,0 +1,113 @@ +// Symbols to protect overriding private methods & properties of SmartMap Class +const KEY__WEAKMAP: unique symbol = Symbol('SmartMap.weakMap'); + +/** + * ### SmartMap + * + * Like WeakMap but can also store values using primitive keys + * + * * * * + * Example usage: + * ```typescript + * import { SmartMap } from "@thalesrc/js-utils"; + * + * const aMap = new SmartMap(); + * + * aMap.set("foo", "foo"); + * aMap.set(1, "thales rocks"); + * + * console.log(aMap.size) // 2 + * + * aMap.set({}, "thales rocks again"); + * console.log(aMap.size) // 2 + * + * const anObject = {}; + * aMap.set(anObject, "thales rocks again and again"); + * console.log(aMap.size) // 3 + * console.log(aMap.get(anObject)) // "thales rocks again and again" + * ``` + * * * * + * @template K Typeof Key + * @template V Typeof Value + */ +export class SmartMap extends Map { + /** + * Symbol refference of weakmap property + */ + protected static readonly KEY__WEAKMAP = KEY__WEAKMAP; + + /** + * Allowed key types to store them in a weakmap instance + */ + public static readonly TYPES_TO_STORE_IN_WEAKMAP = Object.freeze(['object', 'function']); + + /** + * The WeakMap object to store values with associated non-primitive (object) keys + */ + protected [KEY__WEAKMAP] = new WeakMap(); + + /** + * Returns a boolean asserting whether a value has been associated to the key in the SmartMap object or not. + * + * @param key The key of the element to test for presence in the SmartMap object. + * @returns A boolean asserting whether a value has been associated to the key in the SmartMap object or not. + */ + public has(key: K): boolean { + if (this.isStorableInWeakMap(key)) { + return this[KEY__WEAKMAP].has(key); + } + + return super.has(key); + } + + /** + * Returns the value associated to the key, or undefined if there is none. + * + * @param key The key of the element to return from the SmartMap object. + * @returns The value associated to the key + */ + public get(key: K): V { + if (this.isStorableInWeakMap(key)) { + return this[KEY__WEAKMAP].get(key); + } + + return super.get(key); + } + + /** + * Sets the value for the key in the SmartMap object. + * + * @param key The key of the element to add to the SmartMap object. + * @param value The value of the element to add to the SmartMap object. + * @returns Returns the SmartMap object. + */ + public set(key: K, value: V): this { + if (this.isStorableInWeakMap(key)) { + this[KEY__WEAKMAP].set(key, value); + } else { + super.set(key, value); + } + + return this; + } + + /** + * Removes the associated value from the SmartMap object, + * + * @param key The key of the element to remove from the SmartMap object. + * @returns `true` if an element in the SmartMap object existed and has been removed, or `false` if the element does not exist. `SmartMap.prototype.has(key)` will return false afterwards. + */ + public delete(key: K): boolean { + return this[KEY__WEAKMAP].delete(key) || super.delete(key); + } + + /** + * Returns a boolean whether if the key is storable in weakmap or not + * + * @param key Key to check if it can be stored in weakmap + * @returns A boolean whether if the key is storable in weakmap or not + */ + protected isStorableInWeakMap(key: K): boolean { + return SmartMap.TYPES_TO_STORE_IN_WEAKMAP.some(_key => typeof key === _key); + } +} diff --git a/tsconfig.json b/tsconfig.json index bd826f7..ed59b1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "sourceMap": true, - "target": "es5", + "target": "es6", "allowSyntheticDefaultImports": true, "declaration": true, "noResolve": false,