function transform(ast, opts) { if (opts !== BYPASS_OPTIONS) { _options.set(opts); } _shouldRemoveTrailingWs = Boolean(_options.get('whiteSpace.removeTrailing')); plugins.transformBefore(ast); _tk.eachInBetween(ast.startToken, ast.endToken, preprocessToken); rocambole.moonwalk(ast, transformNode); _tk.eachInBetween(ast.startToken, ast.endToken, postprocessToken); _br.limitBeforeEndOfFile(ast); // indent should come after all other transformations since it depends on // line breaks caused by "parent" nodes, otherwise it will cause conflicts. // it should also happen after the postprocessToken since it adds line breaks // before/after comments and that changes the indent logic indent.transform(ast); // plugin transformation comes after the indentation since we assume user // knows what he is doing (will increase flexibility and allow plugin to // override the indentation logic) // we have an alias "transform" to match v0.3 API, but favor `transformAfter` // moving forward. (we might deprecate "transform" in the future) plugins.transform(ast); plugins.transformAfter(ast); return ast; }
function stripAsserts(node) { // looking only for `libtess.assert` calls if (node.type !== 'CallExpression' || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'assert' || node.callee.object.type !== 'Identifier' || node.callee.object.name !== PACKAGE_NAME) { return; } // need to expand [startToken, endToken] to include beginning whitespace and // ending `;\n` var startToken = node.startToken; if (startToken.prev.type === 'WhiteSpace') { startToken = startToken.prev; } var endToken = node.endToken; if (endToken.next.value === ';' && endToken.next.next.type === 'LineBreak') { endToken = endToken.next.next; } // if removing the assert is going to leave two blank lines, remove one if (startToken.prev.prev.type === 'LineBreak' && endToken.next.type === 'LineBreak') { endToken = endToken.next; } // because our lint rules require things like always using curly braces, we // can safely remove libtess.assert(...) calls without replacing them with // `void 0` or the like. rocamboleToken.eachInBetween(startToken, endToken, rocamboleToken.remove); removedAsserts++; }
function countBrInBetween(start, end) { var count = 0; _tk.eachInBetween(start, end, function(token) { if (_tk.isBr(token)) count++; }); return count; }
function expectedLength(node) { var length = 0; var startOfTheLine = _tk.findPrev(node.startToken, 'LineBreak'); // No linebreak indicates first line of the file, find first token instead if (!startOfTheLine) { startOfTheLine = _tk.findPrev(node.startToken, function(token) { return !token.prev; }); } var firstChar = _tk.findNextNonEmpty(startOfTheLine); // Need to take into consideration the indent _tk.eachInBetween(startOfTheLine, firstChar, function(token) { length += String(token.raw || token.value).length; }); var prev; _tk.eachInBetween(firstChar, node.endToken, function(token) { if (_tk.isEmpty(token)) { // Empty tokens are "collapsed" (multiple linebreaks/whitespace becomes // a single whitespace) length += _tk.isEmpty(prev) ? 0 : 1; prev = token; return; } // Don't collapse objects with line comments; block comments should be okay if (token.type === 'LineComment') { length += 1000; } length += String(token.raw || token.value).length; prev = token; }); if (length === 0) { throw new Error('Failed to measure length of object expression: ' + node.toString()); } return length; }
function transform(ast, opts) { _options.set(opts); _shouldRemoveTrailingWs = Boolean(_options.get('whiteSpace.removeTrailing')); _tk.eachInBetween(ast.startToken, ast.endToken, preprocessToken); rocambole.moonwalk(ast, transformNode); _tk.eachInBetween(ast.startToken, ast.endToken, postprocessToken); // indent should come after all other transformations since it depends on // line breaks caused by "parent" nodes, otherwise it will cause conflicts. // it should also happen after the postprocessToken since it adds line breaks // before/after comments and that changes the indent logic indent.transform(ast); if (process.env.LOG_TOKENS) { _ast.logTokens(ast); } return ast; }
// Below from https://gist.github.com/jzaefferer/23bef744ffea751b2668 // Copyright Jörn Zaefferer; licensed MIT function collapse(node) { // This one seems short _tk.eachInBetween(node.startToken, node.endToken, function(token) { if (_tk.isBr(token)) { // Insert one blank to replace the line break _tk.before(token, { type: 'WhiteSpace', value: ' ' }); // Remove all whitespace/indent after the line break var next = token.next; while (_tk.isEmpty(next)) { _tk.remove(next); next = next.next; } // Remove the line break itself _tk.remove(token); } }); }
removeTask: function(task) { var taskObj = task ? this.tasks[task] : null; if ( !taskObj ) { return this; } // We also need to remove extra whitespace and comma var next = taskObj.node.endToken.next; while ( next.type === 'Punctuator' || next.type === 'LineBreak' || next.type === 'WhiteSpace' ) { next = next.next; } var end = next.prev; // Remove everything tk.eachInBetween(taskObj.node.startToken, end, tk.remove); if ( this.autosave ) { this.save(); } this.reparse(); return this; },
exports._transformNode = function (node) { // If the token is not a variable declaration (e.g. `var`, `let`), exit early // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API // interface VariableDeclaration <: Declaration { // type: "VariableDeclaration"; // declarations: [ VariableDeclarator ]; // kind: "var" | "let" | "const"; // } // interface VariableDeclarator <: Node { // type: "VariableDeclarator"; // id: Pattern; // init: Expression | null; // } if (node.type !== 'VariableDeclaration') { return node; } // If we are inside of a loop, do nothing (e.g. `for`, `while`, `do ... while`) // DEV: Technically, a while/dowhile can't have a `var` but this is for good measure var parentType = node.parent ? node.parent.type : ''; if (parentType.match(/WhileStatement|DoWhileStatement|ForStatement|ForInStatement/)) { return node; } // Determine the terminating character // Example: `var foo = bar;` // varDeclaration = {type: VariableDeclaration, declarations: [...], kind: 'var'} // declarators[*] = {type: VariableDeclarator, id: {type: Identifier, name: 'foo'}, init: {type: Literal, value: 'bar'}} var varDeclaration = node; var declarators = varDeclaration.declarations; // Find the head and tail of the var declaration for reuse among its declaration clones // e.g. `var hello = 'world', goodbye = 'moon';` -> ['var', ' '] = starting tokens; ['hello = world'] = declaration; ...; [';'] = endToken var startingTokens = []; rocamboleToken.eachInBetween(varDeclaration.startToken, declarators[0].startToken.prev, function saveToken (token) { startingTokens.push(token); }); // Determine whether we use automatic semicolon insertion or not var endingSemicolonToken = rocamboleToken.findNext(varDeclaration.endToken.prev, function findStatementTerminator (token) { return rocamboleToken.isSemiColon(token) || rocamboleToken.isBr(token); }); if (rocamboleToken.isBr(endingSemicolonToken)) { endingSemicolonToken = null; } // Additionally, find the whitespace tokens before our `var` started (e.g. all indents/whitespace) var preStartingTokens = []; var token = varDeclaration.startToken.prev; while (token) { // If the token is whitespace or an indent, save it // https://github.com/millermedeiros/rocambole-token/blob/fc03674b38f288dc545db0a5b2bdfd2d96cab170/is.js#L19-L25 if (token.type === 'WhiteSpace' || token.type === 'Indent') { preStartingTokens.unshift(token); token = token.prev; // Otherwise, stop // DEV: We ignore line breaks because this could be the start of a program // Also, line breaks can lead to weird edge cases so we keep it consistent/predictable with a single one } else { break; } } // Copy over the preStartingTokens as betweenDeclarationTokens and add in `;` (if applicable) and `\n` // DEV: We add from the left of the queue so `\n` then `;` to get `[';', '\n', ' ']` var betweenDeclarationTokens = preStartingTokens.slice(); betweenDeclarationTokens.unshift(exports.createToken({ type: 'LineBreak', value: options.lineBreak.value, root: varDeclaration.startToken.root })); if (endingSemicolonToken) { betweenDeclarationTokens.unshift(exports.cloneToken(endingSemicolonToken)); } // Generate a `var` for each of the declarators // e.g. `var hello = 'world', goodbye = 'moon';` -> `var hello = 'world'; var goodbye = 'moon';` var declarations = declarators.map(function generateDeclaration (declarator, index) { // DEV: A brief refresher on nodes and tokens // Nodes are the AST representation of parts of a program (e.g. Identifier, VariableDeclaration) // Tokens are the actual chunks of code these represent (e.g. Keyword, WhiteSpace) // Tokens can be present without there being a node related to them // Nodes have a prev (previous node on the same level), next (next node on the same level), // parent (node containing our node), and sometimes something like a `body` key where they declare child nodes // `body` varies from node type to node type // Tokens don't have levels but are one giant chain // Tokens have next (next token to render), prev (previous token to render), // root (root node of the entire token chain -- i.e. a Program node) // Nodes also have startToken and endToken which are the tokens that a node will start/end on // (e.g. `var` is the start token for a VariableDeclaration) // The only attachment from tokens to nodes is via `range` but this is brittle in rocambole so avoid it // Generate a new declaration similar to the original // Example: `var hello = 'world', goodbye = 'moon';` should use `var` and have a trailing semicolon `;` // https://github.com/millermedeiros/rocambole/blob/a3d0d63d58b769d13bad288aca32c6e2f7766542/rocambole.js#L69-L74 var declaration = { type: varDeclaration.type, // should always be `VariableDeclaration` declarations: [declarator], kind: varDeclaration.kind, // (e.g. `var`, `let`) toString: varDeclaration.toString // prev: bound later // next: bound later // startToken: bound later // endToken: bound later }; return declaration; }); // Set up linkages for nodes // DEV: None of these changes will affect the token chain // However, each `node.toString()` is more/less impractical as there are no tokens bound to declarations declarations.forEach(function connectNodes (declaration, index) { // Attach declaration as the declarator's parent node var declarator = declaration.declarations[0]; declarator.parent = declaration; // If this is the first node, connect to var declaration's previous node if (index === 0) { var varDeclarationPrevNode = varDeclaration.prev; if (varDeclarationPrevNode) { declaration.prev = varDeclarationPrevNode; varDeclarationPrevNode.next = declaration; } // Otherwise, connect to the last declaration } else { var lastDeclarationNode = declarations[index - 1]; declaration.prev = lastDeclarationNode; lastDeclarationNode.next = declaration; } // If this is the last node, connect it to var declaration's next node if (index === declarations.length - 1) { var varDeclarationNextNode = varDeclaration.next; if (varDeclarationNextNode) { declaration.next = varDeclarationNextNode; varDeclarationNextNode.prev = declaration; } // Otherwise, do nothing as we will connect to the next node via the previous if/else } else { // Do nothing } // In all cases, save this var declaration's parent node as this declaration node's parent declaration.parent = varDeclaration.parent; }); // Swap the declarations in the `body` of the parent block statement // e.g. `BlockStatement.body = [{orig VariableDeclaration}, some other expressions]` // -> `BlockStatement.body = [{new VariableDeclaration}, {another new VariableDeclaration}, some other expressions]` var varDeclarationParentNode = varDeclaration.parent; // DEV: Our `body` can be `body` in `Program` or `BlockStatement` but is `consequent` for `SwitchCase's` // https://github.com/estree/estree/blob/9a35a3d091af6ff9cf7fadfe27c49e22533b2bce/spec.md#blockstatement // https://github.com/estree/estree/blob/9a35a3d091af6ff9cf7fadfe27c49e22533b2bce/spec.md#switchcase var varDeclarationParentBody = varDeclarationParentNode.body || varDeclarationParentNode.consequent; var varDeclarationParentBodyIndex = varDeclarationParentBody.indexOf(varDeclaration); var spliceArgs = [varDeclarationParentBodyIndex, 1].concat(declarations); varDeclarationParentBody.splice.apply(varDeclarationParentBody, spliceArgs); // Handle token bindings (aka the annoying/hard part) var queue = []; declarations.forEach(function defineAndAttachTokens (declaration, index) { // DEV: We have a few linkages to perform: // Example: HEAD; var a = 1, b = 2; TAIL // VariableDeclaration tokens = ['var', ' ', 'a', ' ', '=', ..., ';'] // VariableDeclarator tokens = ['a', ' ', '=', ..., '1'] var declarator = declaration.declarations[0]; // If this is the first VariableDeclaration if (index === 0) { // Define STARTING tokens for each VariableDeclaration (e.g. `var ` for `var a = 1`) // DEV: `varDeclaration.startToken` is already linked with all previous tokens in the application, making this transition easy // DEV: `varDeclaration.startToken` will be the `var` of `var ` (i.e. `['var', ' ']`, it's the `'var'`) var firstStartingToken = varDeclaration.startToken; var lastStartingToken = null; // Save ORIGINAL VariableDeclaration token (which links to HEAD) AS FIRST VariableDeclaration token (e.g. reuse the existing `var ` token chain) declaration.startToken = firstStartingToken; // Otherwise, (we are a non-first VariableDeclaration) } else { // Insert leading content for each non-first VariableDeclaration between VariableDeclaration's // Create `var ` tokens var newStartingTokens = exports.cloneTokenChain(startingTokens); // DEV: This is always defined as we always need a `var` keyword var firstStartingToken = newStartingTokens[0]; var lastStartingToken = newStartingTokens[newStartingTokens.length - 1]; // Save firstStartingToken and lastStartingToken for later declaration.firstStartingToken = firstStartingToken; declaration.lastStartingToken = lastStartingToken; // Attach FIRST `var ` token TO last VariableDeclaration END token // and attach FIRST `var ` token AS current VariableDeclaration START token var lastDeclaration = declarations[index - 1]; lastDeclaration.endToken.next = firstStartingToken; firstStartingToken.prev = lastDeclaration.endToken; declaration.startToken = firstStartingToken; // Attach LAST `var ` token TO FIRST VariableDeclarator token AS PREVIOUS token declarator.startToken.prev = lastStartingToken; lastStartingToken.next = declarator.startToken; } // If this is a non-last VariableDeclaration if (index < declarations.length - 1) { // Define ENDING tokens for each VariableDeclaration (e.g. `;\n ` for `var a = 1;\n `) // Insert terminating content for each VariableDeclaration between VariableDeclaration's (e.g. `;\n `) // Create `;\n ` tokens var newBetweenTokens = exports.cloneTokenChain(betweenDeclarationTokens); var firstBetweenToken = newBetweenTokens[0]; var lastBetweenToken = newBetweenTokens[newBetweenTokens.length - 1]; // Attach LAST `;\n ` token TO current VariableDeclaration AS END token // and attach LAST `;\n ` token to next VariableDeclaration AS PREVIOUS token declaration.endToken = lastBetweenToken; var nextDeclaration = declarations[index + 1]; // DEV: We need to queue these actions since `nextDeclaration.startToken` doesn't exist yet =/ // QUEUE: nextDeclaration.startToken.prev = lastBetweenToken; // QUEUE: lastBetweenToken.next = nextDeclaration.startToken; queue.push({ srcNode: nextDeclaration, srcTokenKey: 'startToken', srcDirectionKey: 'prev', targetToken: lastBetweenToken, targetDirectionKey: 'next' }); // Attach FIRST `;\n ` token TO LAST VariableDeclarator token AS NEXT token declarator.endToken.next = firstBetweenToken; firstBetweenToken.prev = declarator.endToken; // Otherwise, (this is the last VariableDeclaration) } else { // DEV: `lastDeclarator.endToken.next` is already linked with all previous tokens in the application, making this transition easy // DEV: `lastDeclarator.endToken.next` will be the `;` of `;\n ` (i.e. `[';', '\n', ' ']`, it's the `';'`) var lastDeclarator = varDeclaration.declarations[index]; var firstEndingToken = lastDeclarator.endToken.next; var lastEndingToken = null; // If there is no first ending token, then we are at the end of the program (e.g. `var a = 1EOF`) if (!firstEndingToken) { // Save the same `lastDeclarator.endToken` as our `declaration.endToken` for consistency // DEV: `lastDeclarator.endToken` is already bound to `Program.endToken` as this was the original setup declarator.endToken = lastDeclarator.endToken; // Save ORIGINAL VariableDeclaration token (which links to TAIL) AS START token for VariableDeclaration END token (e.g. reuse the existing `var ` token chain) } else { declaration.endToken = firstEndingToken; } } }); // Walk over our queued actions and bind them queue.forEach(function saveQueueAction (action) { var srcNode = action.srcNode; var srcToken = srcNode[action.srcTokenKey]; var targetToken = action.targetToken; srcToken[action.srcDirectionKey] = targetToken; targetToken[action.targetDirectionKey] = srcToken; }); // Return the updated node return node; };
exports.stringBefore = function(str) { rocambole.parseFn = espree.parse.bind(espree); var ast = rocambole.parse(str, parseOptions); tk.eachInBetween(ast.startToken, ast.endToken, processToken); return ast.toString(); };
function removeNode(node) { token.eachInBetween(node.startToken, node.endToken, token.remove); }