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