UNPKG

lively.ast

Version:

Parsing JS code into ASTs and tools to query and transform these trees.

184 lines (158 loc) 7.57 kB
/*global beforeEach, afterEach, describe, it*/ import { expect } from "mocha-es6"; import { withMozillaAstDo, rematchAstWithSource } from "../lib/mozilla-ast-visitor-interface.js"; import { parse } from "../lib/parser.js"; import { arr } from "lively.lang"; import { acorn, walk, findSiblings, findNodeByAstIndex, findStatementOfNode, copy } from "../lib/acorn-extension.js"; import stringify from "../lib/stringify.js"; describe('walk extension', function() { it("finds siblings", function() { var src = 'function foo() {\nvar a;\nvar b;\nvar c;\nvar d;\n}'; var parsed = parse(src); var decls = Array.prototype.slice.call(parsed.body[0].body.body); var a = decls[0]; var b = decls[1]; var c = decls[2]; var d = decls[3]; expect(arr.without(decls, b)).deep.equals(findSiblings(parsed, b)); expect([a]).deep.equals(findSiblings(parsed, b, 'before')); expect([c,d]).deep.equals(findSiblings(parsed, b, 'after')); }); // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- it("findNodeByAstIndex", function() { var src = 'var x = 3; function foo() { var y = 3; return y }; x + foo();', parsed = parse(src), expected = parsed.body[1].body.body[1].argument, // the y in "return y" found = findNodeByAstIndex(parsed, 9); expect(expected).equals(found, 'node not found'); }); it("findNodeByAstIndexNoReIndex", function() { var src = 'var x = 3; function foo() { var y = 3; return y }; x + foo();', parsed = parse(src), found = findNodeByAstIndex(parsed, 9, false); expect(null).equals(found, 'node found (but should not add index)'); }); it("findStatementOfNode", function() { var tests = [{ src: 'var x = 3; function foo() { var y = 3; return y + 2 }; x + foo();', target: function(ast) { return ast.body[1].body.body[1].argument.left; }, expected: function(ast) { return ast.body[1].body.body[1]; } }, { src: 'var x = 1; x;', target: function(ast) { return ast.body[1]; }, expected: function(ast) { return ast.body[1]; } }, { src: 'switch (123) { case 123: debugger; }', target: function(ast) { return ast.body[0].cases[0].consequent[0]; }, expected: function(ast) { return ast.body[0].cases[0].consequent[0]; } }, { src: 'if (true) { var a = 1; }', target: function(ast) { return ast.body[0].consequent.body[0].declarations[0]; }, expected: function(ast) { return ast.body[0].consequent.body[0]; } }, { src: 'if (true) var a = 1;', target: function(ast) { return ast.body[0].consequent.declarations[0]; }, expected: function(ast) { return ast.body[0].consequent; } }, { src: 'if (true) var a = 1; else var a = 2;', target: function(ast) { return ast.body[0].alternate.declarations[0]; }, expected: function(ast) { return ast.body[0].alternate; } }, { src: 'a;', // testing scenario where node is not found target: function(ast) { return { type: 'EmptyStatement' } }, expected: function(ast) { return undefined; } }]; tests.forEach(function(test, i) { var parsed = parse(test.src), found = findStatementOfNode(parsed, test.target(parsed)); expect(test.expected(parsed)).equals(found, 'node not found ' + (i + 1)); }); }); it("updateSourceCodePositions", function() { var src = 'var x = { z: 3 }; function foo() { var y = 3; return y; } x.z + foo();', prettySrc = 'var x = { z: 3 };\nfunction foo() {\n var y = 3;\n return y;\n}\nx.z + foo();', parsed = parse(src), genSrc = stringify(parsed), genAst = parse(genSrc); expect(prettySrc).equals(genSrc, 'pretty printed source and generated source do not match'); rematchAstWithSource(parsed, genSrc); expect(parsed).to.deep.equal(genAst, 'source code positions were not corrected'); }); it("updateSourceCodePositionsInSubTree", function() { var src1 = 'function foo() { var y = 3; return y; }', src2 = 'var x = { z: 3 };\nfunction foo() {\n var y = 3;\n return y;\n}\nx.z + foo();', ast1 = parse(src1).body[0], ast2 = parse(src2), genSrc = stringify(ast2), genAst = parse(genSrc); rematchAstWithSource(ast1, genSrc, null, 'body.1'); expect(ast1).to.deep.equal(genAst.body[1], 'source code positions were not corrected'); }); it("updateSourceCodeLocations", function() { var src = 'var x = { z: 3 }; function foo() { var y = 3; return y; } x.z + foo();', prettySrc = 'var x = { z: 3 };\nfunction foo() {\n var y = 3;\n return y;\n}\nx.z + foo();', parsed = parse(src), genSrc = stringify(parsed), genAst = parse(genSrc); expect(prettySrc).equals(genSrc, 'pretty printed source and generated source do not match'); rematchAstWithSource(parsed, genSrc, true); // sample some locations var tests = [{ // var = x = { z: 3 }; expected: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, subject: parsed.body[0].loc }, { // function foo() { ... } expected: { start: { line: 2, column: 0 }, end: { line: 5, column: 1 } }, subject: parsed.body[1].loc }, { // var y = 3; expected: { start: { line: 3, column: 4 }, end: { line: 3, column: 14 } }, subject: parsed.body[1].body.body[0].loc }, { // y in return y; expected: { start: { line: 4, column: 11 }, end: { line: 4, column: 12 } }, subject: parsed.body[1].body.body[1].argument.loc }, { // x.z + foo(); expected: { start: { line: 6, column: 0 }, end: { line: 6, column: 12 } }, subject: parsed.body[2].loc }]; tests.forEach(function(test, i) { expect(test.subject).to.containSubset(test.expected, 'incorrect location for test ' + (i+1)); }); // compare withour considering locations withMozillaAstDo(parsed, {}, function(next, node) { delete node.loc; next(); }) expect(parsed).to.deep.equal(genAst, 'source code positions were not corrected'); }); it("parseWithComments", function() { var src = '// comment1\n\n//comment2\nvar x = 3; // comment3\n// comment3\nfunction foo() { var y = 3; /*comment4*/ return y }; x + foo();', parsed = parse(src, {withComments: true}), comments = parsed.comments, expectedTopLevelComments = [{ column: undefined, line: undefined, isBlock: false, start: 0, end: 11, node: null, text: " comment1" },{ column: undefined, line: undefined, isBlock: false, start: 13, end: 23, node: null, text: "comment2" },{ column: undefined, line: undefined, isBlock: false, start: 35, end: 58, node: null, text: " comment3\n comment3" }], expectedScopedComments = [{ node: null, column: undefined, line: undefined, start: 87,end: 99, isBlock: true, text: "comment4" }]; expect(parsed.comments).to.deep.equal(expectedTopLevelComments, 'topLevel'); expect(parsed.body[1].body.comments).to.deep.equal(expectedScopedComments, 'scoped'); }); it("should deep copy ast", function() { var parsed = parse('var x = 3;', {addSource: true, addAstIndex: true}), parsedCopy = copy(parsed); // FIXME: sourceType should be copied too delete parsed.sourceType; expect(parsed).to.containSubset(parsedCopy); parsed.body[0].declarations[0].init.value = 'foo'; expect(parsed).not.to.containSubset(parsedCopy); }); });