Skip to content

Commit fcd064b

Browse files
committed
docgen: implement doc link resolution in current module
no special handling of routineKinds here fix segfault fix references in the Manual a supplemental test generics: rm type parameters in normalized names allow underscore in names and export (*) many fixes of warnings: - always resolve all the 3 cases: manual references, RST anchors, Nim anchors and print warnings in case of ambiguity - preserve anchor/substitution definition places - correctly handle `include` in ``.nim`` files for doc comment warnings/errors - make warning messages more detailed change wording & add test regarding brackets in input parameter types some progress: - fixes for handling symbols with backticks - now reference points to concrete function (not group) if there is no other overloads - link text is the full name (with function parameters) if there is only 1 overload. If there are > 1 then it's like "proc binarySearch (2 overloads)" add more testcases, fix func add specification of the feature [skip ci] update the spec reworked the implementation + more tests fix ambiguity handling and reporting, including adding RST explicit hyperlinks to ambiguity printing Default priorities now conform to the `docgen.rst` spec also. enable `doc2tex` fix generics parameters for types some cleanups and clarifications tests on inclusion of .rst & .nim into .nim update forgotten files for tests
1 parent 0ef8305 commit fcd064b

File tree

22 files changed

+1782
-141
lines changed

22 files changed

+1782
-141
lines changed

compiler/docgen.nim

Lines changed: 138 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import
1414
ast, strutils, strtabs, algorithm, sequtils, options, msgs, os, idents,
1515
wordrecg, syntaxes, renderer, lexer,
16-
packages/docutils/rst, packages/docutils/rstgen,
16+
packages/docutils/[rst, rstgen, dochelpers],
1717
json, xmltree, trees, types,
1818
typesrenderer, astalgo, lineinfos, intsets,
1919
pathutils, tables, nimpaths, renderverbatim, osproc
@@ -44,8 +44,14 @@ type
4444
## runnableExamples).
4545
substitutions: seq[string] ## Variable names in `doc.item`...
4646
sortName: string ## The string used for sorting in output
47+
info: rstast.TLineInfo ## place where symbol was defined (for messages)
48+
anchor: string ## e.g. HTML anchor
49+
name: string ## short name of the symbol, not unique
50+
## (includes backticks ` if present)
51+
detailedName: string ## longer name like `proc search(x: int): int`
4752
ModSection = object ## Section like Procs, Types, etc.
48-
secItems: seq[Item] ## Pre-processed items.
53+
secItems: Table[string, seq[Item]]
54+
## Map basic name -> pre-processed items.
4955
finalMarkup: string ## The items, after RST pass 2 and rendering.
5056
ModSections = array[TSymKind, ModSection]
5157
TocItem = object ## HTML TOC item
@@ -91,12 +97,22 @@ type
9197
thisDir*: AbsoluteDir
9298
exampleGroups: OrderedTable[string, ExampleGroup]
9399
wroteSupportFiles*: bool
100+
nimToRstFid: Table[lineinfos.FileIndex, rstast.FileIndex]
101+
## map Nim FileIndex -> RST one, it's needed because we keep them separate
94102

95103
PDoc* = ref TDocumentor ## Alias to type less.
96104

97105
proc add(dest: var ItemPre, rst: PRstNode) = dest.add ItemFragment(isRst: true, rst: rst)
98106
proc add(dest: var ItemPre, str: string) = dest.add ItemFragment(isRst: false, str: str)
99107

108+
proc addRstFileIndex(d: PDoc, info: lineinfos.TLineInfo): rstast.FileIndex =
109+
let invalid = rstast.FileIndex(-1)
110+
result = d.nimToRstFid.getOrDefault(info.fileIndex, default = invalid)
111+
if result == invalid:
112+
let fname = toFullPath(d.conf, info)
113+
result = addFilename(d.sharedState, fname)
114+
d.nimToRstFid[info.fileIndex] = result
115+
100116
proc cmpDecimalsIgnoreCase(a, b: string): int =
101117
## For sorting with correct handling of cases like 'uint8' and 'uint16'.
102118
## Also handles leading zeros well (however note that leading zeros are
@@ -223,6 +239,7 @@ template declareClosures =
223239
of meFootnoteMismatch: k = errRstFootnoteMismatch
224240
of mwRedefinitionOfLabel: k = warnRstRedefinitionOfLabel
225241
of mwUnknownSubstitution: k = warnRstUnknownSubstitutionX
242+
of mwAmbiguousLink: k = warnRstAmbiguousLink
226243
of mwBrokenLink: k = warnRstBrokenLink
227244
of mwUnsupportedLanguage: k = warnRstLanguageXNotSupported
228245
of mwUnsupportedField: k = warnRstFieldXNotSupported
@@ -236,7 +253,7 @@ template declareClosures =
236253
result = getCurrentDir() / s
237254
if not fileExists(result): result = ""
238255

239-
proc parseRst(text, filename: string,
256+
proc parseRst(text: string,
240257
line, column: int,
241258
conf: ConfigRef, sharedState: PRstSharedState): PRstNode =
242259
declareClosures()
@@ -352,7 +369,8 @@ proc getVarIdx(varnames: openArray[string], id: string): int =
352369

353370
proc genComment(d: PDoc, n: PNode): PRstNode =
354371
if n.comment.len > 0:
355-
result = parseRst(n.comment, toFullPath(d.conf, n.info),
372+
d.sharedState.currFileIdx = addRstFileIndex(d, n.info)
373+
result = parseRst(n.comment,
356374
toLinenumber(n.info),
357375
toColumn(n.info) + DocColOffset,
358376
d.conf, d.sharedState)
@@ -885,6 +903,57 @@ proc genSeeSrc(d: PDoc, path: string, line: int): string =
885903
"path", path.string, "line", $line, "url", gitUrl,
886904
"commit", commit, "devel", develBranch]])
887905

906+
proc symbolPriority(k: TSymKind): int =
907+
result = case k
908+
of skMacro: -3
909+
of skTemplate: -2
910+
of skIterator: -1
911+
else: 0 # including skProc which have higher priority
912+
# documentation itself has even higher priority 1
913+
914+
proc toLangSymbol(k: TSymKind, n: PNode, baseName: string): LangSymbol =
915+
## Converts symbol info (names/types/parameters) in `n` into format
916+
## `LangSymbol` convenient for ``rst.nim``/``dochelpers.nim``.
917+
result.name = baseName.nimIdentNormalize
918+
result.symKind = k.toHumanStr
919+
if k in routineKinds:
920+
var
921+
paramTypes: seq[string]
922+
renderParamTypes(paramTypes, n[paramsPos], toNormalize=true)
923+
let paramNames = renderParamNames(n[paramsPos], toNormalize=true)
924+
# In some rare cases (system.typeof) parameter type is not set for default:
925+
doAssert paramTypes.len <= paramNames.len
926+
for i in 0 ..< paramNames.len:
927+
if i < paramTypes.len:
928+
result.parameters.add (paramNames[i], paramTypes[i])
929+
else:
930+
result.parameters.add (paramNames[i], "")
931+
result.parametersProvided = true
932+
933+
result.outType = renderOutType(n[paramsPos], toNormalize=true)
934+
935+
if k in {skProc, skFunc, skType, skIterator}:
936+
# Obtain `result.generics`
937+
# Use `n[miscPos]` since n[genericParamsPos] does not contain constraints
938+
var genNode: PNode = nil
939+
if k == skType:
940+
genNode = n[1] # FIXME: what is index 1?
941+
else:
942+
if n[miscPos].kind != nkEmpty:
943+
genNode = n[miscPos][1] # FIXME: what is index 1?
944+
if genNode != nil:
945+
var literal = ""
946+
var r: TSrcGen
947+
initTokRender(r, genNode, {renderNoBody, renderNoComments,
948+
renderNoPragmas, renderNoProcDefs})
949+
var kind = tkEof
950+
while true:
951+
getNextTok(r, kind, literal)
952+
if kind == tkEof:
953+
break
954+
if kind != tkSpaces:
955+
result.generics.add(literal.nimIdentNormalize)
956+
888957
proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
889958
if (docFlags != kForceExport) and not isVisible(d, nameNode): return
890959
let
@@ -915,6 +984,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
915984
inc(d.id)
916985
let
917986
plainNameEsc = esc(d.target, plainName.strip)
987+
detailedName = k.toHumanStr & " " & (
988+
if k in routineKinds: plainName else: name)
918989
uniqueName = if k in routineKinds: plainNameEsc else: name
919990
sortName = if k in routineKinds: plainName.strip else: name
920991
cleanPlainSymbol = renderPlainSymbolName(nameNode)
@@ -923,20 +994,32 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind, docFlags: DocFlags) =
923994
symbolOrId = d.newUniquePlainSymbol(complexSymbol)
924995
symbolOrIdEnc = encodeUrl(symbolOrId, usePlus = false)
925996
deprecationMsg = genDeprecationMsg(d, pragmaNode)
997+
rstLangSymbol = toLangSymbol(k, n, cleanPlainSymbol)
998+
999+
# we generate anchors automatically for subsequent use in doc comments
1000+
let lineinfo = rstast.TLineInfo(
1001+
line: nameNode.info.line, col: nameNode.info.col,
1002+
fileIndex: addRstFileIndex(d, nameNode.info))
1003+
addAnchorNim(d.sharedState, refn = symbolOrId, tooltip = detailedName,
1004+
rstLangSymbol, priority = symbolPriority(k), info = lineinfo)
9261005

9271006
nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments,
9281007
renderDocComments, renderSyms}, symbolOrIdEnc)
9291008

9301009
let seeSrc = genSeeSrc(d, toFullPath(d.conf, n.info), n.info.line.int)
9311010

932-
d.section[k].secItems.add Item(
1011+
d.section[k].secItems.mgetOrPut(cleanPlainSymbol, newSeq[Item]()).add Item(
9331012
descRst: comm,
9341013
sortName: sortName,
1014+
info: lineinfo,
1015+
anchor: symbolOrId,
1016+
detailedName: detailedName,
1017+
name: name,
9351018
substitutions: @[
936-
"name", name, "uniqueName", uniqueName,
1019+
"uniqueName", uniqueName,
9371020
"header", result, "itemID", $d.id,
9381021
"header_plain", plainNameEsc, "itemSym", cleanPlainSymbol,
939-
"itemSymOrID", symbolOrId, "itemSymEnc", plainSymbolEnc,
1022+
"itemSymEnc", plainSymbolEnc,
9401023
"itemSymOrIDEnc", symbolOrIdEnc, "seeSrc", seeSrc,
9411024
"deprecationMsg", deprecationMsg])
9421025

@@ -1184,6 +1267,11 @@ proc generateDoc*(d: PDoc, n, orig: PNode, docFlags: DocFlags = kDefault) =
11841267
if comm.len != 0: d.modDescPre.add(comm)
11851268
else: discard
11861269

1270+
proc overloadGroupName(s: string, k: TSymKind): string =
1271+
## Turns a name like `f` into anchor `f-procs-all`
1272+
#s & " " & k.toHumanStr & "s all"
1273+
s & "-" & k.toHumanStr & "s-all"
1274+
11871275
proc finishGenerateDoc*(d: var PDoc) =
11881276
## Perform 2nd RST pass for resolution of links/footnotes/headings...
11891277
# copy file map `filenames` to ``rstgen.nim`` for its warnings
@@ -1197,6 +1285,21 @@ proc finishGenerateDoc*(d: var PDoc) =
11971285
break
11981286
preparePass2(d.sharedState, firstRst)
11991287

1288+
# add anchors to overload groups before RST resolution
1289+
for k in TSymKind:
1290+
if k in routineKinds:
1291+
for plainName, overloadChoices in d.section[k].secItems:
1292+
if overloadChoices.len > 1:
1293+
let refn = overloadGroupName(plainName, k)
1294+
let tooltip = "$1 ($2 overloads)" % [
1295+
k.toHumanStr & " " & plainName, $overloadChoices.len]
1296+
addAnchorNim(d.sharedState, refn, tooltip,
1297+
LangSymbol(symKind: k.toHumanStr, name: plainName,
1298+
isGroup: true),
1299+
priority = symbolPriority(k),
1300+
# select index `0` just to have any meaningful warning:
1301+
info = overloadChoices[0].info)
1302+
12001303
# Finalize fragments of ``.nim`` or ``.rst`` file
12011304
proc renderItemPre(d: PDoc, fragments: ItemPre, result: var string) =
12021305
for f in fragments:
@@ -1207,14 +1310,33 @@ proc finishGenerateDoc*(d: var PDoc) =
12071310
of false: result &= f.str
12081311
proc cmp(x, y: Item): int = cmpDecimalsIgnoreCase(x.sortName, y.sortName)
12091312
for k in TSymKind:
1210-
for item in d.section[k].secItems.sorted(cmp):
1211-
var itemDesc: string
1212-
renderItemPre(d, item.descRst, itemDesc)
1213-
d.section[k].finalMarkup.add(
1214-
getConfigVar(d.conf, "doc.item") % (
1215-
item.substitutions & @["desc", itemDesc]))
1216-
itemDesc = ""
1217-
d.section[k].secItems.setLen 0
1313+
# add symbols to section for each `k`, while optionally wrapping
1314+
# overloadable items with the same basic name by ``doc.item2``
1315+
let overloadableNames = toSeq(keys(d.section[k].secItems))
1316+
for plainName in overloadableNames.sorted(cmpDecimalsIgnoreCase):
1317+
var overloadChoices = d.section[k].secItems[plainName]
1318+
overloadChoices.sort(cmp)
1319+
var nameContent = ""
1320+
for item in overloadChoices:
1321+
var itemDesc: string
1322+
renderItemPre(d, item.descRst, itemDesc)
1323+
nameContent.add(
1324+
getConfigVar(d.conf, "doc.item") % (
1325+
item.substitutions & @[
1326+
"desc", itemDesc,
1327+
"name", item.name,
1328+
"itemSymOrID", item.anchor]))
1329+
if k in routineKinds:
1330+
let plainNameEsc1 = esc(d.target, plainName.strip)
1331+
let plainNameEsc2 = esc(d.target, plainName.strip, escMode=emUrl)
1332+
d.section[k].finalMarkup.add(
1333+
getConfigVar(d.conf, "doc.item2") % (
1334+
@["header_plain", plainNameEsc1,
1335+
"overloadGroupName", overloadGroupName(plainNameEsc2, k),
1336+
"content", nameContent]))
1337+
else:
1338+
d.section[k].finalMarkup.add(nameContent)
1339+
d.section[k].secItems.clear
12181340
renderItemPre(d, d.modDescPre, d.modDescFinal)
12191341
d.modDescPre.setLen 0
12201342
d.hasToc = d.hasToc or d.sharedState.hasToc
@@ -1493,7 +1615,7 @@ proc commandRstAux(cache: IdentCache, conf: ConfigRef;
14931615
filename: AbsoluteFile, outExt: string) =
14941616
var filen = addFileExt(filename, "txt")
14951617
var d = newDocumentor(filen, cache, conf, outExt, isPureRst = true)
1496-
let rst = parseRst(readFile(filen.string), filen.string,
1618+
let rst = parseRst(readFile(filen.string),
14971619
line=LineRstInit, column=ColRstInit,
14981620
conf, d.sharedState)
14991621
d.modDescPre = @[ItemFragment(isRst: true, rst: rst)]

compiler/lineinfos.nim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type
5050
warnSmallLshouldNotBeUsed = "SmallLshouldNotBeUsed", warnUnknownMagic = "UnknownMagic",
5151
warnRstRedefinitionOfLabel = "RedefinitionOfLabel",
5252
warnRstUnknownSubstitutionX = "UnknownSubstitutionX",
53+
warnRstAmbiguousLink = "AmbiguousLink",
5354
warnRstBrokenLink = "BrokenLink",
5455
warnRstLanguageXNotSupported = "LanguageXNotSupported",
5556
warnRstFieldXNotSupported = "FieldXNotSupported",
@@ -123,6 +124,7 @@ const
123124
warnUnknownMagic: "unknown magic '$1' might crash the compiler",
124125
warnRstRedefinitionOfLabel: "redefinition of label '$1'",
125126
warnRstUnknownSubstitutionX: "unknown substitution '$1'",
127+
warnRstAmbiguousLink: "ambiguous doc link $1",
126128
warnRstBrokenLink: "broken link '$1'",
127129
warnRstLanguageXNotSupported: "language '$1' not supported",
128130
warnRstFieldXNotSupported: "field '$1' not supported",

0 commit comments

Comments
 (0)