|
| 1 | +'use strict'; |
| 2 | +var fs = require('fs'); |
| 3 | +const esprima = require("esprima"); |
| 4 | +const ts = require('typescript'); |
| 5 | + |
| 6 | +const LOG_LEVEL_SUCCESS = 0; |
| 7 | +const LOG_LEVEL_FAIL = 1; |
| 8 | + |
| 9 | +const INDEX_FILE = 'index.js'; |
| 10 | +const TS_DEF_FILE_PATH = 'index.d.ts'; |
| 11 | +const MODULE_DIR = 'modules/'; |
| 12 | + |
| 13 | +const JS_FILES = 'Javascript Files'; |
| 14 | +const TS_DEF_FILE = 'Typescript Definition File'; |
| 15 | + |
| 16 | +function parseModule(block) { |
| 17 | + var functions = []; |
| 18 | + var enums = []; |
| 19 | + let tree = esprima.parseModule(block); |
| 20 | + tree.body.forEach((el) => { |
| 21 | + if ((el.type == "VariableDeclaration" && el.declarations[0].init.type == "ObjectExpression") || (el.type == "ExportDefaultDeclaration" && el.declaration.type == "ObjectExpression")) { |
| 22 | + var props = []; |
| 23 | + if (el.type == "ExportDefaultDeclaration" && el.declaration.type == "ObjectExpression") { |
| 24 | + props = el.declaration.properties; |
| 25 | + } else { |
| 26 | + props = el.declarations[0].init.properties; |
| 27 | + } |
| 28 | + props.forEach((prop) => { |
| 29 | + if (prop.value.type == "FunctionExpression") { |
| 30 | + var params = []; |
| 31 | + prop.value.params.forEach((param) => { |
| 32 | + if (param.type == "ObjectPattern") { // flatten composite object params |
| 33 | + param.properties.forEach((param) => { |
| 34 | + params.push(param.key.name) |
| 35 | + }); |
| 36 | + } else { |
| 37 | + params.push(param.name) |
| 38 | + } |
| 39 | + }); |
| 40 | + functions.push({ name: prop.key.name, params: params }); |
| 41 | + } else { |
| 42 | + var values = []; |
| 43 | + prop.value.properties.forEach((value) => { |
| 44 | + values.push(value.key.name) |
| 45 | + }); |
| 46 | + enums.push({ name: prop.key.name, values: values }); |
| 47 | + } |
| 48 | + }); |
| 49 | + } |
| 50 | + }); |
| 51 | + return { functions: functions, enums: enums }; |
| 52 | +} |
| 53 | + |
| 54 | +function parseDefinition() { |
| 55 | + const node = ts.createSourceFile( |
| 56 | + TS_DEF_FILE_PATH, |
| 57 | + fs.readFileSync(TS_DEF_FILE_PATH, 'utf8'), |
| 58 | + ts.ScriptTarget.Latest |
| 59 | + ); |
| 60 | + var allDef = []; |
| 61 | + var indexFunctions = []; |
| 62 | + var indexEnums = []; |
| 63 | + node.statements.forEach((statement) => { |
| 64 | + if (statement.body) { |
| 65 | + // this is a module |
| 66 | + var functions = []; |
| 67 | + var enums = []; |
| 68 | + statement.body.statements.forEach((moduleStatement) => { |
| 69 | + var params = []; |
| 70 | + if (moduleStatement.parameters) { |
| 71 | + moduleStatement.parameters.forEach((param) => { |
| 72 | + if (param.type.members) { |
| 73 | + param.type.members.forEach((member) => { |
| 74 | + params.push(member.name.escapedText); |
| 75 | + }); |
| 76 | + } else { |
| 77 | + params.push(param.name.escapedText); |
| 78 | + } |
| 79 | + }); |
| 80 | + functions.push({ name: moduleStatement.name.escapedText, params: params }); |
| 81 | + } else { |
| 82 | + var enumValues = []; |
| 83 | + moduleStatement.members.forEach((member) => { |
| 84 | + enumValues.push(member.name.escapedText); |
| 85 | + }); |
| 86 | + enums.push({ name: moduleStatement.name.escapedText, values: enumValues }); |
| 87 | + } |
| 88 | + }); |
| 89 | + allDef.push({ moduleName: statement.name.escapedText, data: { functions: functions, enums: enums } }); |
| 90 | + } else { |
| 91 | + var params = []; |
| 92 | + var enumValues = []; |
| 93 | + if (statement.parameters) { |
| 94 | + statement.parameters.forEach((param) => { // flatten composite object params |
| 95 | + if (param.type.members) { |
| 96 | + param.type.members.forEach((member) => { |
| 97 | + params.push(member.name.escapedText); |
| 98 | + }); |
| 99 | + } else { |
| 100 | + params.push(param.name.escapedText); |
| 101 | + } |
| 102 | + }); |
| 103 | + indexFunctions.push({ name: statement.name.escapedText, params: params }); |
| 104 | + } else { |
| 105 | + if (statement.kind == 252) { // statement kind 252 indicates an enum declare statement |
| 106 | + var enumValues = []; |
| 107 | + statement.members.forEach((member) => { |
| 108 | + enumValues.push(member.name.escapedText); |
| 109 | + }); |
| 110 | + indexEnums.push({ name: statement.name.escapedText, values: enumValues }); |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + }); |
| 115 | + allDef.push({ moduleName: "index", data: { functions: indexFunctions, enums: indexEnums } }); |
| 116 | + return allDef; |
| 117 | +} |
| 118 | + |
| 119 | +function compareModuleDefinition(module, definition, baseFile) { |
| 120 | + var flag = true; |
| 121 | + module.data.functions.forEach((func) => { |
| 122 | + var funcFound = false; |
| 123 | + for (var i = 0; i < definition.data.functions.length; i++) { |
| 124 | + if (func.name === definition.data.functions[i].name) { |
| 125 | + func.params.forEach((param) => { |
| 126 | + var paramFound = false; |
| 127 | + for (var j = 0; j < definition.data.functions[i].params.length; j++) { |
| 128 | + if (param === definition.data.functions[i].params[j]) { |
| 129 | + paramFound = true; |
| 130 | + break; |
| 131 | + } |
| 132 | + } |
| 133 | + if (!paramFound) { |
| 134 | + logParamDiscrepency(param, func.name, module.moduleName, baseFile); |
| 135 | + flag = false; |
| 136 | + } |
| 137 | + }); |
| 138 | + var funcFound = true; |
| 139 | + break; |
| 140 | + } |
| 141 | + } |
| 142 | + if (!funcFound) { |
| 143 | + logFunctionDiscrepency(module.moduleName, func.name, baseFile); |
| 144 | + flag = false; |
| 145 | + } |
| 146 | + }); |
| 147 | + |
| 148 | + module.data.enums.forEach((en) => { |
| 149 | + var enumFound = false; |
| 150 | + for (var i = 0; i < definition.data.enums.length; i++) { |
| 151 | + if (en.name === definition.data.enums[i].name) { |
| 152 | + en.values.forEach((value) => { |
| 153 | + var valueFound = false; |
| 154 | + for (var j = 0; j < definition.data.enums[i].values.length; j++) { |
| 155 | + if (value === definition.data.enums[i].values[j]) { |
| 156 | + valueFound = true; |
| 157 | + break; |
| 158 | + } |
| 159 | + } |
| 160 | + if (!valueFound) { |
| 161 | + logEnumValueDiscrepency(value, en.name, module.moduleName, baseFile); |
| 162 | + flag = false; |
| 163 | + } |
| 164 | + }); |
| 165 | + var enumFound = true; |
| 166 | + break; |
| 167 | + } |
| 168 | + } |
| 169 | + if (!enumFound) { |
| 170 | + logEnumDiscrepency(en.name, module.moduleName, fileName); |
| 171 | + flag = false; |
| 172 | + } |
| 173 | + }); |
| 174 | + |
| 175 | + return flag; |
| 176 | +} |
| 177 | + |
| 178 | +function logModuleDiscrepency(module, fileName) { |
| 179 | + console.error(" Module: " + module + " was not found in" + fileName); |
| 180 | +} |
| 181 | + |
| 182 | +function logFunctionDiscrepency(module, func, fileName) { |
| 183 | + console.error("Function: " + func + " in Module: " + module + " was not found in " + fileName); |
| 184 | + |
| 185 | +} |
| 186 | + |
| 187 | +function logParamDiscrepency(param, func, module, fileName) { |
| 188 | + console.error("Param: " + param + " for the function: " + func + " in Module: " + module + " was not found in " + fileName); |
| 189 | +} |
| 190 | + |
| 191 | +function logEnumDiscrepency(en, module, fileName) { |
| 192 | + console.error("Enum: " + en + " in Module: " + module + " was not found in " + fileName) |
| 193 | +} |
| 194 | + |
| 195 | + |
| 196 | +function logEnumValueDiscrepency(value, en, module, fileName) { |
| 197 | + console.error("Value: " + value + " for the Enum: " + en + " in Module: " + module + " was not found in " + fileName); |
| 198 | +} |
| 199 | + |
| 200 | + |
| 201 | +function finish(logLevel, message) { |
| 202 | + if (logLevel === LOG_LEVEL_SUCCESS) { |
| 203 | + console.info(message); |
| 204 | + } else { |
| 205 | + console.error(message); |
| 206 | + process.exit(1); |
| 207 | + } |
| 208 | +} |
| 209 | + |
| 210 | +var allMatch = true; |
| 211 | +var all = []; |
| 212 | +var allDef = []; |
| 213 | +var modules = fs.readdirSync(MODULE_DIR); |
| 214 | +for (var i = 0; i < modules.length; i++) { |
| 215 | + modules[i] = MODULE_DIR + modules[i]; |
| 216 | +} |
| 217 | +modules.push(INDEX_FILE); |
| 218 | +modules.forEach((module) => { |
| 219 | + var data = fs.readFileSync(module); |
| 220 | + all.push({ moduleName: module.replace('.js', '').replace(MODULE_DIR, ''), data: parseModule(data.toString()) }); |
| 221 | +}); |
| 222 | + |
| 223 | +allDef = parseDefinition(); |
| 224 | + |
| 225 | +console.log(JS_FILES + ' <-- ' + TS_DEF_FILE); |
| 226 | +for (var i = 0; i < allDef.length; i++) { |
| 227 | + var moduleFound = false; |
| 228 | + for (var j = 0; j < all.length; j++) { |
| 229 | + if (allDef[i].moduleName === all[j].moduleName) { |
| 230 | + if (!compareModuleDefinition(allDef[i], all[j], JS_FILES)) { |
| 231 | + allMatch = false; |
| 232 | + } |
| 233 | + moduleFound = true; |
| 234 | + break; |
| 235 | + } |
| 236 | + } |
| 237 | + if (!moduleFound) { |
| 238 | + logModuleDiscrepency(allDef[i].moduleName, JS_FILES); |
| 239 | + allMatch = false; |
| 240 | + } |
| 241 | +} |
| 242 | +if (allMatch) { |
| 243 | + console.log('✅'); |
| 244 | +} |
| 245 | +console.log('\n'); |
| 246 | +console.log(TS_DEF_FILE + ' <-- ' + JS_FILES); |
| 247 | +for (var i = 0; i < all.length; i++) { |
| 248 | + var moduleFound = false; |
| 249 | + for (var j = 0; j < allDef.length; j++) { |
| 250 | + if (all[i].moduleName === allDef[j].moduleName) { |
| 251 | + if (!compareModuleDefinition(all[i], allDef[j], TS_DEF_FILE)) { |
| 252 | + allMatch = false; |
| 253 | + } |
| 254 | + moduleFound = true; |
| 255 | + break; |
| 256 | + } |
| 257 | + } |
| 258 | + if (!moduleFound) { |
| 259 | + logModuleDiscrepency(all[i].moduleName, TS_DEF_FILE); |
| 260 | + allMatch = false; |
| 261 | + } |
| 262 | +} |
| 263 | +if (allMatch) { |
| 264 | + console.log('✅'); |
| 265 | + finish(LOG_LEVEL_SUCCESS, "Javascript and Typescript Definition match!") |
| 266 | +} else { |
| 267 | + finish(LOG_LEVEL_FAIL, "\n ❌ Javascript and Typescript Definition do not match!") |
| 268 | + |
| 269 | +} |
0 commit comments