1/*
2 * Copyright (c) 2024 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 { SUPER_ARGS, COMPONENT_SENDABLE_DECORATOR } from './pre_define';
18
19function transformOptionalMemberForSendable(node: ts.PropertyDeclaration): ts.PropertyDeclaration {
20  let updatedTypeNode: ts.TypeNode = node.type;
21
22  if (ts.isUnionTypeNode(updatedTypeNode)) {
23    if (!updatedTypeNode.types.find(type => type.kind === ts.SyntaxKind.UndefinedKeyword)) {
24      updatedTypeNode = ts.factory.createUnionTypeNode([
25        ...updatedTypeNode.types,
26        ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
27      ]);
28    }
29  } else {
30    updatedTypeNode = ts.factory.createUnionTypeNode([
31      updatedTypeNode,
32      ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword)
33    ]);
34  }
35
36  return ts.factory.createPropertyDeclaration(
37    node.modifiers,
38    node.name,
39    undefined,
40    updatedTypeNode,
41    node.initializer ? node.initializer : ts.factory.createIdentifier('undefined')
42  );
43}
44
45function removeSendableDecorator(modifiers: ts.NodeArray<ts.ModifierLike>): ts.NodeArray<ts.ModifierLike> {
46  return ts.factory.createNodeArray(
47    modifiers.filter(decorator => {
48      const originalDecortor: string = decorator.getText().replace(/\(.*\)$/, '').trim();
49      return originalDecortor !== COMPONENT_SENDABLE_DECORATOR;
50    })
51  );
52}
53
54function updateSendableConstructor(constructor: ts.ConstructorDeclaration): ts.ConstructorDeclaration {
55  // Skip the overloaded signature of the constructor
56  if (constructor.body === undefined) {
57    return constructor;
58  }
59  const statementArray: ts.Statement[] = [
60    ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable')),
61    ...constructor.body.statements
62  ];
63
64  return ts.factory.updateConstructorDeclaration(
65    constructor,
66    constructor.modifiers,
67    constructor.parameters,
68    ts.factory.updateBlock(constructor.body, statementArray)
69  );
70}
71
72function addConstructorForSendableClass(
73  members: ts.NodeArray<ts.ClassElement>,
74  needSuper: boolean): ts.NodeArray<ts.ClassElement> {
75  const params: ts.ParameterDeclaration[] = [];
76  const constructorStatements: ts.Statement[] = [
77    ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable'))
78  ];
79  if (needSuper) {
80    constructorStatements.push(
81      ts.factory.createExpressionStatement(
82        ts.factory.createCallExpression(
83          ts.factory.createSuper(), undefined, [ts.factory.createSpreadElement(ts.factory.createIdentifier(SUPER_ARGS))])
84      )
85    );
86    params.push(
87      ts.factory.createParameterDeclaration(
88        undefined,
89        ts.factory.createToken(ts.SyntaxKind.DotDotDotToken),
90        ts.factory.createIdentifier(SUPER_ARGS),
91        undefined,
92        undefined,
93        undefined)
94    );
95  }
96  const constructor: ts.ConstructorDeclaration = ts.factory.createConstructorDeclaration(
97    undefined,
98    params,
99    ts.factory.createBlock(constructorStatements, true)
100  );
101
102  return ts.factory.createNodeArray([constructor, ...(members || [])]);
103}
104
105export function processSendableClass(node: ts.ClassDeclaration): ts.ClassDeclaration {
106  let hasConstructor = false;
107  let updatedMembers: ts.NodeArray<ts.ClassElement> = node.members;
108  let updatedModifiers: ts.NodeArray<ts.ModifierLike> = removeSendableDecorator(node.modifiers);
109  let needSuper: boolean =
110    node.heritageClauses?.some(clause => clause.token === ts.SyntaxKind.ExtendsKeyword) || false;
111
112  for (const member of node.members) {
113    if (ts.isPropertyDeclaration(member) && member.questionToken) {
114      const propertyDecl: ts.PropertyDeclaration = member as ts.PropertyDeclaration;
115      const updatedPropertyDecl: ts.PropertyDeclaration = transformOptionalMemberForSendable(member);
116      updatedMembers = ts.factory.createNodeArray(
117        updatedMembers.map(member => (member === propertyDecl ? updatedPropertyDecl : member))
118      );
119    }
120    if (ts.isConstructorDeclaration(member)) {
121      hasConstructor = true;
122      const constructor: ts.ConstructorDeclaration = member as ts.ConstructorDeclaration;
123      updatedMembers = ts.factory.createNodeArray(
124        updatedMembers.map(member => (member === constructor ? updateSendableConstructor(constructor) : member))
125      );
126    }
127  }
128
129  if (!hasConstructor) {
130    updatedMembers = addConstructorForSendableClass(updatedMembers, needSuper);
131  }
132
133  node = ts.factory.updateClassDeclaration(node, updatedModifiers, node.name, node.typeParameters,
134    node.heritageClauses, updatedMembers);
135
136  return node;
137}
138
139export function processSendableFunction(node: ts.FunctionDeclaration): ts.FunctionDeclaration {
140  if (node.body) {
141    const statementArray: ts.Statement[] =
142      [ts.factory.createExpressionStatement(ts.factory.createStringLiteral('use sendable')),
143        ...node.body.statements];
144    return ts.factory.updateFunctionDeclaration(node, ts.getModifiers(node), node.asteriskToken, node.name,
145      node.typeParameters, node.parameters, node.type, ts.factory.updateBlock(node.body, statementArray));
146  }
147  return ts.factory.createFunctionDeclaration(undefined, node.asteriskToken, node.name,
148    node.typeParameters, node.parameters, node.type, node.body);
149}
150
151export function processSendableType(node: ts.TypeAliasDeclaration): ts.TypeAliasDeclaration {
152  return ts.factory.createTypeAliasDeclaration(undefined, node.name, node.typeParameters, node.type);
153}
154