diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/can-reflect-test.js b/can-reflect-test.js index cf5a711..94449af 100644 --- a/can-reflect-test.js +++ b/can-reflect-test.js @@ -3,3 +3,5 @@ require("./reflections/shape/shape-test"); require("./reflections/get-set/get-set-test"); require("./reflections/call/call-test"); require("./reflections/observe/observe-test"); +require("./types/map-test"); +require("./types/set-test"); diff --git a/can-reflect.js b/can-reflect.js index b762e2d..3c27b03 100644 --- a/can-reflect.js +++ b/can-reflect.js @@ -12,4 +12,7 @@ var reflect = {}; } }); +require("./types/map"); +require("./types/set"); + module.exports = namespace.Reflect = reflect; diff --git a/index.html b/index.html deleted file mode 100644 index 6da88bd..0000000 --- a/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - can-reflect - A Plugin for DoneJS - - - -

Created with the

- - DoneJS logo - - plugin generator - - diff --git a/reflections/get-set/get-set.js b/reflections/get-set/get-set.js index 2a9cd3f..00d9365 100644 --- a/reflections/get-set/get-set.js +++ b/reflections/get-set/get-set.js @@ -14,7 +14,7 @@ var reflections = { * * @signature `setKeyValue(obj, key, value)` * - * Set the property on Map-like `obj`, identified by the String or Symbol value `key`, to the value `value`. + * Set the property on Map-like `obj`, identified by the String, Symbol or Object value `key`, to the value `value`. * The default behavior can be overridden on `obj` by implementing [can-symbol/symbols/setKeyValue @@@@can.setKeyValue], * otherwise native named property access is used for string keys, and `Object.defineProperty` is used to set symbols. * @@ -179,12 +179,23 @@ var reflections = { } }, - splice: function(obj, index, howMany, values){ + splice: function(obj, index, removing, adding){ + var howMany; + if(typeof removing !== "number") { + var updateValues = obj[canSymbol.for("can.updateValues")]; + if(updateValues) { + return updateValues.call(obj, index, removing, adding); + } + howMany = removing.length; + } else { + howMany = removing; + } + var splice = obj[canSymbol.for("can.splice")]; if(splice) { - return splice.call(obj, index, howMany, values); + return splice.call(obj, index, howMany, adding); } - return [].splice.apply(obj, [index, howMany].concat(values) ); + return [].splice.apply(obj, [index, howMany].concat(adding) ); } }; /** diff --git a/reflections/shape/shape.js b/reflections/shape/shape.js index ef01969..f94c38d 100644 --- a/reflections/shape/shape.js +++ b/reflections/shape/shape.js @@ -3,6 +3,7 @@ var getSetReflections = require("../get-set/get-set"); var typeReflections = require("../type/type"); var helpers = require("../helpers"); +var shapeReflections; var shiftFirstArgumentToThis = function(func){ return function(){ @@ -17,6 +18,8 @@ var shiftedGetKeyValue = shiftFirstArgumentToThis(getSetReflections.getKeyValue) var setKeyValueSymbol = canSymbol.for("can.setKeyValue"); var shiftedSetKeyValue = shiftFirstArgumentToThis(getSetReflections.setKeyValue); +var sizeSymbol = canSymbol.for("can.size"); + var serializeMap = null; var hasUpdateSymbol = helpers.makeGetFirstSymbolValue(["can.updateDeep","can.assignDeep","can.setKeyValue"]); @@ -122,16 +125,53 @@ function makeSerializer(methodName, symbolsToCheck){ }; } +// returns a Map type of the keys mapped to true +var makeMap; +if(typeof Map !== "undefined") { + makeMap = function(keys) { + var map = new Map(); + shapeReflections.eachIndex(keys, function(key){ + map.set(key, true); + }) + return map; + }; +} else { + makeMap = function(keys) { + var map = {}; + keys.forEach(function(key){ + map[key] = true; + }); - -function makeMap(keys) { - var map = {}; - keys.forEach(function(key){ - map[key] = true; - }); - return map; + return { + get: function(key){ + return map[key]; + }, + set: function(key, value) { + map[key] = value; + }, + keys: function(){ + return keys + } + }; + }; } +// creates an optimized hasOwnKey lookup. +// If the object has hasOwnKey, then we just use that. +// Otherwise, try to put all keys in a map. +var fastHasOwnKey = function(obj){ + var hasOwnKey = obj[canSymbol.for("can.hasOwnKey")]; + if(hasOwnKey) { + return hasOwnKey.bind(obj); + } else { + var map = makeMap( shapeReflections.getOwnEnumerableKeys(obj) ); + return function(key) { + return map.get(key) + }; + } +}; + + // combines patches if it makes sense function addPatch(patches, patch) { var lastPatch = patches[patches.length -1]; @@ -177,7 +217,7 @@ function updateDeepList(target, source, isAssign) { return target; } -var shapeReflections = { +shapeReflections = { /** * @function {Object, function(*), [Object]} can-reflect/shape.each each * @parent can-reflect/shape @@ -189,7 +229,7 @@ var shapeReflections = { * iterating over numeric indexes from 0 to `obj.length - 1` and calling `callback` with each property and * index, optionally with `context` as `this` (defaulting to `obj`). If not, `each` functions as * [can-reflect/shape.eachKey eachKey], - * iterating over every Number and String key on `obj` and calling `callback` on each one. + * iterating over every key on `obj` and calling `callback` on each one. * * ``` * var foo = new DefineMap({ bar: "baz" }); @@ -267,6 +307,14 @@ var shapeReflections = { eachListLike: function(list, callback, context){ var index = -1; var length = list.length; + if( length === undefined ) { + var size = list[sizeSymbol]; + if(size) { + length = size.call(list); + } else { + throw new Error("can-reflect: unable to iterate."); + } + } while (++index < length) { var item = list[index]; @@ -528,12 +576,12 @@ var shapeReflections = { assignMap: function(target, source) { // read each key and set it on target - var targetKeyMap = makeMap(this.getOwnEnumerableKeys(target)); + var hasOwnKey = fastHasOwnKey(target); var getKeyValue = target[getKeyValueSymbol] || shiftedGetKeyValue; var setKeyValue = target[setKeyValueSymbol] || shiftedSetKeyValue; this.eachKey(source,function(value, key){ // if the target doesn't have this key or the keys are not the same - if(!targetKeyMap[key] || getKeyValue.call(target, key) !== value) { + if(!hasOwnKey(key) || getKeyValue.call(target, key) !== value) { setKeyValue.call(target, key, value); } }); @@ -541,7 +589,7 @@ var shapeReflections = { }, assignList: function(target, source) { var inserting = this.toArray(source); - getSetReflections.splice(target, 0, inserting.length, inserting ); + getSetReflections.splice(target, 0, inserting, inserting ); return target; }, assign: function(target, source) { @@ -555,12 +603,12 @@ var shapeReflections = { }, assignDeepMap: function(target, source) { - var targetKeyMap = makeMap(this.getOwnEnumerableKeys(target)); + var hasOwnKey = fastHasOwnKey(target); var getKeyValue = target[getKeyValueSymbol] || shiftedGetKeyValue; var setKeyValue = target[setKeyValueSymbol] || shiftedSetKeyValue; this.eachKey(source, function(newVal, key){ - if(!targetKeyMap[key]) { + if(!hasOwnKey(key)) { // set no matter what getSetReflections.setKeyValue(target, key, newVal); } else { @@ -601,11 +649,11 @@ var shapeReflections = { var targetSetKeyValue = target[setKeyValueSymbol] || shiftedSetKeyValue; this.eachKey(target, function(curVal, key){ - if(!sourceKeyMap[key]) { + if(!sourceKeyMap.get(key)) { getSetReflections.deleteKeyValue(target, key); return; } - sourceKeyMap[key] = false; + sourceKeyMap.set(key, false); var newVal = sourceGetKeyValue.call(source, key); // if either was primitive, no recursive update possible @@ -614,16 +662,18 @@ var shapeReflections = { } }, this); - for(var key in sourceKeyMap) { - if(sourceKeyMap[key]) { + this.eachIndex(sourceKeyMap.keys(), function(key){ + if(sourceKeyMap.get(key)) { targetSetKeyValue.call(target, key, sourceGetKeyValue.call(source, key) ); } - } + }) + return target; }, updateList: function(target, source) { var inserting = this.toArray(source); - getSetReflections.splice(target, 0, target.length, inserting ); + + getSetReflections.splice(target, 0, target, inserting ); return target; }, update: function(target, source) { @@ -643,11 +693,11 @@ var shapeReflections = { this.eachKey(target, function(curVal, key){ - if(!sourceKeyMap[key]) { + if(!sourceKeyMap.get(key)) { getSetReflections.deleteKeyValue(target, key); return; } - sourceKeyMap[key] = false; + sourceKeyMap.set(key, false); var newVal = sourceGetKeyValue.call(source, key); // if either was primitive, no recursive update possible @@ -659,11 +709,11 @@ var shapeReflections = { }, this); - for(var key in sourceKeyMap) { - if(sourceKeyMap[key]) { + this.eachIndex(sourceKeyMap.keys(), function(key){ + if(sourceKeyMap.get(key)) { targetSetKeyValue.call(target, key, sourceGetKeyValue.call(source, key) ); } - } + }) return target; }, updateDeepList: function(target, source) { diff --git a/types/map-test.js b/types/map-test.js new file mode 100644 index 0000000..0ffeea1 --- /dev/null +++ b/types/map-test.js @@ -0,0 +1,107 @@ +var QUnit = require("steal-qunit"); +var shape = require("../reflections/shape/shape"); +require("./map"); + +if(typeof Map !== "undefined") { + QUnit.module("can-reflect/types/map Map"); + + QUnit.test("assign", function(){ + var map = new Map(); + shape.assign( map, {name: "CanJS"} ); + QUnit.equal( map.get("name"), "CanJS" , "object to map"); + + var map1 = new Map(); + map = new Map(); + var o1 = {name: "foo"}; + var o2 = {name: "bar"}; + + map1.set(o1, o2); + shape.assign( map, map1 ); + QUnit.equal( map.get(o1), o2 , "map to map"); + + }); + + QUnit.test("has", function(){ + var map = new Map(); + var o1 = {name: "foo"}; + var o2 = {name: "bar"}; + map.set(o1, o2); + QUnit.ok( shape.hasOwnKey(map, o1), true); + }); + + QUnit.test("update", function(){ + var map = new Map(); + var o1 = {name: "o1"}; + var o2 = {name: "o2"}; + var o3 = {name: "o3"}; + map.set(o1, o2); + map.set(o2, o1); + + + var map2 = new Map(); + map2.set(o1, o3); + map2.set(o3, o1); + shape.update(map, map2); + + QUnit.notOk( map.has(o2), "removed key"); + QUnit.equal( map.get(o3), o1, "added key"); + QUnit.equal( map.get(o1), o3, "updated key"); + + }); +} + + +if(typeof WeakMap !== "undefined") { + QUnit.module("can-reflect/types/map WeakMap"); + + QUnit.test("assign", function(){ + var canjs = new Map(); + var name = {name: "toUpperCase"}; + canjs.set(name, "CANJS"); + + var map = new WeakMap(); + + shape.assign( map, canjs ); + QUnit.equal( map.get(name), "CANJS" , "map to weakmap"); + + + map = new WeakMap(); + + var map1 = new Map(); + var o1 = {name: "foo"}; + var o2 = {name: "bar"}; + map1.set(o1, o2); + + shape.assign( map, map1 ); + QUnit.equal( map.get(o1), o2 , "map to map"); + + }); + + QUnit.test("has", function(){ + var map = new WeakMap(); + var o1 = {name: "foo"}; + var o2 = {name: "bar"}; + map.set(o1, o2); + QUnit.ok( shape.hasOwnKey(map, o1), true); + }); + + QUnit.test("update", function(){ + var map = new WeakMap(); + var o1 = {name: "o1"}; + var o2 = {name: "o2"}; + var o3 = {name: "o3"}; + map.set(o1, o2); + map.set(o2, o1); + + + var map2 = new WeakMap(); + map2.set(o1, o3); + map2.set(o3, o1); + try{ + shape.update(map, map2); + } catch(e) { + QUnit.ok(true, "throws an error"); + } + + }); +} diff --git a/types/map.js b/types/map.js new file mode 100644 index 0000000..50d0f75 --- /dev/null +++ b/types/map.js @@ -0,0 +1,22 @@ +var shape = require("../reflections/shape/shape"); + +if(typeof Map !== undefined) { + shape.assignSymbols(Map.prototype,{ + "can.getOwnEnumerableKeys": Map.prototype.keys, + "can.setKeyValue": Map.prototype.set, + "can.getKeyValue": Map.prototype.get, + "can.deleteKeyValue": Map.prototype["delete"], + "can.hasOwnKey": Map.prototype.has + }); +} +if(typeof WeakMap !== undefined) { + shape.assignSymbols(WeakMap.prototype,{ + "can.getOwnEnumerableKeys": function(){ + throw new Error("can-reflect: WeakMaps do not have enumerable keys."); + }, + "can.setKeyValue": WeakMap.prototype.set, + "can.getKeyValue": WeakMap.prototype.get, + "can.deleteKeyValue": WeakMap.prototype["delete"], + "can.hasOwnKey": WeakMap.prototype.has + }); +} diff --git a/types/set-test.js b/types/set-test.js new file mode 100644 index 0000000..3479c0d --- /dev/null +++ b/types/set-test.js @@ -0,0 +1,89 @@ +var QUnit = require("steal-qunit"); +var shape = require("../reflections/shape/shape"); +var type = require("../reflections/type/type"); +require("./set"); + +if(typeof Set !== "undefined") { + QUnit.module("can-reflect/types/set Set"); + + QUnit.test("isListLike", function(){ + QUnit.ok( type.isListLike(new Set()), "isListLike" ); + QUnit.ok( type.isMoreListLikeThanMapLike(new Set()), "isMoreListLikeThanMapLike" ); + + }); + + QUnit.test("shape.each", function(){ + var arr = ["a","b"]; + var set = new Set(arr); + + var count = 0; + shape.each(set, function(value){ + QUnit.equal(value, arr[count++], "got the right values back"); + }); + }); + + QUnit.test("shape.update", function(){ + var set = new Set(["a","b"]); + + shape.update(set, ["a","a","c"]); + + QUnit.deepEqual( Array.from(set), [ + "a","c" + ], ".update"); + }); + + QUnit.test("shape.assign", function(){ + var set = new Set(["a","b"]); + + shape.assign(set, ["a","a","c"]); + + QUnit.deepEqual( Array.from(set), [ + "a","b","c" + ], ".assign"); + }); +} + +if(typeof WeakSet !== "undefined") { + QUnit.module("can-reflect/types/set WeakSet"); + + QUnit.test("isListLike", function(){ + QUnit.ok( type.isListLike(new WeakSet()), "isListLike" ); + QUnit.ok( type.isMoreListLikeThanMapLike(new WeakSet()), "isMoreListLikeThanMapLike" ); + + }); + + QUnit.test("shape.each", function(){ + var arr = [{},{}]; + var set = new WeakSet(arr); + + try { + shape.each(set, function(){}); + } catch(e) { + QUnit.ok(true, "Error "+e.message); + } + + }); + + QUnit.test("shape.update", function(){ + var a = {}, b = {}, c = {}; + var set = new WeakSet([a, b]); + try { + shape.update(set, [a,a, c]); + } catch(e) { + QUnit.ok(true, "Error "+e.message); + } + }); + + QUnit.test("shape.assign", function(){ + var a = {}, b = {}, c = {}; + var set = new WeakSet([a,b]); + + shape.assign(set, [a,a,c]); + + // should have everything + QUnit.ok(set.has(a)); + QUnit.ok(set.has(b)); + QUnit.ok(set.has(c)); + + }); +} diff --git a/types/set.js b/types/set.js new file mode 100644 index 0000000..b383290 --- /dev/null +++ b/types/set.js @@ -0,0 +1,39 @@ +var shape = require("../reflections/shape/shape"); + +if(typeof Set !== undefined) { + shape.assignSymbols(Set.prototype,{ + "can.isMoreListLikeThanMapLike": true, + "can.updateValues": function(index, removing, adding) { + if(removing !== adding) { + shape.each(removing, function(value){ + this.delete(value); + }, this); + } + shape.each(adding, function(value){ + this.add(value); + }, this); + }, + "can.size": function(){ + return this.size; + } + }); +} +if(typeof WeakSet !== undefined) { + shape.assignSymbols(WeakSet.prototype,{ + "can.isListLike": true, + "can.isMoreListLikeThanMapLike": true, + "can.updateValues": function(index, removing, adding) { + if(removing !== adding) { + shape.each(removing, function(value){ + this.delete(value); + }, this); + } + shape.each(adding, function(value){ + this.add(value); + }, this); + }, + "can.size": function(){ + throw new Error("can-reflect: WeakSets do not have enumerable keys."); + } + }); +} diff --git a/types/test.html b/types/test.html new file mode 100644 index 0000000..f0398fc --- /dev/null +++ b/types/test.html @@ -0,0 +1,4 @@ +