1/* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// singleton to parse commandLine infos 17import commandLineArgs from "command-line-args"; 18import commandLineUsage from "command-line-usage"; 19import * as ts from "typescript"; 20import { LOGE } from "./log"; 21import * as path from "path"; 22import { execute } from "./base/util"; 23 24export const ts2pandaOptions = [ 25 { name: 'commonjs', alias: 'c', type: Boolean, defaultValue: false, description: "compile as commonJs module." }, 26 { name: 'modules', alias: 'm', type: Boolean, defaultValue: false, description: "compile as module." }, 27 { name: 'debug-log', alias: 'l', type: Boolean, defaultValue: false, description: "show info debug log and generate the json file." }, 28 { name: 'dump-assembly', alias: 'a', type: Boolean, defaultValue: false, description: "dump assembly to file." }, 29 { name: 'debug', alias: 'd', type: Boolean, defaultValue: false, description: "compile with debug info." }, 30 { name: 'debug-add-watch', alias: 'w', type: String, lazyMultiple: true, defaultValue: [], description: "watch expression, abc file path and maybe watchTimeOut(in seconds) in debug mode." }, 31 { name: 'keep-persistent-watch', alias: 'k', type: String, lazyMultiple: true, defaultValue: [], description: "keep persistent watch on js file with watched expression." }, 32 { name: 'show-statistics', alias: 's', type: String, lazyMultiple: true, defaultValue: "", description: "show compile statistics(ast, histogram, hoisting, all)." }, 33 { name: 'output', alias: 'o', type: String, defaultValue: "", description: "set output file." }, 34 { name: 'timeout', alias: 't', type: Number, defaultValue: 0, description: "js to abc timeout threshold(unit: seconds)." }, 35 { name: 'opt-log-level', type: String, defaultValue: "error", description: "specifie optimizer log level. Possible values: ['debug', 'info', 'error', 'fatal']" }, 36 { 37 name: 'opt-level', type: Number, defaultValue: 2, description: "Optimization level. Possible values: [0, 1, 2]. Default: 0\n 0: no optimizations\n \ 38 1: basic bytecode optimizations, including valueNumber, lowering, constantResolver, regAccAllocator\n \ 39 2: other bytecode optimizations, unimplemented yet"}, 40 { name: 'help', alias: 'h', type: Boolean, description: "Show usage guide." }, 41 { name: 'bc-version', alias: 'v', type: Boolean, defaultValue: false, description: "Print ark bytecode version" }, 42 { name: 'bc-min-version', type: Boolean, defaultValue: false, description: "Print ark bytecode minimum supported version" }, 43 { name: 'included-files', alias: 'i', type: String, lazyMultiple: true, defaultValue: [], description: "The list of dependent files." }, 44 { name: 'record-type', alias: 'p', type: Boolean, defaultValue: false, description: "Record type info. Default: true" }, 45 { name: 'dts-type-record', alias: 'q', type: Boolean, defaultValue: false, description: "Record type info for .d.ts files. Default: false" }, 46 { name: 'dts-builtin-type-record', alias: 'b', type: Boolean, defaultValue: false, description: "Recognize builtin types for .d.ts files. Default: false" }, 47 { name: 'debug-type', alias: 'g', type: Boolean, defaultValue: false, description: "Print type-related log. Default: false" }, 48 { name: 'output-type', type: Boolean, defaultValue: false, description: "set output type."}, 49 { name: 'display-typeinfo', type: Boolean, defaultValue: false, description: "Display typeinfo of pairs of instruction orders and types when enable-typeinfo is true" }, 50 { name: 'function-sourcecode', type: Boolean, defaultValue: false, description: "Record functions' sourceCode to support the feature of [function].toString()" }, 51 { name: 'expression-watch-toolchain', type: String, defaultValue: "es2panda", description: "Specify the tool chain used to transform the expression" }, 52 { name: 'source-file', type: String, defaultValue: "", description: "specify the file path info recorded in generated abc" }, 53 { name: 'generate-tmp-file', type: Boolean, defaultValue: false, description: "whether to generate intermediate temporary files"}, 54 { name: 'record-name', type: String, defaultValue: "", description: "specify the record name, this option can only be used when [merge-abc] is enabled." }, 55 { name: 'package-name', type: String, defaultValue: "", description: "specify the package that the compiling file belongs to." }, 56 { name: 'output-proto', type: Boolean, defaultValue: false, description: "Output protoBin file. Default: false" }, 57 { name: 'merge-abc', type: Boolean, defaultValue: false, description: "Compile as merge abc" }, 58 { name: 'input-file', type: String, defaultValue: "", description: "A file containing a list of source files to be compiled. Each line of this file should be constructed in such format: fileName;recordName;moduleType;sourceFile;packageName" }, 59 { name: 'oh-modules', type: Boolean, defaultValue: false, description: "Set oh-modules as typescript compiler's package manager type. Default: false" } 60] 61 62 63 64export class CmdOptions { 65 private static parsedResult: ts.ParsedCommandLine; 66 private static options: commandLineArgs.CommandLineOptions; 67 68 static getDisplayTypeinfo(): boolean { 69 if (!this.options) { 70 return false; 71 } 72 return this.options["display-typeinfo"]; 73 } 74 75 static isEnableDebugLog(): boolean { 76 if (!this.options) { 77 return false; 78 } 79 return this.options["debug-log"]; 80 } 81 82 static isAssemblyMode(): boolean { 83 if (!this.options) { 84 return false; 85 } 86 return this.options["dump-assembly"]; 87 } 88 89 static isDebugMode(): boolean { 90 if (!this.options) { 91 return false; 92 } 93 return this.options["debug"]; 94 } 95 96 static setWatchEvaluateExpressionArgs(watchArgs: string[]): void { 97 this.options["debug-add-watch"] = watchArgs; 98 } 99 100 static getDeamonModeArgs(): string[] { 101 if (!this.options) { 102 return []; 103 } 104 return this.options["keep-persistent-watch"]; 105 } 106 107 static isWatchEvaluateDeamonMode(): boolean { 108 return CmdOptions.getDeamonModeArgs()[0] === "start"; 109 } 110 111 static isStopEvaluateDeamonMode(): boolean { 112 return CmdOptions.getDeamonModeArgs()[0] === "stop"; 113 } 114 115 static getEvaluateDeamonPath(): string { 116 return CmdOptions.getDeamonModeArgs()[1]; 117 } 118 119 static isWatchEvaluateExpressionMode(): boolean { 120 if (!this.options) { 121 return false; 122 } 123 return this.options["debug-add-watch"].length != 0; 124 } 125 126 static getEvaluateExpression(): string { 127 return this.options["debug-add-watch"][0]; 128 } 129 130 static getWatchJsPath(): string { 131 return this.options["debug-add-watch"][1]; 132 } 133 134 static getWatchTimeOutValue(): number { 135 if (this.options["debug-add-watch"].length === 2) { 136 return 0; 137 } 138 return this.options["debug-add-watch"][2]; 139 } 140 141 static watchViaEs2pandaToolchain(): boolean { 142 if (!this.options) { 143 return false; 144 } 145 if (this.options["expression-watch-toolchain"] && this.options["expression-watch-toolchain"] != "es2panda") { 146 return false; 147 } 148 return true; 149 } 150 151 static isCompileFilesList(): boolean { 152 if (!this.options) { 153 return false; 154 } 155 return this.options["input-file"].length != 0; 156 } 157 158 static getCompileFilesList(): string { 159 return this.options["input-file"]; 160 } 161 162 static isCommonJs(): boolean { 163 if (!this.options) { 164 return false; 165 } 166 167 if (this.options["commonjs"] && this.options["modules"]) { 168 throw new Error("Can not compile with [-c] and [-m] options at the same time"); 169 } 170 171 return this.options["commonjs"]; 172 } 173 174 static isModules(): boolean { 175 if (!this.options) { 176 return false; 177 } 178 179 if (this.options["modules"] && this.options["commonjs"]) { 180 throw new Error("Can not compile with [-m] and [-c] options at the same time"); 181 } 182 183 return this.options["modules"]; 184 } 185 186 static getOptLevel(): number { 187 return this.options["opt-level"]; 188 } 189 190 static getOptLogLevel(): string { 191 return this.options["opt-log-level"]; 192 } 193 194 static showASTStatistics(): boolean { 195 if (!this.options) { 196 return false; 197 } 198 return this.options["show-statistics"].includes("ast") || this.options["show-statistics"].includes("all"); 199 } 200 201 static showHistogramStatistics(): boolean { 202 if (!this.options) { 203 return false; 204 } 205 return this.options["show-statistics"].includes("all") || this.options["show-statistics"].includes("histogram"); 206 } 207 208 static showHoistingStatistics(): boolean { 209 if (!this.options) { 210 return false; 211 } 212 return this.options["show-statistics"].includes("all") || this.options["show-statistics"].includes("hoisting"); 213 } 214 215 static getInputFileName(): string { 216 let path = this.parsedResult.fileNames[0]; 217 let inputFile = path.substring(0, path.lastIndexOf('.')); 218 return inputFile; 219 } 220 221 static getOutputBinName(): string { 222 let outputFile = this.options.output; 223 if (outputFile === "") { 224 outputFile = CmdOptions.getInputFileName() + ".abc"; 225 } 226 return outputFile; 227 } 228 229 static setMergeAbc(mergeAbcMode: Boolean): void { 230 if (!this.options) { 231 return; 232 } 233 this.options["merge-abc"] = mergeAbcMode; 234 } 235 236 static getRecordName(): string { 237 if (!this.options || !this.options["merge-abc"]) { 238 return ""; 239 } 240 241 return this.options["record-name"]; 242 } 243 244 static getTimeOut(): Number { 245 if (!this.options) { 246 return 0; 247 } 248 return this.options["timeout"]; 249 } 250 251 static isOutputType(): false { 252 if (!this.options) { 253 return false; 254 } 255 return this.options["output-type"]; 256 } 257 258 static showHelp(): void { 259 const usage = commandLineUsage([ 260 { 261 header: "Ark JavaScript Compiler", 262 content: 'node --expose-gc index.js [options] file.js' 263 }, 264 { 265 header: 'Options', 266 optionList: ts2pandaOptions 267 }, 268 { 269 content: 'Project Ark' 270 } 271 ]) 272 LOGE(usage); 273 } 274 275 static isBcVersion(): boolean { 276 if (!this.options) { 277 return false; 278 } 279 return this.options["bc-version"]; 280 } 281 282 static getVersion(isBcVersion: boolean = true): void { 283 let js2abc = path.join(path.resolve(__dirname, '../bin'), "js2abc"); 284 let versionArg = isBcVersion ? "--bc-version" : "--bc-min-version"; 285 execute(`${js2abc}`, [versionArg]); 286 } 287 288 static isBcMinVersion(): boolean { 289 if (!this.options) { 290 return false; 291 } 292 return this.options["bc-min-version"]; 293 } 294 295 static getIncludedFiles(): string[] { 296 if (!this.options) { 297 return []; 298 } 299 300 return this.options["included-files"]; 301 } 302 303 static needRecordType(): boolean { 304 if (!this.options) { 305 return false; 306 } 307 308 return !this.options["record-type"]; 309 } 310 311 static needRecordDtsType(): boolean { 312 if (!this.options) { 313 return false; 314 } 315 return this.options["dts-type-record"]; 316 } 317 318 static needRecordBuiltinDtsType(): boolean { 319 if (!this.options) { 320 return false; 321 } 322 return this.options["dts-builtin-type-record"]; 323 } 324 325 static enableTypeLog(): boolean { 326 if (!this.options) { 327 return false; 328 } 329 return this.options["debug-type"]; 330 } 331 332 static needRecordSourceCode(): boolean { 333 if (!this.options) { 334 return false; 335 } 336 return this.options["function-sourcecode"]; 337 } 338 339 static getSourceFile(): string { 340 return this.options["source-file"]; 341 } 342 343 static getPackageName(): string { 344 if (!this.options || !this.options["merge-abc"]) { 345 return ""; 346 } 347 348 return this.options["package-name"]; 349 } 350 351 static needGenerateTmpFile(): boolean { 352 if (!this.options) { 353 return false; 354 } 355 return this.options["generate-tmp-file"]; 356 } 357 358 static isOutputproto(): boolean { 359 if (!this.options) { 360 return false; 361 } 362 return this.options["output-proto"]; 363 } 364 365 static isMergeAbc(): boolean { 366 if (!this.options) { 367 return false; 368 } 369 return this.options["merge-abc"] 370 } 371 372 static isOhModules(): boolean { 373 if (!this.options) { 374 return false; 375 } 376 return this.options["oh-modules"] 377 } 378 379 // @ts-ignore 380 static parseUserCmd(args: string[]): ts.ParsedCommandLine | undefined { 381 this.options = commandLineArgs(ts2pandaOptions, { partial: true }); 382 if (this.options.help) { 383 this.showHelp(); 384 return undefined; 385 } 386 387 if (this.isBcVersion() || this.isBcMinVersion()) { 388 this.getVersion(this.isBcVersion()); 389 return undefined; 390 } 391 392 if (!this.options._unknown) { 393 LOGE("options at least one file is needed"); 394 this.showHelp(); 395 return undefined; 396 } 397 398 this.parsedResult = ts.parseCommandLine(this.options._unknown!); 399 return this.parsedResult; 400 } 401 402 static parseCustomLibrary(args: string[]): string[] | undefined { 403 this.options = commandLineArgs(ts2pandaOptions, { partial: true }); 404 if (this.options.help || this.isBcVersion() || this.isBcMinVersion() || !this.options._unknown) { 405 return undefined; 406 } 407 this.parsedResult = ts.parseCommandLine(this.options._unknown!); 408 return this.parsedResult.options["lib"]; 409 } 410} 411