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

yuin / goldmark / 29184789560

12 Jul 2026 07:46AM UTC coverage: 82.991% (-1.2%) from 84.201%
29184789560

push

github

yuin
perf: Optimize text/value WriteTo methods to avoid unnecessary allocations and copying

39 of 75 new or added lines in 2 files covered. (52.0%)

368 existing lines in 14 files now uncovered.

6392 of 7702 relevant lines covered (82.99%)

349203.15 hits per line

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

91.75
/parser/parser.go
1
// Package parser contains stuff that are related to parsing a Markdown text.
2
package parser
3

4
import (
5
        "fmt"
6
        "strings"
7
        "sync"
8

9
        "github.com/yuin/goldmark/v2/ast"
10
        "github.com/yuin/goldmark/v2/text"
11
        "github.com/yuin/goldmark/v2/util"
12
)
13

14
// A LinkDefinition interface represents a link definition in Markdown text.
15
type LinkDefinition interface {
16
        // String implements Stringer.
17
        String() string
18

19
        // Label returns a label of the link definition.
20
        Label() []byte
21

22
        // Destination returns a destination(URL) of the link definition.
23
        Destination() []byte
24

25
        // Title returns a title of the link definition.
26
        Title() []byte
27
}
28

29
type linkDefinition struct {
30
        node        *ast.LinkReferenceDefinition
31
        label       []byte
32
        destination []byte
33
        title       []byte
34
}
35

36
// NewLinkDefinition returns a new LinkDefinition.
37
func NewLinkDefinition(label, destination, title []byte) LinkDefinition {
×
38
        return &linkDefinition{nil, label, destination, title}
×
39
}
×
40

41
func newLinkDefinitionFromNode(v *ast.LinkReferenceDefinition, src []byte) LinkDefinition {
531✔
42
        return &linkDefinition{
531✔
43
                node:        v,
531✔
44
                label:       v.Label.Bytes(src),
531✔
45
                destination: v.Destination.Bytes(src),
531✔
46
                title:       v.Title.Bytes(src),
531✔
47
        }
531✔
48
}
531✔
49

50
func (r *linkDefinition) Label() []byte {
531✔
51
        return r.label
531✔
52
}
531✔
53

54
func (r *linkDefinition) Destination() []byte {
×
55
        return r.destination
×
56
}
×
57

58
func (r *linkDefinition) Title() []byte {
×
59
        return r.title
×
60
}
×
61

62
func (r *linkDefinition) String() string {
×
63
        return fmt.Sprintf("LinkDefinition{Label:%s, Destination:%s, Title:%s}", r.label, r.destination, r.title)
×
64
}
×
65

66
// An IDGenerator generates element IDs from a value and node kind.
67
// Implementations should return a base ID; uniqueness is handled by IDs.
68
type IDGenerator interface {
69
        // Generate generates a base element id for the given value and node kind.
70
        Generate(value []byte, kind ast.NodeKind) []byte
71
}
72

73
// IDs is a collection of element ids, tracking uniqueness across a parse.
74
type IDs struct {
75
        values    map[string]bool
76
        generator IDGenerator
77
}
78

79
type idsConfig struct {
80
        IDGenerator IDGenerator
81
}
82

83
// An IDsOption is an interface for options that can be passed to NewIDs.
84
type IDsOption interface {
85
        SetIDsOption(*idsConfig)
86
}
87

88
// NewIDs returns a new IDs.
89
// By default, the default IDGenerator is used.
90
// Use WithIDGenerator as an IDsOption to customize ID generation.
91
func NewIDs(opts ...IDsOption) *IDs {
4,524✔
92
        c := &idsConfig{IDGenerator: &defaultIDGenerator{}}
4,524✔
93
        for _, opt := range opts {
9,048✔
94
                opt.SetIDsOption(c)
4,524✔
95
        }
4,524✔
96
        return &IDs{
4,524✔
97
                values:    map[string]bool{},
4,524✔
98
                generator: c.IDGenerator,
4,524✔
99
        }
4,524✔
100
}
101

102
// Generate generates a unique element id for the given value and node kind.
103
// If the base id from the generator is already used, a numeric suffix is appended.
104
func (s *IDs) Generate(value []byte, kind ast.NodeKind) []byte {
216✔
105
        result := s.generator.Generate(value, kind)
216✔
106
        if _, ok := s.values[util.BytesToReadOnlyString(result)]; !ok {
378✔
107
                s.values[util.BytesToReadOnlyString(result)] = true
162✔
108
                return result
162✔
109
        }
162✔
110
        for i := 1; ; i++ {
150✔
111
                newResult := fmt.Sprintf("%s-%d", result, i)
96✔
112
                if _, ok := s.values[newResult]; !ok {
150✔
113
                        s.values[newResult] = true
54✔
114
                        return []byte(newResult)
54✔
115
                }
54✔
116
        }
117
}
118

119
// Put marks the given element id as used.
120
func (s *IDs) Put(value []byte) {
27✔
121
        s.values[util.BytesToReadOnlyString(value)] = true
27✔
122
}
27✔
123

124
type defaultIDGenerator struct{}
125

126
func (g *defaultIDGenerator) Generate(value []byte, kind ast.NodeKind) []byte {
210✔
127
        value = util.TrimLeftSpace(value)
210✔
128
        value = util.TrimRightSpace(value)
210✔
129
        result := []byte{}
210✔
130
        for i := 0; i < len(value); {
1,509✔
131
                v := value[i]
1,299✔
132
                l := util.UTF8Len(v)
1,299✔
133
                i += int(l)
1,299✔
134
                if l != 1 {
1,299✔
135
                        continue
×
136
                }
137
                if util.IsAlphaNumeric(v) {
2,304✔
138
                        if 'A' <= v && v <= 'Z' {
1,110✔
139
                                v += 'a' - 'A'
105✔
140
                        }
105✔
141
                        result = append(result, v)
1,005✔
142
                } else if util.IsSpace(v) || v == '-' || v == '_' {
372✔
143
                        result = append(result, '-')
78✔
144
                }
78✔
145
        }
146
        if len(result) == 0 {
222✔
147
                if kind == ast.KindHeading {
24✔
148
                        return []byte("heading")
12✔
149
                }
12✔
150
                return []byte("id")
×
151
        }
152
        return result
198✔
153
}
154

155
// ContextKey is a key that is used to set arbitrary values to the context.
156
type ContextKey int
157

158
// ContextKeyMax is a maximum value of the ContextKey.
159
var ContextKeyMax ContextKey
160

161
// NewContextKey return a new ContextKey value.
162
func NewContextKey() ContextKey {
99✔
163
        ContextKeyMax++
99✔
164
        return ContextKeyMax
99✔
165
}
99✔
166

167
// A Context interface holds a information that are necessary to parse
168
// Markdown text.
169
type Context interface {
170
        // String implements Stringer.
171
        String() string
172

173
        // Get returns a value associated with the given key.
174
        Get(ContextKey) any
175

176
        // ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value.
177
        ComputeIfAbsent(ContextKey, func() any) any
178

179
        // Set sets the given value to the context.
180
        Set(ContextKey, any)
181

182
        // AddLinkDefinition adds the given link definition to this context.
183
        AddLinkDefinition(LinkDefinition)
184

185
        // LinkDefinition returns (a link definition, true) if a link definition associated with
186
        // the given label exists, otherwise (nil, false).
187
        LinkDefinition(label string) (LinkDefinition, bool)
188

189
        // LinkDefinitions returns a list of link definitions.
190
        LinkDefinitions() []LinkDefinition
191

192
        // IDs returns a collection of the element ids.
193
        IDs() *IDs
194

195
        // BlockOffset returns a first non-space character position on current line.
196
        // This value is valid only for BlockParser.Open.
197
        // BlockOffset returns -1 if current line is blank.
198
        BlockOffset() int
199

200
        // SetBlockOffset sets a first non-space character position on current line.
201
        // This value is valid only for BlockParser.Open.
202
        SetBlockOffset(int)
203

204
        // BlockIndent returns an indent width on current line.
205
        // This value is valid only for BlockParser.Open.
206
        // BlockIndent returns -1 if current line is blank.
207
        BlockIndent() int
208

209
        // SetBlockIndent sets an indent width on current line.
210
        // This value is valid only for BlockParser.Open.
211
        SetBlockIndent(int)
212

213
        // FirstDelimiter returns a first delimiter of the current delimiter list.
214
        FirstDelimiter() *Delimiter
215

216
        // LastDelimiter returns a last delimiter of the current delimiter list.
217
        LastDelimiter() *Delimiter
218

219
        // PushDelimiter appends the given delimiter to the tail of the current
220
        // delimiter list.
221
        PushDelimiter(delimiter *Delimiter)
222

223
        // RemoveDelimiter removes the given delimiter from the current delimiter list.
224
        RemoveDelimiter(d *Delimiter)
225

226
        // ClearDelimiters clears the current delimiter list.
227
        ClearDelimiters(bottom ast.Node)
228

229
        // OpenedBlocks returns a list of nodes that are currently in parsing.
230
        OpenedBlocks() []Block
231

232
        // SetOpenedBlocks sets a list of nodes that are currently in parsing.
233
        SetOpenedBlocks([]Block)
234

235
        // LastOpenedBlock returns a last node that is currently in parsing.
236
        LastOpenedBlock() Block
237

238
        // IsInLinkLabel returns true if current position seems to be in link label.
239
        IsInLinkLabel() bool
240
}
241

242
// A ContextOption is an interface for options that can be passed to NewContext.
243
type ContextOption interface {
244
        SetContextOption(*contextConfig)
245
}
246

247
type contextConfig struct {
248
        IDGenerator IDGenerator
249
}
250

251
type parseContext struct {
252
        store         []any
253
        ids           *IDs
254
        linkDefs      map[string]LinkDefinition
255
        blockOffset   int
256
        blockIndent   int
257
        delimiters    *Delimiter
258
        lastDelimiter *Delimiter
259
        openedBlocks  []Block
260
}
261

262
// NewContext returns a new Context.
263
// By default, a new IDs with the default IDGenerator is used.
264
// Use WithIDGenerator as a ContextOption to customize ID generation.
265
func NewContext(opts ...ContextOption) Context {
4,524✔
266
        cc := &contextConfig{IDGenerator: &defaultIDGenerator{}}
4,524✔
267
        for _, opt := range opts {
9,048✔
268
                opt.SetContextOption(cc)
4,524✔
269
        }
4,524✔
270
        return &parseContext{
4,524✔
271
                store:        make([]any, ContextKeyMax+1),
4,524✔
272
                linkDefs:     map[string]LinkDefinition{},
4,524✔
273
                ids:          NewIDs(WithIDGenerator(cc.IDGenerator)),
4,524✔
274
                blockOffset:  -1,
4,524✔
275
                blockIndent:  -1,
4,524✔
276
                openedBlocks: []Block{},
4,524✔
277
        }
4,524✔
278
}
279

280
func (p *parseContext) Get(key ContextKey) any {
1,681,773✔
281
        return p.store[key]
1,681,773✔
282
}
1,681,773✔
283

284
func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() any) any {
93✔
285
        v := p.store[key]
93✔
286
        if v == nil {
123✔
287
                v = f()
30✔
288
                p.store[key] = v
30✔
289
        }
30✔
290
        return v
93✔
291
}
292

293
func (p *parseContext) Set(key ContextKey, value any) {
918,006✔
294
        p.store[key] = value
918,006✔
295
}
918,006✔
296

297
func (p *parseContext) IDs() *IDs {
243✔
298
        return p.ids
243✔
299
}
243✔
300

301
func (p *parseContext) BlockOffset() int {
13,161✔
302
        return p.blockOffset
13,161✔
303
}
13,161✔
304

305
func (p *parseContext) SetBlockOffset(v int) {
15,024✔
306
        p.blockOffset = v
15,024✔
307
}
15,024✔
308

309
func (p *parseContext) BlockIndent() int {
90✔
310
        return p.blockIndent
90✔
311
}
90✔
312

313
func (p *parseContext) SetBlockIndent(v int) {
15,024✔
314
        p.blockIndent = v
15,024✔
315
}
15,024✔
316

317
func (p *parseContext) LastDelimiter() *Delimiter {
459,084✔
318
        return p.lastDelimiter
459,084✔
319
}
459,084✔
320

321
func (p *parseContext) FirstDelimiter() *Delimiter {
1,233✔
322
        return p.delimiters
1,233✔
323
}
1,233✔
324

325
func (p *parseContext) PushDelimiter(d *Delimiter) {
3,015✔
326
        if p.delimiters == nil {
4,332✔
327
                p.delimiters = d
1,317✔
328
                p.lastDelimiter = d
1,317✔
329
        } else {
3,015✔
330
                l := p.lastDelimiter
1,698✔
331
                p.lastDelimiter = d
1,698✔
332
                l.NextDelimiter = d
1,698✔
333
                d.PreviousDelimiter = l
1,698✔
334
        }
1,698✔
335
}
336

337
func (p *parseContext) RemoveDelimiter(d *Delimiter) {
3,015✔
338
        if d.PreviousDelimiter == nil {
5,283✔
339
                p.delimiters = d.NextDelimiter
2,268✔
340
        } else {
3,015✔
341
                d.PreviousDelimiter.NextDelimiter = d.NextDelimiter
747✔
342
                if d.NextDelimiter != nil {
1,197✔
343
                        d.NextDelimiter.PreviousDelimiter = d.PreviousDelimiter
450✔
344
                }
450✔
345
        }
346
        if d.NextDelimiter == nil {
4,629✔
347
                p.lastDelimiter = d.PreviousDelimiter
1,614✔
348
        }
1,614✔
349
        if p.delimiters != nil {
4,713✔
350
                p.delimiters.PreviousDelimiter = nil
1,698✔
351
        }
1,698✔
352
        if p.lastDelimiter != nil {
4,713✔
353
                p.lastDelimiter.NextDelimiter = nil
1,698✔
354
        }
1,698✔
355
        d.NextDelimiter = nil
3,015✔
356
        d.PreviousDelimiter = nil
3,015✔
357
        if d.Length != 0 {
3,858✔
358
                ast.MergeOrReplaceTextSegment(d.Parent(), d, d.value)
843✔
359
        } else {
3,015✔
360
                d.Parent().RemoveChild(d)
2,172✔
361
        }
2,172✔
362
}
363

364
func (p *parseContext) ClearDelimiters(bottom ast.Node) {
1,401✔
365
        if p.lastDelimiter == nil {
2,301✔
366
                return
900✔
367
        }
900✔
368
        var c ast.Node
501✔
369
        for c = p.lastDelimiter; c != nil && c != bottom; {
1,485✔
370
                prev := c.PreviousSibling()
984✔
371
                if d, ok := c.(*Delimiter); ok {
1,572✔
372
                        p.RemoveDelimiter(d)
588✔
373
                }
588✔
374
                c = prev
984✔
375
        }
376
}
377

378
func (p *parseContext) AddLinkDefinition(ref LinkDefinition) {
531✔
379
        key := util.ToLinkReference(ref.Label())
531✔
380
        if _, ok := p.linkDefs[key]; !ok {
1,050✔
381
                p.linkDefs[key] = ref
519✔
382
        }
519✔
383
}
384

385
func (p *parseContext) LinkDefinition(label string) (LinkDefinition, bool) {
816✔
386
        v, ok := p.linkDefs[label]
816✔
387
        return v, ok
816✔
388
}
816✔
389

390
func (p *parseContext) LinkDefinitions() []LinkDefinition {
×
391
        ret := make([]LinkDefinition, 0, len(p.linkDefs))
×
392
        for _, v := range p.linkDefs {
×
393
                ret = append(ret, v)
×
394
        }
×
395
        return ret
×
396
}
397

398
func (p *parseContext) String() string {
×
399
        refs := []string{}
×
400
        for _, r := range p.linkDefs {
×
401
                refs = append(refs, r.String())
×
402
        }
×
403

404
        return fmt.Sprintf("Context{Store:%#v, LinkDefinitions:%s}", p.store, strings.Join(refs, ","))
×
405
}
406

407
func (p *parseContext) OpenedBlocks() []Block {
34,542✔
408
        return p.openedBlocks
34,542✔
409
}
34,542✔
410

411
func (p *parseContext) SetOpenedBlocks(v []Block) {
24,270✔
412
        p.openedBlocks = v
24,270✔
413
}
24,270✔
414

415
func (p *parseContext) LastOpenedBlock() Block {
44,382✔
416
        if l := len(p.openedBlocks); l != 0 {
65,910✔
417
                return p.openedBlocks[l-1]
21,528✔
418
        }
21,528✔
419
        return Block{}
22,854✔
420
}
421

422
func (p *parseContext) IsInLinkLabel() bool {
7,482✔
423
        tlist := p.Get(linkLabelStateKey)
7,482✔
424
        return tlist != nil
7,482✔
425
}
7,482✔
426

427
// State represents parser's state.
428
// State is designed to use as a bit flag.
429
type State int
430

431
const (
432
        // None is a default value of the [State].
433
        None State = 1 << iota
434

435
        // Continue indicates parser can continue parsing.
436
        Continue
437

438
        // Close indicates parser cannot parse anymore.
439
        Close
440

441
        // HasChildren indicates parser may have child blocks.
442
        HasChildren
443

444
        // NoChildren indicates parser does not have child blocks.
445
        NoChildren
446

447
        // RequireParagraph indicates parser requires that the last node
448
        // must be a paragraph and is not converted to other nodes by
449
        // ParagraphTransformers.
450
        RequireParagraph
451
)
452

453
// A Config struct is a data structure that holds configuration of the Parser.
454
type Config struct {
455
        // IDGenerator is a custom IDGenerator for element id generation.
456
        IDGenerator IDGenerator
457

458
        // EscapedSpace indicates that a '\' escaped half-space(0x20) should not trigger parsers.
459
        // nil means the option was not set via NewParser/AddOptions.
460
        EscapedSpace bool
461

462
        // Attribute indicates that custom attributes are enabled.
463
        // nil means the option was not set via NewParser/AddOptions.
464
        Attribute bool
465

466
        withoutDefaultParsers bool
467

468
        autoHeadingID bool
469

470
        blockParsers          util.PrioritizedValues[BlockParser]
471
        inlineParsers         util.PrioritizedValues[InlineParser]
472
        paragraphTransformers util.PrioritizedValues[ParagraphTransformer]
473
        astTransformers       util.PrioritizedValues[ASTTransformer]
474
        extensions            []Extension
475
}
476

477
// An Option interface is a functional option type for the Parser.
478
type Option interface {
479
        SetParserOption(*Config)
480
}
481

482
type withAttribute struct{}
483

484
func (o *withAttribute) SetParserOption(c *Config) {
1,965✔
485
        c.Attribute = true
1,965✔
486
}
1,965✔
487

488
func (o *withAttribute) setHeadingOption(p *HeadingConfig) {
3,930✔
489
        p.attribute = true
3,930✔
490
}
3,930✔
491

492
// WithAttribute is a functional option that enables custom attributes.
493
// It can be used as a parser Option and a HeadingOption.
494
func WithAttribute() interface {
495
        Option
496
        HeadingOption
497
} {
3,930✔
498
        return &withAttribute{}
3,930✔
499
}
3,930✔
500

501
type withDefaultParsers struct {
502
        v bool
503
}
504

505
func (o *withDefaultParsers) SetParserOption(c *Config) {
×
506
        c.withoutDefaultParsers = !o.v
×
507
}
×
508

509
// WithDefaultParsers is a functional option that indicates whether default parsers should be used.
510
func WithDefaultParsers(v bool) Option {
×
511
        return &withDefaultParsers{v}
×
512
}
×
513

514
type withExtensions struct {
515
        value []Extension
516
}
517

518
func (o *withExtensions) SetParserOption(c *Config) {
2,031✔
519
        c.extensions = append(c.extensions, o.value...)
2,031✔
520
}
2,031✔
521

522
// WithExtensions is a functional option that allows you to add extensions to the parser.
523
func WithExtensions(ext ...Extension) Option {
2,031✔
524
        return &withExtensions{ext}
2,031✔
525
}
2,031✔
526

527
// Nil is a special AST node that represents an empty node.
528
// If a parser returns Nil, the parser is considered as successful but does not add any node to the AST tree.
529
var Nil = ast.NewText()
530

531
// A Parser interface parses Markdown text into AST nodes.
532
type Parser interface {
533
        // Parse parses the given Markdown text into AST nodes.
534
        Parse(source []byte) ast.Node
535

536
        // ParseStringSource is a helper function that parses a string source into AST nodes using the given parser.
537
        //
538
        // This function converts the string source into a read-only byte slice without copying the data, and then
539
        // calls the Parse method of the provided parser.
540
        ParseStringSource(source string) ast.Node
541
}
542

543
// A BlockParser interface parses a block level element like Paragraph, List,
544
// Blockquote etc.
545
type BlockParser interface {
546
        // Trigger returns a list of characters that triggers Parse method of
547
        // this parser.
548
        // If Trigger returns a nil, Open will be called with any lines.
549
        Trigger() []byte
550

551
        // Open parses the current line and returns a result of parsing.
552
        //
553
        // Open must not parse beyond the current line.
554
        // If Open has been able to parse the current line, Open must advance a reader
555
        // position by consumed byte length.
556
        //
557
        // If Open has not been able to parse the current line, Open should returns
558
        // (nil, NoChildren). If Open has been able to parse the current line, Open
559
        // should returns a new Block node and returns HasChildren or NoChildren.
560
        Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State)
561

562
        // Continue parses the current line and returns a result of parsing.
563
        //
564
        // Continue must not parse beyond the current line.
565
        // If Continue has been able to parse the current line, Continue must advance
566
        // a reader position by consumed byte length.
567
        //
568
        // If Continue has not been able to parse the current line, Continue should
569
        // returns Close. If Continue has been able to parse the current line,
570
        // Continue should returns (Continue | NoChildren) or
571
        // (Continue | HasChildren)
572
        Continue(node ast.Node, reader text.Reader, pc Context) State
573

574
        // Close will be called when the parser returns Close.
575
        Close(node ast.Node, reader text.Reader, pc Context)
576

577
        // CanInterruptParagraph returns true if the parser can interrupt paragraphs,
578
        // otherwise false.
579
        CanInterruptParagraph() bool
580

581
        // CanAcceptIndentedLine returns true if the parser can open new node when
582
        // the given line is being indented more than 3 spaces.
583
        CanAcceptIndentedLine() bool
584
}
585

586
// An InlineParser interface parses an inline level element like CodeSpan, Link etc.
587
type InlineParser interface {
588
        // Trigger returns a list of characters that triggers Parse method of
589
        // this parser.
590
        // Trigger characters must be a punctuation or a halfspace.
591
        // Halfspaces triggers this parser when character is any spaces characters or
592
        // a head of line
593
        Trigger() []byte
594

595
        // Parse parse the given block into an inline node.
596
        //
597
        // Parse can parse beyond the current line.
598
        // If Parse has been able to parse the current line, it must advance a reader
599
        // position by consumed byte length.
600
        Parse(parent ast.Node, block text.Reader, pc Context) ast.Node
601
}
602

603
// A CloseBlocker interface is a callback function that will be
604
// called when block is closed in the inline parsing.
605
type CloseBlocker interface {
606
        // CloseBlock will be called when a block is closed.
607
        CloseBlock(parent ast.Node, block text.Reader, pc Context)
608
}
609

610
// A ParagraphTransformer transforms parsed Paragraph nodes.
611
// For example, link references are searched in parsed Paragraphs.
612
type ParagraphTransformer interface {
613
        // Transform transforms the given paragraph.
614
        Transform(node *ast.Paragraph, reader text.Reader, pc Context)
615
}
616

617
// ASTTransformer transforms entire Markdown document AST tree.
618
type ASTTransformer interface {
619
        // Transform transforms the given AST tree.
620
        Transform(node *ast.Document, reader text.Reader, pc Context)
621
}
622

623
// A Block struct holds a node and correspond parser pair.
624
type Block struct {
625
        // Node is a BlockNode.
626
        Node ast.Node
627
        // Parser is a BlockParser.
628
        Parser BlockParser
629
}
630

631
type parser struct {
632
        blockParsers          [256][]BlockParser
633
        freeBlockParsers      []BlockParser
634
        inlineParsers         [256][]InlineParser
635
        closeBlockers         []CloseBlocker
636
        paragraphTransformers []ParagraphTransformer
637
        astTransformers       []ASTTransformer
638
        escapedSpace          bool
639
        idGenerator           IDGenerator
640
        config                *Config
641
        initSync              sync.Once
642
}
643

644
type withBlockParsers struct {
645
        value []util.PrioritizedValue[BlockParser]
646
}
647

648
func (o *withBlockParsers) SetParserOption(c *Config) {
6,054✔
649
        c.blockParsers = append(c.blockParsers, o.value...)
6,054✔
650
}
6,054✔
651

652
// WithBlockParsers is a functional option that allow you to add
653
// BlockParsers to the parser.
654
func WithBlockParsers(bs ...util.PrioritizedValue[BlockParser]) Option {
6,054✔
655
        return &withBlockParsers{bs}
6,054✔
656
}
6,054✔
657

658
type withInlineParsers struct {
659
        value []util.PrioritizedValue[InlineParser]
660
}
661

662
func (o *withInlineParsers) SetParserOption(c *Config) {
15,858✔
663
        c.inlineParsers = append(c.inlineParsers, o.value...)
15,858✔
664
}
15,858✔
665

666
// WithInlineParsers is a functional option that allow you to add
667
// InlineParsers to the parser.
668
func WithInlineParsers(bs ...util.PrioritizedValue[InlineParser]) Option {
15,858✔
669
        return &withInlineParsers{bs}
15,858✔
670
}
15,858✔
671

672
type withParagraphTransformers struct {
673
        value []util.PrioritizedValue[ParagraphTransformer]
674
}
675

676
func (o *withParagraphTransformers) SetParserOption(c *Config) {
6,066✔
677
        c.paragraphTransformers = append(c.paragraphTransformers, o.value...)
6,066✔
678
}
6,066✔
679

680
// WithParagraphTransformers is a functional option that allow you to add
681
// ParagraphTransformers to the parser.
682
func WithParagraphTransformers(ps ...util.PrioritizedValue[ParagraphTransformer]) Option {
6,066✔
683
        return &withParagraphTransformers{ps}
6,066✔
684
}
6,066✔
685

686
type withASTTransformers struct {
687
        value []util.PrioritizedValue[ASTTransformer]
688
}
689

690
func (o *withASTTransformers) SetParserOption(c *Config) {
3,954✔
691
        c.astTransformers = append(c.astTransformers, o.value...)
3,954✔
692
}
3,954✔
693

694
// WithASTTransformers is a functional option that allow you to add
695
// ASTTransformers to the parser.
696
func WithASTTransformers(ps ...util.PrioritizedValue[ASTTransformer]) Option {
3,954✔
697
        return &withASTTransformers{ps}
3,954✔
698
}
3,954✔
699

700
type withEscapedSpace struct {
701
}
702

703
func (o *withEscapedSpace) SetParserOption(c *Config) {
12✔
704
        c.EscapedSpace = true
12✔
705
}
12✔
706

707
// WithEscapedSpace is a functional option indicates that a '\' escaped half-space(0x20) should not trigger parsers.
708
func WithEscapedSpace() Option {
12✔
709
        return &withEscapedSpace{}
12✔
710
}
12✔
711

712
type withIDGenerator struct {
713
        gen IDGenerator
714
}
715

716
func (o *withIDGenerator) SetParserOption(c *Config) {
3✔
717
        c.IDGenerator = o.gen
3✔
718
}
3✔
719

720
func (o *withIDGenerator) SetContextOption(c *contextConfig) {
4,524✔
721
        c.IDGenerator = o.gen
4,524✔
722
}
4,524✔
723

724
func (o *withIDGenerator) SetIDsOption(c *idsConfig) {
4,524✔
725
        c.IDGenerator = o.gen
4,524✔
726
}
4,524✔
727

728
// WithIDGenerator is a functional option that sets a custom IDGenerator for element id generation.
729
// It can be used as a parser Option, a parser ContextOption, and a parser IDsOption.
730
func WithIDGenerator(gen IDGenerator) interface {
731
        Option
732
        ContextOption
733
        IDsOption
734
} {
9,051✔
735
        return &withIDGenerator{gen}
9,051✔
736
}
9,051✔
737

738
// New returns a new Parser with given options.
739
func New(options ...Option) Parser {
2,118✔
740
        config := &Config{}
2,118✔
741
        for _, opt := range options {
8,103✔
742
                opt.SetParserOption(config)
5,985✔
743
        }
5,985✔
744
        if !config.withoutDefaultParsers {
4,236✔
745
                for _, opt := range CommonMark.ParserOptions(config) {
8,472✔
746
                        opt.SetParserOption(config)
6,354✔
747
                }
6,354✔
748
        }
749

750
        for _, ext := range config.extensions {
15,903✔
751
                options := ext.ParserOptions(config)
13,785✔
752
                for _, opt := range options {
39,357✔
753
                        opt.SetParserOption(config)
25,572✔
754
                }
25,572✔
755
        }
756

757
        p := &parser{
2,118✔
758
                config: config,
2,118✔
759
        }
2,118✔
760

2,118✔
761
        return p
2,118✔
762
}
763

UNCOV
764
func (p *parser) AddOptions(opts ...Option) {
×
765
        for _, opt := range opts {
×
766
                opt.SetParserOption(p.config)
×
767
        }
×
768
}
769

770
func (p *parser) addBlockParser(v util.PrioritizedValue[BlockParser]) {
27,081✔
771
        bp := v.Value
27,081✔
772
        tcs := bp.Trigger()
27,081✔
773
        if tcs == nil {
31,317✔
774
                p.freeBlockParsers = append(p.freeBlockParsers, bp)
4,236✔
775
        } else {
27,081✔
776
                for _, tc := range tcs {
104,994✔
777
                        if p.blockParsers[tc] == nil {
128,445✔
778
                                p.blockParsers[tc] = []BlockParser{}
46,296✔
779
                        }
46,296✔
780
                        p.blockParsers[tc] = append(p.blockParsers[tc], bp)
82,149✔
781
                }
782
        }
783
}
784

785
func (p *parser) addInlineParser(v util.PrioritizedValue[InlineParser]) {
24,330✔
786
        ip := v.Value
24,330✔
787
        tcs := ip.Trigger()
24,330✔
788
        if cb, ok := ip.(CloseBlocker); ok {
26,448✔
789
                p.closeBlockers = append(p.closeBlockers, cb)
2,118✔
790
        }
2,118✔
791
        for _, tc := range tcs {
88,377✔
792
                if p.inlineParsers[tc] == nil {
96,558✔
793
                        p.inlineParsers[tc] = []InlineParser{}
32,511✔
794
                }
32,511✔
795
                p.inlineParsers[tc] = append(p.inlineParsers[tc], ip)
64,047✔
796
        }
797
}
798

799
func (p *parser) addParagraphTransformer(v util.PrioritizedValue[ParagraphTransformer]) {
6,066✔
800
        pt := v.Value
6,066✔
801
        p.paragraphTransformers = append(p.paragraphTransformers, pt)
6,066✔
802
}
6,066✔
803

804
func (p *parser) addASTTransformer(v util.PrioritizedValue[ASTTransformer]) {
3,954✔
805
        at := v.Value
3,954✔
806
        p.astTransformers = append(p.astTransformers, at)
3,954✔
807
}
3,954✔
808

809
func (p *parser) Parse(source []byte) ast.Node {
4,524✔
810
        p.initSync.Do(func() {
6,642✔
811
                p.config.blockParsers.Sort()
2,118✔
812
                for _, v := range p.config.blockParsers {
29,199✔
813
                        p.addBlockParser(v)
27,081✔
814
                }
27,081✔
815
                for i := range p.blockParsers {
544,326✔
816
                        if p.blockParsers[i] != nil {
588,504✔
817
                                p.blockParsers[i] = append(p.blockParsers[i], p.freeBlockParsers...)
46,296✔
818
                        }
46,296✔
819
                }
820

821
                p.config.inlineParsers.Sort()
2,118✔
822
                for _, v := range p.config.inlineParsers {
26,448✔
823
                        p.addInlineParser(v)
24,330✔
824
                }
24,330✔
825
                p.config.paragraphTransformers.Sort()
2,118✔
826
                for _, v := range p.config.paragraphTransformers {
8,184✔
827
                        p.addParagraphTransformer(v)
6,066✔
828
                }
6,066✔
829
                p.config.astTransformers.Sort()
2,118✔
830
                for _, v := range p.config.astTransformers {
6,072✔
831
                        p.addASTTransformer(v)
3,954✔
832
                }
3,954✔
833
                p.escapedSpace = p.config.EscapedSpace
2,118✔
834
                p.idGenerator = p.config.IDGenerator
2,118✔
835
                if p.idGenerator == nil {
4,233✔
836
                        p.idGenerator = &defaultIDGenerator{}
2,115✔
837
                }
2,115✔
838
                p.config = nil
2,118✔
839
        })
840
        reader := text.NewReader(source)
4,524✔
841
        pc := NewContext(WithIDGenerator(p.idGenerator))
4,524✔
842
        root := ast.NewDocument()
4,524✔
843
        p.parseBlocks(root, reader, pc)
4,524✔
844

4,524✔
845
        blockReader := text.NewBlockReader(reader.Source(), nil)
4,524✔
846
        p.walkBlock(root, func(node ast.Node) {
20,421✔
847
                p.parseBlock(blockReader, node, pc)
15,897✔
848
        })
15,897✔
849
        for _, at := range p.astTransformers {
8,517✔
850
                at.Transform(root, reader, pc)
3,993✔
851
        }
3,993✔
852

853
        return root
4,524✔
854
}
855

UNCOV
856
func (p *parser) ParseStringSource(source string) ast.Node {
×
UNCOV
857
        return p.Parse(util.StringToReadOnlyBytes(source))
×
UNCOV
858
}
×
859

860
func (p *parser) transformParagraph(node *ast.Paragraph, reader text.Reader, pc Context) bool {
6,225✔
861
        for _, pt := range p.paragraphTransformers {
16,704✔
862
                pt.Transform(node, reader, pc)
10,479✔
863
                if node.Parent() == nil {
11,004✔
864
                        return true
525✔
865
                }
525✔
866
        }
867
        return false
5,700✔
868
}
869

870
func (p *parser) closeBlocks(from, to int, reader text.Reader, pc Context) {
8,541✔
871
        blocks := pc.OpenedBlocks()
8,541✔
872
        for i := from; i >= to; i-- {
19,410✔
873
                node := blocks[i].Node
10,869✔
874
                paragraph, ok := node.(*ast.Paragraph)
10,869✔
875
                if ok && node.Parent() != nil {
16,926✔
876
                        p.transformParagraph(paragraph, reader, pc)
6,057✔
877
                }
6,057✔
878
                if node.Parent() != nil { // closes only if node has not been transformed
21,210✔
879
                        blocks[i].Parser.Close(blocks[i].Node, reader, pc)
10,341✔
880
                }
10,341✔
881
        }
882
        if from == len(blocks)-1 {
15,336✔
883
                blocks = blocks[0:to]
6,795✔
884
        } else {
8,541✔
885
                blocks = append(blocks[0:to], blocks[from+1:]...)
1,746✔
886
        }
1,746✔
887
        pc.SetOpenedBlocks(blocks)
8,541✔
888
}
889

890
type blockOpenResult int
891

892
const (
893
        paragraphContinuation blockOpenResult = iota + 1
894
        newBlocksOpened
895
        noBlocksOpened
896
)
897

898
func (p *parser) openBlocks(parent ast.Node, blankLine bool, reader text.Reader, pc Context) blockOpenResult {
12,231✔
899
        result := blockOpenResult(noBlocksOpened)
12,231✔
900
        continuable := false
12,231✔
901
        lastBlock := pc.LastOpenedBlock()
12,231✔
902
        if lastBlock.Node != nil {
18,186✔
903
                continuable = ast.IsParagraph(lastBlock.Node)
5,955✔
904
        }
5,955✔
905
retry:
906
        var bps []BlockParser
15,024✔
907
        line, _ := reader.PeekLine()
15,024✔
908
        w, pos := util.IndentWidth(line, reader.LineOffset())
15,024✔
909
        if len(line) == 0 {
15,066✔
910
                pc.SetBlockOffset(-1)
42✔
911
                pc.SetBlockIndent(-1)
42✔
912
        } else {
15,024✔
913
                pc.SetBlockOffset(pos)
14,982✔
914
                pc.SetBlockIndent(w)
14,982✔
915
        }
14,982✔
916

917
        if line == nil || line[0] == '\n' {
17,526✔
918
                goto continuable
2,502✔
919
        }
920
        bps = p.freeBlockParsers
12,522✔
921
        if pos < len(line) {
25,044✔
922
                bps = p.blockParsers[line[pos]]
12,522✔
923
                if bps == nil {
18,036✔
924
                        bps = p.freeBlockParsers
5,514✔
925
                }
5,514✔
926
        }
927
        if bps == nil {
12,522✔
UNCOV
928
                goto continuable
×
929
        }
930

931
        for _, bp := range bps {
42,066✔
932
                if continuable && result == noBlocksOpened && !bp.CanInterruptParagraph() {
32,490✔
933
                        continue
2,946✔
934
                }
935

936
                if w > 3 && !bp.CanAcceptIndentedLine() {
26,811✔
937
                        continue
213✔
938
                }
939
                lastBlock = pc.LastOpenedBlock()
26,385✔
940
                last := lastBlock.Node
26,385✔
941
                _, blockPos := reader.Position()
26,385✔
942
                node, state := bp.Open(parent, reader, pc)
26,385✔
943
                if node != nil {
37,428✔
944
                        node.SetPos(blockPos.Start + max(pc.BlockOffset(), 0))
11,043✔
945

11,043✔
946
                        // Parser requires last node to be a paragraph.
11,043✔
947
                        // With table extension:
11,043✔
948
                        //
11,043✔
949
                        //     0
11,043✔
950
                        //     -:
11,043✔
951
                        //     -
11,043✔
952
                        //
11,043✔
953
                        // '-' on 3rd line seems a Setext heading because 1st and 2nd lines
11,043✔
954
                        // are being paragraph when the Settext heading parser tries to parse the 3rd
11,043✔
955
                        // line.
11,043✔
956
                        // But 1st line and 2nd line are a table. Thus this paragraph will be transformed
11,043✔
957
                        // by a paragraph transformer. So this text should be converted to a table and
11,043✔
958
                        // an empty list.
11,043✔
959
                        if state&RequireParagraph != 0 {
11,217✔
960
                                if last == parent.LastChild() {
342✔
961
                                        // Opened paragraph may be transformed by ParagraphTransformers in
168✔
962
                                        // closeBlocks().
168✔
963
                                        lastBlock.Parser.Close(last, reader, pc)
168✔
964
                                        blocks := pc.OpenedBlocks()
168✔
965
                                        pc.SetOpenedBlocks(blocks[0 : len(blocks)-1])
168✔
966
                                        if p.transformParagraph(last.(*ast.Paragraph), reader, pc) {
174✔
967
                                                // Paragraph has been transformed.
6✔
968
                                                // So this parser is considered as failing.
6✔
969
                                                continuable = false
6✔
970
                                                goto retry
6✔
971
                                        }
972
                                }
973
                        }
974
                        node.(ast.BlockNode).SetBlankPreviousLines(blankLine)
11,037✔
975
                        if last != nil && last.Parent() == nil {
11,037✔
UNCOV
976
                                lastPos := len(pc.OpenedBlocks()) - 1
×
UNCOV
977
                                p.closeBlocks(lastPos, lastPos, reader, pc)
×
UNCOV
978
                        }
×
979
                        parent.AppendChild(node)
11,037✔
980
                        result = newBlocksOpened
11,037✔
981
                        be := Block{node, bp}
11,037✔
982
                        pc.SetOpenedBlocks(append(pc.OpenedBlocks(), be))
11,037✔
983
                        if state&HasChildren != 0 {
13,824✔
984
                                parent = node
2,787✔
985
                                goto retry // try child block
2,787✔
986
                        }
987
                        break // no children, can not open more blocks on this line
8,250✔
988
                }
989
        }
990

991
continuable:
992
        if result == noBlocksOpened && continuable {
15,360✔
993
                state := lastBlock.Parser.Continue(lastBlock.Node, reader, pc)
3,129✔
994
                if state&Continue != 0 {
4,590✔
995
                        result = paragraphContinuation
1,461✔
996
                }
1,461✔
997
        }
998
        return result
12,231✔
999
}
1000

1001
type lineStat struct {
1002
        lineNum int
1003
        level   int
1004
        isBlank bool
1005
}
1006

1007
func isBlankLine(lineNum, level int, stats []lineStat) bool {
5,955✔
1008
        l := len(stats)
5,955✔
1009
        if l == 0 {
5,955✔
UNCOV
1010
                return true
×
UNCOV
1011
        }
×
1012
        for i := l - 1 - level; i >= 0; i-- {
15,138✔
1013
                s := stats[i]
9,183✔
1014
                if s.lineNum == lineNum && s.level <= level {
12,144✔
1015
                        return s.isBlank
2,961✔
1016
                } else if s.lineNum < lineNum {
9,183✔
UNCOV
1017
                        break
×
1018
                }
1019
        }
1020
        return false
2,994✔
1021
}
1022

1023
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
4,524✔
1024
        pc.SetOpenedBlocks(nil)
4,524✔
1025
        blankLines := make([]lineStat, 0, 128)
4,524✔
1026
        for { // process blocks separated by blank lines
10,959✔
1027
                _, _, ok := reader.SkipBlankLines()
6,435✔
1028
                if !ok {
6,594✔
1029
                        return
159✔
1030
                }
159✔
1031
                // first, we try to open blocks
1032
                if p.openBlocks(parent, true, reader, pc) != newBlocksOpened {
6,276✔
UNCOV
1033
                        return
×
UNCOV
1034
                }
×
1035
                reader.AdvanceLine()
6,276✔
1036
                blankLines = blankLines[0:0]
6,276✔
1037
                for { // process opened blocks line by line
21,072✔
1038
                        openedBlocks := pc.OpenedBlocks()
14,796✔
1039
                        l := len(openedBlocks)
14,796✔
1040
                        if l == 0 {
16,707✔
1041
                                break
1,911✔
1042
                        }
1043
                        lastIndex := l - 1
12,885✔
1044
                        for i := range l {
29,232✔
1045
                                be := openedBlocks[i]
16,347✔
1046
                                line, _ := reader.PeekLine()
16,347✔
1047
                                if line == nil {
20,712✔
1048
                                        p.closeBlocks(lastIndex, 0, reader, pc)
4,365✔
1049
                                        reader.AdvanceLine()
4,365✔
1050
                                        return
4,365✔
1051
                                }
4,365✔
1052
                                lineNum, _ := reader.Position()
11,982✔
1053
                                blankLines = append(blankLines, lineStat{lineNum, i, util.IsBlank(line)})
11,982✔
1054
                                // If node is a paragraph, p.openBlocks determines whether it is continuable.
11,982✔
1055
                                // So we do not process paragraphs here.
11,982✔
1056
                                if !ast.IsParagraph(be.Node) {
20,532✔
1057
                                        state := be.Parser.Continue(be.Node, reader, pc)
8,550✔
1058
                                        if state&Continue != 0 {
14,895✔
1059
                                                // When current node is a container block and has no children,
6,345✔
1060
                                                // we try to open new child nodes
6,345✔
1061
                                                if state&HasChildren != 0 && i == lastIndex {
6,663✔
1062
                                                        isBlank := isBlankLine(lineNum-1, i+1, blankLines)
318✔
1063
                                                        p.openBlocks(be.Node, isBlank, reader, pc)
318✔
1064
                                                        break
318✔
1065
                                                }
1066
                                                continue
6,027✔
1067
                                        }
1068
                                }
1069
                                // current node may be closed or lazy continuation
1070
                                isBlank := isBlankLine(lineNum-1, i, blankLines)
5,637✔
1071
                                thisParent := parent
5,637✔
1072
                                if i != 0 {
7,179✔
1073
                                        thisParent = openedBlocks[i-1].Node
1,542✔
1074
                                }
1,542✔
1075
                                lastNode := openedBlocks[lastIndex].Node
5,637✔
1076
                                result := p.openBlocks(thisParent, isBlank, reader, pc)
5,637✔
1077
                                if result != paragraphContinuation {
9,813✔
1078
                                        // lastNode is a paragraph and was transformed by the paragraph
4,176✔
1079
                                        // transformers.
4,176✔
1080
                                        if openedBlocks[lastIndex].Node != lastNode {
4,344✔
1081
                                                lastIndex--
168✔
1082
                                        }
168✔
1083
                                        p.closeBlocks(lastIndex, i, reader, pc)
4,176✔
1084
                                }
1085
                                break
5,637✔
1086
                        }
1087

1088
                        reader.AdvanceLine()
8,520✔
1089
                }
1090
        }
1091
}
1092

1093
func (p *parser) walkBlock(block ast.Node, cb func(node ast.Node)) {
15,897✔
1094
        for c := block.FirstChild(); c != nil; c = c.NextSibling() {
27,270✔
1095
                p.walkBlock(c, cb)
11,373✔
1096
        }
11,373✔
1097
        cb(block)
15,897✔
1098
}
1099

1100
const (
1101
        lineBreakHard uint8 = 1 << iota
1102
        lineBreakSoft
1103
        lineBreakVisible
1104
)
1105

1106
func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context) {
15,897✔
1107
        if len(parent.(ast.BlockNode).Source()) == 0 {
25,398✔
1108
                return
9,501✔
1109
        }
9,501✔
1110
        escaped := false
6,396✔
1111
        source := block.Source()
6,396✔
1112
        block.Reset(parent.(ast.BlockNode).Source())
6,396✔
1113
        for {
18,027✔
1114
        retry:
11,631✔
1115
                line, _ := block.PeekLine()
469,959✔
1116
                if len(line) == 0 {
476,355✔
1117
                        break
6,396✔
1118
                }
1119
                lineLength := len(line)
463,563✔
1120
                var lineBreakFlags uint8
463,563✔
1121
                hasNewLine := line[lineLength-1] == '\n'
463,563✔
1122
                if ((lineLength >= 3 && line[lineLength-2] == '\\' &&
463,563✔
1123
                        line[lineLength-3] != '\\') || (lineLength == 2 && line[lineLength-2] == '\\')) && hasNewLine { // ends with \\n
463,635✔
1124
                        lineLength -= 2
72✔
1125
                        lineBreakFlags |= lineBreakHard | lineBreakVisible
72✔
1126
                } else if ((lineLength >= 4 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' &&
463,563✔
1127
                        line[lineLength-4] != '\\') || (lineLength == 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r')) &&
463,491✔
1128
                        hasNewLine { // ends with \\r\n
463,494✔
1129
                        lineLength -= 3
3✔
1130
                        lineBreakFlags |= lineBreakHard | lineBreakVisible
3✔
1131
                } else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' &&
463,491✔
1132
                        hasNewLine { // ends with [space][space]\n
463,536✔
1133
                        lineLength -= 3
48✔
1134
                        lineBreakFlags |= lineBreakHard
48✔
1135
                } else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' &&
463,488✔
1136
                        line[lineLength-2] == '\r' && hasNewLine { // ends with [space][space]\r\n
463,443✔
1137
                        lineLength -= 4
3✔
1138
                        lineBreakFlags |= lineBreakHard
3✔
1139
                } else if hasNewLine {
465,366✔
1140
                        // If the line ends with a newline character, but it is not a hardlineBreak, then it is a softLinebreak
1,926✔
1141
                        // If the line ends with a hardlineBreak, then it cannot end with a softLinebreak
1,926✔
1142
                        // See https://spec.commonmark.org/0.30/#soft-line-breaks
1,926✔
1143
                        lineBreakFlags |= lineBreakSoft
1,926✔
1144
                }
1,926✔
1145

1146
                l, startPosition := block.Position()
463,563✔
1147
                n := 0
463,563✔
1148
                for i := range lineLength {
4,630,878✔
1149
                        c := line[i]
4,167,315✔
1150
                        if c == '\n' {
4,168,284✔
1151
                                break
969✔
1152
                        }
1153
                        isSpace := util.IsSpace(c) && c != '\r' && c != '\n'
4,166,346✔
1154
                        isPunct := util.IsPunct(c)
4,166,346✔
1155
                        if (isPunct && !escaped) || isSpace && (!escaped || !p.escapedSpace) || i == 0 {
7,203,795✔
1156
                                parserChar := c
3,037,449✔
1157
                                if isSpace || (i == 0 && !isPunct) {
4,108,737✔
1158
                                        parserChar = ' '
1,071,288✔
1159
                                }
1,071,288✔
1160
                                ips := p.inlineParsers[parserChar]
3,037,449✔
1161
                                if ips != nil {
4,550,529✔
1162
                                        block.Advance(n)
1,513,080✔
1163
                                        n = 0
1,513,080✔
1164
                                        savedLine, savedPosition := block.Position()
1,513,080✔
1165
                                        if i != 0 {
2,869,680✔
1166
                                                _, currentPosition := block.Position()
1,356,600✔
1167
                                                ast.MergeOrAppendTextSegment(parent, startPosition.Between(currentPosition))
1,356,600✔
1168
                                                _, startPosition = block.Position()
1,356,600✔
1169
                                        }
1,356,600✔
1170
                                        var inlineNode ast.Node
1,513,080✔
1171
                                        for _, ip := range ips {
3,631,944✔
1172
                                                inlineNode = ip.Parse(parent, block, pc)
2,118,864✔
1173
                                                if inlineNode != nil {
2,577,192✔
1174
                                                        if inlineNode.Pos() < 0 {
460,860✔
1175
                                                                inlineNode.(interface{ SetPos(int) }).SetPos(startPosition.Start)
2,532✔
1176
                                                        }
2,532✔
1177
                                                        break
458,328✔
1178
                                                }
1179
                                                block.SetPosition(savedLine, savedPosition)
1,660,536✔
1180
                                        }
1181
                                        if inlineNode != nil {
1,971,408✔
1182
                                                if inlineNode != Nil {
916,635✔
1183
                                                        parent.AppendChild(inlineNode)
458,307✔
1184
                                                }
458,307✔
1185
                                                goto retry
458,328✔
1186
                                        }
1187
                                }
1188
                        }
1189
                        if escaped {
3,708,546✔
1190
                                escaped = false
528✔
1191
                                n++
528✔
1192
                                continue
528✔
1193
                        }
1194

1195
                        if c == '\\' {
3,708,036✔
1196
                                escaped = true
546✔
1197
                                n++
546✔
1198
                                continue
546✔
1199
                        }
1200

1201
                        escaped = false
3,706,944✔
1202
                        n++
3,706,944✔
1203
                }
1204
                if n != 0 {
10,341✔
1205
                        block.Advance(n)
5,106✔
1206
                }
5,106✔
1207
                currentL, currentPosition := block.Position()
5,235✔
1208
                if l != currentL {
5,235✔
UNCOV
1209
                        continue
×
1210
                }
1211
                diff := startPosition.Between(currentPosition)
5,235✔
1212
                var text *ast.Text
5,235✔
1213
                if lineBreakFlags&(lineBreakHard|lineBreakVisible) == lineBreakHard|lineBreakVisible {
5,274✔
1214
                        text = ast.NewSegmentText(diff)
39✔
1215
                } else {
5,235✔
1216
                        text = ast.NewSegmentText(diff.TrimRightSpace(source))
5,196✔
1217
                }
5,196✔
1218
                text.SetSoftLineBreak(lineBreakFlags&lineBreakSoft != 0)
5,235✔
1219
                text.SetHardLineBreak(lineBreakFlags&lineBreakHard != 0)
5,235✔
1220
                parent.AppendChild(text)
5,235✔
1221
                block.AdvanceLine()
5,235✔
1222
        }
1223

1224
        ProcessDelimiters(nil, pc)
6,396✔
1225
        for _, ip := range p.closeBlockers {
12,792✔
1226
                ip.CloseBlock(parent, block, pc)
6,396✔
1227
        }
6,396✔
1228

1229
}
1230

1231
// Extension is an interface that represents an extension for the parser.
1232
type Extension interface {
1233
        // ParserOptions returns a list of parser options to be applied to the parser.
1234
        ParserOptions(*Config) []Option
1235
}
1236

1237
type commonMark struct {
1238
        opts []Option
1239
}
1240

1241
// NewCommonMark returns a new CommonMark extension.
UNCOV
1242
func NewCommonMark(opts ...Option) Extension {
×
UNCOV
1243
        return &commonMark{opts}
×
UNCOV
1244
}
×
1245

1246
func (e *commonMark) ParserOptions(cfg *Config) []Option {
2,118✔
1247
        if len(e.opts) != 0 {
2,118✔
1248
                thisConfig := *cfg
×
UNCOV
1249
                for _, opt := range e.opts {
×
UNCOV
1250
                        opt.SetParserOption(&thisConfig)
×
UNCOV
1251
                }
×
1252
                cfg = &thisConfig
×
1253
        }
1254

1255
        var hopts []HeadingOption
2,118✔
1256
        if cfg.Attribute {
4,083✔
1257
                hopts = append(hopts, WithAttribute())
1,965✔
1258
        }
1,965✔
1259
        if cfg.autoHeadingID {
4,086✔
1260
                hopts = append(hopts, WithAutoHeadingID())
1,968✔
1261
        }
1,968✔
1262
        return []Option{
2,118✔
1263
                WithBlockParsers(
2,118✔
1264
                        util.Prioritized(NewSetextHeadingParser(hopts...), 100),
2,118✔
1265
                        util.Prioritized(NewThematicBreakParser(), 200),
2,118✔
1266
                        util.Prioritized(NewListParser(), 300),
2,118✔
1267
                        util.Prioritized(NewListItemParser(), 400),
2,118✔
1268
                        util.Prioritized(NewCodeBlockParser(), 500),
2,118✔
1269
                        util.Prioritized(NewATXHeadingParser(hopts...), 600),
2,118✔
1270
                        util.Prioritized(NewFencedCodeBlockParser(), 700),
2,118✔
1271
                        util.Prioritized(NewBlockquoteParser(), 800),
2,118✔
1272
                        util.Prioritized(NewHTMLBlockParser(), 900),
2,118✔
1273
                        util.Prioritized(NewParagraphParser(), 1000),
2,118✔
1274
                ),
2,118✔
1275
                WithInlineParsers(
2,118✔
1276
                        util.Prioritized(NewCodeSpanParser(), 100),
2,118✔
1277
                        util.Prioritized(NewLinkParser(), 200),
2,118✔
1278
                        util.Prioritized(NewAutoLinkParser(), 300),
2,118✔
1279
                        util.Prioritized(NewRawHTMLParser(), 400),
2,118✔
1280
                        util.Prioritized(NewEmphasisParser(), 500),
2,118✔
1281
                ),
2,118✔
1282
                WithParagraphTransformers(
2,118✔
1283
                        util.Prioritized(LinkReferenceParagraphTransformer, 100),
2,118✔
1284
                ),
2,118✔
1285
        }
2,118✔
1286
}
1287

1288
// CommonMark is a commonmark compliant extension.
1289
// This extension adds default block parsers, inline parsers, and paragraph transformers to the parser.
1290
//
1291
// Block parsers:
1292
//
1293
//   - SetextHeadingParser, 100
1294
//   - ThematicBreakParser, 200
1295
//   - ListParser, 300
1296
//   - ListItemParser, 400
1297
//   - CodeBlockParser, 500
1298
//   - ATXHeadingParser, 600
1299
//   - FencedCodeBlockParser, 700
1300
//   - BlockquoteParser, 800
1301
//   - HTMLBlockParser, 900
1302
//   - ParagraphParser, 1000
1303
//
1304
// Inline parsers:
1305
//
1306
//   - CodeSpanParser, 100
1307
//   - LinkParser, 200
1308
//   - AutoLinkParser, 300
1309
//   - RawHTMLParser, 400
1310
//   - EmphasisParser, 500
1311
//
1312
// Paragraph transformers:
1313
//
1314
//   - LinkReferenceParagraphTransformer, 100
1315
var CommonMark = &commonMark{}
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