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 "ETSparser.h"
17#include "ETSNolintParser.h"
18
19#include "lexer/lexer.h"
20#include "ir/module/importNamespaceSpecifier.h"
21
22namespace ark::es2panda::parser {
23ETSNolintParser::ETSNolintParser(const ParserImpl *mainParser) : parser_(mainParser)
24{
25    line_ = parser_->Lexer()->Line();
26
27    warningsMap_ = {
28        {std::u32string(U"ets-implicit-boxing-unboxing"), ETSWarnings::IMPLICIT_BOXING_UNBOXING},
29        {std::u32string(U"ets-prohibit-top-level-statements"), ETSWarnings::PROHIBIT_TOP_LEVEL_STATEMENTS},
30        {std::u32string(U"ets-boost-equality-statement"), ETSWarnings::BOOST_EQUALITY_STATEMENT},
31        {std::u32string(U"ets-remove-lambda"), ETSWarnings::REMOVE_LAMBDA},
32        {std::u32string(U"ets-suggest-final"), ETSWarnings::SUGGEST_FINAL},
33        {std::u32string(U"ets-remove-async"), ETSWarnings::REMOVE_ASYNC_FUNCTIONS},
34    };
35}
36
37void ETSNolintParser::SetStartPos()
38{
39    line_ = parser_->Lexer()->Pos().Line();
40    startPos_ = parser_->Lexer()->Pos().Iterator().Index();
41    posOffset_ = startPos_;
42
43    BackwardSymbol(startPos_);
44}
45
46void ETSNolintParser::CollectETSNolints()
47{
48    SetStartPos();
49    char32_t cp = PeekSymbol();
50
51    while (cp != lexer::LEX_CHAR_EOS && cp != lexer::UNICODE_CODE_POINT_MAX && cp != lexer::UNICODE_INVALID_CP) {
52        if (!IsEtsNolint()) {
53            NextSymbol();
54            cp = PeekSymbol();
55            continue;
56        }
57        std::size_t line = line_;
58        std::set<ETSWarnings> collection;
59        if (IsNextLine()) {
60            collection = ParseETSNolintArgs();
61            line += 1;
62        } else if (IsBegin()) {
63            collection = ParseETSNolintArgs();
64            for (const auto it : collection) {
65                applyingCollection_.insert(it);
66            }
67        } else if (IsEnd()) {
68            collection = ParseETSNolintArgs();
69
70            for (const auto it : collection) {
71                applyingCollection_.erase(it);
72            }
73            cp = PeekSymbol();
74            continue;
75        } else {
76            collection = ParseETSNolintArgs();
77        }
78        AddToETSNolintLinesCollection(line, collection);
79        cp = PeekSymbol();
80    }
81
82    RewindToStart();
83}
84
85void ETSNolintParser::ApplyETSNolintsToStatements(ArenaVector<ir::Statement *> &statements) const
86{
87    for (auto *it : statements) {
88        ApplyETSNolintsToNodesRecursively(it);
89    }
90}
91
92void ETSNolintParser::NextSymbol()
93{
94    if (PeekSymbol() == lexer::LEX_CHAR_LF) {
95        if (!applyingCollection_.empty()) {
96            AddToETSNolintLinesCollection(line_, applyingCollection_);
97        }
98        line_++;
99    }
100
101    posOffset_++;
102    parser_->Lexer()->Pos().Iterator().Forward(1);
103}
104
105void ETSNolintParser::BackwardSymbol()
106{
107    posOffset_--;
108    parser_->Lexer()->Pos().Iterator().Backward(1);
109
110    if (PeekSymbol() == lexer::LEX_CHAR_LF) {
111        line_--;
112    }
113}
114
115void ETSNolintParser::NextSymbol(std::size_t i)
116{
117    for (; i > 0; --i) {
118        NextSymbol();
119    }
120}
121
122void ETSNolintParser::BackwardSymbol(std::size_t i)
123{
124    for (; i > 0; --i) {
125        BackwardSymbol();
126    }
127}
128
129void ETSNolintParser::RewindToStart() const
130{
131    parser_->Lexer()->Pos().Iterator().Backward(posOffset_ - startPos_);
132}
133
134void ETSNolintParser::AddToETSNolintLinesCollection(std::size_t line, const std::set<ETSWarnings> &collection)
135{
136    const auto search = linesCollection_.find(line);
137    if (search != linesCollection_.end()) {
138        search->second.insert(collection.begin(), collection.end());
139        return;
140    }
141
142    linesCollection_.insert({line, collection});
143}
144
145char32_t ETSNolintParser::PeekSymbol() const
146{
147    return parser_->Lexer()->Pos().Iterator().Peek();
148}
149
150bool ETSNolintParser::TryPeekU32String(const std::u32string &u32str)
151{
152    std::size_t localPosOffset = 0;
153    char32_t cp;
154
155    for (const char32_t i : u32str) {
156        cp = PeekSymbol();
157        if (i != cp) {
158            BackwardSymbol(localPosOffset);
159            return false;
160        }
161        NextSymbol();
162        localPosOffset++;
163    }
164    return true;
165}
166
167bool ETSNolintParser::IsEtsNolint()
168{
169    static const std::u32string ETSNOLINT_CHAR32T = {
170        lexer::LEX_CHAR_UPPERCASE_E, lexer::LEX_CHAR_UPPERCASE_T, lexer::LEX_CHAR_UPPERCASE_S,
171        lexer::LEX_CHAR_UPPERCASE_N, lexer::LEX_CHAR_UPPERCASE_O, lexer::LEX_CHAR_UPPERCASE_L,
172        lexer::LEX_CHAR_UPPERCASE_I, lexer::LEX_CHAR_UPPERCASE_N, lexer::LEX_CHAR_UPPERCASE_T};
173
174    char32_t cp;
175
176    for (unsigned long i = 0; i < ETSNOLINT_CHAR32T.length(); i++) {
177        cp = PeekSymbol();
178        if (ETSNOLINT_CHAR32T[i] != cp) {
179            return false;
180        }
181
182        NextSymbol();
183    }
184
185    return true;
186}
187
188bool ETSNolintParser::IsNextLine()
189{
190    static const std::u32string NEXTLINE_CHAR32T = {
191        lexer::LEX_CHAR_MINUS,       lexer::LEX_CHAR_UPPERCASE_N, lexer::LEX_CHAR_UPPERCASE_E,
192        lexer::LEX_CHAR_UPPERCASE_X, lexer::LEX_CHAR_UPPERCASE_T, lexer::LEX_CHAR_UPPERCASE_L,
193        lexer::LEX_CHAR_UPPERCASE_I, lexer::LEX_CHAR_UPPERCASE_N, lexer::LEX_CHAR_UPPERCASE_E};
194
195    return TryPeekU32String(NEXTLINE_CHAR32T);
196}
197
198bool ETSNolintParser::IsBegin()
199{
200    static const std::u32string BEGIN_CHAR32T = {lexer::LEX_CHAR_MINUS,       lexer::LEX_CHAR_UPPERCASE_B,
201                                                 lexer::LEX_CHAR_UPPERCASE_E, lexer::LEX_CHAR_UPPERCASE_G,
202                                                 lexer::LEX_CHAR_UPPERCASE_I, lexer::LEX_CHAR_UPPERCASE_N};
203
204    return TryPeekU32String(BEGIN_CHAR32T);
205}
206
207bool ETSNolintParser::IsEnd()
208{
209    static const std::u32string END_CHAR32T = {lexer::LEX_CHAR_MINUS, lexer::LEX_CHAR_UPPERCASE_E,
210                                               lexer::LEX_CHAR_UPPERCASE_N, lexer::LEX_CHAR_UPPERCASE_D};
211
212    return TryPeekU32String(END_CHAR32T);
213}
214
215ETSWarnings ETSNolintParser::MapETSNolintArg(const std::u32string &warningName) const
216{
217    const auto search = warningsMap_.find(warningName);
218    ASSERT(search != warningsMap_.end());
219
220    return search->second;
221}
222
223bool ETSNolintParser::ValidETSNolintArg(const std::u32string &warningName) const
224{
225    return warningsMap_.find(warningName) != warningsMap_.end();
226}
227
228std::set<ETSWarnings> ETSNolintParser::ParseETSNolintArgs()
229{
230    std::set<ETSWarnings> warningsCollection;
231
232    if (PeekSymbol() != lexer::LEX_CHAR_LEFT_PAREN) {
233        for (const auto &it : warningsMap_) {
234            warningsCollection.insert(it.second);
235        }
236
237        return warningsCollection;
238    }
239
240    NextSymbol();
241    char32_t cp = 0;
242    std::u32string warningName;
243
244    while (cp != lexer::LEX_CHAR_SP && cp != lexer::LEX_CHAR_LF && cp != lexer::LEX_CHAR_EOS) {
245        cp = PeekSymbol();
246        if (cp != lexer::LEX_CHAR_MINUS && cp != lexer::LEX_CHAR_COMMA && cp != lexer::LEX_CHAR_RIGHT_PAREN &&
247            (cp < lexer::LEX_CHAR_LOWERCASE_A || cp > lexer::LEX_CHAR_LOWERCASE_Z)) {
248            const std::string msg = "Unexpected character for ETSNOLINT argument! [VALID ONLY: a-z, '-'].";
249            throw Error {ErrorType::SYNTAX, parser_->GetProgram()->SourceFilePath().Utf8(), msg.c_str(), line_ + 1, 0};
250        }
251        if ((cp == lexer::LEX_CHAR_COMMA || cp == lexer::LEX_CHAR_RIGHT_PAREN) && !ValidETSNolintArg(warningName)) {
252            const std::string msg = "Invalid argument for ETSNOLINT!";
253            throw Error {ErrorType::SYNTAX, parser_->GetProgram()->SourceFilePath().Utf8(), msg.c_str(), line_ + 1, 0};
254        }
255        if ((cp == lexer::LEX_CHAR_COMMA || cp == lexer::LEX_CHAR_RIGHT_PAREN) && ValidETSNolintArg(warningName)) {
256            warningsCollection.insert(MapETSNolintArg(warningName));
257            warningName.clear();
258        } else {
259            warningName += cp;
260        }
261        if (cp == lexer::LEX_CHAR_RIGHT_PAREN) {
262            break;
263        }
264
265        NextSymbol();
266    }
267
268    return warningsCollection;
269}
270
271bool ETSNolintParser::IsLineWithETSNolint(const std::size_t line) const
272{
273    return linesCollection_.find(line) != linesCollection_.end();
274}
275
276std::set<ETSWarnings> ETSNolintParser::GetWarningsCollectionByLine(std::size_t line) const
277{
278    const auto search = linesCollection_.find(line);
279    return search == linesCollection_.end() ? std::set<ETSWarnings> {} : search->second;
280}
281
282void ETSNolintParser::ApplyETSNolintsToNodesRecursively(ir::AstNode *node) const
283{
284    if (node == nullptr) {
285        return;
286    }
287    const std::size_t line = node->Start().line;
288    if (IsLineWithETSNolint(line)) {
289        const std::set<ETSWarnings> warningsCollection = GetWarningsCollectionByLine(line);
290
291        parser_->GetProgram()->AddNodeToETSNolintCollection(node, warningsCollection);
292    }
293    node->Iterate([&](auto *childNode) { ApplyETSNolintsToNodesRecursively(childNode); });
294}
295}  // namespace ark::es2panda::parser
296