• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 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
16const { Project, Sdk, FileSystem, Logger } = require('./utils');
17const { ApiWriter, ApiExcelWriter } = require('./api_writer');
18const { SystemApiRecognizer } = require('./api_recognizer');
19const { ReporterFormat } = require('./configs');
20const ts = require('typescript');
21const fs = require('fs');
22
23class ProgramFactory {
24  setLibPath(libPath) {
25    this.libPath = libPath;
26  }
27
28  getETSOptions(componentLibs) {
29    const tsconfig = require('../tsconfig.json');
30    const etsConfig = tsconfig.compilerOptions.ets;
31    etsConfig.libs = [...componentLibs];
32    return etsConfig;
33  }
34
35  createProgram(rootNames, apiLibs, componentLibs, esLibs) {
36    const compilerOption = {
37      target: ts.ScriptTarget.ES2017,
38      ets: this.getETSOptions([]),
39      allowJs: false,
40      lib: [...apiLibs, ...componentLibs, ...esLibs],
41      module: ts.ModuleKind.CommonJS,
42    };
43    this.compilerHost = this.createCompilerHost({
44      resolveModuleName: (moduleName) => {
45        return this.resolveModuleName(moduleName, apiLibs);
46      },
47    }, compilerOption);
48
49    if (this.libPath && fs.existsSync(this.libPath)) {
50      Logger.info('ProgramFactory', `set default lib location: ${this.libPath}`);
51      this.compilerHost.getDefaultLibLocation = () => {
52        return this.libPath;
53      };
54    }
55    return ts.createProgram({
56      rootNames: [...rootNames],
57      options: compilerOption,
58      host: this.compilerHost,
59    });
60  }
61
62  resolveModuleName(moduleName, libs) {
63    if (moduleName.startsWith('@')) {
64      const moduleFileName = `${moduleName}.d.ts`;
65      for (const lib of libs) {
66        if (lib.endsWith(moduleFileName)) {
67          return lib;
68        }
69      }
70    }
71    return undefined;
72  }
73
74  createCompilerHost(moduleResolver, compilerOption) {
75    const compilerHost = ts.createCompilerHost(compilerOption);
76    compilerHost.resolveModuleNames = this.getResolveModuleNames(moduleResolver);
77    return compilerHost;
78  }
79
80  getResolveModuleNames(moduleResolver) {
81    return (moduleNames, containingFile, reusedNames, redirectedReference, options) => {
82      const resolvedModules = [];
83      for (const moduleName of moduleNames) {
84        const moduleLookupLocaton = ts.resolveModuleName(moduleName, containingFile, options, {
85          fileExists: (fileName) => {
86            return fileName && ts.sys.fileExists(fileName);
87          },
88          readFile: (fileName) => {
89            ts.sys.readFile(fileName);
90          },
91        });
92        if (moduleLookupLocaton.resolvedModule) {
93          resolvedModules.push(moduleLookupLocaton.resolvedModule);
94        } else {
95          const modulePath = moduleResolver.resolveModuleName(moduleName);
96          const resolved = modulePath && ts.sys.fileExists(modulePath) ? { resolvedFileName: modulePath } : undefined;
97          resolvedModules.push(resolved);
98        }
99      }
100      return resolvedModules;
101    };
102  }
103}
104
105class ApiCollector {
106  constructor(argv) {
107    const appProject = argv.app ? argv.app : (argv.dir ? argv.dir : undefined);
108    if (!appProject) {
109      throw 'app not found';
110    }
111    this.project = new Project(appProject, argv.dir !== undefined);
112    this.sdk = new Sdk(this.project, argv.sdk, argv.sdkRoot);
113    this.formatFlag = ReporterFormat.getFlag(argv.format);
114    this.outputPath = !argv.output ? appProject : argv.output;
115    this.logTag = 'ApiCollector';
116    this.debugFlag = argv.debug;
117  }
118
119  setLibPath(libPath) {
120    this.libPath = libPath;
121    if (libPath && !fs.existsSync(this.libPath)) {
122      Logger.warn(this.logTag, `${libPath} is not exist`);
123    } else {
124      Logger.info(this.logTag, `set lib path ${libPath}`);
125    }
126    return this;
127  }
128
129  setIncludeTest(isIncludeTest) {
130    this.isIncludeTest = isIncludeTest;
131    return this;
132  }
133
134  async start() {
135    const sdkPath = this.sdk.getPath();
136    if (!sdkPath || !fs.existsSync(sdkPath)) {
137      return;
138    }
139    Logger.info(this.logTag, `scan app ${this.project.getPath()}`);
140    Logger.info(this.logTag, `sdk is in ${sdkPath}`);
141    const apiLibs = this.sdk.getApiLibs();
142    const componentLibs = this.sdk.getComponentLibs();
143    const eslibs = this.sdk.getESLibs(this.libPath);
144    const appSourceSet = this.project.getAppSources(this.isIncludeTest);
145    const programFactory = new ProgramFactory();
146    programFactory.setLibPath(this.libPath);
147    let program = programFactory.createProgram(appSourceSet, apiLibs, componentLibs, eslibs);
148
149    if (this.debugFlag) {
150      program.getSourceFiles().forEach((sf) => {
151        Logger.info('ApiCollector', sf.fileName);
152      });
153    }
154
155    let systemApiRecognizer = new SystemApiRecognizer(sdkPath);
156    systemApiRecognizer.setTypeChecker(program.getTypeChecker());
157    Logger.info(this.logTag, `start scanning ${this.project.getPath()}`);
158    appSourceSet.forEach((appCodeFilePath) => {
159      const canonicalFileName = programFactory.compilerHost.getCanonicalFileName(appCodeFilePath);
160      const sourceFile = program.getSourceFileByPath(canonicalFileName);
161      if (sourceFile) {
162        if (this.debugFlag) {
163          Logger.info(this.logTag, `scan ${sourceFile.fileName}`);
164        }
165        systemApiRecognizer.visitNode(sourceFile, sourceFile.fileName);
166      } else {
167        Logger.warn(this.logTag, `no sourceFile ${appCodeFilePath}`);
168      }
169    });
170    Logger.info(this.logTag, `end scan ${this.project.getPath()}`);
171    const apiWriter = this.getApiWriter();
172    apiWriter.add(systemApiRecognizer.getApiInformations());
173    // avoid oom
174    systemApiRecognizer = undefined;
175    program = undefined;
176    await apiWriter.flush();
177  }
178
179  getApiWriter() {
180    if (!this.apiWriter) {
181      this.apiWriter = new ApiWriter(this.outputPath, this.formatFlag);
182    }
183    return this.apiWriter;
184  }
185
186  setApiWriter(apiWriter) {
187    this.apiWriter = apiWriter;
188  }
189}
190
191class MultiProjectApiCollector {
192  constructor(argv) {
193    this.argv = argv;
194  }
195
196  setLibPath(libPath) {
197    this.libPath = libPath;
198    if (libPath && !fs.existsSync(this.libPath)) {
199      Logger.warn(this.logTag, `${libPath} is not exist`);
200    } else {
201      Logger.info(this.logTag, `set lib path ${libPath}`);
202    }
203    return this;
204  }
205
206  setIncludeTest(isIncludeTest) {
207    this.isIncludeTest = isIncludeTest;
208    return this;
209  }
210
211  async start() {
212    const allApps = FileSystem.listAllAppDirs(this.argv.appDir);
213    if (allApps.length === 0) {
214      Logger.info('MultiProjectApiCollector', `project not found in ${this.argv.appDir}`);
215      return;
216    }
217    const output = !this.argv.output ? this.argv.appDir : this.argv.output;
218    const apiExcelWriter = new ApiExcelWriter(output);
219    apiExcelWriter.close();
220    allApps.forEach((app) => {
221      if (app) {
222        this.argv.app = app;
223        const apiCollector = new ApiCollector(this.argv);
224        apiCollector.setApiWriter(apiExcelWriter);
225        apiCollector.setLibPath(this.libPath).setIncludeTest(this.isIncludeTest).start();
226      }
227    });
228    apiExcelWriter.open();
229    await apiExcelWriter.flush();
230  }
231}
232
233exports.ApiCollector = ApiCollector;
234exports.MultiProjectApiCollector = MultiProjectApiCollector;