• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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