closure-builder
Version:
Simple Closure, Soy and JavaScript Build system
164 lines (151 loc) • 5.5 kB
JavaScript
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Interfaces and helper functions for Soy maps/proto maps/ES6
* maps.
*/
goog.module('soy.map');
goog.module.declareLegacyNamespace();
const UnsanitizedText = goog.require('goog.soy.data.UnsanitizedText');
const {assertString} = goog.require('goog.asserts');
const {shuffle} = goog.require('goog.array');
/**
* Structural interface for representing Soy `map`s in JavaScript.
*
* <p>The Soy `map` type was originally represented in JavaScript by plain
* objects (`Object<K,V>`). However, plain object access syntax (`obj['key']`)
* is incompatible with the ES6 Map and jspb.Map APIs, both of which use
* `map.get('key')`. In order to allow the Soy `map` type to interoperate with
* ES6 Maps and proto maps, Soy now uses this interface to represent the `map`
* type. (The Soy `legacy_object_literal_map` type continues to use plain
* objects for backwards compatibility.)
*
* <p>This is a structural interface -- ES6 Map and jspb.Map implicitly
* implement it without declaring that they do.
*
* @record
* @template K, V
*/
class SoyMap {
/**
* @param {K} k
* @return {V|undefined}
*/
get(k) {}
/**
* Set method is required for the runtime method that copies the content of a
* ES6 map to jspb map.
* @param {K} k
* @param {V} v
* @return {!SoyMap<K, V>}
*/
set(k, v) {}
/**
* @return {!IteratorIterable<K>} An iterator that contains the keys for each
* element in this map.
*/
keys() {}
/**
* Returns an iterator over the [key, value] pair entries of this map.
*
* TODO(b/69049599): structural interfaces defeat property renaming.
* This could cause anything in the compilation unit that has get() and
* entries() methods to no longer rename entries(). If that increases code
* size too much, we could use the keys() method instead in
* $$mapToLegacyObjectMap. Not renaming "keys" is presumably ~43% less bad
* than not renaming "entries".
*
* @return {!IteratorIterable<!Array<K|V>>}
*/
entries() {}
}
/**
* Converts an ES6 Map or jspb.Map into an equivalent legacy object map.
* N.B.: although ES6 Maps and jspb.Maps allow many values to serve as map keys,
* legacy object maps allow only string keys.
* @param {!SoyMap<?, V>} map
* @return {!Object<V>}
* @template V
*/
function $$mapToLegacyObjectMap(map) {
const obj = {};
for (const [k, v] of map.entries()) {
obj[assertString(k)] = v;
}
return obj;
}
/**
* Gets the keys in a map as an array. There are no guarantees on the order.
* @param {!SoyMap<K, V>} map The map to get the keys of.
* @return {!Array<K>} The array of keys in the given map.
* @template K, V
*/
function $$getMapKeys(map) {
const keys = Array.from(map.keys());
// The iteration order of Soy map keys and proto maps is documented as
// undefined. But the iteration order of ES6 Maps is specified as insertion
// order. In debug mode, shuffle the keys to hopefully catch callers that are
// making assumptions about iteration order.
if (goog.DEBUG) {
shuffle(keys);
}
return keys;
}
/**
* Saves the contents of a SoyMap to another SoyMap.
* This is used for proto initialization in Soy. Protocol buffer in JS does not
* generate setters for map fields. To construct a proto map field, we use this
* help method to save the content of map literal to proto.
* @param {!SoyMap<K, V>} jspbMap
* @param {!SoyMap<K, V>} map
* @template K, V
*/
function $$populateMap(jspbMap, map) {
for (const [k, v] of map.entries()) {
jspbMap.set(k, v);
}
}
/**
* SoyMaps, like ES6 Maps and proto maps, allow non-string values as map keys.
* But UnsanitizedText keys still need to be coerced to strings so that
* instances with identical textual content are considered identical for map
* lookups.
* @param {?} key The key that is being inserted into or looked up in the map.
* @return {?} The key, coerced to a string if it is an UnsanitizedText object.
*/
function $$maybeCoerceKeyToString(key) {
return key instanceof UnsanitizedText ? key.getContent() : key;
}
/**
* Determines if the argument matches the soy.map.Map interface.
* @param {?} map The object to check.
* @return {boolean} True if it is a soy.map.Map, false otherwise.
*/
function $$isSoyMap(map) {
return goog.isObject(map) && goog.isFunction(map.get) &&
goog.isFunction(map.set) && goog.isFunction(map.keys) &&
goog.isFunction(map.entries);
}
exports = {
$$mapToLegacyObjectMap,
$$maybeCoerceKeyToString,
$$populateMap,
$$getMapKeys,
$$isSoyMap,
// This is declared as SoyMap instead of Map to avoid shadowing ES6 Map, which
// is used by $$legacyObjectMapToMap. But the external name can still be Map.
Map: SoyMap,
};