• 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 { componentCollection } from './validate_ui_syntax';
20import { processComponentClass } from './process_component_class';
21import processImport from './process_import';
22import {
23  PAGE_ENTRY_FUNCTION_NAME,
24  PREVIEW_COMPONENT_FUNCTION_NAME,
25  STORE_PREVIEW_COMPONENTS,
26  GET_PREVIEW_FLAG_FUNCTION_NAME,
27  COMPONENT_CONSTRUCTOR_UNDEFINED,
28  BUILD_ON,
29  COMPONENT_BUILDER_DECORATOR,
30  COMPONENT_EXTEND_DECORATOR,
31  COMPONENT_STYLES_DECORATOR,
32  RESOURCE,
33  RESOURCE_TYPE,
34  WORKER_OBJECT,
35  RESOURCE_NAME_ID,
36  RESOURCE_NAME_TYPE,
37  RESOURCE_NAME_PARAMS,
38  RESOURCE_RAWFILE,
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} from './pre_define';
48import {
49  componentInfo,
50  LogInfo,
51  LogType,
52  hasDecorator,
53  FileLog
54} from './utils';
55import {
56  processComponentBlock,
57  bindComponentAttr
58} from './process_component_build';
59import {
60  BUILDIN_STYLE_NAMES,
61  CUSTOM_BUILDER_METHOD,
62  EXTEND_ATTRIBUTE,
63  INNER_STYLE_FUNCTION,
64  GLOBAL_STYLE_FUNCTION,
65  INTERFACE_NODE_SET
66} from './component_map';
67import {
68  localStorageLinkCollection,
69  localStoragePropCollection
70} from './validate_ui_syntax'
71import {
72  resources,
73  projectConfig
74} from '../main';
75import { createCustomComponentNewExpression, createViewCreate } from './process_component_member';
76
77export const transformLog: FileLog = new FileLog();
78export let contextGlobal: ts.TransformationContext;
79
80export function processUISyntax(program: ts.Program, ut = false): Function {
81  return (context: ts.TransformationContext) => {
82    contextGlobal = context;
83    let pagesDir: string;
84    return (node: ts.SourceFile) => {
85      pagesDir = path.resolve(path.dirname(node.fileName));
86      if (process.env.compiler === BUILD_ON) {
87        if (!ut && (process.env.compileMode !== 'moduleJson' &&
88          path.resolve(node.fileName) === path.resolve(projectConfig.projectPath, 'app.ets') ||
89          /\.ts$/.test(node.fileName))) {
90          node = ts.visitEachChild(node, processResourceNode, context);
91          return node;
92        }
93        transformLog.sourceFile = node;
94        node = createEntryNode(node, context);
95        node = ts.visitEachChild(node, processAllNodes, context);
96        GLOBAL_STYLE_FUNCTION.forEach((block, styleName) => {
97          BUILDIN_STYLE_NAMES.delete(styleName);
98        });
99        GLOBAL_STYLE_FUNCTION.clear();
100        const statements: ts.Statement[] = Array.from(node.statements);
101        INTERFACE_NODE_SET.forEach(item => {
102          statements.unshift(item);
103        });
104        node = ts.factory.updateSourceFile(node, statements);
105        INTERFACE_NODE_SET.clear();
106        return node;
107      } else {
108        return node;
109      }
110    };
111    function processAllNodes(node: ts.Node): ts.Node {
112      if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) ||
113        ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
114        processImport(node, pagesDir, transformLog.errors);
115      } else if (ts.isStructDeclaration(node)) {
116        componentCollection.currentClassName = node.name.getText();
117        node = processComponentClass(node, context, transformLog.errors, program);
118        componentCollection.currentClassName = null;
119        INNER_STYLE_FUNCTION.forEach((block, styleName) => {
120          BUILDIN_STYLE_NAMES.delete(styleName);
121        });
122        INNER_STYLE_FUNCTION.clear();
123      } else if (ts.isFunctionDeclaration(node)) {
124        if (hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) {
125          node = processExtend(node, transformLog.errors);
126        } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR) && node.name && node.body &&
127          ts.isBlock(node.body)) {
128          CUSTOM_BUILDER_METHOD.add(node.name.getText());
129          node = ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers,
130            node.asteriskToken, node.name, node.typeParameters, node.parameters, node.type,
131            processComponentBlock(node.body, false, transformLog.errors));
132        } else if (hasDecorator(node, COMPONENT_STYLES_DECORATOR)) {
133          if (node.parameters.length === 0) {
134            node = undefined;
135          } else {
136            transformLog.errors.push({
137              type: LogType.ERROR,
138              message: `@Styles can't have parameters.`,
139              pos: node.getStart()
140            });
141          }
142        }
143      } else if (isResource(node)) {
144        node = processResourceData(node as ts.CallExpression);
145      } else if (isWorker(node)) {
146        node = processWorker(node as ts.NewExpression);
147      } else if (isAnimateTo(node)) {
148        node = processAnimateTo(node as ts.CallExpression);
149      } else if (isCustomDialogController(node)) {
150        node = createCustomDialogController(node.parent, node, transformLog.errors);
151      }
152      return ts.visitEachChild(node, processAllNodes, context);
153    }
154    function processResourceNode(node: ts.Node): ts.Node {
155      if (isResource(node)) {
156        node = processResourceData(node as ts.CallExpression);
157      }
158      return ts.visitEachChild(node, processResourceNode, context);
159    }
160  };
161}
162
163function isCustomDialogController(node: ts.Expression) {
164  const tempParent: ts.Node = node.parent;
165  // @ts-ignore
166  if (!node.parent && node.original) {
167    // @ts-ignore
168    node.parent = node.original.parent;
169  }
170  if (ts.isNewExpression(node) && node.expression && ts.isIdentifier(node.expression) &&
171    node.expression.escapedText.toString() === SET_CONTROLLER_CTR_TYPE) {
172    return true;
173  } else {
174    // @ts-ignore
175    node.parent = tempParent;
176    return false;
177  }
178}
179
180function createCustomDialogController(parent: ts.Expression, node: ts.NewExpression,
181  log: LogInfo[]): ts.NewExpression {
182  if (node.arguments && node.arguments.length === 1 &&
183    ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) {
184    const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => {
185      if (isCustomDialogControllerPropertyAssignment(item, log)) {
186        item = processCustomDialogControllerPropertyAssignment(parent, item as ts.PropertyAssignment);
187      }
188      return item;
189    });
190    return ts.factory.createNewExpression(node.expression, node.typeArguments,
191      [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]);
192  }
193}
194
195function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike,
196  log: LogInfo[]): boolean {
197  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) &&
198    node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) {
199    if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) &&
200      componentCollection.customDialogs.has(node.initializer.expression.getText())) {
201      return true;
202    } else {
203      validateCustomDialogControllerBuilderInit(node, log);
204    }
205  }
206}
207
208function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike,
209  log: LogInfo[]): void {
210  log.push({
211    type: LogType.ERROR,
212    message: 'The builder should be initialized with a @CustomDialog Component.',
213    pos: node.getStart()
214  });
215}
216
217function processCustomDialogControllerPropertyAssignment(parent: ts.Expression,
218  node: ts.PropertyAssignment): ts.PropertyAssignment {
219  if (ts.isCallExpression(node.initializer)) {
220    return ts.factory.updatePropertyAssignment(node, node.name,
221      processCustomDialogControllerBuilder(parent, node.initializer));
222  }
223}
224
225function processCustomDialogControllerBuilder(parent: ts.Expression,
226  node: ts.CallExpression): ts.ArrowFunction {
227  const newExp: ts.Expression = createCustomComponentNewExpression(node);
228  const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG);
229  return createCustomComponentBuilderArrowFunction(parent, jsDialog, newExp);
230}
231
232function createCustomComponentBuilderArrowFunction(parent: ts.Expression,
233  jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction {
234  let mountNodde: ts.PropertyAccessExpression;
235  if (ts.isBinaryExpression(parent)) {
236    mountNodde = parent.left;
237  } else if (ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent)) {
238    mountNodde = ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
239      parent.name as ts.Identifier);
240  }
241  return ts.factory.createArrowFunction(
242    undefined,
243    undefined,
244    [],
245    undefined,
246    ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
247    ts.factory.createBlock(
248      [
249        ts.factory.createVariableStatement(
250          undefined,
251          ts.factory.createVariableDeclarationList(
252            [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)],
253            ts.NodeFlags.Let
254          )
255        ),
256        ts.factory.createExpressionStatement(
257          ts.factory.createCallExpression(
258            ts.factory.createPropertyAccessExpression(
259              jsDialog,
260              ts.factory.createIdentifier(SET_CONTROLLER_METHOD)
261            ),
262            undefined,
263            [mountNodde]
264          )
265        ),
266        ts.factory.createExpressionStatement(createViewCreate(jsDialog))
267      ],
268      true
269    )
270  );
271}
272
273function isResource(node: ts.Node): boolean {
274  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
275    (node.expression.escapedText.toString() === RESOURCE ||
276    node.expression.escapedText.toString() === RESOURCE_RAWFILE) && node.arguments.length > 0;
277}
278
279function isAnimateTo(node: ts.Node): boolean {
280  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) &&
281    node.expression.escapedText.toString() === ATTRIBUTE_ANIMATETO;
282}
283
284function processResourceData(node: ts.CallExpression): ts.Node {
285  if (ts.isStringLiteral(node.arguments[0])) {
286    if (node.expression.getText() === RESOURCE_RAWFILE) {
287      return createResourceParam(0, RESOURCE_TYPE.rawfile, [node.arguments[0]]);
288    } else {
289      return getResourceDataNode(node);
290    }
291  }
292  return node;
293}
294
295function getResourceDataNode(node: ts.CallExpression): ts.Node {
296  const resourceData: string[] = (node.arguments[0] as ts.StringLiteral).text.trim().split('.');
297  if (validateResourceData(resourceData, resources, node.arguments[0].getStart())) {
298    const resourceType: number = RESOURCE_TYPE[resourceData[1]];
299    if (resourceType === undefined) {
300      transformLog.errors.push({
301        type: LogType.ERROR,
302        message: `The resource type ${resourceData[1]} is not supported.`,
303        pos: node.getStart()
304      });
305      return node;
306    }
307    const resourceValue: number = resources[resourceData[0]][resourceData[1]][resourceData[2]];
308    return createResourceParam(resourceValue, resourceType,
309      Array.from(node.arguments).slice(1));
310  }
311  return node;
312}
313
314function createResourceParam(resourceValue: number, resourceType: number, argsArr: ts.Expression[]):
315  ts.ObjectLiteralExpression {
316  const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression(
317    [
318      ts.factory.createPropertyAssignment(
319        ts.factory.createStringLiteral(RESOURCE_NAME_ID),
320        ts.factory.createNumericLiteral(resourceValue)
321      ),
322      ts.factory.createPropertyAssignment(
323        ts.factory.createStringLiteral(RESOURCE_NAME_TYPE),
324        ts.factory.createNumericLiteral(resourceType)
325      ),
326      ts.factory.createPropertyAssignment(
327        ts.factory.createIdentifier(RESOURCE_NAME_PARAMS),
328        ts.factory.createArrayLiteralExpression(
329          argsArr,
330          false
331        )
332      )
333    ],
334    false
335  );
336  return resourceParams;
337}
338
339function validateResourceData(resourceData: string[], resources: object, pos: number): boolean {
340  if (resourceData.length !== 3) {
341    transformLog.errors.push({
342      type: LogType.ERROR,
343      message: 'The input parameter is not supported.',
344      pos: pos
345    });
346  } else if (!resources[resourceData[0]]) {
347    transformLog.errors.push({
348      type: LogType.ERROR,
349      message: `Unknown resource source '${resourceData[0]}'.`,
350      pos: pos
351    });
352  } else if (!resources[resourceData[0]][resourceData[1]]) {
353    transformLog.errors.push({
354      type: LogType.ERROR,
355      message: `Unknown resource type '${resourceData[1]}'.`,
356      pos: pos
357    });
358  } else if (!resources[resourceData[0]][resourceData[1]][resourceData[2]]) {
359    transformLog.errors.push({
360      type: LogType.ERROR,
361      message: `Unknown resource name '${resourceData[2]}'.`,
362      pos: pos
363    });
364  } else {
365    return true;
366  }
367  return false;
368}
369
370function isWorker(node: ts.Node): boolean {
371  return ts.isNewExpression(node) && ts.isPropertyAccessExpression(node.expression) &&
372    ts.isIdentifier(node.expression.name) &&
373    node.expression.name.escapedText.toString() === WORKER_OBJECT;
374}
375
376function processWorker(node: ts.NewExpression): ts.Node {
377  if (node.arguments.length && ts.isStringLiteral(node.arguments[0])) {
378    const args: ts.Expression[] = Array.from(node.arguments);
379    // @ts-ignore
380    const workerPath: string = node.arguments[0].text;
381    const stringNode: ts.StringLiteral = ts.factory.createStringLiteral(
382      workerPath.replace(/\.ts$/, '.js'));
383    args.splice(0, 1, stringNode);
384    return ts.factory.updateNewExpression(node, node.expression, node.typeArguments, args);
385  }
386  return node;
387}
388
389function processAnimateTo(node: ts.CallExpression): ts.CallExpression {
390  return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression(
391    ts.factory.createIdentifier(GLOBAL_CONTEXT), ts.factory.createIdentifier(ATTRIBUTE_ANIMATETO)),
392  node.typeArguments, node.arguments);
393}
394
395function processExtend(node: ts.FunctionDeclaration, log: LogInfo[]): ts.FunctionDeclaration {
396  const componentName: string = isExtendFunction(node);
397  if (componentName && node.body && node.body.statements.length) {
398    const statementArray: ts.Statement[] = [];
399    const attrSet: ts.CallExpression = node.body.statements[0].expression;
400    const changeCompName: ts.ExpressionStatement = ts.factory.createExpressionStatement(processExtendBody(attrSet));
401    bindComponentAttr(changeCompName as ts.ExpressionStatement,
402      ts.factory.createIdentifier(componentName), statementArray, log);
403    let extendFunctionName: string;
404    if (node.name.getText().startsWith('__' + componentName + '__')) {
405      extendFunctionName = node.name.getText();
406    } else {
407      extendFunctionName = '__' + componentName + '__' + node.name.getText();
408      collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.escapedText.toString());
409    }
410    return ts.factory.updateFunctionDeclaration(node, undefined, node.modifiers, node.asteriskToken,
411      ts.factory.createIdentifier(extendFunctionName), node.typeParameters,
412      node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray));
413  }
414}
415
416function processExtendBody(node: ts.Node): ts.Expression {
417  switch (node.kind) {
418    case ts.SyntaxKind.CallExpression:
419      return ts.factory.createCallExpression(processExtendBody(node.expression), undefined, node.arguments);
420    case ts.SyntaxKind.PropertyAccessExpression:
421      return ts.factory.createPropertyAccessExpression(processExtendBody(node.expression), node.name);
422    case ts.SyntaxKind.Identifier:
423      return ts.factory.createIdentifier(node.escapedText.toString().replace(INSTANCE, ''));
424  }
425}
426
427export function collectExtend(collectionSet: Map<string, Set<string>>, component: string, attribute: string): void {
428  if (collectionSet.has(component)) {
429    collectionSet.get(component).add(attribute);
430  } else {
431    collectionSet.set(component, new Set([attribute]));
432  }
433}
434
435export function isExtendFunction(node: ts.FunctionDeclaration): string {
436  if (node.decorators && node.decorators[0].expression && node.decorators[0].expression.expression &&
437    node.decorators[0].expression.expression.escapedText.toString() === CHECK_COMPONENT_EXTEND_DECORATOR &&
438    node.decorators[0].expression.arguments) {
439    return node.decorators[0].expression.arguments[0].escapedText.toString();
440  } else {
441    return null;
442  }
443}
444
445function createEntryNode(node: ts.SourceFile, context: ts.TransformationContext): ts.SourceFile {
446  if (componentCollection.previewComponent.size === 0 || !projectConfig.isPreview) {
447    if (componentCollection.entryComponent) {
448      const entryNode: ts.ExpressionStatement =
449        createEntryFunction(componentCollection.entryComponent, context);
450      return context.factory.updateSourceFile(node, [...node.statements, entryNode]);
451    } else {
452      return node;
453    }
454  } else {
455    const statementsArray: ts.Statement =
456      createPreviewComponentFunction(componentCollection.entryComponent, context);
457    return context.factory.updateSourceFile(node, [...node.statements, statementsArray]);
458  }
459}
460
461function createEntryFunction(name: string, context: ts.TransformationContext)
462  : ts.ExpressionStatement {
463  let localStorageName: string;
464  const localStorageNum: number = localStorageLinkCollection.get(name).size +
465    localStoragePropCollection.get(name).size;
466  if (componentCollection.entryComponent === name && componentCollection.localStorageName &&
467    localStorageNum) {
468    localStorageName = componentCollection.localStorageName;
469  } else if (componentCollection.entryComponent === name && !componentCollection.localStorageName
470    && localStorageNum) {
471    transformLog.errors.push({
472      type: LogType.ERROR,
473      message: `@Entry should have a parameter, like '@Entry (storage)'.`,
474      pos: componentCollection.entryComponentPos
475    });
476    return;
477  }
478  const newArray: ts.Expression[] = [
479    context.factory.createStringLiteral((++componentInfo.id).toString()),
480    context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED),
481    context.factory.createObjectLiteralExpression([], false)
482  ]
483  if (localStorageName) {
484    newArray.push(context.factory.createIdentifier(localStorageName))
485  }
486  const newExpressionStatement: ts.ExpressionStatement =
487    context.factory.createExpressionStatement(context.factory.createCallExpression(
488      context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME), undefined,
489      [context.factory.createNewExpression(context.factory.createIdentifier(name),
490        undefined, newArray)]));
491  return newExpressionStatement;
492}
493
494function createPreviewComponentFunction(name: string, context: ts.TransformationContext)
495  : ts.Statement {
496  const newArray: ts.Expression[] = [
497    context.factory.createStringLiteral((++componentInfo.id).toString()),
498    context.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED),
499    context.factory.createObjectLiteralExpression([], false)
500  ];
501  const argsArr: ts.Expression[] = [];
502  componentCollection.previewComponent.forEach(componentName => {
503    const newExpression: ts.Expression = context.factory.createNewExpression(
504      context.factory.createIdentifier(componentName),
505      undefined,
506      newArray
507    );
508    argsArr.push(context.factory.createStringLiteral(componentName));
509    argsArr.push(newExpression);
510  });
511  const ifStatement: ts.Statement = context.factory.createIfStatement(
512    context.factory.createBinaryExpression(
513      context.factory.createTypeOfExpression(context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME)),
514      context.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken),
515      context.factory.createStringLiteral(COMPONENT_CONSTRUCTOR_UNDEFINED)
516    ),
517    context.factory.createBlock(
518      [
519        context.factory.createIfStatement(
520          context.factory.createCallExpression(
521            context.factory.createIdentifier(GET_PREVIEW_FLAG_FUNCTION_NAME),
522            undefined,
523            []
524          ),
525          context.factory.createBlock(
526            [context.factory.createExpressionStatement(context.factory.createCallExpression(
527              context.factory.createIdentifier(PREVIEW_COMPONENT_FUNCTION_NAME),
528              undefined,
529              []
530            ))],
531            true
532          ),
533          context.factory.createBlock(
534            [
535              context.factory.createExpressionStatement(context.factory.createCallExpression(
536                context.factory.createIdentifier(STORE_PREVIEW_COMPONENTS),
537                undefined,
538                [
539                  context.factory.createNumericLiteral(componentCollection.previewComponent.size),
540                  ...argsArr
541                ]
542              )),
543              name ? context.factory.createExpressionStatement(context.factory.createCallExpression(
544                context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME),
545                undefined,
546                [context.factory.createNewExpression(
547                  context.factory.createIdentifier(name),
548                  undefined,
549                  newArray
550                )]
551              )) : undefined
552            ],
553            true
554          )
555        )
556      ],
557      true
558    ),
559    context.factory.createBlock(
560      [
561        context.factory.createExpressionStatement(context.factory.createCallExpression(
562        context.factory.createIdentifier(PAGE_ENTRY_FUNCTION_NAME),
563        undefined,
564        [context.factory.createNewExpression(
565          context.factory.createIdentifier(name ? name : Array.from(componentCollection.previewComponent)[0]),
566          undefined,
567          newArray
568        )]
569        ))
570      ],
571      true
572    )
573  )
574  return ifStatement;
575}
576
577export function resetLog(): void {
578  transformLog.errors = [];
579}
580