highlight: rename enter_visit and htmlize (and fix spell of hightlight)
[nit.git] / src / highlight.nit
index ecc4c7d..da18cce 100644 (file)
@@ -19,13 +19,113 @@ import frontend
 import html
 import pipeline
 import astutil
+import serialization
+
+# Fully process `content` as a Nit source file.
+#
+# Set `print_errors = true` to print errors in the code to the console.
+fun hightlightcode(hl: HighlightVisitor, content: String, print_errors: nullable Bool): HLCode
+do
+       # Prepare a stand-alone tool context
+       var tc = new ToolContext
+       tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
+       tc.keep_going = true # no exit, obviously
+       if print_errors != true then tc.opt_warn.value = -1 # no output
+
+       # Prepare an stand-alone model and model builder.
+       # Unfortunately, models are enclosing and append-only.
+       # There is no way (yet?) to have a shared module `core` with
+       # isolated and throwable user modules.
+       var model = new Model
+       var mb = new ModelBuilder(model, tc)
+
+       # Parse the code
+       var source = new SourceFile.from_string("", content + "\n")
+       var lexer = new Lexer(source)
+       var parser = new Parser(lexer)
+       var tree = parser.parse
+
+       var hlcode = new HLCode(hl, content, source)
+
+       # Check syntax error
+       var eof = tree.n_eof
+       if eof isa AError then
+               mb.error(eof, eof.message)
+               hl.hightlight_source(source)
+               return hlcode
+       end
+       var amodule = tree.n_base.as(not null)
+
+       # Load the AST as a module in the model
+       # Then process it
+       mb.load_rt_module(null, amodule, "")
+       mb.run_phases
+
+       # Highlight the processed module
+       hl.enter_visit(amodule)
+       return hlcode
+end
+
+# A standalone highlighted piece of code
+class HLCode
+       super Serializable
+
+       # The highlighter used
+       var hl: HighlightVisitor
+
+       # The raw code source
+       var content: String
+
+       # The pseudo source-file
+       var source: SourceFile
+
+       # JavaScript code to update an existing codemirror editor.
+       fun code_mirror_update: Template
+       do
+
+               var res = new Template
+               res.add """
+       function nitmessage() {
+               editor.operation(function(){
+                       for (var i = 0; i < widgets.length; ++i)
+                             editor.removeLineWidget(widgets[i]);
+                       widgets.length = 0;
+"""
+
+               for m in source.messages do
+                       res.add """
+                       var l = document.createElement("div");
+                       l.className = "lint-error"
+                       l.innerHTML = "<span class='glyphicon glyphicon-warning-sign lint-error-icon'></span> {{{m.text.html_escape}}}";
+                       var w = editor.addLineWidget({{{m.location.line_start-1}}}, l);
+                       widgets.push(w);
+"""
+               end
+               res.add """});}"""
+               return res
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("code", hl.html.write_to_string)
+               var msgs = new Array[Map[String, Serializable]]
+               for m in source.messages do
+                       var o = new Map[String, Serializable]
+                       msgs.add o
+                       o["line"] = m.location.line_start-1
+                       o["message"] = m.text
+               end
+               v.serialize_attribute("messages", msgs)
+       end
+end
 
 # Visitor used to produce a HTML tree based on a AST on a `Source`
 class HighlightVisitor
        # The root of the HTML hierarchy
        var html = new HTMLTag("span")
 
-       # Is the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
+       # Should the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
+       #
        # Used to have a really huge and verbose HTML (mainly for debug)
        var with_ast = false is writable
 
@@ -43,28 +143,148 @@ class HighlightVisitor
        # The last line to generate, null if finish at the last line
        var last_line: nullable Int = null is writable
 
+       # When highlighting a node, show its messages (errors, warnings), if any.
+       #
+       # default: true
+       var show_messages = true is writable
+
+       # When highlighting a node, attach a full popupable infobox, if any.
+       #
+       # If `false`, only a simple `title` tooltip is used.
+       #
+       # default: true
+       var show_infobox = true is writable
+
+       # A reference to an entity used in generated `<a>` elements.
+       #
+       # It is used to refer to some specific entities when generating links.
+       # If `null` is returned, then no link are generated and `<a>` elements become `<span>`.
+       #
+       # By default, `null` is returned.
+       # Clients are therefore encouraged to redefine the method in a subclass to control where entities should link to.
+       fun hrefto(entity: MEntity): nullable String do return null
+
        init
        do
                html.add_class("nitcode")
        end
 
-       # The entry-point of the highlighting.
-       # Will fill `html` with the generated HTML content.
-       fun enter_visit(n: ANode)
+       # When highlighting a node, also consider the loose tokens around it.
+       #
+       # Loose tokens are tokens discarded from the AST but attached before
+       # or after some non-loose tokens. See `Token::is_loose`.
+       #
+       # When this flag is set to `true`, the loose tokens that are before the
+       # first token and after the last token are also highlighted.
+       #
+       # Default: false.
+       var include_loose_tokens = false is writable
+
+       # When highlighting a node, the first and the last lines are fully included.
+       #
+       # If the highlighted node starts (or ends) in the middle of a line,
+       # this flags forces the whole line to be highlighted.
+       #
+       # Default: false
+       var include_whole_lines = false is writable
+
+       # Highlight a AST element.
+       fun highlight_node(n: ANode)
        do
                n.parentize_tokens
-               var s = n.location.file
-               htmlize(s.first_token.as(not null), s.last_token.as(not null))
+
+               var f
+               var l
+
+               if n isa Token then
+                       f = n
+                       l = n
+               else
+                       assert n isa Prod
+                       f = n.first_token
+                       if f == null then return
+                       l = n.last_token
+                       if l == null then return
+               end
+
+               if include_loose_tokens then
+                       if f.prev_looses.not_empty then f = f.prev_looses.first
+                       if l.next_looses.not_empty then l = l.next_looses.last
+               end
+
+               var line = first_line
+               if line != null then
+                       while f.location.line_start < line do
+                               f = f.next_token
+                               if f == null then return
+                       end
+               end
+
+               line = last_line
+               if line != null then
+                       while l.location.line_end > line do
+                               l = l.prev_token
+                               if l == null then return
+                       end
+               end
+
+               if include_whole_lines then
+                       f = f.first_real_token_in_line
+                       l = l.last_real_token_in_line
+               end
+
+               do_highlight(f, l)
        end
 
-       # Produce HTML between two tokens
-       protected fun htmlize(first_token, last_token: Token)
+       private fun full_tag(anode: ANode, hv: HighlightVisitor): nullable HTMLTag
+       do
+               var tag = anode.make_tag(hv)
+               if tag == null then return null
+               var infobox = anode.infobox(hv)
+               if infobox == null and anode isa Token then
+                       var pa = anode.parent
+                       if pa != null then
+                               infobox = pa.decorate_tag(hv, tag, anode)
+                       end
+               end
+               if infobox != null and not show_infobox then
+                       tag.attr("title", infobox.title)
+                       tag.classes.add "titled"
+                       infobox = null
+               end
+               var messages = anode.location.messages
+               if messages != null and show_messages then
+                       tag.css("border-bottom", "solid 2px red")
+                       if infobox == null then
+                               infobox = new HInfoBox(hv, "Messages")
+                       end
+                       var c = infobox.new_dropdown("{messages.length} message(s)", "")
+                       for m in messages do
+                               c.open("li").append(m.text)
+                       end
+               end
+               if infobox != null then
+                       tag.attach_infobox(infobox)
+               end
+               return tag
+       end
+
+       # Highlight a full lexed source file.
+       #
+       # REQUIRE `source.first_token != null`
+       fun highlight_source(source: SourceFile)
+       do
+               do_highlight(source.first_token.as(not null), null)
+       end
+
+       # Low-level highlighting between 2 tokens
+       protected fun do_highlight(first_token: Token, last_token: nullable Token)
        do
                var stack2 = new Array[HTMLTag]
                var stack = new Array[Prod]
                var line = 0
                var c: nullable Token = first_token
-               var hv = new HighlightVisitor
+               var hv = self
                while c != null do
                        var starting
 
@@ -79,11 +299,9 @@ class HighlightVisitor
                                if c0 != null then starting = c0.starting_prods
                                if starting != null then for p in starting do
                                        if not p.is_block then continue
-                                       var tag = p.make_tag(hv)
+                                       var tag = full_tag(p, hv)
                                        if tag == null then continue
                                        tag.add_class("foldable")
-                                       var infobox = p.infobox(hv)
-                                       if infobox != null then tag.attach_infobox(infobox)
                                        stack2.add(html)
                                        html.add tag
                                        html = tag
@@ -108,10 +326,8 @@ class HighlightVisitor
                        starting = c.starting_prods
                        if starting != null then for p in starting do
                                if not p.is_span then continue
-                               var tag = p.make_tag(hv)
+                               var tag = full_tag(p, hv)
                                if tag == null then continue
-                               var infobox = p.infobox(hv)
-                               if infobox != null then tag.attach_infobox(infobox)
                                stack2.add(html)
                                html.add tag
                                html = tag
@@ -119,20 +335,11 @@ class HighlightVisitor
                        end
 
                        # Add the token
-                       if c isa TEol then 
+                       if c isa TEol then
                                html.append "\n"
                        else
-                               var tag = c.make_tag(hv)
-                               var pa = c.parent
-                               var infobox = null
-                               if c isa TId or c isa TClassid or c isa TAttrid or c isa TokenLiteral or c isa TokenOperator then
-                                       assert c != null
-                                       if pa != null then infobox = pa.decorate_tag(hv, tag, c)
-                               else if c isa TComment and pa isa ADoc then
-                                       infobox = pa.decorate_tag(hv, tag, c)
-                               end
-                               if infobox != null then tag.attach_infobox(infobox)
-                               html.add tag
+                               var tag = full_tag(c, hv)
+                               if tag != null then html.add tag
                        end
 
                        # Handle ending span productions
@@ -165,8 +372,7 @@ class HighlightVisitor
 
                        c = n
                end
-               assert stack.is_empty
-               assert stack2.is_empty
+               if not stack2.is_empty then html = stack2.first
        end
 
        # Return a default CSS content related to CSS classes used in the `html` tree.
@@ -175,6 +381,7 @@ class HighlightVisitor
        do
                return """
 .nitcode a { color: inherit; cursor:pointer; }
+.nitcode .titled:hover { text-decoration: underline; } /* underline titles */
 .nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
 .nitcode .foldable { display: block } /* for block productions*/
 .nitcode .line{ display: block } /* for lines */
@@ -269,12 +476,14 @@ class HInfoBox
        end
 
        # Append a new dropdown in the popuped content
-       fun new_dropdown(title, text: String): HTMLTag
+       fun new_dropdown(title, text: String, text_is_html: nullable Bool): HTMLTag
        do
                content.add_raw_html """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
                content.append(title)
                content.add_raw_html "</b> "
-               content.append(text)
+               if text_is_html == true then
+                       content.add_raw_html(text)
+               else content.append(text)
                content.add_raw_html """<span class="caret"></span></a>"""
                var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
                content.add_raw_html "</div>"
@@ -288,9 +497,6 @@ end
 interface HInfoBoxable
        # An new infobox documenting the entity
        fun infobox(v: HighlightVisitor): HInfoBox is abstract
-
-       # A human-readable hyper-text for the entity
-       fun linkto: HTMLTag is abstract
 end
 
 redef class MDoc
@@ -311,56 +517,73 @@ end
 
 redef class MEntity
        super HInfoBoxable
+
+       # A HTML version of `to_s` with hyper-links.
+       #
+       # By default, `linkto_text(v, to_s)` is used, c.f. see `linkto_text`.
+       #
+       # For some complex entities, like generic types, multiple `<a>` and `<span>` elements can be generated.
+       # E.g. `Array[Int]` might become `<a>Array</a>[<a>Int</a>]` with the correct `href` attributes
+       # provided  by `v.hrefto`.
+       fun linkto(v: HighlightVisitor): HTMLTag do return linkto_text(v, to_s)
+
+       # Link to the `self` with a specific text.
+       #
+       # The whole text is linked with a single `<a>` element.
+       #
+       # The `href` used is provided by `v.hrefto`.
+       # If `href` is null then a `<span>` element is used instead of `<a>`.
+       fun linkto_text(v: HighlightVisitor, text: String): HTMLTag
+       do
+               var href = v.hrefto(self)
+               if href == null then
+                       return (new HTMLTag("span")).text(text)
+               end
+               return (new HTMLTag("a")).attr("href", href).text(text)
+       end
+
+       # Append an entry for the doc in the given infobox
+       private fun add_doc_to_infobox(res: HInfoBox)
+       do
+               var mdoc = mdoc_or_fallback
+               if mdoc != null then mdoc.fill_infobox(res)
+       end
 end
 
 redef class MModule
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, "module {name}")
-               res.href = href
-               res.new_field("module").add(linkto)
-               var mdoc = self.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               res.href = v.hrefto(self)
+               res.new_field("module").add(linkto(v))
+               add_doc_to_infobox(res)
                if in_importation.greaters.length > 1 then
                        var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules")
                        for x in in_importation.greaters do
                                if x == self then continue
-                               c.open("li").add x.linkto
+                               c.open("li").add x.linkto(v)
                        end
                end
                return res
        end
 
-       # The module HTML page
-       fun href: String
-       do
-               return name + ".html"
-       end
-
-       redef fun linkto do return linkto_text(name)
-
-       # Link to the entitiy with a specific text
-       fun linkto_text(text: String): HTMLTag
-       do
-               return (new HTMLTag("a")).attr("href", href).text(text)
-       end
+       redef fun linkto(v) do return linkto_text(v, name)
 end
 
 redef class MClassDef
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, "class {mclass.name}")
-               res.href = href
+               res.href = v.hrefto(self)
                if is_intro then
                        res.new_field("class").text(mclass.name)
                else
                        res.new_field("redef class").text(mclass.name)
-                       res.new_field("intro").add mclass.intro.linkto_text("in {mclass.intro.mmodule.to_s}")
+                       res.new_field("intro").add mclass.intro.linkto_text(v, "in {mclass.intro_mmodule.to_s}")
                end
-               var mdoc = self.mdoc
-               if mdoc == null then mdoc = mclass.intro.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               add_doc_to_infobox(res)
 
+               var in_hierarchy = self.in_hierarchy
                if in_hierarchy == null then return res
 
                if in_hierarchy.greaters.length > 1 then
@@ -368,7 +591,7 @@ redef class MClassDef
                        for x in in_hierarchy.greaters do
                                if x == self then continue
                                if not x.is_intro then continue
-                               c.open("li").add x.linkto
+                               c.open("li").add x.linkto(v)
                        end
                end
                if in_hierarchy.smallers.length > 1 then
@@ -376,142 +599,134 @@ redef class MClassDef
                        for x in in_hierarchy.smallers do
                                if x == self then continue
                                if not x.is_intro then continue
-                               c.open("li").add x.linkto
+                               c.open("li").add x.linkto(v)
                        end
                end
                if mclass.mclassdefs.length > 1 then
                        var c = res.new_dropdown("redefs", "refinements")
                        for x in mclass.mclassdefs do
                                if x == self then continue
-                               c.open("li").add x.linkto_text("in {x.mmodule}")
+                               c.open("li").add x.linkto_text(v, "in {x.mmodule}")
                        end
                end
                return res
        end
-
-       # The class HTML page (an anchor in the module page)
-       fun href: String
-       do
-               return mmodule.href + "#" + to_s
-       end
-
-       redef fun linkto do return linkto_text(mclass.name)
-
-       # Link to the entitiy with a specific text
-       fun linkto_text(text: String): HTMLTag
-       do
-               return (new HTMLTag("a")).attr("href", href).text(text)
-       end
 end
 
 redef class MPropDef
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, to_s)
-               res.href = href
+               res.href = v.hrefto(self)
                if self isa MMethodDef then
-                       if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto
+                       var msignature = self.msignature
+                       if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v)
                else if self isa MAttributeDef then
-                       if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto
+                       var static_mtype = self.static_mtype
+                       if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v)
                else if self isa MVirtualTypeDef then
-                       if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto
+                       var bound = self.bound
+                       if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v)
                else
                        res.new_field("wat?").append(mproperty.name)
                end
 
                if is_intro then
                else
-                       res.new_field("intro").add mproperty.intro.linkto_text("in {mproperty.intro.mclassdef}")
+                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
                end
-               var mdoc = self.mdoc
-               if mdoc == null then mdoc = mproperty.intro.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               add_doc_to_infobox(res)
                if mproperty.mpropdefs.length > 1 then
                        var c = res.new_dropdown("redef", "redefinitions")
                        for x in mproperty.mpropdefs do
-                               c.open("li").add x.linkto_text("in {x.mclassdef}")
+                               c.open("li").add x.linkto_text(v, "in {x.mclassdef}")
                        end
                end
 
                return res
        end
-
-       # The property HTML page (an anchor in the module page)
-       fun href: String
-       do
-               return self.mclassdef.mmodule.href + "#" + self.to_s
-       end
-
-       redef fun linkto do return linkto_text(mproperty.name)
-
-       # Link to the entitiy with a specific text
-       fun linkto_text(text: String): HTMLTag
-       do
-               return (new HTMLTag("a")).attr("href", href).text(text)
-       end
 end
 
 redef class MClassType
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, to_s)
-               res.href = mclass.intro.href
-               res.new_field("class").add mclass.intro.linkto
-               var mdoc = mclass.mdoc
-               if mdoc == null then mdoc = mclass.intro.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               res.href = v.hrefto(self)
+               res.new_field("class").add mclass.intro.linkto(v)
+               add_doc_to_infobox(res)
                return res
        end
-       redef fun linkto
+       redef fun linkto(v)
        do
-               return mclass.intro.linkto
+               return mclass.intro.linkto(v)
        end
 end
 redef class MVirtualType
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, to_s)
-               res.href = mproperty.intro.href
+               res.href = v.hrefto(mproperty)
                var p = mproperty
-               var pd = p.intro
-               res.new_field("virtual type").add pd.linkto
-               var mdoc = pd.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               res.new_field("virtual type").add p.intro.linkto(v)
+               add_doc_to_infobox(res)
                return res
        end
-       redef fun linkto
+       redef fun linkto(v)
        do
-               return mproperty.intro.linkto
+               return mproperty.intro.linkto(v)
        end
 end
 redef class MParameterType
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, to_s)
-               res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto
+               res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto(v)
                return res
        end
-       redef fun linkto
+end
+
+redef class MNullableType
+       redef fun infobox(v)
+       do
+               return mtype.infobox(v)
+       end
+       redef fun linkto(v)
        do
-               return (new HTMLTag("span")).text(name)
+               var res = new HTMLTag("span")
+               res.append("nullable ").add(mtype.linkto(v))
+               return res
        end
 end
 
-redef class MNullableType
+redef class MNotNullType
        redef fun infobox(v)
        do
                return mtype.infobox(v)
        end
-       redef fun linkto
+       redef fun linkto(v)
        do
                var res = new HTMLTag("span")
-               res.append("nullable ").add(mtype.linkto)
+               res.append("not null ").add(mtype.linkto(v))
+               return res
+       end
+end
+
+redef class MNullType
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               return res
+       end
+       redef fun linkto(v)
+       do
+               var res = new HTMLTag("span")
+               res.append("null")
                return res
        end
 end
 
 redef class MSignature
-       redef fun linkto
+       redef fun linkto(v)
        do
                var res = new HTMLTag("span")
                var first = true
@@ -525,39 +740,36 @@ redef class MSignature
                                end
                                res.append p.name
                                res.append ": "
-                               res.add p.mtype.linkto
+                               res.add p.mtype.linkto(v)
                        end
                        res.append ")"
                end
                var ret = return_mtype
                if ret != null then
                        res.append ": "
-                       res.add ret.linkto
+                       res.add ret.linkto(v)
                end
                return res
        end
 end
 
 redef class CallSite
-       super HInfoBoxable
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, "call {mpropdef}")
-               res.href = mpropdef.href
-               res.new_field("call").add(mpropdef.linkto).add(msignature.linkto)
+               res.href = v.hrefto(mpropdef)
+               res.new_field("call").add(mpropdef.linkto(v)).add(msignature.linkto(v))
                if mpropdef.is_intro then
                else
-                       res.new_field("intro").add mproperty.intro.linkto_text("in {mproperty.intro.mclassdef}")
+                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
                end
-               var mdoc = mpropdef.mdoc
-               if mdoc == null then mdoc = mproperty.intro.mdoc
-               if mdoc != null then mdoc.fill_infobox(res)
+               add_doc_to_infobox(res)
 
                return res
        end
-       redef fun linkto
+       redef fun linkto(v)
        do
-               return mpropdef.linkto
+               return mpropdef.linkto(v)
        end
 end
 
@@ -572,13 +784,9 @@ redef class Variable
                        return res
                end
                var res = new HInfoBox(v, "{name}: {declared_type}")
-               res.new_field("local var").append("{name}:").add(declared_type.linkto)
+               res.new_field("local var").append("{name}:").add(declared_type.linkto(v))
                return res
        end
-       redef fun linkto
-       do
-               return (new HTMLTag("span")).text(name)
-       end
 end
 
 
@@ -600,6 +808,26 @@ redef class ANode
        fun infobox(v: HighlightVisitor): nullable HInfoBox do return null
 end
 
+redef class AQclassid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var parent = self.parent
+               if parent == null then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
+redef class AQid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var parent = self.parent
+               if parent == null then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
 redef class AStdClassdef
        redef fun make_tag(v)
        do
@@ -611,6 +839,7 @@ redef class AStdClassdef
        end
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_def")
 
                var md = mclassdef
@@ -649,7 +878,7 @@ end
 redef class Token
        # Produce an HTMLTag with the correct contents and CSS classes
        # Subclasses can redefine it to decorate the tag
-       redef fun make_tag(v: HighlightVisitor): HTMLTag
+       redef fun make_tag(v): HTMLTag
        do
                var res = new HTMLTag("span")
                res.text(text)
@@ -669,8 +898,6 @@ redef class TokenOperator
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_o")
                return res
        end
@@ -679,6 +906,7 @@ end
 redef class AVarFormExpr
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var variable = self.variable
                if variable == null then return null
                res.add_class("nc_v")
@@ -689,6 +917,7 @@ end
 redef class AVardeclExpr
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var variable = self.variable
                if variable == null then return null
                res.add_class("nc_v")
@@ -696,7 +925,7 @@ redef class AVardeclExpr
        end
 end
 
-redef class AForExpr
+redef class AForGroup
        redef fun decorate_tag(v, res, token)
        do
                if not token isa TId then return null
@@ -712,6 +941,7 @@ end
 redef class AParam
        redef fun decorate_tag(v, res, token)
        do
+               if token != n_id then return null
                var mp = mparameter
                if mp == null then return null
                var variable = self.variable
@@ -724,6 +954,7 @@ end
 redef class AAssertExpr
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_ast")
                return null
        end
@@ -732,6 +963,7 @@ end
 redef class ALabel
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_la")
                return null
        end
@@ -740,6 +972,7 @@ end
 redef class ASendExpr
        redef fun decorate_tag(v, res, token)
        do
+               var callsite = self.callsite
                if callsite == null then return null
                return callsite.infobox(v)
        end
@@ -748,6 +981,7 @@ end
 redef class ANewExpr
        redef fun decorate_tag(v, res, token)
        do
+               var callsite = self.callsite
                if callsite == null then return null
                return callsite.infobox(v)
        end
@@ -768,13 +1002,16 @@ end
 redef class AModuleName
        redef fun decorate_tag(v, res, token)
        do
-               return parent.decorate_tag(v, res, token)
+               var p = parent
+               if p == null then return null
+               return p.decorate_tag(v, res, token)
        end
 end
 
 redef class AModuledecl
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_def")
                res.add_class("nc_m")
                var p = parent
@@ -788,6 +1025,7 @@ end
 redef class AStdImport
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_m")
                var mm = mmodule
                if mm == null then return null
@@ -797,6 +1035,7 @@ end
 redef class AAttrPropdef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TId then return null
                res.add_class("nc_def")
                var mpd: nullable MPropDef
                mpd = mreadpropdef
@@ -810,8 +1049,6 @@ redef class TId
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_i")
                return res
        end
@@ -841,8 +1078,6 @@ redef class TAttrid
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_a")
                return res
        end
@@ -850,6 +1085,7 @@ end
 redef class AAttrFormExpr
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TAttrid then return null
                var p = mproperty
                if p == null then return null
                return p.intro.infobox(v)
@@ -859,8 +1095,6 @@ redef class TClassid
        redef fun make_tag(v)
        do
                var res = super
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                res.add_class("nc_t")
                return res
        end
@@ -868,10 +1102,11 @@ end
 redef class AType
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                var mt = mtype
                if mt == null then return null
-               mt = mt.as_notnullable
-               if mt isa MVirtualType or mt isa MParameterType then
+               mt = mt.undecorate
+               if mt isa MFormalType then
                        res.add_class("nc_vt")
                end
                return mt.infobox(v)
@@ -880,7 +1115,9 @@ end
 redef class AFormaldef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_vt")
+               var mtype = self.mtype
                if mtype == null then return null
                return mtype.infobox(v)
        end
@@ -888,6 +1125,7 @@ end
 redef class ATypePropdef
        redef fun decorate_tag(v, res, token)
        do
+               if not token isa TClassid then return null
                res.add_class("nc_def")
                var md = mpropdef
                if md == null then return null
@@ -898,7 +1136,7 @@ redef class TComment
        redef fun make_tag(v)
        do
                var res = super
-               if not parent isa ADoc then
+               if is_loose then
                        res.add_class("nc_c")
                end
                return res
@@ -917,8 +1155,6 @@ redef class TokenLiteral
        do
                var res = super
                res.add_class("nc_l")
-               var p = parent
-               if p != null then p.decorate_tag(v, res, self)
                return res
        end
 end
@@ -936,7 +1172,7 @@ redef class AStringFormExpr
                # Workaround to tag strings
                res.classes.remove("nc_l")
                res.add_class("nc_s")
-               return null
+               return super
        end
 end
 redef class AExpr