| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							|  |  |  |  * Copyright Google Inc. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {MetadataOverride} from '@angular/core/testing'; | 
					
						
							| 
									
										
										
										
											2016-08-30 18:07:40 -07:00
										 |  |  | import {stringify} from './facade/lang'; | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | type StringMap = { | 
					
						
							|  |  |  |   [key: string]: any | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | let _nextReferenceId = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class MetadataOverrider { | 
					
						
							|  |  |  |   private _references = new Map<any, string>(); | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Creates a new instance for the given metadata class | 
					
						
							|  |  |  |    * based on an old instance and overrides. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   overrideMetadata<C extends T, T>( | 
					
						
							|  |  |  |       metadataClass: {new (options: T): C;}, oldMetadata: C, override: MetadataOverride<T>): C { | 
					
						
							|  |  |  |     const props: StringMap = {}; | 
					
						
							|  |  |  |     if (oldMetadata) { | 
					
						
							|  |  |  |       _valueProps(oldMetadata).forEach((prop) => props[prop] = (<any>oldMetadata)[prop]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (override.set) { | 
					
						
							|  |  |  |       if (override.remove || override.add) { | 
					
						
							| 
									
										
										
										
											2016-08-25 00:50:16 -07:00
										 |  |  |         throw new Error(`Cannot set and add/remove ${stringify(metadataClass)} at the same time!`); | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       setMetadata(props, override.set); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (override.remove) { | 
					
						
							|  |  |  |       removeMetadata(props, override.remove, this._references); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (override.add) { | 
					
						
							|  |  |  |       addMetadata(props, override.add); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return new metadataClass(<any>props); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function removeMetadata(metadata: StringMap, remove: any, references: Map<any, string>) { | 
					
						
							|  |  |  |   const removeObjects = new Set<string>(); | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   for (const prop in remove) { | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     const removeValue = remove[prop]; | 
					
						
							|  |  |  |     if (removeValue instanceof Array) { | 
					
						
							|  |  |  |       removeValue.forEach( | 
					
						
							|  |  |  |           (value: any) => { removeObjects.add(_propHashKey(prop, value, references)); }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       removeObjects.add(_propHashKey(prop, removeValue, references)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   for (const prop in metadata) { | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     const propValue = metadata[prop]; | 
					
						
							|  |  |  |     if (propValue instanceof Array) { | 
					
						
							|  |  |  |       metadata[prop] = propValue.filter( | 
					
						
							| 
									
										
										
										
											2016-10-04 15:57:37 -07:00
										 |  |  |           (value: any) => !removeObjects.has(_propHashKey(prop, value, references))); | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       if (removeObjects.has(_propHashKey(prop, propValue, references))) { | 
					
						
							|  |  |  |         metadata[prop] = undefined; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function addMetadata(metadata: StringMap, add: any) { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   for (const prop in add) { | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     const addValue = add[prop]; | 
					
						
							|  |  |  |     const propValue = metadata[prop]; | 
					
						
							|  |  |  |     if (propValue != null && propValue instanceof Array) { | 
					
						
							|  |  |  |       metadata[prop] = propValue.concat(addValue); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       metadata[prop] = addValue; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function setMetadata(metadata: StringMap, set: any) { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |   for (const prop in set) { | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     metadata[prop] = set[prop]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function _propHashKey(propName: any, propValue: any, references: Map<any, string>): string { | 
					
						
							|  |  |  |   const replacer = (key: any, value: any) => { | 
					
						
							|  |  |  |     if (typeof value === 'function') { | 
					
						
							|  |  |  |       value = _serializeReference(value, references); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return value; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return `${propName}:${JSON.stringify(propValue, replacer)}`; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function _serializeReference(ref: any, references: Map<any, string>): string { | 
					
						
							|  |  |  |   let id = references.get(ref); | 
					
						
							|  |  |  |   if (!id) { | 
					
						
							|  |  |  |     id = `${stringify(ref)}${_nextReferenceId++}`; | 
					
						
							|  |  |  |     references.set(ref, id); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   return id; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function _valueProps(obj: any): string[] { | 
					
						
							|  |  |  |   const props: string[] = []; | 
					
						
							|  |  |  |   // regular public props
 | 
					
						
							| 
									
										
										
										
											2016-08-04 19:35:41 +02:00
										 |  |  |   Object.keys(obj).forEach((prop) => { | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |     if (!prop.startsWith('_')) { | 
					
						
							|  |  |  |       props.push(prop); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2016-08-12 17:39:33 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |   // getters
 | 
					
						
							| 
									
										
										
										
											2016-08-12 17:39:33 -07:00
										 |  |  |   let proto = obj; | 
					
						
							|  |  |  |   while (proto = Object.getPrototypeOf(proto)) { | 
					
						
							|  |  |  |     Object.keys(proto).forEach((protoProp) => { | 
					
						
							| 
									
										
										
										
											2016-11-12 14:08:58 +01:00
										 |  |  |       const desc = Object.getOwnPropertyDescriptor(proto, protoProp); | 
					
						
							| 
									
										
										
										
											2016-08-12 17:39:33 -07:00
										 |  |  |       if (!protoProp.startsWith('_') && desc && 'get' in desc) { | 
					
						
							|  |  |  |         props.push(protoProp); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2016-07-28 04:54:49 -07:00
										 |  |  |   return props; | 
					
						
							|  |  |  | } |