1/*
2 * Copyright (c) 2024 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 ts from 'typescript';
17import path from 'path';
18import fs from 'fs';
19
20import {
21  projectConfig,
22  extendSdkConfigs,
23  globalProgram,
24  ohosSystemModulePaths,
25  systemModules,
26  allModulesPaths,
27  ohosSystemModuleSubDirPaths
28} from '../../../main';
29import {
30  LogType,
31  LogInfo,
32  IFileLog
33} from '../../utils';
34import { type ResolveModuleInfo } from '../../ets_checker';
35import {
36  PERMISSION_TAG_CHECK_NAME,
37  PERMISSION_TAG_CHECK_ERROR,
38  SYSTEM_API_TAG_CHECK_NAME,
39  SYSTEM_API_TAG_CHECK_WARNING,
40  TEST_TAG_CHECK_NAME,
41  TEST_TAG_CHECK_ERROR,
42  SYSCAP_TAG_CHECK_NAME,
43  SYSCAP_TAG_CONDITION_CHECK_WARNING,
44  SYSCAP_TAG_CHECK_WARNING,
45  CANIUSE_FUNCTION_NAME,
46  FORM_TAG_CHECK_NAME,
47  FORM_TAG_CHECK_ERROR,
48  FIND_MODULE_WARNING,
49  CROSSPLATFORM_TAG_CHECK_NAME,
50  CROSSPLATFORM_TAG_CHECK_ERROER,
51  DEPRECATED_TAG_CHECK_NAME,
52  DEPRECATED_TAG_CHECK_WARNING,
53  FA_TAG_CHECK_NAME,
54  FA_TAG_HUMP_CHECK_NAME,
55  FA_TAG_CHECK_ERROR,
56  STAGE_TAG_CHECK_NAME,
57  STAGE_TAG_HUMP_CHECK_NAME,
58  STAGE_TAG_CHECK_ERROR,
59  STAGE_COMPILE_MODE,
60  ATOMICSERVICE_BUNDLE_TYPE,
61  ATOMICSERVICE_TAG_CHECK_NAME,
62  ATOMICSERVICE_TAG_CHECK_ERROER,
63  ATOMICSERVICE_TAG_CHECK_VERSION,
64  RUNTIME_OS_OH,
65  CONSTANT_STEP_0,
66  CONSTANT_STEP_1,
67  CONSTANT_STEP_2,
68  CONSTANT_STEP_3,
69  GLOBAL_DECLARE_WHITE_LIST
70} from './api_check_define';
71import { JsDocCheckService } from './api_check_permission';
72
73/**
74 * bundle info
75 *
76 * @interface BundleInfo
77 */
78interface BundleInfo {
79  bundlePath: string;
80  bundleVersion: string;
81}
82
83export interface CheckValidCallbackInterface {
84  (jsDocTag: ts.JSDocTag, config: ts.JsDocNodeCheckConfigItem): boolean;
85}
86
87export interface CheckJsDocSpecialValidCallbackInterface {
88  (jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean;
89}
90
91/**
92 * get the bundleInfo of ohm
93 *
94 * @param {string} modulePath
95 * @return {BundleInfo}
96 */
97function parseOhmBundle(modulePath: string): BundleInfo {
98  const apiCode: string = fs.readFileSync(modulePath, { encoding: 'utf-8' });
99  const bundleTags: string[] = apiCode.match(/@bundle.+/g);
100  const bundleInfo: BundleInfo = {
101    bundlePath: '',
102    bundleVersion: ''
103  };
104  if (bundleTags && bundleTags.length > CONSTANT_STEP_0) {
105    const bundleTag: string = bundleTags[CONSTANT_STEP_0];
106    const bundleInfos: string[] = bundleTag.split(' ');
107    if (bundleInfos.length === CONSTANT_STEP_3) {
108      bundleInfo.bundlePath = bundleInfos[CONSTANT_STEP_1];
109      bundleInfo.bundleVersion = bundleInfos[CONSTANT_STEP_2];
110    }
111  }
112  return bundleInfo;
113}
114
115/**
116 * jude a version string , string has two format
117 *   xx:is a number and need greater than 10
118 *   x.x.x: a string join '.', the first part and second part is number and need greater than 4.1
119 *
120 * @param {string} bundleVersion - version string
121 * @returns {boolean}
122 */
123function checkBundleVersion(bundleVersion: string): boolean {
124  const compatibleSdkVersion: string = projectConfig.compatibleSdkVersion;
125  let bundleVersionNumber: number = 0;
126  const bundleVersionArr = bundleVersion.match(/(?<=\().*(?=\))/g);
127  if (bundleVersionArr && bundleVersionArr.length === 1) {
128    bundleVersionNumber = Number(bundleVersionArr[CONSTANT_STEP_0]);
129  } else {
130    bundleVersionNumber = Number(bundleVersion);
131  }
132  if (bundleVersion && bundleVersion !== '' && !isNaN(bundleVersionNumber) &&
133    !isNaN(Number(compatibleSdkVersion)) && Number(compatibleSdkVersion) >= bundleVersionNumber) {
134    return true;
135  }
136  return false;
137}
138
139/**
140 * get the real path about a list in module path
141 *
142 * @param {string[]} apiDirs - file list
143 * @param {string} moduleName - module dir
144 * @param {string[]} exts - ext
145 * @returns  {ResolveModuleInfo}
146 */
147export function getRealModulePath(apiDirs: string[], moduleName: string, exts: string[]): ResolveModuleInfo {
148  const resolveResult: ResolveModuleInfo = {
149    modulePath: '',
150    isEts: true
151  };
152  for (let i = 0; i < apiDirs.length; i++) {
153    const dir = apiDirs[i];
154    for (let i = 0; i < exts.length; i++) {
155      const ext = exts[i];
156      const moduleDir = path.resolve(dir, moduleName + ext);
157      if (!fs.existsSync(moduleDir)) {
158        continue;
159      }
160      resolveResult.modulePath = moduleDir;
161      if (ext === '.d.ts') {
162        resolveResult.isEts = false;
163      }
164      break;
165    }
166  }
167  return resolveResult;
168}
169
170/**
171 * get a request path about ohos
172 *
173 * @param {string} moduleRequest - import request path
174 * @param {string} _ - import request path
175 * @param {number} moduleType
176 * @param {string} systemKey
177 * @returns {string}
178 */
179export function moduleRequestCallback(moduleRequest: string, _: string,
180  moduleType: string, systemKey: string): string {
181  for (const config of extendSdkConfigs.values()) {
182    if (config.prefix === '@arkui-x') {
183      continue;
184    }
185    if (moduleRequest.startsWith(config.prefix + '.')) {
186      let compileRequest: string = `${config.prefix}:${systemKey}`;
187      const resolveModuleInfo: ResolveModuleInfo = getRealModulePath(config.apiPath, moduleRequest,
188        ['.d.ts', '.d.ets']);
189      const modulePath: string = resolveModuleInfo.modulePath;
190      if (!fs.existsSync(modulePath)) {
191        return compileRequest;
192      }
193      const bundleInfo: BundleInfo = parseOhmBundle(modulePath);
194      if (checkBundleVersion(bundleInfo.bundleVersion)) {
195        compileRequest = `@bundle:${bundleInfo.bundlePath}`;
196      }
197      return compileRequest;
198    }
199  }
200  return '';
201}
202
203/**
204 * check arkui dependences in ts files
205 * api check from sdk
206 *
207 * @param {ts.TypeReferenceNode} node - typeReferenceNode
208 * @param {IFileLog} transformLog - log info
209 */
210export function checkTypeReference(node: ts.TypeReferenceNode, transformLog: IFileLog): void {
211  const fileName: string = transformLog.sourceFile.fileName;
212  const currentTypeName: string = node.getText();
213  if (/(?<!\.d)\.ts$/g.test(fileName)) {
214    const checker: ts.TypeChecker = globalProgram.checker;
215    if (!checker) {
216      return;
217    }
218    const type: ts.Type = checker.getTypeAtLocation(node);
219    let sourceFile: ts.SourceFile | undefined;
220    if (type && type.aliasSymbol && type.aliasSymbol.declarations && type.aliasSymbol.declarations.length > 0) {
221      sourceFile = ts.getSourceFileOfNode(type.aliasSymbol.declarations[0]);
222    } else if (type && type.symbol && type.symbol.declarations && type.symbol.declarations.length > 0) {
223      sourceFile = ts.getSourceFileOfNode(type.symbol.declarations[0]);
224    }
225    if (!sourceFile) {
226      return;
227    }
228    const sourceBaseName: string = path.basename(sourceFile.fileName);
229    if (isArkuiDependence(sourceFile.fileName) &&
230      sourceBaseName !== 'common_ts_ets_api.d.ts' &&
231      sourceBaseName !== 'global.d.ts'
232    ) {
233      // TODO: change to error
234      transformLog.errors.push({
235        type: LogType.WARN,
236        message: `Cannot find name '${currentTypeName}'.`,
237        pos: node.getStart()
238      });
239    } else if (GLOBAL_DECLARE_WHITE_LIST.has(currentTypeName) &&
240      ohosSystemModulePaths.includes(sourceFile.fileName.replace(/\//g, '\\'))) {
241      transformLog.errors.push({
242        type: LogType.WARN,
243        message: `Cannot find name '${currentTypeName}'.`,
244        pos: node.getStart()
245      });
246    }
247  }
248}
249
250/**
251 * get jsDocNodeCheckConfigItem object
252 *
253 * @param {string[]} tagName - tag name
254 * @param {string} message - error message
255 * @param {ts.DiagnosticCategory} type - error type
256 * @param {boolean} tagNameShouldExisted - tag is required
257 * @param {CheckValidCallbackInterface} [checkValidCallback]
258 * @param {CheckJsDocSpecialValidCallbackInterface} [checkJsDocSpecialValidCallback]
259 * @returns  {ts.JsDocNodeCheckConfigItem}
260 */
261function getJsDocNodeCheckConfigItem(tagName: string[], message: string, needConditionCheck: boolean,
262  type: ts.DiagnosticCategory, specifyCheckConditionFuncName: string,
263  tagNameShouldExisted: boolean, checkValidCallback?: CheckValidCallbackInterface,
264  checkJsDocSpecialValidCallback?: CheckJsDocSpecialValidCallbackInterface): ts.JsDocNodeCheckConfigItem {
265  return {
266    tagName: tagName,
267    message: message,
268    needConditionCheck: needConditionCheck,
269    type: type,
270    specifyCheckConditionFuncName: specifyCheckConditionFuncName,
271    tagNameShouldExisted: tagNameShouldExisted,
272    checkValidCallback: checkValidCallback,
273    checkJsDocSpecialValidCallback: checkJsDocSpecialValidCallback
274  };
275}
276
277/**
278 * judge a file is card file
279 *
280 * @param {string} file - file path
281 * @returns {boolean}
282 */
283export function isCardFile(file: string): boolean {
284  for (const key in projectConfig.cardEntryObj) {
285    if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) {
286      return true;
287    }
288  }
289  return false;
290}
291
292const jsDocNodeCheckConfigCache: Map<string, Map<string, ts.JsDocNodeCheckConfig>> = new Map<string, Map<string, ts.JsDocNodeCheckConfig>>();
293let permissionsArray: string[] = [];
294/**
295 * get tagName where need to be determined based on the file path
296 *
297 * @param {string} fileName - file name
298 * @param {string} sourceFileName - resource reference path
299 * @returns {ts.JsDocNodeCheckConfig}
300 */
301export function getJsDocNodeCheckConfig(fileName: string, sourceFileName: string): ts.JsDocNodeCheckConfig {
302  let byFileName: Map<string, ts.JsDocNodeCheckConfig> | undefined = jsDocNodeCheckConfigCache.get(fileName);
303  if (byFileName === undefined) {
304    byFileName = new Map<string, ts.JsDocNodeCheckConfig>();
305    jsDocNodeCheckConfigCache.set(fileName, byFileName);
306  }
307  let result: ts.JsDocNodeCheckConfig | undefined = byFileName.get(sourceFileName);
308  if (result !== undefined) {
309    return result;
310  }
311  let needCheckResult: boolean = false;
312  const checkConfigArray: ts.JsDocNodeCheckConfigItem[] = [];
313  const apiName: string = path.basename(fileName);
314  const sourceBaseName: string = path.basename(sourceFileName);
315  if (/(?<!\.d)\.ts$/g.test(fileName) && isArkuiDependence(sourceFileName) &&
316    sourceBaseName !== 'common_ts_ets_api.d.ts' && sourceBaseName !== 'global.d.ts') {
317    checkConfigArray.push(getJsDocNodeCheckConfigItem([], FIND_MODULE_WARNING, false, ts.DiagnosticCategory.Warning,
318      '', true));
319  }
320  if (!systemModules.includes(apiName) && (allModulesPaths.includes(path.normalize(sourceFileName)) ||
321    isArkuiDependence(sourceFileName))) {
322    permissionsArray = projectConfig.requestPermissions;
323    checkConfigArray.push(getJsDocNodeCheckConfigItem([DEPRECATED_TAG_CHECK_NAME], DEPRECATED_TAG_CHECK_WARNING, false,
324      ts.DiagnosticCategory.Warning, '', false));
325    checkConfigArray.push(getJsDocNodeCheckConfigItem([SYSTEM_API_TAG_CHECK_NAME], SYSTEM_API_TAG_CHECK_WARNING, false,
326      ts.DiagnosticCategory.Warning, '', false));
327    // TODO: the third param is to be opened
328    checkConfigArray.push(getJsDocNodeCheckConfigItem([SYSCAP_TAG_CHECK_NAME],
329      SYSCAP_TAG_CHECK_WARNING, false, ts.DiagnosticCategory.Warning,
330      CANIUSE_FUNCTION_NAME, false, undefined, checkSyscapAbility));
331    if (projectConfig.projectRootPath) {
332      const ohosTestDir = ts.sys.resolvePath(path.join(projectConfig.projectRootPath, 'entry', 'src', 'ohosTest'));
333      // TODO:fix error type in the feature
334      if (!ts.sys.resolvePath(fileName).startsWith(ohosTestDir)) {
335        permissionsArray = projectConfig.requestPermissions;
336        checkConfigArray.push(getJsDocNodeCheckConfigItem([TEST_TAG_CHECK_NAME], TEST_TAG_CHECK_ERROR, false,
337          ts.DiagnosticCategory.Warning, '', false));
338      }
339    }
340    checkConfigArray.push(getJsDocNodeCheckConfigItem([PERMISSION_TAG_CHECK_NAME], PERMISSION_TAG_CHECK_ERROR, false,
341      ts.DiagnosticCategory.Warning, '', false, undefined, checkPermissionValue));
342    if (isCardFile(fileName)) {
343      needCheckResult = true;
344      checkConfigArray.push(getJsDocNodeCheckConfigItem([FORM_TAG_CHECK_NAME], FORM_TAG_CHECK_ERROR, false,
345        ts.DiagnosticCategory.Error, '', true));
346    }
347    if (projectConfig.isCrossplatform) {
348      needCheckResult = true;
349      checkConfigArray.push(getJsDocNodeCheckConfigItem([CROSSPLATFORM_TAG_CHECK_NAME], CROSSPLATFORM_TAG_CHECK_ERROER,
350        false, ts.DiagnosticCategory.Warning, '', true));
351    }
352    if (process.env.compileMode === STAGE_COMPILE_MODE) {
353      needCheckResult = true;
354      checkConfigArray.push(getJsDocNodeCheckConfigItem([FA_TAG_CHECK_NAME, FA_TAG_HUMP_CHECK_NAME],
355        FA_TAG_CHECK_ERROR, false, ts.DiagnosticCategory.Error, '', false));
356    } else if (process.env.compileMode !== '') {
357      needCheckResult = true;
358      checkConfigArray.push(getJsDocNodeCheckConfigItem([STAGE_TAG_CHECK_NAME, STAGE_TAG_HUMP_CHECK_NAME],
359        STAGE_TAG_CHECK_ERROR, false,
360        ts.DiagnosticCategory.Error, '', false));
361    }
362    if (projectConfig.bundleType === ATOMICSERVICE_BUNDLE_TYPE &&
363      projectConfig.compileSdkVersion >= ATOMICSERVICE_TAG_CHECK_VERSION) {
364      needCheckResult = true;
365      checkConfigArray.push(getJsDocNodeCheckConfigItem([ATOMICSERVICE_TAG_CHECK_NAME], ATOMICSERVICE_TAG_CHECK_ERROER,
366        false, ts.DiagnosticCategory.Error, '', true));
367    }
368  }
369  result = {
370    nodeNeedCheck: needCheckResult,
371    checkConfig: checkConfigArray
372  };
373  byFileName.set(sourceFileName, result);
374  return result;
375}
376
377const arkuiDependenceMap: Map<string, boolean> = new Map<string, boolean>();
378/**
379 * return a file path is Arkui path
380 *
381 * @param {string} file - file path
382 * @returns {boolean}
383 */
384function isArkuiDependence(file: string): boolean {
385  let exists: boolean | undefined = arkuiDependenceMap.get(file);
386  if (exists !== undefined) {
387    return exists;
388  }
389  const fileDir: string = path.dirname(file);
390  const declarationsPath: string = path.resolve(__dirname, '../../../declarations').replace(/\\/g, '/');
391  const componentPath: string = path.resolve(__dirname, '../../../../../component').replace(/\\/g, '/');
392  exists = fileDir === declarationsPath || fileDir === componentPath;
393  arkuiDependenceMap.set(file, exists);
394  return exists;
395}
396
397/**
398 * check a secondary directory of Arkui is used in the moduleSpecifier of import
399 *
400 * @param {ts.Expression} moduleSpecifier - the moduleSpecifier of import
401 * @param {LogInfo[]} log - log list
402 */
403export function validateModuleSpecifier(moduleSpecifier: ts.Expression, log: LogInfo[]): void {
404  const moduleSpecifierStr: string = moduleSpecifier.getText().replace(/'|"/g, '');
405  const hasSubDirPath: boolean = ohosSystemModuleSubDirPaths.some((filePath: string) => {
406    return filePath === moduleSpecifierStr;
407  });
408  if (hasSubDirPath) {
409    // TODO: change to error
410    const error: LogInfo = {
411      type: LogType.WARN,
412      message: `Cannot find module '${moduleSpecifierStr}' or its corresponding type declarations.`,
413      pos: moduleSpecifier.getStart()
414    };
415    log.push(error);
416  }
417}
418
419interface SystemConfig {
420  deviceTypesMessage: string,
421  deviceTypes: string[],
422  runtimeOS: string,
423  externalApiPaths: string[],
424  syscapIntersectionSet: Set<string>,
425  syscapUnionSet: Set<string>
426}
427
428interface SyscapConfig {
429  SysCaps: string[]
430}
431
432/**
433 * configure syscapInfo to this.share.projectConfig
434 *
435 * @param config this.share.projectConfig
436 */
437export function configureSyscapInfo(config: SystemConfig): void {
438  config.deviceTypesMessage = config.deviceTypes.join(',');
439  const deviceDir: string = path.resolve(__dirname, '../../../../../api/device-define/');
440  const deviceInfoMap: Map<string, string[]> = new Map();
441  const syscaps: Array<string[]> = [];
442  let allSyscaps: string[] = [];
443  config.deviceTypes.forEach((deviceType: string) => {
444    collectOhSyscapInfos(deviceType, deviceDir, deviceInfoMap);
445  });
446  if (config.runtimeOS !== RUNTIME_OS_OH) {
447    collectExternalSyscapInfos(config.externalApiPaths, config.deviceTypes, deviceInfoMap);
448  }
449  deviceInfoMap.forEach((value: string[]) => {
450    syscaps.push(value);
451    allSyscaps = allSyscaps.concat(value);
452  });
453  const intersectNoRepeatTwice = (arrs: Array<string[]>) => {
454    return arrs.reduce(function (prev: string[], cur: string[]) {
455      return Array.from(new Set(cur.filter((item: string) => {
456        return prev.includes(item);
457      })));
458    });
459  };
460  let syscapIntersection: string[] = [];
461  if (config.deviceTypes.length === 1 || syscaps.length === 1) {
462    syscapIntersection = syscaps[0];
463  } else if (syscaps.length > 1) {
464    syscapIntersection = intersectNoRepeatTwice(syscaps);
465  }
466  config.syscapIntersectionSet = new Set(syscapIntersection);
467  config.syscapUnionSet = new Set(allSyscaps);
468}
469
470function collectOhSyscapInfos(deviceType: string, deviceDir: string, deviceInfoMap: Map<string, string[]>) {
471  let syscapFilePath: string = '';
472  if (deviceType === 'phone') {
473    syscapFilePath = path.resolve(deviceDir, 'default.json');
474  } else {
475    syscapFilePath = path.resolve(deviceDir, deviceType + '.json');
476  }
477  if (fs.existsSync(syscapFilePath)) {
478    const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8'));
479    if (deviceInfoMap.get(deviceType)) {
480      deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps));
481    } else {
482      deviceInfoMap.set(deviceType, content.SysCaps);
483    }
484  }
485}
486
487function collectExternalSyscapInfos(
488  externalApiPaths: string[],
489  deviceTypes: string[],
490  deviceInfoMap: Map<string, string[]>
491) {
492  const externalDeviceDirs: string[] = [];
493  externalApiPaths.forEach((externalApiPath: string) => {
494    const externalDeviceDir: string = path.resolve(externalApiPath, './api/device-define');
495    if (fs.existsSync(externalDeviceDir)) {
496      externalDeviceDirs.push(externalDeviceDir);
497    }
498  });
499  externalDeviceDirs.forEach((externalDeviceDir: string) => {
500    deviceTypes.forEach((deviceType: string) => {
501      let syscapFilePath: string = '';
502      const files: string[] = fs.readdirSync(externalDeviceDir);
503      files.forEach((fileName: string) => {
504        if (fileName.startsWith(deviceType)) {
505          syscapFilePath = path.resolve(externalDeviceDir, fileName);
506          if (fs.existsSync(syscapFilePath)) {
507            const content: SyscapConfig = JSON.parse(fs.readFileSync(syscapFilePath, 'utf-8'));
508            if (deviceInfoMap.get(deviceType)) {
509              deviceInfoMap.set(deviceType, deviceInfoMap.get(deviceType).concat(content.SysCaps));
510            } else {
511              deviceInfoMap.set(deviceType, content.SysCaps);
512            }
513          }
514        }
515      });
516    });
517  });
518}
519
520/**
521 * Determine the necessity of syscap check.
522 * @param jsDocTags 
523 * @param config 
524 * @returns 
525 */
526export function checkSyscapAbility(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean {
527  let currentSyscapValue: string = '';
528  for (let i = 0; i < jsDocTags.length; i++) {
529    const jsDocTag: ts.JSDocTag = jsDocTags[i];
530    if (jsDocTag && jsDocTag.tagName.escapedText.toString() === SYSCAP_TAG_CHECK_NAME) {
531      currentSyscapValue = jsDocTag.comment as string;
532      break;
533    }
534  }
535  return projectConfig.syscapIntersectionSet && !projectConfig.syscapIntersectionSet.has(currentSyscapValue);
536}
537
538interface ConfigPermission {
539  requestPermissions: Array<{ name: string }>;
540  definePermissions: Array<{ name: string }>;
541}
542
543interface PermissionsConfig {
544  permission: ConfigPermission,
545  requestPermissions: string[],
546  definePermissions: string[],
547}
548/**
549 * configure permissionInfo to this.share.projectConfig
550 *
551 * @param config this.share.projectConfig
552 */
553export function configurePermission(config: PermissionsConfig): void {
554  const permission: ConfigPermission = config.permission;
555  config.requestPermissions = [];
556  config.definePermissions = [];
557  if (permission.requestPermissions) {
558    config.requestPermissions = getNameFromArray(permission.requestPermissions);
559  }
560  if (permission.definePermissions) {
561    config.definePermissions = getNameFromArray(permission.definePermissions);
562  }
563}
564
565function getNameFromArray(array: Array<{ name: string }>): string[] {
566  return array.map((item: { name: string }) => {
567    return String(item.name);
568  });
569}
570
571/**
572 *  Determine the necessity of permission check
573 *
574 * @param {ts.JSDocTag[]} jsDocTags
575 * @param {ts.JsDocNodeCheckConfigItem} config
576 * @returns {boolean}
577 */
578export function checkPermissionValue(jsDocTags: readonly ts.JSDocTag[], config: ts.JsDocNodeCheckConfigItem): boolean {
579  const jsDocTag: ts.JSDocTag = jsDocTags.find((item: ts.JSDocTag) => {
580    return item.tagName.getText() === PERMISSION_TAG_CHECK_NAME;
581  });
582  if (!jsDocTag) {
583    return false;
584  }
585  const comment: string = typeof jsDocTag.comment === 'string' ?
586    jsDocTag.comment :
587    ts.getTextOfJSDocComment(jsDocTag.comment);
588  config.message = config.message.replace('$DT', comment);
589  return comment !== '' && !JsDocCheckService.validPermission(comment, permissionsArray);
590}
591
592/**
593 * custom condition check
594 * @param { ts.FileCheckModuleInfo } jsDocFileCheckedInfo
595 * @param { ts.JsDocTagInfo[] } jsDocs
596 * @returns
597 */
598export function getJsDocNodeConditionCheckResult(jsDocFileCheckedInfo: ts.FileCheckModuleInfo, jsDocs: ts.JsDocTagInfo[]):
599  ts.ConditionCheckResult {
600  const result: ts.ConditionCheckResult = {
601    valid: true
602  };
603  let currentSyscapValue: string = '';
604  for (let i = 0; i < jsDocs.length; i++) {
605    const jsDocTag: ts.JsDocTagInfo = jsDocs[i];
606    if (jsDocTag.name === SYSCAP_TAG_CHECK_NAME) {
607      currentSyscapValue = jsDocTag.text as string;
608      break;
609    }
610  }
611  if (!projectConfig.syscapIntersectionSet || !projectConfig.syscapUnionSet) {
612    return result;
613  }
614  if (!projectConfig.syscapIntersectionSet.has(currentSyscapValue) && projectConfig.syscapUnionSet.has(currentSyscapValue)) {
615    result.valid = false;
616    result.type = ts.DiagnosticCategory.Warning;
617    result.message = SYSCAP_TAG_CONDITION_CHECK_WARNING;
618  } else if (!projectConfig.syscapUnionSet.has(currentSyscapValue)) {
619    result.valid = false;
620    // TODO: fix to error in the feature
621    result.type = ts.DiagnosticCategory.Warning;
622    result.message = SYSCAP_TAG_CHECK_WARNING.replace('$DT', projectConfig.deviceTypesMessage);
623  }
624  return result;
625}
626