summaryrefslogtreecommitdiff
path: root/src/scaladoc/scala/tools/nsc/doc/html/page/IndexScript.scala
blob: 28304e76c7a0f6c894239459dac89b25762aa578 (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
143
144
145
146
147
148
149
150
151
152
153
/* NSC -- new Scala compiler
 * Copyright 2007-2016 LAMP/EPFL
 * @author  David Bernard, Manohar Jonnalagedda, Felix Mulder
 */

package scala.tools.nsc.doc
package html
package page

import scala.tools.nsc.doc
import scala.tools.nsc.doc.model.{Package, DocTemplateEntity}
import scala.tools.nsc.doc.html.{Page, HtmlFactory}

class IndexScript(universe: doc.Universe) extends Page {
  import model._
  import scala.tools.nsc.doc.base.comment.Text
  import scala.collection.immutable.Map

  def path = List("index.js")

  override def writeFor(site: HtmlFactory) {
    writeFile(site) {
      _.write(s"Index.PACKAGES = $packages;")
    }
  }

  val packages = {
    val pairs = allPackagesWithTemplates.toIterable.map(_ match {
      case (pack, templates) => {
        val merged = mergeByQualifiedName(templates)

        val ary = merged.keys.toVector.sortBy(_.toLowerCase).map { key =>
          /** One pair is generated for the class/trait and one for the
           *  companion object, both will have the same {"name": key}
           *
           *  As such, we need to distinguish between the members that are
           *  generated by the object, and the members generated by the
           *  class/trait instance. Otherwise one of the member objects will be
           *  overwritten.
           */
          val pairs = merged(key).flatMap { t: DocTemplateEntity =>
            val kind = kindToString(t)
            Seq(
              kind -> relativeLinkTo(t),
              "kind" -> kind,
              s"members_$kind" -> membersToJSON(t.members.toVector.filter(!_.isShadowedOrAmbiguousImplicit), t),
              "shortDescription" -> shortDesc(t))
          }

          JSONObject(Map(pairs : _*) + ("name" -> key))
        }

        pack.qualifiedName -> JSONArray(ary)
      }
    }).toSeq

    JSONObject(Map(pairs : _*))
  }

  private def mergeByQualifiedName(source: List[DocTemplateEntity]): collection.mutable.Map[String, List[DocTemplateEntity]] = {
    val result = collection.mutable.Map[String, List[DocTemplateEntity]]()

    for (t <- source) {
      val k = t.qualifiedName
      result += k -> (result.getOrElse(k, Nil) :+ t)
    }

    result
  }

  def allPackages: List[Package] = {
    def f(parent: Package): List[Package] = {
      parent.packages.flatMap(
        p => f(p) :+ p
      )
    }
    f(universe.rootPackage).sortBy(_.toString)
  }

  def allPackagesWithTemplates: Map[Package, List[DocTemplateEntity]] = {
    Map(allPackages.map((key) => {
      key -> key.templates.collect {
        case t: DocTemplateEntity if !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) => t
      }
    }) : _*)
  }

  /** Gets the short description i.e. the first sentence of the docstring */
  def shortDesc(mbr: MemberEntity): String = mbr.comment.fold("") { c =>
    Page.inlineToStr(c.short).replaceAll("\n", "")
  }

  /** Returns the json representation of the supplied members */
  def membersToJSON(entities: Vector[MemberEntity], parent: DocTemplateEntity): JSONArray =
    JSONArray(entities.map(memberToJSON(_, parent)))

  private def memberToJSON(mbr: MemberEntity, parent: DocTemplateEntity): JSONObject = {
    /** This function takes a member and gets eventual parameters and the
     *  return type. For example, the definition:
     *  {{{ def get(key: A): Option[B] }}}
     *  Gets turned into: "(key: A): Option[B]"
     */
    def memberTail: MemberEntity => String = {
      case d: Def => d
          .valueParams //List[List[ValueParam]]
          .map { params =>
            params.map(p => p.name + ": " + p.resultType.name).mkString(", ")
          }
          .mkString("(", ")(", "): " + d.resultType.name)
      case v: Val => ": " + v.resultType.name
      case _ => ""
    }

    /** This function takes a member entity and return all modifiers in a
     *  string, example:
     *  {{{ lazy val scalaProps: java.util.Properties }}}
     *  Gets turned into: "lazy val"
     */
    def memberKindToString(mbr: MemberEntity): String = {
      val kind = mbr.flags.map(_.text.asInstanceOf[Text].text).mkString(" ")
      val space = if (kind == "") "" else " "

      kind + space + kindToString(mbr)
    }

    /** This function turns a member entity into a JSON object that the index.js
     *  script can use to render search results
     */
    def jsonObject(m: MemberEntity): JSONObject =
      JSONObject(Map(
        "label"  -> "[^\\.]*\\.([^#]+#)?".r.replaceAllIn(m.definitionName, ""), // member name
        "member" -> m.definitionName.replaceFirst("#", "."), // full member name
        "tail"   -> memberTail(m),
        "kind"   -> memberKindToString(m), // modifiers i.e. "abstract def"
        "link"   -> memberToUrl(m)))       // permalink to the member

    mbr match {
      case x @ (_: Def | _: Val | _: Object | _: AliasType) => jsonObject(x)
      case e @ (_: Class | _: Trait) if parent.isRootPackage || !parent.isPackage => jsonObject(e)
      case m: MemberEntity =>
        JSONObject(Map("member" -> m.definitionName, "error" -> "unsupported entity"))
    }
  }

  def memberToUrl(mbr: MemberEntity): String = {
    val path = templateToPath(mbr.inTemplate).reverse.mkString("/")
    s"$path#${mbr.signature}"
  }
}

object IndexScript {
  def apply(universe: doc.Universe) = new IndexScript(universe)
}