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 | };
|
---|