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

yuin / goldmark / 30431627644

29 Jul 2026 07:24AM UTC coverage: 82.966% (-0.03%) from 82.991%
30431627644

push

github

yuin
feat: `goldmark_v1_attribute` build tag

154 of 236 new or added lines in 13 files covered. (65.25%)

17 existing lines in 3 files now uncovered.

6400 of 7714 relevant lines covered (82.97%)

348703.75 hits per line

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

82.91
/parser/attribute.go
1
//go:build !goldmark_v1_attribute
2

3
package parser
4

5
import (
6
        "bytes"
7
        "unsafe"
8

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

14
// ParseAttributes parses attributes into a slice of ast.Attribute.
15
// ParseAttributes returns a parsed attributes and true if could parse
16
// attributes, otherwise nil and false.
17
func ParseAttributes(reader text.Reader) ([]gast.Attribute, bool) {
60✔
18
        savedLine, savedPosition := reader.Position()
60✔
19
        reader.SkipSpaces()
60✔
20
        if reader.Peek() != '{' {
60✔
21
                reader.SetPosition(savedLine, savedPosition)
×
22
                return nil, false
×
23
        }
×
24
        reader.Advance(1)
60✔
25
        var attrs []gast.Attribute
60✔
26
        for {
240✔
27
                if reader.Peek() == '}' {
240✔
28
                        reader.Advance(1)
60✔
29
                        return attrs, true
60✔
30
                }
60✔
31
                attr, ok := parseAttribute(reader)
120✔
32
                if !ok {
120✔
UNCOV
33
                        reader.SetPosition(savedLine, savedPosition)
×
UNCOV
34
                        return nil, false
×
UNCOV
35
                }
×
36
                if attr.Name == "class" {
159✔
37
                        updated := false
39✔
38
                        for i, a := range attrs {
90✔
39
                                if a.Name == "class" {
63✔
40
                                        existing := a.Value.Str(reader.Source())
12✔
41
                                        newVal := attr.Value.Str(reader.Source())
12✔
42
                                        attrs[i].Value = text.NewStringMultilineValue(existing + " " + newVal)
12✔
43
                                        updated = true
12✔
44
                                        break
12✔
45
                                }
46
                        }
47
                        if !updated {
66✔
48
                                attrs = append(attrs, attr)
27✔
49
                        }
27✔
50
                } else {
81✔
51
                        attrs = append(attrs, attr)
81✔
52
                }
81✔
53
                reader.SkipSpaces()
120✔
54
        }
55
}
56

57
func parseAttribute(reader text.Reader) (gast.Attribute, bool) {
120✔
58
        reader.SkipSpaces()
120✔
59
        c := reader.Peek()
120✔
60
        if c == '#' || c == '.' {
186✔
61
                reader.Advance(1)
66✔
62
                line, seg := reader.PeekLine()
66✔
63
                i := 0
66✔
64
                for i < len(line) && !util.IsSpace(line[i]) &&
66✔
65
                        (!util.IsPunct(line[i]) || line[i] == '_' ||
66✔
66
                                line[i] == '-' || line[i] == ':' || line[i] == '.') {
426✔
67
                        i++
360✔
68
                }
360✔
69
                if i == 0 {
66✔
NEW
70
                        return gast.Attribute{}, false
×
NEW
71
                }
×
72
                name := "class"
66✔
73
                if c == '#' {
102✔
74
                        name = "id"
36✔
75
                }
36✔
76
                reader.Advance(i)
66✔
77
                return gast.Attribute{
66✔
78
                        Name:  name,
66✔
79
                        Value: text.NewIndexMultilineValue(text.NewIndex(seg.Start, seg.Start+i)),
66✔
80
                }, true
66✔
81
        }
82
        line, seg := reader.PeekLine()
54✔
83
        if len(line) == 0 {
54✔
84
                return gast.Attribute{}, false
×
85
        }
×
86
        c = line[0]
54✔
87
        if util.IsSpace(c) || c == '=' || c == '/' || c == '}' {
54✔
UNCOV
88
                return gast.Attribute{}, false
×
89
        }
×
90
        i := 0
54✔
91
        for ; i < len(line); i++ {
504✔
92
                c = line[i]
450✔
93
                if util.IsSpace(c) || c == '=' || c == '/' || c == '}' {
504✔
94
                        break
54✔
95
                }
96
        }
97
        name := line[:i]
54✔
98
        reader.Advance(i)
54✔
99
        reader.SkipSpaces()
54✔
100
        c = reader.Peek()
54✔
101
        if c != '=' {
54✔
NEW
102
                return gast.Attribute{
×
NEW
103
                        Name:  util.BytesToReadOnlyString(name),
×
NEW
104
                        Value: text.NewIndexMultilineValue(text.NewIndex(seg.Start, seg.Start+i)),
×
NEW
105
                }, true
×
UNCOV
106
        }
×
107
        reader.Advance(1)
54✔
108
        reader.SkipSpaces()
54✔
109
        value, ok := parseAttributeValue(reader)
54✔
110
        if !ok {
54✔
UNCOV
111
                return gast.Attribute{}, false
×
UNCOV
112
        }
×
113
        return gast.Attribute{
54✔
114
                Name:  util.BytesToReadOnlyString(name),
54✔
115
                Value: value,
54✔
116
        }, true
54✔
117
}
118

119
func parseAttributeValue(reader text.Reader) (text.MultilineValue, bool) {
54✔
120
        reader.SkipSpaces()
54✔
121
        c := reader.Peek()
54✔
122
        switch c {
54✔
123
        case text.EOF:
×
124
                return text.MultilineValue{}, false
×
125
        case '"':
18✔
126
                return parseAttributeQuoted(reader, '"')
18✔
NEW
127
        case '\'':
×
NEW
128
                return parseAttributeQuoted(reader, '\'')
×
129
        default:
36✔
130
                return parseAttributeUnquoted(reader)
36✔
131
        }
132
}
133

134
func parseAttributeQuoted(reader text.Reader, q byte) (text.MultilineValue, bool) {
18✔
135
        reader.Advance(1) // skip "/'
18✔
136
        var lines []text.Segment
18✔
137
        var buf bytes.Buffer
18✔
138
        owned := false
18✔
139
        for {
39✔
140
                line, seg := reader.PeekLine()
21✔
141
                if len(line) == 0 {
21✔
NEW
142
                        break
×
143
                }
144

145
                i := 0
21✔
146
                offset := 0
21✔
147
                for ; i < len(line); i++ {
252✔
148
                        c := line[i]
231✔
149
                        if c == q {
249✔
150
                                offset = 1
18✔
151
                                break
18✔
152
                        }
153
                }
154

155
                reader.Advance(i + offset)
21✔
156
                v, resolved := resolveEntityReferences(line[:i])
21✔
157
                if resolved && !owned {
27✔
158
                        owned = true
6✔
159
                        for _, l := range lines {
6✔
NEW
160
                                buf.Write(l.Bytes(reader.Source()))
×
UNCOV
161
                        }
×
162
                }
163
                if owned {
27✔
164
                        buf.Write(v)
6✔
165
                        if offset == 1 {
12✔
166
                                return text.NewStringMultilineValue(buf.String()), true
6✔
167
                        }
6✔
168
                } else {
15✔
169
                        lines = append(lines, seg.WithStop(seg.Start+i))
15✔
170
                        if offset == 1 {
27✔
171
                                return text.NewMultilineValueFromSegments(lines), true
12✔
172
                        }
12✔
173
                }
174
        }
UNCOV
175
        return text.MultilineValue{}, false
×
176
}
177

178
func parseAttributeUnquoted(reader text.Reader) (text.MultilineValue, bool) {
36✔
179
        line, seg := reader.PeekLine()
36✔
180
        i := 0
36✔
181
        for ; i < len(line); i++ {
321✔
182
                c := line[i]
285✔
183
                if util.IsSpace(c) || c == '}' {
321✔
184
                        break
36✔
185
                }
186
                // ", \, >, <, =, `, and ' are not allowed in unquoted HTML attribute values.
187
                // But most of implementations ignore this rule, so we also ignore it.
188
        }
189
        reader.Advance(i)
36✔
190
        v := line[:i]
36✔
191
        v, resolved := resolveEntityReferences(v)
36✔
192
        if resolved {
39✔
193
                return text.NewStringMultilineValue(util.BytesToReadOnlyString(v)), true
3✔
194
        }
3✔
195
        return text.NewIndexMultilineValue(text.NewIndex(seg.Start, seg.Start+i)), true
33✔
196
}
197

198
func resolveEntityReferences(v []byte) ([]byte, bool) {
57✔
199
        addr := uintptr(unsafe.Pointer(&v[0]))
57✔
200
        v = util.ResolveNumericReferences(v)
57✔
201
        v = util.ResolveEntityNames(v)
57✔
202
        return v, addr != uintptr(unsafe.Pointer(&v[0]))
57✔
203
}
57✔
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