forked from scalameta/metals
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ScaladocIndexer.scala
163 lines (156 loc) · 4.57 KB
/
ScaladocIndexer.scala
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
154
155
156
157
158
159
160
161
162
163
package scala.meta.internal.metals
import scala.collection.mutable
import scala.meta._
import scala.meta.dialects.Scala213
import scala.meta.internal.docstrings._
import scala.meta.internal.jdk.CollectionConverters._
import scala.meta.internal.mtags.ScalaMtags
import scala.meta.internal.semanticdb.Scala.Descriptor
import scala.meta.internal.semanticdb.Scala.Symbols
import scala.meta.internal.semanticdb.SymbolInformation
import scala.meta.internal.semanticdb.SymbolOccurrence
import scala.meta.pc.SymbolDocumentation
import scala.meta.trees.Origin
/**
* Extracts Scaladoc from Scala source code.
*/
class ScaladocIndexer(
input: Input.VirtualFile,
fn: SymbolDocumentation => Unit,
dialect: Dialect
) extends ScalaMtags(input, dialect) {
val defines: mutable.Map[String, String] = mutable.Map.empty[String, String]
override def visitOccurrence(
occ: SymbolOccurrence,
sinfo: SymbolInformation,
owner: String
): Unit = {
val docstring = currentTree.origin match {
case parsed: Origin.Parsed =>
val leadingDocstring =
ScaladocIndexer.findLeadingDocstring(
source.tokens,
parsed.begTokenIdx - 1
)
leadingDocstring match {
case Some(value) => value
case None => ""
}
case _ => ""
}
// Register `@define` macros to use for expanding in later docstrings.
defines ++= ScaladocParser.extractDefines(docstring)
val comment = ScaladocParser.parseComment(docstring, defines)
val markdown = MarkdownGenerator.toMarkdown(comment, docstring)
def param(name: String, default: String): SymbolDocumentation = {
val paramDoc = comment.valueParams
.get(name)
.orElse(comment.typeParams.get(name))
.map(MarkdownGenerator.toMarkdown)
.getOrElse("")
MetalsSymbolDocumentation(
Symbols.Global(owner, Descriptor.Parameter(name)),
name,
paramDoc,
default
)
}
def mparam(member: Member): SymbolDocumentation = {
val default = member match {
case Term.Param(_, _, _, Some(term)) => term.syntax
case _ =>
""
}
param(member.name.value, default)
}
val info = currentTree match {
case _: Defn.Trait | _: Pkg.Object | _: Defn.Val | _: Defn.Var |
_: Decl.Val | _: Decl.Var | _: Defn.Type | _: Decl.Type =>
Some(
MetalsSymbolDocumentation(
occ.symbol,
sinfo.displayName,
markdown
)
)
case t: Defn.Def =>
Some(
MetalsSymbolDocumentation(
occ.symbol,
t.name.value,
markdown,
"",
t.tparams.map(mparam).asJava,
t.paramss.flatten.map(mparam).asJava
)
)
case t: Decl.Def =>
Some(
MetalsSymbolDocumentation(
occ.symbol,
t.name.value,
markdown,
"",
t.tparams.map(mparam).asJava,
t.paramss.flatten.map(mparam).asJava
)
)
case t: Defn.Class =>
Some(
MetalsSymbolDocumentation(
occ.symbol,
t.name.value,
markdown,
"",
// Type parameters are intentionally excluded because constructors
// cannot have type parameters.
Nil.asJava,
t.ctor.paramss.flatten.map(mparam).asJava
)
)
case t: Member =>
Some(
MetalsSymbolDocumentation(
occ.symbol,
t.name.value,
markdown
)
)
case _ =>
None
}
info.foreach(fn)
}
}
object ScaladocIndexer {
/**
* Extracts Scaladoc from Scala source code.
*
* @param fn callback function for calculated SymbolDocumentation
*/
def foreach(
input: Input.VirtualFile,
dialect: Dialect
)(fn: SymbolDocumentation => Unit): Unit = {
new ScaladocIndexer(input, fn, dialect).indexRoot()
}
/**
* Returns a Scaladoc string leading the given start position, if any.
* @param start the offset in the `Tokens` array.
*/
def findLeadingDocstring(tokens: Tokens, start: Int): Option[String] =
if (start < 0) None
else {
tokens(start) match {
case c: Token.Comment =>
val syntax = c.syntax
if (syntax.startsWith("/**")) Some(syntax)
else None
case _: Token.Space | _: Token.LF | _: Token.CR | _: Token.LFLF |
_: Token.Tab =>
findLeadingDocstring(tokens, start - 1)
case _ =>
None
}
}
}