/** * 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. */ /* jshint browser: true */ /* jslint evil: true */ /*eslint-disable no-eval */ /*eslint-disable block-scoped-var */ 'use strict'; var ReactTools = require('../main'); var inlineSourceMap = require('./inline-source-map'); var headEl; var dummyAnchor; var inlineScriptCount = 0; // The source-map library relies on Object.defineProperty, but IE8 doesn't // support it fully even with es5-sham. Indeed, es5-sham's defineProperty // throws when Object.prototype.__defineGetter__ is missing, so we skip building // the source map in that case. var supportsAccessors = Object.prototype.hasOwnProperty('__defineGetter__'); /** * Run provided code through jstransform. * * @param {string} source Original source code * @param {object?} options Options to pass to jstransform * @return {object} object as returned from jstransform */ function transformReact(source, options) { options = options || {}; // Force the sourcemaps option manually. We don't want to use it if it will // break (see above note about supportsAccessors). We'll only override the // value here if sourceMap was specified and is truthy. This guarantees that // we won't override any user intent (since this method is exposed publicly). if (options.sourceMap) { options.sourceMap = supportsAccessors; } // Otherwise just pass all options straight through to react-tools. return ReactTools.transformWithDetails(source, options); } /** * Eval provided source after transforming it. * * @param {string} source Original source code * @param {object?} options Options to pass to jstransform */ function exec(source, options) { return eval(transformReact(source, options).code); } /** * This method returns a nicely formated line of code pointing to the exact * location of the error `e`. The line is limited in size so big lines of code * are also shown in a readable way. * * Example: * ... x', overflow:'scroll'}} id={} onScroll={this.scroll} class=" ... * ^ * * @param {string} code The full string of code * @param {Error} e The error being thrown * @return {string} formatted message * @internal */ function createSourceCodeErrorMessage(code, e) { var sourceLines = code.split('\n'); // e.lineNumber is non-standard so we can't depend on its availability. If // we're in a browser where it isn't supported, don't even bother trying to // format anything. We may also hit a case where the line number is reported // incorrectly and is outside the bounds of the actual code. Handle that too. if (!e.lineNumber || e.lineNumber > sourceLines.length) { return ''; } var erroneousLine = sourceLines[e.lineNumber - 1]; // Removes any leading indenting spaces and gets the number of // chars indenting the `erroneousLine` var indentation = 0; erroneousLine = erroneousLine.replace(/^\s+/, function(leadingSpaces) { indentation = leadingSpaces.length; return ''; }); // Defines the number of characters that are going to show // before and after the erroneous code var LIMIT = 30; var errorColumn = e.column - indentation; if (errorColumn > LIMIT) { erroneousLine = '... ' + erroneousLine.slice(errorColumn - LIMIT); errorColumn = 4 + LIMIT; } if (erroneousLine.length - errorColumn > LIMIT) { erroneousLine = erroneousLine.slice(0, errorColumn + LIMIT) + ' ...'; } var message = '\n\n' + erroneousLine + '\n'; message += new Array(errorColumn - 1).join(' ') + '^'; return message; } /** * Actually transform the code. * * @param {string} code * @param {string?} url * @param {object?} options * @return {string} The transformed code. * @internal */ function transformCode(code, url, options) { try { var transformed = transformReact(code, options); } catch(e) { e.message += '\n at '; if (url) { if ('fileName' in e) { // We set `fileName` if it's supported by this error object and // a `url` was provided. // The error will correctly point to `url` in Firefox. e.fileName = url; } e.message += url + ':' + e.lineNumber + ':' + e.columnNumber; } else { e.message += location.href; } e.message += createSourceCodeErrorMessage(code, e); throw e; } if (!transformed.sourceMap) { return transformed.code; } var source; if (url == null) { source = 'Inline JSX script'; inlineScriptCount++; if (inlineScriptCount > 1) { source += ' (' + inlineScriptCount + ')'; } } else if (dummyAnchor) { // Firefox has problems when the sourcemap source is a proper URL with a // protocol and hostname, so use the pathname. We could use just the // filename, but hopefully using the full path will prevent potential // issues where the same filename exists in multiple directories. dummyAnchor.href = url; source = dummyAnchor.pathname.substr(1); } return ( transformed.code + '\n' + inlineSourceMap(transformed.sourceMap, code, source) ); } /** * Appends a script element at the end of the with the content of code, * after transforming it. * * @param {string} code The original source code * @param {string?} url Where the code came from. null if inline * @param {object?} options Options to pass to jstransform * @internal */ function run(code, url, options) { var scriptEl = document.createElement('script'); scriptEl.text = transformCode(code, url, options); headEl.appendChild(scriptEl); } /** * Load script from the provided url and pass the content to the callback. * * @param {string} url The location of the script src * @param {function} callback Function to call with the content of url * @internal */ function load(url, successCallback, errorCallback) { var xhr; xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest(); // async, however scripts will be executed in the order they are in the // DOM to mirror normal script loading. xhr.open('GET', url, true); if ('overrideMimeType' in xhr) { xhr.overrideMimeType('text/plain'); } xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 0 || xhr.status === 200) { successCallback(xhr.responseText); } else { errorCallback(); throw new Error('Could not load ' + url); } } }; return xhr.send(null); } /** * Loop over provided script tags and get the content, via innerHTML if an * inline script, or by using XHR. Transforms are applied if needed. The scripts * are executed in the order they are found on the page. * * @param {array} scripts The