structure.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { List } from '../utils/List.js';
  2. const { hasOwnProperty } = Object.prototype;
  3. function isValidNumber(value) {
  4. // Number.isInteger(value) && value >= 0
  5. return (
  6. typeof value === 'number' &&
  7. isFinite(value) &&
  8. Math.floor(value) === value &&
  9. value >= 0
  10. );
  11. }
  12. function isValidLocation(loc) {
  13. return (
  14. Boolean(loc) &&
  15. isValidNumber(loc.offset) &&
  16. isValidNumber(loc.line) &&
  17. isValidNumber(loc.column)
  18. );
  19. }
  20. function createNodeStructureChecker(type, fields) {
  21. return function checkNode(node, warn) {
  22. if (!node || node.constructor !== Object) {
  23. return warn(node, 'Type of node should be an Object');
  24. }
  25. for (let key in node) {
  26. let valid = true;
  27. if (hasOwnProperty.call(node, key) === false) {
  28. continue;
  29. }
  30. if (key === 'type') {
  31. if (node.type !== type) {
  32. warn(node, 'Wrong node type `' + node.type + '`, expected `' + type + '`');
  33. }
  34. } else if (key === 'loc') {
  35. if (node.loc === null) {
  36. continue;
  37. } else if (node.loc && node.loc.constructor === Object) {
  38. if (typeof node.loc.source !== 'string') {
  39. key += '.source';
  40. } else if (!isValidLocation(node.loc.start)) {
  41. key += '.start';
  42. } else if (!isValidLocation(node.loc.end)) {
  43. key += '.end';
  44. } else {
  45. continue;
  46. }
  47. }
  48. valid = false;
  49. } else if (fields.hasOwnProperty(key)) {
  50. valid = false;
  51. for (let i = 0; !valid && i < fields[key].length; i++) {
  52. const fieldType = fields[key][i];
  53. switch (fieldType) {
  54. case String:
  55. valid = typeof node[key] === 'string';
  56. break;
  57. case Boolean:
  58. valid = typeof node[key] === 'boolean';
  59. break;
  60. case null:
  61. valid = node[key] === null;
  62. break;
  63. default:
  64. if (typeof fieldType === 'string') {
  65. valid = node[key] && node[key].type === fieldType;
  66. } else if (Array.isArray(fieldType)) {
  67. valid = node[key] instanceof List;
  68. }
  69. }
  70. }
  71. } else {
  72. warn(node, 'Unknown field `' + key + '` for ' + type + ' node type');
  73. }
  74. if (!valid) {
  75. warn(node, 'Bad value for `' + type + '.' + key + '`');
  76. }
  77. }
  78. for (const key in fields) {
  79. if (hasOwnProperty.call(fields, key) &&
  80. hasOwnProperty.call(node, key) === false) {
  81. warn(node, 'Field `' + type + '.' + key + '` is missed');
  82. }
  83. }
  84. };
  85. }
  86. function genTypesList(fieldTypes, path) {
  87. const docsTypes = [];
  88. for (let i = 0; i < fieldTypes.length; i++) {
  89. const fieldType = fieldTypes[i];
  90. if (fieldType === String || fieldType === Boolean) {
  91. docsTypes.push(fieldType.name.toLowerCase());
  92. } else if (fieldType === null) {
  93. docsTypes.push('null');
  94. } else if (typeof fieldType === 'string') {
  95. docsTypes.push(fieldType);
  96. } else if (Array.isArray(fieldType)) {
  97. docsTypes.push('List<' + (genTypesList(fieldType, path) || 'any') + '>'); // TODO: use type enum
  98. } else {
  99. throw new Error('Wrong value `' + fieldType + '` in `' + path + '` structure definition');
  100. }
  101. }
  102. return docsTypes.join(' | ');
  103. }
  104. function processStructure(name, nodeType) {
  105. const structure = nodeType.structure;
  106. const fields = {
  107. type: String,
  108. loc: true
  109. };
  110. const docs = {
  111. type: '"' + name + '"'
  112. };
  113. for (const key in structure) {
  114. if (hasOwnProperty.call(structure, key) === false) {
  115. continue;
  116. }
  117. const fieldTypes = fields[key] = Array.isArray(structure[key])
  118. ? structure[key].slice()
  119. : [structure[key]];
  120. docs[key] = genTypesList(fieldTypes, name + '.' + key);
  121. }
  122. return {
  123. docs,
  124. check: createNodeStructureChecker(name, fields)
  125. };
  126. }
  127. export function getStructureFromConfig(config) {
  128. const structure = {};
  129. if (config.node) {
  130. for (const name in config.node) {
  131. if (hasOwnProperty.call(config.node, name)) {
  132. const nodeType = config.node[name];
  133. if (nodeType.structure) {
  134. structure[name] = processStructure(name, nodeType);
  135. } else {
  136. throw new Error('Missed `structure` field in `' + name + '` node type definition');
  137. }
  138. }
  139. }
  140. }
  141. return structure;
  142. };