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
-
-
-
- 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 @@
+