generateNodeUtils.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. const definitions = require("../src/definitions");
  2. const flatMap = require("array.prototype.flatmap");
  3. const {
  4. typeSignature,
  5. iterateProps,
  6. mapProps,
  7. filterProps,
  8. unique,
  9. } = require("./util");
  10. const stdout = process.stdout;
  11. const jsTypes = ["string", "number", "boolean"];
  12. const quote = (value) => `"${value}"`;
  13. function params(fields) {
  14. const optionalDefault = (field) =>
  15. field.default ? ` = ${field.default}` : "";
  16. return mapProps(fields)
  17. .map((field) => `${typeSignature(field)}${optionalDefault(field)}`)
  18. .join(",");
  19. }
  20. function assertParamType({ assertNodeType, array, name, type }) {
  21. if (array) {
  22. // TODO - assert contents of array?
  23. return `assert(typeof ${name} === "object" && typeof ${name}.length !== "undefined")\n`;
  24. } else {
  25. if (jsTypes.includes(type)) {
  26. return `assert(
  27. typeof ${name} === "${type}",
  28. "Argument ${name} must be of type ${type}, given: " + typeof ${name}
  29. )`;
  30. }
  31. if (assertNodeType === true) {
  32. return `assert(
  33. ${name}.type === "${type}",
  34. "Argument ${name} must be of type ${type}, given: " + ${name}.type
  35. )`;
  36. }
  37. return "";
  38. }
  39. }
  40. function assertParam(meta) {
  41. const paramAssertion = assertParamType(meta);
  42. if (paramAssertion === "") {
  43. return "";
  44. }
  45. if (meta.maybe || meta.optional) {
  46. return `
  47. if (${meta.name} !== null && ${meta.name} !== undefined) {
  48. ${paramAssertion};
  49. }
  50. `;
  51. } else {
  52. return paramAssertion;
  53. }
  54. }
  55. function assertParams(fields) {
  56. return mapProps(fields).map(assertParam).join("\n");
  57. }
  58. function buildObject(typeDef) {
  59. const optionalField = (meta) => {
  60. if (meta.array) {
  61. // omit optional array properties if the constructor function was supplied
  62. // with an empty array
  63. return `
  64. if (typeof ${meta.name} !== "undefined" && ${meta.name}.length > 0) {
  65. node.${meta.name} = ${meta.name};
  66. }
  67. `;
  68. } else if (meta.type === "Object") {
  69. // omit optional object properties if they have no keys
  70. return `
  71. if (typeof ${meta.name} !== "undefined" && Object.keys(${meta.name}).length !== 0) {
  72. node.${meta.name} = ${meta.name};
  73. }
  74. `;
  75. } else if (meta.type === "boolean") {
  76. // omit optional boolean properties if they are not true
  77. return `
  78. if (${meta.name} === true) {
  79. node.${meta.name} = true;
  80. }
  81. `;
  82. } else {
  83. return `
  84. if (typeof ${meta.name} !== "undefined") {
  85. node.${meta.name} = ${meta.name};
  86. }
  87. `;
  88. }
  89. };
  90. const fields = mapProps(typeDef.fields)
  91. .filter((f) => !f.optional && !f.constant)
  92. .map((f) => f.name);
  93. const constants = mapProps(typeDef.fields)
  94. .filter((f) => f.constant)
  95. .map((f) => `${f.name}: "${f.value}"`);
  96. return `
  97. const node: ${typeDef.flowTypeName || typeDef.name} = {
  98. type: "${typeDef.name}",
  99. ${constants.concat(fields).join(",")}
  100. }
  101. ${mapProps(typeDef.fields)
  102. .filter((f) => f.optional)
  103. .map(optionalField)
  104. .join("")}
  105. `;
  106. }
  107. function lowerCamelCase(name) {
  108. return name.substring(0, 1).toLowerCase() + name.substring(1);
  109. }
  110. function generate() {
  111. stdout.write(`
  112. // @flow
  113. // THIS FILE IS AUTOGENERATED
  114. // see scripts/generateNodeUtils.js
  115. import { assert } from "mamacro";
  116. function isTypeOf(t: string) {
  117. return (n: Node) => n.type === t;
  118. }
  119. function assertTypeOf(t: string) {
  120. return (n: Node) => assert(n.type === t);
  121. }
  122. `);
  123. // Node builders
  124. iterateProps(definitions, (typeDefinition) => {
  125. stdout.write(`
  126. export function ${lowerCamelCase(typeDefinition.name)} (
  127. ${params(filterProps(typeDefinition.fields, (f) => !f.constant))}
  128. ): ${typeDefinition.name} {
  129. ${assertParams(filterProps(typeDefinition.fields, (f) => !f.constant))}
  130. ${buildObject(typeDefinition)}
  131. return node;
  132. }
  133. `);
  134. });
  135. // Node testers
  136. iterateProps(definitions, (typeDefinition) => {
  137. stdout.write(`
  138. export const is${typeDefinition.name}: ((n: Node) => boolean) =
  139. isTypeOf("${typeDefinition.name}");
  140. `);
  141. });
  142. // Node union type testers
  143. const unionTypes = unique(
  144. flatMap(
  145. mapProps(definitions).filter((d) => d.unionType),
  146. (d) => d.unionType
  147. )
  148. );
  149. unionTypes.forEach((unionType) => {
  150. stdout.write(
  151. `
  152. export const is${unionType} = (node: Node): boolean => ` +
  153. mapProps(definitions)
  154. .filter((d) => d.unionType && d.unionType.includes(unionType))
  155. .map((d) => `is${d.name}(node) `)
  156. .join("||") +
  157. ";\n\n"
  158. );
  159. });
  160. // Node assertion
  161. iterateProps(definitions, (typeDefinition) => {
  162. stdout.write(`
  163. export const assert${typeDefinition.name}: ((n: Node) => void) =
  164. assertTypeOf("${typeDefinition.name}");
  165. `);
  166. });
  167. // a map from node type to its set of union types
  168. stdout.write(
  169. `
  170. export const unionTypesMap = {` +
  171. mapProps(definitions)
  172. .filter((d) => d.unionType)
  173. .map((t) => `"${t.name}": [${t.unionType.map(quote).join(",")}]\n`) +
  174. `};
  175. `
  176. );
  177. // an array of all node and union types
  178. stdout.write(
  179. `
  180. export const nodeAndUnionTypes = [` +
  181. mapProps(definitions)
  182. .map((t) => `"${t.name}"`)
  183. .concat(unionTypes.map(quote))
  184. .join(",") +
  185. `];`
  186. );
  187. }
  188. generate();