mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
333 lines
8.9 KiB
JavaScript
333 lines
8.9 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*/
|
|
|
|
/**
|
|
* State represents the given parser state. It has a local and global parts.
|
|
* Global contains parser position, source, etc. Local contains scope based
|
|
* properties, like current class name. State should contain all the info
|
|
* required for transformation. It's the only mandatory object that is being
|
|
* passed to every function in transform chain.
|
|
*
|
|
* @param {String} source
|
|
* @param {Object} transformOptions
|
|
* @return {Object}
|
|
*/
|
|
function createState(source, transformOptions) {
|
|
return {
|
|
/**
|
|
* Name of the super class variable
|
|
* @type {String}
|
|
*/
|
|
superVar: '',
|
|
/**
|
|
* Name of the enclosing class scope
|
|
* @type {String}
|
|
*/
|
|
scopeName: '',
|
|
/**
|
|
* Global state (not affected by updateState)
|
|
* @type {Object}
|
|
*/
|
|
g: {
|
|
/**
|
|
* A set of general options that transformations can consider while doing
|
|
* a transformation:
|
|
*
|
|
* - minify
|
|
* Specifies that transformation steps should do their best to minify
|
|
* the output source when possible. This is useful for places where
|
|
* minification optimizations are possible with higher-level context
|
|
* info than what jsxmin can provide.
|
|
*
|
|
* For example, the ES6 class transform will minify munged private
|
|
* variables if this flag is set.
|
|
*/
|
|
opts: transformOptions,
|
|
/**
|
|
* Current position in the source code
|
|
* @type {Number}
|
|
*/
|
|
position: 0,
|
|
/**
|
|
* Buffer containing the result
|
|
* @type {String}
|
|
*/
|
|
buffer: '',
|
|
/**
|
|
* Indentation offset (only negative offset is supported now)
|
|
* @type {Number}
|
|
*/
|
|
indentBy: 0,
|
|
/**
|
|
* Source that is being transformed
|
|
* @type {String}
|
|
*/
|
|
source: source,
|
|
|
|
/**
|
|
* Cached parsed docblock (see getDocblock)
|
|
* @type {object}
|
|
*/
|
|
docblock: null,
|
|
|
|
/**
|
|
* Whether the thing was used
|
|
* @type {Boolean}
|
|
*/
|
|
tagNamespaceUsed: false,
|
|
|
|
/**
|
|
* If using bolt xjs transformation
|
|
* @type {Boolean}
|
|
*/
|
|
isBolt: undefined,
|
|
|
|
/**
|
|
* Whether to record source map (expensive) or not
|
|
* @type {SourceMapGenerator|null}
|
|
*/
|
|
sourceMap: null,
|
|
|
|
/**
|
|
* Filename of the file being processed. Will be returned as a source
|
|
* attribute in the source map
|
|
*/
|
|
sourceMapFilename: 'source.js',
|
|
|
|
/**
|
|
* Only when source map is used: last line in the source for which
|
|
* source map was generated
|
|
* @type {Number}
|
|
*/
|
|
sourceLine: 1,
|
|
|
|
/**
|
|
* Only when source map is used: last line in the buffer for which
|
|
* source map was generated
|
|
* @type {Number}
|
|
*/
|
|
bufferLine: 1,
|
|
|
|
/**
|
|
* The top-level Program AST for the original file.
|
|
*/
|
|
originalProgramAST: null,
|
|
|
|
sourceColumn: 0,
|
|
bufferColumn: 0
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Updates a copy of a given state with "update" and returns an updated state.
|
|
*
|
|
* @param {Object} state
|
|
* @param {Object} update
|
|
* @return {Object}
|
|
*/
|
|
function updateState(state, update) {
|
|
return {
|
|
g: state.g,
|
|
superVar: update.superVar || state.superVar,
|
|
scopeName: update.scopeName || state.scopeName
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Given a state fill the resulting buffer from the original source up to
|
|
* the end
|
|
* @param {Number} end
|
|
* @param {Object} state
|
|
* @param {Function?} contentTransformer Optional callback to transform newly
|
|
* added content.
|
|
*/
|
|
function catchup(end, state, contentTransformer) {
|
|
if (end < state.g.position) {
|
|
// cannot move backwards
|
|
return;
|
|
}
|
|
var source = state.g.source.substring(state.g.position, end);
|
|
var transformed = updateIndent(source, state);
|
|
if (state.g.sourceMap && transformed) {
|
|
// record where we are
|
|
state.g.sourceMap.addMapping({
|
|
generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
|
|
original: { line: state.g.sourceLine, column: state.g.sourceColumn },
|
|
source: state.g.sourceMapFilename
|
|
});
|
|
|
|
// record line breaks in transformed source
|
|
var sourceLines = source.split('\n');
|
|
var transformedLines = transformed.split('\n');
|
|
// Add line break mappings between last known mapping and the end of the
|
|
// added piece. So for the code piece
|
|
// (foo, bar);
|
|
// > var x = 2;
|
|
// > var b = 3;
|
|
// var c =
|
|
// only add lines marked with ">": 2, 3.
|
|
for (var i = 1; i < sourceLines.length - 1; i++) {
|
|
state.g.sourceMap.addMapping({
|
|
generated: { line: state.g.bufferLine, column: 0 },
|
|
original: { line: state.g.sourceLine, column: 0 },
|
|
source: state.g.sourceMapFilename
|
|
});
|
|
state.g.sourceLine++;
|
|
state.g.bufferLine++;
|
|
}
|
|
// offset for the last piece
|
|
if (sourceLines.length > 1) {
|
|
state.g.sourceLine++;
|
|
state.g.bufferLine++;
|
|
state.g.sourceColumn = 0;
|
|
state.g.bufferColumn = 0;
|
|
}
|
|
state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
|
|
state.g.bufferColumn +=
|
|
transformedLines[transformedLines.length - 1].length;
|
|
}
|
|
state.g.buffer +=
|
|
contentTransformer ? contentTransformer(transformed) : transformed;
|
|
state.g.position = end;
|
|
}
|
|
|
|
/**
|
|
* Applies `catchup` but passing in a function that removes any non-whitespace
|
|
* characters.
|
|
*/
|
|
var re = /(\S)/g;
|
|
function stripNonWhite(value) {
|
|
return value.replace(re, function() {
|
|
return '';
|
|
});
|
|
}
|
|
/**
|
|
* Catches up as `catchup` but turns each non-white character into a space.
|
|
*/
|
|
function catchupWhiteSpace(end, state) {
|
|
catchup(end, state, stripNonWhite);
|
|
}
|
|
|
|
/**
|
|
* Same as catchup but does not touch the buffer
|
|
* @param {Number} end
|
|
* @param {Object} state
|
|
*/
|
|
function move(end, state) {
|
|
// move the internal cursors
|
|
if (state.g.sourceMap) {
|
|
if (end < state.g.position) {
|
|
state.g.position = 0;
|
|
state.g.sourceLine = 1;
|
|
state.g.sourceColumn = 0;
|
|
}
|
|
|
|
var source = state.g.source.substring(state.g.position, end);
|
|
var sourceLines = source.split('\n');
|
|
if (sourceLines.length > 1) {
|
|
state.g.sourceLine += sourceLines.length - 1;
|
|
state.g.sourceColumn = 0;
|
|
}
|
|
state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
|
|
}
|
|
state.g.position = end;
|
|
}
|
|
|
|
/**
|
|
* Appends a string of text to the buffer
|
|
* @param {String} string
|
|
* @param {Object} state
|
|
*/
|
|
function append(string, state) {
|
|
if (state.g.sourceMap && string) {
|
|
state.g.sourceMap.addMapping({
|
|
generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
|
|
original: { line: state.g.sourceLine, column: state.g.sourceColumn },
|
|
source: state.g.sourceMapFilename
|
|
});
|
|
var transformedLines = string.split('\n');
|
|
if (transformedLines.length > 1) {
|
|
state.g.bufferLine += transformedLines.length - 1;
|
|
state.g.bufferColumn = 0;
|
|
}
|
|
state.g.bufferColumn +=
|
|
transformedLines[transformedLines.length - 1].length;
|
|
}
|
|
state.g.buffer += string;
|
|
}
|
|
|
|
/**
|
|
* Update indent using state.indentBy property. Indent is measured in
|
|
* double spaces. Updates a single line only.
|
|
*
|
|
* @param {String} str
|
|
* @param {Object} state
|
|
* @return {String}
|
|
*/
|
|
function updateIndent(str, state) {
|
|
for (var i = 0; i < -state.g.indentBy; i++) {
|
|
str = str.replace(/(^|\n)( {2}|\t)/g, '$1');
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Calculates indent from the beginning of the line until "start" or the first
|
|
* character before start.
|
|
* @example
|
|
* " foo.bar()"
|
|
* ^
|
|
* start
|
|
* indent will be 2
|
|
*
|
|
* @param {Number} start
|
|
* @param {Object} state
|
|
* @return {Number}
|
|
*/
|
|
function indentBefore(start, state) {
|
|
var end = start;
|
|
start = start - 1;
|
|
|
|
while (start > 0 && state.g.source[start] != '\n') {
|
|
if (!state.g.source[start].match(/[ \t]/)) {
|
|
end = start;
|
|
}
|
|
start--;
|
|
}
|
|
return state.g.source.substring(start + 1, end);
|
|
}
|
|
|
|
function getDocblock(state) {
|
|
if (!state.g.docblock) {
|
|
var docblock = require('./docblock');
|
|
state.g.docblock =
|
|
docblock.parseAsObject(docblock.extract(state.g.source));
|
|
}
|
|
return state.g.docblock;
|
|
}
|
|
|
|
exports.catchup = catchup;
|
|
exports.catchupWhiteSpace = catchupWhiteSpace;
|
|
exports.append = append;
|
|
exports.move = move;
|
|
exports.updateIndent = updateIndent;
|
|
exports.indentBefore = indentBefore;
|
|
exports.updateState = updateState;
|
|
exports.createState = createState;
|
|
exports.getDocblock = getDocblock;
|