|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +const merge = ( |
| 4 | + // Distinct merge miltiple arrays |
| 5 | + ...args // array of array |
| 6 | + // Returns: array |
| 7 | +) => { |
| 8 | + const array = args[0]; |
| 9 | + let i, ilen, j, jlen, arr, val; |
| 10 | + for (i = 1, ilen = args.length; i < ilen; i++) { |
| 11 | + arr = args[i]; |
| 12 | + for (j = 0, jlen = arr.length; j < jlen; j++) { |
| 13 | + val = arr[j]; |
| 14 | + if (!array.includes(val)) array.push(val); |
| 15 | + } |
| 16 | + } |
| 17 | + return array; |
| 18 | +}; |
| 19 | + |
| 20 | +const section = ( |
| 21 | + // Splits string by the first occurrence of separator |
| 22 | + s, // string |
| 23 | + separator // string, or char |
| 24 | + // Example: rsection('All you need is JavaScript', 'is') |
| 25 | + // Returns: ['All you need ', ' JavaScript'] |
| 26 | +) => { |
| 27 | + const i = s.indexOf(separator); |
| 28 | + if (i < 0) return [s, '']; |
| 29 | + return [s.slice(0, i), s.slice(i + separator.length)]; |
| 30 | +}; |
| 31 | + |
| 32 | +const SCALAR_TYPES = ['string', 'number', 'boolean', 'undefined']; |
| 33 | +const OBJECT_TYPES = ['function', 'array', 'object', 'null', 'symbol']; |
| 34 | +const META_TYPES = ['char', 'hash', 'record', 'set', 'map']; |
| 35 | +const ALL_TYPES = merge(SCALAR_TYPES, OBJECT_TYPES, META_TYPES); |
| 36 | + |
| 37 | +const FUNC_TERMS = [') {', ') => {', ') => (']; |
| 38 | +const NAMED_LINES = ['Example:', 'Returns:', 'Hint:', 'Result:']; |
| 39 | + |
| 40 | +const indexing = s => term => s.indexOf(term); |
| 41 | + |
| 42 | +const last = arr => arr[arr.length - 1]; |
| 43 | + |
| 44 | +const parseLines = ( |
| 45 | + // Parse signature lines |
| 46 | + s, // string, signature lines |
| 47 | + signature // record, { title, description, parameters, comments } |
| 48 | + // Returns: array of string |
| 49 | +) => { |
| 50 | + let lines = s.split('\n'); |
| 51 | + lines.pop(); |
| 52 | + signature.title = (lines.shift() || '').replace('//', '').trim(); |
| 53 | + lines = lines.map( |
| 54 | + d => d.trim().replace(/^(.*) \/\//, '$1:').replace(',:', ':') |
| 55 | + ); |
| 56 | + for (let line of lines) { |
| 57 | + if (line.startsWith('//')) { |
| 58 | + line = line.replace(/^\/\/ /, '').trim(); |
| 59 | + if (NAMED_LINES.find(s => line.startsWith(s))) { |
| 60 | + const [name, comment] = section(line, ': '); |
| 61 | + signature.comments.push({ name, comment }); |
| 62 | + } else if (signature.parameters.length === 0) { |
| 63 | + if (signature.description.length > 0) { |
| 64 | + signature.description += '\n'; |
| 65 | + } |
| 66 | + signature.description += line; |
| 67 | + } else { |
| 68 | + const par = last(signature.parameters); |
| 69 | + par.comment += '\n' + line; |
| 70 | + } |
| 71 | + } else { |
| 72 | + const [name, text] = section(line, ': '); |
| 73 | + let [type, comment] = section(text, ', '); |
| 74 | + if (!ALL_TYPES.find(s => type.startsWith(s))) { |
| 75 | + comment = type; |
| 76 | + type = ''; |
| 77 | + } |
| 78 | + signature.parameters.push({ name, type, comment }); |
| 79 | + } |
| 80 | + } |
| 81 | +}; |
| 82 | + |
| 83 | +const parseSignature = ( |
| 84 | + // Parse function signature |
| 85 | + fn // function, method |
| 86 | + // Returns: { title, description, parameters, comments } |
| 87 | +) => { |
| 88 | + const signature = { |
| 89 | + title: '', description: '', |
| 90 | + parameters: [], comments: [] |
| 91 | + }; |
| 92 | + let s = fn.toString(); |
| 93 | + let pos = FUNC_TERMS.map(indexing(s)) |
| 94 | + .filter(k => k !== -1) |
| 95 | + .reduce((prev, cur) => (prev < cur ? prev : cur), s.length); |
| 96 | + if (pos !== -1) { |
| 97 | + s = s.substring(0, pos); |
| 98 | + pos = s.indexOf('\n'); |
| 99 | + s = s.substring(pos + 1); |
| 100 | + parseLines(s, signature); |
| 101 | + } |
| 102 | + return signature; |
| 103 | +}; |
| 104 | + |
| 105 | +const introspect = ( |
| 106 | + // Introspect interface |
| 107 | + namespace // hash of interfaces |
| 108 | + // Returns: hash of hash of record, { method, title, parameters } |
| 109 | +) => { |
| 110 | + const inventory = {}; |
| 111 | + let name, iface, methods, method, fn, signature; |
| 112 | + for (name in namespace) { |
| 113 | + iface = namespace[name]; |
| 114 | + methods = {}; |
| 115 | + inventory[name] = methods; |
| 116 | + for (method in iface) { |
| 117 | + fn = iface[method]; |
| 118 | + signature = parseSignature(fn); |
| 119 | + signature = Object.assign({ |
| 120 | + method: name + '.' + method |
| 121 | + }, signature); |
| 122 | + methods[method] = signature; |
| 123 | + } |
| 124 | + } |
| 125 | + return inventory; |
| 126 | +}; |
| 127 | + |
| 128 | +const badIntrospect = ( |
| 129 | + // Introspect interface |
| 130 | + namespace // hash of interfaces |
| 131 | + // Returns: hash of hash of record, { method, title, parameters } |
| 132 | +) => { |
| 133 | + const inventory = {}; |
| 134 | + let name, iface, methods, method, fn, signature; |
| 135 | + for (name in namespace) { |
| 136 | + iface = namespace[name]; |
| 137 | + methods = {}; |
| 138 | + inventory[name] = methods; |
| 139 | + for (method in iface) { |
| 140 | + fn = iface[method]; |
| 141 | + signature = { |
| 142 | + title: '', description: '', |
| 143 | + parameters: [], comments: [] |
| 144 | + }; |
| 145 | + let s = fn.toString(); |
| 146 | + let pos = FUNC_TERMS.map(indexing(s)) |
| 147 | + .filter(k => k !== -1) |
| 148 | + .reduce((prev, cur) => (prev < cur ? prev : cur), s.length); |
| 149 | + if (pos !== -1) { |
| 150 | + s = s.substring(0, pos); |
| 151 | + pos = s.indexOf('\n'); |
| 152 | + s = s.substring(pos + 1); |
| 153 | + let lines = s.split('\n'); |
| 154 | + lines.pop(); |
| 155 | + signature.title = (lines.shift() || '').replace('//', '').trim(); |
| 156 | + lines = lines.map( |
| 157 | + d => d.trim().replace(/^(.*) \/\//, '$1:').replace(',:', ':') |
| 158 | + ); |
| 159 | + for (let line of lines) { |
| 160 | + if (line.startsWith('//')) { |
| 161 | + line = line.replace(/^\/\/ /, '').trim(); |
| 162 | + if (NAMED_LINES.find(s => line.startsWith(s))) { |
| 163 | + const [name, comment] = section(line, ': '); |
| 164 | + signature.comments.push({ name, comment }); |
| 165 | + } else if (signature.parameters.length === 0) { |
| 166 | + if (signature.description.length > 0) { |
| 167 | + signature.description += '\n'; |
| 168 | + } |
| 169 | + signature.description += line; |
| 170 | + } else { |
| 171 | + const par = last(signature.parameters); |
| 172 | + par.comment += '\n' + line; |
| 173 | + } |
| 174 | + } else { |
| 175 | + const [name, text] = section(line, ': '); |
| 176 | + let [type, comment] = section(text, ', '); |
| 177 | + if (!ALL_TYPES.find(s => type.startsWith(s))) { |
| 178 | + comment = type; |
| 179 | + type = ''; |
| 180 | + } |
| 181 | + signature.parameters.push({ name, type, comment }); |
| 182 | + } |
| 183 | + } |
| 184 | + } |
| 185 | + signature = Object.assign({ |
| 186 | + method: name + '.' + method |
| 187 | + }, signature); |
| 188 | + methods[method] = signature; |
| 189 | + } |
| 190 | + } |
| 191 | + return inventory; |
| 192 | +}; |
| 193 | + |
| 194 | +const iface = { common: { merge, section } }; |
| 195 | +console.dir(introspect(iface)); |
| 196 | +console.dir(badIntrospect(iface)); |
0 commit comments