├── .gitignore ├── package.json ├── .eslintrc ├── src ├── toTokens.js ├── index.js ├── attachComments.js ├── toToken.js ├── convertTemplateType.js └── toAST.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.cache 3 | .DS_Store 4 | node_modules 5 | lib 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acorn-to-esprima", 3 | "version": "2.0.8", 4 | "description": "Convert acorn tokens to esprima", 5 | "main": "src/index.js", 6 | "repository": "babel/acorn-to-esprima", 7 | "keywords": [ 8 | "acorn", 9 | "esprima", 10 | "babel-eslint", 11 | "babel-jscs", 12 | "babel" 13 | ], 14 | "author": "Sebastian McKenzie ", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/babel/acorn-to-esprima/issues" 18 | }, 19 | "homepage": "https://github.com/babel/acorn-to-esprima#readme" 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "strict": 0, 4 | "no-underscore-dangle": 0, 5 | "curly": 0, 6 | "no-multi-spaces": 0, 7 | "key-spacing": 0, 8 | "no-return-assign": 0, 9 | "consistent-return": 0, 10 | "no-shadow": 0, 11 | "comma-dangle": 0, 12 | "no-use-before-define": 0, 13 | "no-empty": 0, 14 | "new-parens": 0, 15 | "no-cond-assign": 0, 16 | "no-fallthrough": 0, 17 | "new-cap": 0, 18 | "no-loop-func": 0, 19 | "no-unreachable": 0, 20 | "no-process-exit": 0, 21 | "quotes": [1, "double", "avoid-escape"] 22 | }, 23 | "env": { 24 | "node": true, 25 | "mocha": true 26 | } 27 | } -------------------------------------------------------------------------------- /src/toTokens.js: -------------------------------------------------------------------------------- 1 | // var tt = require("babylon").tokTypes; 2 | // var tt = require("babel-core").acorn.tokTypes; 3 | var convertTemplateType = require("./convertTemplateType"); 4 | var toToken = require("./toToken"); 5 | 6 | module.exports = function (tokens, tt, code) { 7 | // transform tokens to type "Template" 8 | convertTemplateType(tokens, tt); 9 | var transformedTokens = tokens.filter(function (token) { 10 | return token.type !== "CommentLine" && token.type !== "CommentBlock"; 11 | }); 12 | 13 | for (var i = 0, l = transformedTokens.length; i < l; i++) { 14 | transformedTokens[i] = toToken(transformedTokens[i], tt, code); 15 | } 16 | 17 | return transformedTokens; 18 | }; 19 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | exports.attachComments = require("./attachComments"); 2 | 3 | exports.toTokens = require("./toTokens"); // requires babel tokTypes 4 | exports.toAST = require("./toAST"); // requires traversal method 5 | 6 | exports.convertComments = function (comments) { 7 | for (var i = 0; i < comments.length; i++) { 8 | var comment = comments[i]; 9 | if (comment.type === "CommentBlock") { 10 | comment.type = "Block"; 11 | } else if (comment.type === "CommentLine") { 12 | comment.type = "Line"; 13 | } 14 | // sometimes comments don't get ranges computed, 15 | // even with options.ranges === true 16 | if (!comment.range) { 17 | comment.range = [comment.start, comment.end]; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Sebastian McKenzie 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # acorn-to-esprima 2 | 3 | Some functions to help transform an acorn/babel ast to esprima format. 4 | 5 | Primarily for use in [babel-eslint](https://github.com/babel/babel-eslint), [babel-jscs](https://github.com/jscs-dev/babel-jscs), and [ast explorer](https://github.com/fkling/esprima_ast_explorer) 6 | 7 | **There are no dependencies** (the methods were changed to pass in dependencies instead) 8 | 9 | The current functions exposed are: 10 | 11 | - `function attachComments(ast, comments, tokens)` 12 | - This modifies the comments passed in. 13 | - `function toTokens(tokens, tt)` 14 | - `tt` is `require("babel-core").acorn.tokTypes` 15 | - Converts template string tokens (`convertTemplateType`) 16 | - filters out comment tokens 17 | - runs `toToken` over each token 18 | - `function toToken(token, tt)` 19 | - Sets `token.type`, `token.range`, and `token.value` 20 | - `function toAST(ast, traverse)` 21 | - `traverse` is `require("babel-core").traverse;` 22 | - traverses over the ast and makes any necessary changes (usually es6+) 23 | - `function convertComments(comments)` 24 | - Modifies `comment.type` 25 | 26 | How to use: 27 | 28 | Check out the parse method of https://github.com/babel/babel-eslint/blob/master/index.js 29 | ```js 30 | // example 31 | exports.parse = function (code) { 32 | var comments = opts.onComment = []; 33 | var tokens = opts.onToken = []; 34 | 35 | var ast; 36 | try { 37 | ast = parse(code, { 38 | locations: true, 39 | ranges: true 40 | }); 41 | } catch (err) { throw err; } 42 | 43 | tokens.pop(); 44 | ast.tokens = acornToEsprima.toTokens(tokens, tt); 45 | 46 | acornToEsprima.convertComments(comments); 47 | ast.comments = comments; 48 | acornToEsprima.attachComments(ast, comments, ast.tokens); 49 | 50 | acornToEsprima.toAST(ast, traverse); 51 | 52 | return ast; 53 | } 54 | ``` -------------------------------------------------------------------------------- /src/attachComments.js: -------------------------------------------------------------------------------- 1 | // comment fixes 2 | module.exports = function (ast, comments, tokens) { 3 | if (comments.length) { 4 | var firstComment = comments[0]; 5 | var lastComment = comments[comments.length - 1]; 6 | // fixup program start 7 | if (!tokens.length) { 8 | // if no tokens, the program starts at the end of the last comment 9 | ast.start = lastComment.end; 10 | ast.loc.start.line = lastComment.loc.end.line; 11 | ast.loc.start.column = lastComment.loc.end.column; 12 | 13 | if (ast.leadingComments === null && ast.innerComments.length) { 14 | ast.leadingComments = ast.innerComments; 15 | } 16 | } else if (firstComment.start < tokens[0].start) { 17 | // if there are comments before the first token, the program starts at the first token 18 | var token = tokens[0]; 19 | ast.start = token.start; 20 | ast.loc.start.line = token.loc.start.line; 21 | ast.loc.start.column = token.loc.start.column; 22 | 23 | // estraverse do not put leading comments on first node when the comment 24 | // appear before the first token 25 | if (ast.body.length) { 26 | var node = ast.body[0]; 27 | node.leadingComments = []; 28 | var firstTokenStart = token.start; 29 | var len = comments.length; 30 | for (var i = 0; i < len && comments[i].start < firstTokenStart; i++) { 31 | node.leadingComments.push(comments[i]); 32 | } 33 | } 34 | } 35 | // fixup program end 36 | if (tokens.length) { 37 | var lastToken = tokens[tokens.length - 1]; 38 | if (lastComment.end > lastToken.end) { 39 | // If there is a comment after the last token, the program ends at the 40 | // last token and not the comment 41 | ast.end = lastToken.end; 42 | ast.loc.end.line = lastToken.loc.end.line; 43 | ast.loc.end.column = lastToken.loc.end.column; 44 | } 45 | } 46 | } else { 47 | if (!tokens.length) { 48 | ast.loc.start.line = 0; 49 | ast.loc.end.line = 0; 50 | } 51 | } 52 | if (ast.body && ast.body.length > 0) { 53 | ast.loc.start.line = ast.body[0].loc.start.line; 54 | ast.start = ast.body[0].start; 55 | } 56 | ast.range[0] = ast.start; 57 | ast.range[1] = ast.end; 58 | }; 59 | -------------------------------------------------------------------------------- /src/toToken.js: -------------------------------------------------------------------------------- 1 | module.exports = function (token, tt, source) { 2 | var type = token.type; 3 | token.range = [token.start, token.end]; 4 | 5 | if (type === tt.name) { 6 | token.type = "Identifier"; 7 | } else if (type === tt.semi || type === tt.comma || 8 | type === tt.parenL || type === tt.parenR || 9 | type === tt.braceL || type === tt.braceR || 10 | type === tt.slash || type === tt.dot || 11 | type === tt.bracketL || type === tt.bracketR || 12 | type === tt.ellipsis || type === tt.arrow || 13 | type === tt.star || type === tt.incDec || 14 | type === tt.colon || type === tt.question || 15 | type === tt.template || type === tt.backQuote || 16 | type === tt.dollarBraceL || type === tt.at || 17 | type === tt.logicalOR || type === tt.logicalAND || 18 | type === tt.bitwiseOR || type === tt.bitwiseXOR || 19 | type === tt.bitwiseAND || type === tt.equality || 20 | type === tt.relational || type === tt.bitShift || 21 | type === tt.plusMin || type === tt.modulo || 22 | type === tt.exponent || type === tt.prefix || 23 | type === tt.doubleColon || 24 | type.isAssign) { 25 | token.type = "Punctuator"; 26 | if (!token.value) token.value = type.label; 27 | } else if (type === tt.jsxTagStart) { 28 | token.type = "Punctuator"; 29 | token.value = "<"; 30 | } else if (type === tt.jsxTagEnd) { 31 | token.type = "Punctuator"; 32 | token.value = ">"; 33 | } else if (type === tt.jsxName) { 34 | token.type = "JSXIdentifier"; 35 | } else if (type === tt.jsxText) { 36 | token.type = "JSXText"; 37 | } else if (type.keyword === "null") { 38 | token.type = "Null"; 39 | } else if (type.keyword === "false" || type.keyword === "true") { 40 | token.type = "Boolean"; 41 | } else if (type.keyword) { 42 | token.type = "Keyword"; 43 | } else if (type === tt.num) { 44 | token.type = "Numeric"; 45 | token.value = source.slice(token.start, token.end); 46 | } else if (type === tt.string) { 47 | token.type = "String"; 48 | token.value = source.slice(token.start, token.end); 49 | } else if (type === tt.regexp) { 50 | token.type = "RegularExpression"; 51 | var value = token.value; 52 | token.regex = { 53 | pattern: value.pattern, 54 | flags: value.flags 55 | }; 56 | token.value = "/" + value.pattern + "/" + value.flags; 57 | } 58 | 59 | return token; 60 | }; 61 | -------------------------------------------------------------------------------- /src/convertTemplateType.js: -------------------------------------------------------------------------------- 1 | module.exports = function (tokens, tt) { 2 | var startingToken = 0; 3 | var currentToken = 0; 4 | var numBraces = 0; // track use of {} 5 | var numBackQuotes = 0; // track number of nested templates 6 | 7 | function isBackQuote(token) { 8 | return tokens[token].type === tt.backQuote; 9 | } 10 | 11 | function isTemplateStarter(token) { 12 | return isBackQuote(token) || 13 | // only can be a template starter when in a template already 14 | tokens[token].type === tt.braceR && numBackQuotes > 0; 15 | } 16 | 17 | function isTemplateEnder(token) { 18 | return isBackQuote(token) || 19 | tokens[token].type === tt.dollarBraceL; 20 | } 21 | 22 | // append the values between start and end 23 | function createTemplateValue(start, end) { 24 | var value = ""; 25 | while (start <= end) { 26 | if (tokens[start].value) { 27 | value += tokens[start].value; 28 | } else if (tokens[start].type !== tt.template) { 29 | value += tokens[start].type.label; 30 | } 31 | start++; 32 | } 33 | return value; 34 | } 35 | 36 | // create Template token 37 | function replaceWithTemplateType(start, end) { 38 | var templateToken = { 39 | type: "Template", 40 | value: createTemplateValue(start, end), 41 | start: tokens[start].start, 42 | end: tokens[end].end, 43 | loc: { 44 | start: tokens[start].loc.start, 45 | end: tokens[end].loc.end 46 | } 47 | }; 48 | 49 | // put new token in place of old tokens 50 | tokens.splice(start, end - start + 1, templateToken); 51 | } 52 | 53 | function trackNumBraces(token) { 54 | if (tokens[token].type === tt.braceL) { 55 | numBraces++; 56 | } else if (tokens[token].type === tt.braceR) { 57 | numBraces--; 58 | } 59 | } 60 | 61 | while (startingToken < tokens.length) { 62 | // template start: check if ` or } 63 | if (isTemplateStarter(startingToken) && numBraces === 0) { 64 | if (isBackQuote(startingToken)) { 65 | numBackQuotes++; 66 | } 67 | 68 | currentToken = startingToken + 1; 69 | 70 | // check if token after template start is "template" 71 | if (currentToken >= tokens.length - 1 || tokens[currentToken].type !== tt.template) { 72 | break; 73 | } 74 | 75 | // template end: find ` or ${ 76 | while (!isTemplateEnder(currentToken)) { 77 | if (currentToken >= tokens.length - 1) { 78 | break; 79 | } 80 | currentToken++; 81 | } 82 | 83 | if (isBackQuote(currentToken)) { 84 | numBackQuotes--; 85 | } 86 | // template start and end found: create new token 87 | replaceWithTemplateType(startingToken, currentToken); 88 | } else if (numBackQuotes > 0) { 89 | trackNumBraces(startingToken); 90 | } 91 | startingToken++; 92 | } 93 | } -------------------------------------------------------------------------------- /src/toAST.js: -------------------------------------------------------------------------------- 1 | // var traverse = require("babel-core").traverse; 2 | 3 | var source; 4 | 5 | module.exports = function (ast, traverse, code) { 6 | source = code; 7 | ast.sourceType = "module"; 8 | ast.range = [ast.start, ast.end]; 9 | traverse(ast, astTransformVisitor); 10 | }; 11 | 12 | function changeToLiteral(node) { 13 | node.type = 'Literal'; 14 | if (!node.raw) { 15 | if (node.extra && node.extra.raw) { 16 | node.raw = node.extra.raw; 17 | } else { 18 | node.raw = source.slice(node.start, node.end); 19 | } 20 | } 21 | } 22 | 23 | var astTransformVisitor = { 24 | noScope: true, 25 | enter: function (path) { 26 | var node = path.node; 27 | node.range = [node.start, node.end]; 28 | 29 | // private var to track original node type 30 | node._babelType = node.type; 31 | 32 | if (node.innerComments) { 33 | node.trailingComments = node.innerComments; 34 | delete node.innerComments; 35 | } 36 | 37 | if (node.trailingComments) { 38 | for (var i = 0; i < node.trailingComments.length; i++) { 39 | var comment = node.trailingComments[i]; 40 | if (comment.type === 'CommentLine') { 41 | comment.type = 'Line'; 42 | } else if (comment.type === 'CommentBlock') { 43 | comment.type = 'Block'; 44 | } 45 | comment.range = [comment.start, comment.end]; 46 | } 47 | } 48 | 49 | if (node.leadingComments) { 50 | for (var i = 0; i < node.leadingComments.length; i++) { 51 | var comment = node.leadingComments[i]; 52 | if (comment.type === 'CommentLine') { 53 | comment.type = 'Line'; 54 | } else if (comment.type === 'CommentBlock') { 55 | comment.type = 'Block'; 56 | } 57 | comment.range = [comment.start, comment.end]; 58 | } 59 | } 60 | 61 | // make '_paths' non-enumerable (babel-eslint #200) 62 | Object.defineProperty(node, "_paths", { value: node._paths, writable: true }); 63 | }, 64 | exit: function (path) { 65 | var node = path.node; 66 | 67 | [ 68 | fixDirectives, 69 | ].forEach(function (fixer) { 70 | fixer(path); 71 | }); 72 | 73 | if (path.isJSXText()) { 74 | node.type = 'Literal'; 75 | node.raw = node.value; 76 | } 77 | 78 | if (path.isNumericLiteral() || 79 | path.isStringLiteral()) { 80 | changeToLiteral(node); 81 | } 82 | 83 | if (path.isBooleanLiteral()) { 84 | node.type = 'Literal'; 85 | node.raw = String(node.value); 86 | } 87 | 88 | if (path.isNullLiteral()) { 89 | node.type = 'Literal'; 90 | node.raw = 'null'; 91 | node.value = null; 92 | } 93 | 94 | if (path.isRegExpLiteral()) { 95 | node.type = 'Literal'; 96 | node.raw = node.extra.raw; 97 | node.value = {}; 98 | node.regex = { 99 | pattern: node.pattern, 100 | flags: node.flags 101 | }; 102 | delete node.extra; 103 | delete node.pattern; 104 | delete node.flags; 105 | } 106 | 107 | if (path.isObjectProperty()) { 108 | node.type = 'Property'; 109 | node.kind = 'init'; 110 | } 111 | 112 | if (path.isClassMethod() || path.isObjectMethod()) { 113 | var code = source.slice(node.key.end, node.body.start); 114 | var offset = code.indexOf("("); 115 | 116 | node.value = { 117 | type: 'FunctionExpression', 118 | id: node.id, 119 | params: node.params, 120 | body: node.body, 121 | async: node.async, 122 | generator: node.generator, 123 | expression: node.expression, 124 | defaults: [], // basic support - TODO: remove (old esprima) 125 | loc: { 126 | start: { 127 | line: node.key.loc.start.line, 128 | column: node.key.loc.end.column + offset // a[() {] 129 | }, 130 | end: node.body.loc.end 131 | } 132 | } 133 | 134 | // [asdf]() { 135 | node.value.range = [node.key.end + offset, node.body.end]; 136 | 137 | if (node.returnType) { 138 | node.value.returnType = node.returnType; 139 | } 140 | 141 | if (node.typeParameters) { 142 | node.value.typeParameters = node.typeParameters; 143 | } 144 | 145 | if (path.isClassMethod()) { 146 | node.type = 'MethodDefinition'; 147 | } 148 | 149 | if (path.isObjectMethod()) { 150 | node.type = 'Property'; 151 | if (node.kind === 'method') { 152 | node.kind = 'init'; 153 | } 154 | } 155 | 156 | delete node.body; 157 | delete node.id; 158 | delete node.async; 159 | delete node.generator; 160 | delete node.expression; 161 | delete node.params; 162 | delete node.returnType; 163 | delete node.typeParameters; 164 | } 165 | 166 | if (path.isRestProperty() || path.isSpreadProperty()) { 167 | node.type = "SpreadProperty"; 168 | node.key = node.value = node.argument; 169 | } 170 | 171 | // flow: prevent "no-undef" 172 | // for "Component" in: "let x: React.Component" 173 | if (path.isQualifiedTypeIdentifier()) { 174 | delete node.id; 175 | } 176 | // for "b" in: "var a: { b: Foo }" 177 | if (path.isObjectTypeProperty()) { 178 | delete node.key; 179 | } 180 | // for "indexer" in: "var a: {[indexer: string]: number}" 181 | if (path.isObjectTypeIndexer()) { 182 | delete node.id; 183 | } 184 | // for "param" in: "var a: { func(param: Foo): Bar };" 185 | if (path.isFunctionTypeParam()) { 186 | delete node.name; 187 | } 188 | 189 | // modules 190 | 191 | if (path.isImportDeclaration()) { 192 | delete node.isType; 193 | } 194 | 195 | if (path.isExportDeclaration()) { 196 | var declar = path.get("declaration"); 197 | if (declar.isClassExpression()) { 198 | node.declaration.type = "ClassDeclaration"; 199 | } else if (declar.isFunctionExpression()) { 200 | node.declaration.type = "FunctionDeclaration"; 201 | } 202 | } 203 | 204 | // remove class property keys (or patch in escope) 205 | if (path.isClassProperty()) { 206 | delete node.key; 207 | } 208 | 209 | // async function as generator 210 | if (path.isFunction()) { 211 | if (node.async) node.generator = true; 212 | } 213 | 214 | // TODO: remove (old esprima) 215 | if (path.isFunction()) { 216 | if (!node.defaults) { 217 | node.defaults = []; 218 | } 219 | } 220 | 221 | // await transform to yield 222 | if (path.isAwaitExpression()) { 223 | node.type = "YieldExpression"; 224 | node.delegate = node.all; 225 | delete node.all; 226 | } 227 | 228 | // template string range fixes 229 | if (path.isTemplateLiteral()) { 230 | node.quasis.forEach(function (q) { 231 | q.range[0] -= 1; 232 | if (q.tail) { 233 | q.range[1] += 1; 234 | } else { 235 | q.range[1] += 2; 236 | } 237 | q.loc.start.column -= 1; 238 | if (q.tail) { 239 | q.loc.end.column += 1; 240 | } else { 241 | q.loc.end.column += 2; 242 | } 243 | }); 244 | } 245 | } 246 | }; 247 | 248 | 249 | function fixDirectives (path) { 250 | if (!(path.isProgram() || path.isFunction())) return; 251 | 252 | var node = path.node; 253 | var directivesContainer = node; 254 | var body = node.body; 255 | 256 | if (node.type !== "Program") { 257 | directivesContainer = body; 258 | body = body.body; 259 | } 260 | 261 | if (!directivesContainer.directives) return; 262 | 263 | directivesContainer.directives.reverse().forEach(function (directive) { 264 | directive.type = "ExpressionStatement"; 265 | directive.expression = directive.value; 266 | delete directive.value; 267 | directive.expression.type = "Literal"; 268 | changeToLiteral(directive.expression); 269 | body.unshift(directive); 270 | }); 271 | delete directivesContainer.directives; 272 | } 273 | // fixDirectives 274 | --------------------------------------------------------------------------------