idlize
253 строки · 10.3 Кб
1/*
2* Copyright (c) 2022-2023 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 * as ts from 'ohos-typescript'17import { AbstractVisitor } from './AbstractVisitor'18import { ExtensionStylesTransformer } from './ExtensionStylesTransformer'19import { Importer } from './Importer'20import { bindThis, createThisFieldAccess, getDeclarationsByNode, id, isUndefined, undefinedValue } from './ApiUtils'21import { CallTable, extendsLikeFunctionName, isBuilderLambdaCall, RewriteNames } from './utils'22
23
24enum ExtensionStyleRewrite {25Regular,26StylesFunction,27ExtendFunction,28AnimatableExtendFunction,29StylesMethod,30ExtendMethod,31AnimatableExtendMethod
32}
33
34export class StyleTransformer extends AbstractVisitor {35constructor(36sourceFile: ts.SourceFile,37ctx: ts.TransformationContext,38private typechecker: ts.TypeChecker,39public importer: Importer,40private styleFunctionTransformer: ExtensionStylesTransformer,41private callTable: CallTable42) {43super(sourceFile, ctx)44}45
46private isStylesFunction(name: string): boolean {47return this.styleFunctionTransformer.stylesFunctions.includes(name)48}49
50private isExtendFunction(name: string): boolean {51return this.styleFunctionTransformer.extendFunctions.includes(name)52}53
54private isAnimatableExtendFunction(name: string): boolean {55return this.styleFunctionTransformer.animatableExtendFunctions.includes(name)56}57
58private isStylesMethod(name: ts.MethodDeclaration): boolean {59return this.styleFunctionTransformer.stylesMethods.includes(name)60}61
62private isExtendMethod(name: ts.MethodDeclaration): boolean {63return this.styleFunctionTransformer.extendMethods.includes(name)64}65
66private isAnimatableExtendMethod(name: ts.MethodDeclaration): boolean {67return this.styleFunctionTransformer.animatableExtendMethods.includes(name)68}69
70// Do we need classes here, like we have for property initializers?71private classifyExtensionStyleRewrite(name: ts.Identifier, nameExtension: ts.Identifier): ExtensionStyleRewrite {72const nameString = ts.idText(name)73const nameOfExtendsFunction = extendsLikeFunctionName(nameString, ts.idText(nameExtension))74if (this.isStylesFunction(nameString)) return ExtensionStyleRewrite.StylesFunction75if (this.isExtendFunction(nameOfExtendsFunction)) return ExtensionStyleRewrite.ExtendFunction76if (this.isAnimatableExtendFunction(nameOfExtendsFunction)) return ExtensionStyleRewrite.AnimatableExtendFunction77
78const declaration = getDeclarationsByNode(this.typechecker, name)[0]79if (!declaration) return ExtensionStyleRewrite.Regular80if (!ts.isMethodDeclaration(declaration)) return ExtensionStyleRewrite.Regular81const originalDeclaration = ts.getOriginalNode(declaration) as ts.MethodDeclaration82
83if (this.isStylesMethod(originalDeclaration)) return ExtensionStyleRewrite.StylesMethod84if (this.isExtendMethod(originalDeclaration)) return ExtensionStyleRewrite.ExtendMethod85if (this.isAnimatableExtendMethod(originalDeclaration)) return ExtensionStyleRewrite.AnimatableExtendMethod86
87return ExtensionStyleRewrite.Regular88}89
90transformEtsComponent(91node: ts.CallExpression,92dot: ts.PropertyAccessExpression,93originalNode: ts.EtsComponentExpression | ts.CallExpression,94receiverName: ts.Identifier,95args: ts.NodeArray<ts.Expression>,96isEts: boolean97): ts.EtsComponentExpression | ts.CallExpression {98const firstEtsArg: ts.Expression = args[0]99const firstEtsArgOrUndefined = isUndefined(firstEtsArg) ? undefined : firstEtsArg100const restEtsArgs = args.slice(1)101
102const rewrite = this.classifyExtensionStyleRewrite(dot.name as ts.Identifier, receiverName)103
104const propertyName = dot.name105
106const maybeSyntheticName = this.maybeSyntheticFunctionName(rewrite, propertyName, receiverName)107const styleApplicatorOrOriginalPropertyName = this.maybeStyleApplicator(rewrite, propertyName)108
109const newDot = ts.factory.updatePropertyAccessExpression(110dot,111firstEtsArgOrUndefined ?? id("instance"),112styleApplicatorOrOriginalPropertyName
113)114
115const detachedStyle = ts.factory.updateCallExpression(116node,117newDot,118undefined,119this.maybePrependStyleFunctionArgument(rewrite, maybeSyntheticName, node.arguments)120)121
122if (isEts) {123const ets = originalNode as ts.EtsComponentExpression124return ts.factory.updateEtsComponentExpression(125ets,126receiverName,127[detachedStyle, ...restEtsArgs],128ets.body129)130}131
132const call = originalNode as ts.CallExpression133return ts.factory.updateCallExpression(134call,135receiverName,136undefined,137[detachedStyle, ...restEtsArgs],138)139}140
141private maybeStyleApplicator(rewrite: ExtensionStyleRewrite, propertyName: ts.MemberName): ts.MemberName {142switch (rewrite) {143case ExtensionStyleRewrite.StylesFunction:144case ExtensionStyleRewrite.ExtendFunction:145case ExtensionStyleRewrite.StylesMethod:146return id(RewriteNames.ApplyStyle)147case ExtensionStyleRewrite.AnimatableExtendFunction:148return id(RewriteNames.ApplyAnimatableExtend)149case ExtensionStyleRewrite.ExtendMethod:150case ExtensionStyleRewrite.AnimatableExtendMethod:151console.log("TODO: need an implementatyion of @Extend applied to methods")152return propertyName153default:154return propertyName155}156}157
158private maybeSyntheticFunctionName(rewrite: ExtensionStyleRewrite, propertyName: ts.MemberName, componentName: ts.Identifier): ts.Identifier {159switch (rewrite) {160case ExtensionStyleRewrite.ExtendFunction:161case ExtensionStyleRewrite.AnimatableExtendFunction:162case ExtensionStyleRewrite.ExtendMethod:163case ExtensionStyleRewrite.AnimatableExtendMethod:164const propertyNameString = ts.idText(propertyName)165const componentNameString = ts.idText(componentName)166return id(extendsLikeFunctionName(propertyNameString, componentNameString))167default:168return propertyName as ts.Identifier169}170}171
172private maybePrependStyleFunctionArgument(rewrite: ExtensionStyleRewrite, maybeSyntheticName: ts.MemberName, nodeArguments: ts.NodeArray<ts.Expression>): readonly ts.Expression[] {173switch (rewrite) {174case ExtensionStyleRewrite.StylesFunction:175case ExtensionStyleRewrite.ExtendFunction:176case ExtensionStyleRewrite.AnimatableExtendFunction:177return [maybeSyntheticName, ...nodeArguments]178case ExtensionStyleRewrite.StylesMethod:179case ExtensionStyleRewrite.ExtendMethod:180case ExtensionStyleRewrite.AnimatableExtendMethod:181return [bindThis(createThisFieldAccess(maybeSyntheticName)), ...nodeArguments]182default:183return nodeArguments184}185}186
187// Assumption: Ets nodes were preprocessed to have "undefined"188// as their first argument.189//190// We need to rewrite191// ets(undefined, some, args).foo().bar().qux()192// ets(instance.foo().bar().qux(), some, args)193// No style ets is left as is:194// ets(some, args)195// The trick here is that ets() is the deepest in the PropertyAccessExpression tree.196visitor(beforeChildren: ts.Node): ts.Node {197const node = this.visitEachChild(beforeChildren)198
199// Recognize ets().foo()200if (ts.isCallExpression(node) &&201ts.isPropertyAccessExpression(node.expression) &&202ts.isEtsComponentExpression(node.expression.expression)) {203const dot = node.expression204const ets = node.expression.expression205return this.transformEtsComponent(node, dot, ets, ets.expression as ts.Identifier, ets.arguments, true)206}207// Recognize ets().foo()208if (ts.isCallExpression(node) &&209ts.isPropertyAccessExpression(node.expression) &&210ts.isCallExpression(node.expression.expression) &&211isBuilderLambdaCall(this.callTable, node.expression.expression)) {212const dot = node.expression213const call = node.expression.expression214return this.transformEtsComponent(node, dot, call, call.expression as ts.Identifier, call.arguments, false)215}216
217return node218}219}
220
221// Preprocess Ets nodes by providing an undefined as the first arg
222// so that we don't have to think if it is already provided later
223export class EtsFirstArgTransformer extends AbstractVisitor {224constructor(225sourceFile: ts.SourceFile,226ctx: ts.TransformationContext,227public importer: Importer,228private callTable: CallTable229) {230super(sourceFile, ctx)231}232
233visitor(beforeChildren: ts.Node): ts.Node {234const node = this.visitEachChild(beforeChildren)235if (ts.isEtsComponentExpression(node)) {236return ts.factory.updateEtsComponentExpression(237node,238node.expression as ts.Identifier,239[undefinedValue(), ...node.arguments],240node.body241)242}243if (ts.isCallExpression(node) && isBuilderLambdaCall(this.callTable, node)) {244return ts.factory.updateCallExpression(245node,246node.expression,247node.typeArguments,248[undefinedValue(), ...node.arguments]249)250}251return node252}253}
254