idlize

Форк
0
/
diagnostics-visitor.ts 
275 строк · 10.8 Кб
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

16
import * as ts from 'typescript';
17
import { MemoArgumentDetector } from './MemoArgumentDetector';
18
import { ScopedVisitor } from './ScopedVisitor';
19
import { Rewrite } from './transformation-context';
20
import {
21
    FunctionKind,
22
    Tracer,
23
    isFunctionOrMethod,
24
    isAnyMemoKind,
25
    RuntimeNames,
26
    getDeclarationsByNode,
27
    isMemoKind
28
} from "./util"
29

30
enum MessageCode {
31
    CALLINNG_MEMO_FROM_NON_MEMO = 10001,
32
    SHORTHAND_PROPERTY_ASSIGNMENT_IS_NOT_ALLOWED = 10002,
33
    CAN_NOT_CALL_MEMO_FUNCTION_IN_PARAMETER_DEFAULT_VALUE = 10003,
34
    CHANGING_STATE_IN_MEMO_CONTEXT_IS_NOT_ALLOWED = 10004,
35
    CAN_NOT_ASSIGN_TO_MEMO_FUNCTION_PARAMETER = 10005,
36
    MEMO_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED = 10006,
37
    MEMO_ARROW_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED = 10007,
38
}
39

40
export class DiagnosticsVisitor extends ScopedVisitor<ts.FunctionLikeDeclaration> {
41
    constructor(
42
        public tracer: Tracer,
43
        public typechecker: ts.TypeChecker,
44
        public sourceFile: ts.SourceFile,
45
        public rewrite: Rewrite,
46
        public extras: ts.TransformerExtras|undefined,
47
        ctx: ts.TransformationContext
48
    ) {
49
        super(rewrite.functionTable, ctx)
50
    }
51

52
    reportError(code: number, text: string, node: ts.Node) {
53
        const lineAndChar = ts.getLineAndCharacterOfPosition(this.sourceFile, node.pos)
54
        this.extras?.addDiagnostic({
55
            code: code,
56
            messageText: text,
57
            category: ts.DiagnosticCategory.Error,
58
            file: this.sourceFile,
59
            start: node.pos,
60
            length: node.end - node.pos
61
        })
62
    }
63

64
    reportCallingMemoFromNonMemo(node: ts.CallExpression) {
65
        const name = ts.isIdentifier(node.expression) ? `"${ts.idText(node.expression)}"` : ""
66
        this.reportError(
67
            MessageCode.CALLINNG_MEMO_FROM_NON_MEMO,
68
            `Calling a ${RuntimeNames.ANNOTATION} function ${name} from a non-${RuntimeNames.ANNOTATION} context`,
69
            node.expression,
70
        )
71
    }
72

73
    reportShorthandPropertyInMemo(node: ts.ShorthandPropertyAssignment) {
74
        const name = ts.idText(node.name)
75
        this.reportError(
76
            MessageCode.SHORTHAND_PROPERTY_ASSIGNMENT_IS_NOT_ALLOWED,
77
            `Shorthand property assignment is not allowed in ${RuntimeNames.ANNOTATION} code: ${name}`,
78
            node,
79
        )
80
    }
81

82
    functionName(node: ts.CallExpression): string {
83
        return this.nameOrFunction(node.expression)
84
    }
85

86
    nameOrFunction(node: ts.Node|undefined): string {
87
        let name : string
88
        if (node && ts.isIdentifier(node)) {
89
            name = `"` + ts.idText(node) + `"`
90
        } else {
91
            name = `function`
92
        }
93
        return name
94
    }
95

96
    reportMemoArgument(name: ts.BindingName, initializer: ts.CallExpression) {
97
        let parameter : string
98
        if(ts.isIdentifier(name)) {
99
            parameter = ` "` + ts.idText(name) + `"`
100
        }
101
        else { parameter = "" }
102

103
        this.reportError(
104
            MessageCode.CAN_NOT_CALL_MEMO_FUNCTION_IN_PARAMETER_DEFAULT_VALUE,
105
            `Can not call ${RuntimeNames.ANNOTATION} ${this.functionName(initializer)} in parameter${parameter} default value expression`,
106
            name,
107
        )
108
    }
109

110
    reportChangingStateInMemo(node: ts.PropertyAccessExpression) {
111
        const name = ts.isIdentifier(node.expression) ? `"${ts.idText(node.expression)}"` : ""
112
        this.reportError(
113
            MessageCode.CHANGING_STATE_IN_MEMO_CONTEXT_IS_NOT_ALLOWED,
114
            `Changing state ${name} in ${RuntimeNames.ANNOTATION} context is not allowed`,
115
            node.expression,
116
        )
117
    }
118

119
    reportAssignToParameterOfMemo(node: ts.Identifier) {
120
        const name = ts.idText(node)
121
        this.reportError(
122
            MessageCode.CAN_NOT_ASSIGN_TO_MEMO_FUNCTION_PARAMETER,
123
            `Can not assign to memo function parameter: ${name}`,
124
            node,
125
        )
126
    }
127

128
    isParameterOfMemo(node: ts.Expression): boolean {
129
        if (!ts.isIdentifier(node)) return false
130
        const declarations = getDeclarationsByNode(this.typechecker, node)
131
        if (!declarations || declarations.length == 0) return false
132
        const firstDeclaration = declarations[0]
133
        if (!ts.isParameter(firstDeclaration)) return false
134
        const parent = firstDeclaration.parent
135
        if (!ts.isFunctionDeclaration(parent)) return false
136

137
        return isMemoKind(this.declarationKind(parent))
138
    }
139

140
    isAssignment(node: ts.Node): boolean {
141
        if (node.kind >= ts.SyntaxKind.FirstAssignment && node.kind <= ts.SyntaxKind.LastAssignment) { return true }
142
        else { return false }
143
    }
144

145
    isStateVariable(node: ts.Node): boolean {
146
        if (!ts.isPropertyAccessExpression(node)) return false
147
        if (!ts.isIdentifier(node.name)) return false
148
        if (!(ts.idText(node.name) == "value")) return false
149
        const declarations = getDeclarationsByNode(this.typechecker, node)
150
        if(!declarations.length) return false
151
        let suspect = declarations[0].parent
152
        if (!ts.isInterfaceDeclaration(suspect)) return false
153
        if (!suspect.heritageClauses) return false
154
        for (const postsuspect of suspect.heritageClauses) {
155
            for (const parent of postsuspect.types) {
156
                if (ts.isIdentifier(parent.expression)) {
157
                    if (ts.idText(parent.expression) == "State") return true
158
                }
159
            }
160
        }
161
        return false
162
    }
163

164
    containsMemoCall(parameter: ts.ParameterDeclaration) {
165
        const memoArgumentDetector = new MemoArgumentDetector(this.typechecker, this.rewrite, parameter, this.ctx)
166
        if (parameter.initializer) {
167
            const hasMemoArgument = memoArgumentDetector.usingMemoFunction(parameter.initializer)
168
            if (hasMemoArgument && ts.isCallExpression(memoArgumentDetector.memoArgument)) {
169
                this.reportMemoArgument(parameter.name, memoArgumentDetector.memoArgument)
170
            }
171
        }
172
    }
173

174
    checkReturnType(returnNode: ts.ReturnStatement, functionNode: ts.FunctionLikeDeclaration | undefined) {
175
        if (!functionNode) return
176
        if (returnNode.expression !== undefined && functionNode.type === undefined) {
177
            this.reportError(
178
                MessageCode.MEMO_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED,
179
                `${RuntimeNames.ANNOTATION} ${this.nameOrFunction(functionNode.name)} must have its type specified explicitly`,
180
                functionNode,
181
            )
182
        }
183
    }
184

185
    canFindVoidType(body: ts.Expression): boolean {
186
        const type = this.typechecker.getTypeAtLocation(body)
187
        const text = this.typechecker.typeToString(type)
188
        // Oh my. Can we detect void|undefined in a some faster way?
189
        return (text == "void" || text == "void | undefined" || text == "undefined | void" )
190
    }
191

192
    checkArrowReturnType(arrow: ts.ArrowFunction) {
193
        if (!ts.isBlock(arrow.body) && arrow.type === undefined) {
194
            // So we have an arrow function like
195
            //   (some args) => some_expression
196
            // we need to check if some_expression has type void
197
            if (!ts.isBlock(arrow.body) && // The Block case is handled in checkReturnType()
198
                !this.canFindVoidType(arrow.body)
199
            ) {
200
                this.reportError(
201
                    MessageCode.MEMO_ARROW_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED,
202
                    `${RuntimeNames.ANNOTATION} arrow must have its type specified explicitly`,
203
                    arrow,
204
                )
205
            }
206
        }
207
    }
208

209
    indent = 0
210
    visitor(node: ts.Node): ts.Node {
211
        if (isFunctionOrMethod(node)) {
212
            const kind = this.declarationKind(node)
213
            if (ts.isArrowFunction(node) && isMemoKind(kind)) {
214
                this.checkArrowReturnType(node)
215
            }
216
            return this.withFunctionScope<ts.Node>(kind, node, () => {
217
                return this.visitEachChild(node)
218
            })
219
        } else if (ts.isCallExpression(node)) {
220
            const callKind = this.rewrite.callTable.get(node) ?? FunctionKind.REGULAR
221
            const scopeKind = this.currentKind()
222
            if (isAnyMemoKind(callKind) && !isAnyMemoKind(scopeKind)) {
223
                if (!this.rewrite.entryTable.has(node)) {
224
                    this.reportCallingMemoFromNonMemo(node)
225
                }
226
            }
227
            return this.visitEachChild(node)
228
        } else if (ts.isBinaryExpression(node) &&
229
            this.isAssignment(node.operatorToken)) {
230

231
            if (this.isStateVariable(node.left)) {
232
                const scopeKind = this.currentKind()
233
                if (isAnyMemoKind(scopeKind)) {
234
                    if (ts.isPropertyAccessExpression(node.left)) {
235
                        this.reportChangingStateInMemo(node.left)
236
                    }
237

238
                }
239
            }
240
            if (this.isParameterOfMemo(node.left)) {
241
                this.reportAssignToParameterOfMemo(node.left as ts.Identifier)
242
            }
243
            return this.visitEachChild(node)
244
        } else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) {
245
            if (node.operator == ts.SyntaxKind.PlusPlusToken || node.operator == ts.SyntaxKind.MinusMinusToken) {
246
                if (this.isStateVariable(node.operand)) {
247
                    const scopeKind = this.currentKind()
248
                    if (isAnyMemoKind(scopeKind)) {
249
                        if (ts.isPropertyAccessExpression(node.operand)) {
250
                            this.reportChangingStateInMemo(node.operand)
251
                        }
252

253
                    }
254
                }
255
            }
256
            return this.visitEachChild(node)
257
        } else if (ts.isParameter(node)) {
258
            this.containsMemoCall(node)
259
            return this.visitEachChild(node)
260
        } else if (ts.isShorthandPropertyAssignment(node)) {
261
            const scopeKind = this.currentKind()
262
            if (isAnyMemoKind(scopeKind)) {
263
                this.reportShorthandPropertyInMemo(node)
264
            }
265
            return this.visitEachChild(node)
266
        } else if (ts.isReturnStatement(node)) {
267
            if (isMemoKind(this.currentKind())) {
268
                this.checkReturnType(node, this.functionScopes.peek()?.data)
269
            }
270
            return this.visitEachChild(node)
271
        } else {
272
            return this.visitEachChild(node)
273
        }
274
    }
275
}
276

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.