1/* 2 * Copyright (c) 2022-2025 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 "@koalaui/ets-tsc" 17import { asString, collect, filterDecorators, throwError } from "./utils" 18 19export function parameter( 20 name: string | ts.Identifier, 21 type: ts.TypeNode | undefined, 22 initializer?: ts.Expression 23) { 24 return ts.factory.createParameterDeclaration( 25 undefined, 26 undefined, 27 name, 28 undefined, 29 type, 30 initializer 31 ) 32} 33 34export function optionalParameter( 35 name: string | ts.Identifier, 36 type: ts.TypeNode | undefined, 37 initializer?: ts.Expression 38) { 39 return ts.factory.createParameterDeclaration( 40 undefined, 41 undefined, 42 name, 43 ts.factory.createToken(ts.SyntaxKind.QuestionToken), 44 type, 45 initializer 46 ) 47} 48 49export function assignment(left: ts.Expression, right: ts.Expression): ts.ExpressionStatement { 50 return ts.factory.createExpressionStatement( 51 ts.factory.createBinaryExpression( 52 left, 53 ts.factory.createToken(ts.SyntaxKind.EqualsToken), 54 right 55 ) 56 ) 57} 58 59export function tripleNotEqualsUndefined(left: ts.Expression): ts.Expression { 60 return ts.factory.createBinaryExpression( 61 left, 62 ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), 63 undefinedValue() 64 ) 65} 66 67export function id(name: string): ts.Identifier { 68 return ts.factory.createIdentifier(name) 69} 70 71export function Any(): ts.TypeNode { 72 return ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) 73} 74 75export function Undefined() { 76 return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword) 77} 78 79export function ObjectType(): ts.TypeNode { 80 return ts.factory.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword) 81} 82 83export function StringType(): ts.TypeNode { 84 return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword) 85} 86 87export function Void() { 88 return ts.factory.createToken(ts.SyntaxKind.VoidKeyword) 89} 90 91export function Export() { 92 return ts.factory.createToken(ts.SyntaxKind.ExportKeyword) 93} 94 95export function Private() { 96 return ts.factory.createToken(ts.SyntaxKind.PrivateKeyword) 97} 98 99export function Exclamation() { 100 return ts.factory.createToken(ts.SyntaxKind.ExclamationToken) 101} 102 103export function undefinedValue(): ts.Expression { 104 return ts.factory.createIdentifier("undefined") 105} 106 107export function anyIfNoType(type: ts.TypeNode | undefined): ts.TypeNode { 108 if (!type) return Any() 109 return type 110} 111 112 113export function provideAnyTypeIfNone(parameter: ts.ParameterDeclaration): ts.ParameterDeclaration { 114 return ts.factory.updateParameterDeclaration( 115 parameter, 116 parameter.modifiers, 117 parameter.dotDotDotToken, 118 parameter.name, 119 parameter.questionToken, 120 anyIfNoType(parameter.type), 121 parameter.initializer 122 ) 123} 124 125export function orUndefined(type: ts.TypeNode): ts.TypeNode { 126 return ts.factory.createUnionTypeNode([ 127 type, 128 Undefined() 129 ]) 130} 131 132export function isKnownIdentifier(name: ts.Node | undefined, value: string): boolean { 133 return (name != undefined) && 134 (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) && 135 ts.idText(name) == value 136} 137 138export function isUndefined(node: ts.Expression): boolean { 139 return node.kind == ts.SyntaxKind.UndefinedKeyword || 140 ts.isIdentifier(node) && ts.idText(node) == "undefined" 141} 142 143export function prependComment<T extends ts.Node>(node: T, comment: string): T { 144 return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, comment, true); 145} 146 147export function isDecorator(modifierLike: ts.ModifierLike, name: string): boolean { 148 if (!ts.isDecorator(modifierLike)) { 149 return false 150 } 151 return isKnownIdentifier(modifierLike.expression, name) || isCallDecorator(modifierLike, name) 152} 153 154export function isCallDecorator(decorator: ts.Decorator, name: string): boolean { 155 return ts.isCallExpression(decorator.expression) && isKnownIdentifier(decorator.expression.expression, name) 156} 157 158export function hasDecorator(node: ts.Node, name: string): boolean { 159 return getDecorator(node, name) != undefined 160} 161 162export function getDecorator(node: ts.Node, name: string): ts.Decorator | undefined { 163 return filterDecorators(node)?.find(it => isDecorator(it, name)) 164} 165 166export function findDecoratorArguments( 167 decorators: ReadonlyArray<ts.Decorator> | undefined, 168 name: string, 169 nth: number 170): ReadonlyArray<ts.Expression> | undefined { 171 return decorators 172 ?.filter(it => isCallDecorator(it, name)) 173 ?.map(it => (it.expression as ts.CallExpression).arguments[nth]) 174} 175 176export function findDecoratorLiterals( 177 decorators: ReadonlyArray<ts.Decorator> | undefined, 178 name: string, 179 nth: number, 180): ReadonlyArray<string> | undefined { 181 return findDecoratorArguments(decorators, name, nth)?.map(it => (it as ts.StringLiteral).text) 182} 183 184export function findDecoratorArgument( 185 decorators: ReadonlyArray<ts.Decorator> | undefined, 186 name: string, 187 nth: number, 188): ts.Expression { 189 const args = findDecoratorArguments(decorators, name, nth) 190 if (args?.length === 1) return args[0] 191 throw new Error(name + " must have only one argument, but got " + args?.length) 192} 193 194export function createThisFieldAccess(name: string | ts.Identifier | ts.PrivateIdentifier): ts.Expression { 195 return ts.factory.createPropertyAccessExpression( 196 ts.factory.createThis(), 197 name 198 ) 199} 200 201export function bindThis(expression: ts.Expression): ts.Expression { 202 return ts.factory.createCallExpression( 203 ts.factory.createPropertyAccessExpression( 204 expression, 205 "bind" 206 ), 207 undefined, 208 [ts.factory.createThis()] 209 ) 210 211} 212 213export function getLineNumber(sourceFile: ts.SourceFile, position: number): number { 214 return ts.getLineAndCharacterOfPosition(sourceFile, position).line + 1 // First line is 1. 215} 216 217export function getDeclarationsByNode(typechecker: ts.TypeChecker, node: ts.Node): ts.Declaration[] { 218 const symbol = typechecker.getSymbolAtLocation(node) 219 const declarations = symbol?.getDeclarations() ?? [] 220 return declarations 221} 222 223export function dropModifier(modifierLikes: ts.ModifierLike[] | undefined, kind: ts.SyntaxKind): ts.ModifierLike[] | undefined { 224 return modifierLikes?.filter(it => it.kind != kind) 225} 226 227export function dropModifiers(modifierLikes: ts.ModifierLike[] | undefined, ...kinds: ts.SyntaxKind[]): ts.ModifierLike[] | undefined { 228 return modifierLikes?.filter(it => kinds.every(kind => it.kind != kind)) 229} 230 231export function dropReadonly(modifierLikes: ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined { 232 return dropModifier(modifierLikes, ts.SyntaxKind.ReadonlyKeyword) 233} 234 235export function dropPublic(modifierLikes: ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined { 236 return dropModifier(modifierLikes, ts.SyntaxKind.PublicKeyword) 237} 238 239export function dropPrivate(modifierLikes: ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined { 240 return dropModifier(modifierLikes, ts.SyntaxKind.PrivateKeyword) 241} 242 243export function makePrivate(modifierLikes: ts.ModifierLike[] | undefined): ts.ModifierLike[] | undefined { 244 return collect(Private(), dropModifiers(modifierLikes, ts.SyntaxKind.PublicKeyword, ts.SyntaxKind.PrivateKeyword, ts.SyntaxKind.ProtectedKeyword)) 245} 246 247export function isStatic(node: ts.Node): boolean { 248 if (!ts.canHaveModifiers(node)) return false 249 const modifierLikes = ts.getModifiers(node) 250 return !!(modifierLikes?.find(it => 251 it.kind == ts.SyntaxKind.StaticKeyword) 252 ) 253} 254 255export function sourceStructName(node: ts.StructDeclaration) { 256 return node.name ?? throwError(`Nameless struct`) 257} 258 259export function asIdentifier(node: ts.Node): ts.Identifier { 260 if (ts.isIdentifier(node)) return node 261 throwError(`Expected ts.Identifier, got ${asString(node)}`) 262} 263 264export function findObjectPropertyValue(arg: ts.ObjectLiteralExpression, name: string): ts.Expression|undefined { 265 const property = arg.properties 266 .filter(it => ts.isPropertyAssignment(it)) 267 .find(it => it.name?.getText() == name) as ts.PropertyAssignment|undefined 268 return property?.initializer 269}