generateNodeUtils.js 4.9 KB

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