• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}