Skip to content

Commit c2d1a55

Browse files
committed
feat: add a meta scope for a document's machine-readable content
Signed-off-by: Joseph Kato <joseph@jdkato.io>
1 parent 33e4012 commit c2d1a55

4 files changed

Lines changed: 134 additions & 5 deletions

File tree

‎internal/lint/ast.go‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,22 @@ func (l *Linter) lintHTMLTokens(f *core.File, raw []byte, offset int) error { //
266266
}
267267

268268
func (l *Linter) lintScope(f *core.File, state *walker, txt string) error {
269+
// A `data` element holds a document's machine-readable content -- an
270+
// anchor name, a file name, an identifier -- which is in the document
271+
// without being prose.
272+
//
273+
// Like `link` and `code`, `meta` is a sibling of `text` rather than a
274+
// child of it, so an ordinary rule passes over it and only a rule that
275+
// asks for `meta` does any work. That is the whole point: the name is
276+
// reachable for a rule about naming, and invisible to a style about
277+
// writing. It is not segmented either -- an identifier has no sentences.
278+
if core.StringInSlice("data", state.tagHistory) {
279+
f.Metrics["meta"]++
280+
281+
b := state.block(txt, withClasses("meta", state)+f.MetaScope+f.RealExt, 0)
282+
return l.lintBlock(f, b, state.lines, 0, false)
283+
}
284+
269285
for _, tag := range state.tagHistory {
270286
scope, match := tagToScope[tag]
271287
if (match && !core.StringInSlice(tag, inlineTags)) || heading.MatchString(tag) {

‎internal/lint/qdoc.go‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ var qdocSkipLine = map[string]struct{}{
7070
"inqmlmodule": {},
7171
"instantiates": {},
7272
"internal": {},
73-
"keyword": {},
7473
"macro": {},
7574
"meta": {},
7675
"module": {},
@@ -124,7 +123,6 @@ var qdocSkipLine = map[string]struct{}{
124123
"snippet": {},
125124
"startpage": {},
126125
"tableofcontents": {},
127-
"target": {},
128126
"threadsafe": {},
129127
"toc": {},
130128
"tocentry": {},
@@ -175,6 +173,16 @@ func qdocClass(arg string) string {
175173
return arg
176174
}
177175

176+
// meta writes one piece of the document's machine-readable content -- an
177+
// anchor name, an image's file name -- under the `meta` scope, where a rule
178+
// about naming can reach it and a style about prose cannot.
179+
func (c *qdocConv) meta(kind, value string) {
180+
if value == "" {
181+
return
182+
}
183+
c.html.WriteString(`<data class="` + kind + `">` + value + "</data>\n")
184+
}
185+
178186
// qdocOpenDiv writes a `<div>` carrying `arg`'s class, if it names one.
179187
func (c *qdocConv) openDiv(arg string) {
180188
c.flush()
@@ -531,10 +539,18 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
531539
case name == "caption":
532540
c.flush()
533541
c.html.WriteString("<figcaption>" + qdocInline(rest) + "</figcaption>\n")
542+
case name == "target", name == "keyword":
543+
// The anchor a link points at: a name, reachable under `meta` for a
544+
// rule about naming and invisible to one about prose.
545+
c.flush()
546+
c.meta("anchor", qdocArg(rest))
534547
case name == "image", name == "inlineimage":
535548
// `\image file.png An optional caption of prose.`
536549
c.flush()
537550
fields := strings.Fields(rest)
551+
if len(fields) > 0 {
552+
c.meta("image", fields[0])
553+
}
538554
if len(fields) > 1 {
539555
c.html.WriteString("<figcaption>" +
540556
qdocInline(strings.Join(fields[1:], " ")) + "</figcaption>\n")

‎internal/lint/qdoc_test.go‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,18 @@ func TestQdocHTML(t *testing.T) {
9797
[]string{"\\value"},
9898
},
9999
{
100-
"an image's caption is prose, its file markup",
100+
"an image's caption is prose, its file name metadata",
101101
"\\image diagram.png A caption sentence.\n",
102-
[]string{"<figcaption>A caption sentence.</figcaption>"},
103-
[]string{"diagram.png"},
102+
[]string{`<data class="image">diagram.png</data>`,
103+
"<figcaption>A caption sentence.</figcaption>"},
104+
nil,
105+
},
106+
{
107+
"an anchor's name is metadata",
108+
"\\target some-anchor\n\\keyword {Another Anchor}\n\nProse here.\n",
109+
[]string{`<data class="anchor">some-anchor</data>`,
110+
`<data class="anchor">Another Anchor</data>`, "<p>Prose here.</p>"},
111+
[]string{"\\target", "\\keyword"},
104112
},
105113
{
106114
"sa lines are markup",

‎testdata/e2e/lint.yaml‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,95 @@ cases:
426426
api.qdocinc:3:7:T.Sidebar:'ZQX' in the sidebar
427427
api.qdocinc:8:35:T.Header:'ZQX' in a header span
428428
429+
- name: html-data
430+
about: "`data` holds machine-readable content in any markup format, not
431+
only QDoc: an ordinary rule passes over it"
432+
files:
433+
.vale.ini: |
434+
StylesPath = styles
435+
MinAlertLevel = suggestion
436+
437+
[*]
438+
T.Prose = YES
439+
T.Meta = YES
440+
styles/T/Prose.yml: |
441+
extends: existence
442+
message: "'%s' as prose"
443+
level: error
444+
nonword: true
445+
raw:
446+
- ZQX
447+
styles/T/Meta.yml: |
448+
extends: existence
449+
message: "'%s' as metadata"
450+
scope: meta
451+
level: error
452+
nonword: true
453+
raw:
454+
- ZQX
455+
page.html: |
456+
<p>A ZQX in a paragraph.</p>
457+
<data value="1">ZQX-identifier</data>
458+
args: page.html
459+
exit: 1
460+
want: |
461+
page.html:1:6:T.Prose:'ZQX' as prose
462+
page.html:2:17:T.Meta:'ZQX' as metadata
463+
464+
- name: qdoc-meta
465+
about: "anchor names and image file names are a document's machine-readable
466+
content: an ordinary rule passes over them, and only a rule scoped to
467+
`meta` reaches one"
468+
files:
469+
.vale.ini: |
470+
StylesPath = styles
471+
MinAlertLevel = suggestion
472+
473+
[*]
474+
T.Prose = YES
475+
T.Anchor = YES
476+
T.Image = YES
477+
styles/T/Prose.yml: |
478+
extends: existence
479+
message: "'%s' as prose"
480+
level: error
481+
nonword: true
482+
raw:
483+
- ZQX
484+
styles/T/Anchor.yml: |
485+
extends: existence
486+
message: "'%s' in an anchor"
487+
scope: meta.class.anchor
488+
level: error
489+
nonword: true
490+
raw:
491+
- ZQX
492+
styles/T/Image.yml: |
493+
extends: existence
494+
message: "'%s' in a file name"
495+
scope: meta.class.image
496+
level: error
497+
nonword: true
498+
raw:
499+
- ZQX
500+
anchors.qdoc: |
501+
/*!
502+
\target ZQX-anchor
503+
\keyword {ZQX Keyword}
504+
505+
A ZQX in ordinary prose.
506+
507+
\image ZQX-diagram.png A caption with a ZQX in it.
508+
*/
509+
args: anchors.qdoc
510+
exit: 1
511+
want: |
512+
anchors.qdoc:2:13:T.Anchor:'ZQX' in an anchor
513+
anchors.qdoc:3:15:T.Anchor:'ZQX' in an anchor
514+
anchors.qdoc:5:7:T.Prose:'ZQX' as prose
515+
anchors.qdoc:7:12:T.Image:'ZQX' in a file name
516+
anchors.qdoc:7:45:T.Prose:'ZQX' as prose
517+
429518
- name: qdoc-fragments
430519
about: doc comments in C++ sources lint as QDoc through `[formats]`, with
431520
alerts mapped back to the source; only `/*!` is documentation, while

0 commit comments

Comments
 (0)