• 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';
17
18import {
19  COMPONENT_NON_DECORATOR,
20  COMPONENT_STATE_DECORATOR,
21  COMPONENT_PROP_DECORATOR,
22  COMPONENT_LINK_DECORATOR,
23  COMPONENT_STORAGE_LINK_DECORATOR,
24  COMPONENT_PROVIDE_DECORATOR,
25  COMPONENT_OBJECT_LINK_DECORATOR,
26  COMPONENT_CREATE_FUNCTION,
27  BASE_COMPONENT_NAME,
28  CUSTOM_COMPONENT_EARLIER_CREATE_CHILD,
29  COMPONENT_CONSTRUCTOR_UPDATE_PARAMS,
30  CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID,
31  COMPONENT_CONSTRUCTOR_UNDEFINED,
32  CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION,
33  CUSTOM_COMPONENT_MARK_STATIC_FUNCTION
34} from './pre_define';
35import {
36  propertyCollection,
37  stateCollection,
38  linkCollection,
39  propCollection,
40  regularCollection,
41  storagePropCollection,
42  storageLinkCollection,
43  provideCollection,
44  consumeCollection,
45  objectLinkCollection,
46  isStaticViewCollection
47} from './validate_ui_syntax';
48import {
49  propAndLinkDecorators,
50  curPropMap,
51  observedPropertyDecorators,
52  createViewCreate,
53  createCustomComponentNewExpression
54} from './process_component_member';
55import { builderParamObjectCollection } from './process_component_member';
56import {
57  LogType,
58  LogInfo,
59  componentInfo
60} from './utils';
61
62const localArray: string[] = [...observedPropertyDecorators, COMPONENT_NON_DECORATOR,
63  COMPONENT_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR];
64
65const decoractorMap: Map<string, Map<string, Set<string>>> = new Map(
66  [[COMPONENT_STATE_DECORATOR, stateCollection],
67    [COMPONENT_LINK_DECORATOR, linkCollection],
68    [COMPONENT_PROP_DECORATOR, propCollection],
69    [COMPONENT_NON_DECORATOR, regularCollection],
70    [COMPONENT_PROVIDE_DECORATOR, provideCollection],
71    [COMPONENT_OBJECT_LINK_DECORATOR, objectLinkCollection]]);
72
73export function processCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[],
74  log: LogInfo[]): void {
75  if (ts.isCallExpression(node.expression)) {
76    let ischangeNode: boolean = false;
77    let customComponentNewExpression: ts.NewExpression = createCustomComponentNewExpression(
78      node.expression);
79    let argumentsArray: ts.PropertyAssignment[];
80    if (isHasChild(node.expression)) {
81      // @ts-ignore
82      argumentsArray = node.expression.arguments[0].properties.slice();
83      argumentsArray.forEach((item: ts.PropertyAssignment, index: number) => {
84        if (isToChange(item, node.expression as ts.CallExpression)) {
85          ischangeNode = true;
86          const propertyAssignmentNode: ts.PropertyAssignment = ts.factory.updatePropertyAssignment(
87            item, item.name, changeNodeFromCallToArrow(item.initializer as ts.CallExpression));
88          argumentsArray.splice(index, 1, propertyAssignmentNode);
89        }
90      });
91      if (ischangeNode) {
92        const newNode: ts.ExpressionStatement = ts.factory.updateExpressionStatement(node,
93          ts.factory.createNewExpression(node.expression.expression, node.expression.typeArguments,
94            [ts.factory.createObjectLiteralExpression(argumentsArray, true)]));
95        customComponentNewExpression = createCustomComponentNewExpression(
96          newNode.expression as ts.CallExpression);
97      }
98    }
99    addCustomComponent(node, newStatements, customComponentNewExpression, log);
100  }
101}
102
103function isHasChild(node: ts.CallExpression): boolean {
104  return node.arguments && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) &&
105  node.arguments[0].properties && node.arguments[0].properties.length;
106}
107
108function isToChange(item: ts.PropertyAssignment, node: ts.CallExpression): boolean {
109  const builderParamName: Set<string> = builderParamObjectCollection.get(node.expression.getText());
110  if (item.initializer && ts.isCallExpression(item.initializer) && builderParamName &&
111    builderParamName.has(item.name.getText()) &&
112    !/\.(bind|call|apply)/.test(item.initializer.getText())) {
113    return true;
114  }
115  return false;
116}
117
118function changeNodeFromCallToArrow(node: ts.CallExpression): ts.ArrowFunction {
119  return ts.factory.createArrowFunction(undefined, undefined, [], undefined,
120    ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
121    ts.factory.createBlock([ts.factory.createExpressionStatement(node)], true));
122}
123
124function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[],
125  newNode: ts.NewExpression, log: LogInfo[]): void {
126  if (ts.isNewExpression(newNode)) {
127    const customComponentName: string = getCustomComponentName(newNode);
128    const propertyArray: ts.ObjectLiteralElementLike[] = [];
129    validateCustomComponentPrams(node, customComponentName, propertyArray, log);
130    addCustomComponentStatements(node, newStatements, newNode, customComponentName, propertyArray);
131  }
132}
133
134function addCustomComponentStatements(node: ts.ExpressionStatement, newStatements: ts.Statement[],
135  newNode: ts.NewExpression, name: string, props: ts.ObjectLiteralElementLike[]): void {
136  const id: string = componentInfo.id.toString();
137  newStatements.push(createFindChildById(id, name), createCustomComponentIfStatement(id,
138    ts.factory.updateExpressionStatement(node, createViewCreate(newNode)),
139    ts.factory.createObjectLiteralExpression(props, true), name));
140}
141
142function validateCustomComponentPrams(node: ts.ExpressionStatement, name: string,
143  props: ts.ObjectLiteralElementLike[], log: LogInfo[]): void {
144  const curChildProps: Set<string> = new Set([]);
145  const nodeExpression: ts.CallExpression = node.expression as ts.CallExpression;
146  const nodeArguments: ts.NodeArray<ts.Expression> = nodeExpression.arguments;
147  const propertySet: Set<string> = getCollectionSet(name, propertyCollection);
148  const linkSet: Set<string> = getCollectionSet(name, linkCollection);
149  if (nodeArguments && nodeArguments.length === 1 &&
150    ts.isObjectLiteralExpression(nodeArguments[0])) {
151    const nodeArgument: ts.ObjectLiteralExpression = nodeArguments[0] as ts.ObjectLiteralExpression;
152    nodeArgument.properties.forEach(item => {
153      if (item.name && ts.isIdentifier(item.name)) {
154        curChildProps.add(item.name.escapedText.toString());
155      }
156      validateStateManagement(item, name, log);
157      if (isNonThisProperty(item, linkSet)) {
158        if (isToChange(item as ts.PropertyAssignment, node.expression as ts.CallExpression)) {
159          item = ts.factory.updatePropertyAssignment(item as ts.PropertyAssignment,
160            item.name, changeNodeFromCallToArrow(item.initializer));
161        }
162        props.push(item);
163      }
164    });
165  }
166  validateMandatoryToAssignmentViaParam(node, name, curChildProps, log);
167}
168
169function getCustomComponentName(newNode: ts.NewExpression): string {
170  let customComponentName: string;
171  if (ts.isIdentifier(newNode.expression)) {
172    customComponentName = newNode.expression.escapedText.toString();
173  } else if (ts.isPropertyAccessExpression(newNode.expression)) {
174    customComponentName = newNode.expression.name.escapedText.toString();
175  }
176  return customComponentName;
177}
178
179function getCollectionSet(name: string, collection: Map<string, Set<string>>): Set<string> {
180  if (!collection) {
181    return new Set([]);
182  }
183  return collection.get(name) || new Set([]);
184}
185
186function isThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean {
187  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) &&
188    propertySet.has(node.name.escapedText.toString())) {
189    return true;
190  }
191  return false;
192}
193
194function isNonThisProperty(node: ts.ObjectLiteralElementLike, propertySet: Set<string>): boolean {
195  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) &&
196    (node.initializer.escapedText && node.initializer.escapedText.includes('$') ||
197    ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression &&
198    node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword &&
199    ts.isIdentifier(node.initializer.name) && node.initializer.name.escapedText.toString().includes('$'))) {
200    return false;
201  }
202  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) &&
203    !propertySet.has(node.name.escapedText.toString())) {
204    return true;
205  }
206  return false;
207}
208
209function validateStateManagement(node: ts.ObjectLiteralElementLike, customComponentName: string,
210  log: LogInfo[]): void {
211  validateForbiddenToInitViaParam(node, customComponentName, log);
212  checkFromParentToChild(node, customComponentName, log);
213}
214
215function checkFromParentToChild(node: ts.ObjectLiteralElementLike, customComponentName: string,
216  log: LogInfo[]): void {
217  let propertyName: string;
218  if (node.name && node.name.escapedText) {
219    // @ts-ignore
220    propertyName = node.name.escapedText.toString();
221  }
222  const curPropertyKind: string = getPropertyDecoratorKind(propertyName, customComponentName);
223  if (curPropertyKind) {
224    if (isInitFromParent(node)) {
225      const parentPropertyName: string =
226        getParentPropertyName(node as ts.PropertyAssignment, curPropertyKind, log);
227      if (!parentPropertyName) {
228        return;
229      }
230      const parentPropertyKind: string = curPropMap.get(parentPropertyName);
231      if (parentPropertyKind && !isCorrectInitFormParent(parentPropertyKind, curPropertyKind)) {
232        validateIllegalInitFromParent(
233          node, propertyName, curPropertyKind, parentPropertyName, parentPropertyKind, log);
234      }
235    } else if (isInitFromLocal(node) && ts.isPropertyAssignment(node)) {
236      if (!localArray.includes(curPropertyKind)) {
237        validateIllegalInitFromParent(node, propertyName, curPropertyKind,
238          node.initializer.getText(), COMPONENT_NON_DECORATOR, log);
239      }
240    }
241  }
242}
243
244function isInitFromParent(node: ts.ObjectLiteralElementLike): boolean {
245  if (ts.isPropertyAssignment(node) && node.initializer) {
246    if (ts.isPropertyAccessExpression(node.initializer) && node.initializer.expression &&
247    node.initializer.expression.kind === ts.SyntaxKind.ThisKeyword &&
248    ts.isIdentifier(node.initializer.name)) {
249      return true;
250    } else if (ts.isIdentifier(node.initializer) &&
251      matchStartWithDollar(node.initializer.getText())) {
252      return true;
253    }
254  }
255}
256
257function isInitFromLocal(node: ts.ObjectLiteralElementLike): boolean {
258  if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.initializer) &&
259    !matchStartWithDollar(node.initializer.getText())) {
260    return true;
261  }
262}
263
264function getParentPropertyName(node: ts.PropertyAssignment, curPropertyKind: string,
265  log: LogInfo[]): string {
266  let parentPropertyName: string;
267  const initExpression: ts.Expression = node.initializer;
268  if (curPropertyKind === COMPONENT_LINK_DECORATOR) {
269    if (hasDollar(initExpression)) {
270      // @ts-ignore
271      const initName: ts.Identifier = initExpression.name || initExpression;
272      parentPropertyName = initName.getText().replace(/^\$/, '');
273    } else {
274      validateLinkWithoutDollar(node, log);
275    }
276  } else {
277    if (hasDollar(initExpression)) {
278      validateNonLinkWithDollar(node, log);
279    } else {
280    // @ts-ignore
281      parentPropertyName = node.initializer.name.getText();
282    }
283  }
284  return parentPropertyName;
285}
286
287function isCorrectInitFormParent(parent: string, child: string): boolean {
288  switch (child) {
289    case COMPONENT_STATE_DECORATOR:
290    case COMPONENT_PROVIDE_DECORATOR:
291      if (parent === COMPONENT_NON_DECORATOR) {
292        return true;
293      }
294      break;
295    case COMPONENT_LINK_DECORATOR:
296      if ([COMPONENT_STATE_DECORATOR, COMPONENT_LINK_DECORATOR,
297        COMPONENT_STORAGE_LINK_DECORATOR].includes(parent)) {
298        return true;
299      }
300      break;
301    case COMPONENT_PROP_DECORATOR:
302      if ([COMPONENT_STATE_DECORATOR, ...propAndLinkDecorators].includes(parent)) {
303        return true;
304      }
305      break;
306    case COMPONENT_NON_DECORATOR:
307      if ([COMPONENT_STATE_DECORATOR, ...propAndLinkDecorators, COMPONENT_NON_DECORATOR,
308        COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR].includes(parent)) {
309        return true;
310      }
311      break;
312    case COMPONENT_OBJECT_LINK_DECORATOR:
313      if (parent === COMPONENT_STATE_DECORATOR) {
314        return true;
315      }
316      break;
317  }
318  return false;
319}
320
321function getPropertyDecoratorKind(propertyName: string, customComponentName: string): string {
322  for (const item of decoractorMap.entries()) {
323    if (getCollectionSet(customComponentName, item[1]).has(propertyName)) {
324      return item[0];
325    }
326  }
327}
328
329function createFindChildById(id: string, name: string): ts.VariableStatement {
330  return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList(
331    [ts.factory.createVariableDeclaration(ts.factory.createIdentifier(
332      `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, ts.factory.createTypeReferenceNode(
333      ts.factory.createIdentifier(name)), ts.factory.createAsExpression(ts.factory.createCallExpression(
334      ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(
335        `${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, [ts.factory.createStringLiteral(id)]),
336    ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(name))))], ts.NodeFlags.Let));
337}
338
339function createCustomComponentIfStatement(id: string, node: ts.ExpressionStatement,
340  newObjectLiteralExpression: ts.ObjectLiteralExpression, parentName: string): ts.IfStatement {
341  const viewName: string = `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`;
342  return ts.factory.createIfStatement(ts.factory.createBinaryExpression(
343    ts.factory.createIdentifier(viewName),
344    ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken),
345    ts.factory.createIdentifier(`${COMPONENT_CONSTRUCTOR_UNDEFINED}`)),
346  ts.factory.createBlock([node], true),
347  ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression(
348    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(
349      viewName), ts.factory.createIdentifier(
350      `${COMPONENT_CONSTRUCTOR_UPDATE_PARAMS}`)), undefined, [newObjectLiteralExpression])),
351  isStaticViewCollection.get(parentName) ? createStaticIf(viewName) : undefined,
352  ts.factory.createExpressionStatement(ts.factory.createCallExpression(
353    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(`${BASE_COMPONENT_NAME}`),
354      ts.factory.createIdentifier(`${COMPONENT_CREATE_FUNCTION}`)), undefined,
355    [ts.factory.createIdentifier(viewName)]))], true));
356}
357
358function createStaticIf(name: string): ts.IfStatement {
359  return ts.factory.createIfStatement(ts.factory.createPrefixUnaryExpression(
360    ts.SyntaxKind.ExclamationToken, ts.factory.createCallExpression(
361      ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name),
362        ts.factory.createIdentifier(CUSTOM_COMPONENT_NEEDS_UPDATE_FUNCTION)), undefined, [])),
363  ts.factory.createBlock([ts.factory.createExpressionStatement(ts.factory.createCallExpression(
364    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(name),
365      ts.factory.createIdentifier(CUSTOM_COMPONENT_MARK_STATIC_FUNCTION)),
366    undefined, []))], true), undefined);
367}
368
369function hasDollar(initExpression: ts.Expression): boolean {
370  if (ts.isPropertyAccessExpression(initExpression) &&
371    matchStartWithDollar(initExpression.name.getText())) {
372    return true;
373  } else if (ts.isIdentifier(initExpression) && matchStartWithDollar(initExpression.getText())) {
374    return true;
375  } else {
376    return false;
377  }
378}
379
380function matchStartWithDollar(name: string): boolean {
381  return /^\$/.test(name);
382}
383
384function validateForbiddenToInitViaParam(node: ts.ObjectLiteralElementLike,
385  customComponentName: string, log: LogInfo[]): void {
386  const forbiddenToInitViaParamSet: Set<string> = new Set([
387    ...getCollectionSet(customComponentName, storageLinkCollection),
388    ...getCollectionSet(customComponentName, storagePropCollection),
389    ...getCollectionSet(customComponentName, consumeCollection)]);
390  if (isThisProperty(node, forbiddenToInitViaParamSet)) {
391    log.push({
392      type: LogType.ERROR,
393      message: `Property '${node.name.getText()}' in the custom component '${customComponentName}'` +
394        ` cannot initialize here (forbidden to specify).`,
395      pos: node.name.getStart()
396    });
397  }
398}
399
400function validateNonExistentProperty(node: ts.ObjectLiteralElementLike,
401  customComponentName: string, log: LogInfo[]): void {
402  log.push({
403    type: LogType.ERROR,
404    message: `Property '${node.name.escapedText.toString()}' does not exist on type '${customComponentName}'.`,
405    pos: node.name.getStart()
406  });
407}
408
409function validateMandatoryToAssignmentViaParam(node: ts.CallExpression, customComponentName: string,
410  curChildProps: Set<string>, log: LogInfo[]): void {
411  if (builderParamObjectCollection.get(customComponentName) &&
412    builderParamObjectCollection.get(customComponentName).size) {
413    builderParamObjectCollection.get(customComponentName).forEach((item) => {
414      if (!curChildProps.has(item)) {
415        log.push({
416          type: LogType.ERROR,
417          message: `The property decorated with @BuilderParam '${item}' must be assigned a value .`,
418          pos: node.getStart()
419        });
420      }
421    });
422  }
423}
424
425function validateMandatoryToInitViaParam(node: ts.ExpressionStatement, customComponentName: string,
426  curChildProps: Set<string>, log: LogInfo[]): void {
427  const mandatoryToInitViaParamSet: Set<string> = new Set([
428    ...getCollectionSet(customComponentName, propCollection),
429    ...getCollectionSet(customComponentName, linkCollection),
430    ...getCollectionSet(customComponentName, objectLinkCollection)]);
431  mandatoryToInitViaParamSet.forEach(item => {
432    if (!curChildProps.has(item)) {
433      log.push({
434        type: LogType.ERROR,
435        message: `Property '${item}' in the custom component '${customComponentName}'` +
436          ` is missing (mandatory to specify).`,
437        pos: node.getStart()
438      });
439    }
440  });
441}
442
443function validateIllegalInitFromParent(node: ts.ObjectLiteralElementLike, propertyName: string,
444  curPropertyKind: string, parentPropertyName: string, parentPropertyKind: string,
445  log: LogInfo[]): void {
446  log.push({
447    type: LogType.ERROR,
448    message: `The ${parentPropertyKind} property '${parentPropertyName}' cannot be assigned to ` +
449      `the ${curPropertyKind} property '${propertyName}'.`,
450    // @ts-ignore
451    pos: node.initializer.getStart()
452  });
453}
454
455function validateLinkWithoutDollar(node: ts.PropertyAssignment, log: LogInfo[]): void {
456  log.push({
457    type: LogType.ERROR,
458    message: `The @Link property '${node.name.getText()}' should initialize` +
459      ` using '$' to create a reference to a @State or @Link variable.`,
460    pos: node.initializer.getStart()
461  });
462}
463
464function validateNonLinkWithDollar(node: ts.PropertyAssignment, log: LogInfo[]): void {
465  log.push({
466    type: LogType.ERROR,
467    message: `Property '${node.name.getText()}' cannot initialize` +
468      ` using '$' to create a reference to a variable.`,
469    pos: node.initializer.getStart()
470  });
471}
472