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
39namespace ark::es2panda::checker {
40
41void 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
76void 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
120void 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
137void 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
152void 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
172void 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
209void 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
225void 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
236void 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
246void 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
282void 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
318void ETSWarningAnalyzer::CheckTypeOfBoxingUnboxing(const ir::AstNode *node)
319{
320    ASSERT(node != nullptr);
321
322    CheckTypeOfBoxing(node);
323    CheckTypeOfUnboxing(node);
324}
325
326std::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
359void 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
385void 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