• 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 fs from 'fs';
17import path from 'path';
18import * as ts from 'typescript';
19
20import {
21  projectConfig,
22  systemModules,
23  globalProgram
24} from '../main';
25import {
26  processSystemApi,
27  preprocessExtend,
28  preprocessNewExtend
29} from './validate_ui_syntax';
30import {
31  INNER_COMPONENT_MEMBER_DECORATORS,
32  COMPONENT_DECORATORS_PARAMS,
33  COMPONENT_BUILD_FUNCTION,
34  STYLE_ADD_DOUBLE_DOLLAR,
35  $$,
36  PROPERTIES_ADD_DOUBLE_DOLLAR,
37  $$_BLOCK_INTERFACE,
38  COMPONENT_EXTEND_DECORATOR,
39  COMPONENT_BUILDER_DECORATOR,
40  FOREACH_LAZYFOREACH,
41  EXTNAME_D_ETS,
42  EXTNAME_JS
43} from './pre_define';
44import { getName } from './process_component_build';
45import { INNER_COMPONENT_NAMES } from './component_map';
46import {
47  props,
48  logger
49} from './compile_info';
50import { hasDecorator } from './utils';
51import { generateSourceFilesInHar } from './utils';
52import { isExtendFunction, isOriginalExtend } from './process_ui_syntax';
53
54export function readDeaclareFiles(): string[] {
55  const declarationsFileNames: string[] = [];
56  fs.readdirSync(path.resolve(__dirname, '../declarations'))
57    .forEach((fileName: string) => {
58      if (/\.d\.ts$/.test(fileName)) {
59        declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName));
60      }
61    });
62  return declarationsFileNames;
63}
64
65const compilerOptions: ts.CompilerOptions = ts.readConfigFile(
66  path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
67function setCompilerOptions() {
68  const allPath: Array<string> = [
69    '*'
70  ];
71  if (!projectConfig.aceModuleJsonPath) {
72    allPath.push('../../../../../*');
73    allPath.push('../../*');
74  } else {
75    allPath.push('../../../../*');
76    allPath.push('../*');
77  }
78  Object.assign(compilerOptions, {
79    'allowJs': false,
80    'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
81    'module': ts.ModuleKind.CommonJS,
82    'moduleResolution': ts.ModuleResolutionKind.NodeJs,
83    'noEmit': true,
84    'target': ts.ScriptTarget.ES2017,
85    'baseUrl': path.resolve(projectConfig.projectPath),
86    'paths': {
87      '*': allPath
88    },
89    'lib': [
90      'lib.es2020.d.ts'
91    ]
92  });
93  if (projectConfig.packageDir === 'oh_modules') {
94    Object.assign(compilerOptions, {
95      'packageManagerType': 'ohpm'
96    });
97  }
98}
99
100interface extendInfo {
101  start: number,
102  end: number,
103  compName: string
104}
105
106export function createLanguageService(rootFileNames: string[]): ts.LanguageService {
107  setCompilerOptions();
108  const files: ts.MapLike<{ version: number }> = {};
109  const servicesHost: ts.LanguageServiceHost = {
110    getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()],
111    getScriptVersion: fileName =>
112      files[fileName] && files[fileName].version.toString(),
113    getScriptSnapshot: fileName => {
114      if (!fs.existsSync(fileName)) {
115        return undefined;
116      }
117      if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
118        let content: string = processContent(fs.readFileSync(fileName).toString());
119        const extendFunctionInfo: extendInfo[] = [];
120        content = instanceInsteadThis(content, fileName, extendFunctionInfo);
121        return ts.ScriptSnapshot.fromString(content);
122      }
123      return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString());
124    },
125    getCurrentDirectory: () => process.cwd(),
126    getCompilationSettings: () => compilerOptions,
127    getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
128    fileExists: ts.sys.fileExists,
129    readFile: ts.sys.readFile,
130    readDirectory: ts.sys.readDirectory,
131    resolveModuleNames: resolveModuleNames,
132    directoryExists: ts.sys.directoryExists,
133    getDirectories: ts.sys.getDirectories,
134    getTagNameNeededCheckByFile: (fileName, sourceFileName) => {
135      let needCheckResult: boolean = false;
136      if ((/compiler\/declarations/.test(sourceFileName) || /ets-loader\/declarations/.test(sourceFileName)) &&
137        isCardFile(fileName)) {
138        needCheckResult = true;
139      }
140      return {
141        needCheck: needCheckResult,
142        checkConfig: [{
143          tagName: "form",
144          message: "'{0}' can't support form application.",
145          needConditionCheck: false,
146          type: ts.DiagnosticCategory.Error,
147          specifyCheckConditionFuncName: '',
148          tagNameShouldExisted: true
149        }]
150      }
151    }
152  };
153  return ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
154}
155
156interface CacheFileName {
157  mtimeMs: number,
158  children: string[],
159  parent: string[],
160  error: boolean
161}
162interface NeedUpdateFlag {
163  flag: boolean;
164}
165interface CheckerResult {
166  count: number
167}
168interface WholeCache {
169  runtimeOS: string,
170  sdkInfo: string,
171  fileList: Cache
172}
173type Cache = Record<string, CacheFileName>;
174export let cache: Cache = {};
175export const hotReloadSupportFiles: Set<string> = new Set();
176export const shouldResolvedFiles: Set<string> = new Set();
177const allResolvedModules: Set<string> = new Set();
178
179let fastBuildLogger = null;
180
181export const checkerResult: CheckerResult = {count: 0};
182export function serviceChecker(rootFileNames: string[], newLogger: any = null): void {
183  fastBuildLogger = newLogger;
184  let languageService: ts.LanguageService = null;
185  let cacheFile: string = null;
186  if (projectConfig.xtsMode) {
187    languageService = createLanguageService(rootFileNames);
188  } else {
189    cacheFile = path.resolve(projectConfig.cachePath, '../.ts_checker_cache');
190    const wholeCache: WholeCache = fs.existsSync(cacheFile) ?
191      JSON.parse(fs.readFileSync(cacheFile).toString()) :
192      {'runtimeOS': projectConfig.runtimeOS, 'sdkInfo': projectConfig.sdkInfo, 'fileList': {}};
193    if (wholeCache.runtimeOS === projectConfig.runtimeOS && wholeCache.sdkInfo === projectConfig.sdkInfo) {
194      cache = wholeCache.fileList;
195    } else {
196      cache = {};
197    }
198    const filterFiles: string[] = filterInput(rootFileNames);
199    languageService = createLanguageService(filterFiles);
200  }
201  globalProgram.program = languageService.getProgram();
202  const allDiagnostics: ts.Diagnostic[] = globalProgram.program
203    .getSyntacticDiagnostics()
204    .concat(globalProgram.program.getSemanticDiagnostics())
205    .concat(globalProgram.program.getDeclarationDiagnostics());
206  allDiagnostics.forEach((diagnostic: ts.Diagnostic) => {
207    printDiagnostic(diagnostic);
208  });
209  if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
210    fs.writeFileSync(cacheFile, JSON.stringify({
211      'runtimeOS': projectConfig.runtimeOS,
212      'sdkInfo': projectConfig.sdkInfo,
213      'fileList': cache
214    }, null, 2));
215  }
216  if (projectConfig.compileHar || projectConfig.compileShared) {
217    [...allResolvedModules, ...rootFileNames].forEach(moduleFile => {
218      if (!(moduleFile.match(new RegExp(projectConfig.packageDir)) && projectConfig.compileHar)) {
219        try {
220          const emit: any = languageService.getEmitOutput(moduleFile, true, true);
221          if (emit.outputFiles[0]) {
222            generateSourceFilesInHar(moduleFile, emit.outputFiles[0].text, '.d' + path.extname(moduleFile),
223              projectConfig);
224          } else {
225            console.warn(this.yellow,
226              "ArkTS:WARN doesn't generate .d" + path.extname(moduleFile) + ' for ' + moduleFile, this.reset);
227          }
228        } catch (err) {}
229      }
230    });
231  }
232}
233
234function isCardFile(file: string): boolean {
235  for (const key in projectConfig.cardEntryObj) {
236    if (path.normalize(projectConfig.cardEntryObj[key]) === path.normalize(file)) {
237      return true;
238    }
239  }
240  return false;
241}
242
243function containFormError(message: string): boolean {
244  if (/can't support form application./.test(message)) {
245    return true;
246  }
247  return false;
248}
249
250export function printDiagnostic(diagnostic: ts.Diagnostic): void {
251  const message: string = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
252  if (validateError(message)) {
253    if (process.env.watchMode !== 'true' && !projectConfig.xtsMode) {
254      updateErrorFileCache(diagnostic);
255    }
256
257    if (containFormError(message) && !isCardFile(diagnostic.file.fileName)) {
258        return;
259    }
260
261    checkerResult.count += 1;
262    if (diagnostic.file) {
263      const { line, character }: ts.LineAndCharacter =
264        diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!);
265      fastBuildLogger ?
266        fastBuildLogger.error('\u001b[31m' +
267          `ArkTS:ERROR File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`) :
268        logger.error('\u001b[31m',
269          `ArkTS:ERROR File: ${diagnostic.file.fileName}:${line + 1}:${character + 1}\n ${message}\n`);
270    } else {
271      fastBuildLogger ? fastBuildLogger.error('\u001b[31m' + `ArkTS:ERROR: ${message}`) :
272        logger.error('\u001b[31m', `ArkTS:ERROR: ${message}`);
273    }
274  }
275}
276
277function validateError(message: string): boolean {
278  const propInfoReg: RegExp = /Cannot find name\s*'(\$?\$?[_a-zA-Z0-9]+)'/;
279  const stateInfoReg: RegExp = /Property\s*'(\$?[_a-zA-Z0-9]+)' does not exist on type/;
280  if (matchMessage(message, props, propInfoReg) ||
281    matchMessage(message, props, stateInfoReg)) {
282    return false;
283  }
284  return true;
285}
286function matchMessage(message: string, nameArr: any, reg: RegExp): boolean {
287  if (reg.test(message)) {
288    const match: string[] = message.match(reg);
289    if (match[1] && nameArr.includes(match[1])) {
290      return true;
291    }
292  }
293  return false;
294}
295
296function updateErrorFileCache(diagnostic: ts.Diagnostic): void {
297  if (diagnostic.file && cache[path.resolve(diagnostic.file.fileName)]) {
298    cache[path.resolve(diagnostic.file.fileName)].error = true;
299  }
300}
301
302function filterInput(rootFileNames: string[]): string[] {
303  return rootFileNames.filter((file: string) => {
304    const needUpdate: NeedUpdateFlag = { flag: false };
305    const alreadyCheckedFiles: Set<string> = new Set();
306    checkNeedUpdateFiles(path.resolve(file), needUpdate, alreadyCheckedFiles);
307    return needUpdate.flag;
308  });
309}
310
311function checkNeedUpdateFiles(file: string, needUpdate: NeedUpdateFlag, alreadyCheckedFiles: Set<string>): void {
312  if (alreadyCheckedFiles.has(file)) {
313    return;
314  } else {
315    alreadyCheckedFiles.add(file);
316  }
317
318  if (needUpdate.flag) {
319    return;
320  }
321
322  const value: CacheFileName = cache[file];
323  const mtimeMs: number = fs.statSync(file).mtimeMs;
324  if (value) {
325    if (value.error || value.mtimeMs !== mtimeMs) {
326      needUpdate.flag = true;
327      return;
328    }
329    for (let i = 0; i < value.children.length; ++i) {
330      if (fs.existsSync(value.children[i])) {
331        checkNeedUpdateFiles(value.children[i], needUpdate, alreadyCheckedFiles);
332      } else {
333        needUpdate.flag = true;
334      }
335    }
336  } else {
337    cache[file] = { mtimeMs, children: [], parent: [], error: false };
338    needUpdate.flag = true;
339  }
340}
341
342const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map();
343
344function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] {
345  const resolvedModules: ts.ResolvedModuleFull[] = [];
346  if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile))
347    || !(resolvedModulesCache[path.resolve(containingFile)] &&
348      resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) {
349    for (const moduleName of moduleNames) {
350      const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, {
351        fileExists(fileName: string): boolean {
352          return ts.sys.fileExists(fileName);
353        },
354        readFile(fileName: string): string | undefined {
355          return ts.sys.readFile(fileName);
356        },
357        realpath(path: string): string {
358          return ts.sys.realpath(path);
359        }
360      });
361      if (result.resolvedModule) {
362        if (result.resolvedModule.resolvedFileName &&
363          path.extname(result.resolvedModule.resolvedFileName) === EXTNAME_JS) {
364          const resultDETSPath: string =
365            result.resolvedModule.resolvedFileName.replace(EXTNAME_JS, EXTNAME_D_ETS);
366          if (ts.sys.fileExists(resultDETSPath)) {
367            resolvedModules.push(getResolveModule(resultDETSPath, EXTNAME_D_ETS));
368          } else {
369            resolvedModules.push(result.resolvedModule);
370          }
371        } else {
372          resolvedModules.push(result.resolvedModule);
373        }
374      } else if (/^@(system|ohos)\./i.test(moduleName.trim())) {
375        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
376        if (systemModules.includes(moduleName + '.d.ts') && ts.sys.fileExists(modulePath)) {
377          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
378        } else {
379          resolvedModules.push(null);
380        }
381      } else if (/\.ets$/.test(moduleName) && !/\.d\.ets$/.test(moduleName)) {
382        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
383        if (ts.sys.fileExists(modulePath)) {
384          resolvedModules.push(getResolveModule(modulePath, '.ets'));
385        } else {
386          resolvedModules.push(null);
387        }
388      } else if (/\.ts$/.test(moduleName)) {
389        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
390        if (ts.sys.fileExists(modulePath)) {
391          resolvedModules.push(getResolveModule(modulePath, '.ts'));
392        } else {
393          resolvedModules.push(null);
394        }
395      } else {
396        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
397        const suffix: string = /\.js$/.test(moduleName) ? '' : '.js';
398        const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix);
399        const fileModulePath: string =
400          path.resolve(__dirname, '../node_modules', moduleName + '/index.js');
401        const DETSModulePath: string = path.resolve(path.dirname(containingFile),
402          /\.d\.ets$/.test(moduleName) ? moduleName : moduleName + EXTNAME_D_ETS);
403        if (ts.sys.fileExists(modulePath)) {
404          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
405        } else if (ts.sys.fileExists(jsModulePath)) {
406          resolvedModules.push(getResolveModule(jsModulePath, '.js'));
407        } else if (ts.sys.fileExists(fileModulePath)) {
408          resolvedModules.push(getResolveModule(fileModulePath, '.js'));
409        } else if (ts.sys.fileExists(DETSModulePath)) {
410          resolvedModules.push(getResolveModule(DETSModulePath, '.d.ets'));
411        } else {
412          const srcIndex: number = projectConfig.projectPath.indexOf('src' + path.sep + 'main');
413          let DETSModulePathFromModule: string;
414          if (srcIndex > 0) {
415            DETSModulePathFromModule = path.resolve(
416              projectConfig.projectPath.substring(0, srcIndex), moduleName + path.sep + 'index' + EXTNAME_D_ETS);
417            if (DETSModulePathFromModule && ts.sys.fileExists(DETSModulePathFromModule)) {
418              resolvedModules.push(getResolveModule(DETSModulePathFromModule, '.d.ets'));
419            } else {
420              resolvedModules.push(null);
421            }
422          } else {
423            resolvedModules.push(null);
424          }
425        }
426      }
427      if (projectConfig.hotReload && resolvedModules.length &&
428        resolvedModules[resolvedModules.length - 1]) {
429        hotReloadSupportFiles.add(path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName));
430      }
431      if ((projectConfig.compileHar || projectConfig.compileShared) && resolvedModules[resolvedModules.length - 1] &&
432        path.resolve(resolvedModules[resolvedModules.length - 1].resolvedFileName).match(/(\.[^d]|[^\.]d|[^\.][^d])\.e?ts$/)) {
433        allResolvedModules.add(resolvedModules[resolvedModules.length - 1].resolvedFileName);
434      }
435    }
436    if (!projectConfig.xtsMode) {
437      createOrUpdateCache(resolvedModules, path.resolve(containingFile));
438    }
439    resolvedModulesCache[path.resolve(containingFile)] = resolvedModules;
440    return resolvedModules;
441  }
442  return resolvedModulesCache[path.resolve(containingFile)];
443}
444
445function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void {
446  const children: string[] = [];
447  const error: boolean = false;
448  resolvedModules.forEach(moduleObj => {
449    if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) {
450      const file: string = path.resolve(moduleObj.resolvedFileName);
451      const mtimeMs: number = fs.statSync(file).mtimeMs;
452      children.push(file);
453      const value: CacheFileName = cache[file];
454      if (value) {
455        value.mtimeMs = mtimeMs;
456        value.error = error;
457        value.parent = value.parent || [];
458        value.parent.push(path.resolve(containingFile));
459        value.parent = [...new Set(value.parent)];
460      } else {
461        cache[file] = { mtimeMs, children: [], parent: [containingFile], error };
462      }
463    }
464  });
465  cache[path.resolve(containingFile)] = { mtimeMs: fs.statSync(containingFile).mtimeMs, children,
466    parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ?
467      cache[path.resolve(containingFile)].parent : [], error };
468}
469
470export function createWatchCompilerHost(rootFileNames: string[],
471  reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function,
472  isPipe: boolean = false): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> {
473  if (projectConfig.hotReload) {
474    rootFileNames.forEach(fileName => {
475      hotReloadSupportFiles.add(fileName);
476    });
477  }
478  setCompilerOptions();
479  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
480  const host = ts.createWatchCompilerHost(
481    [...rootFileNames, ...readDeaclareFiles()], compilerOptions,
482    ts.sys, createProgram, reportDiagnostic,
483    (diagnostic: ts.Diagnostic) => {
484      if ([6031, 6032].includes(diagnostic.code)) {
485        if (!isPipe) {
486          process.env.watchTs = 'start';
487          resetErrorCount();
488        }
489      }
490      // End of compilation in watch mode flag.
491      if ([6193, 6194].includes(diagnostic.code)) {
492        if (!isPipe) {
493          process.env.watchTs = 'end';
494          if (fastBuildLogger) {
495            fastBuildLogger.debug('TS Watch End');
496          }
497        }
498        delayPrintLogCount();
499      }
500    });
501  host.readFile = (fileName: string) => {
502    if (!fs.existsSync(fileName)) {
503      return undefined;
504    }
505    if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
506      let content: string = processContent(fs.readFileSync(fileName).toString());
507      const extendFunctionInfo: extendInfo[] = [];
508      content = instanceInsteadThis(content, fileName, extendFunctionInfo);
509      return content;
510    }
511    return fs.readFileSync(fileName).toString();
512  };
513  host.resolveModuleNames = resolveModuleNames;
514  return host;
515}
516
517export function watchChecker(rootFileNames: string[], newLogger: any = null): void {
518  fastBuildLogger = newLogger;
519  globalProgram.watchProgram = ts.createWatchProgram(
520    createWatchCompilerHost(rootFileNames, printDiagnostic, () => {}, () => {}));
521}
522
523function instanceInsteadThis(content: string, fileName: string, extendFunctionInfo: extendInfo[]): string {
524  checkUISyntax(content, fileName, extendFunctionInfo);
525  extendFunctionInfo.reverse().forEach((item) => {
526    const subStr: string = content.substring(item.start, item.end);
527    const insert: string = subStr.replace(/(\s)\$(\.)/g, (origin, item1, item2) => {
528      return item1 + item.compName + 'Instance' + item2;
529    });
530    content = content.slice(0, item.start) + insert + content.slice(item.end);
531  });
532  return content;
533}
534
535function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull {
536  return {
537    resolvedFileName: modulePath,
538    isExternalLibraryImport: false,
539    extension: type
540  };
541}
542
543export const dollarCollection: Set<string> = new Set();
544export const decoratorParamsCollection: Set<string> = new Set();
545export const extendCollection: Set<string> = new Set();
546export const importModuleCollection: Set<string> = new Set();
547
548function checkUISyntax(source: string, fileName: string, extendFunctionInfo: extendInfo[]): void {
549  if (/\.ets$/.test(fileName)) {
550    if (process.env.compileMode === 'moduleJson' ||
551      path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) {
552      const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source,
553        ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
554      parseAllNode(sourceFile, sourceFile, extendFunctionInfo);
555      props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection);
556    }
557  }
558}
559
560function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, extendFunctionInfo: extendInfo[]): void {
561  if (ts.isStructDeclaration(node)) {
562    if (node.members) {
563      node.members.forEach(item => {
564        if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
565          const propertyName: string = item.name.getText();
566          if (item.decorators && item.decorators.length) {
567            for (let i = 0; i < item.decorators.length; i++) {
568              const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim();
569              if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
570                dollarCollection.add('$' + propertyName);
571              }
572              if (isDecoratorCollection(item.decorators[i], decoratorName)) {
573                decoratorParamsCollection.add(item.decorators[i].expression.arguments[0].getText());
574              }
575            }
576          }
577        }
578      });
579    }
580  }
581  if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION ||
582    (ts.isMethodDeclaration(node) || ts.isFunctionDeclaration(node)) &&
583    hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) {
584    if (node.body && node.body.statements && node.body.statements.length) {
585      const checkProp: ts.NodeArray<ts.Statement> = node.body.statements;
586      checkProp.forEach((item, index) => {
587        traverseBuild(item, index);
588      });
589    }
590  }
591  if (ts.isFunctionDeclaration(node) && hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
592    if (node.body && node.body.statements && node.body.statements.length &&
593      !isOriginalExtend(node.body)) {
594      extendFunctionInfo.push({start: node.pos, end: node.end, compName: isExtendFunction(node)});
595    }
596  }
597  node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode, extendFunctionInfo));
598}
599
600function isForeachAndLzayForEach(node: ts.Node): boolean {
601  return ts.isCallExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
602    FOREACH_LAZYFOREACH.has(node.expression.escapedText.toString()) && node.arguments && node.arguments[1] &&
603    ts.isArrowFunction(node.arguments[1]) && node.arguments[1].body && ts.isBlock(node.arguments[1].body);
604}
605
606function traverseBuild(node: ts.Node, index: number): void {
607  if (ts.isExpressionStatement(node)) {
608    let parentComponentName: string = getName(node);
609    if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 &&
610      node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) {
611      parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText;
612    }
613    node = node.expression;
614    if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
615      ts.isIdentifier(node.expression) && !$$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
616      node.body.statements.forEach((item: ts.Statement, indexBlock: number) => {
617        traverseBuild(item, indexBlock);
618      });
619    } else if (isForeachAndLzayForEach(node)) {
620      node.arguments[1].body.statements.forEach((item: ts.Statement, indexBlock: number) => {
621        traverseBuild(item, indexBlock);
622      });
623    } else {
624      loopNodeFindDoubleDollar(node, parentComponentName);
625    }
626  } else if (ts.isIfStatement(node)) {
627    if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) {
628      node.thenStatement.statements.forEach((item, indexIfBlock) => {
629        traverseBuild(item, indexIfBlock);
630      });
631    }
632    if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) {
633      node.elseStatement.statements.forEach((item, indexElseBlock) => {
634        traverseBuild(item, indexElseBlock);
635      });
636    }
637  }
638}
639
640function isPropertiesAddDoubleDollar(node: ts.Node): boolean {
641  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments && node.arguments.length) {
642    return true;
643  } else if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body) &&
644    ts.isIdentifier(node.expression) && $$_BLOCK_INTERFACE.has(node.expression.escapedText.toString())) {
645    return true;
646  } else {
647    return false;
648  }
649}
650function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void {
651  while (node) {
652    if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
653      const argument: ts.NodeArray<ts.Node> = node.arguments;
654      const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name;
655      if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) {
656        argument.forEach((item: ts.Node) => {
657          doubleDollarCollection(item);
658        });
659      }
660    } else if (isPropertiesAddDoubleDollar(node)) {
661      node.arguments.forEach((item: ts.Node) => {
662        if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) {
663          item.properties.forEach((param: ts.Node) => {
664            if (isObjectPram(param, parentComponentName)) {
665              doubleDollarCollection(param.initializer);
666            }
667          });
668        }
669        if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) {
670          doubleDollarCollection(item);
671        }
672      });
673    }
674    node = node.expression;
675  }
676}
677
678function doubleDollarCollection(item: ts.Node): void {
679  if (item.getText().startsWith($$)) {
680    while (item.expression) {
681      item = item.expression;
682    }
683    dollarCollection.add(item.getText());
684  }
685}
686
687function isObjectPram(param: ts.Node, parentComponentName:string): boolean {
688  return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) &&
689    param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
690    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText());
691}
692
693function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean {
694  return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
695    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) ||
696    STYLE_ADD_DOUBLE_DOLLAR.has(propertyName);
697}
698
699function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean {
700  return COMPONENT_DECORATORS_PARAMS.has(decoratorName) &&
701    // @ts-ignore
702    item.expression.arguments && item.expression.arguments.length &&
703    // @ts-ignore
704    ts.isIdentifier(item.expression.arguments[0]);
705}
706
707function processContent(source: string): string {
708  source = processSystemApi(source, false);
709  source = preprocessExtend(source, extendCollection);
710  source = preprocessNewExtend(source, extendCollection);
711  return source;
712}
713
714function judgeFileShouldResolved(file: string, shouldResolvedFiles: Set<string>): void {
715  if (shouldResolvedFiles.has(file)) {
716    return;
717  }
718  shouldResolvedFiles.add(file);
719  if (cache && cache[file] && cache[file].parent) {
720    cache[file].parent.forEach((item) => {
721      judgeFileShouldResolved(item, shouldResolvedFiles);
722    });
723    cache[file].parent = [];
724  }
725  if (cache && cache[file] && cache[file].children) {
726    cache[file].children.forEach((item) => {
727      judgeFileShouldResolved(item, shouldResolvedFiles);
728    });
729    cache[file].children = [];
730  }
731}
732
733export function incrementWatchFile(watchModifiedFiles: string[],
734  watchRemovedFiles: string[]): void {
735  const changedFiles: string[] = [...watchModifiedFiles, ...watchRemovedFiles];
736  if (changedFiles.length) {
737    shouldResolvedFiles.clear();
738  }
739  changedFiles.forEach((file) => {
740    judgeFileShouldResolved(file, shouldResolvedFiles);
741  });
742}
743