Skip to content

Commit

Permalink
Wire up keyEscapeUtils
Browse files Browse the repository at this point in the history
Add keyEscapeUtils and related tests

Handle separator unescape case

Unescape component key for warnings

Address linting errors

Capitalize utility class name
  • Loading branch information
hkal committed Apr 15, 2016
1 parent 6c11bb6 commit f9131dc
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 39 deletions.
3 changes: 2 additions & 1 deletion src/renderers/shared/reconciler/ReactChildReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
var ReactReconciler = require('ReactReconciler');

var instantiateReactComponent = require('instantiateReactComponent');
var KeyEscapeUtils = require('KeyEscapeUtils');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('warning');
Expand All @@ -27,7 +28,7 @@ function instantiateChild(childInstances, child, name) {
'flattenChildren(...): Encountered two children with the same key, ' +
'`%s`. Child keys must be unique; when two children share a key, only ' +
'the first child will be used.',
name
KeyEscapeUtils.unescape(name)
);
}
if (child != null && keyUnique) {
Expand Down
51 changes: 51 additions & 0 deletions src/shared/utils/KeyEscapeUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule KeyEscapeUtils
*/

'use strict';

function escape(key) {
var escapeRegex = /[=:]/g;
var escaperLookup = {
'=': '=0',
':': '=2',
};
var escapedString = ('' + key).replace(
escapeRegex,
function(match) {
return escaperLookup[match];
}
);

return '$' + escapedString;
}

function unescape(key) {
var unescapeRegex = /(=0|=2)/g;
var unescaperLookup = {
'=0': '=',
'=2': ':',
};
var keySubstring = (key[0] === '.') ? key.substring(2) : key.substring(1);

return ('' + keySubstring).replace(
unescapeRegex,
function(match) {
return unescaperLookup[match];
}
);
}

var KeyEscapeUtils = {
escape: escape,
unescape: unescape,
};

module.exports = KeyEscapeUtils;
37 changes: 37 additions & 0 deletions src/shared/utils/__tests__/KeyEscapeUtils-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/

'use strict';

var KeyEscapeUtils;

describe('KeyEscapeUtils', () => {
beforeEach(() => {
jest.resetModuleRegistry();

KeyEscapeUtils = require('KeyEscapeUtils');
});

describe('escape', () => {
it('should properly escape and wrap user defined keys', () => {
expect(KeyEscapeUtils.escape('1')).toBe('$1');
expect(KeyEscapeUtils.escape('1=::=2')).toBe('$1=0=2=2=02');
});
});

describe('unescape', () => {
it('should properly unescape and unwrap user defined keys', () => {
expect(KeyEscapeUtils.unescape('$1')).toBe('1');
expect(KeyEscapeUtils.unescape('.$1')).toBe('1');
expect(KeyEscapeUtils.unescape('$1=0=2=2=02')).toBe('1=::=2');
});
});
});
3 changes: 2 additions & 1 deletion src/shared/utils/flattenChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var KeyEscapeUtils = require('KeyEscapeUtils');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('warning');

Expand All @@ -29,7 +30,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
'flattenChildren(...): Encountered two children with the same key, ' +
'`%s`. Child keys must be unique; when two children share a key, only ' +
'the first child will be used.',
name
KeyEscapeUtils.unescape(name)
);
}
if (keyUnique && child != null) {
Expand Down
40 changes: 3 additions & 37 deletions src/shared/utils/traverseAllChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var ReactElement = require('ReactElement');

var getIteratorFn = require('getIteratorFn');
var invariant = require('invariant');
var KeyEscapeUtils = require('KeyEscapeUtils');
var warning = require('warning');

var SEPARATOR = '.';
Expand All @@ -26,19 +27,8 @@ var SUBSEPARATOR = ':';
* pattern.
*/

var userProvidedKeyEscaperLookup = {
'=': '=0',
':': '=2',
};

var userProvidedKeyEscapeRegex = /[=:]/g;

var didWarnAboutMaps = false;

function userProvidedKeyEscaper(match) {
return userProvidedKeyEscaperLookup[match];
}

/**
* Generate a key string that identifies a component within a set.
*
Expand All @@ -51,36 +41,12 @@ function getComponentKey(component, index) {
// that we don't block potential future ES APIs.
if (component && typeof component === 'object' && component.key != null) {
// Explicit key
return wrapUserProvidedKey(component.key);
return KeyEscapeUtils.escape(component.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
}

/**
* Escape a component key so that it is safe to use in a reactid.
*
* @param {*} text Component key to be escaped.
* @return {string} An escaped string.
*/
function escapeUserProvidedKey(text) {
return ('' + text).replace(
userProvidedKeyEscapeRegex,
userProvidedKeyEscaper
);
}

/**
* Wrap a `key` value explicitly provided by the user to distinguish it from
* implicitly-generated keys generated by a component's index in its parent.
*
* @param {string} key Value of a user-provided `key` attribute
* @return {string}
*/
function wrapUserProvidedKey(key) {
return '$' + escapeUserProvidedKey(key);
}

/**
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
Expand Down Expand Up @@ -166,7 +132,7 @@ function traverseAllChildrenImpl(
child = entry[1];
nextName = (
nextNamePrefix +
wrapUserProvidedKey(entry[0]) + SUBSEPARATOR +
KeyEscapeUtils.escape(entry[0]) + SUBSEPARATOR +
getComponentKey(child, 0)
);
subtreeCount += traverseAllChildrenImpl(
Expand Down

0 comments on commit f9131dc

Please sign in to comment.