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