• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 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 fs from 'fs';
18import path from 'path';
19
20import {
21  EXTNAME_ETS,
22  NODE_MODULES,
23  INDEX_ETS,
24  INDEX_TS,
25  PACKAGE_JSON,
26  STRUCT,
27  CLASS,
28  CUSTOM_COMPONENT_DEFAULT,
29  CUSTOM_DECORATOR_NAME,
30  COMPONENT_DECORATOR_ENTRY
31} from './pre_define';
32import {
33  propertyCollection,
34  linkCollection,
35  componentCollection,
36  preprocessExtend,
37  preprocessNewExtend,
38  processSystemApi,
39  propCollection,
40  isObservedClass,
41  isCustomDialogClass,
42  observedClassCollection,
43  enumCollection,
44  getComponentSet,
45  IComponentSet
46} from './validate_ui_syntax';
47import { LogInfo, LogType } from './utils';
48import { projectConfig } from '../main';
49import { INNER_COMPONENT_NAMES } from './component_map';
50import { builderParamObjectCollection } from './process_component_member';
51
52const IMPORT_FILE_ASTCACHE: Map<string, ts.SourceFile> = new Map();
53
54export default function processImport(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration |
55  ts.ExportDeclaration, pagesDir: string, log: LogInfo[], asName: Map<string, string> = new Map(),
56  isEntryPage: boolean = true, pathCollection: Set<string> = new Set()): void {
57  let filePath: string;
58  let defaultName: string;
59  if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
60    filePath = node.moduleSpecifier.getText().replace(/'|"/g, '');
61    if (ts.isImportDeclaration(node) && node.importClause && node.importClause.name &&
62      ts.isIdentifier(node.importClause.name)) {
63      defaultName = node.importClause.name.escapedText.toString();
64      if (isEntryPage) {
65        asName.set(defaultName, defaultName);
66      }
67    }
68    if (ts.isImportDeclaration(node) && node.importClause && node.importClause.namedBindings &&
69      ts.isNamedImports(node.importClause.namedBindings) &&
70      node.importClause.namedBindings.elements && isEntryPage) {
71      node.importClause.namedBindings.elements.forEach(item => {
72        if (item.name && ts.isIdentifier(item.name)) {
73          validateModuleName(item.name, log);
74          if (item.propertyName && ts.isIdentifier(item.propertyName) && asName) {
75            asName.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString());
76          } else {
77            asName.set(item.name.escapedText.toString(), item.name.escapedText.toString());
78          }
79        }
80      });
81    }
82  } else {
83    if (node.moduleReference && ts.isExternalModuleReference(node.moduleReference) &&
84      node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) {
85      filePath = node.moduleReference.expression.text;
86      defaultName = node.name.escapedText.toString();
87      if (isEntryPage) {
88        asName.set(defaultName, defaultName);
89      }
90    }
91  }
92  if (filePath && path.extname(filePath) !== EXTNAME_ETS && !isModule(filePath)) {
93    const dirIndexPath: string = path.resolve(path.resolve(pagesDir, filePath), INDEX_ETS);
94    if (/^(\.|\.\.)\//.test(filePath) && !fs.existsSync(path.resolve(pagesDir, filePath + EXTNAME_ETS)) &&
95      fs.existsSync(dirIndexPath)) {
96      filePath = dirIndexPath;
97    } else {
98      filePath += EXTNAME_ETS;
99    }
100  }
101  try {
102    let fileResolvePath: string;
103    if (/^(\.|\.\.)\//.test(filePath) && filePath.indexOf(NODE_MODULES) < 0) {
104      fileResolvePath = path.resolve(pagesDir, filePath);
105    } else if (/^\//.test(filePath) && filePath.indexOf(NODE_MODULES) < 0 ||
106      fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
107      fileResolvePath = filePath;
108    } else {
109      fileResolvePath = getFileResolvePath(fileResolvePath, pagesDir, filePath, projectConfig.projectPath);
110    }
111    if (fs.existsSync(fileResolvePath) && fs.statSync(fileResolvePath).isFile() &&
112      !pathCollection.has(fileResolvePath)) {
113      let sourceFile: ts.SourceFile;
114      pathCollection.add(fileResolvePath);
115      if (IMPORT_FILE_ASTCACHE.has(fileResolvePath)) {
116        sourceFile = IMPORT_FILE_ASTCACHE.get(fileResolvePath);
117      } else {
118        const content: string = preprocessNewExtend(preprocessExtend(processSystemApi(
119          fs.readFileSync(fileResolvePath, { encoding: 'utf-8' }).replace(
120            new RegExp('\\b' + STRUCT + '\\b.+\\{', 'g'), item => {
121              return item.replace(new RegExp('\\b' + STRUCT + '\\b', 'g'), `${CLASS} `);
122            }))));
123        sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
124        IMPORT_FILE_ASTCACHE[fileResolvePath] = sourceFile;
125      }
126      visitAllNode(sourceFile, sourceFile, defaultName, asName, path.dirname(fileResolvePath), log, new Set(),
127        new Set(), new Set(), new Map(), pathCollection, fileResolvePath);
128    }
129  } catch (e) {
130    // ignore
131  }
132}
133
134function visitAllNode(node: ts.Node, sourceFile: ts.SourceFile, defaultNameFromParent: string, asNameFromParent: Map<string, string>,
135  pagesDir: string, log: LogInfo[], entryCollection: Set<string>, exportCollection: Set<string>,
136  defaultCollection: Set<string>, asExportCollection: Map<string, string>, pathCollection: Set<string>,
137  fileResolvePath: string) {
138  if (isObservedClass(node)) {
139    // @ts-ignore
140    observedClassCollection.add(node.name.getText());
141  }
142  if (isCustomDialogClass(node)) {
143    // @ts-ignore
144    componentCollection.customDialogs.add(node.name.getText());
145  }
146  if (ts.isEnumDeclaration(node) && node.name) {
147    enumCollection.add(node.name.getText());
148  }
149  if ((ts.isClassDeclaration(node) || ts.isStructDeclaration(node)) && ts.isIdentifier(node.name) &&
150    isCustomComponent(node)) {
151    addDependencies(node, defaultNameFromParent, asNameFromParent);
152    isExportEntry(node, log, entryCollection, exportCollection);
153    if (asExportCollection.has(node.name.getText())) {
154      componentCollection.customComponents.add(asExportCollection.get(node.name.getText()));
155    }
156    if (node.modifiers && node.modifiers.length >= 2 && node.modifiers[0] &&
157      node.modifiers[0].kind === ts.SyntaxKind.ExportKeyword && node.modifiers[1] &&
158      node.modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) {
159      if (!defaultNameFromParent && hasCollection(node.name)) {
160        addDefaultExport(node);
161      } else if (defaultNameFromParent && asNameFromParent.has(defaultNameFromParent)) {
162        componentCollection.customComponents.add(asNameFromParent.get(defaultNameFromParent));
163      }
164    }
165    if (defaultCollection.has(node.name.getText())) {
166      componentCollection.customComponents.add('default');
167    }
168  }
169  if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression) &&
170    hasCollection(node.expression)) {
171    if (defaultNameFromParent) {
172      setDependencies(defaultNameFromParent,
173        linkCollection.get(node.expression.escapedText.toString()),
174        propertyCollection.get(node.expression.escapedText.toString()),
175        propCollection.get(node.expression.escapedText.toString()),
176        builderParamObjectCollection.get(node.expression.escapedText.toString()));
177    }
178    addDefaultExport(node);
179  }
180  if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) {
181    if (defaultNameFromParent) {
182      asNameFromParent.set(node.expression.getText(), asNameFromParent.get(defaultNameFromParent));
183    }
184    defaultCollection.add(node.expression.getText());
185  }
186  if (ts.isExportDeclaration(node) && node.exportClause &&
187    ts.isNamedExports(node.exportClause) && node.exportClause.elements) {
188    node.exportClause.elements.forEach(item => {
189      if (item.name && item.propertyName && ts.isIdentifier(item.name) &&
190        ts.isIdentifier(item.propertyName)) {
191        validateModuleName(item.name, log, sourceFile, fileResolvePath);
192        if (hasCollection(item.propertyName)) {
193          let asExportName: string = item.name.escapedText.toString();
194          const asExportPropertyName: string = item.propertyName.escapedText.toString();
195          if (asNameFromParent.has(asExportName)) {
196            asExportName = asNameFromParent.get(asExportName);
197          }
198          setDependencies(asExportName, linkCollection.get(asExportPropertyName),
199            propertyCollection.get(asExportPropertyName),
200            propCollection.get(asExportPropertyName),
201            builderParamObjectCollection.get(asExportPropertyName));
202        }
203        asExportCollection.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString());
204      }
205      if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString()) &&
206        item.propertyName && ts.isIdentifier(item.propertyName)) {
207        asNameFromParent.set(item.propertyName.escapedText.toString(),
208          asNameFromParent.get(item.name.escapedText.toString()));
209      }
210    });
211  }
212  if (ts.isExportDeclaration(node) && node.moduleSpecifier &&
213    ts.isStringLiteral(node.moduleSpecifier)) {
214    if (projectConfig.isPreview && node.exportClause && ts.isNamedExports(node.exportClause) &&
215      node.exportClause.elements) {
216      node.exportClause.elements.forEach(item => {
217        exportCollection.add((item.propertyName ? item.propertyName : item.name).escapedText.toString());
218        if (item.propertyName && ts.isIdentifier(item.propertyName) && item.name &&
219          ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) {
220          asNameFromParent.set(item.propertyName.escapedText.toString(),
221            asNameFromParent.get(item.name.escapedText.toString()));
222            defaultCollection.add(item.name.escapedText.toString());
223        }
224      });
225    }
226    processImport(node, pagesDir, log, asNameFromParent, true, new Set(pathCollection));
227  }
228  if (ts.isImportDeclaration(node)) {
229    if (node.importClause && node.importClause.name && ts.isIdentifier(node.importClause.name) &&
230      asNameFromParent.has(node.importClause.name.getText())) {
231      processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection));
232    } else if (node.importClause && node.importClause.namedBindings &&
233      ts.isNamedImports(node.importClause.namedBindings) && node.importClause.namedBindings.elements) {
234      let nested: boolean = false;
235      node.importClause.namedBindings.elements.forEach(item => {
236        if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) {
237          nested = true;
238          if (item.propertyName && ts.isIdentifier(item.propertyName)) {
239            asNameFromParent.set(item.propertyName.escapedText.toString(),
240              asNameFromParent.get(item.name.escapedText.toString()));
241          }
242        }
243      });
244      if (nested) {
245        processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection));
246      }
247    }
248  }
249  node.getChildren().reverse().forEach((item: ts.Node) => visitAllNode(item, sourceFile, defaultNameFromParent,
250    asNameFromParent, pagesDir, log, entryCollection, exportCollection, defaultCollection,
251    asExportCollection, pathCollection, fileResolvePath));
252}
253
254function isExportEntry(node: ts.ClassDeclaration, log: LogInfo[], entryCollection: Set<string>,
255  exportCollection: Set<string>): void {
256  if (projectConfig.isPreview && node && node.decorators) {
257    let existExport: boolean = false;
258    let existEntry: boolean = false;
259    if (node.modifiers) {
260      for (let i = 0; i < node.modifiers.length; i++) {
261        if (node.modifiers[i].kind === ts.SyntaxKind.ExportKeyword) {
262          existExport = true;
263          break;
264        }
265      }
266    }
267    for (let i = 0; i < node.decorators.length; i++) {
268      if (node.decorators[i].getText() === COMPONENT_DECORATOR_ENTRY) {
269        entryCollection.add(node.name.escapedText.toString());
270        existEntry = true;
271        break;
272      }
273    }
274  }
275}
276
277function addDependencies(node: ts.ClassDeclaration, defaultNameFromParent: string,
278  asNameFromParent: Map<string, string>): void {
279  const componentName: string = node.name.getText();
280  const ComponentSet: IComponentSet = getComponentSet(node);
281  if (defaultNameFromParent && node.modifiers && node.modifiers.length >= 2 && node.modifiers[0] &&
282    node.modifiers[1] && node.modifiers[0].kind === ts.SyntaxKind.ExportKeyword &&
283    node.modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) {
284    setDependencies(defaultNameFromParent, ComponentSet.links, ComponentSet.properties,
285      ComponentSet.props, ComponentSet.builderParams);
286  } else if (asNameFromParent.has(componentName)) {
287    setDependencies(asNameFromParent.get(componentName), ComponentSet.links, ComponentSet.properties,
288      ComponentSet.props, ComponentSet.builderParams);
289  } else {
290    setDependencies(componentName, ComponentSet.links, ComponentSet.properties, ComponentSet.props,
291      ComponentSet.builderParams);
292  }
293}
294
295function addDefaultExport(node: ts.ClassDeclaration | ts.ExportAssignment): void {
296  let name: string;
297  if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
298    name = node.name.escapedText.toString();
299  } else if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) {
300    name = node.expression.escapedText.toString();
301  } else {
302    return;
303  }
304  setDependencies(CUSTOM_COMPONENT_DEFAULT,
305    linkCollection.has(CUSTOM_COMPONENT_DEFAULT) ?
306      new Set([...linkCollection.get(CUSTOM_COMPONENT_DEFAULT), ...linkCollection.get(name)]) :
307      linkCollection.get(name),
308    propertyCollection.has(CUSTOM_COMPONENT_DEFAULT) ?
309      new Set([...propertyCollection.get(CUSTOM_COMPONENT_DEFAULT), ...propertyCollection.get(name)]) :
310      propertyCollection.get(name),
311    propCollection.has(CUSTOM_COMPONENT_DEFAULT) ?
312      new Set([...propCollection.get(CUSTOM_COMPONENT_DEFAULT), ...propCollection.get(name)]) :
313      propCollection.get(name),
314    builderParamObjectCollection.has(CUSTOM_COMPONENT_DEFAULT) ?
315      new Set([...builderParamObjectCollection.get(CUSTOM_COMPONENT_DEFAULT), ...builderParamObjectCollection.get(name)]) :
316      builderParamObjectCollection.get(name));
317
318}
319
320function setDependencies(component: string, linkArray: Set<string>, propertyArray: Set<string>,
321  propArray: Set<string>, builderParamArray: Set<string>): void {
322  linkCollection.set(component, linkArray);
323  propertyCollection.set(component, propertyArray);
324  propCollection.set(component, propArray);
325  builderParamObjectCollection.set(component, builderParamArray);
326  componentCollection.customComponents.add(component);
327}
328
329function hasCollection(node: ts.Identifier): boolean {
330  return linkCollection.has(node.escapedText.toString()) ||
331    propCollection.has(node.escapedText.toString()) ||
332    propertyCollection.has(node.escapedText.toString());
333}
334
335function isModule(filePath: string): boolean {
336  return !/^(\.|\.\.)?\//.test(filePath) || filePath.indexOf(NODE_MODULES) > -1;
337}
338
339function isCustomComponent(node: ts.ClassDeclaration | ts.StructDeclaration): boolean {
340  if (node.decorators && node.decorators.length) {
341    for (let i = 0; i < node.decorators.length; ++i) {
342      const decoratorName: ts.Identifier = node.decorators[i].expression as ts.Identifier;
343      if (ts.isIdentifier(decoratorName) &&
344        CUSTOM_DECORATOR_NAME.has(decoratorName.escapedText.toString())) {
345        return true;
346      }
347    }
348  }
349  return false;
350}
351
352function isPackageJsonEntry(filePath: string): boolean {
353  const packageJsonPath: string = path.join(filePath, PACKAGE_JSON);
354  if (fs.existsSync(packageJsonPath)) {
355    let entry: string;
356    try {
357      entry = JSON.parse(fs.readFileSync(packageJsonPath).toString()).main;
358    } catch (e) {
359      return false;
360    }
361    if (typeof entry === 'string' && fs.existsSync(path.join(filePath, entry))) {
362      return true;
363    }
364  }
365}
366
367function getPackageJsonEntry(filePath: string): string {
368  return path.join(filePath, JSON.parse(fs.readFileSync(path.join(filePath, PACKAGE_JSON)).toString()).main);
369}
370
371function getModuleFilePath(filePath: string): string {
372  if (filePath && path.extname(filePath) !== EXTNAME_ETS && isModule(filePath)) {
373    filePath += EXTNAME_ETS;
374  }
375  return filePath;
376}
377
378function getFileResolvePath(fileResolvePath: string, pagesDir: string, filePath: string,
379  projectPath: string): string {
380  const moduleFilePath: string = getModuleFilePath(filePath);
381  const defaultModule: string = path.join(projectPath, moduleFilePath);
382  if (fs.existsSync(defaultModule)) {
383    return defaultModule;
384  }
385  let entryModule: string;
386  let etsModule: string;
387  if (!projectConfig.aceModuleJsonPath) {
388    entryModule = path.join(projectPath, '../../../../../', moduleFilePath);
389    etsModule = path.join(projectPath, '../../', moduleFilePath);
390  } else {
391    entryModule = path.join(projectPath, '../../../../', moduleFilePath);
392    etsModule = path.join(projectPath, '../', moduleFilePath);
393  }
394  if (fs.existsSync(entryModule)) {
395    return entryModule;
396  }
397  if (fs.existsSync(etsModule)) {
398    return etsModule;
399  }
400  let curPageDir: string = pagesDir;
401  while (!fs.existsSync(fileResolvePath)) {
402    if (filePath.indexOf(NODE_MODULES) > -1) {
403      fileResolvePath = path.join(curPageDir, filePath);
404    } else {
405      fileResolvePath = path.join(curPageDir, NODE_MODULES, filePath);
406    }
407    if (fs.existsSync(fileResolvePath + EXTNAME_ETS)) {
408      fileResolvePath = fileResolvePath + EXTNAME_ETS;
409    } else if (isPackageJsonEntry(fileResolvePath)) {
410      fileResolvePath = getPackageJsonEntry(fileResolvePath);
411      if (fs.statSync(fileResolvePath).isDirectory()) {
412        if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) {
413          fileResolvePath = path.join(fileResolvePath, INDEX_ETS);
414        } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) {
415          fileResolvePath = path.join(fileResolvePath, INDEX_TS);
416        }
417      }
418    } else if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) {
419      fileResolvePath = path.join(fileResolvePath, INDEX_ETS);
420    } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) {
421      fileResolvePath = path.join(fileResolvePath, INDEX_TS);
422    }
423    if (curPageDir === path.parse(curPageDir).root) {
424      break;
425    }
426    curPageDir = path.dirname(curPageDir);
427  }
428  return fileResolvePath;
429}
430
431function validateModuleName(moduleNode: ts.Identifier, log: LogInfo[], sourceFile?: ts.SourceFile,
432  fileResolvePath?: string): void {
433  const moduleName: string = moduleNode.escapedText.toString();
434  if (INNER_COMPONENT_NAMES.has(moduleName)) {
435    const error: LogInfo = {
436      type: LogType.ERROR,
437      message: `The module name '${moduleName}' can not be the same as the inner component name.`,
438      pos: moduleNode.getStart()
439    }
440    if (sourceFile && fileResolvePath) {
441      const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(moduleNode.getStart());
442      const line: number = posOfNode.line + 1;
443      const column: number = posOfNode.character + 1;
444      Object.assign(error, {
445        fileName: fileResolvePath,
446        line: line,
447        column: column
448      })
449    }
450    log.push(error);
451  }
452}
453