161847f8eSopenharmony_ci/*
261847f8eSopenharmony_ci * Copyright (c) 2023 Huawei Device Co., Ltd.
361847f8eSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License");
461847f8eSopenharmony_ci * you may not use this file except in compliance with the License.
561847f8eSopenharmony_ci * You may obtain a copy of the License at
661847f8eSopenharmony_ci *
761847f8eSopenharmony_ci *     http://www.apache.org/licenses/LICENSE-2.0
861847f8eSopenharmony_ci *
961847f8eSopenharmony_ci * Unless required by applicable law or agreed to in writing, software
1061847f8eSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS,
1161847f8eSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1261847f8eSopenharmony_ci * See the License for the specific language governing permissions and
1361847f8eSopenharmony_ci * limitations under the License.
1461847f8eSopenharmony_ci */
1561847f8eSopenharmony_ci
1661847f8eSopenharmony_ciconst { Project, Sdk, FileSystem, Logger } = require('./utils');
1761847f8eSopenharmony_ciconst { ApiWriter, ApiExcelWriter } = require('./api_writer');
1861847f8eSopenharmony_ciconst { SystemApiRecognizer } = require('./api_recognizer');
1961847f8eSopenharmony_ciconst { ReporterFormat } = require('./configs');
2061847f8eSopenharmony_ciconst ts = require('typescript');
2161847f8eSopenharmony_ciconst fs = require('fs');
2261847f8eSopenharmony_ciconst path = require('path');
2361847f8eSopenharmony_ci
2461847f8eSopenharmony_ciclass ProgramFactory {
2561847f8eSopenharmony_ci  setLibPath(libPath) {
2661847f8eSopenharmony_ci    this.libPath = libPath;
2761847f8eSopenharmony_ci  }
2861847f8eSopenharmony_ci
2961847f8eSopenharmony_ci  getETSOptions(componentLibs) {
3061847f8eSopenharmony_ci    const tsconfig = require('../tsconfig.json');
3161847f8eSopenharmony_ci    const etsConfig = tsconfig.compilerOptions.ets;
3261847f8eSopenharmony_ci    etsConfig.libs = [...componentLibs];
3361847f8eSopenharmony_ci    return etsConfig;
3461847f8eSopenharmony_ci  }
3561847f8eSopenharmony_ci
3661847f8eSopenharmony_ci  createProgram(rootNames, apiLibs, componentLibs, esLibs) {
3761847f8eSopenharmony_ci    const compilerOption = {
3861847f8eSopenharmony_ci      target: ts.ScriptTarget.ES2017,
3961847f8eSopenharmony_ci      ets: this.getETSOptions([]),
4061847f8eSopenharmony_ci      allowJs: false,
4161847f8eSopenharmony_ci      lib: [...apiLibs, ...componentLibs, ...esLibs],
4261847f8eSopenharmony_ci      module: ts.ModuleKind.CommonJS,
4361847f8eSopenharmony_ci    };
4461847f8eSopenharmony_ci    this.compilerHost = this.createCompilerHost({
4561847f8eSopenharmony_ci      resolveModuleName: (moduleName) => {
4661847f8eSopenharmony_ci        return this.resolveModuleName(moduleName, apiLibs);
4761847f8eSopenharmony_ci      },
4861847f8eSopenharmony_ci    }, compilerOption);
4961847f8eSopenharmony_ci
5061847f8eSopenharmony_ci    if (this.libPath && fs.existsSync(this.libPath)) {
5161847f8eSopenharmony_ci      Logger.info('ProgramFactory', `set default lib location: ${this.libPath}`);
5261847f8eSopenharmony_ci      this.compilerHost.getDefaultLibLocation = () => {
5361847f8eSopenharmony_ci        return this.libPath;
5461847f8eSopenharmony_ci      };
5561847f8eSopenharmony_ci    }
5661847f8eSopenharmony_ci    return ts.createProgram({
5761847f8eSopenharmony_ci      rootNames: [...rootNames],
5861847f8eSopenharmony_ci      options: compilerOption,
5961847f8eSopenharmony_ci      host: this.compilerHost,
6061847f8eSopenharmony_ci    });
6161847f8eSopenharmony_ci  }
6261847f8eSopenharmony_ci
6361847f8eSopenharmony_ci  resolveModuleName(moduleName, libs) {
6461847f8eSopenharmony_ci    if (moduleName.startsWith('@')) {
6561847f8eSopenharmony_ci      const moduleFileName = `${moduleName}.d.ts`;
6661847f8eSopenharmony_ci      const etsModuleFileName = `${moduleName}.d.ets`;
6761847f8eSopenharmony_ci      for (const lib of libs) {
6861847f8eSopenharmony_ci        if (lib.endsWith(moduleFileName) || lib.endsWith(etsModuleFileName)) {
6961847f8eSopenharmony_ci          return lib;
7061847f8eSopenharmony_ci        }
7161847f8eSopenharmony_ci      }
7261847f8eSopenharmony_ci    }
7361847f8eSopenharmony_ci    return undefined;
7461847f8eSopenharmony_ci  }
7561847f8eSopenharmony_ci
7661847f8eSopenharmony_ci  createCompilerHost(moduleResolver, compilerOption) {
7761847f8eSopenharmony_ci    const compilerHost = ts.createCompilerHost(compilerOption);
7861847f8eSopenharmony_ci    compilerHost.resolveModuleNames = this.getResolveModuleNames(moduleResolver);
7961847f8eSopenharmony_ci    return compilerHost;
8061847f8eSopenharmony_ci  }
8161847f8eSopenharmony_ci
8261847f8eSopenharmony_ci  getResolveModuleNames(moduleResolver) {
8361847f8eSopenharmony_ci    return (moduleNames, containingFile, reusedNames, redirectedReference, options) => {
8461847f8eSopenharmony_ci      const resolvedModules = [];
8561847f8eSopenharmony_ci      for (const moduleName of moduleNames) {
8661847f8eSopenharmony_ci        const moduleLookupLocaton = ts.resolveModuleName(moduleName, containingFile, options,
8761847f8eSopenharmony_ci          this.moduleLookupResolutionHost());
8861847f8eSopenharmony_ci        if (moduleLookupLocaton.resolvedModule) {
8961847f8eSopenharmony_ci          resolvedModules.push(moduleLookupLocaton.resolvedModule);
9061847f8eSopenharmony_ci        } else {
9161847f8eSopenharmony_ci          const modulePath = moduleResolver.resolveModuleName(moduleName);
9261847f8eSopenharmony_ci          const resolved = modulePath && ts.sys.fileExists(modulePath) ?
9361847f8eSopenharmony_ci            { resolvedFileName: modulePath } :
9461847f8eSopenharmony_ci            undefined;
9561847f8eSopenharmony_ci          resolvedModules.push(resolved);
9661847f8eSopenharmony_ci        }
9761847f8eSopenharmony_ci      }
9861847f8eSopenharmony_ci      return resolvedModules;
9961847f8eSopenharmony_ci    };
10061847f8eSopenharmony_ci  }
10161847f8eSopenharmony_ci
10261847f8eSopenharmony_ci  moduleLookupResolutionHost() {
10361847f8eSopenharmony_ci    return {
10461847f8eSopenharmony_ci      fileExists: (fileName) => {
10561847f8eSopenharmony_ci        return fileName && ts.sys.fileExists(fileName);
10661847f8eSopenharmony_ci      },
10761847f8eSopenharmony_ci      readFile: (fileName) => {
10861847f8eSopenharmony_ci        ts.sys.readFile(fileName);
10961847f8eSopenharmony_ci      },
11061847f8eSopenharmony_ci    };
11161847f8eSopenharmony_ci  }
11261847f8eSopenharmony_ci}
11361847f8eSopenharmony_ci
11461847f8eSopenharmony_ciclass ApiCollector {
11561847f8eSopenharmony_ci  constructor(argv) {
11661847f8eSopenharmony_ci    const appProject = argv.app ? argv.app : (argv.dir ? argv.dir : undefined);
11761847f8eSopenharmony_ci    if (!appProject) {
11861847f8eSopenharmony_ci      throw 'app not found';
11961847f8eSopenharmony_ci    }
12061847f8eSopenharmony_ci    this.project = new Project(appProject, argv.dir !== undefined);
12161847f8eSopenharmony_ci    this.sdk = new Sdk(this.project, argv.sdk, argv.sdkRoot);
12261847f8eSopenharmony_ci    this.formatFlag = ReporterFormat.getFlag(argv.format);
12361847f8eSopenharmony_ci    this.outputPath = !argv.output ? appProject : argv.output;
12461847f8eSopenharmony_ci    this.logTag = 'ApiCollector';
12561847f8eSopenharmony_ci    this.debugFlag = argv.debug;
12661847f8eSopenharmony_ci    this.noRepeat = argv.noRepeat ? true : false;
12761847f8eSopenharmony_ci  }
12861847f8eSopenharmony_ci
12961847f8eSopenharmony_ci  setLibPath(libPath) {
13061847f8eSopenharmony_ci    this.libPath = libPath;
13161847f8eSopenharmony_ci    if (libPath && !fs.existsSync(this.libPath)) {
13261847f8eSopenharmony_ci      Logger.warn(this.logTag, `${libPath} is not exist`);
13361847f8eSopenharmony_ci    } else {
13461847f8eSopenharmony_ci      Logger.info(this.logTag, `set lib path ${libPath}`);
13561847f8eSopenharmony_ci    }
13661847f8eSopenharmony_ci    return this;
13761847f8eSopenharmony_ci  }
13861847f8eSopenharmony_ci
13961847f8eSopenharmony_ci  setIncludeTest(isIncludeTest) {
14061847f8eSopenharmony_ci    this.isIncludeTest = isIncludeTest;
14161847f8eSopenharmony_ci    return this;
14261847f8eSopenharmony_ci  }
14361847f8eSopenharmony_ci
14461847f8eSopenharmony_ci  async start() {
14561847f8eSopenharmony_ci    const sdkPath = this.sdk.getPath();
14661847f8eSopenharmony_ci    if (!sdkPath || !fs.existsSync(sdkPath)) {
14761847f8eSopenharmony_ci      return;
14861847f8eSopenharmony_ci    }
14961847f8eSopenharmony_ci    const handleFilePath = path.join(sdkPath, '/api/@internal/full/global.d.ts');
15061847f8eSopenharmony_ci    const originalContent = fs.readFileSync(handleFilePath, 'utf-8');
15161847f8eSopenharmony_ci    let newContent = originalContent.replace(/\import|export/g, '');
15261847f8eSopenharmony_ci    fs.writeFileSync(handleFilePath, newContent);
15361847f8eSopenharmony_ci    Logger.info(this.logTag, `scan app ${this.project.getPath()}`);
15461847f8eSopenharmony_ci    Logger.info(this.logTag, `sdk is in ${sdkPath}`);
15561847f8eSopenharmony_ci    const apiLibs = this.sdk.getApiLibs();
15661847f8eSopenharmony_ci    const componentLibs = this.sdk.getComponentLibs();
15761847f8eSopenharmony_ci    const eslibs = this.sdk.getESLibs(this.libPath);
15861847f8eSopenharmony_ci    const appSourceSet = this.project.getAppSources(this.isIncludeTest);
15961847f8eSopenharmony_ci    const programFactory = new ProgramFactory();
16061847f8eSopenharmony_ci    programFactory.setLibPath(this.libPath);
16161847f8eSopenharmony_ci    let program = programFactory.createProgram(appSourceSet, apiLibs, componentLibs, eslibs);
16261847f8eSopenharmony_ci
16361847f8eSopenharmony_ci    if (this.debugFlag) {
16461847f8eSopenharmony_ci      program.getSourceFiles().forEach((sf) => {
16561847f8eSopenharmony_ci        Logger.info('ApiCollector', sf.fileName);
16661847f8eSopenharmony_ci      });
16761847f8eSopenharmony_ci    }
16861847f8eSopenharmony_ci
16961847f8eSopenharmony_ci    let systemApiRecognizer = new SystemApiRecognizer(sdkPath);
17061847f8eSopenharmony_ci    systemApiRecognizer.setTypeChecker(program.getTypeChecker());
17161847f8eSopenharmony_ci    Logger.info(this.logTag, `start scanning ${this.project.getPath()}`);
17261847f8eSopenharmony_ci    appSourceSet.forEach((appCodeFilePath) => {
17361847f8eSopenharmony_ci      const canonicalFileName = programFactory.compilerHost.getCanonicalFileName(appCodeFilePath);
17461847f8eSopenharmony_ci      const sourceFile = program.getSourceFileByPath(canonicalFileName);
17561847f8eSopenharmony_ci      if (sourceFile) {
17661847f8eSopenharmony_ci        if (this.debugFlag) {
17761847f8eSopenharmony_ci          Logger.info(this.logTag, `scan ${sourceFile.fileName}`);
17861847f8eSopenharmony_ci        }
17961847f8eSopenharmony_ci        systemApiRecognizer.visitNode(sourceFile, sourceFile.fileName);
18061847f8eSopenharmony_ci      } else {
18161847f8eSopenharmony_ci        Logger.warn(this.logTag, `no sourceFile ${appCodeFilePath}`);
18261847f8eSopenharmony_ci      }
18361847f8eSopenharmony_ci    });
18461847f8eSopenharmony_ci    Logger.info(this.logTag, `end scan ${this.project.getPath()}`);
18561847f8eSopenharmony_ci    const apiWriter = this.getApiWriter();
18661847f8eSopenharmony_ci    apiWriter.add(systemApiRecognizer.getApiInformations());
18761847f8eSopenharmony_ci    // avoid oom
18861847f8eSopenharmony_ci    systemApiRecognizer = undefined;
18961847f8eSopenharmony_ci    program = undefined;
19061847f8eSopenharmony_ci    await apiWriter.flush();
19161847f8eSopenharmony_ci    fs.writeFileSync(handleFilePath, originalContent);
19261847f8eSopenharmony_ci  }
19361847f8eSopenharmony_ci
19461847f8eSopenharmony_ci  getApiWriter() {
19561847f8eSopenharmony_ci    if (!this.apiWriter) {
19661847f8eSopenharmony_ci      this.apiWriter = new ApiWriter(this.outputPath, this.formatFlag, this.noRepeat);
19761847f8eSopenharmony_ci    }
19861847f8eSopenharmony_ci    return this.apiWriter;
19961847f8eSopenharmony_ci  }
20061847f8eSopenharmony_ci
20161847f8eSopenharmony_ci  setApiWriter(apiWriter) {
20261847f8eSopenharmony_ci    this.apiWriter = apiWriter;
20361847f8eSopenharmony_ci  }
20461847f8eSopenharmony_ci}
20561847f8eSopenharmony_ci
20661847f8eSopenharmony_ciclass MultiProjectApiCollector {
20761847f8eSopenharmony_ci  constructor(argv) {
20861847f8eSopenharmony_ci    this.argv = argv;
20961847f8eSopenharmony_ci  }
21061847f8eSopenharmony_ci
21161847f8eSopenharmony_ci  setLibPath(libPath) {
21261847f8eSopenharmony_ci    this.libPath = libPath;
21361847f8eSopenharmony_ci    if (libPath && !fs.existsSync(this.libPath)) {
21461847f8eSopenharmony_ci      Logger.warn(this.logTag, `${libPath} is not exist`);
21561847f8eSopenharmony_ci    } else {
21661847f8eSopenharmony_ci      Logger.info(this.logTag, `set lib path ${libPath}`);
21761847f8eSopenharmony_ci    }
21861847f8eSopenharmony_ci    return this;
21961847f8eSopenharmony_ci  }
22061847f8eSopenharmony_ci
22161847f8eSopenharmony_ci  setIncludeTest(isIncludeTest) {
22261847f8eSopenharmony_ci    this.isIncludeTest = isIncludeTest;
22361847f8eSopenharmony_ci    return this;
22461847f8eSopenharmony_ci  }
22561847f8eSopenharmony_ci
22661847f8eSopenharmony_ci  async start() {
22761847f8eSopenharmony_ci    const allApps = FileSystem.listAllAppDirs(this.argv.appDir);
22861847f8eSopenharmony_ci    if (allApps.length === 0) {
22961847f8eSopenharmony_ci      Logger.info('MultiProjectApiCollector', `project not found in ${this.argv.appDir}`);
23061847f8eSopenharmony_ci      return;
23161847f8eSopenharmony_ci    }
23261847f8eSopenharmony_ci    const output = !this.argv.output ? this.argv.appDir : this.argv.output;
23361847f8eSopenharmony_ci    const apiExcelWriter = new ApiExcelWriter(output);
23461847f8eSopenharmony_ci    apiExcelWriter.close();
23561847f8eSopenharmony_ci    allApps.forEach((app) => {
23661847f8eSopenharmony_ci      if (app) {
23761847f8eSopenharmony_ci        this.argv.app = app;
23861847f8eSopenharmony_ci        const apiCollector = new ApiCollector(this.argv);
23961847f8eSopenharmony_ci        apiCollector.setApiWriter(apiExcelWriter);
24061847f8eSopenharmony_ci        apiCollector.setLibPath(this.libPath).setIncludeTest(this.isIncludeTest).start();
24161847f8eSopenharmony_ci      }
24261847f8eSopenharmony_ci    });
24361847f8eSopenharmony_ci    apiExcelWriter.open();
24461847f8eSopenharmony_ci    await apiExcelWriter.flush();
24561847f8eSopenharmony_ci  }
24661847f8eSopenharmony_ci}
24761847f8eSopenharmony_ci
24861847f8eSopenharmony_ciexports.ApiCollector = ApiCollector;
24961847f8eSopenharmony_ciexports.MultiProjectApiCollector = MultiProjectApiCollector;