@@ -180,7 +180,7 @@ func (c *qdocConv) meta(kind, value string) {
180180 if value == "" {
181181 return
182182 }
183- c .html .WriteString (`<data class="` + kind + `">` + value + "</data>\n " )
183+ c .html .WriteString (`<data class="` + kind + `">` + qdocEsc ( value ) + "</data>\n " )
184184}
185185
186186// qdocOpenDiv writes a `<div>` carrying `arg`'s class, if it names one.
@@ -200,6 +200,17 @@ var qdocInlineNames = map[string]struct{}{
200200 "sub" : {}, "sup" : {}, "tt" : {}, "uicontrol" : {}, "underline" : {},
201201}
202202
203+ // qdocEsc makes text safe to write into the HTML the walker reads.
204+ //
205+ // QDoc prose is kept word-for-word, and Qt's prose talks about markup: a
206+ // sentence explaining that a file "must be included using a <script> tag"
207+ // carries a real tag. Written straight through, it opens an element in the
208+ // converted document -- and `<script>` in particular puts the tokenizer into
209+ // raw-text mode, so the rest of the page is swallowed looking for a close
210+ // that never comes. The tokenizer decodes these again, so the text a rule
211+ // sees, and the text located in the source, are unchanged.
212+ var qdocEsc = strings .NewReplacer ("&" , "&" , "<" , "<" , ">" , ">" ).Replace
213+
203214// qdocArg strips the braces from a command argument.
204215func qdocArg (arg string ) string {
205216 if strings .HasPrefix (arg , "{" ) && strings .HasSuffix (arg , "}" ) {
@@ -217,10 +228,10 @@ func qdocInline(text string) string {
217228 for {
218229 loc := qdocInlineCmd .FindStringSubmatchIndex (text )
219230 if loc == nil {
220- out .WriteString (text )
231+ out .WriteString (qdocEsc ( text ) )
221232 break
222233 }
223- out .WriteString (text [:loc [0 ]])
234+ out .WriteString (qdocEsc ( text [:loc [0 ]]) )
224235
225236 if loc [2 ] < 0 {
226237 // `\\` is a literal backslash, not a command.
@@ -238,15 +249,15 @@ func qdocInline(text string) string {
238249
239250 switch name {
240251 case "c" , "tt" , "a" :
241- out .WriteString ("<code>" + qdocArg (arg ) + "</code>" )
252+ out .WriteString ("<code>" + qdocEsc ( qdocArg (arg ) ) + "</code>" )
242253 case "b" , "bold" , "uicontrol" :
243- out .WriteString ("<strong>" + qdocArg (arg ) + "</strong>" )
254+ out .WriteString ("<strong>" + qdocEsc ( qdocArg (arg ) ) + "</strong>" )
244255 case "e" , "i" :
245- out .WriteString ("<em>" + qdocArg (arg ) + "</em>" )
256+ out .WriteString ("<em>" + qdocEsc ( qdocArg (arg ) ) + "</em>" )
246257 case "underline" :
247- out .WriteString ("<u>" + qdocArg (arg ) + "</u>" )
258+ out .WriteString ("<u>" + qdocEsc ( qdocArg (arg ) ) + "</u>" )
248259 case "sub" , "sup" :
249- out .WriteString ("<" + name + ">" + qdocArg (arg ) + "</" + name + ">" )
260+ out .WriteString ("<" + name + ">" + qdocEsc ( qdocArg (arg ) ) + "</" + name + ">" )
250261 case "l" :
251262 label := qdocArg (arg )
252263 if strings .HasPrefix (arg , "{" ) {
@@ -255,7 +266,7 @@ func qdocInline(text string) string {
255266 rest = rest [len (m ):]
256267 }
257268 }
258- out .WriteString (`<a href="#">` + label + "</a>" )
269+ out .WriteString (`<a href="#">` + qdocEsc ( label ) + "</a>" )
259270 case "span" :
260271 // `\span {class="x"} {text}`: the attribute names a class a rule
261272 // can be scoped to, the braced text that follows is prose.
@@ -265,9 +276,9 @@ func qdocInline(text string) string {
265276 rest = rest [len (m ):]
266277 }
267278 if class := qdocClass (arg ); class != "" {
268- out .WriteString (`<span class="` + class + `">` + label + "</span>" )
279+ out .WriteString (`<span class="` + class + `">` + qdocEsc ( label ) + "</span>" )
269280 } else {
270- out .WriteString ("<span>" + label + "</span>" )
281+ out .WriteString ("<span>" + qdocEsc ( label ) + "</span>" )
271282 }
272283 case "image" , "inlineimage" :
273284 // The argument is a file name; a caption, if any, follows as
@@ -301,6 +312,11 @@ type qdocConv struct {
301312 stack []qdocContext
302313 verbatim string // the \end command that closes the open verbatim block
303314 omitted bool
315+
316+ // inComment says whether the reader is inside a `/*! ... */` block, the
317+ // only place QDoc looks. A comment body extracted from a source file
318+ // starts inside one; a whole file starts outside.
319+ inComment bool
304320}
305321
306322func (c * qdocConv ) flush () {
@@ -394,20 +410,46 @@ func (c *qdocConv) endsVerbatim(name string) bool {
394410 return name == qdocVerbatim [c .verbatim ] || name == "end" + c .verbatim
395411}
396412
397- func (c * qdocConv ) line (raw string ) { //nolint:gocyclo // one case per command family
413+ // opensComment reports whether the line starts a `/*!` documentation comment.
414+ func qdocOpensComment (raw string ) bool {
415+ i := strings .Index (raw , "/*!" )
416+ return i >= 0 && strings .TrimSpace (raw [:i ]) == ""
417+ }
418+
419+ // line reads one line of a QDoc source, tracking which of them the reader is
420+ // meant to see at all.
421+ //
422+ // QDoc documents a project from its `/*! ... */` comments and nothing else.
423+ // A `.qdoc` file is a file of those comments, and what sits between them --
424+ // a licence header, a `//! [name]` snippet whose body is shell or C++ -- is
425+ // no more prose than the code in a `.cpp` file is.
426+ func (c * qdocConv ) line (raw string ) {
427+ closes := strings .HasSuffix (strings .TrimSpace (raw ), "*/" )
428+
398429 // A verbatim or omitted block cannot outlive the comment holding it. An
399430 // unterminated one -- a `\code` whose `\endcode` was never written, or a
400431 // spelling this converter does not know -- would otherwise swallow the
401432 // rest of the file.
402- if strings . HasSuffix ( strings . TrimSpace ( raw ), "*/" ) {
433+ if closes {
403434 if c .verbatim != "" {
404435 c .html .WriteString ("</code></pre>\n " )
405436 c .verbatim = ""
406437 }
407438 c .omitted = false
408439 }
409440
410- raw = qdocBlankDelims (raw )
441+ if c .inComment || qdocOpensComment (raw ) {
442+ c .inComment = true
443+ c .content (qdocBlankDelims (raw ))
444+ }
445+
446+ if closes {
447+ c .inComment = false
448+ c .closeDiv ()
449+ }
450+ }
451+
452+ func (c * qdocConv ) content (raw string ) { //nolint:gocyclo // one case per command family
411453 trimmed := strings .TrimSpace (raw )
412454
413455 if c .verbatim != "" {
@@ -516,7 +558,7 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
516558 c .flush ()
517559 fields := strings .Fields (rest )
518560 if len (fields ) > 1 {
519- c .html .WriteString ("<p><code>" + fields [0 ] + "</code> " +
561+ c .html .WriteString ("<p><code>" + qdocEsc ( fields [0 ]) + "</code> " +
520562 qdocInline (strings .Join (fields [1 :], " " )) + "</p>\n " )
521563 }
522564 case name == "quotation" :
@@ -570,8 +612,16 @@ func (c *qdocConv) line(raw string) { //nolint:gocyclo // one case per command f
570612 }
571613}
572614
573- func qdocToHTML (content string ) string {
574- conv := & qdocConv {}
615+ // qdocToHTML converts a whole QDoc source: the `/*! ... */` comments in it
616+ // are the document, and everything else is code.
617+ func qdocToHTML (content string ) string { return qdocConvert (content , false ) }
618+
619+ // qdocFragmentToHTML converts the body of a single comment, already extracted
620+ // from a source file, so there is no `/*!` left to wait for.
621+ func qdocFragmentToHTML (content string ) string { return qdocConvert (content , true ) }
622+
623+ func qdocConvert (content string , inComment bool ) string {
624+ conv := & qdocConv {inComment : inComment }
575625 for _ , line := range strings .Split (content , "\n " ) {
576626 conv .line (line )
577627 }
@@ -580,8 +630,17 @@ func qdocToHTML(content string) string {
580630 return conv .html .String ()
581631}
582632
583- // lintQDoc lints QDoc: Qt's documentation markup.
633+ // lintQDoc lints a QDoc source : Qt's documentation markup.
584634func (l * Linter ) lintQDoc (f * core.File ) error {
635+ return l .lintQDocWith (f , qdocToHTML )
636+ }
637+
638+ // lintQDocFragment lints one QDoc comment lifted out of a code file.
639+ func (l * Linter ) lintQDocFragment (f * core.File ) error {
640+ return l .lintQDocWith (f , qdocFragmentToHTML )
641+ }
642+
643+ func (l * Linter ) lintQDocWith (f * core.File , convert func (string ) string ) error {
585644 err := l .lintMetadata (f )
586645 if err != nil {
587646 return err
@@ -592,5 +651,5 @@ func (l *Linter) lintQDoc(f *core.File) error {
592651 return err
593652 }
594653
595- return l .lintHTMLTokens (f , []byte (qdocToHTML (s )), 0 )
654+ return l .lintHTMLTokens (f , []byte (convert (s )), 0 )
596655}
0 commit comments