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

ArkScript-lang / Ark / 20065764942

09 Dec 2025 01:49PM UTC coverage: 90.564% (+0.008%) from 90.556%
20065764942

push

github

SuperFola
chore(tests): adding IR generation tests for arg attributes

8043 of 8881 relevant lines covered (90.56%)

180911.91 hits per line

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

94.32
/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/Utils/Files.hpp>
8
#include <Ark/Error/Exceptions.hpp>
9
#include <Ark/Error/Diagnostics.hpp>
10
#include <Ark/Compiler/Common.hpp>
11

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

15
Formatter::Formatter(const bool dry_run) :
50 ✔
16
    m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false)
25 ✔
17
{}
50 ✔
18

19
Formatter::Formatter(std::string filename, const bool dry_run) :
50 ✔
20
    m_filename(std::move(filename)), m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false)
25 ✔
21
{}
50 ✔
22

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

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

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

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

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

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

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

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

89
void Formatter::warnIfCommentsWereRemoved(const std::string& original_code, const std::string& filename)
50 ✔
90
{
50 ✔
91
    const std::size_t before_count = std::ranges::count(original_code, '#');
50 ✔
92
    const std::size_t after_count = std::ranges::count(m_output, '#');
50 ✔
93

94
    if (before_count != after_count)
50 ✔
95
    {
96
        fmt::println(
×
97
            "{}: one or more comments from the original source code seem to have been {} by mistake while formatting {}",
×
98
            fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)),
×
99
            before_count > after_count ? "removed" : "duplicated",
×
100
            filename != ARK_NO_NAME_FILE ? filename : "file");
×
101
        fmt::println("Please fill an issue on GitHub: https://github.com/ArkScript-lang/Ark");
×
102
    }
×
103
}
50 ✔
104

105
bool Formatter::isListStartingWithKeyword(const Node& node, const Keyword keyword)
318 ✔
106
{
318 ✔
107
    return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Keyword && node.constList()[0].keyword() == keyword;
318 ✔
108
}
109

110
bool Formatter::isBeginBlock(const Node& node)
226 ✔
111
{
226 ✔
112
    return isListStartingWithKeyword(node, Keyword::Begin);
226 ✔
113
}
114

115
bool Formatter::isFuncDef(const Node& node)
70 ✔
116
{
70 ✔
117
    return isListStartingWithKeyword(node, Keyword::Fun);
70 ✔
118
}
119

120
bool Formatter::isFuncCall(const Node& node)
152 ✔
121
{
152 ✔
122
    return node.isListLike() && !node.constList().empty() && node.constList()[0].nodeType() == NodeType::Symbol;
152 ✔
123
}
124

125
std::size_t Formatter::lineOfLastNodeIn(const Node& node)
350 ✔
126
{
350 ✔
127
    if (node.isListLike() && !node.constList().empty())
350 ✔
128
    {
129
        const std::size_t child_line = lineOfLastNodeIn(node.constList().back());
204 ✔
130
        if (child_line < node.position().start.line)
204 ✔
131
            return node.position().start.line;
2 ✔
132
        return child_line;
202 ✔
133
    }
204 ✔
134
    return node.position().start.line;
146 ✔
135
}
350 ✔
136

137
bool Formatter::shouldSplitOnNewline(const Node& node)
186 ✔
138
{
186 ✔
139
    const std::string formatted = format(node, 0, false);
186 ✔
140
    const std::string::size_type sz = formatted.find_first_of('\n');
186 ✔
141

142
    const bool is_long_line = !((sz < FormatterConfig::LongLineLength || (sz == std::string::npos && formatted.size() < FormatterConfig::LongLineLength)));
186 ✔
143
    if (node.comment().empty() && (isBeginBlock(node) || isFuncCall(node)))
186 ✔
144
        return false;
68 ✔
145
    if (is_long_line || (node.isListLike() && node.constList().size() > 1) || !node.comment().empty())
118 ✔
146
        return true;
26 ✔
147
    return false;
92 ✔
148
}
186 ✔
149

150
bool Formatter::shouldAddNewLineBetweenNodes(const Node& node, const std::size_t at)
248 ✔
151
{
248 ✔
152
    if (at <= 1)
248 ✔
153
        return false;
102 ✔
154

155
    const auto& list = node.constList();
146 ✔
156
    const std::size_t previous_line = lineOfLastNodeIn(list[at - 1]);
146 ✔
157

158
    const auto& child = list[at];
146 ✔
159

160
    // If we have a node before the current one,
161
    // and the line count between the two nodes is more than 1,
162
    // maybe we should add a new line to preserve user spacing.
163
    // However, if the current node has a comment, do not add a new line, this is causing the spacing.
164
    if (child.position().start.line - previous_line > 1 && child.comment().empty())
146 ✔
165
        return true;
20 ✔
166
    // If we do have a comment but the spacing is more than 2,
167
    // then add a newline to preserve user spacing.
168
    if (child.position().start.line - previous_line > 2 && !child.comment().empty())
126 ✔
169
        return true;
2 ✔
170
    return false;
124 ✔
171
}
248 ✔
172

173
std::string Formatter::format(const Node& node, std::size_t indent, bool after_newline)
1,642 ✔
174
{
1,642 ✔
175
    std::string result;
1,642 ✔
176
    if (!node.comment().empty())
1,642 ✔
177
    {
178
        result += formatComment(node.comment(), indent);
76 ✔
179
        after_newline = true;
76 ✔
180
    }
76 ✔
181
    if (after_newline)
1,642 ✔
182
        result += prefix(indent);
296 ✔
183

184
    switch (node.nodeType())
1,642 ✔
185
    {
734 ✔
186
        case NodeType::Symbol:
187
            result += node.string();
734 ✔
188
            break;
738 ✔
189
        case NodeType::MutArg:
190
            result += fmt::format("(mut {})", node.string());
4 ✔
191
            break;
10 ✔
192
        case NodeType::RefArg:
193
            result += fmt::format("(ref {})", node.string());
6 ✔
194
            break;
90 ✔
195
        case NodeType::Capture:
196
            result += "&" + node.string();
84 ✔
197
            break;
84 ✔
198
        case NodeType::Keyword:
199
            result += std::string(keywords[static_cast<std::size_t>(node.keyword())]);
×
200
            break;
70 ✔
201
        case NodeType::String:
202
            result += fmt::format("\"{}\"", node.string());
70 ✔
203
            break;
254 ✔
204
        case NodeType::Number:
205
            result += fmt::format("{}", node.number());
184 ✔
206
            break;
690 ✔
207
        case NodeType::List:
208
            result += formatBlock(node, indent, after_newline);
506 ✔
209
            break;
520 ✔
210
        case NodeType::Spread:
211
            result += fmt::format("...{}", node.string());
14 ✔
212
            break;
32 ✔
213
        case NodeType::Field:
214
        {
215
            std::string field = format(node.constList()[0], indent, false);
18 ✔
216
            for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
62 ✔
217
                field += "." + format(node.constList()[i], indent, false);
44 ✔
218
            result += field;
18 ✔
219
            break;
220
        }
40 ✔
221
        case NodeType::Macro:
222
            result += formatMacro(node, indent);
22 ✔
223
            break;
22 ✔
224
        // not handling Namespace nor Unused node types as those can not be generated by the parser
225
        case NodeType::Namespace:
226
            [[fallthrough]];
227
        case NodeType::Unused:
228
            break;
×
229
    }
1,642 ✔
230

231
    if (!node.commentAfter().empty())
1,642 ✔
232
        result += " " + formatComment(node.commentAfter(), /* indent= */ 0);
42 ✔
233

234
    return result;
1,642 ✔
235
}
1,642 ✔
236

237
std::string Formatter::formatComment(const std::string& comment, const std::size_t indent) const
130 ✔
238
{
130 ✔
239
    std::string result = prefix(indent);
130 ✔
240
    for (std::size_t i = 0, end = comment.size(); i < end; ++i)
1,872 ✔
241
    {
242
        result += comment[i];
1,742 ✔
243
        if (comment[i] == '\n' && i != end - 1)
1,742 ✔
244
            result += prefix(indent);
6 ✔
245
    }
1,742 ✔
246

247
    return result;
130 ✔
248
}
130 ✔
249

250
std::string Formatter::formatBlock(const Node& node, const std::size_t indent, const bool after_newline)
506 ✔
251
{
506 ✔
252
    if (node.constList().empty())
506 ✔
253
        return "()";
30 ✔
254

255
    const Node first = node.constList().front();
476 ✔
256
    if (first.nodeType() == NodeType::Keyword)
476 ✔
257
    {
258
        switch (first.keyword())
240 ✔
259
        {
34 ✔
260
            case Keyword::Fun:
261
                return formatFunction(node, indent);
104 ✔
262
            case Keyword::Let:
263
                [[fallthrough]];
264
            case Keyword::Mut:
265
                [[fallthrough]];
266
            case Keyword::Set:
267
                return formatVariable(node, indent);
112 ✔
268
            case Keyword::If:
269
                return formatCondition(node, indent);
52 ✔
270
            case Keyword::While:
271
                return formatLoop(node, indent);
70 ✔
272
            case Keyword::Begin:
273
                return formatBegin(node, indent, after_newline);
80 ✔
274
            case Keyword::Import:
275
                return formatImport(node, indent);
24 ✔
276
            case Keyword::Del:
277
                return formatDel(node, indent);
4 ✔
278
        }
×
279
        // HACK: should never reach, but the compiler insists that the function doesn't return in every code path
280
        return "";
×
281
    }
282
    return formatCall(node, indent);
236 ✔
283
}
506 ✔
284

285
std::string Formatter::formatFunction(const Node& node, const std::size_t indent)
34 ✔
286
{
34 ✔
287
    const Node args_node = node.constList()[1];
34 ✔
288
    const Node body_node = node.constList()[2];
34 ✔
289

290
    std::string formatted_args;
34 ✔
291

292
    if (!args_node.comment().empty())
34 ✔
293
    {
294
        formatted_args += "\n";
2 ✔
295
        formatted_args += formatComment(args_node.comment(), indent + 1);
2 ✔
296
        formatted_args += prefix(indent + 1);
2 ✔
297
    }
2 ✔
298
    else
299
        formatted_args += " ";
32 ✔
300

301
    if (args_node.isListLike())
34 ✔
302
    {
303
        bool comment_in_args = false;
32 ✔
304
        std::string args;
32 ✔
305
        const bool split = shouldSplitOnNewline(args_node);
32 ✔
306

307
        for (std::size_t i = 0, end = args_node.constList().size(); i < end; ++i)
98 ✔
308
        {
309
            const Node arg_i = args_node.constList()[i];
66 ✔
310
            if (!arg_i.comment().empty())
66 ✔
311
                comment_in_args = true;
8 ✔
312

313
            args += format(arg_i, indent + ((comment_in_args || split) ? 1 : 0), i > 0 && (comment_in_args || split));
112 ✔
314
            if (i != end - 1)
66 ✔
315
                args += (comment_in_args || split) ? '\n' : ' ';
46 ✔
316
        }
66 ✔
317

318
        formatted_args += fmt::format("({}{})", (comment_in_args ? "\n" : ""), args);
32 ✔
319
    }
32 ✔
320
    else
321
        formatted_args += format(args_node, indent, false);
2 ✔
322

323
    if (!shouldSplitOnNewline(body_node) && args_node.comment().empty())
34 ✔
324
        return fmt::format("(fun{} {})", formatted_args, format(body_node, indent + 1, false));
22 ✔
325
    return fmt::format("(fun{}\n{})", formatted_args, format(body_node, indent + 1, true));
12 ✔
326
}
34 ✔
327

328
std::string Formatter::formatVariable(const Node& node, const std::size_t indent)
70 ✔
329
{
70 ✔
330
    const auto keyword = std::string(keywords[static_cast<std::size_t>(node.constList()[0].keyword())]);
70 ✔
331

332
    const Node body_node = node.constList()[2];
70 ✔
333
    const std::string formatted_bind = format(node.constList()[1], indent, false);
70 ✔
334

335
    // we don't want to add another indentation level here, because it would result in a (let a (fun ()\n{indent+=4}...))
336
    if (isFuncDef(body_node) || !shouldSplitOnNewline(body_node))
70 ✔
337
        return fmt::format("({} {} {})", keyword, formatted_bind, format(body_node, indent, false));
68 ✔
338
    return fmt::format("({} {}\n{})", keyword, formatted_bind, format(body_node, indent + 1, true));
2 ✔
339
}
70 ✔
340

341
std::string Formatter::formatCondition(const Node& node, const std::size_t indent, const bool is_macro)
48 ✔
342
{
48 ✔
343
    const Node cond_node = node.constList()[1];
48 ✔
344
    const Node then_node = node.constList()[2];
48 ✔
345

346
    bool cond_on_newline = false;
48 ✔
347
    std::string formatted_cond = format(cond_node, indent + 1, false);
48 ✔
348
    if (formatted_cond.find('\n') != std::string::npos)
48 ✔
349
        cond_on_newline = true;
2 ✔
350

351
    std::string if_cond_formatted = fmt::format(
96 ✔
352
        "({}if{}{}",
48 ✔
353
        is_macro ? "$" : "",
48 ✔
354
        cond_on_newline ? "\n" : " ",
48 ✔
355
        formatted_cond);
356

357
    const bool split_then_newline = shouldSplitOnNewline(then_node);
48 ✔
358

359
    // (if cond then)
360
    if (node.constList().size() == 3)
48 ✔
361
    {
362
        if (cond_on_newline || split_then_newline)
22 ✔
363
            return fmt::format("{}\n{})", if_cond_formatted, format(then_node, indent + 1, true));
2 ✔
364
        return fmt::format("{} {})", if_cond_formatted, format(then_node, indent + 1, false));
20 ✔
365
    }
366
    // (if cond then else)
367
    return fmt::format(
26 ✔
368
        "{}\n{}\n{}{})",
26 ✔
369
        if_cond_formatted,
370
        format(then_node, indent + 1, true),
26 ✔
371
        format(node.constList()[3], indent + 1, true),
26 ✔
372
        node.constList()[3].commentAfter().empty() ? "" : ("\n" + prefix(indent)));
26 ✔
373
}
48 ✔
374

375
std::string Formatter::formatLoop(const Node& node, const std::size_t indent)
10 ✔
376
{
10 ✔
377
    const Node cond_node = node.constList()[1];
10 ✔
378
    const Node body_node = node.constList()[2];
10 ✔
379

380
    bool cond_on_newline = false;
10 ✔
381
    std::string formatted_cond = format(cond_node, indent + 1, false);
10 ✔
382
    if (formatted_cond.find('\n') != std::string::npos)
10 ✔
383
        cond_on_newline = true;
2 ✔
384

385
    if (cond_on_newline || shouldSplitOnNewline(body_node))
10 ✔
386
        return fmt::format(
8 ✔
387
            "(while{}{}\n{})",
4 ✔
388
            cond_on_newline ? "\n" : " ",
4 ✔
389
            formatted_cond,
390
            format(body_node, indent + 1, true));
4 ✔
391
    return fmt::format(
6 ✔
392
        "(while {} {})",
6 ✔
393
        formatted_cond,
394
        format(body_node, indent + 1, false));
6 ✔
395
}
10 ✔
396

397
std::string Formatter::formatBegin(const Node& node, const std::size_t indent, const bool after_newline)
60 ✔
398
{
60 ✔
399
    // only the keyword begin is present
400
    if (node.constList().size() == 1)
60 ✔
401
        return "{}";
8 ✔
402

403
    // after a new line, we need to increment our indentation level
404
    // if the block is a top level one, we also need to increment indentation level
405
    const std::size_t inner_indentation = indent + (after_newline ? 1 : 0) + (indent == 0 ? 1 : 0);
52 ✔
406

407
    std::string result = "{\n";
52 ✔
408
    // skip begin keyword
409
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
152 ✔
410
    {
411
        const Node child = node.constList()[i];
100 ✔
412
        // we want to preserve the node grouping by the user, but remove useless duplicate new line
413
        // but that shouldn't apply to the first node of the block
414
        if (shouldAddNewLineBetweenNodes(node, i) && i > 1)
100 ✔
415
            result += "\n";
×
416

417
        result += format(child, inner_indentation, true);
100 ✔
418
        if (i != end - 1)
100 ✔
419
            result += "\n";
48 ✔
420
    }
100 ✔
421

422
    // if the last node has a comment, add a new line
423
    if (!node.constList().empty() && !node.constList().back().commentAfter().empty())
52 ✔
424
        result += "\n" + prefix(indent) + "}";
2 ✔
425
    else
426
        result += " }";
50 ✔
427
    return result;
52 ✔
428
}
60 ✔
429

430
std::string Formatter::formatImport(const Node& node, const std::size_t indent)
20 ✔
431
{
20 ✔
432
    const Node package_node = node.constList()[1];
20 ✔
433
    std::string package;
20 ✔
434

435
    if (!package_node.comment().empty())
20 ✔
436
        package += "\n" + formatComment(package_node.comment(), indent + 1) + prefix(indent + 1);
2 ✔
437
    else
438
        package += " ";
18 ✔
439

440
    for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
58 ✔
441
    {
442
        package += format(package_node.constList()[i], indent + 1, false);
38 ✔
443
        if (i != end - 1)
38 ✔
444
            package += ".";
18 ✔
445
    }
38 ✔
446

447
    const Node symbols = node.constList()[2];
20 ✔
448
    if (symbols.nodeType() == NodeType::Symbol && symbols.string() == "*")
20 ✔
449
        package += ":*";
2 ✔
450
    else  // symbols is a list
451
    {
452
        if (const auto& sym_list = symbols.constList(); !sym_list.empty())
28 ✔
453
        {
454
            const bool comment_after_last = !sym_list.back().commentAfter().empty();
10 ✔
455

456
            for (const auto& sym : sym_list)
24 ✔
457
            {
458
                if (sym.comment().empty())
14 ✔
459
                {
460
                    if (comment_after_last)
8 ✔
461
                        package += "\n" + prefix(indent + 1) + ":" + sym.string();
2 ✔
462
                    else
463
                        package += " :" + sym.string();
6 ✔
464
                }
8 ✔
465
                else
466
                    package += "\n" + formatComment(sym.comment(), indent + 1) + prefix(indent + 1) + ":" + sym.string();
6 ✔
467
            }
14 ✔
468

469
            if (comment_after_last)
10 ✔
470
            {
471
                package += " " + formatComment(sym_list.back().commentAfter(), /* indent= */ 0);
2 ✔
472
                package += "\n" + prefix(indent + 1);
2 ✔
473
            }
2 ✔
474
        }
10 ✔
475
    }
476

477
    return fmt::format("(import{})", package);
20 ✔
478
}
20 ✔
479

480
std::string Formatter::formatDel(const Node& node, const std::size_t indent)
4 ✔
481
{
4 ✔
482
    std::string formatted_sym = format(node.constList()[1], indent + 1, false);
4 ✔
483
    if (formatted_sym.find('\n') != std::string::npos)
4 ✔
484
        return fmt::format("(del\n{})", formatted_sym);
2 ✔
485
    return fmt::format("(del {})", formatted_sym);
2 ✔
486
}
4 ✔
487

488
std::string Formatter::formatCall(const Node& node, const std::size_t indent)
236 ✔
489
{
236 ✔
490
    bool is_list = false;
236 ✔
491
    bool is_dict = false;
236 ✔
492
    bool is_multiline = false;
236 ✔
493

494
    if (!node.constList().empty() && node.constList().front().nodeType() == NodeType::Symbol)
236 ✔
495
    {
496
        if (node.constList().front().string() == "list")
206 ✔
497
            is_list = true;
4 ✔
498
        else if (node.constList().front().string() == "dict")
202 ✔
499
            is_dict = true;
8 ✔
500
    }
206 ✔
501

502
    std::vector<std::string> formatted_args;
236 ✔
503
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
638 ✔
504
    {
505
        formatted_args.push_back(format(node.constList()[i], indent, false));
402 ✔
506
        // if we have at least one argument taking multiple lines, split them all on their own line
507
        if (formatted_args.back().find('\n') != std::string::npos || !node.constList()[i].commentAfter().empty())
402 ✔
508
            is_multiline = true;
16 ✔
509
    }
402 ✔
510

511
    std::string result = is_list ? "[" : ("(" + format(node.constList()[0], indent, false));
236 ✔
512
    for (std::size_t i = 0, end = formatted_args.size(); i < end; ++i)
638 ✔
513
    {
514
        const std::string& formatted_node = formatted_args[i];
402 ✔
515
        if (is_dict)
402 ✔
516
        {
517
            if (i % 2 == 0 && formatted_args.size() > 2)  // one pair per line if we have at least 2 key-value pairs
32 ✔
518
                result += "\n" + format(node.constList()[i + 1], indent + 1, true);
12 ✔
519
            else
520
                result += " " + formatted_node;
20 ✔
521
        }
32 ✔
522
        else if (is_multiline)
370 ✔
523
            result += "\n" + format(node.constList()[i + 1], indent + 1, true);
28 ✔
524
        else if (is_list && i == 0)
342 ✔
525
            result += formatted_node;
2 ✔
526
        else  // put all arguments on the same line
527
            result += " " + formatted_node;
340 ✔
528
    }
402 ✔
529
    if (!node.constList().back().commentAfter().empty())
236 ✔
530
        result += "\n" + prefix(indent);
6 ✔
531

532
    result += is_list ? "]" : ")";
236 ✔
533
    return result;
236 ✔
534
}
236 ✔
535

536
std::string Formatter::formatMacro(const Node& node, const std::size_t indent)
22 ✔
537
{
22 ✔
538
    if (isListStartingWithKeyword(node, Keyword::If))
22 ✔
539
        return formatCondition(node, indent, /* is_macro= */ true);
6 ✔
540

541
    std::string result = "(macro ";
16 ✔
542
    bool after_newline = false;
16 ✔
543

544
    for (std::size_t i = 0, end = node.constList().size(); i < end; ++i)
62 ✔
545
    {
546
        result += format(node.constList()[i], indent + 1, after_newline);
46 ✔
547
        after_newline = false;
46 ✔
548

549
        if (!node.constList()[i].commentAfter().empty())
46 ✔
550
        {
551
            result += "\n";
6 ✔
552
            after_newline = true;
6 ✔
553
        }
6 ✔
554
        else if (i != end - 1)
40 ✔
555
            result += " ";
26 ✔
556
    }
46 ✔
557

558
    return result + ")";
16 ✔
559
}
22 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc