1
2 /**
3 * Copyright (c) 2024-2025 Huawei Device Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "etsWarningAnalyzer.h"
18
19 #include "generated/diagnostic.h"
20 #include "parser/program/program.h"
21 #include "util/options.h"
22 #include "ir/expressions/binaryExpression.h"
23 #include "ir/base/methodDefinition.h"
24 #include "ir/base/scriptFunction.h"
25 #include "ir/statements/classDeclaration.h"
26 #include "ir/statements/expressionStatement.h"
27 #include "ir/statements/blockStatement.h"
28 #include "ir/expressions/assignmentExpression.h"
29 #include "ir/expressions/callExpression.h"
30 #include "ir/expressions/identifier.h"
31 #include "ir/expressions/memberExpression.h"
32 #include "ir/ets/etsTypeReferencePart.h"
33 #include "ir/ets/etsTypeReference.h"
34 #include "ir/base/classDefinition.h"
35 #include "ir/statements/forOfStatement.h"
36 #include "ir/statements/variableDeclarator.h"
37 #include "ir/statements/variableDeclaration.h"
38 #include "ir/expressions/updateExpression.h"
39
40 namespace ark::es2panda::checker {
41
AnalyzeClassDefForFinalModifier(const ir::ClassDefinition * classDef)42 void ETSWarningAnalyzer::AnalyzeClassDefForFinalModifier(const ir::ClassDefinition *classDef)
43 {
44 ES2PANDA_ASSERT(classDef != nullptr);
45
46 if (program_ == nullptr || classDef->IsFinal() || classDef->IsAbstract() || classDef->IsStatic() ||
47 classDef->IsGlobal() || classDef->IsExported() || classDef->HasExportAlias()) {
48 return;
49 }
50
51 const auto statements = program_->Ast()->Statements();
52 for (const auto *it : statements) {
53 if (!it->IsClassDeclaration() ||
54 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) {
55 continue;
56 }
57
58 const auto *itAsClassDef = it->AsClassDeclaration()->Definition();
59
60 if (!itAsClassDef->IsGlobal()) {
61 const auto *superClass = itAsClassDef->Super();
62
63 if (superClass == nullptr) {
64 continue;
65 }
66
67 if (superClass->IsETSTypeReference() &&
68 superClass->AsETSTypeReference()->Part()->GetIdent()->Name() == classDef->Ident()->Name()) {
69 return;
70 }
71 }
72 }
73
74 LogWarning(diagnostic::SUGGEST_FINAL_MODIFIER_FOR_CLASS, classDef->Ident()->Start());
75 }
76
AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition * methodDef,const ir::ClassDefinition * classDef)77 void ETSWarningAnalyzer::AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition *methodDef,
78 const ir::ClassDefinition *classDef)
79 {
80 ES2PANDA_ASSERT(methodDef != nullptr && classDef != nullptr);
81
82 if (methodDef->IsAbstract() || methodDef->IsStatic() || classDef->IsFinal() || program_ == nullptr ||
83 methodDef->IsFinal() || methodDef->IsConstructor() || classDef->IsGlobal()) {
84 return;
85 }
86
87 bool suggestFinal = true;
88
89 const auto statements = program_->Ast()->Statements();
90 for (const auto *it : statements) {
91 if (!it->IsClassDeclaration() || it->AsClassDeclaration()->Definition()->IsGlobal() ||
92 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) {
93 continue;
94 }
95
96 const auto *statementDef = it->AsClassDeclaration()->Definition();
97 for (const auto *bodyPart : statementDef->Body()) {
98 if (!bodyPart->IsMethodDefinition()) {
99 continue;
100 }
101 static auto classAsETSObject = classDef->TsType()->AsETSObjectType();
102 static auto potentialDescendant = statementDef->TsType()->AsETSObjectType();
103 if (!potentialDescendant->IsDescendantOf(classAsETSObject)) {
104 continue;
105 }
106 auto signature = ETSChecker::GetSignatureFromMethodDefinition(bodyPart->AsMethodDefinition());
107 ES2PANDA_ASSERT(signature != nullptr);
108 const util::StringView bodyMethodName = signature->Function()->Id()->Name();
109 const auto *func = methodDef->Function();
110 ES2PANDA_ASSERT(func != nullptr);
111 if (bodyPart->IsOverride() && bodyMethodName != compiler::Signatures::CTOR &&
112 bodyMethodName == func->Id()->Name()) {
113 suggestFinal = false;
114 break;
115 }
116 }
117 }
118
119 if (suggestFinal) {
120 LogWarning(diagnostic::SUGGEST_FINAL_MODIFIER_FOR_METHOD, classDef->Ident()->Start());
121 }
122 }
123
ETSWarningSuggestFinal(const ir::AstNode * node)124 void ETSWarningAnalyzer::ETSWarningSuggestFinal(const ir::AstNode *node)
125 {
126 if (node->IsClassDeclaration() && !program_->NodeContainsETSNolint(node, ETSWarnings::ETS_SUGGEST_FINAL)) {
127 if (node->AsClassDeclaration()->Definition()->IsClassDefinition()) {
128 AnalyzeClassDefForFinalModifier(node->AsClassDeclaration()->Definition());
129 }
130
131 const auto classBody = node->AsClassDeclaration()->Definition()->Body();
132 for (const auto *it : classBody) {
133 if (it->IsMethodDefinition()) {
134 AnalyzeClassMethodForFinalModifier(it->AsMethodDefinition(), node->AsClassDeclaration()->Definition());
135 }
136 }
137 }
138 node->Iterate([&](auto *childNode) { ETSWarningSuggestFinal(childNode); });
139 }
140
CheckTopLevelExpressions(const ir::Expression * expression)141 void ETSWarningAnalyzer::CheckTopLevelExpressions(const ir::Expression *expression)
142 {
143 if (expression->IsCallExpression()) {
144 const auto exprCallee = expression->AsCallExpression()->Callee();
145 lexer::SourcePosition pos = exprCallee->Start();
146 if (exprCallee->IsMemberExpression()) {
147 pos = exprCallee->AsMemberExpression()->Object()->Start();
148 LogWarning(diagnostic::PROHIBIT_TOP_LEVEL_STATEMENTS, pos);
149 }
150 } else if (expression->IsAssignmentExpression()) {
151 const auto assignmentExpr = expression->AsAssignmentExpression();
152 LogWarning(diagnostic::PROHIBIT_TOP_LEVEL_STATEMENTS, assignmentExpr->Left()->Start());
153 }
154 }
155
CheckProhibitedTopLevelStatements(const ir::Statement * statement)156 void ETSWarningAnalyzer::CheckProhibitedTopLevelStatements(const ir::Statement *statement)
157 {
158 switch (statement->Type()) {
159 case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
160 case ir::AstNodeType::FUNCTION_DECLARATION:
161 case ir::AstNodeType::SCRIPT_FUNCTION:
162 case ir::AstNodeType::ETS_FUNCTION_TYPE:
163 case ir::AstNodeType::IMPORT_NAMESPACE_SPECIFIER:
164 case ir::AstNodeType::CLASS_DECLARATION:
165 case ir::AstNodeType::CLASS_EXPRESSION:
166 case ir::AstNodeType::VARIABLE_DECLARATION:
167 case ir::AstNodeType::CLASS_DEFINITION:
168 case ir::AstNodeType::CLASS_PROPERTY:
169 break;
170 default:
171 LogWarning(diagnostic::PROHIBIT_TOP_LEVEL_STATEMENTS, statement->Start());
172 break;
173 }
174 }
175
ETSWarningAnnotationUnusedGenericAliasWarn(const ir::AstNode * node)176 void ETSWarningAnalyzer::ETSWarningAnnotationUnusedGenericAliasWarn(const ir::AstNode *node)
177 {
178 if (!node->IsTSTypeAliasDeclaration()) {
179 node->Iterate([&](auto *childNode) { ETSWarningAnnotationUnusedGenericAliasWarn(childNode); });
180 return;
181 }
182
183 auto st = node->AsTSTypeAliasDeclaration();
184 for (auto *const param : st->TypeParams()->Params()) {
185 const auto *const res = st->TypeAnnotation()->FindChild([¶m](const ir::AstNode *const astNode) {
186 if (!astNode->IsIdentifier()) {
187 return false;
188 }
189
190 return param->Name()->AsIdentifier()->Variable() == astNode->AsIdentifier()->Variable();
191 });
192
193 if (res == nullptr) {
194 util::DiagnosticMessageParams diagnosticParams = {param->Name()->Name()};
195 LogWarning(diagnostic::ANNOTATION_UNUSED_GENERIC_ALIAS_WARN, diagnosticParams, param->Start());
196 return;
197 }
198 }
199 }
200
ETSWarningsProhibitTopLevelStatements(const ir::AstNode * node)201 void ETSWarningAnalyzer::ETSWarningsProhibitTopLevelStatements(const ir::AstNode *node)
202 {
203 if (!node->IsClassDeclaration() ||
204 program_->NodeContainsETSNolint(node, ETSWarnings::ETS_PROHIBIT_TOP_LEVEL_STATEMENTS)) {
205 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); });
206 return;
207 }
208
209 const auto *classDef = node->AsClassDeclaration()->Definition();
210 if (!classDef->IsGlobal()) {
211 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); });
212 return;
213 }
214
215 for (const auto *itBody : classDef->Body()) {
216 if (!itBody->IsMethodDefinition() || itBody->AsMethodDefinition()->Id() == nullptr ||
217 itBody->AsMethodDefinition()->Id()->Name() != compiler::Signatures::INIT_METHOD) {
218 continue;
219 }
220 const auto *func = itBody->AsMethodDefinition()->Function();
221 ES2PANDA_ASSERT(func != nullptr);
222 for (const auto *statement : func->Body()->AsBlockStatement()->Statements()) {
223 if (program_->NodeContainsETSNolint(statement, ETSWarnings::ETS_PROHIBIT_TOP_LEVEL_STATEMENTS)) {
224 continue;
225 }
226
227 if (!statement->IsExpressionStatement()) {
228 CheckProhibitedTopLevelStatements(statement);
229 continue;
230 }
231
232 // Rewrite this part after fixing AST issue about tiop-level
233 CheckTopLevelExpressions(statement->AsExpressionStatement()->GetExpression());
234 }
235 }
236 }
237
ETSWarningBoostEqualityStatement(const ir::AstNode * node)238 void ETSWarningAnalyzer::ETSWarningBoostEqualityStatement(const ir::AstNode *node)
239 {
240 ES2PANDA_ASSERT(node != nullptr);
241
242 if (node->IsBinaryExpression() &&
243 !program_->NodeContainsETSNolint(node, ETSWarnings::ETS_BOOST_EQUALITY_STATEMENT)) {
244 const auto binExpr = node->AsBinaryExpression();
245 if (binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_EQUAL ||
246 binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
247 if (binExpr->Right()->IsNullLiteral() && !binExpr->Left()->IsNullLiteral()) {
248 LogWarning(diagnostic::BOOST_EQUALITY_STATEMENT, node->Start());
249 }
250 }
251 }
252 node->Iterate([&](auto *childNode) { ETSWarningBoostEqualityStatement(childNode); });
253 }
254
ETSWarningRemoveAsync(const ir::AstNode * node)255 void ETSWarningAnalyzer::ETSWarningRemoveAsync(const ir::AstNode *node)
256 {
257 if (node->IsMethodDefinition() && !program_->NodeContainsETSNolint(node, ETSWarnings::ETS_REMOVE_ASYNC)) {
258 const auto methodDefinition = node->AsMethodDefinition();
259 if (methodDefinition->IsAsync()) {
260 LogWarning(diagnostic::REPLACE_ASYNC_FUNCTION_WITH_COROUTINE, methodDefinition->Start());
261 }
262 }
263 node->Iterate([&](auto *childNode) { ETSWarningRemoveAsync(childNode); });
264 }
265
ETSWarningRemoveLambda(const ir::AstNode * node)266 void ETSWarningAnalyzer::ETSWarningRemoveLambda(const ir::AstNode *node)
267 {
268 ES2PANDA_ASSERT(node != nullptr);
269
270 if (node->IsArrowFunctionExpression() && !program_->NodeContainsETSNolint(node, ETSWarnings::ETS_REMOVE_LAMBDA)) {
271 LogWarning(diagnostic::REPLACE_LAMBDA_FUNCTION_WITH_REGULAR_FUNCTION, node->Start());
272 }
273 node->Iterate([&](auto *childNode) { ETSWarningRemoveLambda(childNode); });
274 }
275
CheckTypeOfBoxing(const ir::AstNode * node)276 void ETSWarningAnalyzer::CheckTypeOfBoxing(const ir::AstNode *node)
277 {
278 ES2PANDA_ASSERT(node != nullptr);
279 const auto flags = node->GetBoxingUnboxingFlags();
280 if ((flags & ir::BoxingUnboxingFlags::BOXING_FLAG) != 0) {
281 std::string diagnosticParam;
282 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::BOXING_FLAG)) {
283 case ir::BoxingUnboxingFlags::BOX_TO_INT:
284 diagnosticParam = "Int";
285 break;
286 case ir::BoxingUnboxingFlags::BOX_TO_BOOLEAN:
287 diagnosticParam = "Boolean";
288 break;
289 case ir::BoxingUnboxingFlags::BOX_TO_BYTE:
290 diagnosticParam = "Byte";
291 break;
292 case ir::BoxingUnboxingFlags::BOX_TO_CHAR:
293 diagnosticParam = "Char";
294 break;
295 case ir::BoxingUnboxingFlags::BOX_TO_DOUBLE:
296 diagnosticParam = "Double";
297 break;
298 case ir::BoxingUnboxingFlags::BOX_TO_FLOAT:
299 diagnosticParam = "Float";
300 break;
301 case ir::BoxingUnboxingFlags::BOX_TO_LONG:
302 diagnosticParam = "Long";
303 break;
304 case ir::BoxingUnboxingFlags::BOX_TO_SHORT:
305 diagnosticParam = "Short";
306 break;
307 default:
308 break;
309 }
310
311 if (!diagnosticParam.empty()) {
312 util::DiagnosticMessageParams diagnosticParams = {diagnosticParam, GetBoxingUnboxingType(node)};
313 LogWarning(diagnostic::IMPLICIT_BOXING_TO, diagnosticParams, node->Start());
314 }
315 }
316 }
317
CheckTypeOfUnboxing(const ir::AstNode * node)318 void ETSWarningAnalyzer::CheckTypeOfUnboxing(const ir::AstNode *node)
319 {
320 ES2PANDA_ASSERT(node != nullptr);
321 const auto flags = node->GetBoxingUnboxingFlags();
322 if ((flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG) != 0) {
323 std::string diagnosticParam;
324 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG)) {
325 case ir::BoxingUnboxingFlags::UNBOX_TO_INT:
326 diagnosticParam = "Int";
327 break;
328 case ir::BoxingUnboxingFlags::UNBOX_TO_BOOLEAN:
329 diagnosticParam = "Boolean";
330 break;
331 case ir::BoxingUnboxingFlags::UNBOX_TO_BYTE:
332 diagnosticParam = "Byte";
333 break;
334 case ir::BoxingUnboxingFlags::UNBOX_TO_CHAR:
335 diagnosticParam = "Char";
336 break;
337 case ir::BoxingUnboxingFlags::UNBOX_TO_DOUBLE:
338 diagnosticParam = "Double";
339 break;
340 case ir::BoxingUnboxingFlags::UNBOX_TO_FLOAT:
341 diagnosticParam = "Float";
342 break;
343 case ir::BoxingUnboxingFlags::UNBOX_TO_LONG:
344 diagnosticParam = "Long";
345 break;
346 case ir::BoxingUnboxingFlags::UNBOX_TO_SHORT:
347 diagnosticParam = "Short";
348 break;
349 default:
350 break;
351 }
352
353 if (!diagnosticParam.empty()) {
354 util::DiagnosticMessageParams diagnosticParams = {diagnosticParam, GetBoxingUnboxingType(node)};
355 LogWarning(diagnostic::IMPLICIT_BOXING_TO, diagnosticParams, node->Start());
356 }
357 }
358 }
359
CheckTypeOfBoxingUnboxing(const ir::AstNode * node)360 void ETSWarningAnalyzer::CheckTypeOfBoxingUnboxing(const ir::AstNode *node)
361 {
362 ES2PANDA_ASSERT(node != nullptr);
363
364 CheckTypeOfBoxing(node);
365 CheckTypeOfUnboxing(node);
366 }
367
GetBoxingUnboxingType(const ir::AstNode * node)368 std::string ETSWarningAnalyzer::GetBoxingUnboxingType(const ir::AstNode *node)
369 {
370 ES2PANDA_ASSERT(node->Parent() != nullptr);
371 switch (node->Parent()->Type()) {
372 case ir::AstNodeType::VARIABLE_DECLARATOR: {
373 return " in Variable Declaration";
374 }
375 case ir::AstNodeType::CALL_EXPRESSION: {
376 return " in Call Method/Function";
377 }
378 case ir::AstNodeType::SWITCH_STATEMENT: {
379 return " in Switch-case Statement";
380 }
381 case ir::AstNodeType::ASSIGNMENT_EXPRESSION: {
382 return " in Assignment Expression";
383 }
384 case ir::AstNodeType::BINARY_EXPRESSION: {
385 return " in Binary Expression";
386 }
387 case ir::AstNodeType::UNARY_EXPRESSION: {
388 return " in Unary Expression";
389 }
390 case ir::AstNodeType::UPDATE_EXPRESSION: {
391 return " in Update Expression";
392 }
393 case ir::AstNodeType::MEMBER_EXPRESSION: {
394 return " in Member Expression";
395 }
396 default:
397 return "";
398 }
399 }
400
ETSWarningImplicitBoxingUnboxing(const ir::AstNode * node)401 void ETSWarningAnalyzer::ETSWarningImplicitBoxingUnboxing(const ir::AstNode *node)
402 {
403 ES2PANDA_ASSERT(node != nullptr);
404
405 switch (node->Type()) {
406 case ir::AstNodeType::VARIABLE_DECLARATOR:
407 case ir::AstNodeType::SWITCH_STATEMENT:
408 case ir::AstNodeType::CALL_EXPRESSION:
409 case ir::AstNodeType::BINARY_EXPRESSION:
410 case ir::AstNodeType::ASSIGNMENT_EXPRESSION:
411 case ir::AstNodeType::UNARY_EXPRESSION:
412 case ir::AstNodeType::UPDATE_EXPRESSION:
413 case ir::AstNodeType::MEMBER_EXPRESSION: {
414 if (!program_->NodeContainsETSNolint(node, ETSWarnings::ETS_IMPLICIT_BOXING_UNBOXING)) {
415 node->Iterate([this](auto *childNode) { CheckTypeOfBoxingUnboxing(childNode); });
416 }
417 break;
418 }
419 default: {
420 break;
421 }
422 }
423
424 node->Iterate([&](auto *childNode) { ETSWarningImplicitBoxingUnboxing(childNode); });
425 }
426
LogWarning(const diagnostic::DiagnosticKind & diagnostic,const lexer::SourcePosition & position) const427 void ETSWarningAnalyzer::LogWarning(const diagnostic::DiagnosticKind &diagnostic,
428 const lexer::SourcePosition &position) const
429 {
430 this->LogWarning(diagnostic, {}, position);
431 }
432
LogWarning(const diagnostic::DiagnosticKind & diagnostic,const util::DiagnosticMessageParams & diagnosticParams,const lexer::SourcePosition & position) const433 void ETSWarningAnalyzer::LogWarning(const diagnostic::DiagnosticKind &diagnostic,
434 const util::DiagnosticMessageParams &diagnosticParams,
435 const lexer::SourcePosition &position) const
436 {
437 diagnosticEngine_.LogDiagnostic(diagnostic, diagnosticParams, position);
438 }
439
440 } // namespace ark::es2panda::checker
441