index.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. import Printer from "../lib/printer";
  2. import generate, { CodeGenerator } from "../lib";
  3. import { parse } from "@babel/parser";
  4. import * as t from "@babel/types";
  5. import fs from "fs";
  6. import path from "path";
  7. import fixtures from "@babel/helper-fixtures";
  8. describe("generation", function() {
  9. it("completeness", function() {
  10. Object.keys(t.VISITOR_KEYS).forEach(function(type) {
  11. expect(Printer.prototype[type]).toBeTruthy();
  12. });
  13. Object.keys(Printer.prototype).forEach(function(type) {
  14. if (!/[A-Z]/.test(type[0])) return;
  15. expect(t.VISITOR_KEYS[type]).toBeTruthy();
  16. });
  17. });
  18. it("multiple sources", function() {
  19. const sources = {
  20. "a.js": "function hi (msg) { console.log(msg); }\n",
  21. "b.js": "hi('hello');\n",
  22. };
  23. const parsed = Object.keys(sources).reduce(function(_parsed, filename) {
  24. _parsed[filename] = parse(sources[filename], {
  25. sourceFilename: filename,
  26. });
  27. return _parsed;
  28. }, {});
  29. const combinedAst = {
  30. type: "File",
  31. program: {
  32. type: "Program",
  33. sourceType: "module",
  34. body: [].concat(
  35. parsed["a.js"].program.body,
  36. parsed["b.js"].program.body,
  37. ),
  38. },
  39. };
  40. const generated = generate(combinedAst, { sourceMaps: true }, sources);
  41. expect(generated.map).toEqual(
  42. {
  43. version: 3,
  44. sources: ["a.js", "b.js"],
  45. mappings:
  46. "AAAA,SAASA,EAAT,CAAaC,GAAb,EAAkB;AAAEC,UAAQC,GAAR,CAAYF,GAAZ;AAAmB;;ACAvCD,GAAG,OAAH",
  47. names: ["hi", "msg", "console", "log"],
  48. sourcesContent: [
  49. "function hi (msg) { console.log(msg); }\n",
  50. "hi('hello');\n",
  51. ],
  52. },
  53. "sourcemap was incorrectly generated",
  54. );
  55. expect(generated.rawMappings).toEqual(
  56. [
  57. {
  58. name: undefined,
  59. generated: { line: 1, column: 0 },
  60. source: "a.js",
  61. original: { line: 1, column: 0 },
  62. },
  63. {
  64. name: "hi",
  65. generated: { line: 1, column: 9 },
  66. source: "a.js",
  67. original: { line: 1, column: 9 },
  68. },
  69. {
  70. name: undefined,
  71. generated: { line: 1, column: 11 },
  72. source: "a.js",
  73. original: { line: 1, column: 0 },
  74. },
  75. {
  76. name: "msg",
  77. generated: { line: 1, column: 12 },
  78. source: "a.js",
  79. original: { line: 1, column: 13 },
  80. },
  81. {
  82. name: undefined,
  83. generated: { line: 1, column: 15 },
  84. source: "a.js",
  85. original: { line: 1, column: 0 },
  86. },
  87. {
  88. name: undefined,
  89. generated: { line: 1, column: 17 },
  90. source: "a.js",
  91. original: { line: 1, column: 18 },
  92. },
  93. {
  94. name: "console",
  95. generated: { line: 2, column: 0 },
  96. source: "a.js",
  97. original: { line: 1, column: 20 },
  98. },
  99. {
  100. name: "log",
  101. generated: { line: 2, column: 10 },
  102. source: "a.js",
  103. original: { line: 1, column: 28 },
  104. },
  105. {
  106. name: undefined,
  107. generated: { line: 2, column: 13 },
  108. source: "a.js",
  109. original: { line: 1, column: 20 },
  110. },
  111. {
  112. name: "msg",
  113. generated: { line: 2, column: 14 },
  114. source: "a.js",
  115. original: { line: 1, column: 32 },
  116. },
  117. {
  118. name: undefined,
  119. generated: { line: 2, column: 17 },
  120. source: "a.js",
  121. original: { line: 1, column: 20 },
  122. },
  123. {
  124. name: undefined,
  125. generated: { line: 3, column: 0 },
  126. source: "a.js",
  127. original: { line: 1, column: 39 },
  128. },
  129. {
  130. name: "hi",
  131. generated: { line: 5, column: 0 },
  132. source: "b.js",
  133. original: { line: 1, column: 0 },
  134. },
  135. {
  136. name: undefined,
  137. generated: { line: 5, column: 3 },
  138. source: "b.js",
  139. original: { line: 1, column: 3 },
  140. },
  141. {
  142. name: undefined,
  143. generated: { line: 5, column: 10 },
  144. source: "b.js",
  145. original: { line: 1, column: 0 },
  146. },
  147. ],
  148. "raw mappings were incorrectly generated",
  149. );
  150. expect(generated.code).toBe(
  151. "function hi(msg) {\n console.log(msg);\n}\n\nhi('hello');",
  152. );
  153. });
  154. it("identifierName", function() {
  155. const code = "function foo() { bar; }\n";
  156. const ast = parse(code, { filename: "inline" }).program;
  157. const fn = ast.body[0];
  158. const id = fn.id;
  159. id.name += "2";
  160. id.loc.identifierName = "foo";
  161. const id2 = fn.body.body[0].expression;
  162. id2.name += "2";
  163. id2.loc.identiferName = "bar";
  164. const generated = generate(
  165. ast,
  166. {
  167. filename: "inline",
  168. sourceFileName: "inline",
  169. sourceMaps: true,
  170. },
  171. code,
  172. );
  173. expect(generated.map).toEqual(
  174. {
  175. version: 3,
  176. sources: ["inline"],
  177. names: ["foo", "bar"],
  178. mappings: "AAAA,SAASA,IAAT,GAAe;AAAEC;AAAM",
  179. sourcesContent: ["function foo() { bar; }\n"],
  180. },
  181. "sourcemap was incorrectly generated",
  182. );
  183. expect(generated.rawMappings).toEqual(
  184. [
  185. {
  186. name: undefined,
  187. generated: { line: 1, column: 0 },
  188. source: "inline",
  189. original: { line: 1, column: 0 },
  190. },
  191. {
  192. name: "foo",
  193. generated: { line: 1, column: 9 },
  194. source: "inline",
  195. original: { line: 1, column: 9 },
  196. },
  197. {
  198. name: undefined,
  199. generated: { line: 1, column: 13 },
  200. source: "inline",
  201. original: { line: 1, column: 0 },
  202. },
  203. {
  204. name: undefined,
  205. generated: { line: 1, column: 16 },
  206. source: "inline",
  207. original: { line: 1, column: 15 },
  208. },
  209. {
  210. name: "bar",
  211. generated: { line: 2, column: 0 },
  212. source: "inline",
  213. original: { line: 1, column: 17 },
  214. },
  215. {
  216. name: undefined,
  217. generated: { line: 3, column: 0 },
  218. source: "inline",
  219. original: { line: 1, column: 23 },
  220. },
  221. ],
  222. "raw mappings were incorrectly generated",
  223. );
  224. expect(generated.code).toBe("function foo2() {\n bar2;\n}");
  225. });
  226. it("lazy source map generation", function() {
  227. const code = "function hi (msg) { console.log(msg); }\n";
  228. const ast = parse(code, { filename: "a.js" }).program;
  229. const generated = generate(ast, {
  230. sourceFileName: "a.js",
  231. sourceMaps: true,
  232. });
  233. expect(Array.isArray(generated.rawMappings)).toBe(true);
  234. expect(
  235. Object.getOwnPropertyDescriptor(generated, "map"),
  236. ).not.toHaveProperty("value");
  237. expect(generated).toHaveProperty("map");
  238. expect(typeof generated.map).toBe("object");
  239. });
  240. });
  241. describe("programmatic generation", function() {
  242. it("numeric member expression", function() {
  243. // Should not generate `0.foo`
  244. const mem = t.memberExpression(
  245. t.numericLiteral(60702),
  246. t.identifier("foo"),
  247. );
  248. new Function(generate(mem).code);
  249. });
  250. it("nested if statements needs block", function() {
  251. const ifStatement = t.ifStatement(
  252. t.stringLiteral("top cond"),
  253. t.whileStatement(
  254. t.stringLiteral("while cond"),
  255. t.ifStatement(
  256. t.stringLiteral("nested"),
  257. t.expressionStatement(t.numericLiteral(1)),
  258. ),
  259. ),
  260. t.expressionStatement(t.stringLiteral("alt")),
  261. );
  262. const ast = parse(generate(ifStatement).code);
  263. expect(ast.program.body[0].consequent.type).toBe("BlockStatement");
  264. });
  265. it("prints directives in block with empty body", function() {
  266. const blockStatement = t.blockStatement(
  267. [],
  268. [t.directive(t.directiveLiteral("use strict"))],
  269. );
  270. const output = generate(blockStatement).code;
  271. expect(output).toBe(`{
  272. "use strict";
  273. }`);
  274. });
  275. it("flow object indentation", function() {
  276. const objectStatement = t.objectTypeAnnotation(
  277. [t.objectTypeProperty(t.identifier("bar"), t.stringTypeAnnotation())],
  278. null,
  279. null,
  280. null,
  281. );
  282. const output = generate(objectStatement).code;
  283. expect(output).toBe(`{
  284. bar: string
  285. }`);
  286. });
  287. it("flow object exact", function() {
  288. const objectStatement = t.objectTypeAnnotation(
  289. [t.objectTypeProperty(t.identifier("bar"), t.stringTypeAnnotation())],
  290. null,
  291. null,
  292. null,
  293. true,
  294. );
  295. const output = generate(objectStatement).code;
  296. expect(output).toBe(`{|
  297. bar: string
  298. |}`);
  299. });
  300. it("flow object indentation with empty leading ObjectTypeProperty", function() {
  301. const objectStatement = t.objectTypeAnnotation(
  302. [],
  303. [
  304. t.objectTypeIndexer(
  305. t.identifier("key"),
  306. t.anyTypeAnnotation(),
  307. t.numberTypeAnnotation(),
  308. ),
  309. ],
  310. null,
  311. );
  312. const output = generate(objectStatement).code;
  313. expect(output).toBe(`{
  314. [key: any]: number
  315. }`);
  316. });
  317. });
  318. describe("CodeGenerator", function() {
  319. it("generate", function() {
  320. const codeGen = new CodeGenerator(t.numericLiteral(123));
  321. const code = codeGen.generate().code;
  322. expect(parse(code).program.body[0].expression.value).toBe(123);
  323. });
  324. });
  325. const suites = fixtures(`${__dirname}/fixtures`);
  326. suites.forEach(function(testSuite) {
  327. describe("generation/" + testSuite.title, function() {
  328. testSuite.tests.forEach(function(task) {
  329. it(
  330. task.title,
  331. !task.disabled &&
  332. function() {
  333. const expected = task.expect;
  334. const actual = task.actual;
  335. const actualCode = actual.code;
  336. if (actualCode) {
  337. const actualAst = parse(actualCode, {
  338. filename: actual.loc,
  339. plugins: task.options.plugins || [],
  340. strictMode: false,
  341. sourceType: "module",
  342. });
  343. const result = generate(actualAst, task.options, actualCode);
  344. if (
  345. !expected.code &&
  346. result.code &&
  347. fs.statSync(path.dirname(expected.loc)).isDirectory() &&
  348. !process.env.CI
  349. ) {
  350. console.log(`New test file created: ${expected.loc}`);
  351. fs.writeFileSync(expected.loc, result.code);
  352. } else {
  353. expect(result.code).toBe(expected.code);
  354. }
  355. }
  356. },
  357. );
  358. });
  359. });
  360. });