• 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
16import path from 'path';
17import type {
18  CallSignatureDeclaration, ComputedPropertyName, FunctionDeclaration, Identifier, MethodDeclaration,
19  MethodSignature, ModifiersArray, ModuleDeclaration, NodeArray, ParameterDeclaration, PropertyName, SourceFile
20} from 'typescript';
21import {
22  isClassDeclaration, isComputedPropertyName, isIdentifier, isModuleBlock, isModuleDeclaration, isPrivateIdentifier
23} from 'typescript';
24import fs from 'fs';
25import ts from 'typescript';
26import type { ImportElementEntity } from '../declaration-node/importAndExportDeclaration';
27import { collectAllKitFiles } from './kitUtils';
28
29const paramIndex = 2;
30const allLegalImports = new Set<string>();
31const fileNameList = new Set<string>();
32const allClassSet = new Set<string>();
33
34export const dtsFileList: Array<string> = [];
35
36/**
37 * get all legal imports
38 * @returns
39 */
40export function getAllLegalImports(): Set<string> {
41  return allLegalImports;
42}
43
44/**
45 * get all legal imports
46 * @param element
47 */
48export function collectAllLegalImports(element: string): void {
49  allLegalImports.add(element);
50}
51
52/**
53 * collect all mock js file path
54 * @returns
55 */
56export function getAllFileNameList(): Set<string> {
57  return fileNameList;
58}
59
60/**
61 * collect all file name
62 */
63export function collectAllFileName(filePath: string): void {
64  const fullFileName = path.basename(filePath);
65  let fileName = '';
66  if (fullFileName.endsWith('d.ts')) {
67    fileName = fullFileName.split('.d.ts')[0];
68  } else if (fullFileName.endsWith('d.ets')) {
69    fileName = fullFileName.split('.d.ets')[0];
70  }
71
72  let outputFileName = '';
73  if (fileName.includes('@')) {
74    outputFileName = fileName.split('@')[1].replace(/\./g, '_');
75  } else {
76    outputFileName = fileName;
77  }
78  fileNameList.add(outputFileName);
79}
80
81/**
82 * get all class name set
83 * @returns
84 */
85export function getClassNameSet(): Set<string> {
86  return allClassSet;
87}
88
89/**
90 * get all class declaration
91 * @param sourceFile
92 * @returns
93 */
94export function getAllClassDeclaration(sourceFile: SourceFile): Set<string> {
95  sourceFile.forEachChild(node => {
96    if (isClassDeclaration(node)) {
97      if (node.name !== undefined) {
98        allClassSet.add(node.name.escapedText.toString());
99      }
100    } else if (isModuleDeclaration(node)) {
101      const moduleDeclaration = node as ModuleDeclaration;
102      const moduleBody = moduleDeclaration.body;
103      getIsModuleDeclaration(moduleBody);
104    }
105  });
106  return allClassSet;
107}
108
109/**
110 * get module class declaration
111 * @param moduleBody
112 * @returns
113 */
114function getIsModuleDeclaration(moduleBody: ts.ModuleBody): void {
115  if (moduleBody !== undefined && isModuleBlock(moduleBody)) {
116    moduleBody.statements.forEach(value => {
117      if (isClassDeclaration(value) && value.name !== undefined) {
118        allClassSet.add(firstCharacterToUppercase(value.name?.escapedText.toString()));
119      }
120      return allClassSet;
121    });
122  }
123}
124
125/**
126 * get keywords
127 * @param modifiers
128 * @returns
129 */
130export function getModifiers(modifiers: ModifiersArray): Array<number> {
131  const modifiersArray: Array<number> = [];
132  modifiers.forEach(value => modifiersArray.push(value.kind));
133  return modifiersArray;
134}
135
136/**
137 * get property name
138 * @param node property node
139 * @param sourceFile
140 * @returns
141 */
142export function getPropertyName(node: PropertyName, sourceFile: SourceFile): string {
143  let propertyName = '';
144  if (isIdentifier(node) || isPrivateIdentifier(node)) {
145    const newNameNode = node as Identifier;
146    propertyName = newNameNode.escapedText.toString();
147  } else if (isComputedPropertyName(node)) {
148    const newNameNode = node as ComputedPropertyName;
149    propertyName = sourceFile.text.substring(newNameNode.expression.pos, newNameNode.expression.end).trim();
150  } else {
151    propertyName = sourceFile.text.substring(node.pos, node.end).trim();
152  }
153  return propertyName;
154}
155
156/**
157 * get parameter declaration
158 * @param parameter
159 * @param sourceFile
160 * @returns
161 */
162export function getParameter(parameter: ParameterDeclaration, sourceFile: SourceFile): ParameterEntity {
163  let paramName = '';
164  let paramTypeString = '';
165  const paramTypeKind = parameter.type?.kind === undefined ? -1 : parameter.type.kind;
166  if (isIdentifier(parameter.name)) {
167    paramName = parameter.name.escapedText === undefined ? '' : parameter.name.escapedText.toString();
168  } else {
169    const start = parameter.name.pos === undefined ? 0 : parameter.name.pos;
170    const end = parameter.name.end === undefined ? 0 : parameter.name.end;
171    paramName = sourceFile.text.substring(start, end).trim();
172  }
173
174  const start = parameter.type?.pos === undefined ? 0 : parameter.type.pos;
175  const end = parameter.type?.end === undefined ? 0 : parameter.type.end;
176  paramTypeString = sourceFile.text.substring(start, end).trim();
177  return {
178    paramName: paramName,
179    paramTypeString: paramTypeString,
180    paramTypeKind: paramTypeKind
181  };
182}
183
184/**
185 * get method or function return info
186 * @param node
187 * @param sourceFile
188 * @returns
189 */
190export function getFunctionAndMethodReturnInfo(
191  node: FunctionDeclaration | MethodDeclaration | MethodSignature | CallSignatureDeclaration,
192  sourceFile: SourceFile
193): ReturnTypeEntity {
194  const returnInfo = { returnKindName: '', returnKind: -1 };
195  if (node.type !== undefined) {
196    const start = node.type.pos === undefined ? 0 : node.type.pos;
197    const end = node.type.end === undefined ? 0 : node.type.end;
198    returnInfo.returnKindName = sourceFile.text.substring(start, end).trim();
199    returnInfo.returnKind = node.type.kind;
200  }
201  return returnInfo;
202}
203
204/**
205 * get export modifiers
206 * @param modifiers
207 * @returns
208 */
209export function getExportKeyword(modifiers: ModifiersArray): Array<number> {
210  const modifiersArray: Array<number> = [];
211  modifiers.forEach(value => {
212    modifiersArray.push(value.kind);
213  });
214  return modifiersArray;
215}
216
217/**
218 *
219 * @param str first letter capitalization
220 * @returns
221 */
222export function firstCharacterToUppercase(str: string): string {
223  return str.slice(0, 1).toUpperCase() + str.slice(1);
224}
225
226/**
227 * parameters entity
228 */
229export interface ParameterEntity {
230  paramName: string,
231  paramTypeString: string,
232  paramTypeKind: number
233}
234
235/**
236 * return type entity
237 */
238export interface ReturnTypeEntity {
239  returnKindName: string,
240  returnKind: number
241}
242
243/**
244 * Get OpenHarmony project dir
245 * @return project dir
246 */
247
248export function getProjectDir(): string {
249  const apiInputPath = process.argv[paramIndex];
250  const privateInterface = path.join('vendor', 'huawei', 'interface', 'hmscore_sdk_js', 'api');
251  const openInterface = path.join('interface', 'sdk-js', 'api');
252  if (apiInputPath.indexOf(openInterface) > -1) {
253    return apiInputPath.replace(`${path.sep}${openInterface}`, '');
254  } else {
255    return apiInputPath.replace(`${path.sep}${privateInterface}`, '');
256  }
257}
258
259/**
260 * return interface api dir in OpenHarmony
261 */
262export function getOhosInterfacesDir(): string {
263  return path.join(getProjectDir(), 'interface', 'sdk-js', 'api');
264}
265
266/**
267 * return interface api root path
268 * @returns apiInputPath
269 */
270export function getApiInputPath(): string {
271  return process.argv[paramIndex];
272}
273
274/**
275 * return OpenHarmony file path dependent on by HarmonyOs
276 * @param importPath path of depend imported
277 * @param sourceFile sourceFile of current file
278 * @returns dependsFilePath
279 */
280export function findOhosDependFile(importPath: string, sourceFile: SourceFile): string {
281  const interFaceDir = getOhosInterfacesDir();
282  const tmpImportPath = importPath.replace(/'/g, '').replace('.d.ts', '').replace('.d.ets', '');
283  const sourceFileDir = path.dirname(sourceFile.fileName);
284  let dependsFilePath: string;
285  if (tmpImportPath.startsWith('./')) {
286    const subIndex = 2;
287    dependsFilePath = path.join(sourceFileDir, tmpImportPath.substring(subIndex));
288  } else if (tmpImportPath.startsWith('../')) {
289    const backSymbolList = tmpImportPath.split('/').filter(step => step === '..');
290    dependsFilePath = [
291      ...sourceFileDir.split(path.sep).slice(0, -backSymbolList.length),
292      ...tmpImportPath.split('/').filter(step => step !== '..')
293    ].join(path.sep);
294  } else if (tmpImportPath.startsWith('@ohos.inner.')) {
295    const pathSteps = tmpImportPath.replace(/@ohos\.inner\./g, '').split('.');
296    for (let i = 0; i < pathSteps.length; i++) {
297      const tmpInterFaceDir = path.join(interFaceDir, ...pathSteps.slice(0, i), pathSteps.slice(i).join('.'));
298      if (fs.existsSync(tmpInterFaceDir + '.d.ts')) {
299        return tmpInterFaceDir + '.d.ts';
300      }
301
302      if (fs.existsSync(tmpInterFaceDir + '.d.ets')) {
303        return tmpInterFaceDir + '.d.ets';
304      }
305    }
306  } else if (tmpImportPath.startsWith('@ohos.')) {
307    dependsFilePath = path.join(getOhosInterfacesDir(), tmpImportPath);
308  }
309
310  if (fs.existsSync(dependsFilePath + '.d.ts')) {
311    return dependsFilePath + '.d.ts';
312  }
313
314  if (fs.existsSync(dependsFilePath + '.d.ets')) {
315    return dependsFilePath + '.d.ets';
316  }
317
318  console.warn(`Cannot find module '${importPath}'`);
319  return '';
320}
321
322/**
323 * Determine if the file is a openHarmony interface file
324 * @param path: interface file path
325 * @returns
326 */
327export function isOhosInterface(path: string): boolean {
328  return path.startsWith(getOhosInterfacesDir());
329}
330
331/**
332 * reutn js-sdk root folder full path
333 * @returns
334 */
335export function getJsSdkDir(): string {
336  let sdkJsDir = process.argv[paramIndex].split(path.sep).slice(0, -1).join(path.sep);
337  sdkJsDir += sdkJsDir.endsWith(path.sep) ? '' : path.sep;
338  return sdkJsDir;
339}
340
341/**
342 * Determine whether the object has been imported
343 * @param importDeclarations imported Declaration list in current file
344 * @param typeName Object being inspected
345 * @returns
346 */
347export function hasBeenImported(importDeclarations: ImportElementEntity[], typeName: string): boolean {
348  if (!typeName.trim()) {
349    return true;
350  }
351  if (isFirstCharLowerCase(typeName)) {
352    return true;
353  }
354  return importDeclarations.some(importDeclaration => {
355    if (importDeclaration.importElements.includes(typeName) && importDeclaration.importPath.includes('./')) {
356      return true;
357    }
358    return false;
359  });
360}
361
362/**
363 * Determine whether the first character in a string is a lowercase letter
364 * @param str target string
365 * @returns
366 */
367function isFirstCharLowerCase(str: string): boolean {
368  const lowerCaseFirstChar = str[0].toLowerCase();
369  return str[0] === lowerCaseFirstChar;
370}
371
372export const specialFiles = [
373  '@internal/component/ets/common.d.ts',
374  '@internal/component/ets/units.d.ts',
375  '@internal/component/ets/common_ts_ets_api.d.ts',
376  '@internal/component/ets/enums.d.ts',
377  '@internal/component/ets/alert_dialog.d.ts',
378  '@internal/component/ets/ability_component.d.ts',
379  '@internal/component/ets/rich_editor.d.ts',
380  '@internal/component/ets/symbolglyph.d.ts',
381  '@internal/component/ets/button.d.ts',
382  '@internal/component/ets/nav_destination.d.ts',
383  '@internal/component/ets/navigation.d.ts',
384  '@internal/component/ets/text_common.d.ts',
385  '@internal/component/ets/styled_string.d.ts'
386];
387
388export const specialType = [
389  'Storage',
390  'File',
391  'ChildProcess',
392  'Cipher',
393  'Sensor',
394  'Authenticator'
395];
396
397export const specialClassName = [
398  'Want',
399  'Configuration',
400  'InputMethodExtensionContext'
401];
402
403/**
404 * get add kit file map
405 * @param apiInputPath api input path
406 * @returns
407 */
408export function generateKitMap(apiInputPath: string): void {
409  const kitPath = path.join(apiInputPath, '../', 'kits');
410  if (!fs.existsSync(kitPath)) {
411    throw new Error(`${kitPath} does not exist.`);
412  }
413  collectAllKitFiles(kitPath);
414}
415
416export interface DependencyListParams {
417  dependency: Array<string>,
418  export: string
419}
420
421export interface DependencyParams {
422  [key: string]: DependencyListParams
423}
424
425// dependence on collecting files
426export const DEPENDENCY_LIST: DependencyParams = {};
427
428/**
429 * generated depend.json
430 */
431export function generateDependJsonFile(): void {
432  const dependInfoPath = path.join(__dirname, '../../../runtime/main/extend/systemplugin/depend.json');
433  fs.writeFileSync(dependInfoPath, JSON.stringify(DEPENDENCY_LIST, null, 2), 'utf-8');
434}
435
436/**
437 * generated MyComponent.js
438 *
439 * @param outDir generated file root directory
440 */
441export function generateMyComponent(outDir: string): void {
442  fs.writeFileSync(
443    path.join(outDir, 'MyComponent.js'),
444    'class MyComponent {}\nexport { MyComponent };'
445  );
446}
447
448// initialize all variables in the file
449export let INITVARIABLE = '';
450
451/**
452 * set initialize variable
453 *
454 * @param value variable name
455 */
456export function setInitVariable(value?: string): void {
457  if (value) {
458    if (!INITVARIABLE.includes(`let ${value} = {};`)) {
459      INITVARIABLE += `let ${value} = {};\n`;
460    }
461  } else {
462    INITVARIABLE = '';
463  }
464}
465
466/**
467 * get all initialize variable
468 * @returns string
469 */
470export function getInitVariable(): string {
471  return INITVARIABLE;
472}