aboutsummaryrefslogtreecommitdiff
path: root/dottydoc/jvm/src/dotty/tools/dottydoc/model/parsers.scala
blob: 2c163b1e626d37366f52bbb04d76fc946ccf6d5c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package dotty.tools
package dottydoc
package model

import dotc.core.Symbols.Symbol
import dotc.core.Contexts.Context
import dotc.util.Positions.NoPosition

object parsers {
  import comment._
  import BodyParsers._
  import model.internal._
  import util.MemberLookup
  import util.traversing._
  import util.internal.setters._

  class WikiParser extends CommentCleaner with CommentParser with CommentExpander {
    private[this] var commentCache: Map[String, (Entity, Map[String, Package]) => Option[Comment]] = Map.empty

    /** Parses comment and returns the path to the entity with an optional comment
     *
     * The idea here is to use this fact to create `Future[Seq[(String, Option[Comment]]]`
     * which can then be awaited near the end of the run - before the pickling.
     */
    def parseHtml(sym: Symbol, entity: Entity, packages: Map[String, Package])(implicit ctx: Context): (String, Option[Comment]) = {
      val cmt = ctx.base.docstring(sym).map { d =>
        val expanded = expand(sym)
        val body = parse(entity, packages, clean(expanded), expanded, d.pos)
        val summary = body.summary.map(_.toHtml(entity)).getOrElse("")
         body.toHtml(entity) match {
          case "" => None
          case x  => Some(Comment(x, summary))
        }
      }.flatten

      (entity.path.mkString("."), cmt)
    }


    def add(entity: Entity, symbol: Symbol, ctx: Context): Unit = {
      val commentParser = { (entity: Entity, packs: Map[String, Package]) =>
        parseHtml(symbol, entity, packs)(ctx)._2
      }

      val path = entity.path.mkString(".")
      if (!commentCache.contains(path) || ctx.base.docstring(symbol).isDefined)
        commentCache = commentCache + (path -> commentParser)
    }

    def +=(entity: Entity, symbol: Symbol, ctx: Context) = add(entity, symbol, ctx)

    def size: Int = commentCache.size

    private def parse(entity: Entity, packs: Map[String, Package]): Option[Comment] =
      commentCache(entity.path.mkString("."))(entity, packs)

    def parse(packs: Map[String, Package]): Unit = {
      def rootPackages: List[String] = {
        var currentDepth = Int.MaxValue
        var packages: List[String] = Nil

        for (key <- packs.keys) {
          val keyDepth = key.split("\\.").length
          packages =
            if (keyDepth < currentDepth) {
              currentDepth = keyDepth
              key :: Nil
            } else if (keyDepth == currentDepth) {
              key :: packages
            } else packages
        }

        packages
      }

      for (pack <- rootPackages) {
        mutateEntities(packs(pack)) { e =>
          val comment = parse(e, packs)
          setComment(e, to = comment)
        }
      }
    }

    def clear(): Unit = commentCache = Map.empty
  }

  sealed trait TypeLinker extends MemberLookup {
    protected def linkReference(ent: Entity, rv: Reference, packs: Map[String, Package]): Reference =
      rv match {
        case rv @ TypeReference(_, UnsetLink(t, query), tps) =>
          val inlineToHtml = InlineToHtml(ent)
          val title = inlineToHtml(t)

          def handleEntityLink(title: String, lt: LinkTo): MaterializableLink = lt match {
            case Tooltip(str)           => NoLink(title, str)
            case LinkToExternal(_, url) => MaterializedLink(title, url)
            case LinkToEntity(target)   => MaterializedLink(title, util.traversing.relativePath(ent, target))
          }

          val target = handleEntityLink(title, makeEntityLink(ent, packs, t, NoPosition, query).link)

          val tpTargets = tps.map {
            case UnsetLink(t, query) =>
              handleEntityLink(inlineToHtml(t), makeEntityLink(ent, packs, t, NoPosition, query).link)
            case x => x
          }

          rv.copy(tpeLink = target, paramLinks = tpTargets)
        case rv @ OrTypeReference(left, right) =>
          rv.copy(left = linkReference(ent, left, packs), right = linkReference(ent, right, packs))
        case rv @ AndTypeReference(left, right) =>
          rv.copy(left = linkReference(ent, left, packs), right = linkReference(ent, right, packs))
        case rv @ NamedReference(_, ref) => rv.copy(ref = linkReference(ent, ref, packs))
        case _ => rv
      }

    def link(packs: Map[String, Package]): Unit
  }

  class ReturnTypeLinker extends TypeLinker {
    def link(packs: Map[String, Package]): Unit =
      for (pack <- packs.values) mutateEntities(pack) {
        case ent: ReturnValue =>
          setReturnValue(ent, linkReference(ent, ent.returnValue, packs))
        case _ => ()
      }
  }

  class ParamListLinker extends TypeLinker {
    def link(packs: Map[String, Package]): Unit =
      for (pack <- packs.values) mutateEntities(pack) {
        case ent: Def =>
          val newParamLists = for {
            list <- ent.paramLists
            newList = list.map(linkReference(ent, _, packs))
          } yield newList.asInstanceOf[List[NamedReference]]

          setParamLists(ent, newParamLists)
        case _ => ()
      }
  }
}