Files
react/vendor/fbtransform/lib/transform.js
T
2013-06-13 18:18:54 -07:00

142 lines
4.2 KiB
JavaScript

/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*global exports:true*/
/*jslint node: true*/
"use strict";
/**
* Syntax transfomer for javascript. Takes the source in, spits the source
* out. Tries to maintain readability and preserve whitespace and line numbers
* where posssible.
*
* Support
* - ES6 class transformation + private property munging, see ./classes.js
* - React XHP style syntax transformations, see ./react.js
* - Bolt XHP style syntax transformations, see ./bolt.js
*
* The general flow is the following:
* - Parse the source with our customized esprima-parser
* https://github.com/voloko/esprima. We have to customize the parser to
* support non-standard XHP-style syntax. We parse the source range: true
* option that forces esprima to return positions in the source within
* resulting parse tree.
*
* - Traverse resulting syntax tree, trying to apply a set of visitors to each
* node. Each visitor should provide a .test() function that tests if the
* visitor can process a given node.
*
* - Visitor is responsible for code generation for a given syntax node.
* Generated code is stored in state.g.buffer that is passed to every
* visitor. It's up to the visitor to process the code the way it sees fit.
* All of the current visitors however use both the node and the original
* source to generate transformed code. They use nodes to generate new
* code and they copy the original source, preserving whitespace and comments,
* for the parts they don't care about.
*/
var esprima = require('esprima');
var createState = require('./utils').createState;
var catchup = require('./utils').catchup;
/**
* @param {object} object
* @param {function} visitor
* @param {array} path
* @param {object} state
*/
function traverse(object, path, state) {
var key, child;
if (walker(traverse, object, path, state) === false) {
return;
}
path.unshift(object);
for (key in object) {
// skip obviously wrong attributes
if (key === 'range' || key === 'loc') {
continue;
}
if (object.hasOwnProperty(key)) {
child = object[key];
if (typeof child === 'object' && child !== null) {
child.range && catchup(child.range[0], state);
traverse(child, path, state);
child.range && catchup(child.range[1], state);
}
}
}
path.shift();
}
function walker(traverse, object, path, state) {
var visitors = state.g.visitors;
for (var i = 0; i < visitors.length; i++) {
if (visitors[i].test(object, path, state)) {
return visitors[i](traverse, object, path, state);
}
}
}
function runPass(source, visitors, options) {
var ast;
try {
ast = esprima.parse(source, { comment: true, loc: true, range: true });
} catch (e) {
e.message = 'Parse Error: ' + e.message;
throw e;
}
var state = createState(source, options);
state.g.originalProgramAST = ast;
state.g.visitors = visitors;
if (options.sourceMap) {
var SourceMapGenerator = require('source-map').SourceMapGenerator;
state.g.sourceMap = new SourceMapGenerator({ file: 'transformed.js' });
}
traverse(ast, [], state);
catchup(source.length, state);
return state;
}
/**
* Applies all available transformations to the source
* @param {array} visitors
* @param {string} source
* @param {?object} options
* @return {object}
*/
function transform(visitors, source, options) {
options = options || {};
var state = runPass(source, visitors, options);
var sourceMap = state.g.sourceMap;
if (sourceMap) {
return {
sourceMap: sourceMap,
sourceMapFilename: options.filename || 'source.js',
code: state.g.buffer
};
} else {
return {
code: state.g.buffer
};
}
}
exports.transform = transform;