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';
17
18import {
19  COMPONENT_CONSTRUCTOR_ID,
20  COMPONENT_CONSTRUCTOR_PARENT,
21  COMPONENT_CONSTRUCTOR_PARAMS,
22  COMPONENT_CONSTRUCTOR_UPDATE_PARAMS,
23  COMPONENT_CONSTRUCTOR_INITIAL_PARAMS,
24  COMPONENT_WATCH_FUNCTION,
25  BASE_COMPONENT_NAME,
26  INTERFACE_NAME_SUFFIX,
27  COMPONENT_CONSTRUCTOR_LOCALSTORAGE,
28  BASE_COMPONENT_NAME_PU,
29  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU,
30  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU,
31  ELMTID,
32  COMPONENT_PARAMS_LAMBDA_FUNCTION,
33  COMPONENT_IF_UNDEFINED,
34  CUSTOM_COMPONENT_EXTRAINFO
35} from './pre_define';
36import { partialUpdateConfig } from '../main';
37import createAstNodeUtils from './create_ast_node_utils';
38
39export function getInitConstructor(members: ts.NodeArray<ts.Node>, parentComponentName: ts.Identifier
40): ts.ConstructorDeclaration {
41  let ctorNode: any = members.find(item => {
42    return ts.isConstructorDeclaration(item);
43  });
44  if (ctorNode) {
45    ctorNode = updateConstructor(ctorNode, [], [], [], true);
46  }
47  return initConstructorParams(ctorNode, parentComponentName);
48}
49
50export function updateConstructor(ctorNode: ts.ConstructorDeclaration, para: ts.ParameterDeclaration[],
51  addStatements: ts.Statement[], decoratorComponentParent: ts.IfStatement[], isSuper: boolean = false,
52  isAdd: boolean = false, parentComponentName?: ts.Identifier): ts.ConstructorDeclaration {
53  let modifyPara: ts.ParameterDeclaration[];
54  if (para && para.length) {
55    modifyPara = Array.from(ctorNode.parameters);
56    if (modifyPara) {
57      modifyPara.push(...para);
58    }
59  }
60  let modifyBody: ts.Statement[];
61  if (addStatements && addStatements.length && ctorNode) {
62    modifyBody = Array.from(ctorNode.body.statements);
63    if (modifyBody) {
64      if (isSuper) {
65        modifyBody.unshift(...addStatements);
66        if (decoratorComponentParent && decoratorComponentParent.length) {
67          modifyBody.push(...decoratorComponentParent);
68        }
69      } else {
70        modifyBody.push(...addStatements);
71      }
72    }
73  }
74  if (ctorNode) {
75    let ctorPara: ts.ParameterDeclaration[] | ts.NodeArray<ts.ParameterDeclaration> =
76      modifyPara || ctorNode.parameters;
77    if (isAdd) {
78      ctorPara = addParamsType(ctorNode, modifyPara, parentComponentName);
79    }
80    ctorNode = ts.factory.updateConstructorDeclaration(ctorNode,
81      ts.getModifiers(ctorNode), modifyPara || ctorNode.parameters,
82      ts.factory.createBlock(modifyBody || ctorNode.body.statements, true));
83  }
84  return ctorNode;
85}
86
87function initConstructorParams(node: ts.ConstructorDeclaration, parentComponentName: ts.Identifier):
88  ts.ConstructorDeclaration {
89  if (!ts.isIdentifier(parentComponentName)) {
90    return node;
91  }
92  const paramNames: Set<string> = !partialUpdateConfig.partialUpdateMode ? new Set([
93    COMPONENT_CONSTRUCTOR_ID,
94    COMPONENT_CONSTRUCTOR_PARENT,
95    COMPONENT_CONSTRUCTOR_PARAMS,
96    COMPONENT_CONSTRUCTOR_LOCALSTORAGE
97  ]) : new Set([
98    COMPONENT_CONSTRUCTOR_PARENT,
99    COMPONENT_CONSTRUCTOR_PARAMS,
100    COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU,
101    ELMTID,
102    COMPONENT_PARAMS_LAMBDA_FUNCTION,
103    CUSTOM_COMPONENT_EXTRAINFO
104  ]);
105  const newParameters: ts.ParameterDeclaration[] = Array.from(node.parameters);
106  if (newParameters.length !== 0) {
107    // @ts-ignore
108    newParameters.splice(0, newParameters.length);
109  }
110  paramNames.forEach((paramName: string) => {
111    newParameters.push(ts.factory.createParameterDeclaration(undefined, undefined,
112      ts.factory.createIdentifier(paramName), undefined, undefined,
113      paramName === ELMTID || paramName === COMPONENT_PARAMS_LAMBDA_FUNCTION ? paramName === ELMTID ?
114        ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral('1')) :
115        ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED) : undefined));
116  });
117
118  return ts.factory.updateConstructorDeclaration(node, ts.getModifiers(node), newParameters, node.body);
119}
120
121function addParamsType(ctorNode: ts.ConstructorDeclaration, modifyPara: ts.ParameterDeclaration[],
122  parentComponentName: ts.Identifier): ts.ParameterDeclaration[] {
123  const tsPara: ts.ParameterDeclaration[] | ts.NodeArray<ts.ParameterDeclaration> =
124    modifyPara || ctorNode.parameters;
125  const newTSPara: ts.ParameterDeclaration[] = [];
126  tsPara.forEach((item) => {
127    let parameter: ts.ParameterDeclaration = item;
128    switch (item.name.escapedText) {
129      case COMPONENT_CONSTRUCTOR_ID:
130        parameter = ts.factory.updateParameterDeclaration(item, ts.getModifiers(item),
131          item.dotDotDotToken, item.name, item.questionToken,
132          ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), item.initializer);
133        break;
134      case COMPONENT_CONSTRUCTOR_PARENT:
135        parameter = ts.factory.createParameterDeclaration(ts.getModifiers(item),
136          item.dotDotDotToken, item.name, item.questionToken,
137          ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(
138            !partialUpdateConfig.partialUpdateMode ? BASE_COMPONENT_NAME : BASE_COMPONENT_NAME_PU), undefined),
139          item.initializer);
140        break;
141      case COMPONENT_CONSTRUCTOR_PARAMS:
142        parameter = ts.factory.updateParameterDeclaration(item, ts.getModifiers(item),
143          item.dotDotDotToken, item.name, item.questionToken,
144          ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(
145            parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), item.initializer);
146        break;
147      case COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU:
148        parameter = ts.factory.createParameterDeclaration(ts.getModifiers(item), item.dotDotDotToken,
149          item.name, ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(
150            ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), undefined), item.initializer);
151        break;
152    }
153    newTSPara.push(parameter);
154  });
155  return newTSPara;
156}
157
158export function addConstructor(ctorNode: any, watchMap: Map<string, ts.Node>,
159  parentComponentName: ts.Identifier): ts.ConstructorDeclaration {
160  const watchStatements: ts.ExpressionStatement[] = [];
161  watchMap.forEach((value, key) => {
162    const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(
163      ts.factory.createCallExpression(
164        ts.factory.createPropertyAccessExpression(
165          ts.factory.createThis(),
166          ts.factory.createIdentifier(COMPONENT_WATCH_FUNCTION)
167        ),
168        undefined,
169        [
170          ts.factory.createStringLiteral(key),
171          ts.isStringLiteral(value) ?
172            ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
173              ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression
174        ]
175      ));
176    watchStatements.push(watchNode);
177  });
178  const callSuperStatement: ts.Statement = createCallSuperStatement();
179  const updateWithValueParamsStatement: ts.Statement = createUPdWithValStatement();
180  const newBody: ts.Statement[] = [updateWithValueParamsStatement, ...watchStatements];
181  if (partialUpdateConfig.partialUpdateMode) {
182    newBody.push(createAstNodeUtils.createFinalizeConstruction());
183  }
184  return updateConstructor(updateConstructor(ctorNode, [], [callSuperStatement], [], true), [],
185    newBody, [], false, true, parentComponentName);
186}
187
188function createCallSuperStatement(): ts.Statement {
189  if (!partialUpdateConfig.partialUpdateMode) {
190    return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
191      ts.factory.createSuper(), undefined,
192      [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_ID),
193        ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT),
194        ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE)]));
195  } else {
196    return ts.factory.createExpressionStatement(
197      ts.factory.createCallExpression(ts.factory.createSuper(), undefined,
198        [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT),
199          ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU),
200          ts.factory.createIdentifier(ELMTID),
201          ts.factory.createIdentifier(CUSTOM_COMPONENT_EXTRAINFO)]));
202  }
203}
204
205function createUPdWithValStatement(): ts.Statement {
206  return ts.factory.createExpressionStatement(
207    ts.factory.createCallExpression(
208      ts.factory.createPropertyAccessExpression(
209        ts.factory.createThis(),
210        ts.factory.createIdentifier(
211          !partialUpdateConfig.partialUpdateMode ?
212            COMPONENT_CONSTRUCTOR_UPDATE_PARAMS : COMPONENT_CONSTRUCTOR_INITIAL_PARAMS
213        )
214      ),
215      undefined,
216      [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)]
217    )
218  );
219}
220