react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / jstransform / src / utils.js
81146 views/**1* Copyright 2013 Facebook, Inc.2*3* Licensed under the Apache License, Version 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6*7* http://www.apache.org/licenses/LICENSE-2.08*9* Unless required by applicable law or agreed to in writing, software10* distributed under the License is distributed on an "AS IS" BASIS,11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12* See the License for the specific language governing permissions and13* limitations under the License.14*/151617/*jslint node: true*/18var Syntax = require('esprima-fb').Syntax;19var leadingIndentRegexp = /(^|\n)( {2}|\t)/g;20var nonWhiteRegexp = /(\S)/g;2122/**23* A `state` object represents the state of the parser. It has "local" and24* "global" parts. Global contains parser position, source, etc. Local contains25* scope based properties like current class name. State should contain all the26* info required for transformation. It's the only mandatory object that is27* being passed to every function in transform chain.28*29* @param {string} source30* @param {object} transformOptions31* @return {object}32*/33function createState(source, rootNode, transformOptions) {34return {35/**36* A tree representing the current local scope (and its lexical scope chain)37* Useful for tracking identifiers from parent scopes, etc.38* @type {Object}39*/40localScope: {41parentNode: rootNode,42parentScope: null,43identifiers: {},44tempVarIndex: 045},46/**47* The name (and, if applicable, expression) of the super class48* @type {Object}49*/50superClass: null,51/**52* The namespace to use when munging identifiers53* @type {String}54*/55mungeNamespace: '',56/**57* Ref to the node for the current MethodDefinition58* @type {Object}59*/60methodNode: null,61/**62* Ref to the node for the FunctionExpression of the enclosing63* MethodDefinition64* @type {Object}65*/66methodFuncNode: null,67/**68* Name of the enclosing class69* @type {String}70*/71className: null,72/**73* Whether we're currently within a `strict` scope74* @type {Bool}75*/76scopeIsStrict: null,77/**78* Indentation offset79* @type {Number}80*/81indentBy: 0,82/**83* Global state (not affected by updateState)84* @type {Object}85*/86g: {87/**88* A set of general options that transformations can consider while doing89* a transformation:90*91* - minify92* Specifies that transformation steps should do their best to minify93* the output source when possible. This is useful for places where94* minification optimizations are possible with higher-level context95* info than what jsxmin can provide.96*97* For example, the ES6 class transform will minify munged private98* variables if this flag is set.99*/100opts: transformOptions,101/**102* Current position in the source code103* @type {Number}104*/105position: 0,106/**107* Auxiliary data to be returned by transforms108* @type {Object}109*/110extra: {},111/**112* Buffer containing the result113* @type {String}114*/115buffer: '',116/**117* Source that is being transformed118* @type {String}119*/120source: source,121122/**123* Cached parsed docblock (see getDocblock)124* @type {object}125*/126docblock: null,127128/**129* Whether the thing was used130* @type {Boolean}131*/132tagNamespaceUsed: false,133134/**135* If using bolt xjs transformation136* @type {Boolean}137*/138isBolt: undefined,139140/**141* Whether to record source map (expensive) or not142* @type {SourceMapGenerator|null}143*/144sourceMap: null,145146/**147* Filename of the file being processed. Will be returned as a source148* attribute in the source map149*/150sourceMapFilename: 'source.js',151152/**153* Only when source map is used: last line in the source for which154* source map was generated155* @type {Number}156*/157sourceLine: 1,158159/**160* Only when source map is used: last line in the buffer for which161* source map was generated162* @type {Number}163*/164bufferLine: 1,165166/**167* The top-level Program AST for the original file.168*/169originalProgramAST: null,170171sourceColumn: 0,172bufferColumn: 0173}174};175}176177/**178* Updates a copy of a given state with "update" and returns an updated state.179*180* @param {object} state181* @param {object} update182* @return {object}183*/184function updateState(state, update) {185var ret = Object.create(state);186Object.keys(update).forEach(function(updatedKey) {187ret[updatedKey] = update[updatedKey];188});189return ret;190}191192/**193* Given a state fill the resulting buffer from the original source up to194* the end195*196* @param {number} end197* @param {object} state198* @param {?function} contentTransformer Optional callback to transform newly199* added content.200*/201function catchup(end, state, contentTransformer) {202if (end < state.g.position) {203// cannot move backwards204return;205}206var source = state.g.source.substring(state.g.position, end);207var transformed = updateIndent(source, state);208if (state.g.sourceMap && transformed) {209// record where we are210state.g.sourceMap.addMapping({211generated: { line: state.g.bufferLine, column: state.g.bufferColumn },212original: { line: state.g.sourceLine, column: state.g.sourceColumn },213source: state.g.sourceMapFilename214});215216// record line breaks in transformed source217var sourceLines = source.split('\n');218var transformedLines = transformed.split('\n');219// Add line break mappings between last known mapping and the end of the220// added piece. So for the code piece221// (foo, bar);222// > var x = 2;223// > var b = 3;224// var c =225// only add lines marked with ">": 2, 3.226for (var i = 1; i < sourceLines.length - 1; i++) {227state.g.sourceMap.addMapping({228generated: { line: state.g.bufferLine, column: 0 },229original: { line: state.g.sourceLine, column: 0 },230source: state.g.sourceMapFilename231});232state.g.sourceLine++;233state.g.bufferLine++;234}235// offset for the last piece236if (sourceLines.length > 1) {237state.g.sourceLine++;238state.g.bufferLine++;239state.g.sourceColumn = 0;240state.g.bufferColumn = 0;241}242state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;243state.g.bufferColumn +=244transformedLines[transformedLines.length - 1].length;245}246state.g.buffer +=247contentTransformer ? contentTransformer(transformed) : transformed;248state.g.position = end;249}250251/**252* Returns original source for an AST node.253* @param {object} node254* @param {object} state255* @return {string}256*/257function getNodeSourceText(node, state) {258return state.g.source.substring(node.range[0], node.range[1]);259}260261function replaceNonWhite(value) {262return value.replace(nonWhiteRegexp, ' ');263}264265/**266* Removes all non-whitespace characters267*/268function stripNonWhite(value) {269return value.replace(nonWhiteRegexp, '');270}271272/**273* Catches up as `catchup` but replaces non-whitespace chars with spaces.274*/275function catchupWhiteOut(end, state) {276catchup(end, state, replaceNonWhite);277}278279/**280* Catches up as `catchup` but removes all non-whitespace characters.281*/282function catchupWhiteSpace(end, state) {283catchup(end, state, stripNonWhite);284}285286/**287* Removes all non-newline characters288*/289var reNonNewline = /[^\n]/g;290function stripNonNewline(value) {291return value.replace(reNonNewline, function() {292return '';293});294}295296/**297* Catches up as `catchup` but removes all non-newline characters.298*299* Equivalent to appending as many newlines as there are in the original source300* between the current position and `end`.301*/302function catchupNewlines(end, state) {303catchup(end, state, stripNonNewline);304}305306307/**308* Same as catchup but does not touch the buffer309*310* @param {number} end311* @param {object} state312*/313function move(end, state) {314// move the internal cursors315if (state.g.sourceMap) {316if (end < state.g.position) {317state.g.position = 0;318state.g.sourceLine = 1;319state.g.sourceColumn = 0;320}321322var source = state.g.source.substring(state.g.position, end);323var sourceLines = source.split('\n');324if (sourceLines.length > 1) {325state.g.sourceLine += sourceLines.length - 1;326state.g.sourceColumn = 0;327}328state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;329}330state.g.position = end;331}332333/**334* Appends a string of text to the buffer335*336* @param {string} str337* @param {object} state338*/339function append(str, state) {340if (state.g.sourceMap && str) {341state.g.sourceMap.addMapping({342generated: { line: state.g.bufferLine, column: state.g.bufferColumn },343original: { line: state.g.sourceLine, column: state.g.sourceColumn },344source: state.g.sourceMapFilename345});346var transformedLines = str.split('\n');347if (transformedLines.length > 1) {348state.g.bufferLine += transformedLines.length - 1;349state.g.bufferColumn = 0;350}351state.g.bufferColumn +=352transformedLines[transformedLines.length - 1].length;353}354state.g.buffer += str;355}356357/**358* Update indent using state.indentBy property. Indent is measured in359* double spaces. Updates a single line only.360*361* @param {string} str362* @param {object} state363* @return {string}364*/365function updateIndent(str, state) {366var indentBy = state.indentBy;367if (indentBy < 0) {368for (var i = 0; i < -indentBy; i++) {369str = str.replace(leadingIndentRegexp, '$1');370}371} else {372for (var i = 0; i < indentBy; i++) {373str = str.replace(leadingIndentRegexp, '$1$2$2');374}375}376return str;377}378379/**380* Calculates indent from the beginning of the line until "start" or the first381* character before start.382* @example383* " foo.bar()"384* ^385* start386* indent will be " "387*388* @param {number} start389* @param {object} state390* @return {string}391*/392function indentBefore(start, state) {393var end = start;394start = start - 1;395396while (start > 0 && state.g.source[start] != '\n') {397if (!state.g.source[start].match(/[ \t]/)) {398end = start;399}400start--;401}402return state.g.source.substring(start + 1, end);403}404405function getDocblock(state) {406if (!state.g.docblock) {407var docblock = require('./docblock');408state.g.docblock =409docblock.parseAsObject(docblock.extract(state.g.source));410}411return state.g.docblock;412}413414function identWithinLexicalScope(identName, state, stopBeforeNode) {415var currScope = state.localScope;416while (currScope) {417if (currScope.identifiers[identName] !== undefined) {418return true;419}420421if (stopBeforeNode && currScope.parentNode === stopBeforeNode) {422break;423}424425currScope = currScope.parentScope;426}427return false;428}429430function identInLocalScope(identName, state) {431return state.localScope.identifiers[identName] !== undefined;432}433434/**435* @param {object} boundaryNode436* @param {?array} path437* @return {?object} node438*/439function initScopeMetadata(boundaryNode, path, node) {440return {441boundaryNode: boundaryNode,442bindingPath: path,443bindingNode: node444};445}446447function declareIdentInLocalScope(identName, metaData, state) {448state.localScope.identifiers[identName] = {449boundaryNode: metaData.boundaryNode,450path: metaData.bindingPath,451node: metaData.bindingNode,452state: Object.create(state)453};454}455456function getLexicalBindingMetadata(identName, state) {457return state.localScope.identifiers[identName];458}459460/**461* Apply the given analyzer function to the current node. If the analyzer462* doesn't return false, traverse each child of the current node using the given463* traverser function.464*465* @param {function} analyzer466* @param {function} traverser467* @param {object} node468* @param {array} path469* @param {object} state470*/471function analyzeAndTraverse(analyzer, traverser, node, path, state) {472if (node.type) {473if (analyzer(node, path, state) === false) {474return;475}476path.unshift(node);477}478479getOrderedChildren(node).forEach(function(child) {480traverser(child, path, state);481});482483node.type && path.shift();484}485486/**487* It is crucial that we traverse in order, or else catchup() on a later488* node that is processed out of order can move the buffer past a node489* that we haven't handled yet, preventing us from modifying that node.490*491* This can happen when a node has multiple properties containing children.492* For example, XJSElement nodes have `openingElement`, `closingElement` and493* `children`. If we traverse `openingElement`, then `closingElement`, then494* when we get to `children`, the buffer has already caught up to the end of495* the closing element, after the children.496*497* This is basically a Schwartzian transform. Collects an array of children,498* each one represented as [child, startIndex]; sorts the array by start499* index; then traverses the children in that order.500*/501function getOrderedChildren(node) {502var queue = [];503for (var key in node) {504if (node.hasOwnProperty(key)) {505enqueueNodeWithStartIndex(queue, node[key]);506}507}508queue.sort(function(a, b) { return a[1] - b[1]; });509return queue.map(function(pair) { return pair[0]; });510}511512/**513* Helper function for analyzeAndTraverse which queues up all of the children514* of the given node.515*516* Children can also be found in arrays, so we basically want to merge all of517* those arrays together so we can sort them and then traverse the children518* in order.519*520* One example is the Program node. It contains `body` and `comments`, both521* arrays. Lexographically, comments are interspersed throughout the body522* nodes, but esprima's AST groups them together.523*/524function enqueueNodeWithStartIndex(queue, node) {525if (typeof node !== 'object' || node === null) {526return;527}528if (node.range) {529queue.push([node, node.range[0]]);530} else if (Array.isArray(node)) {531for (var ii = 0; ii < node.length; ii++) {532enqueueNodeWithStartIndex(queue, node[ii]);533}534}535}536537/**538* Checks whether a node or any of its sub-nodes contains539* a syntactic construct of the passed type.540* @param {object} node - AST node to test.541* @param {string} type - node type to lookup.542*/543function containsChildOfType(node, type) {544return containsChildMatching(node, function(node) {545return node.type === type;546});547}548549function containsChildMatching(node, matcher) {550var foundMatchingChild = false;551function nodeTypeAnalyzer(node) {552if (matcher(node) === true) {553foundMatchingChild = true;554return false;555}556}557function nodeTypeTraverser(child, path, state) {558if (!foundMatchingChild) {559foundMatchingChild = containsChildMatching(child, matcher);560}561}562analyzeAndTraverse(563nodeTypeAnalyzer,564nodeTypeTraverser,565node,566[]567);568return foundMatchingChild;569}570571var scopeTypes = {};572scopeTypes[Syntax.FunctionExpression] = true;573scopeTypes[Syntax.FunctionDeclaration] = true;574scopeTypes[Syntax.Program] = true;575576function getBoundaryNode(path) {577for (var ii = 0; ii < path.length; ++ii) {578if (scopeTypes[path[ii].type]) {579return path[ii];580}581}582throw new Error(583'Expected to find a node with one of the following types in path:\n' +584JSON.stringify(Object.keys(scopeTypes))585);586}587588function getTempVar(tempVarIndex) {589return '$__' + tempVarIndex;590}591592function getTempVarWithValue(tempVarIndex, tempVarValue) {593return getTempVar(tempVarIndex) + '=' + tempVarValue;594}595596exports.append = append;597exports.catchup = catchup;598exports.catchupWhiteOut = catchupWhiteOut;599exports.catchupWhiteSpace = catchupWhiteSpace;600exports.catchupNewlines = catchupNewlines;601exports.containsChildMatching = containsChildMatching;602exports.containsChildOfType = containsChildOfType;603exports.createState = createState;604exports.declareIdentInLocalScope = declareIdentInLocalScope;605exports.getBoundaryNode = getBoundaryNode;606exports.getDocblock = getDocblock;607exports.getLexicalBindingMetadata = getLexicalBindingMetadata;608exports.initScopeMetadata = initScopeMetadata;609exports.identWithinLexicalScope = identWithinLexicalScope;610exports.identInLocalScope = identInLocalScope;611exports.indentBefore = indentBefore;612exports.move = move;613exports.scopeTypes = scopeTypes;614exports.updateIndent = updateIndent;615exports.updateState = updateState;616exports.analyzeAndTraverse = analyzeAndTraverse;617exports.getOrderedChildren = getOrderedChildren;618exports.getNodeSourceText = getNodeSourceText;619exports.getTempVar = getTempVar;620exports.getTempVarWithValue = getTempVarWithValue;621622623