• 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 path from 'path';
18
19import validateReExportType from './validate_module_syntax';
20import {
21  PAGE_ENTRY_FUNCTION_NAME,
22  PREVIEW_COMPONENT_FUNCTION_NAME,
23  STORE_PREVIEW_COMPONENTS,
24  GET_PREVIEW_FLAG_FUNCTION_NAME,
25  COMPONENT_CONSTRUCTOR_UNDEFINED,
26  BUILD_ON,
27  COMPONENT_BUILDER_DECORATOR,
28  COMPONENT_CONCURRENT_DECORATOR,
29  COMPONENT_EXTEND_DECORATOR,
30  COMPONENT_STYLES_DECORATOR,
31  RESOURCE,
32  RESOURCE_TYPE,
33  WORKER_OBJECT,
34  RESOURCE_NAME_ID,
35  RESOURCE_NAME_TYPE,
36  RESOURCE_NAME_PARAMS,
37  RESOURCE_RAWFILE,
38  RESOURCE_NAME_BUNDLE,
39  RESOURCE_NAME_MODULE,
40  ATTRIBUTE_ANIMATETO,
41  GLOBAL_CONTEXT,
42  CHECK_COMPONENT_EXTEND_DECORATOR,
43  INSTANCE,
44  SET_CONTROLLER_CTR_TYPE,
45  SET_CONTROLLER_METHOD,
46  JS_DIALOG,
47  CUSTOM_DIALOG_CONTROLLER_BUILDER,
48  ESMODULE,
49  ARK,
50  EXTNAME_ETS,
51  GENERATE_ID,
52  _GENERATE_ID,
53  VIEWSTACKPROCESSOR,
54  STARTGETACCESSRECORDINGFOR,
55  ALLOCATENEWELMETIDFORNEXTCOMPONENT,
56  STOPGETACCESSRECORDING,
57  CARD_ENTRY_FUNCTION_NAME,
58  CARD_LOG_TYPE_COMPONENTS,
59  CARD_LOG_TYPE_DECORATORS,
60  CARD_LOG_TYPE_IMPORT
61} from './pre_define';
62import {
63  componentInfo,
64  LogInfo,
65  LogType,
66  hasDecorator,
67  FileLog,
68  getPossibleBuilderTypeParameter
69} from './utils';
70import { writeFileSyncByNode } from './process_module_files';
71import {
72  componentCollection,
73  localStorageLinkCollection,
74  localStoragePropCollection,
75} from './validate_ui_syntax';
76import {
77  processComponentClass,
78  createParentParameter,
79  processBuildMember
80} from './process_component_class';
81import processImport from './process_import';
82import {
83  processComponentBlock,
84  bindComponentAttr,
85  getName
86} from './process_component_build';
87import {
88  BUILDIN_STYLE_NAMES,
89  CUSTOM_BUILDER_METHOD,
90  EXTEND_ATTRIBUTE,
91  INNER_STYLE_FUNCTION,
92  GLOBAL_STYLE_FUNCTION,
93  INTERFACE_NODE_SET,
94  ID_ATTRS
95} from './component_map';
96import {
97  resources,
98  projectConfig,
99  partialUpdateConfig
100} from '../main';
101import { createCustomComponentNewExpression, createViewCreate } from './process_component_member';
102import { ModuleSourceFile } from './fast_build/ark_compiler/module/module_source_file';
103
104export const transformLog: FileLog = new FileLog();
105export let contextGlobal: ts.TransformationContext;
106export let resourceFileName: string = '';
107export const builderTypeParameter: { params: string[] } = { params: [] };
108
109export function processUISyntax(program: ts.Program, ut = false): Function {
110  let entryNodeKey: ts.Expression;
111  return (context: ts.TransformationContext) => {
112    contextGlobal = context;
113    let pagesDir: string;
114    return (node: ts.SourceFile) => {
115      pagesDir = path.resolve(path.dirname(node.fileName));
116      resourceFileName = path.resolve(node.fileName);
117      if (process.env.compiler === BUILD_ON || process.env.compileTool === 'rollup') {
118        transformLog.sourceFile = node;
119        preprocessIdAttrs(node.fileName);
120        if (!ut && (process.env.compileMode !== 'moduleJson' &&
121          path.resolve(node.fileName) === path.resolve(projectConfig.projectPath, 'app.ets') ||
122          /\.ts$/.test(node.fileName))) {
123          node = ts.visitEachChild(node, processResourceNode, context);
124          if (projectConfig.compileMode === ESMODULE) {
125            validateReExportType(node, pagesDir, transformLog.errors);
126            if (projectConfig.processTs === true) {
127              process.env.compileTool === 'rollup' ?
128                ModuleSourceFile.newSourceFile(path.normalize(node.fileName), node) :
129                writeFileSyncByNode(node, true, projectConfig);
130            }
131          }
132          return node;
133        }
134        const id: number = ++componentInfo.id;
135        node = ts.visitEachChild(node, processAllNodes, context);
136        node = createEntryNode(node, context, entryNodeKey, id);
137        if (projectConfig.compileMode === ESMODULE) {
138          validateReExportType(node, pagesDir, transformLog.errors);
139        }
140        GLOBAL_STYLE_FUNCTION.forEach((block, styleName) => {
141          BUILDIN_STYLE_NAMES.delete(styleName);
142        });
143        GLOBAL_STYLE_FUNCTION.clear();
144        const statements: ts.Statement[] = Array.from(node.statements);
145        if (!partialUpdateConfig.partialUpdateMode) {
146          generateId(statements, node);
147        }
148        INTERFACE_NODE_SET.forEach(item => {
149          statements.unshift(item);
150        });
151        node = ts.factory.updateSourceFile(node, statements);
152        INTERFACE_NODE_SET.clear();
153        if (projectConfig.compileMode === ESMODULE && projectConfig.processTs === true) {
154          process.env.compileTool === 'rollup' ? ModuleSourceFile.newSourceFile(path.normalize(node.fileName), node) :
155            writeFileSyncByNode(node, true, projectConfig);
156        }
157        return node;
158      } else {
159        return node;
160      }
161    };
162
163    function entryKeyNode(node: ts.Node): ts.Expression {
164      if (node && node.decorators && node.decorators.length) {
165        node.decorators.forEach(item => {
166          if (item.expression && ts.isCallExpression(item.expression) && ts.isIdentifier(item.expression.expression) &&
167            item.expression.expression.escapedText.toString() === 'Entry' && item.expression.arguments &&
168            item.expression.arguments.length && ts.isIdentifier(item.expression.arguments[0])) {
169            entryNodeKey = item.expression.arguments[0];
170          }
171        });
172      }
173      return entryNodeKey;
174    }
175
176    function processAllNodes(node: ts.Node): ts.Node {
177      if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) ||
178        ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
179        processImport(node, pagesDir, transformLog.errors);
180      } else if (ts.isStructDeclaration(node)) {
181        componentCollection.currentClassName = node.name.getText();
182        componentCollection.entryComponent === componentCollection.currentClassName && entryKeyNode(node);
183        node = processComponentClass(node, context, transformLog.errors, program);
184        componentCollection.currentClassName = null;
185        INNER_STYLE_FUNCTION.forEach((block, styleName) => {
186          BUILDIN_STYLE_NAMES.delete(styleName);
187        });
188        INNER_STYLE_FUNCTION.clear();
189      } else if (ts.isFunctionDeclaration(node)) {
190        if (hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
191          node = processExtend(node, transformLog.errors);
192        } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR) && node.name && node.body &&
193          ts.isBlock(node.body)) {
194          CUSTOM_BUILDER_METHOD.add(node.name.getText());
195          builderTypeParameter.params = getPossibleBuilderTypeParameter(node.parameters);
196          node.parameters.push(createParentParameter());
197          node = ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers,
198            node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type,
199            processComponentBlock(node.body, false, transformLog.errors, false, true,
200              node.name.getText(), undefined, true));
201          builderTypeParameter.params = [];
202          node = processBuildMember(node, context, transformLog.errors, true);
203        } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) {
204          if (node.parameters.length === 0) {
205            node = undefined;
206          } else {
207            transformLog.errors.push({
208              type: LogType.ERROR,
209              message: `@Styles can't have parameters.`,
210              pos: node.getStart()
211            });
212          }
213        } else if (hasDecorator(node, COMPONENT_CONCURRENT_DECORATOR)) {
214          // ark compiler's feature
215          node = processConcurrent(node);
216        }
217      } else if (isResource(node)) {
218        node = processResourceData(node as ts.CallExpression);
219      } else if (isWorker(node)) {
220        node = processWorker(node as ts.NewExpression);
221      } else if (isAnimateTo(node)) {
222        node = processAnimateTo(node as ts.CallExpression);
223      } else if (isCustomDialogController(node)) {
224        node = createCustomDialogController(node.parent, node, transformLog.errors);
225      }
226      return ts.visitEachChild(node, processAllNodes, context);
227    }
228    function processResourceNode(node: ts.Node): ts.Node {
229      if (isResource(node)) {
230        node = processResourceData(node as ts.CallExpression);
231      }
232      return ts.visitEachChild(node, processResourceNode, context);
233    }
234  };
235}
236
237function generateId(statements: ts.Statement[], node: ts.SourceFile): void {
238  statements.unshift(
239    ts.factory.createVariableStatement(
240      undefined,
241      ts.factory.createVariableDeclarationList(
242        [ts.factory.createVariableDeclaration(
243          ts.factory.createIdentifier(_GENERATE_ID),
244          undefined,
245          ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
246          ts.factory.createNumericLiteral('0')
247        )],
248        ts.NodeFlags.Let
249      )
250    ),
251    ts.factory.createFunctionDeclaration(
252      undefined,
253      undefined,
254      undefined,
255      ts.factory.createIdentifier(GENERATE_ID),
256      undefined,
257      [],
258      ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
259      ts.factory.createBlock(
260        [ts.factory.createReturnStatement(ts.factory.createBinaryExpression(
261          ts.factory.createStringLiteral(path.basename(node.fileName, EXTNAME_ETS) + '_'),
262          ts.factory.createToken(ts.SyntaxKind.PlusToken), ts.factory.createPrefixUnaryExpression(
263            ts.SyntaxKind.PlusPlusToken,
264            ts.factory.createIdentifier(_GENERATE_ID)
265          )))],
266        true
267      )
268    )
269  );
270}
271
272function preprocessIdAttrs(fileName: string): void {
273  for (const [id, idInfo] of ID_ATTRS) {
274    if (fileName === idInfo.get('path')) {
275      ID_ATTRS.delete(id);
276    }
277  }
278}
279
280function isCustomDialogController(node: ts.Expression) {
281  const tempParent: ts.Node = node.parent;
282  // @ts-ignore
283  if (!node.parent && node.original) {
284    // @ts-ignore
285    node.parent = node.original.parent;
286  }
287  if (ts.isNewExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
288    node.expression.escapedText.toString() === SET_CONTROLLER_CTR_TYPE) {
289    return true;
290  } else {
291    // @ts-ignore
292    node.parent = tempParent;
293    return false;
294  }
295}
296
297function createCustomDialogController(parent: ts.Expression, node: ts.NewExpression,
298  log: LogInfo[]): ts.NewExpression {
299  if (node.arguments && node.arguments.length === 1 &&
300    ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) {
301    const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => {
302      const componentName: string = isCustomDialogControllerPropertyAssignment(item, log);
303      if (componentName !== null) {
304        item = processCustomDialogControllerPropertyAssignment(parent,
305          item as ts.PropertyAssignment, componentName);
306      }
307      return item;
308    });
309    return ts.factory.createNewExpression(node.expression, node.typeArguments,
310      [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]);
311  }
312}
313
314function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike,
315  log: LogInfo[]): string {
316  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) &&
317    node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) {
318    if (node.initializer) {
319      const componentName: string = getName(node.initializer);
320      if (componentCollection.customDialogs.has(componentName)) {
321        return componentName;
322      }
323    } else {
324      validateCustomDialogControllerBuilderInit(node, log);
325    }
326  }
327  return null;
328}
329
330function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike,
331  log: LogInfo[]): void {
332  log.push({
333    type: LogType.ERROR,
334    message: 'The builder should be initialized with a @CustomDialog Component.',
335    pos: node.getStart()
336  });
337}
338
339function processCustomDialogControllerPropertyAssignment(parent: ts.Expression,
340  node: ts.PropertyAssignment, componentName: string): ts.PropertyAssignment {
341  if (ts.isCallExpression(node.initializer)) {
342    return ts.factory.updatePropertyAssignment(node, node.name,
343      processCustomDialogControllerBuilder(parent, node.initializer, componentName));
344  }
345}
346
347function processCustomDialogControllerBuilder(parent: ts.Expression,
348  node: ts.CallExpression, componentName: string): ts.ArrowFunction {
349  const newExp: ts.Expression = createCustomComponentNewExpression(node, componentName, false, false, true);
350  const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG);
351  return createCustomComponentBuilderArrowFunction(parent, jsDialog, newExp);
352}
353
354function createCustomComponentBuilderArrowFunction(parent: ts.Expression,
355  jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction {
356  let mountNodde: ts.PropertyAccessExpression;
357  if (ts.isBinaryExpression(parent)) {
358    mountNodde = parent.left;
359  } else if (ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent)) {
360    mountNodde = ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
361      parent.name as ts.Identifier);
362  }
363  return ts.factory.createArrowFunction(
364    undefined,
365    undefined,
366    [],
367    undefined,
368    ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
369    ts.factory.createBlock(
370      [
371        ts.factory.createVariableStatement(
372          undefined,
373          ts.factory.createVariableDeclarationList(
374            [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)],
375            ts.NodeFlags.Let
376          )
377        ),
378        ts.factory.createExpressionStatement(
379          ts.factory.createCallExpression(
380            ts.factory.createPropertyAccessExpression(
381              jsDialog,
382              ts.factory.createIdentifier(SET_CONTROLLER_METHOD)
383            ),
384            undefined,
385            [mountNodde]
386          )
387        ),
388        ts.factory.createExpressionStatement(createViewCreate(jsDialog))
389      ],
390      true
391    )
392  );
393}
394
395export function isResource(node: ts.Node): boolean {
396  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
397    (node.expression.escapedText.toString() === RESOURCE ||
398    node.expression.escapedText.toString() === RESOURCE_RAWFILE) && node.arguments.length > 0;
399}
400
401export function isAnimateTo(node: ts.Node): boolean {
402  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
403    node.expression.escapedText.toString() === ATTRIBUTE_ANIMATETO;
404}
405
406export function processResourceData(node: ts.CallExpression,
407  previewLog: {isAcceleratePreview: boolean, log: LogInfo[]} = {isAcceleratePreview: false, log: []}): ts.Node {
408  if (ts.isStringLiteral(node.arguments[0])) {
409    if (node.expression.getText() === RESOURCE_RAWFILE) {
410      return createResourceParam(0, RESOURCE_TYPE.rawfile, [node.arguments[0]]);
411    } else {
412      return getResourceDataNode(node, previewLog);
413    }
414  }
415  return node;
416}
417
418function getResourceDataNode(node: ts.CallExpression,
419  previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}): ts.Node {
420  const resourceData: string[] = (node.arguments[0] as ts.StringLiteral).text.trim().split('.');
421  if (preCheckResourceData(resourceData, resources, node.arguments[0].getStart(), previewLog)) {
422    const resourceType: number = RESOURCE_TYPE[resourceData[1]];
423    if (resourceType === undefined) {
424      transformLog.errors.push({
425        type: LogType.ERROR,
426        message: `The resource type ${resourceData[1]} is not supported.`,
427        pos: node.getStart()
428      });
429      return node;
430    }
431    const resourceValue: number = resources[resourceData[0]][resourceData[1]][resourceData[2]];
432    return createResourceParam(resourceValue, resourceType,
433      projectConfig.compileHar ? Array.from(node.arguments) : Array.from(node.arguments).slice(1));
434  }
435  return node;
436}
437
438function createResourceParam(resourceValue: number, resourceType: number, argsArr: ts.Expression[]):
439  ts.ObjectLiteralExpression {
440  if (projectConfig.compileHar) {
441    projectConfig.bundleName = '';
442    projectConfig.moduleName = '';
443    resourceValue = -1;
444  }
445
446  const propertyArray: Array<ts.PropertyAssignment> = [
447    ts.factory.createPropertyAssignment(
448      ts.factory.createStringLiteral(RESOURCE_NAME_ID),
449      ts.factory.createNumericLiteral(resourceValue)
450    ),
451    ts.factory.createPropertyAssignment(
452      ts.factory.createStringLiteral(RESOURCE_NAME_TYPE),
453      ts.factory.createNumericLiteral(resourceType)
454    ),
455    ts.factory.createPropertyAssignment(
456      ts.factory.createIdentifier(RESOURCE_NAME_PARAMS),
457      ts.factory.createArrayLiteralExpression(argsArr, false)
458    )
459  ];
460
461  if (projectConfig.bundleName || projectConfig.bundleName === '') {
462    propertyArray.push(ts.factory.createPropertyAssignment(
463      ts.factory.createStringLiteral(RESOURCE_NAME_BUNDLE),
464      ts.factory.createStringLiteral(projectConfig.bundleName)
465    ));
466  }
467
468  if (projectConfig.moduleName || projectConfig.moduleName === '') {
469    propertyArray.push(ts.factory.createPropertyAssignment(
470      ts.factory.createStringLiteral(RESOURCE_NAME_MODULE),
471      ts.factory.createStringLiteral(projectConfig.moduleName)
472    ));
473  }
474
475  const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression(
476    propertyArray, false);
477  return resourceParams;
478}
479
480function preCheckResourceData(resourceData: string[], resources: object, pos: number,
481  previewLog: {isAcceleratePreview: boolean, log: LogInfo[]}): boolean {
482  if (previewLog.isAcceleratePreview) {
483    return validateResourceData(resourceData, resources, pos, previewLog.log);
484  } else {
485    return validateResourceData(resourceData, resources, pos, transformLog.errors);
486  }
487}
488
489function validateResourceData(resourceData: string[], resources: object, pos: number, log: LogInfo[]): boolean {
490  if (resourceData.length !== 3) {
491    log.push({
492      type: LogType.ERROR,
493      message: 'The input parameter is not supported.',
494      pos: pos
495    });
496  } else if (!resources[resourceData[0]]) {
497    log.push({
498      type: LogType.ERROR,
499      message: `Unknown resource source '${resourceData[0]}'.`,
500      pos: pos
501    });
502  } else if (!resources[resourceData[0]][resourceData[1]]) {
503    log.push({
504      type: LogType.ERROR,
505      message: `Unknown resource type '${resourceData[1]}'.`,
506      pos: pos
507    });
508  } else if (!resources[resourceData[0]][resourceData[1]][resourceData[2]]) {
509    log.push({
510      type: LogType.ERROR,
511      message: `Unknown resource name '${resourceData[2]}'.`,
512      pos: pos
513    });
514  } else {
515    return true;
516  }
517  return false;
518}
519
520function isWorker(node: ts.Node): boolean {
521  return ts.isNewExpression(node) && ts.isPropertyAccessExpression(node.expression) &&
522    ts.isIdentifier(node.expression.name) &&
523    node.expression.name.escapedText.toString() === WORKER_OBJECT;
524}
525
526function processWorker(node: ts.NewExpression): ts.Node {
527  if (node.arguments.length && ts.isStringLiteral(node.arguments[0])) {
528    const args: ts.Expression[] = Array.from(node.arguments);
529    // @ts-ignore
530    const workerPath: string = node.arguments[0].text;
531    const stringNode: ts.StringLiteral = ts.factory.createStringLiteral(
532      workerPath.replace(/\.ts$/, '.js'));
533    args.splice(0, 1, stringNode);
534    return ts.factory.updateNewExpression(node, node.expression, node.typeArguments, args);
535  }
536  return node;
537}
538
539export function processAnimateTo(node: ts.CallExpression): ts.CallExpression {
540  return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression(
541    ts.factory.createIdentifier(GLOBAL_CONTEXT), ts.factory.createIdentifier(ATTRIBUTE_ANIMATETO)),
542  node.typeArguments, node.arguments);
543}
544
545function processExtend(node: ts.FunctionDeclaration, log: LogInfo[]): ts.FunctionDeclaration {
546  const componentName: string = isExtendFunction(node);
547  if (componentName && node.body && node.body.statements.length) {
548    const statementArray: ts.Statement[] = [];
549    let bodynode: ts.Block;
550    const attrSet: ts.CallExpression = node.body.statements[0].expression;
551    if (isOriginalExtend(node.body)) {
552      const changeCompName: ts.ExpressionStatement = ts.factory.createExpressionStatement(processExtendBody(attrSet));
553      bindComponentAttr(changeCompName as ts.ExpressionStatement,
554        ts.factory.createIdentifier(componentName), statementArray, log);
555    } else {
556      bodynode = ts.visitEachChild(node.body, traverseExtendExpression, contextGlobal);
557    }
558    let extendFunctionName: string;
559    if (node.name.getText().startsWith('__' + componentName + '__')) {
560      extendFunctionName = node.name.getText();
561    } else {
562      extendFunctionName = '__' + componentName + '__' + node.name.getText();
563      collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.escapedText.toString());
564    }
565    return ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers, node.asteriskToken,
566      ts.factory.createIdentifier(extendFunctionName), node.typeParameters,
567      node.parameters, ts.factory.createToken(ts.SyntaxKind.VoidKeyword), isOriginalExtend(node.body) ?
568        ts.factory.updateBlock(node.body, statementArray) : bodynode);
569  }
570  function traverseExtendExpression(node: ts.Node): ts.Node {
571    if (ts.isExpressionStatement(node) && isDollarNode(node)) {
572      const changeCompName: ts.ExpressionStatement =
573        ts.factory.createExpressionStatement(processExtendBody(node.expression, componentName));
574      const statementArray: ts.Statement[] = [];
575      bindComponentAttr(changeCompName, ts.factory.createIdentifier(componentName), statementArray, []);
576      return ts.factory.createBlock(statementArray, true);
577    }
578    return ts.visitEachChild(node, traverseExtendExpression, contextGlobal);
579  }
580}
581
582function processConcurrent(node: ts.FunctionDeclaration): ts.FunctionDeclaration {
583  if (node.body) {
584    const statementArray: ts.Statement[]
585      = [ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use concurrent')),
586        ...node.body.statements];
587    return ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers, node.asteriskToken, node.name,
588      node.typeParameters, node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray));
589  }
590  return node;
591}
592
593export function isOriginalExtend(node: ts.Block): boolean {
594  let innerNode: ts.Node = node.statements[0];
595  if (node.statements.length === 1 && ts.isExpressionStatement(innerNode)) {
596    while (innerNode.expression) {
597      innerNode = innerNode.expression;
598    }
599    if (ts.isIdentifier(innerNode) && innerNode.pos && innerNode.end && innerNode.pos === innerNode.end &&
600      innerNode.escapedText.toString().match(/Instance$/)) {
601      return true;
602    }
603  }
604  return false;
605}
606
607function isDollarNode(node: ts.ExpressionStatement): boolean {
608  let innerNode: ts.Node = node;
609  while (innerNode.expression) {
610    innerNode = innerNode.expression;
611  }
612  if (ts.isIdentifier(innerNode) && innerNode.getText() === '$') {
613    return true;
614  } else {
615    return false;
616  }
617}
618
619function processExtendBody(node: ts.Node, componentName?: string): ts.Expression {
620  switch (node.kind) {
621    case ts.SyntaxKind.CallExpression:
622      return ts.factory.createCallExpression(processExtendBody(node.expression, componentName),
623        undefined, node.arguments);
624    case ts.SyntaxKind.PropertyAccessExpression:
625      return ts.factory.createPropertyAccessExpression(
626        processExtendBody(node.expression, componentName), node.name);
627    case ts.SyntaxKind.Identifier:
628      if (!componentName) {
629        return ts.factory.createIdentifier(node.escapedText.toString().replace(INSTANCE, ''));
630      } else {
631        return ts.factory.createIdentifier(componentName);
632      }
633  }
634}
635
636export function collectExtend(collectionSet: Map<string, Set<string>>, component: string, attribute: string): void {
637  if (collectionSet.has(component)) {
638    collectionSet.get(component).add(attribute);
639  } else {
640    collectionSet.set(component, new Set([attribute]));
641  }
642}
643
644export function isExtendFunction(node: ts.FunctionDeclaration): string {
645  if (node.decorators && node.decorators.length) {
646    for (let i = 0, len = node.decorators.length; i < len; i++) {
647      if (node.decorators[i].expression && node.decorators[i].expression.expression &&
648        node.decorators[i].expression.expression.escapedText.toString() === CHECK_COMPONENT_EXTEND_DECORATOR &&
649        node.decorators[i].expression.arguments) {
650        return node.decorators[i].expression.arguments[0].escapedText.toString();
651      }
652    }
653  }
654  return null;
655}
656
657function createEntryNode(node: ts.SourceFile, context: ts.TransformationContext,
658  entryNodeKey: ts.Expression, id: number): ts.SourceFile {
659  let cardRelativePath: string = undefined;
660  if (projectConfig && projectConfig.cardObj) {
661    cardRelativePath = projectConfig.cardObj[resourceFileName];
662  }
663  if (componentCollection.previewComponent.length === 0 || !projectConfig.isPreview) {
664    if (componentCollection.entryComponent) {
665      if (!partialUpdateConfig.partialUpdateMode) {
666        const entryNode: ts.ExpressionStatement =
667          createEntryFunction(componentCollection.entryComponent, context,
668            cardRelativePath, entryNodeKey, id) as ts.ExpressionStatement;
669        return context.factory.updateSourceFile(node, [...node.statements, entryNode]);
670      } else {
671        const entryNodes: ts.ExpressionStatement[] =
672          createEntryFunction(componentCollection.entryComponent, context,
673            cardRelativePath, entryNodeKey, id) as ts.ExpressionStatement[];
674        return entryNodes ?
675          context.factory.updateSourceFile(node, [...node.statements, ...entryNodes]) :
676          context.factory.updateSourceFile(node, [...node.statements]);
677      }
678    } else {
679      return node;
680    }
681  } else {
682    const statementsArray: ts.Statement =
683      createPreviewComponentFunction(componentCollection.entryComponent, context, cardRelativePath, entryNodeKey, id);
684    return context.factory.updateSourceFile(node, [...node.statements, statementsArray]);
685  }
686}
687
688function createEntryFunction(name: string, context: ts.TransformationContext, cardRelativePath: string,
689  entryNodeKey: ts.Expression, id: number): ts.ExpressionStatement | ts.ExpressionStatement[] {
690  const newArray: ts.Expression[] = [
691    context.factory.createStringLiteral(id.toString()),
692    context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED),
693    context.factory.createObjectLiteralExpression([], false)
694  ];
695  const localStorageName: string = addStorageParam(name);
696  if (localStorageName) {
697    newArray.push(entryNodeKey);
698  }
699  const newExpressionParams: any[] = [
700    context.factory.createNewExpression(
701      context.factory.createIdentifier(name),undefined, newArray)];
702  addCardStringliteral(newExpressionParams, context, cardRelativePath);
703  if (!partialUpdateConfig.partialUpdateMode) {
704    const newExpressionStatement: ts.ExpressionStatement =
705      context.factory.createExpressionStatement(context.factory.createCallExpression(
706        context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME :
707          PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams));
708    return newExpressionStatement;
709  } else {
710    return [
711      createStartGetAccessRecording(context),
712      createLoadDocument(context, name, cardRelativePath, localStorageName, entryNodeKey),
713      createStopGetAccessRecording(context)
714    ];
715  }
716}
717
718function createStartGetAccessRecording(context: ts.TransformationContext): ts.ExpressionStatement {
719  return context.factory.createExpressionStatement(
720    context.factory.createCallExpression(
721      context.factory.createPropertyAccessExpression(
722        context.factory.createIdentifier(VIEWSTACKPROCESSOR),
723        context.factory.createIdentifier(STARTGETACCESSRECORDINGFOR)
724      ),
725      undefined,
726      [context.factory.createCallExpression(
727        context.factory.createPropertyAccessExpression(
728          context.factory.createIdentifier(VIEWSTACKPROCESSOR),
729          context.factory.createIdentifier(ALLOCATENEWELMETIDFORNEXTCOMPONENT)
730        ),
731        undefined,
732        []
733      )]
734    )
735  );
736}
737
738function createLoadDocument(context: ts.TransformationContext, name: string,
739  cardRelativePath: string, localStorageName: string, entryNodeKey: ts.Expression): ts.ExpressionStatement {
740  const newArray: ts.Expression[] = [
741    context.factory.createIdentifier('undefined'),
742    context.factory.createObjectLiteralExpression([], false)
743  ];
744  if (localStorageName) {
745    newArray.push(entryNodeKey);
746  }
747  const newExpressionParams: any[] = [
748    context.factory.createNewExpression(
749      context.factory.createIdentifier(name),
750      undefined, newArray)];
751  addCardStringliteral(newExpressionParams, context, cardRelativePath);
752  return context.factory.createExpressionStatement(
753    context.factory.createCallExpression(
754      context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME :
755        PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams)
756  );
757}
758
759function createStopGetAccessRecording(context: ts.TransformationContext): ts.ExpressionStatement {
760  return context.factory.createExpressionStatement(
761    context.factory.createCallExpression(
762      context.factory.createPropertyAccessExpression(
763        context.factory.createIdentifier(VIEWSTACKPROCESSOR),
764        context.factory.createIdentifier(STOPGETACCESSRECORDING)
765      ),
766      undefined,
767      []
768    )
769  );
770}
771
772function addStorageParam(name: string): string {
773  let localStorageName: string;
774  const localStorageNum: number = (localStorageLinkCollection.get(name) || new Set()).size +
775    (localStoragePropCollection.get(name) || new Set()).size;
776  if (componentCollection.entryComponent === name && componentCollection.localStorageName) {
777    localStorageName = componentCollection.localStorageName;
778  } else if (componentCollection.entryComponent === name && !componentCollection.localStorageName
779    && localStorageNum) {
780    transformLog.errors.push({
781      type: LogType.WARN,
782      message: `@Entry should have a parameter, like '@Entry (storage)'.`,
783      pos: componentCollection.entryComponentPos
784    });
785    return;
786  }
787  return localStorageName;
788}
789
790function createPreviewComponentFunction(name: string, context: ts.TransformationContext,
791  cardRelativePath: string, entryNodeKey: ts.Expression, id: number): ts.Statement {
792  const newArray: ts.Expression[] = partialUpdateConfig.partialUpdateMode ?
793    [
794      context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED),
795      context.factory.createObjectLiteralExpression([], false)
796    ] :
797    [
798      context.factory.createStringLiteral(id.toString()),
799      context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED),
800      context.factory.createObjectLiteralExpression([], false)
801    ];
802  if (addStorageParam(name)) {
803    newArray.push(entryNodeKey);
804  }
805  const argsArr: ts.Expression[] = [];
806  componentCollection.previewComponent.forEach(componentName => {
807    const newExpression: ts.Expression = context.factory.createNewExpression(
808      context.factory.createIdentifier(componentName),
809      undefined,
810      newArray
811    );
812    argsArr.push(context.factory.createStringLiteral(componentName));
813    argsArr.push(newExpression);
814  });
815  const newExpressionParams: any[] = name ? [context.factory.createNewExpression(
816    context.factory.createIdentifier(name), undefined, newArray)] : [];
817  addCardStringliteral(newExpressionParams, context, cardRelativePath);
818  const ifStatement: ts.Statement = context.factory.createIfStatement(
819    context.factory.createCallExpression(
820      context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME),
821      undefined,
822      []
823    ),
824    context.factory.createBlock(
825      [context.factory.createExpressionStatement(context.factory.createCallExpression(
826        context.factory.createIdentifier(PREVIEW_COMPONENT_FUNCTION_NAME),
827        undefined,
828        []
829      ))],
830      true
831    ),
832    context.factory.createBlock(
833      [
834        context.factory.createExpressionStatement(context.factory.createCallExpression(
835          context.factory.createIdentifier(STORE_PREVIEW_COMPONENTS),
836          undefined,
837          [
838            context.factory.createNumericLiteral(componentCollection.previewComponent.length),
839            ...argsArr
840          ]
841        )),
842        name && partialUpdateConfig.partialUpdateMode ? createStartGetAccessRecording(context) : undefined,
843        name ? context.factory.createExpressionStatement(context.factory.createCallExpression(
844          context.factory.createIdentifier(cardRelativePath ? CARD_ENTRY_FUNCTION_NAME :
845            PAGE_ENTRY_FUNCTION_NAME), undefined, newExpressionParams
846        )) : undefined,
847        name && partialUpdateConfig.partialUpdateMode ? createStopGetAccessRecording(context) : undefined
848      ],
849      true
850    )
851  );
852  return ifStatement;
853}
854
855export function resetLog(): void {
856  transformLog.errors = [];
857}
858
859function addCardStringliteral(newExpressionParams: any[], context: ts.TransformationContext,
860  cardRelativePath: string): void {
861  if (cardRelativePath) {
862    newExpressionParams.push(context.factory.createStringLiteral(
863      projectConfig.bundleName + '/' + projectConfig.moduleName + '/' +
864      cardRelativePath));
865  }
866}
867
868export function validatorCard(log: any[], type: number, pos: number,
869  name: string = ''): void {
870  if (projectConfig && projectConfig.cardObj && resourceFileName
871    && projectConfig.cardObj[resourceFileName]) {
872    const logInfo: object = {
873      type: LogType.ERROR,
874      message: '',
875      pos: pos
876    };
877    switch (type) {
878      case CARD_LOG_TYPE_COMPONENTS:
879        logInfo.message = `Card page cannot use the component ${name}.`;
880        break;
881      case CARD_LOG_TYPE_DECORATORS:
882        logInfo.message = `Card page cannot use ${name}`;
883        break;
884      case CARD_LOG_TYPE_IMPORT:
885        logInfo.message = `Card page cannot use import.`;
886        break;
887    }
888    log.push(logInfo);
889  }
890}
891