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 16import * as path from "path"; 17import * as ts from "typescript"; 18import * as fs from "fs"; 19import { CmdOptions, ts2pandaOptions } from "./cmdOptions"; 20import commandLineArgs from "command-line-args"; 21import { CompilerDriver } from "./compilerDriver"; 22import * as diag from "./diagnostic"; 23import * as jshelpers from "./jshelpers"; 24import { 25 LOGE, 26 printAstRecursively 27} from "./log"; 28import { 29 setGlobalDeclare, 30 setGlobalStrict 31} from "./strictMode"; 32import { TypeChecker } from "./typeChecker"; 33import { IGNORE_ERROR_CODE } from './ignoreSyntaxError'; 34import { 35 setPos, 36 isBase64Str, 37 transformCommonjsModule, 38 getRecordName, 39 getOutputBinName, 40 terminateWritePipe, 41 makeNameForGeneratedNode, 42 resetUniqueNameIndex 43} from "./base/util"; 44 45function checkIsGlobalDeclaration(sourceFile: ts.SourceFile): boolean { 46 for (let statement of sourceFile.statements) { 47 if (statement.modifiers) { 48 for (let modifier of statement.modifiers) { 49 if (modifier.kind === ts.SyntaxKind.ExportKeyword) { 50 return false; 51 } 52 } 53 } else if (statement.kind === ts.SyntaxKind.ExportAssignment) { 54 return false; 55 } else if (statement.kind === ts.SyntaxKind.ImportKeyword || statement.kind === ts.SyntaxKind.ImportDeclaration) { 56 return false; 57 } 58 } 59 return true; 60} 61 62function generateDTs(node: ts.SourceFile, options: ts.CompilerOptions): void { 63 let outputBinName = getOutputBinName(node); 64 let compilerDriver = new CompilerDriver(outputBinName, getRecordName(node)); 65 CompilerDriver.srcNode = node; 66 setGlobalStrict(jshelpers.isEffectiveStrictModeSourceFile(node, options)); 67 compilerDriver.compile(node); 68 compilerDriver.showStatistics(); 69} 70 71function main(fileNames: string[], options: ts.CompilerOptions, inputJsonFiles?: string[], cmdArgsSet?: Map<string, string[]>): void { 72 const host = ts.createCompilerHost(options); 73 if (!CmdOptions.needGenerateTmpFile()) { 74 host.writeFile = () => {}; 75 } 76 77 let program = ts.createProgram(fileNames, options, host); 78 let typeChecker = TypeChecker.getInstance(); 79 typeChecker.setTypeChecker(program.getTypeChecker()); 80 81 if (CmdOptions.needRecordDtsType()) { 82 for (let sourceFile of program.getSourceFiles()) { 83 let originFileNames; 84 if (!Compiler.Options.Default["lib"]) { 85 originFileNames = new Set(fileNames.slice(0, fileNames.length - dtsFiles.length)); 86 } else { 87 originFileNames = new Set(fileNames.slice(0, fileNames.length)); 88 } 89 if (sourceFile.isDeclarationFile && !program.isSourceFileDefaultLibrary(sourceFile) && originFileNames.has(sourceFile.fileName)) { 90 setGlobalDeclare(checkIsGlobalDeclaration(sourceFile)); 91 generateDTs(sourceFile, options); 92 } 93 } 94 } 95 96 if (checkDiagnosticsError(program)) { 97 return; 98 } 99 100 let initCompilerDriver = new CompilerDriver("", ""); 101 let initTs2abcProcessState = false; 102 103 let emitResult = program.emit( 104 undefined, 105 undefined, 106 undefined, 107 undefined, 108 { 109 before: [ 110 // @ts-ignore 111 (ctx: ts.TransformationContext) => { 112 return (node: ts.SourceFile) => { 113 if (CmdOptions.isCompileFilesList()) { 114 let specifiedCmdArgs = cmdArgsSet.get(node.fileName); 115 if (specifiedCmdArgs === undefined) { 116 return node; 117 } 118 let originProcessArgs = process.argv.slice(0); 119 // insert specifiedCmdArgs's elements ahead the process.argv's element 120 // whose index is 2 (count from 0) 121 Array.prototype.splice.apply(process.argv, specifiedCmdArgs); 122 //@ts-ignore 123 CmdOptions.options = commandLineArgs(ts2pandaOptions, { partial: true }); 124 process.argv = originProcessArgs.slice(0); 125 } 126 let outputBinName = getOutputBinName(node); 127 let compilerDriver = new CompilerDriver(outputBinName, getRecordName(node)); 128 compilerDriver.compileForSyntaxCheck(node); 129 return node; 130 } 131 } 132 ], 133 after: [ 134 // @ts-ignore 135 (ctx: ts.TransformationContext) => { 136 return (node: ts.SourceFile) => { 137 if (CmdOptions.isCompileFilesList()) { 138 if (!initTs2abcProcessState) { 139 initCompilerDriver.initiateTs2abcChildProcess(["--multi-programs-pipe"]); 140 initTs2abcProcessState = true; 141 } 142 let specifiedCmdArgs = cmdArgsSet.get(node.fileName); 143 if (specifiedCmdArgs === undefined) { 144 return node; 145 } 146 } 147 resetUniqueNameIndex(); 148 makeNameForGeneratedNode(node); 149 printAstRecursively(node, 0, node); 150 151 if (ts.getEmitHelpers(node)) { 152 let newStatements = []; 153 ts.getEmitHelpers(node)?.forEach( 154 item => { 155 let emitHelperSourceFile = ts.createSourceFile(node.fileName, <string>item.text, options.target!, true, ts.ScriptKind.JS); 156 emitHelperSourceFile.statements.forEach(emitStatement => { 157 let emitNode = setPos(emitStatement); 158 newStatements.push(emitNode); 159 }); 160 } 161 ) 162 newStatements.push(...node.statements); 163 node = ts.factory.updateSourceFile(node, newStatements); 164 } 165 if (CmdOptions.isCommonJs()) { 166 node = transformCommonjsModule(node); 167 } 168 let outputBinName = getOutputBinName(node); 169 let compilerDriver = new CompilerDriver(outputBinName, getRecordName(node)); 170 CompilerDriver.srcNode = node; 171 setGlobalStrict(jshelpers.isEffectiveStrictModeSourceFile(node, options)); 172 if (CmdOptions.isCompileFilesList()) { 173 compilerDriver.compile(node, initCompilerDriver.getTs2abcProcess()); 174 } else { 175 compilerDriver.compile(node); 176 } 177 compilerDriver.showStatistics(); 178 return node; 179 } 180 } 181 ] 182 } 183 ); 184 185 if (CmdOptions.isCompileFilesList()) { 186 if (!initTs2abcProcessState) { 187 initCompilerDriver.initiateTs2abcChildProcess(["--multi-programs-pipe"]); 188 } 189 for (let i = 0; i < inputJsonFiles.length; i++) { 190 let jsonFileName = inputJsonFiles[i]; 191 let originProcessArgs = process.argv.slice(0); 192 Array.prototype.splice.apply(process.argv, cmdArgsSet.get(jsonFileName)); 193 //@ts-ignore 194 CmdOptions.options = commandLineArgs(ts2pandaOptions, { partial: true }); 195 process.argv = originProcessArgs.slice(0); 196 initCompilerDriver.dumpInputJsonFileContent(initCompilerDriver.getTs2abcProcess(), jsonFileName, CmdOptions.getRecordName()); 197 } 198 terminateWritePipe(initCompilerDriver.getTs2abcProcess()); 199 } 200 201 let allDiagnostics = ts 202 .getPreEmitDiagnostics(program) 203 .concat(emitResult.diagnostics); 204 205 allDiagnostics.forEach(diagnostic => { 206 let ignoerErrorSet = new Set(IGNORE_ERROR_CODE); 207 if (ignoerErrorSet.has(diagnostic.code)) { 208 return; 209 } 210 diag.printDiagnostic(diagnostic); 211 }); 212} 213 214function transformSourcefilesList(parsed: ts.ParsedCommandLine | undefined): void { 215 let inputFile = CmdOptions.getCompileFilesList(); 216 let sourceFileInfoArray = []; 217 try { 218 sourceFileInfoArray = fs.readFileSync(inputFile).toString().split("\n"); 219 } catch(err) { 220 throw err; 221 } 222 if (sourceFileInfoArray.length === 0) return; 223 224 let files: string[] = parsed.fileNames; 225 let inputJsonFiles: string[] = []; 226 let cmdArgsSet = new Map(); 227 228 for (let i = 0; i < sourceFileInfoArray.length; i++) { 229 let sourceFileInfo = sourceFileInfoArray[i].split(";"); 230 if (sourceFileInfo.length != 5) { 231 throw new Error( 232 "Input info for each file need \"fileName;recordName;moduleType;sourceFile;packageName\" style."); 233 } 234 235 let inputFileName = sourceFileInfo[0]; 236 let inputFileSuffix = inputFileName.substring(inputFileName.lastIndexOf(".") + 1); 237 if (inputFileSuffix === "json") { 238 inputJsonFiles.push(inputFileName); 239 } else { 240 files.unshift(inputFileName); 241 } 242 243 let specifiedCmdArgs = ["--record-name", sourceFileInfo[1]]; 244 switch (sourceFileInfo[2]) { 245 case "esm": 246 specifiedCmdArgs.push.apply(specifiedCmdArgs, ["-m"]); 247 break; 248 case "commonjs": 249 specifiedCmdArgs.push.apply(specifiedCmdArgs, ["-c"]); 250 break; 251 case "na": 252 break; 253 default: 254 throw new Error("The third info should be \"esm\", \"commonjs\" or \"na\"."); 255 } 256 specifiedCmdArgs.push.apply(specifiedCmdArgs, ["--source-file", sourceFileInfo[3]]); 257 specifiedCmdArgs.push.apply(specifiedCmdArgs, ["--package-name", sourceFileInfo[4]]); 258 // insert two elements 2 and 0 at the beginning of the specifiedCmdArgs array, 259 // useful for Array.prototype.splice.apply 260 specifiedCmdArgs.unshift(2, 0); 261 cmdArgsSet.set(inputFileName, specifiedCmdArgs); 262 } 263 264 main(files, parsed.options, inputJsonFiles, cmdArgsSet); 265} 266 267function getDtsFiles(libDir: string): string[] { 268 let dtsFiles:string[] = []; 269 function finDtsFile(dir){ 270 let files = fs.readdirSync(dir); 271 files.forEach(function (item, _) { 272 let fPath = path.join(dir,item); 273 let stat = fs.statSync(fPath); 274 if(stat.isDirectory() === true) { 275 finDtsFile(fPath); 276 } 277 if (stat.isFile() === true && item.endsWith(".d.ts") === true) { 278 dtsFiles.push(fPath); 279 } 280 }); 281 } 282 finDtsFile(libDir); 283 return dtsFiles; 284} 285 286function specifyCustomLib(customLib): void { 287 Compiler.Options.Default["lib"] = customLib; 288 let curFiles = fs.readdirSync(__dirname); 289 const { execSync } = require('child_process'); 290 dtsFiles.forEach(function (dtsFile, _) { 291 let target = dtsFile.split(path.sep).pop(); 292 if (curFiles.indexOf(target) === -1){ 293 let createSymlinkCmd; 294 if (/^win/.test(require('os').platform())) { 295 // On windows platform, administrator permission is needed to create Symlink. 296 createSymlinkCmd = `mklink ${__dirname}${path.sep}${target} ${dtsFile}`; 297 } else { 298 createSymlinkCmd = `ln -s ${dtsFile} ${__dirname}${path.sep}${target}`; 299 } 300 execSync(createSymlinkCmd, (err, stdout, stderr) => { 301 if (err) { 302 LOGE(err); 303 } 304 }); 305 } 306 }); 307} 308 309const stopWatchingStr = "####"; 310const watchAbcFileDefaultTimeOut = 10; 311const watchFileName = "watch_expressions"; 312const watchOutputFileName = "Base64Output"; 313// this path is only available in sdk 314const es2abcBinaryPath = path["join"](__dirname, "..", "bin", path.sep); 315const es2abcBinaryName = /^win/.test(require('os').platform()) ? "es2abc.exe" : "es2abc"; 316const es2abcBase64Input = "--base64Input"; 317const es2abcDebuggerEvaluateFlag = "--debugger-evaluate-expression"; 318const es2abcBase64Output = "--base64Output"; 319// need to specify the record name as 'Base64Output' in es2abc's commandline; cancel the opMergeAbc option 320 321function callEs2pandaToolChain(ideInputStr: string): void { 322 let commandLine = "\"" + es2abcBinaryPath + es2abcBinaryName + "\" " + es2abcBase64Input + " \"" + ideInputStr 323 + "\" " + es2abcDebuggerEvaluateFlag + " " + es2abcBase64Output; 324 let exec = require('child_process').exec; 325 exec(`${commandLine}`, function(error, stdout) { 326 if (error) { 327 console.log("generate abc file failed, please check the input string and syntax of the expression"); 328 return; 329 } 330 process.stdout.write(stdout); 331 }); 332} 333 334function updateWatchJsFile(): void { 335 let ideInputStr = CmdOptions.getEvaluateExpression(); 336 if (CmdOptions.watchViaEs2pandaToolchain()) { 337 callEs2pandaToolChain(ideInputStr); 338 return; 339 } 340 if (!isBase64Str(ideInputStr)) { 341 throw new Error("Passed expression string for evaluating is not base64 style."); 342 } 343 let watchAbcFileTimeOut = watchAbcFileDefaultTimeOut; 344 if (CmdOptions.getWatchTimeOutValue() != 0) { watchAbcFileTimeOut = CmdOptions.getWatchTimeOutValue(); } 345 let watchFilePrefix = CmdOptions.getWatchJsPath() + path.sep + watchFileName; 346 let originExpre = Buffer.from(ideInputStr, 'base64').toString(); 347 let jsFileName = watchFilePrefix + ".js"; 348 let abcFileName = watchFilePrefix + ".abc"; 349 let errorMsgFileName = watchFilePrefix + ".err"; 350 351 fs.watchFile(errorMsgFileName, { persistent: true, interval: 50 }, (curr, prev) => { 352 if (+curr.mtime <= +prev.mtime) { 353 fs.unwatchFile(jsFileName); 354 fs.unwatchFile(abcFileName); 355 throw new Error("watched errMsg file has not been initialized"); 356 } 357 console.log("error in genarate abc file for this expression."); 358 fs.unwatchFile(abcFileName); 359 fs.unwatchFile(errorMsgFileName); 360 process.exit(); 361 }); 362 fs.watchFile(abcFileName, { persistent: true, interval: 50 }, (curr, prev) => { 363 if (+curr.mtime <= +prev.mtime) { 364 fs.unwatchFile(jsFileName); 365 fs.unwatchFile(errorMsgFileName); 366 throw new Error("watched abc file has not been initialized"); 367 } 368 let base64data = fs.readFileSync(abcFileName); 369 let watchResStr = Buffer.from(base64data).toString('base64'); 370 console.log(watchResStr); 371 fs.unwatchFile(abcFileName); 372 fs.unwatchFile(errorMsgFileName); 373 process.exit(); 374 }); 375 fs.writeFileSync(jsFileName, originExpre); 376 setTimeout(() => { 377 fs.unwatchFile(jsFileName); 378 fs.unwatchFile(abcFileName); 379 fs.unwatchFile(errorMsgFileName); 380 fs.unlinkSync(jsFileName); 381 fs.unlinkSync(abcFileName); 382 fs.unlinkSync(errorMsgFileName); 383 throw new Error("watchFileServer has not been initialized"); 384 }, watchAbcFileTimeOut*1000); 385} 386 387function compileWatchExpression(jsFileName: string, errorMsgFileName: string, options: ts.CompilerOptions, 388 watchedProgram: ts.Program): void { 389 CmdOptions.setWatchEvaluateExpressionArgs(['','']); 390 let fileName = watchFileName + ".js"; 391 let errorMsgRecordFlag = false; 392 let sourceFile = ts.createSourceFile(fileName, fs.readFileSync(jsFileName).toString(), ts.ScriptTarget.ES2017); 393 let jsFileDiagnostics = watchedProgram.getSyntacticDiagnostics(sourceFile); 394 jsFileDiagnostics.forEach(diagnostic => { 395 if (!errorMsgRecordFlag) { 396 fs.writeFileSync(errorMsgFileName, "There are syntax errors in input expression.\n"); 397 errorMsgRecordFlag = true; 398 } 399 diag.printDiagnostic(diagnostic); 400 return; 401 }); 402 if (errorMsgRecordFlag) { 403 return; 404 } 405 watchedProgram.emit( 406 undefined, 407 undefined, 408 undefined, 409 undefined, 410 { 411 before: [ 412 // @ts-ignore 413 (ctx: ts.TransformationContext) => { 414 return (node: ts.SourceFile) => { 415 if (path.basename(node.fileName) === fileName) { node = sourceFile; } 416 let outputBinName = getOutputBinName(node); 417 let compilerDriver = new CompilerDriver(outputBinName, watchOutputFileName); 418 compilerDriver.compileForSyntaxCheck(node); 419 return node; 420 } 421 } 422 ], 423 after: [ 424 // @ts-ignore 425 (ctx: ts.TransformationContext) => { 426 return (node: ts.SourceFile) => { 427 resetUniqueNameIndex(); 428 makeNameForGeneratedNode(node); 429 if (ts.getEmitHelpers(node)) { 430 let newStatements = []; 431 ts.getEmitHelpers(node)?.forEach( 432 item => { 433 let emitHelperSourceFile = ts.createSourceFile(node.fileName, <string>item.text, options.target!, true, ts.ScriptKind.JS); 434 emitHelperSourceFile.statements.forEach(emitStatement => { 435 let emitNode = setPos(emitStatement); 436 newStatements.push(emitNode); 437 }); 438 } 439 ) 440 newStatements.push(...node.statements); 441 node = ts.factory.updateSourceFile(node, newStatements); 442 } 443 let outputBinName = getOutputBinName(node); 444 let compilerDriver = new CompilerDriver(outputBinName, watchOutputFileName); 445 CompilerDriver.srcNode = node; 446 setGlobalStrict(jshelpers.isEffectiveStrictModeSourceFile(node, options)); 447 compilerDriver.compile(node); 448 return node; 449 } 450 } 451 ] 452 } 453 ); 454} 455 456function launchWatchEvaluateDeamon(parsed: ts.ParsedCommandLine | undefined): void { 457 if (CmdOptions.watchViaEs2pandaToolchain()) { 458 console.log("startWatchingSuccess supportTimeout"); 459 return; 460 } 461 let deamonJSFilePrefix = CmdOptions.getEvaluateDeamonPath() + path.sep + watchFileName; 462 let deamonABCFilePrefix = CmdOptions.getEvaluateDeamonPath() + path.sep + watchOutputFileName; 463 let jsFileName = deamonJSFilePrefix + ".js"; 464 let abcFileName = deamonABCFilePrefix + ".abc"; 465 let errorMsgFileName = deamonJSFilePrefix + ".err"; 466 467 if (fs.existsSync(jsFileName)) { 468 console.log("watchFileServer has been initialized supportTimeout"); 469 return; 470 } 471 let files: string[] = parsed.fileNames; 472 fs.writeFileSync(jsFileName, "initJsFile\n"); 473 fs.writeFileSync(errorMsgFileName, "initErrMsgFile\n"); 474 files.unshift(jsFileName); 475 let watchedProgram = ts.createProgram(files, parsed.options); 476 compileWatchExpression(jsFileName, errorMsgFileName, parsed.options, watchedProgram); 477 478 fs.watchFile(jsFileName, { persistent: true, interval: 50 }, (curr, prev) => { 479 if (+curr.mtime <= +prev.mtime) { 480 throw new Error("watched js file has not been initialized"); 481 } 482 if (fs.readFileSync(jsFileName).toString() === stopWatchingStr) { 483 fs.unwatchFile(jsFileName); 484 console.log("stopWatchingSuccess"); 485 return; 486 } 487 compileWatchExpression(jsFileName, errorMsgFileName, parsed.options, watchedProgram); 488 }); 489 console.log("startWatchingSuccess supportTimeout"); 490 491 process.on("exit", () => { 492 fs.unlinkSync(jsFileName); 493 fs.unlinkSync(abcFileName); 494 fs.unlinkSync(errorMsgFileName); 495 }); 496} 497 498function checkDiagnosticsError(program: ts.Program): boolean { 499 let diagnosticsFlag = false; 500 let allDiagnostics = ts 501 .getPreEmitDiagnostics(program); 502 allDiagnostics.forEach(diagnostic => { 503 let ignoerErrorSet = new Set(IGNORE_ERROR_CODE); 504 if (ignoerErrorSet.has(diagnostic.code)) { 505 diagnosticsFlag = false; 506 return; 507 } 508 diagnosticsFlag = true; 509 diag.printDiagnostic(diagnostic); 510 }); 511 512 return diagnosticsFlag; 513} 514 515namespace Compiler { 516 export namespace Options { 517 export let Default: ts.CompilerOptions = { 518 outDir: "../tmp/build", 519 allowJs: true, 520 noEmitOnError: false, 521 noImplicitAny: false, 522 target: ts.ScriptTarget.ES2018, 523 module: ts.ModuleKind.ES2015, 524 strictNullChecks: false, 525 skipLibCheck: true, 526 alwaysStrict: true, 527 importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove, 528 experimentalDecorators: true, 529 preserveConstEnums: true 530 }; 531 } 532} 533 534function run(args: string[], options?: ts.CompilerOptions): void { 535 let parsed = CmdOptions.parseUserCmd(args); 536 if (!parsed) { 537 return; 538 } 539 540 if (options) { 541 if (!((parsed.options.project) || (parsed.options.build))) { 542 parsed.options = options; 543 } 544 } 545 if (CmdOptions.isOhModules()) { 546 parsed.options["packageManagerType"] = "ohpm"; 547 } 548 try { 549 if (CmdOptions.isWatchEvaluateDeamonMode()) { 550 launchWatchEvaluateDeamon(parsed); 551 return; 552 } 553 if (CmdOptions.isStopEvaluateDeamonMode()) { 554 if (CmdOptions.watchViaEs2pandaToolchain()) { 555 console.log("stopWatchingSuccess"); 556 } else { 557 fs.writeFileSync(CmdOptions.getEvaluateDeamonPath() + path.sep + watchFileName + ".js", stopWatchingStr); 558 } 559 return; 560 } 561 if (CmdOptions.isWatchEvaluateExpressionMode()) { 562 updateWatchJsFile(); 563 return; 564 } 565 if (CmdOptions.isCompileFilesList()) { 566 transformSourcefilesList(parsed); 567 return; 568 } 569 570 main(parsed.fileNames.concat(CmdOptions.getIncludedFiles()), parsed.options); 571 } catch (err) { 572 if (err instanceof diag.DiagnosticError) { 573 let diagnostic = diag.getDiagnostic(err.code); 574 if (diagnostic != undefined) { 575 let diagnosticLog = diag.createDiagnostic(err.file, err.irnode, diagnostic, ...err.args); 576 diag.printDiagnostic(diagnosticLog); 577 } 578 } else if (err instanceof SyntaxError) { 579 LOGE(err.name, err.message); 580 } else { 581 throw err; 582 } 583 } 584} 585 586let dtsFiles = getDtsFiles(path["join"](__dirname, "../node_modules/typescript/lib")); 587let customLib = CmdOptions.parseCustomLibrary(process.argv); 588if (!customLib || customLib.length === 0) { 589 process.argv.push(...dtsFiles); 590} else { 591 specifyCustomLib(customLib); 592} 593 594run(process.argv.slice(2), Compiler.Options.Default); 595global.gc(); 596