• 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} from '../main';
24import {
25  processSystemApi,
26  preprocessExtend,
27  preprocessNewExtend
28} from './validate_ui_syntax';
29import {
30  INNER_COMPONENT_MEMBER_DECORATORS,
31  COMPONENT_IF,
32  COMPONENT_DECORATORS_PARAMS,
33  COMPONENT_BUILD_FUNCTION,
34  STYLE_ADD_DOUBLE_DOLLAR,
35  $$,
36  PROPERTIES_ADD_DOUBLE_DOLLAR
37} from './pre_define';
38import { JS_BIND_COMPONENTS } from './component_map';
39import { getName } from './process_component_build';
40import { INNER_COMPONENT_NAMES } from './component_map';
41import {
42  props,
43  CacheFileName,
44  cache,
45  shouldResolvedFiles
46} from './compile_info';
47
48function readDeaclareFiles(): string[] {
49  const declarationsFileNames: string[] = [];
50  fs.readdirSync(path.resolve(__dirname, '../declarations'))
51    .forEach((fileName: string) => {
52      if (/\.d\.ts$/.test(fileName)) {
53        declarationsFileNames.push(path.resolve(__dirname, '../declarations', fileName));
54      }
55    });
56  return declarationsFileNames;
57}
58
59const compilerOptions: ts.CompilerOptions = ts.readConfigFile(
60  path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions;
61function setCompilerOptions() {
62  const allPath: Array<string> = [
63    '*',
64  ]
65  if (!projectConfig.aceModuleJsonPath) {
66    allPath.push('../../../../../*');
67    allPath.push('../../*');
68  } else {
69    allPath.push('../../../../*');
70    allPath.push('../*');
71  }
72  Object.assign(compilerOptions, {
73    'allowJs': false,
74    'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
75    'module': ts.ModuleKind.CommonJS,
76    'moduleResolution': ts.ModuleResolutionKind.NodeJs,
77    'noEmit': true,
78    'target': ts.ScriptTarget.ES2017,
79    'baseUrl': path.resolve(projectConfig.projectPath),
80    'paths': {
81      '*': allPath
82    },
83    'lib': [
84      'lib.es2020.d.ts'
85    ]
86  });
87}
88
89export function createLanguageService(rootFileNames: string[]): ts.LanguageService {
90  setCompilerOptions();
91  const files: ts.MapLike<{ version: number }> = {};
92  const servicesHost: ts.LanguageServiceHost = {
93    getScriptFileNames: () => [...rootFileNames, ...readDeaclareFiles()],
94    getScriptVersion: fileName =>
95      files[fileName] && files[fileName].version.toString(),
96    getScriptSnapshot: fileName => {
97      if (!fs.existsSync(fileName)) {
98        return undefined;
99      }
100      if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
101        const content: string = processContent(fs.readFileSync(fileName).toString());
102        checkUISyntax(content, fileName);
103        return ts.ScriptSnapshot.fromString(content);
104      }
105      return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString());
106    },
107    getCurrentDirectory: () => process.cwd(),
108    getCompilationSettings: () => compilerOptions,
109    getDefaultLibFileName: options => ts.getDefaultLibFilePath(options),
110    fileExists: ts.sys.fileExists,
111    readFile: ts.sys.readFile,
112    readDirectory: ts.sys.readDirectory,
113    resolveModuleNames: resolveModuleNames,
114    directoryExists: ts.sys.directoryExists,
115    getDirectories: ts.sys.getDirectories
116  };
117  return ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
118}
119
120const resolvedModulesCache: Map<string, ts.ResolvedModuleFull[]> = new Map();
121
122function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] {
123  const resolvedModules: ts.ResolvedModuleFull[] = [];
124  if (![...shouldResolvedFiles].length || shouldResolvedFiles.has(path.resolve(containingFile)) ||
125    !(resolvedModulesCache[path.resolve(containingFile)] &&
126      resolvedModulesCache[path.resolve(containingFile)].length === moduleNames.length)) {
127    for (const moduleName of moduleNames) {
128      const result = ts.resolveModuleName(moduleName, containingFile, compilerOptions, {
129        fileExists(fileName: string): boolean {
130          return ts.sys.fileExists(fileName);
131        },
132        readFile(fileName: string): string | undefined {
133          return ts.sys.readFile(fileName);
134        }
135      });
136      if (result.resolvedModule) {
137        resolvedModules.push(result.resolvedModule);
138      } else if (/^@(system|ohos)/i.test(moduleName.trim())) {
139        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
140        if (systemModules.includes(moduleName + '.d.ts') && ts.sys.fileExists(modulePath)) {
141          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
142        } else {
143          resolvedModules.push(null);
144        }
145      } else if (/\.ets$/.test(moduleName)) {
146        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
147        if (ts.sys.fileExists(modulePath)) {
148          resolvedModules.push(getResolveModule(modulePath, '.ets'));
149        } else {
150          resolvedModules.push(null);
151        }
152      } else if (/\.ts$/.test(moduleName)) {
153        const modulePath: string = path.resolve(path.dirname(containingFile), moduleName);
154        if (ts.sys.fileExists(modulePath)) {
155          resolvedModules.push(getResolveModule(modulePath, '.ts'));
156        } else {
157          resolvedModules.push(null);
158        }
159      } else {
160        const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts');
161        const suffix: string = /\.js$/.test(moduleName) ? '' : '.js';
162        const jsModulePath: string = path.resolve(__dirname, '../node_modules', moduleName + suffix);
163        if (ts.sys.fileExists(modulePath)) {
164          resolvedModules.push(getResolveModule(modulePath, '.d.ts'));
165        } else if (ts.sys.fileExists(jsModulePath)) {
166          resolvedModules.push(getResolveModule(modulePath, '.js'));
167        } else {
168          resolvedModules.push(null);
169        }
170      }
171    }
172    if (!projectConfig.xtsMode) {
173      createOrUpdateCache(resolvedModules, path.resolve(containingFile));
174    }
175    resolvedModulesCache[path.resolve(containingFile)] = resolvedModules;
176    return resolvedModules;
177  }
178  return resolvedModulesCache[path.resolve(containingFile)];
179}
180
181function createOrUpdateCache(resolvedModules: ts.ResolvedModuleFull[], containingFile: string): void {
182  const children: string[] = [];
183  const error: boolean = false;
184  resolvedModules.forEach(moduleObj => {
185    if (moduleObj && moduleObj.resolvedFileName && /(?<!\.d)\.(ets|ts)$/.test(moduleObj.resolvedFileName)) {
186      const file: string = path.resolve(moduleObj.resolvedFileName);
187      const mtimeMs: number = fs.statSync(file).mtimeMs;
188      children.push(file);
189      const value: CacheFileName = cache[file];
190      if (value) {
191        value.mtimeMs = mtimeMs;
192        value.error = error;
193        value.parent = value.parent || [];
194        value.parent.push(path.resolve(containingFile));
195        value.parent = [...new Set(value.parent)];
196      } else {
197        cache[file] = { mtimeMs, children: [], parent: [containingFile], error };
198      }
199    }
200  });
201  cache[path.resolve(containingFile)] = { mtimeMs: fs.statSync(containingFile).mtimeMs, children,
202    parent: cache[path.resolve(containingFile)] && cache[path.resolve(containingFile)].parent ?
203    cache[path.resolve(containingFile)].parent : [], error };
204}
205
206export function createWatchCompilerHost(rootFileNames: string[],
207  reportDiagnostic: ts.DiagnosticReporter, delayPrintLogCount: Function, resetErrorCount: Function,
208  isPipe: boolean = false): ts.WatchCompilerHostOfFilesAndCompilerOptions<ts.BuilderProgram> {
209  setCompilerOptions();
210  const createProgram = ts.createSemanticDiagnosticsBuilderProgram;
211  const host = ts.createWatchCompilerHost(
212    [...rootFileNames, ...readDeaclareFiles()], compilerOptions,
213    ts.sys, createProgram, reportDiagnostic,
214    (diagnostic: ts.Diagnostic) => {
215      if ([6031, 6032].includes(diagnostic.code)) {
216        if (!isPipe) {
217          process.env.watchTs = 'start';
218          resetErrorCount();
219        }
220      }
221      // End of compilation in watch mode flag.
222      if ([6193, 6194].includes(diagnostic.code)) {
223        if (!isPipe) {
224          process.env.watchTs = 'end';
225        }
226        delayPrintLogCount();
227      }
228    });
229  host.readFile = (fileName: string) => {
230    if (!fs.existsSync(fileName)) {
231      return undefined;
232    }
233    if (/(?<!\.d)\.(ets|ts)$/.test(fileName)) {
234      const content: string = processContent(fs.readFileSync(fileName).toString());
235      checkUISyntax(content, fileName);
236      return content;
237    }
238    return fs.readFileSync(fileName).toString();
239  };
240  host.resolveModuleNames = resolveModuleNames;
241  return host;
242}
243
244function getResolveModule(modulePath: string, type): ts.ResolvedModuleFull {
245  return {
246    resolvedFileName: modulePath,
247    isExternalLibraryImport: false,
248    extension: type
249  };
250}
251
252export const dollarCollection: Set<string> = new Set();
253export const appComponentCollection: Set<string> = new Set();
254export const decoratorParamsCollection: Set<string> = new Set();
255export const extendCollection: Set<string> = new Set();
256export const importModuleCollection: Set<string> = new Set();
257
258function checkUISyntax(source: string, fileName: string): void {
259  if (/\.ets$/.test(fileName)) {
260    if (process.env.compileMode === 'moduleJson' ||
261      path.resolve(fileName) !== path.resolve(projectConfig.projectPath, 'app.ets')) {
262      const sourceFile: ts.SourceFile = ts.createSourceFile(fileName, source,
263        ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
264      collectComponents(sourceFile);
265      parseAllNode(sourceFile, sourceFile);
266      props.push(...dollarCollection, ...decoratorParamsCollection, ...extendCollection);
267    }
268  }
269}
270
271function collectComponents(node: ts.SourceFile): void {
272  // @ts-ignore
273  if (process.env.watchMode !== 'true' && node.identifiers && node.identifiers.size) {
274    // @ts-ignore
275    for (const key of node.identifiers.keys()) {
276      if (JS_BIND_COMPONENTS.has(key)) {
277        appComponentCollection.add(key);
278      }
279    }
280  }
281}
282
283function parseAllNode(node: ts.Node, sourceFileNode: ts.SourceFile): void {
284  if (ts.isStructDeclaration(node)) {
285    if (node.members) {
286      node.members.forEach(item => {
287        if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
288          const propertyName: string = item.name.getText();
289          if (item.decorators && item.decorators.length) {
290            for (let i = 0; i < item.decorators.length; i++) {
291              const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim();
292              if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
293                dollarCollection.add('$' + propertyName);
294              }
295              if (isDecoratorCollection(item.decorators[i], decoratorName)) {
296                decoratorParamsCollection.add(item.decorators[i].expression.arguments[0].getText());
297              }
298            }
299          }
300        }
301      });
302    }
303  }
304  if (ts.isIfStatement(node)) {
305    appComponentCollection.add(COMPONENT_IF);
306  }
307  if (ts.isMethodDeclaration(node) && node.name.getText() === COMPONENT_BUILD_FUNCTION) {
308    if (node.body && node.body.statements && node.body.statements.length) {
309      const checkProp: ts.NodeArray<ts.Statement> = node.body.statements;
310      checkProp.forEach((item, index) => {
311        traverseBuild(item, index);
312      });
313    }
314  }
315  node.getChildren().forEach((item: ts.Node) => parseAllNode(item, sourceFileNode));
316}
317
318function traverseBuild(node: ts.Node, index: number): void {
319  if (ts.isExpressionStatement(node)) {
320    let parentComponentName: string = getName(node);
321    if (!INNER_COMPONENT_NAMES.has(parentComponentName) && node.parent && node.parent.statements && index >= 1 &&
322      node.parent.statements[index - 1].expression && node.parent.statements[index - 1].expression.expression) {
323      parentComponentName = node.parent.statements[index - 1].expression.expression.escapedText;
324    }
325    node = node.expression;
326    if (ts.isEtsComponentExpression(node) && node.body && ts.isBlock(node.body)) {
327      node.body.statements.forEach((item, indexBlock) => {
328        traverseBuild(item, indexBlock);
329      });
330    } else {
331      loopNodeFindDoubleDollar(node, parentComponentName);
332    }
333  } else if (ts.isIfStatement(node)) {
334    if (node.thenStatement && ts.isBlock(node.thenStatement) && node.thenStatement.statements) {
335      node.thenStatement.statements.forEach((item, indexIfBlock) => {
336        traverseBuild(item, indexIfBlock);
337      });
338    }
339    if (node.elseStatement && ts.isBlock(node.elseStatement) && node.elseStatement.statements) {
340      node.elseStatement.statements.forEach((item, indexElseBlock) => {
341        traverseBuild(item, indexElseBlock);
342      });
343    }
344  }
345}
346
347function loopNodeFindDoubleDollar(node: ts.Node, parentComponentName: string): void {
348  while (node) {
349    if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
350      const argument: ts.NodeArray<ts.Node> = node.arguments;
351      const propertyName: ts.Identifier | ts.PrivateIdentifier = node.expression.name;
352      if (isCanAddDoubleDollar(propertyName.getText(), parentComponentName)) {
353        argument.forEach((item: ts.Node) => {
354          doubleDollarCollection(item);
355        });
356      }
357    } else if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.arguments
358      && node.arguments.length) {
359      node.arguments.forEach((item: ts.Node) => {
360        if (ts.isObjectLiteralExpression(item) && item.properties && item.properties.length) {
361          item.properties.forEach((param: ts.Node) => {
362            if (isObjectPram(param, parentComponentName)) {
363              doubleDollarCollection(param.initializer);
364            }
365          });
366        }
367        if (STYLE_ADD_DOUBLE_DOLLAR.has(node.expression.getText()) && ts.isPropertyAccessExpression(item)) {
368          doubleDollarCollection(item);
369        }
370      });
371    }
372    node = node.expression;
373  }
374}
375
376function doubleDollarCollection(item: ts.Node): void {
377  if (item.getText().startsWith($$)) {
378    while (item.expression) {
379      item = item.expression;
380    }
381    dollarCollection.add(item.getText());
382  }
383}
384
385function isObjectPram(param: ts.Node, parentComponentName:string): boolean {
386  return ts.isPropertyAssignment(param) && param.name && ts.isIdentifier(param.name) &&
387    param.initializer && PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
388    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(param.name.getText());
389}
390
391function isCanAddDoubleDollar(propertyName: string, parentComponentName: string): boolean {
392  return PROPERTIES_ADD_DOUBLE_DOLLAR.has(parentComponentName) &&
393    PROPERTIES_ADD_DOUBLE_DOLLAR.get(parentComponentName).has(propertyName) ||
394    STYLE_ADD_DOUBLE_DOLLAR.has(propertyName);
395}
396
397function isDecoratorCollection(item: ts.Decorator, decoratorName: string): boolean {
398  return COMPONENT_DECORATORS_PARAMS.has(decoratorName) &&
399    // @ts-ignore
400    item.expression.arguments && item.expression.arguments.length &&
401    // @ts-ignore
402    ts.isIdentifier(item.expression.arguments[0]);
403}
404
405function processDraw(source: string): string {
406  const reg: RegExp = /new\s+\b(Circle|Ellipse|Rect|Path)\b/g;
407  return source.replace(reg, (item:string, item1: string) => {
408    return '\xa0'.repeat(item.length - item1.length) + item1;
409  });
410}
411
412function processContent(source: string): string {
413  source = processSystemApi(source);
414  source = preprocessExtend(source, extendCollection);
415  source = preprocessNewExtend(source, extendCollection);
416  source = processDraw(source);
417  return source;
418}
419