@samitouri / QOS-React / commits / dc6fc8cc07

Helper for escaping and unescpaing component keys (#6500)

- Abstract escaping - Provide human readible same key warnings

hkal committed Apr 15, 2016 at 23:55 UTC dc6fc8cc0726458a14f0544a30514af208d0098b
5 files changed +109 -39
src/renderers/shared/reconciler/ReactChildReconciler.js
+2 -1
@@ -14,6 +14,7 @@
14 var ReactReconciler = require('ReactReconciler');
15
16 var instantiateReactComponent = require('instantiateReactComponent');
17 +var KeyEscapeUtils = require('KeyEscapeUtils');
18 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
19 var traverseAllChildren = require('traverseAllChildren');
20 var warning = require('warning');
@@ -27,7 +28,7 @@ function instantiateChild(childInstances, child, name) {
28 'flattenChildren(...): Encountered two children with the same key, ' +
29 '`%s`. Child keys must be unique; when two children share a key, only ' +
30 'the first child will be used.',
30 - name
31 + KeyEscapeUtils.unescape(name)
32 );
33 }
34 if (child != null && keyUnique) {
src/shared/utils/KeyEscapeUtils.js new
+64
@@ -0,0 +1,64 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule KeyEscapeUtils
10 + */
11 +
12 +'use strict';
13 +
14 +/**
15 + * Escape and wrap key so it is safe to use as a reactid
16 + *
17 + * @param {*} key to be escaped.
18 + * @return {string} the escaped key.
19 + */
20 +function escape(key) {
21 + var escapeRegex = /[=:]/g;
22 + var escaperLookup = {
23 + '=': '=0',
24 + ':': '=2',
25 + };
26 + var escapedString = ('' + key).replace(
27 + escapeRegex,
28 + function(match) {
29 + return escaperLookup[match];
30 + }
31 + );
32 +
33 + return '$' + escapedString;
34 +}
35 +
36 +/**
37 + * Unescape and unwrap key for human-readable display
38 + *
39 + * @param {string} key to unescape.
40 + * @return {string} the unescaped key.
41 + */
42 +function unescape(key) {
43 + var unescapeRegex = /(=0|=2)/g;
44 + var unescaperLookup = {
45 + '=0': '=',
46 + '=2': ':',
47 + };
48 + var keySubstring = (key[0] === '.' && key[1] === '$')
49 + ? key.substring(2) : key.substring(1);
50 +
51 + return ('' + keySubstring).replace(
52 + unescapeRegex,
53 + function(match) {
54 + return unescaperLookup[match];
55 + }
56 + );
57 +}
58 +
59 +var KeyEscapeUtils = {
60 + escape: escape,
61 + unescape: unescape,
62 +};
63 +
64 +module.exports = KeyEscapeUtils;
src/shared/utils/__tests__/KeyEscapeUtils-test.js new
+38
@@ -0,0 +1,38 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +
12 +'use strict';
13 +
14 +var KeyEscapeUtils;
15 +
16 +describe('KeyEscapeUtils', () => {
17 + beforeEach(() => {
18 + jest.resetModuleRegistry();
19 +
20 + KeyEscapeUtils = require('KeyEscapeUtils');
21 + });
22 +
23 + describe('escape', () => {
24 + it('should properly escape and wrap user defined keys', () => {
25 + expect(KeyEscapeUtils.escape('1')).toBe('$1');
26 + expect(KeyEscapeUtils.escape('1=::=2')).toBe('$1=0=2=2=02');
27 + });
28 + });
29 +
30 + describe('unescape', () => {
31 + it('should properly unescape and unwrap user defined keys', () => {
32 + expect(KeyEscapeUtils.unescape('.1')).toBe('1');
33 + expect(KeyEscapeUtils.unescape('$1')).toBe('1');
34 + expect(KeyEscapeUtils.unescape('.$1')).toBe('1');
35 + expect(KeyEscapeUtils.unescape('$1=0=2=2=02')).toBe('1=::=2');
36 + });
37 + });
38 +});
src/shared/utils/flattenChildren.js
+2 -1
@@ -11,6 +11,7 @@
11
12 'use strict';
13
14 +var KeyEscapeUtils = require('KeyEscapeUtils');
15 var traverseAllChildren = require('traverseAllChildren');
16 var warning = require('warning');
17
@@ -29,7 +30,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
30 'flattenChildren(...): Encountered two children with the same key, ' +
31 '`%s`. Child keys must be unique; when two children share a key, only ' +
32 'the first child will be used.',
32 - name
33 + KeyEscapeUtils.unescape(name)
34 );
35 }
36 if (keyUnique && child != null) {
src/shared/utils/traverseAllChildren.js
+3 -37
@@ -16,6 +16,7 @@ var ReactElement = require('ReactElement');
16
17 var getIteratorFn = require('getIteratorFn');
18 var invariant = require('invariant');
19 +var KeyEscapeUtils = require('KeyEscapeUtils');
20 var warning = require('warning');
21
22 var SEPARATOR = '.';
@@ -26,19 +27,8 @@ var SUBSEPARATOR = ':';
27 * pattern.
28 */
29
29 -var userProvidedKeyEscaperLookup = {
30 - '=': '=0',
31 - ':': '=2',
32 -};
33 -
34 -var userProvidedKeyEscapeRegex = /[=:]/g;
35 -
30 var didWarnAboutMaps = false;
31
38 -function userProvidedKeyEscaper(match) {
39 - return userProvidedKeyEscaperLookup[match];
40 -}
41 -
32 /**
33 * Generate a key string that identifies a component within a set.
34 *
@@ -51,36 +41,12 @@ function getComponentKey(component, index) {
41 // that we don't block potential future ES APIs.
42 if (component && typeof component === 'object' && component.key != null) {
43 // Explicit key
54 - return wrapUserProvidedKey(component.key);
44 + return KeyEscapeUtils.escape(component.key);
45 }
46 // Implicit key determined by the index in the set
47 return index.toString(36);
48 }
49
60 -/**
61 - * Escape a component key so that it is safe to use in a reactid.
62 - *
63 - * @param {*} text Component key to be escaped.
64 - * @return {string} An escaped string.
65 - */
66 -function escapeUserProvidedKey(text) {
67 - return ('' + text).replace(
68 - userProvidedKeyEscapeRegex,
69 - userProvidedKeyEscaper
70 - );
71 -}
72 -
73 -/**
74 - * Wrap a `key` value explicitly provided by the user to distinguish it from
75 - * implicitly-generated keys generated by a component's index in its parent.
76 - *
77 - * @param {string} key Value of a user-provided `key` attribute
78 - * @return {string}
79 - */
80 -function wrapUserProvidedKey(key) {
81 - return '$' + escapeUserProvidedKey(key);
82 -}
83 -
50 /**
51 * @param {?*} children Children tree container.
52 * @param {!string} nameSoFar Name of the key path so far.
@@ -166,7 +132,7 @@ function traverseAllChildrenImpl(
132 child = entry[1];
133 nextName = (
134 nextNamePrefix +
169 - wrapUserProvidedKey(entry[0]) + SUBSEPARATOR +
135 + KeyEscapeUtils.escape(entry[0]) + SUBSEPARATOR +
136 getComponentKey(child, 0)
137 );
138 subtreeCount += traverseAllChildrenImpl(