Files
Zos/Skills/@be/node_modules/jstransform/src/utils.js

480 lines
12 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.
*/
/*jslint node: true*/
/**
* A `state` object represents the state of the parser. It has "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, rootNode, transformOptions) {
return {
/**
* A tree representing the current local scope (and its lexical scope chain)
* Useful for tracking identifiers from parent scopes, etc.
* @type {Object}
*/
localScope: {
parentNode: rootNode,
parentScope: null,
identifiers: {}
},
/**
* The name (and, if applicable, expression) of the super class
* @type {Object}
*/
superClass: null,
/**
* The namespace to use when munging identifiers
* @type {String}
*/
mungeNamespace: '',
/**
* Ref to the node for the FunctionExpression of the enclosing
* MethodDefinition
* @type {Object}
*/
methodFuncNode: null,
/**
* Name of the enclosing class
* @type {String}
*/
className: null,
/**
* Whether we're currently within a `strict` scope
* @type {Bool}
*/
scopeIsStrict: null,
/**
* 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) {
var ret = Object.create(state);
Object.keys(update).forEach(function(updatedKey) {
ret[updatedKey] = update[updatedKey];
});
return ret;
}
/**
* 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;
}
/**
* Removes all non-whitespace characters
*/
var reNonWhite = /(\S)/g;
function stripNonWhite(value) {
return value.replace(reNonWhite, function() {
return '';
});
}
/**
* Catches up as `catchup` but removes all non-whitespace characters.
*/
function catchupWhiteSpace(end, state) {
catchup(end, state, stripNonWhite);
}
/**
* Removes all non-newline characters
*/
var reNonNewline = /[^\n]/g;
function stripNonNewline(value) {
return value.replace(reNonNewline, function() {
return '';
});
}
/**
* Catches up as `catchup` but removes all non-newline characters.
*
* Equivalent to appending as many newlines as there are in the original source
* between the current position and `end`.
*/
function catchupNewlines(end, state) {
catchup(end, state, stripNonNewline);
}
/**
* 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} str
* @param {object} state
*/
function append(str, state) {
if (state.g.sourceMap && str) {
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 = str.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 += str;
}
/**
* 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;
}
function identWithinLexicalScope(identName, state, stopBeforeNode) {
var currScope = state.localScope;
while (currScope) {
if (currScope.identifiers[identName] !== undefined) {
return true;
}
if (stopBeforeNode && currScope.parentNode === stopBeforeNode) {
break;
}
currScope = currScope.parentScope;
}
return false;
}
function identInLocalScope(identName, state) {
return state.localScope.identifiers[identName] !== undefined;
}
function declareIdentInLocalScope(identName, state) {
state.localScope.identifiers[identName] = true;
}
/**
* Apply the given analyzer function to the current node. If the analyzer
* doesn't return false, traverse each child of the current node using the given
* traverser function.
*
* @param {function} analyzer
* @param {function} traverser
* @param {object} node
* @param {function} visitor
* @param {array} path
* @param {object} state
*/
function analyzeAndTraverse(analyzer, traverser, node, path, state) {
var key, child;
if (node.type) {
if (analyzer(node, path, state) === false) {
return;
}
path.unshift(node);
}
for (key in node) {
// skip obviously wrong attributes
if (key === 'range' || key === 'loc') {
continue;
}
if (node.hasOwnProperty(key)) {
child = node[key];
if (typeof child === 'object' && child !== null) {
traverser(child, path, state);
}
}
}
node.type && path.shift();
}
/**
* Checks whether a node or any of its sub-nodes contains
* a syntactic construct of the passed type.
* @param {object} node - AST node to test.
* @param {string} type - node type to lookup.
*/
function containsChildOfType(node, type) {
var foundMatchingChild = false;
function nodeTypeAnalyzer(node) {
if (node.type === type) {
foundMatchingChild = true;
return false;
}
}
function nodeTypeTraverser(child, path, state) {
if (!foundMatchingChild) {
foundMatchingChild = containsChildOfType(child, type);
}
}
analyzeAndTraverse(
nodeTypeAnalyzer,
nodeTypeTraverser,
node,
[]
);
return foundMatchingChild;
}
exports.append = append;
exports.catchup = catchup;
exports.catchupWhiteSpace = catchupWhiteSpace;
exports.catchupNewlines = catchupNewlines;
exports.containsChildOfType = containsChildOfType;
exports.createState = createState;
exports.declareIdentInLocalScope = declareIdentInLocalScope;
exports.getDocblock = getDocblock;
exports.identWithinLexicalScope = identWithinLexicalScope;
exports.identInLocalScope = identInLocalScope;
exports.indentBefore = indentBefore;
exports.move = move;
exports.updateIndent = updateIndent;
exports.updateState = updateState;
exports.analyzeAndTraverse = analyzeAndTraverse;