1
2 /**
3 * Copyright (c) 2024 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 "parser/program/program.h"
20 #include "util/options.h"
21 #include "ir/expressions/binaryExpression.h"
22 #include "ir/base/methodDefinition.h"
23 #include "ir/base/scriptFunction.h"
24 #include "ir/statements/classDeclaration.h"
25 #include "ir/statements/expressionStatement.h"
26 #include "ir/statements/blockStatement.h"
27 #include "ir/expressions/assignmentExpression.h"
28 #include "ir/expressions/callExpression.h"
29 #include "ir/expressions/identifier.h"
30 #include "ir/expressions/memberExpression.h"
31 #include "ir/ets/etsTypeReferencePart.h"
32 #include "ir/ets/etsTypeReference.h"
33 #include "ir/base/classDefinition.h"
34 #include "ir/statements/forOfStatement.h"
35 #include "ir/statements/variableDeclarator.h"
36 #include "ir/statements/variableDeclaration.h"
37 #include "ir/expressions/updateExpression.h"
38
39 namespace ark::es2panda::checker {
40
AnalyzeClassDefForFinalModifier(const ir::ClassDefinition *classDef)41 void ETSWarningAnalyzer::AnalyzeClassDefForFinalModifier(const ir::ClassDefinition *classDef)
42 {
43 ASSERT(classDef != nullptr);
44
45 if (program_ == nullptr || classDef->IsFinal() || classDef->IsAbstract() || classDef->IsStatic() ||
46 classDef->IsGlobal() || classDef->IsExported()) {
47 return;
48 }
49
50 const auto statements = program_->Ast()->Statements();
51 for (const auto *it : statements) {
52 if (!it->IsClassDeclaration() ||
53 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) {
54 continue;
55 }
56
57 const auto *itAsClassDef = it->AsClassDeclaration()->Definition();
58
59 if (!itAsClassDef->IsGlobal()) {
60 const auto *superClass = itAsClassDef->Super();
61
62 if (superClass == nullptr) {
63 continue;
64 }
65
66 if (superClass->IsETSTypeReference() && superClass->AsETSTypeReference()->Part()->Name()->IsIdentifier() &&
67 superClass->AsETSTypeReference()->Part()->Name()->AsIdentifier()->Name() == classDef->Ident()->Name()) {
68 return;
69 }
70 }
71 }
72
73 ETSThrowWarning("Suggest 'final' modifier for class", classDef->Ident()->Start());
74 }
75
AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition *methodDef, const ir::ClassDefinition *classDef)76 void ETSWarningAnalyzer::AnalyzeClassMethodForFinalModifier(const ir::MethodDefinition *methodDef,
77 const ir::ClassDefinition *classDef)
78 {
79 ASSERT(methodDef != nullptr && classDef != nullptr);
80
81 if (methodDef->IsAbstract() || methodDef->IsStatic() || classDef->IsFinal() || program_ == nullptr ||
82 methodDef->IsFinal() || methodDef->IsConstructor() || classDef->IsGlobal()) {
83 return;
84 }
85
86 bool suggestFinal = true;
87
88 const auto statements = program_->Ast()->Statements();
89 for (const auto *it : statements) {
90 if (!it->IsClassDeclaration() || it->AsClassDeclaration()->Definition()->IsGlobal() ||
91 classDef->Ident()->Name() == it->AsClassDeclaration()->Definition()->Ident()->Name()) {
92 continue;
93 }
94
95 const auto *statementDef = it->AsClassDeclaration()->Definition();
96 for (const auto *bodyPart : statementDef->Body()) {
97 if (!bodyPart->IsMethodDefinition()) {
98 continue;
99 }
100 static auto classAsETSObject = classDef->TsType()->AsETSObjectType();
101 static auto potentialDescendant = statementDef->TsType()->AsETSObjectType();
102 if (!potentialDescendant->IsDescendantOf(classAsETSObject)) {
103 continue;
104 }
105 const util::StringView bodyMethodName =
106 ETSChecker::GetSignatureFromMethodDefinition(bodyPart->AsMethodDefinition())->Function()->Id()->Name();
107 if (bodyPart->IsOverride() && bodyMethodName != compiler::Signatures::CTOR &&
108 bodyMethodName == methodDef->Function()->Id()->Name()) {
109 suggestFinal = false;
110 break;
111 }
112 }
113 }
114
115 if (suggestFinal) {
116 ETSThrowWarning("Suggest 'final' modifier for method", methodDef->Function()->Start());
117 }
118 }
119
ETSWarningSuggestFinal(const ir::AstNode *node)120 void ETSWarningAnalyzer::ETSWarningSuggestFinal(const ir::AstNode *node)
121 {
122 if (node->IsClassDeclaration() && !program_->NodeContainsETSNolint(node, ETSWarnings::SUGGEST_FINAL)) {
123 if (node->AsClassDeclaration()->Definition()->IsClassDefinition()) {
124 AnalyzeClassDefForFinalModifier(node->AsClassDeclaration()->Definition());
125 }
126
127 const auto classBody = node->AsClassDeclaration()->Definition()->Body();
128 for (const auto *it : classBody) {
129 if (it->IsMethodDefinition()) {
130 AnalyzeClassMethodForFinalModifier(it->AsMethodDefinition(), node->AsClassDeclaration()->Definition());
131 }
132 }
133 }
134 node->Iterate([&](auto *childNode) { ETSWarningSuggestFinal(childNode); });
135 }
136
CheckTopLevelExpressions(const ir::Expression *expression)137 void ETSWarningAnalyzer::CheckTopLevelExpressions(const ir::Expression *expression)
138 {
139 if (expression->IsCallExpression()) {
140 const auto exprCallee = expression->AsCallExpression()->Callee();
141 lexer::SourcePosition pos = exprCallee->Start();
142 if (exprCallee->IsMemberExpression()) {
143 pos = exprCallee->AsMemberExpression()->Object()->Start();
144 ETSThrowWarning("Prohibit top-level statements", pos);
145 }
146 } else if (expression->IsAssignmentExpression()) {
147 const auto assignmentExpr = expression->AsAssignmentExpression();
148 ETSThrowWarning("Prohibit top-level statements", assignmentExpr->Left()->Start());
149 }
150 }
151
CheckProhibitedTopLevelStatements(const ir::Statement *statement)152 void ETSWarningAnalyzer::CheckProhibitedTopLevelStatements(const ir::Statement *statement)
153 {
154 switch (statement->Type()) {
155 case ir::AstNodeType::ARROW_FUNCTION_EXPRESSION:
156 case ir::AstNodeType::FUNCTION_DECLARATION:
157 case ir::AstNodeType::SCRIPT_FUNCTION:
158 case ir::AstNodeType::ETS_FUNCTION_TYPE:
159 case ir::AstNodeType::IMPORT_NAMESPACE_SPECIFIER:
160 case ir::AstNodeType::CLASS_DECLARATION:
161 case ir::AstNodeType::CLASS_EXPRESSION:
162 case ir::AstNodeType::VARIABLE_DECLARATION:
163 case ir::AstNodeType::CLASS_DEFINITION:
164 case ir::AstNodeType::CLASS_PROPERTY:
165 break;
166 default:
167 ETSThrowWarning("Prohibit top-level statements", statement->Start());
168 break;
169 }
170 }
171
ETSWarningsProhibitTopLevelStatements(const ir::AstNode *node)172 void ETSWarningAnalyzer::ETSWarningsProhibitTopLevelStatements(const ir::AstNode *node)
173 {
174 if (!node->IsClassDeclaration() ||
175 program_->NodeContainsETSNolint(node, ETSWarnings::PROHIBIT_TOP_LEVEL_STATEMENTS)) {
176 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); });
177 return;
178 }
179
180 const auto *classDef = node->AsClassDeclaration()->Definition();
181 if (!classDef->IsGlobal()) {
182 node->Iterate([&](auto *childNode) { ETSWarningsProhibitTopLevelStatements(childNode); });
183 return;
184 }
185
186 for (const auto *itBody : classDef->Body()) {
187 if (!itBody->IsMethodDefinition() ||
188 itBody->AsMethodDefinition()->Id()->Name() != compiler::Signatures::INIT_METHOD) {
189 continue;
190 }
191
192 for (const auto *statement :
193 itBody->AsMethodDefinition()->Function()->Body()->AsBlockStatement()->Statements()) {
194 if (program_->NodeContainsETSNolint(statement, ETSWarnings::PROHIBIT_TOP_LEVEL_STATEMENTS)) {
195 continue;
196 }
197
198 if (!statement->IsExpressionStatement()) {
199 CheckProhibitedTopLevelStatements(statement);
200 continue;
201 }
202
203 // Rewrite this part after fixing AST issue about tiop-level
204 CheckTopLevelExpressions(statement->AsExpressionStatement()->GetExpression());
205 }
206 }
207 }
208
ETSWarningBoostEqualityStatement(const ir::AstNode *node)209 void ETSWarningAnalyzer::ETSWarningBoostEqualityStatement(const ir::AstNode *node)
210 {
211 ASSERT(node != nullptr);
212
213 if (node->IsBinaryExpression() && !program_->NodeContainsETSNolint(node, ETSWarnings::BOOST_EQUALITY_STATEMENT)) {
214 const auto binExpr = node->AsBinaryExpression();
215 if (binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_EQUAL ||
216 binExpr->OperatorType() == lexer::TokenType::PUNCTUATOR_NOT_EQUAL) {
217 if (binExpr->Right()->IsNullLiteral() && !binExpr->Left()->IsNullLiteral()) {
218 ETSThrowWarning("Boost Equality Statement. Change sides of binary expression", node->Start());
219 }
220 }
221 }
222 node->Iterate([&](auto *childNode) { ETSWarningBoostEqualityStatement(childNode); });
223 }
224
ETSWarningRemoveAsync(const ir::AstNode *node)225 void ETSWarningAnalyzer::ETSWarningRemoveAsync(const ir::AstNode *node)
226 {
227 if (node->IsMethodDefinition() && !program_->NodeContainsETSNolint(node, ETSWarnings::REMOVE_ASYNC_FUNCTIONS)) {
228 const auto methodDefinition = node->AsMethodDefinition();
229 if (methodDefinition->IsAsync()) {
230 ETSThrowWarning("Replace asynchronous function with coroutine", methodDefinition->Start());
231 }
232 }
233 node->Iterate([&](auto *childNode) { ETSWarningRemoveAsync(childNode); });
234 }
235
ETSWarningRemoveLambda(const ir::AstNode *node)236 void ETSWarningAnalyzer::ETSWarningRemoveLambda(const ir::AstNode *node)
237 {
238 ASSERT(node != nullptr);
239
240 if (node->IsArrowFunctionExpression() && !program_->NodeContainsETSNolint(node, ETSWarnings::REMOVE_LAMBDA)) {
241 ETSThrowWarning("Replace the lambda function with a regular function", node->Start());
242 }
243 node->Iterate([&](auto *childNode) { ETSWarningRemoveLambda(childNode); });
244 }
245
CheckTypeOfBoxing(const ir::AstNode *node)246 void ETSWarningAnalyzer::CheckTypeOfBoxing(const ir::AstNode *node)
247 {
248 ASSERT(node != nullptr);
249 const auto flags = node->GetBoxingUnboxingFlags();
250 if ((flags & ir::BoxingUnboxingFlags::BOXING_FLAG) != 0) {
251 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::BOXING_FLAG)) {
252 case ir::BoxingUnboxingFlags::BOX_TO_INT:
253 ETSThrowWarning("Implicit Boxing to Int" + GetBoxingUnboxingType(node), node->Start());
254 break;
255 case ir::BoxingUnboxingFlags::BOX_TO_BOOLEAN:
256 ETSThrowWarning("Implicit Boxing to Boolean" + GetBoxingUnboxingType(node), node->Start());
257 break;
258 case ir::BoxingUnboxingFlags::BOX_TO_BYTE:
259 ETSThrowWarning("Implicit Boxing to Byte" + GetBoxingUnboxingType(node), node->Start());
260 break;
261 case ir::BoxingUnboxingFlags::BOX_TO_CHAR:
262 ETSThrowWarning("Implicit Boxing to Char" + GetBoxingUnboxingType(node), node->Start());
263 break;
264 case ir::BoxingUnboxingFlags::BOX_TO_DOUBLE:
265 ETSThrowWarning("Implicit Boxing to Double" + GetBoxingUnboxingType(node), node->Start());
266 break;
267 case ir::BoxingUnboxingFlags::BOX_TO_FLOAT:
268 ETSThrowWarning("Implicit Boxing to Float" + GetBoxingUnboxingType(node), node->Start());
269 break;
270 case ir::BoxingUnboxingFlags::BOX_TO_LONG:
271 ETSThrowWarning("Implicit Boxing to Long" + GetBoxingUnboxingType(node), node->Start());
272 break;
273 case ir::BoxingUnboxingFlags::BOX_TO_SHORT:
274 ETSThrowWarning("Implicit Boxing to Short" + GetBoxingUnboxingType(node), node->Start());
275 break;
276 default:
277 break;
278 }
279 }
280 }
281
CheckTypeOfUnboxing(const ir::AstNode *node)282 void ETSWarningAnalyzer::CheckTypeOfUnboxing(const ir::AstNode *node)
283 {
284 ASSERT(node != nullptr);
285 const auto flags = node->GetBoxingUnboxingFlags();
286 if ((flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG) != 0) {
287 switch (static_cast<ir::BoxingUnboxingFlags>(flags & ir::BoxingUnboxingFlags::UNBOXING_FLAG)) {
288 case ir::BoxingUnboxingFlags::UNBOX_TO_INT:
289 ETSThrowWarning("Implicit Unboxing to int" + GetBoxingUnboxingType(node), node->Start());
290 break;
291 case ir::BoxingUnboxingFlags::UNBOX_TO_BOOLEAN:
292 ETSThrowWarning("Implicit Unboxing to boolean" + GetBoxingUnboxingType(node), node->Start());
293 break;
294 case ir::BoxingUnboxingFlags::UNBOX_TO_BYTE:
295 ETSThrowWarning("Implicit Unboxing to byte" + GetBoxingUnboxingType(node), node->Start());
296 break;
297 case ir::BoxingUnboxingFlags::UNBOX_TO_CHAR:
298 ETSThrowWarning("Implicit Unboxing to char" + GetBoxingUnboxingType(node), node->Start());
299 break;
300 case ir::BoxingUnboxingFlags::UNBOX_TO_DOUBLE:
301 ETSThrowWarning("Implicit Unboxing to double" + GetBoxingUnboxingType(node), node->Start());
302 break;
303 case ir::BoxingUnboxingFlags::UNBOX_TO_FLOAT:
304 ETSThrowWarning("Implicit Unboxing to float" + GetBoxingUnboxingType(node), node->Start());
305 break;
306 case ir::BoxingUnboxingFlags::UNBOX_TO_LONG:
307 ETSThrowWarning("Implicit Unboxing to long" + GetBoxingUnboxingType(node), node->Start());
308 break;
309 case ir::BoxingUnboxingFlags::UNBOX_TO_SHORT:
310 ETSThrowWarning("Implicit Unboxing to short" + GetBoxingUnboxingType(node), node->Start());
311 break;
312 default:
313 break;
314 }
315 }
316 }
317
CheckTypeOfBoxingUnboxing(const ir::AstNode *node)318 void ETSWarningAnalyzer::CheckTypeOfBoxingUnboxing(const ir::AstNode *node)
319 {
320 ASSERT(node != nullptr);
321
322 CheckTypeOfBoxing(node);
323 CheckTypeOfUnboxing(node);
324 }
325
GetBoxingUnboxingType(const ir::AstNode *node)326 std::string ETSWarningAnalyzer::GetBoxingUnboxingType(const ir::AstNode *node)
327 {
328 ASSERT(node->Parent() != nullptr);
329 switch (node->Parent()->Type()) {
330 case ir::AstNodeType::VARIABLE_DECLARATOR: {
331 return " in Variable Declaration";
332 }
333 case ir::AstNodeType::CALL_EXPRESSION: {
334 return " in Call Method/Function";
335 }
336 case ir::AstNodeType::SWITCH_STATEMENT: {
337 return " in Switch-case Statement";
338 }
339 case ir::AstNodeType::ASSIGNMENT_EXPRESSION: {
340 return " in Assignment Expression";
341 }
342 case ir::AstNodeType::BINARY_EXPRESSION: {
343 return " in Binary Expression";
344 }
345 case ir::AstNodeType::UNARY_EXPRESSION: {
346 return " in Unary Expression";
347 }
348 case ir::AstNodeType::UPDATE_EXPRESSION: {
349 return " in Update Expression";
350 }
351 case ir::AstNodeType::MEMBER_EXPRESSION: {
352 return " in Member Expression";
353 }
354 default:
355 return "";
356 }
357 }
358
ETSWarningImplicitBoxingUnboxing(const ir::AstNode *node)359 void ETSWarningAnalyzer::ETSWarningImplicitBoxingUnboxing(const ir::AstNode *node)
360 {
361 ASSERT(node != nullptr);
362
363 switch (node->Type()) {
364 case ir::AstNodeType::VARIABLE_DECLARATOR:
365 case ir::AstNodeType::SWITCH_STATEMENT:
366 case ir::AstNodeType::CALL_EXPRESSION:
367 case ir::AstNodeType::BINARY_EXPRESSION:
368 case ir::AstNodeType::ASSIGNMENT_EXPRESSION:
369 case ir::AstNodeType::UNARY_EXPRESSION:
370 case ir::AstNodeType::UPDATE_EXPRESSION:
371 case ir::AstNodeType::MEMBER_EXPRESSION: {
372 if (!program_->NodeContainsETSNolint(node, ETSWarnings::IMPLICIT_BOXING_UNBOXING)) {
373 node->Iterate([this](auto *childNode) { CheckTypeOfBoxingUnboxing(childNode); });
374 }
375 break;
376 }
377 default: {
378 break;
379 }
380 }
381
382 node->Iterate([&](auto *childNode) { ETSWarningImplicitBoxingUnboxing(childNode); });
383 }
384
ETSThrowWarning(const std::string &message, const lexer::SourcePosition &pos)385 void ETSWarningAnalyzer::ETSThrowWarning(const std::string &message, const lexer::SourcePosition &pos)
386 {
387 lexer::LineIndex index(program_->SourceCode());
388 lexer::SourceLocation location = index.GetLocation(pos);
389
390 if (etsWerror_) {
391 throw Error(ErrorType::ETS_WARNING, ark::es2panda::util::BaseName(program_->SourceFilePath().Utf8()), message,
392 location.line, location.col);
393 }
394
395 std::cout << "ETS Warning: " << message << "."
396 << " [" << ark::es2panda::util::BaseName(program_->SourceFilePath().Utf8()) << ":" << location.line << ":"
397 << location.col << "]" << std::endl;
398 }
399
400 } // namespace ark::es2panda::checker
401