1/*
2 * Copyright (c) 2024 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#include "helpers.h"
17#include "identifierHasVariable.h"
18#include "variableHasScope.h"
19#include "ir/base/scriptFunction.h"
20#include "ir/ts/tsEnumDeclaration.h"
21#include "ir/typeNode.h"
22
23namespace ark::es2panda::compiler::ast_verifier {
24
25CheckResult VariableHasScope::operator()(CheckContext &ctx, const ir::AstNode *ast)
26{
27    if (!ast->IsIdentifier()) {
28        // Check invariant of Identifier only
29        return {CheckDecision::CORRECT, CheckAction::CONTINUE};
30    }
31
32    // we will check invariant for only local variables of identifiers
33    if (const auto maybeVar = GetLocalScopeVariable(allocator_, ctx, ast); maybeVar.has_value()) {
34        const auto var = *maybeVar;
35        const auto scope = var->GetScope();
36        if (scope == nullptr) {
37            ctx.AddCheckMessage("NULL_SCOPE_LOCAL_VAR", *ast, ast->Start());
38            return {CheckDecision::INCORRECT, CheckAction::CONTINUE};
39        }
40
41        auto result = std::make_tuple(CheckDecision::CORRECT, CheckAction::CONTINUE);
42        if (!ScopeEncloseVariable(ctx, var)) {
43            result = {CheckDecision::INCORRECT, CheckAction::CONTINUE};
44        }
45
46        return result;
47    }
48
49    return {CheckDecision::CORRECT, CheckAction::CONTINUE};
50}
51
52std::optional<varbinder::LocalVariable *> VariableHasScope::GetLocalScopeVariable(ArenaAllocator &allocator,
53                                                                                  CheckContext &ctx,
54                                                                                  const ir::AstNode *ast)
55{
56    if (!ast->IsIdentifier()) {
57        return std::nullopt;
58    }
59
60    auto invariantHasVariable = IdentifierHasVariable {allocator};
61    const auto variable = ast->AsIdentifier()->Variable();
62    const auto [decision, action] = invariantHasVariable(ctx, ast);
63
64    if (variable == nullptr) {
65        // NOTE(kkonkuznetsov): variable should not be null
66        // but currently some identifiers do not have variables,
67        // see exceptions in IdentifierHasVariable check
68        return std::nullopt;
69    }
70
71    if (decision == CheckDecision::CORRECT && variable->IsLocalVariable()) {
72        const auto localVar = variable->AsLocalVariable();
73        if (localVar->HasFlag(varbinder::VariableFlags::LOCAL)) {
74            return localVar;
75        }
76    }
77
78    return std::nullopt;
79}
80
81bool VariableHasScope::ScopeEncloseVariable(CheckContext &ctx, const varbinder::LocalVariable *var)
82{
83    ASSERT(var);
84
85    const auto scope = var->GetScope();
86    if (scope == nullptr || var->Declaration() == nullptr) {
87        return true;
88    }
89
90    const auto node = var->Declaration()->Node();
91    if (node == nullptr) {
92        return true;
93    }
94
95    const auto varStart = node->Start();
96    bool isOk = true;
97    if (scope->Bindings().count(var->Name()) == 0) {
98        ctx.AddCheckMessage("SCOPE_DO_NOT_ENCLOSE_LOCAL_VAR", *node, varStart);
99        isOk = false;
100    }
101
102    const auto scopeNode = scope->Node();
103    const auto varNode = node;
104    bool skip = CheckAstExceptions(varNode);
105
106    if (!IsContainedIn(varNode, scopeNode) || scopeNode == nullptr) {
107        if (!skip) {
108            ctx.AddCheckMessage("SCOPE_NODE_DONT_DOMINATE_VAR_NODE", *node, varStart);
109            isOk = false;
110        }
111    }
112
113    const auto &decls = scope->Decls();
114    const auto declDominate = std::count(decls.begin(), decls.end(), var->Declaration());
115    if (declDominate == 0) {
116        if (!skip) {
117            ctx.AddCheckMessage("SCOPE_DECL_DONT_DOMINATE_VAR_DECL", *node, varStart);
118            isOk = false;
119        }
120    }
121
122    return isOk;
123}
124
125bool VariableHasScope::CheckAstExceptions(const ir::AstNode *ast)
126{
127    // Labels are attached to loop scopes,
128    // however label identifier is outside of loop.
129    // Example:
130    //
131    // ```
132    // loop: for (let i = 0; i < 10; i++) {
133    // }
134    // ```
135    return ast->IsLabelledStatement();
136}
137
138}  // namespace ark::es2panda::compiler::ast_verifier
139