You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
7.0 KiB
183 lines
7.0 KiB
/** |
|
* Copyright 2013-2015, 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 Danger |
|
* @typechecks static-only |
|
*/ |
|
|
|
/*jslint evil: true, sub: true */ |
|
|
|
'use strict'; |
|
|
|
var ExecutionEnvironment = require("./ExecutionEnvironment"); |
|
|
|
var createNodesFromMarkup = require("./createNodesFromMarkup"); |
|
var emptyFunction = require("./emptyFunction"); |
|
var getMarkupWrap = require("./getMarkupWrap"); |
|
var invariant = require("./invariant"); |
|
|
|
var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; |
|
var RESULT_INDEX_ATTR = 'data-danger-index'; |
|
|
|
/** |
|
* Extracts the `nodeName` from a string of markup. |
|
* |
|
* NOTE: Extracting the `nodeName` does not require a regular expression match |
|
* because we make assumptions about React-generated markup (i.e. there are no |
|
* spaces surrounding the opening tag and there is at least one attribute). |
|
* |
|
* @param {string} markup String of markup. |
|
* @return {string} Node name of the supplied markup. |
|
* @see http://jsperf.com/extract-nodename |
|
*/ |
|
function getNodeName(markup) { |
|
return markup.substring(1, markup.indexOf(' ')); |
|
} |
|
|
|
var Danger = { |
|
|
|
/** |
|
* Renders markup into an array of nodes. The markup is expected to render |
|
* into a list of root nodes. Also, the length of `resultList` and |
|
* `markupList` should be the same. |
|
* |
|
* @param {array<string>} markupList List of markup strings to render. |
|
* @return {array<DOMElement>} List of rendered nodes. |
|
* @internal |
|
*/ |
|
dangerouslyRenderMarkup: function(markupList) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
ExecutionEnvironment.canUseDOM, |
|
'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + |
|
'thread. Make sure `window` and `document` are available globally ' + |
|
'before requiring React when unit testing or use ' + |
|
'React.renderToString for server rendering.' |
|
) : invariant(ExecutionEnvironment.canUseDOM)); |
|
var nodeName; |
|
var markupByNodeName = {}; |
|
// Group markup by `nodeName` if a wrap is necessary, else by '*'. |
|
for (var i = 0; i < markupList.length; i++) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
markupList[i], |
|
'dangerouslyRenderMarkup(...): Missing markup.' |
|
) : invariant(markupList[i])); |
|
nodeName = getNodeName(markupList[i]); |
|
nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; |
|
markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; |
|
markupByNodeName[nodeName][i] = markupList[i]; |
|
} |
|
var resultList = []; |
|
var resultListAssignmentCount = 0; |
|
for (nodeName in markupByNodeName) { |
|
if (!markupByNodeName.hasOwnProperty(nodeName)) { |
|
continue; |
|
} |
|
var markupListByNodeName = markupByNodeName[nodeName]; |
|
|
|
// This for-in loop skips the holes of the sparse array. The order of |
|
// iteration should follow the order of assignment, which happens to match |
|
// numerical index order, but we don't rely on that. |
|
var resultIndex; |
|
for (resultIndex in markupListByNodeName) { |
|
if (markupListByNodeName.hasOwnProperty(resultIndex)) { |
|
var markup = markupListByNodeName[resultIndex]; |
|
|
|
// Push the requested markup with an additional RESULT_INDEX_ATTR |
|
// attribute. If the markup does not start with a < character, it |
|
// will be discarded below (with an appropriate console.error). |
|
markupListByNodeName[resultIndex] = markup.replace( |
|
OPEN_TAG_NAME_EXP, |
|
// This index will be parsed back out below. |
|
'$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" ' |
|
); |
|
} |
|
} |
|
|
|
// Render each group of markup with similar wrapping `nodeName`. |
|
var renderNodes = createNodesFromMarkup( |
|
markupListByNodeName.join(''), |
|
emptyFunction // Do nothing special with <script> tags. |
|
); |
|
|
|
for (var j = 0; j < renderNodes.length; ++j) { |
|
var renderNode = renderNodes[j]; |
|
if (renderNode.hasAttribute && |
|
renderNode.hasAttribute(RESULT_INDEX_ATTR)) { |
|
|
|
resultIndex = +renderNode.getAttribute(RESULT_INDEX_ATTR); |
|
renderNode.removeAttribute(RESULT_INDEX_ATTR); |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
!resultList.hasOwnProperty(resultIndex), |
|
'Danger: Assigning to an already-occupied result index.' |
|
) : invariant(!resultList.hasOwnProperty(resultIndex))); |
|
|
|
resultList[resultIndex] = renderNode; |
|
|
|
// This should match resultList.length and markupList.length when |
|
// we're done. |
|
resultListAssignmentCount += 1; |
|
|
|
} else if ("production" !== process.env.NODE_ENV) { |
|
console.error( |
|
'Danger: Discarding unexpected node:', |
|
renderNode |
|
); |
|
} |
|
} |
|
} |
|
|
|
// Although resultList was populated out of order, it should now be a dense |
|
// array. |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
resultListAssignmentCount === resultList.length, |
|
'Danger: Did not assign to every index of resultList.' |
|
) : invariant(resultListAssignmentCount === resultList.length)); |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
resultList.length === markupList.length, |
|
'Danger: Expected markup to render %s nodes, but rendered %s.', |
|
markupList.length, |
|
resultList.length |
|
) : invariant(resultList.length === markupList.length)); |
|
|
|
return resultList; |
|
}, |
|
|
|
/** |
|
* Replaces a node with a string of markup at its current position within its |
|
* parent. The markup must render into a single root node. |
|
* |
|
* @param {DOMElement} oldChild Child node to replace. |
|
* @param {string} markup Markup to render in place of the child node. |
|
* @internal |
|
*/ |
|
dangerouslyReplaceNodeWithMarkup: function(oldChild, markup) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
ExecutionEnvironment.canUseDOM, |
|
'dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a ' + |
|
'worker thread. Make sure `window` and `document` are available ' + |
|
'globally before requiring React when unit testing or use ' + |
|
'React.renderToString for server rendering.' |
|
) : invariant(ExecutionEnvironment.canUseDOM)); |
|
("production" !== process.env.NODE_ENV ? invariant(markup, 'dangerouslyReplaceNodeWithMarkup(...): Missing markup.') : invariant(markup)); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
oldChild.tagName.toLowerCase() !== 'html', |
|
'dangerouslyReplaceNodeWithMarkup(...): Cannot replace markup of the ' + |
|
'<html> node. This is because browser quirks make this unreliable ' + |
|
'and/or slow. If you want to render to the root you must use ' + |
|
'server rendering. See React.renderToString().' |
|
) : invariant(oldChild.tagName.toLowerCase() !== 'html')); |
|
|
|
var newChild = createNodesFromMarkup(markup, emptyFunction)[0]; |
|
oldChild.parentNode.replaceChild(newChild, oldChild); |
|
} |
|
|
|
}; |
|
|
|
module.exports = Danger;
|
|
|