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

ArkScript-lang / Ark / 22870305923

09 Mar 2026 07:10PM UTC coverage: 93.778% (+0.3%) from 93.519%
22870305923

push

github

SuperFola
feat(logs): allow using a custom output stream for warnings

41 of 42 new or added lines in 12 files covered. (97.62%)

28 existing lines in 5 files now uncovered.

9510 of 10141 relevant lines covered (93.78%)

270952.18 hits per line

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

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

4
#include <fmt/core.h>
5

6
#include <Ark/Utils/Files.hpp>
7
#include <Ark/Error/Exceptions.hpp>
8
#include <Ark/Error/Diagnostics.hpp>
9
#include <Ark/Compiler/Common.hpp>
10
#include <Ark/Utils/Literals.hpp>
11

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

16
Formatter::Formatter(const bool dry_run) :
60✔
17
    m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false), m_logger("formatter", 0)
30✔
18
{}
60✔
19

20
Formatter::Formatter(std::string filename, const bool dry_run) :
60✔
21
    m_filename(std::move(filename)), m_dry_run(dry_run), m_parser(/* debug= */ 0, ParserMode::Raw), m_updated(false), m_logger("formatter", 0)
30✔
22
{}
60✔
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

137
bool Formatter::isLongLine(const Node& node)
194✔
138
{
194✔
139
    const std::string formatted = format(node, 0, false);
194✔
140
    const std::size_t max_len =
388✔
141
        std::ranges::max(
194✔
142
            Utils::splitString(formatted, '\n'),
194✔
143
            [](const std::string& lhs, const std::string& rhs) {
128✔
144
                return lhs.size() < rhs.size();
128✔
145
            })
146
            .size();
194✔
147
    const std::size_t newlines = std::ranges::count(formatted, '\n');
194✔
148

149
    // split on multiple lines if we have a very long node,
150
    // or if we added many line breaks while doing dumb formatting
151
    return max_len >= FormatterConfig::LongLineLength || (newlines > 0 && node.isListLike() && newlines + 1 >= node.constList().size());
194✔
152
}
194✔
153

154
bool Formatter::shouldSplitOnNewline(const Node& node)
284✔
155
{
284✔
156
    if (node.comment().empty() && (isBeginBlock(node) || isFuncCall(node)))
284✔
157
        return false;
142✔
158
    if (isLongLine(node) || (node.isListLike() && node.constList().size() > 1) || !node.comment().empty())
142✔
159
        return true;
28✔
160
    return false;
114✔
161
}
284✔
162

163
bool Formatter::shouldAddNewLineBetweenNodes(const Node& node, const std::size_t at)
350✔
164
{
350✔
165
    if (at <= 1)
350✔
166
        return false;
134✔
167

168
    const auto& list = node.constList();
216✔
169
    const std::size_t previous_line = lineOfLastNodeIn(list[at - 1]);
216✔
170

171
    const auto& child = list[at];
216✔
172

173
    // If we have a node before the current one,
174
    // and the line count between the two nodes is more than 1,
175
    // maybe we should add a new line to preserve user spacing.
176
    // However, if the current node has a comment, do not add a new line, this is causing the spacing.
177
    if (child.position().start.line - previous_line > 1 && child.comment().empty())
216✔
178
        return true;
40✔
179
    // If we do have a comment but the spacing is more than 2,
180
    // then add a newline to preserve user spacing.
181
    if (child.position().start.line - previous_line > 2 && !child.comment().empty())
176✔
182
        return true;
2✔
183
    return false;
174✔
184
}
350✔
185

186
std::string Formatter::format(const Node& node, std::size_t indent, bool after_newline)
2,660✔
187
{
2,660✔
188
    std::string result;
2,660✔
189
    if (!node.comment().empty())
2,660✔
190
    {
191
        result += formatComment(node.comment(), indent);
76✔
192
        after_newline = true;
76✔
193
    }
76✔
194
    if (after_newline)
2,660✔
195
        result += prefix(indent);
528✔
196

197
    switch (node.nodeType())
2,660✔
198
    {
1,328✔
199
        case NodeType::Symbol:
200
            result += node.string();
1,328✔
201
            break;
1,340✔
202
        case NodeType::MutArg:
203
            result += fmt::format("(mut {})", node.string());
12✔
204
            break;
30✔
205
        case NodeType::RefArg:
206
            result += fmt::format("(ref {})", node.string());
18✔
207
            break;
148✔
208
        case NodeType::Capture:
209
            result += "&" + node.string();
130✔
210
            break;
130✔
211
        case NodeType::Keyword:
UNCOV
212
            result += std::string(keywords[static_cast<std::size_t>(node.keyword())]);
×
213
            break;
76✔
214
        case NodeType::String:
215
            result += fmt::format("\"{}\"", node.string());
76✔
216
            break;
260✔
217
        case NodeType::Number:
218
            result += fmt::format("{}", node.number());
184✔
219
            break;
1,044✔
220
        case NodeType::List:
221
            result += formatBlock(node, indent, after_newline);
860✔
222
            break;
870✔
223
        case NodeType::Spread:
224
            result += fmt::format("...{}", node.string());
10✔
225
            break;
28✔
226
        case NodeType::Field:
227
        {
228
            std::string field = format(node.constList()[0], indent, false);
18✔
229
            for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
62✔
230
                field += "." + format(node.constList()[i], indent, false);
44✔
231
            result += field;
18✔
232
            break;
233
        }
42✔
234
        case NodeType::Macro:
235
            result += formatMacro(node, indent);
24✔
236
            break;
24✔
237
        // not handling Namespace nor Unused node types as those can not be generated by the parser
238
        case NodeType::Namespace:
239
            [[fallthrough]];
240
        case NodeType::Unused:
UNCOV
241
            break;
×
242
    }
2,660✔
243

244
    if (!node.commentAfter().empty())
2,660✔
245
        result += " " + formatComment(node.commentAfter(), /* indent= */ 0);
42✔
246

247
    return result;
2,660✔
248
}
2,660✔
249

250
std::string Formatter::formatComment(const std::string& comment, const std::size_t indent) const
130✔
251
{
130✔
252
    std::string result = prefix(indent);
130✔
253
    for (std::size_t i = 0, end = comment.size(); i < end; ++i)
3,158✔
254
    {
255
        result += comment[i];
3,028✔
256
        if (comment[i] == '\n' && i != end - 1)
3,028✔
257
            result += prefix(indent);
52✔
258
    }
3,028✔
259

260
    return result;
130✔
261
}
130✔
262

263
std::string Formatter::formatBlock(const Node& node, const std::size_t indent, const bool after_newline)
860✔
264
{
860✔
265
    if (node.constList().empty())
860✔
266
        return "()";
30✔
267

268
    const Node first = node.constList().front();
830✔
269
    if (first.nodeType() == NodeType::Keyword)
830✔
270
    {
271
        switch (first.keyword())
398✔
272
        {
54✔
273
            case Keyword::Fun:
274
                return formatFunction(node, indent);
200✔
275
            case Keyword::Let:
276
                [[fallthrough]];
277
            case Keyword::Mut:
278
                [[fallthrough]];
279
            case Keyword::Set:
280
                return formatVariable(node, indent);
220✔
281
            case Keyword::If:
282
                return formatCondition(node, indent);
94✔
283
            case Keyword::While:
284
                return formatLoop(node, indent);
100✔
285
            case Keyword::Begin:
286
                return formatBegin(node, indent, after_newline);
100✔
287
            case Keyword::Import:
288
                return formatImport(node, indent);
24✔
289
            case Keyword::Del:
290
                return formatDel(node, indent);
4✔
UNCOV
291
        }
×
292
        // HACK: should never reach, but the compiler insists that the function doesn't return in every code path
293
        return "";
×
294
    }
295
    return formatCall(node, indent);
432✔
296
}
860✔
297

298
std::string Formatter::formatFunction(const Node& node, const std::size_t indent)
54✔
299
{
54✔
300
    const Node args_node = node.constList()[1];
54✔
301
    const Node body_node = node.constList()[2];
54✔
302

303
    std::string formatted_args;
54✔
304

305
    if (!args_node.comment().empty())
54✔
306
    {
307
        formatted_args += "\n";
2✔
308
        formatted_args += formatComment(args_node.comment(), indent + 1);
2✔
309
        formatted_args += prefix(indent + 1);
2✔
310
    }
2✔
311
    else
312
        formatted_args += " ";
52✔
313

314
    if (args_node.isListLike())
54✔
315
    {
316
        bool comment_in_args = false;
52✔
317
        std::string args;
52✔
318
        const bool split = (isLongLine(args_node) || !args_node.comment().empty());
52✔
319

320
        for (std::size_t i = 0, end = args_node.constList().size(); i < end; ++i)
152✔
321
        {
322
            const Node arg_i = args_node.constList()[i];
100✔
323
            if (!arg_i.comment().empty())
100✔
324
                comment_in_args = true;
8✔
325

326
            args += format(arg_i, indent + ((comment_in_args || split) ? 1 : 0), i > 0 && (comment_in_args || split));
160✔
327
            if (i != end - 1)
100✔
328
                args += (comment_in_args || split) ? '\n' : ' ';
60✔
329
        }
100✔
330

331
        formatted_args += fmt::format("({}{})", (comment_in_args ? "\n" : ""), args);
52✔
332
    }
52✔
333
    else
334
        formatted_args += format(args_node, indent, false);
2✔
335

336
    if (!shouldSplitOnNewline(body_node) && args_node.comment().empty())
54✔
337
        return fmt::format("(fun{} {})", formatted_args, format(body_node, indent + 1, false));
40✔
338
    return fmt::format("(fun{}\n{})", formatted_args, format(body_node, indent + 1, true));
14✔
339
}
54✔
340

341
std::string Formatter::formatVariable(const Node& node, const std::size_t indent)
146✔
342
{
146✔
343
    const auto keyword = std::string(keywords[static_cast<std::size_t>(node.constList()[0].keyword())]);
146✔
344

345
    const Node body_node = node.constList()[2];
146✔
346
    const std::string formatted_bind = format(node.constList()[1], indent, false);
146✔
347

348
    // we don't want to add another indentation level here, because it would result in a (let a (fun ()\n{indent+=4}...))
349
    if (isFuncDef(body_node) || !shouldSplitOnNewline(body_node))
146✔
350
        return fmt::format("({} {} {})", keyword, formatted_bind, format(body_node, indent, false));
140✔
351
    return fmt::format("({} {}\n{})", keyword, formatted_bind, format(body_node, indent + 1, true));
6✔
352
}
146✔
353

354
std::string Formatter::formatCondition(const Node& node, const std::size_t indent, const bool is_macro)
80✔
355
{
80✔
356
    const Node cond_node = node.constList()[1];
80✔
357
    const Node then_node = node.constList()[2];
80✔
358

359
    bool cond_on_newline = false;
80✔
360
    const std::string formatted_cond = format(cond_node, indent + 1, false);
80✔
361
    if (formatted_cond.find('\n') != std::string::npos)
80✔
362
        cond_on_newline = true;
4✔
363

364
    std::string if_cond_formatted = fmt::format(
240✔
365
        "({}if{}{}",
80✔
366
        is_macro ? "$" : "",
80✔
367
        cond_on_newline ? "\n" : " ",
80✔
368
        cond_on_newline ? format(cond_node, indent + 1, true) : formatted_cond);
80✔
369

370
    const bool split_then_newline = shouldSplitOnNewline(then_node) || isBeginBlock(then_node);
80✔
371

372
    // (if cond then)
373
    if (node.constList().size() == 3)
80✔
374
    {
375
        if (cond_on_newline || split_then_newline)
38✔
376
            return fmt::format("{}\n{})", if_cond_formatted, format(then_node, indent + 1, true));
22✔
377
        return fmt::format("{} {})", if_cond_formatted, format(then_node, indent + 1, false));
16✔
378
    }
379
    // (if cond then else)
380
    return fmt::format(
42✔
381
        "{}\n{}\n{}{})",
42✔
382
        if_cond_formatted,
383
        format(then_node, indent + 1, true),
42✔
384
        format(node.constList()[3], indent + 1, true),
42✔
385
        node.constList()[3].commentAfter().empty() ? "" : ("\n" + prefix(indent)));
42✔
386
}
80✔
387

388
std::string Formatter::formatLoop(const Node& node, const std::size_t indent)
20✔
389
{
20✔
390
    const Node cond_node = node.constList()[1];
20✔
391
    const Node body_node = node.constList()[2];
20✔
392

393
    bool cond_on_newline = false;
20✔
394
    std::string formatted_cond = format(cond_node, indent + 1, false);
20✔
395
    if (formatted_cond.find('\n') != std::string::npos)
20✔
396
        cond_on_newline = true;
2✔
397

398
    if (cond_on_newline || shouldSplitOnNewline(body_node))
20✔
399
        return fmt::format(
8✔
400
            "(while{}{}\n{})",
4✔
401
            cond_on_newline ? "\n" : " ",
4✔
402
            formatted_cond,
403
            format(body_node, indent + 1, true));
4✔
404
    return fmt::format(
16✔
405
        "(while {} {})",
16✔
406
        formatted_cond,
407
        format(body_node, indent + 1, false));
16✔
408
}
20✔
409

410
std::string Formatter::formatBegin(const Node& node, const std::size_t indent, const bool after_newline)
80✔
411
{
80✔
412
    // only the keyword begin is present
413
    if (node.constList().size() == 1)
80✔
414
        return "{}";
6✔
415

416
    // after a new line, we need to increment our indentation level
417
    // if the block is a top level one, we also need to increment indentation level
418
    const std::size_t inner_indentation = indent + (after_newline ? 1 : 0) + (indent == 0 ? 1 : 0);
74✔
419

420
    std::string result = "{\n";
74✔
421
    // skip begin keyword
422
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
266✔
423
    {
424
        const Node child = node.constList()[i];
192✔
425
        // we want to preserve the node grouping by the user, but remove useless duplicate new line
426
        // but that shouldn't apply to the first node of the block
427
        if (shouldAddNewLineBetweenNodes(node, i) && i > 1)
192✔
428
            result += "\n";
20✔
429

430
        result += format(child, inner_indentation, true);
192✔
431
        if (i != end - 1)
192✔
432
            result += "\n";
118✔
433
    }
192✔
434

435
    // if the last node has a comment, add a new line
436
    if (!node.constList().empty() && !node.constList().back().commentAfter().empty())
74✔
437
        result += "\n" + prefix(indent) + "}";
2✔
438
    else
439
        result += " }";
72✔
440
    return result;
74✔
441
}
80✔
442

443
std::string Formatter::formatImport(const Node& node, const std::size_t indent)
20✔
444
{
20✔
445
    const Node package_node = node.constList()[1];
20✔
446
    std::string package;
20✔
447

448
    if (!package_node.comment().empty())
20✔
449
        package += "\n" + formatComment(package_node.comment(), indent + 1) + prefix(indent + 1);
2✔
450
    else
451
        package += " ";
18✔
452

453
    for (std::size_t i = 0, end = package_node.constList().size(); i < end; ++i)
58✔
454
    {
455
        package += format(package_node.constList()[i], indent + 1, false);
38✔
456
        if (i != end - 1)
38✔
457
            package += ".";
18✔
458
    }
38✔
459

460
    const Node symbols = node.constList()[2];
20✔
461
    if (symbols.nodeType() == NodeType::Symbol && symbols.string() == "*")
20✔
462
        package += ":*";
2✔
463
    else  // symbols is a list
464
    {
465
        if (const auto& sym_list = symbols.constList(); !sym_list.empty())
28✔
466
        {
467
            const bool comment_after_last = !sym_list.back().commentAfter().empty();
10✔
468

469
            for (const auto& sym : sym_list)
24✔
470
            {
471
                if (sym.comment().empty())
14✔
472
                {
473
                    if (comment_after_last)
8✔
474
                        package += "\n" + prefix(indent + 1) + ":" + sym.string();
2✔
475
                    else
476
                        package += " :" + sym.string();
6✔
477
                }
8✔
478
                else
479
                    package += "\n" + formatComment(sym.comment(), indent + 1) + prefix(indent + 1) + ":" + sym.string();
6✔
480
            }
14✔
481

482
            if (comment_after_last)
10✔
483
            {
484
                package += " " + formatComment(sym_list.back().commentAfter(), /* indent= */ 0);
2✔
485
                package += "\n" + prefix(indent + 1);
2✔
486
            }
2✔
487
        }
10✔
488
    }
489

490
    return fmt::format("(import{})", package);
20✔
491
}
20✔
492

493
std::string Formatter::formatDel(const Node& node, const std::size_t indent)
4✔
494
{
4✔
495
    std::string formatted_sym = format(node.constList()[1], indent + 1, false);
4✔
496
    if (formatted_sym.find('\n') != std::string::npos)
4✔
497
        return fmt::format("(del\n{})", formatted_sym);
2✔
498
    return fmt::format("(del {})", formatted_sym);
2✔
499
}
4✔
500

501
std::string Formatter::formatCall(const Node& node, const std::size_t indent)
432✔
502
{
432✔
503
    bool is_list = false;
432✔
504
    bool is_dict = false;
432✔
505
    bool is_multiline = false;
432✔
506

507
    if (!node.constList().empty() && node.constList().front().nodeType() == NodeType::Symbol)
432✔
508
    {
509
        if (node.constList().front().string() == "list")
402✔
510
            is_list = true;
10✔
511
        else if (node.constList().front().string() == "dict")
392✔
512
            is_dict = true;
4✔
513
    }
402✔
514

515
    std::vector<std::string> formatted_args;
432✔
516
    for (std::size_t i = 1, end = node.constList().size(); i < end; ++i)
1,162✔
517
    {
518
        formatted_args.push_back(format(node.constList()[i], indent, false));
730✔
519
        // if we have at least one argument taking multiple lines, split them all on their own line
520
        if (formatted_args.back().find('\n') != std::string::npos || !node.constList()[i].commentAfter().empty())
730✔
521
            is_multiline = true;
22✔
522
    }
730✔
523

524
    std::string result = is_list ? "[" : ("(" + format(node.constList()[0], indent, false));
432✔
525

526
    // Split args on multiple lines even if, individually, they fit in the configured line length, if grouped together
527
    // on a single line they are too long
528
    const std::size_t args_line_length = std::accumulate(
432✔
529
        formatted_args.begin(),
432✔
530
        formatted_args.end(),
432✔
531
        result.size() + 1,  // +1 to count the closing paren/bracket
432✔
532
        [](const std::size_t acc, const std::string& val) {
730✔
533
            return acc + val.size() + 1_z;
730✔
534
        });
535
    if (args_line_length >= FormatterConfig::LongLineLength)
432✔
536
        is_multiline = true;
18✔
537

538
    for (std::size_t i = 0, end = formatted_args.size(); i < end; ++i)
1,162✔
539
    {
540
        const std::string& formatted_node = formatted_args[i];
730✔
541
        if (is_dict)
730✔
542
        {
543
            if (i % 2 == 0 && formatted_args.size() > 2)  // one pair per line if we have at least 2 key-value pairs
16✔
544
                result += "\n" + format(node.constList()[i + 1], indent + 1, true);
6✔
545
            else
546
                result += " " + formatted_node;
10✔
547
        }
16✔
548
        else if (is_multiline)
714✔
549
            result += "\n" + format(node.constList()[i + 1], indent + 1, true);
108✔
550
        else if (is_list && i == 0)
606✔
551
            result += formatted_node;
4✔
552
        else  // put all arguments on the same line
553
            result += " " + formatted_node;
602✔
554
    }
730✔
555
    if (!node.constList().back().commentAfter().empty())
432✔
556
        result += "\n" + prefix(indent);
6✔
557

558
    result += is_list ? "]" : ")";
432✔
559
    return result;
432✔
560
}
432✔
561

562
std::string Formatter::formatMacro(const Node& node, const std::size_t indent)
24✔
563
{
24✔
564
    if (isListStartingWithKeyword(node, Keyword::If))
24✔
565
        return formatCondition(node, indent, /* is_macro= */ true);
6✔
566

567
    std::string result = "(macro ";
18✔
568
    bool after_newline = false;
18✔
569

570
    for (std::size_t i = 0, end = node.constList().size(); i < end; ++i)
70✔
571
    {
572
        result += format(node.constList()[i], indent + 1, after_newline);
52✔
573
        after_newline = false;
52✔
574

575
        if (!node.constList()[i].commentAfter().empty())
52✔
576
        {
577
            result += "\n";
6✔
578
            after_newline = true;
6✔
579
        }
6✔
580
        else if (i != end - 1)
46✔
581
            result += " ";
30✔
582
    }
52✔
583

584
    return result + ")";
18✔
585
}
24✔
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

© 2026 Coveralls, Inc