• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ArkScript-lang / Ark / 11445793250

21 Oct 2024 05:51PM UTC coverage: 76.877% (+0.4%) from 76.499%
11445793250

push

github

SuperFola
refactor(macro): removing useless constructor

5130 of 6673 relevant lines covered (76.88%)

9379.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.3
/src/arkscript/Formatter.cpp
1
#include <Ark/Constants.hpp>
2
#include <CLI/Formatter.hpp>
3

4
#include <fmt/core.h>
5
#include <fmt/color.h>
6

7
#include <Ark/Files.hpp>
8
#include <Ark/Exceptions.hpp>
9
#include <Ark/Compiler/Common.hpp>
10

11
using namespace Ark;
12
using namespace Ark::internal;
13

14
Formatter::Formatter(bool dry_run) :
42✔
15
    m_dry_run(dry_run), m_parser(/* debug= */ 0, /* interpret= */ false), m_updated(false)
21✔
16
{}
42✔
17

18
Formatter::Formatter(std::string filename, const bool dry_run) :
42✔
19
    m_filename(std::move(filename)), m_dry_run(dry_run), m_parser(/* debug= */ 0, /* interpret= */ false), m_updated(false)
21✔
20
{}
42✔
21

22
void Formatter::run()
22✔
23
{
21✔
24
    try
25
    {
26
        const std::string code = Utils::readFile(m_filename);
21✔
27
        m_parser.process(m_filename, code);
21✔
28
        processAst(m_parser.ast());
21✔
29
        warnIfCommentsWereRemoved(code, ARK_NO_NAME_FILE);
21✔
30

×
31
        m_updated = code != m_output;
21✔
32
    }
22✔
33
    catch (const CodeError& e)
34
    {
35
        Diagnostics::generate(e);
×
36
    }
21✔
37
}
21✔
38

39
void Formatter::runWithString(const std::string& code)
21✔
40
{
21✔
41
    try
42
    {
1✔
43
        m_parser.process(ARK_NO_NAME_FILE, code);
21✔
44
        processAst(m_parser.ast());
21✔
45
        warnIfCommentsWereRemoved(code, ARK_NO_NAME_FILE);
21✔
46

47
        m_updated = code != m_output;
21✔
48
    }
21✔
49
    catch (const CodeError& e)
50
    {
×
51
        Diagnostics::generate(e);
×
52
    }
22✔
53
}
21✔
54

55
const std::string& Formatter::output() const
42✔
56
{
42✔
57
    return m_output;
42✔
58
}
59

60
bool Formatter::codeModified() const
×
61
{
×
62
    return m_updated;
1✔
63
}
64

65
void Formatter::processAst(const Node& ast)
42✔
66
{
42✔
67
    // remove useless surrounding begin (generated by the parser)
68
    if (isBeginBlock(ast))
42✔
69
    {
70
        for (std::size_t i = 1, end = ast.constList().size(); i < end; ++i)
176✔
71
        {
72
            const Node node = ast.constList()[i];
135✔
73
            if (shouldAddNewLineBetweenNodes(ast, i) && !m_output.empty())
134✔
74
                m_output += "\n";
18✔
75
            m_output += format(node, 0, false) + "\n";
134✔
76
        }
134✔
77
    }
42✔
78
    else
79
        m_output = format(ast, 0, false);
×
80

×
81
    if (!m_dry_run)
42✔
82
    {
83
        std::ofstream stream(m_filename);
×
84
        stream << m_output;
×
85
    }
×
86
}
42✔
87

88
void Formatter::warnIfCommentsWereRemoved(const std::string& original_code, const std::string& filename)
42✔
89
{
42✔
90
    if (std::ranges::count(original_code, '#') != std::ranges::count(m_output, '#'))
42✔
91
    {
92
        fmt::println(
×
93
            "{}: one or more comments from the original source code seem to have been removed by mistake while formatting {}",
×
94
            fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)),
×
95
            filename != ARK_NO_NAME_FILE ? filename : "file");
×
96
        fmt::println("Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark");
×
97
    }
×
98
}
42✔
99

100
bool Formatter::isListStartingWithKeyword(const Node& node, const Keyword keyword)
262✔
101
{
262✔
102
    return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword && node.constList()[0].keyword() == keyword;
262✔
103
}
104

105
bool Formatter::isBeginBlock(const Node& node)
178✔
106
{
178✔
107
    return isListStartingWithKeyword(node, Keyword::Begin);
178✔
108
}
109

110
bool Formatter::isFuncDef(const Node& node)
64✔
111
{
64✔
112
    return isListStartingWithKeyword(node, Keyword::Fun);
64✔
113
}
114

115
bool Formatter::isFuncCall(const Node& node)
112✔
116
{
112✔
117
    return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol;
112✔
118
}
119

120
std::size_t Formatter::lineOfLastNodeIn(const Node& node)
322✔
121
{
322✔
122
    if (node.isListLike() && !node.constList().empty())
322✔
123
    {
124
        std::size_t child_line = lineOfLastNodeIn(node.constList().back());
186✔
125
        if (child_line < node.line())
186✔
126
            return node.line();
×
127
        return child_line;
186✔
128
    }
187✔
129
    return node.line();
136✔
130
}
322✔
131

132
bool Formatter::shouldSplitOnNewline(const Node& node)
144✔
133
{
144✔
134
    const std::string formatted = format(node, 0, false);
144✔
135
    const std::string::size_type sz = formatted.find_first_of('\n');
144✔
136

137
    const bool is_long_line = !((sz < FormatterConfig.LongLineLength || (sz == std::string::npos && formatted.size() < FormatterConfig.LongLineLength)));
144✔
138
    if (node.comment().empty() && (isBeginBlock(node) || isFuncCall(node)))
144✔
139
        return false;
50✔
140
    if (is_long_line || (node.isListLike() && node.constList().size() > 1) || !node.comment().empty())
94✔
141
        return true;
20✔
142
    return false;
74✔
143
}
144✔
144

145
bool Formatter::shouldAddNewLineBetweenNodes(const Node& node, const std::size_t at)
228✔
146
{
228✔
147
    if (at <= 1)
228✔
148
        return false;
92✔
149

150
    const auto& list = node.constList();
136✔
151
    std::size_t previous_line = lineOfLastNodeIn(list[at - 1]);
136✔
152

153
    const auto& child = list[at];
136✔
154

155
    // If we have a node before the current one,
156
    // and the line count between the two nodes is more than 1,
157
    // maybe we should add a new line to preserve user spacing.
×
158
    // However, if the current node has a comment, do not add a new line, this is causing the spacing.
159
    if (child.line() - previous_line > 1 && child.comment().empty())
137✔
160
        return true;
16✔
161
    // If we do have a comment but the spacing is more than 2,
162
    // then add a newline to preserve user spacing.
163
    if (child.line() - previous_line > 2 && !child.comment().empty())
120✔
164
        return true;
2✔
165
    return false;
118✔
166
}
228✔
167

168
std::string Formatter::format(const Node& node, std::size_t indent, bool after_newline)
1,374✔
169
{
1,374✔
170
    std::string output;
1,374✔
171
    if (!node.comment().empty())
1,374✔
172
    {
173
        output += formatComment(node.comment(), indent);
58✔
174
        after_newline = true;
58✔
175
    }
58✔
176
    if (after_newline)
1,374✔
177
        output += prefix(indent);
218✔
178

179
    switch (node.nodeType())
1,374✔
180
    {
678✔
181
        case NodeType::Symbol:
182
            output += node.string();
678✔
183
            break;
680✔
184
        case NodeType::Capture:
185
            output += "&" + node.string();
2✔
186
            break;
2✔
187
        case NodeType::Keyword:
188
            output += std::string(keywords[static_cast<std::size_t>(node.keyword())]);
×
189
            break;
42✔
190
        case NodeType::String:
191
            output += fmt::format("\"{}\"", node.string());
42✔
192
            break;
206✔
193
        case NodeType::Number:
194
            output += fmt::format("{}", node.number());
164✔
195
            break;
600✔
196
        case NodeType::List:
197
            output += formatBlock(node, indent, after_newline);
436✔
198
            break;
450✔
199
        case NodeType::Spread:
200
            output += fmt::format("...{}", node.string());
14✔
201
            break;
32✔
202
        case NodeType::Field:
203
        {
204
            std::string field = format(node.constList()[0], indent, false);
18✔
205
            for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
62✔
206
                field += "." + format(node.constList()[i], indent, false);
44✔
207
            output += field;
18✔
208
            break;
209
        }
38✔
210
        case NodeType::Macro:
211
            output += formatMacro(node, indent);
20✔
212
            break;
20✔
213
        case NodeType::Unused:
214
            break;
×
215
    }
1,374✔
216

217
    if (!node.commentAfter().empty())
1,374✔
218
        output += " " + formatComment(node.commentAfter(), /* indent= */ 0);
40✔
219

220
    return output;
1,374✔
221
}
1,374✔
222

223
std::string Formatter::formatComment(const std::string& comment, const std::size_t indent) const
110✔
224
{
110✔
225
    std::string output = prefix(indent);
110✔
226
    for (std::size_t i = 0, end = comment.size(); i < end; ++i)
1,654✔
227
    {
228
        output += comment[i];
1,544✔
229
        if (comment[i] == '\n' && i != end - 1)
1,544✔
230
            output += prefix(indent);
6✔
231
    }
1,544✔
232

233
    return output;
110✔
234
}
110✔
235

236
std::string Formatter::formatBlock(const Node& node, const std::size_t indent, const bool after_newline)
436✔
237
{
436✔
238
    if (node.constList().empty())
436✔
239
        return "()";
8✔
240

241
    const Node first = node.constList().front();
428✔
242
    if (first.nodeType() == NodeType::Keyword)
428✔
243
    {
244
        switch (first.keyword())
228✔
245
        {
30✔
246
            case Keyword::Fun:
247
                return formatFunction(node, indent);
94✔
248
            case Keyword::Let:
249
                [[fallthrough]];
250
            case Keyword::Mut:
251
                [[fallthrough]];
252
            case Keyword::Set:
253
                return formatVariable(node, indent);
106✔
254
            case Keyword::If:
255
                return formatCondition(node, indent);
52✔
256
            case Keyword::While:
257
                return formatLoop(node, indent);
68✔
258
            case Keyword::Begin:
259
                return formatBegin(node, indent, after_newline);
78✔
260
            case Keyword::Import:
261
                return formatImport(node, indent);
24✔
262
            case Keyword::Del:
263
                return formatDel(node, indent);
4✔
264
        }
×
265
        // HACK: should never reach, but the compiler insists that the function doesn't return in every code path
266
        return "";
×
267
    }
268
    return formatCall(node, indent);
200✔
269
}
436✔
270

271
std::string Formatter::formatFunction(const Node& node, const std::size_t indent)
30✔
272
{
30✔
273
    const Node args_node = node.constList()[1];
30✔
274
    const Node body_node = node.constList()[2];
30✔
275

276
    std::string formatted_args;
30✔
277

278
    if (!args_node.comment().empty())
30✔
279
    {
280
        formatted_args += "\n";
2✔
281
        formatted_args += formatComment(args_node.comment(), indent + 1);
2✔
282
        formatted_args += prefix(indent + 1);
2✔
283
    }
2✔
284
    else
285
        formatted_args += " ";
28✔
286

287
    if (args_node.isListLike())
30✔
288
    {
289
        bool comment_in_args = false;
28✔
290
        std::string args;
28✔
291
        for (std::size_t i = 0, end = args_node.constList().size(); i < end; ++i)
52✔
292
        {
293
            const Node arg_i = args_node.constList()[i];
24✔
294
            if (!arg_i.comment().empty())
24✔
295
                comment_in_args = true;
4✔
296

297
            args += format(arg_i, indent + (comment_in_args ? 1 : 0), comment_in_args);
24✔
298
            if (i != end - 1)
24✔
299
                args += comment_in_args ? '\n' : ' ';
8✔
300
        }
24✔
301

302
        formatted_args += fmt::format("({}{})", (comment_in_args ? "\n" : ""), args);
28✔
303
    }
28✔
304
    else
305
        formatted_args += format(args_node, indent, false);
2✔
306

307
    if (!shouldSplitOnNewline(body_node) && args_node.comment().empty())
30✔
308
        return fmt::format("(fun{} {})", formatted_args, format(body_node, indent + 1, false));
18✔
309
    return fmt::format("(fun{}\n{})", formatted_args, format(body_node, indent + 1, true));
12✔
310
}
30✔
311

312
std::string Formatter::formatVariable(const Node& node, const std::size_t indent)
64✔
313
{
64✔
314
    std::string keyword = std::string(keywords[static_cast<std::size_t>(node.constList()[0].keyword())]);
64✔
315

316
    const Node body_node = node.constList()[2];
64✔
317
    const std::string formatted_bind = format(node.constList()[1], indent, false);
64✔
318

319
    // we don't want to add another indentation level here, because it would result in a (let a (fun ()\n{indent+=4}...))
320
    if (isFuncDef(body_node))
64✔
321
        return fmt::format("({} {} {})", keyword, formatted_bind, format(body_node, indent, false));
6✔
322
    if (!shouldSplitOnNewline(body_node))
58✔
323
        return fmt::format("({} {} {})", keyword, formatted_bind, format(body_node, indent + 1, false));
56✔
324
    return fmt::format("({} {}\n{})", keyword, formatted_bind, format(body_node, indent + 1, true));
2✔
325
}
64✔
326

327
std::string Formatter::formatCondition(const Node& node, const std::size_t indent, const bool is_macro)
48✔
328
{
48✔
329
    const Node cond_node = node.constList()[1];
48✔
330
    const Node then_node = node.constList()[2];
48✔
331

332
    bool cond_on_newline = false;
48✔
333
    std::string formatted_cond = format(cond_node, indent + 1, false);
48✔
334
    if (formatted_cond.find('\n') != std::string::npos)
48✔
335
        cond_on_newline = true;
2✔
336

337
    std::string if_cond_formatted = fmt::format(
96✔
338
        "({}if{}{}",
48✔
339
        is_macro ? "$" : "",
48✔
340
        cond_on_newline ? "\n" : " ",
48✔
341
        formatted_cond);
342

343
    const bool split_then_newline = shouldSplitOnNewline(then_node);
48✔
344

345
    // (if cond then)
346
    if (node.constList().size() == 3)
48✔
347
    {
348
        if (cond_on_newline || split_then_newline)
22✔
349
            return fmt::format("{}\n{})", if_cond_formatted, format(then_node, indent + 1, true));
2✔
350
        return fmt::format("{} {})", if_cond_formatted, format(then_node, indent + 1, false));
20✔
351
    }
352
    // (if cond then else)
353
    return fmt::format(
26✔
354
        "{}\n{}\n{}{})",
26✔
355
        if_cond_formatted,
356
        format(then_node, indent + 1, true),
26✔
357
        format(node.constList()[3], indent + 1, true),
26✔
358
        node.constList()[3].commentAfter().empty() ? "" : ("\n" + prefix(indent)));
26✔
359
}
48✔
360

361
std::string Formatter::formatLoop(const Node& node, const std::size_t indent)
10✔
362
{
10✔
363
    const Node cond_node = node.constList()[1];
10✔
364
    const Node body_node = node.constList()[2];
10✔
365

366
    bool cond_on_newline = false;
10✔
367
    std::string formatted_cond = format(cond_node, indent + 1, false);
10✔
368
    if (formatted_cond.find('\n') != std::string::npos)
10✔
369
        cond_on_newline = true;
2✔
370

371
    if (cond_on_newline || shouldSplitOnNewline(body_node))
10✔
372
        return fmt::format(
8✔
373
            "(while{}{}\n{})",
4✔
374
            cond_on_newline ? "\n" : " ",
4✔
375
            formatted_cond,
376
            format(body_node, indent + 1, true));
4✔
377
    return fmt::format(
6✔
378
        "(while {} {})",
6✔
379
        formatted_cond,
380
        format(body_node, indent + 1, false));
6✔
381
}
10✔
382

383
std::string Formatter::formatBegin(const Node& node, const std::size_t indent, const bool after_newline)
58✔
384
{
58✔
385
    // only the keyword begin is present
386
    if (node.constList().size() == 1)
58✔
387
        return "{}";
8✔
388

389
    // after a new line, we need to increment our indentation level
390
    // if the block is a top level one, we also need to increment indentation level
391
    const std::size_t inner_indentation = indent + (after_newline ? 1 : 0) + (indent == 0 ? 1 : 0);
50✔
392

393
    std::string output = "{\n";
50✔
394
    // skip begin keyword
395
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
144✔
396
    {
397
        const Node child = node.constList()[i];
94✔
398
        // we want to preserve the node grouping by the user, but remove useless duplicate new line
399
        // but that shouldn't apply to the first node of the block
400
        if (shouldAddNewLineBetweenNodes(node, i) && i > 1)
94✔
401
            output += "\n";
×
402

403
        output += format(child, inner_indentation, true);
94✔
404
        if (i != end - 1)
94✔
405
            output += "\n";
44✔
406
    }
94✔
407

408
    // if the last node has a comment, add a new line
409
    if (!node.constList().empty() && !node.constList().back().commentAfter().empty())
50✔
410
        output += "\n" + prefix(indent) + "}";
2✔
411
    else
412
        output += " }";
48✔
413
    return output;
50✔
414
}
58✔
415

416
std::string Formatter::formatImport(const Node& node, const std::size_t indent)
20✔
417
{
20✔
418
    const Node package_node = node.constList()[1];
20✔
419
    std::string package;
20✔
420

421
    if (!package_node.comment().empty())
20✔
422
        package += "\n" + formatComment(package_node.comment(), indent + 1) + prefix(indent + 1);
2✔
423
    else
424
        package += " ";
18✔
425

426
    for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
58✔
427
    {
428
        package += format(package_node.constList()[i], indent + 1, false);
38✔
429
        if (i != end - 1)
38✔
430
            package += ".";
18✔
431
    }
38✔
432

433
    const Node symbols = node.constList()[2];
20✔
434
    if (symbols.nodeType() == NodeType::Symbol && symbols.string() == "*")
20✔
435
        package += ":*";
2✔
436
    else  // symbols is a list
437
    {
438
        if (const auto& sym_list = symbols.constList(); !sym_list.empty())
28✔
439
        {
440
            const bool comment_after_last = !sym_list.back().commentAfter().empty();
10✔
441

442
            for (const auto& sym : sym_list)
24✔
443
            {
444
                if (sym.comment().empty())
14✔
445
                {
446
                    if (comment_after_last)
8✔
447
                        package += "\n" + prefix(indent + 1) + ":" + sym.string();
2✔
448
                    else
449
                        package += " :" + sym.string();
6✔
450
                }
8✔
451
                else
452
                    package += "\n" + formatComment(sym.comment(), indent + 1) + prefix(indent + 1) + ":" + sym.string();
6✔
453
            }
14✔
454

455
            if (comment_after_last)
10✔
456
            {
457
                package += " " + formatComment(sym_list.back().commentAfter(), /* indent= */ 0);
2✔
458
                package += "\n" + prefix(indent + 1);
2✔
459
            }
2✔
460
        }
10✔
461
    }
462

463
    return fmt::format("(import{})", package);
20✔
464
}
20✔
465

466
std::string Formatter::formatDel(const Node& node, const std::size_t indent)
4✔
467
{
4✔
468
    std::string formatted_sym = format(node.constList()[1], indent + 1, false);
4✔
469
    if (formatted_sym.find('\n') != std::string::npos)
4✔
470
        return fmt::format("(del\n{})", formatted_sym);
2✔
471
    return fmt::format("(del {})", formatted_sym);
2✔
472
}
4✔
473

474
std::string Formatter::formatCall(const Node& node, const std::size_t indent)
200✔
475
{
200✔
476
    bool is_list = false;
200✔
477
    if (!node.constList().empty() && node.constList().front().nodeType() == NodeType::Symbol &&
374✔
478
        node.constList().front().string() == "list")
174✔
479
        is_list = true;
4✔
480

481
    bool is_multiline = false;
200✔
482

483
    std::vector<std::string> formatted_args;
200✔
484
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
516✔
485
    {
486
        formatted_args.push_back(format(node.constList()[i], indent, false));
316✔
487
        // if we have at least one argument taking multiple lines, split them all on their own line
488
        if (formatted_args.back().find('\n') != std::string::npos || !node.constList()[i].commentAfter().empty())
316✔
489
            is_multiline = true;
12✔
490
    }
316✔
491

492
    std::string output = is_list ? "[" : ("(" + format(node.constList()[0], indent, false));
200✔
493
    for (std::size_t i = 0, end = formatted_args.size(); i < end; ++i)
516✔
494
    {
495
        const std::string formatted_node = formatted_args[i];
316✔
496
        if (is_multiline)
316✔
497
            output += "\n" + format(node.constList()[i + 1], indent + 1, true);
20✔
498
        else
499
            output += (is_list && i == 0 ? "" : " ") + formatted_node;
296✔
500
    }
316✔
501
    if (!node.constList().back().commentAfter().empty())
200✔
502
        output += "\n" + prefix(indent);
6✔
503
    output += is_list ? "]" : ")";
200✔
504
    return output;
200✔
505
}
200✔
506

507
std::string Formatter::formatMacro(const Node& node, const std::size_t indent)
20✔
508
{
20✔
509
    if (isListStartingWithKeyword(node, Keyword::If))
20✔
510
        return formatCondition(node, indent, /* is_macro= */ true);
6✔
511

512
    std::string output = "($ ";
14✔
513
    bool after_newline = false;
14✔
514

515
    for (std::size_t i = 0, end = node.constList().size(); i < end; ++i)
54✔
516
    {
517
        output += format(node.constList()[i], indent + 1, after_newline);
40✔
518
        after_newline = false;
40✔
519

520
        if (!node.constList()[i].commentAfter().empty())
40✔
521
        {
522
            output += "\n";
4✔
523
            after_newline = true;
4✔
524
        }
4✔
525
        else if (i != end - 1)
36✔
526
            output += " ";
24✔
527
    }
40✔
528

529
    return output + ")";
14✔
530
}
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc