idlize
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
16import * as ts from 'typescript';
17import { MemoArgumentDetector } from './MemoArgumentDetector';
18import { ScopedVisitor } from './ScopedVisitor';
19import { Rewrite } from './transformation-context';
20import {
21FunctionKind,
22Tracer,
23isFunctionOrMethod,
24isAnyMemoKind,
25RuntimeNames,
26getDeclarationsByNode,
27isMemoKind
28} from "./util"
29
30enum MessageCode {
31CALLINNG_MEMO_FROM_NON_MEMO = 10001,
32SHORTHAND_PROPERTY_ASSIGNMENT_IS_NOT_ALLOWED = 10002,
33CAN_NOT_CALL_MEMO_FUNCTION_IN_PARAMETER_DEFAULT_VALUE = 10003,
34CHANGING_STATE_IN_MEMO_CONTEXT_IS_NOT_ALLOWED = 10004,
35CAN_NOT_ASSIGN_TO_MEMO_FUNCTION_PARAMETER = 10005,
36MEMO_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED = 10006,
37MEMO_ARROW_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED = 10007,
38}
39
40export class DiagnosticsVisitor extends ScopedVisitor<ts.FunctionLikeDeclaration> {
41constructor(
42public tracer: Tracer,
43public typechecker: ts.TypeChecker,
44public sourceFile: ts.SourceFile,
45public rewrite: Rewrite,
46public extras: ts.TransformerExtras|undefined,
47ctx: ts.TransformationContext
48) {
49super(rewrite.functionTable, ctx)
50}
51
52reportError(code: number, text: string, node: ts.Node) {
53const lineAndChar = ts.getLineAndCharacterOfPosition(this.sourceFile, node.pos)
54this.extras?.addDiagnostic({
55code: code,
56messageText: text,
57category: ts.DiagnosticCategory.Error,
58file: this.sourceFile,
59start: node.pos,
60length: node.end - node.pos
61})
62}
63
64reportCallingMemoFromNonMemo(node: ts.CallExpression) {
65const name = ts.isIdentifier(node.expression) ? `"${ts.idText(node.expression)}"` : ""
66this.reportError(
67MessageCode.CALLINNG_MEMO_FROM_NON_MEMO,
68`Calling a ${RuntimeNames.ANNOTATION} function ${name} from a non-${RuntimeNames.ANNOTATION} context`,
69node.expression,
70)
71}
72
73reportShorthandPropertyInMemo(node: ts.ShorthandPropertyAssignment) {
74const name = ts.idText(node.name)
75this.reportError(
76MessageCode.SHORTHAND_PROPERTY_ASSIGNMENT_IS_NOT_ALLOWED,
77`Shorthand property assignment is not allowed in ${RuntimeNames.ANNOTATION} code: ${name}`,
78node,
79)
80}
81
82functionName(node: ts.CallExpression): string {
83return this.nameOrFunction(node.expression)
84}
85
86nameOrFunction(node: ts.Node|undefined): string {
87let name : string
88if (node && ts.isIdentifier(node)) {
89name = `"` + ts.idText(node) + `"`
90} else {
91name = `function`
92}
93return name
94}
95
96reportMemoArgument(name: ts.BindingName, initializer: ts.CallExpression) {
97let parameter : string
98if(ts.isIdentifier(name)) {
99parameter = ` "` + ts.idText(name) + `"`
100}
101else { parameter = "" }
102
103this.reportError(
104MessageCode.CAN_NOT_CALL_MEMO_FUNCTION_IN_PARAMETER_DEFAULT_VALUE,
105`Can not call ${RuntimeNames.ANNOTATION} ${this.functionName(initializer)} in parameter${parameter} default value expression`,
106name,
107)
108}
109
110reportChangingStateInMemo(node: ts.PropertyAccessExpression) {
111const name = ts.isIdentifier(node.expression) ? `"${ts.idText(node.expression)}"` : ""
112this.reportError(
113MessageCode.CHANGING_STATE_IN_MEMO_CONTEXT_IS_NOT_ALLOWED,
114`Changing state ${name} in ${RuntimeNames.ANNOTATION} context is not allowed`,
115node.expression,
116)
117}
118
119reportAssignToParameterOfMemo(node: ts.Identifier) {
120const name = ts.idText(node)
121this.reportError(
122MessageCode.CAN_NOT_ASSIGN_TO_MEMO_FUNCTION_PARAMETER,
123`Can not assign to memo function parameter: ${name}`,
124node,
125)
126}
127
128isParameterOfMemo(node: ts.Expression): boolean {
129if (!ts.isIdentifier(node)) return false
130const declarations = getDeclarationsByNode(this.typechecker, node)
131if (!declarations || declarations.length == 0) return false
132const firstDeclaration = declarations[0]
133if (!ts.isParameter(firstDeclaration)) return false
134const parent = firstDeclaration.parent
135if (!ts.isFunctionDeclaration(parent)) return false
136
137return isMemoKind(this.declarationKind(parent))
138}
139
140isAssignment(node: ts.Node): boolean {
141if (node.kind >= ts.SyntaxKind.FirstAssignment && node.kind <= ts.SyntaxKind.LastAssignment) { return true }
142else { return false }
143}
144
145isStateVariable(node: ts.Node): boolean {
146if (!ts.isPropertyAccessExpression(node)) return false
147if (!ts.isIdentifier(node.name)) return false
148if (!(ts.idText(node.name) == "value")) return false
149const declarations = getDeclarationsByNode(this.typechecker, node)
150if(!declarations.length) return false
151let suspect = declarations[0].parent
152if (!ts.isInterfaceDeclaration(suspect)) return false
153if (!suspect.heritageClauses) return false
154for (const postsuspect of suspect.heritageClauses) {
155for (const parent of postsuspect.types) {
156if (ts.isIdentifier(parent.expression)) {
157if (ts.idText(parent.expression) == "State") return true
158}
159}
160}
161return false
162}
163
164containsMemoCall(parameter: ts.ParameterDeclaration) {
165const memoArgumentDetector = new MemoArgumentDetector(this.typechecker, this.rewrite, parameter, this.ctx)
166if (parameter.initializer) {
167const hasMemoArgument = memoArgumentDetector.usingMemoFunction(parameter.initializer)
168if (hasMemoArgument && ts.isCallExpression(memoArgumentDetector.memoArgument)) {
169this.reportMemoArgument(parameter.name, memoArgumentDetector.memoArgument)
170}
171}
172}
173
174checkReturnType(returnNode: ts.ReturnStatement, functionNode: ts.FunctionLikeDeclaration | undefined) {
175if (!functionNode) return
176if (returnNode.expression !== undefined && functionNode.type === undefined) {
177this.reportError(
178MessageCode.MEMO_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED,
179`${RuntimeNames.ANNOTATION} ${this.nameOrFunction(functionNode.name)} must have its type specified explicitly`,
180functionNode,
181)
182}
183}
184
185canFindVoidType(body: ts.Expression): boolean {
186const type = this.typechecker.getTypeAtLocation(body)
187const text = this.typechecker.typeToString(type)
188// Oh my. Can we detect void|undefined in a some faster way?
189return (text == "void" || text == "void | undefined" || text == "undefined | void" )
190}
191
192checkArrowReturnType(arrow: ts.ArrowFunction) {
193if (!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
197if (!ts.isBlock(arrow.body) && // The Block case is handled in checkReturnType()
198!this.canFindVoidType(arrow.body)
199) {
200this.reportError(
201MessageCode.MEMO_ARROW_MUST_HAVE_ITS_TYPE_EXPLICITLY_SPECIFIED,
202`${RuntimeNames.ANNOTATION} arrow must have its type specified explicitly`,
203arrow,
204)
205}
206}
207}
208
209indent = 0
210visitor(node: ts.Node): ts.Node {
211if (isFunctionOrMethod(node)) {
212const kind = this.declarationKind(node)
213if (ts.isArrowFunction(node) && isMemoKind(kind)) {
214this.checkArrowReturnType(node)
215}
216return this.withFunctionScope<ts.Node>(kind, node, () => {
217return this.visitEachChild(node)
218})
219} else if (ts.isCallExpression(node)) {
220const callKind = this.rewrite.callTable.get(node) ?? FunctionKind.REGULAR
221const scopeKind = this.currentKind()
222if (isAnyMemoKind(callKind) && !isAnyMemoKind(scopeKind)) {
223if (!this.rewrite.entryTable.has(node)) {
224this.reportCallingMemoFromNonMemo(node)
225}
226}
227return this.visitEachChild(node)
228} else if (ts.isBinaryExpression(node) &&
229this.isAssignment(node.operatorToken)) {
230
231if (this.isStateVariable(node.left)) {
232const scopeKind = this.currentKind()
233if (isAnyMemoKind(scopeKind)) {
234if (ts.isPropertyAccessExpression(node.left)) {
235this.reportChangingStateInMemo(node.left)
236}
237
238}
239}
240if (this.isParameterOfMemo(node.left)) {
241this.reportAssignToParameterOfMemo(node.left as ts.Identifier)
242}
243return this.visitEachChild(node)
244} else if (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) {
245if (node.operator == ts.SyntaxKind.PlusPlusToken || node.operator == ts.SyntaxKind.MinusMinusToken) {
246if (this.isStateVariable(node.operand)) {
247const scopeKind = this.currentKind()
248if (isAnyMemoKind(scopeKind)) {
249if (ts.isPropertyAccessExpression(node.operand)) {
250this.reportChangingStateInMemo(node.operand)
251}
252
253}
254}
255}
256return this.visitEachChild(node)
257} else if (ts.isParameter(node)) {
258this.containsMemoCall(node)
259return this.visitEachChild(node)
260} else if (ts.isShorthandPropertyAssignment(node)) {
261const scopeKind = this.currentKind()
262if (isAnyMemoKind(scopeKind)) {
263this.reportShorthandPropertyInMemo(node)
264}
265return this.visitEachChild(node)
266} else if (ts.isReturnStatement(node)) {
267if (isMemoKind(this.currentKind())) {
268this.checkReturnType(node, this.functionScopes.peek()?.data)
269}
270return this.visitEachChild(node)
271} else {
272return this.visitEachChild(node)
273}
274}
275}
276