ldoc
ldoc copied to clipboard
Add tags from JSDoc
Inspired by something I stumbled on - https://dev.fandom.com/wiki/Global_Lua_Modules/Docbunto#Tags
Tags that could be added:
@member/@propertyfor class members@variablefor local variables (accepts type)@notedeveloper notes@imagefor adding an image to a doc item@examplefor example files@filefor non executable generics or datastores@requirefor dependencies
Features to add:
@errorline number can be specified by [number] affixed to the tag.
@WikiaUsers a patch to expose flag tags and improve exports in your documentation, probably broken:
diff --git a/docbunto.lua b/docbunto.lua
index 246fee0..2ec3252 100644
--- a/docbunto.lua
+++ b/docbunto.lua
@@ -3,17 +3,17 @@
-- the form of MediaWiki markup, using `@tag`-prefixed comments embedded
-- in the source code of a Scribunto module. The taglet parser & doclet
-- renderer Docbunto uses are also publicly exposed to other modules.
---
+--
-- Docbunto code items are introduced by a block comment (`--[[]]--`), an
-- inline comment with three hyphens (`---`), or a inline `@tag` comment.
-- The module can use static code analysis to infer variable names, item
-- privacy (`local` keyword), tables (`{}` constructor) and functions
-- (`function` keyword). MediaWiki and Markdown formatting is supported.
---
+--
-- Items are usually rendered in the order they are defined, if they are
-- public items, or emulated classes extending the Lua primitives. There
-- are many customisation options available to change Docbunto behaviour.
---
+--
-- @module docbunto
-- @alias p
-- @require Module:I18n
@@ -329,14 +328,14 @@ end
--- Item export utility.
-- @function export_item
-- @param {table} documentation Package documentation data.
--- @param {string} name Identifier name for item.
--- @param {string} item_no Identifier name for item.
--- @param {string} alias Export alias for item.
--- @param {boolean} factory Whether the documentation item is a factory function.
+-- @param {string} item_reference Identifier name for item.
+-- @param {string} item_index Identifier name for item.
+-- @param {string} item_alias Export alias for item.
+-- @param {boolean} factory_item Whether the documentation item is a factory function.
-- @local
-local function export_item(documentation, name, item_no, alias, factory)
+local function export_item(documentation, item_reference, item_index, item_alias, factory_item)
for _, item in ipairs(documentation.items) do
- if name == item.name then
+ if item_reference == item.name then
item.tags['local'] = nil
item.tags['private'] = nil
@@ -348,17 +347,17 @@ local function export_item(documentation, name, item_no, alias, factory)
item.type = item.type:gsub('variable', 'member')
- if factory then
+ if factory_item then
item.alias =
- documentation.items[item_no].tags['factory'].value ..
- (alias:find('^%[') and '' or (not item.tags['static'] and ':' or '.')) ..
- alias
+ documentation.items[item_index].tags['factory'].value ..
+ (item_alias:find('^%[') and '' or (not item.tags['static'] and ':' or '.')) ..
+ item_alias
else
item.alias =
((documentation.tags['alias'] or {}).value or documentation.name) ..
- (alias:find('^%[') and '' or (documentation.type == 'classmod' and not item.tags['static'] and ':' or '.')) ..
- alias
+ (item_alias:find('^%[') and '' or (documentation.type == 'classmod' and not item.tags['static'] and ':' or '.')) ..
+ item_alias
end
item.hierarchy = mw.text.split((item.alias:gsub('["\']?%]', '')), '[.:%[\'""]+')
@@ -564,19 +563,28 @@ local function render_item(stream, item, options, preop)
if preop then preop(item, options) end
local item_name = item.alias or item.name
+ local item_type = item.type
+
+ for _, name in ipairs(p.tags._subtype_hierarchy) do
+ if item.tags[name] then
+ item_type = item_type .. i18n:msg('separator-dot') .. name
+ end
+ end
+ item_type = i18n:msg('parentheses', item_type)
+
if options.strip and item.export and item.hierarchy then
item_name = item_name:gsub('^[%w_]+[.[]?', '')
end
type_reference(item, options)
- stream:wikitext(';<code id="' .. item_id .. '">' .. item_name .. '</code>' .. i18n:msg('parentheses', item.type)):newline()
+ stream:wikitext(';<code id="' .. item_id .. '">' .. item_name .. '</code>' .. item_type):newline()
if (#(item.summary or '') + #item.description) ~= 0 then
- local sep = #(item.summary or '') ~= 0 and #item.description ~= 0
+ local separator = #(item.summary or '') ~= 0 and #item.description ~= 0
and (item.description:find('^[{:#*]+%s+') and '\n' or ' ')
or ''
- local intro = (item.summary or '') .. sep .. item.description
+ local intro = (item.summary or '') .. separator .. item.description
stream:wikitext(':' .. intro:gsub('\n([{:#*])', '\n:%1'):gsub('\n\n([^=])', '\n:%1')):newline()
end
end
@@ -636,7 +644,7 @@ local function render_tag(stream, name, tag, options, preop)
if tag_el.value:find('\n[{:#*]') and (tag_el.type or (tag_el.modifiers or {})['opt']) then
stream:newline():wikitext(':' .. (options.ulist and '*' or ':') .. (tag_el.value:match('^[*:]+') or ''))
end
-
+
if tag_el.type and (tag_el.modifiers or {})['opt'] then
stream:wikitext(i18n:msg{
key = 'parentheses',
@@ -966,7 +974,7 @@ function p.taglet(modname, options)
documentation.tags = {}
documentation.items = {}
local line_no = 0
- local item_no = 0
+ local item_index = 0
-- Taglet tracking variables.
local start_mode = true
@@ -1008,7 +1016,7 @@ function p.taglet(modname, options)
pragma_mode = tag_name == 'pragma'
export_mode = tag_name == 'export'
special_tag = pragma_mode or export_mode
- local tags, subtokens, sep
+ local tags, subtokens, separator
-- Line counter.
if t.posFirst == 1 then
@@ -1024,10 +1032,10 @@ function p.taglet(modname, options)
table.insert(documentation.comments, t.data)
if comment_mode and not new_tag and not doctag_mode and not comment_brace and not pretty_comment then
- sep = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
+ separator = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
and '\n'
or (#documentation.description ~= 0 and DOCBUNTO_CONCAT or '')
- documentation.description = documentation.description .. sep .. mw.text.trim(comment_tail)
+ documentation.description = documentation.description .. separator .. mw.text.trim(comment_tail)
end
if new_tag and not special_tag then
@@ -1037,10 +1045,10 @@ function p.taglet(modname, options)
elseif doctag_mode and not comment_brace and not pretty_comment then
tags = documentation.tags
if p.tags[tags[#tags].name] == TAG_MULTI then
- sep = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
+ separator = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
and '\n'
or DOCBUNTO_CONCAT
- tags[#tags].value = tags[#tags].value .. sep .. mw.text.trim(comment_tail)
+ tags[#tags].value = tags[#tags].value .. separator .. mw.text.trim(comment_tail)
elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then
tags[#tags].value = tags[#tags].value .. '\n' .. comment_tail
end
@@ -1050,49 +1058,49 @@ function p.taglet(modname, options)
-- Documentation item detection.
if not start_mode and (new_item or (new_tag and tokens[i - 1].type ~= 'comment')) and not special_tag then
table.insert(documentation.items, {})
- item_no = item_no + 1
- documentation.items[item_no].lineno = line_no
- documentation.items[item_no].code = ''
- documentation.items[item_no].comments = {}
- documentation.items[item_no].description = ''
- documentation.items[item_no].tags = {}
+ item_index = item_index + 1
+ documentation.items[item_index].lineno = line_no
+ documentation.items[item_index].code = ''
+ documentation.items[item_index].comments = {}
+ documentation.items[item_index].description = ''
+ documentation.items[item_index].tags = {}
end
if not start_mode and comment_mode and not new_tag and not doctag_mode and not comment_brace and not pretty_comment then
- sep = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
+ separator = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
and '\n'
- or (#documentation.items[item_no].description ~= 0 and DOCBUNTO_CONCAT or '')
- documentation.items[item_no].description =
- documentation.items[item_no].description ..
- sep ..
+ or (#documentation.items[item_index].description ~= 0 and DOCBUNTO_CONCAT or '')
+ documentation.items[item_index].description =
+ documentation.items[item_index].description ..
+ separator ..
mw.text.trim(comment_tail)
end
if not start_mode and new_tag and not special_tag then
doctag_mode = true
- table.insert(documentation.items[item_no].tags, process_tag(comment_tail))
+ table.insert(documentation.items[item_index].tags, process_tag(comment_tail))
elseif not start_mode and doctag_mode and not comment_brace and not pretty_comment then
- tags = documentation.items[item_no].tags
+ tags = documentation.items[item_index].tags
if p.tags[tags[#tags].name] == TAG_MULTI then
- sep = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
+ separator = mw.text.trim(comment_tail):find('^[{|!}:#*=]+[%s-}]+')
and '\n'
or DOCBUNTO_CONCAT
- tags[#tags].value = tags[#tags].value .. sep .. mw.text.trim(comment_tail)
+ tags[#tags].value = tags[#tags].value .. separator .. mw.text.trim(comment_tail)
elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then
tags[#tags].value = tags[#tags].value .. '\n' .. comment_tail
end
end
if not start_mode and (comment_mode or doctag_mode) then
- table.insert(documentation.items[item_no].comments, t.data)
+ table.insert(documentation.items[item_index].comments, t.data)
end
-- Export tag support.
if export_mode then
factory_mode = t.posFirst ~= 1
if factory_mode then
- documentation.items[item_no].exports = true
+ documentation.items[item_index].exports = true
else
documentation.exports = true
end
@@ -1100,8 +1108,8 @@ function p.taglet(modname, options)
subtokens = {}
while t and (not factory_mode or (factory_mode and t.data ~= 'end')) do
if factory_mode then
- documentation.items[item_no].code =
- documentation.items[item_no].code ..
+ documentation.items[item_index].code =
+ documentation.items[item_index].code ..
(t.posFirst == 1 and '\n' or '') ..
t.data
end
@@ -1114,33 +1122,33 @@ function p.taglet(modname, options)
end
end
- local sep = {
- ['{'] = true, ['}'] = true;
- [','] = true, [';'] = true;
- }
- local increment = 0
- for index, subtoken in ipairs(subtokens) do
- if
- subtoken.type == 'ident' and
- sep[subtokens[index + 1].data] and
- (subtokens[index - 1].data == '=' or sep[subtokens[index - 1].data])
- then
- local t2, i2, alias = subtoken, index, ''
- if subtokens[index - 1].data == '=' then
- t2, i2 = subtokens[i2 - 2], i2 - 2
+ local separator = { [','] = true, [';'] = true }
+ local brace = { ['{'] = true, ['}'] = true }
+
+ local item_reference, item_alias = '', ''
+ local sequence_index, has_key = 0, false
+ local subtoken, index, terminating_index = subtokens[2], 2, #subtokens - 1
+
+ while not brace[subtoken.data] do
+ if subtoken.data == '=' then
+ has_key = true
+ item_reference, item_alias = item_alias, item_reference
+ elseif not separator[subtoken.data] then
+ if has_key then
+ item_reference = item_reference .. subtoken.data
+ else
+ item_alias = item_alias .. subtoken.data
end
- if not sep[subtokens[index - 1].data] then
- while not sep[t2.data] do
- alias = t2.data .. alias
- t2, i2 = subtokens[i2 - 1], i2 - 1
- end
- end
- if #alias == 0 then
+ elseif separator[subtoken.data] or index == terminating_index then
+ if not has_key then
increment = increment + 1
+ item_reference, item_alias = item_alias, item_reference
alias = '[' .. tostring(increment) .. ']'
end
- export_item(documentation, subtoken.data, item_no, alias, factory_mode)
+ export_item(documentation, item_reference, item_index, item_alias, factory_mode)
+ item_reference, item_alias, has_key = '', '', false
end
+ subtoken, index = subtokens[index + 1], index + 1
end
if not factory_mode then
@@ -1182,15 +1190,15 @@ function p.taglet(modname, options)
end
-- Item data post-processing.
- if item_no ~= 0 then
- documentation.items[item_no].tags = hash_map(documentation.items[item_no].tags)
- documentation.items[item_no].name = extract_name(documentation.items[item_no])
- documentation.items[item_no].type = extract_type(documentation.items[item_no])
- if #documentation.items[item_no].description ~= 0 then
- documentation.items[item_no].summary = match(documentation.items[item_no].description, DOCBUNTO_SUMMARY)
- documentation.items[item_no].description = gsub(documentation.items[item_no].description, DOCBUNTO_SUMMARY .. '%s*', '')
+ if item_index ~= 0 then
+ documentation.items[item_index].tags = hash_map(documentation.items[item_index].tags)
+ documentation.items[item_index].name = extract_name(documentation.items[item_index])
+ documentation.items[item_index].type = extract_type(documentation.items[item_index])
+ if #documentation.items[item_index].description ~= 0 then
+ documentation.items[item_index].summary = match(documentation.items[item_index].description, DOCBUNTO_SUMMARY)
+ documentation.items[item_index].description = gsub(documentation.items[item_index].description, DOCBUNTO_SUMMARY .. '%s*', '')
end
- documentation.items[item_no].description = documentation.items[item_no].description:gsub('%s%s+', '\n\n')
+ documentation.items[item_index].description = documentation.items[item_index].description:gsub('%s%s+', '\n\n')
new_item_code = true
end
@@ -1208,12 +1216,12 @@ function p.taglet(modname, options)
end
-- Item code concatenation.
- if item_no ~= 0 and not doctag_mode and not comment_mode and not return_mode then
- sep = #documentation.items[item_no].code ~= 0 and t.posFirst == 1 and '\n' or ''
- documentation.items[item_no].code = documentation.items[item_no].code .. sep .. t.data
+ if item_index ~= 0 and not doctag_mode and not comment_mode and not return_mode then
+ separator = #documentation.items[item_index].code ~= 0 and t.posFirst == 1 and '\n' or ''
+ documentation.items[item_index].code = documentation.items[item_index].code .. separator .. t.data
-- Code analysis on item head.
- if new_item_code and documentation.items[item_no].code:find('\n') then
- code_static_analysis(documentation.items[item_no])
+ if new_item_code and documentation.items[item_index].code:find('\n') then
+ code_static_analysis(documentation.items[item_index])
new_item_code = false
end
end
@@ -1385,10 +1393,10 @@ function p.doclet(data, options)
-- Documentation lede.
if not options.code and (#(data.summary or '') + #data.description) ~= 0 then
- local sep = #data.summary ~= 0 and #data.description ~= 0
+ local separator = #data.summary ~= 0 and #data.description ~= 0
and (data.description:find('^[{|!}:#*=]+[%s-}]+') and '\n\n' or ' ')
or ''
- local intro = (data.summary or '') .. sep .. data.description
+ local intro = (data.summary or '') .. separator .. data.description
intro = frame:preprocess(maybe_md(intro:gsub('^(' .. codepage .. ')', '<b>%1</b>')))
documentation:wikitext(intro):newline():newline()
end
@@ -1462,10 +1470,10 @@ function p.doclet(data, options)
elseif item.type == 'type' then
codedoc:wikitext('=== <code>' .. (item.alias or item.name) .. '</code> ==='):newline()
if (#(item.summary or '') + #item.description) ~= 0 then
- local sep = #(item.summary or '') ~= 0 and #item.description ~= 0
+ local separator = #(item.summary or '') ~= 0 and #item.description ~= 0
and (item.description:find('^[{:#*=]+[%s-}]+') and '\n\n' or ' ')
or ''
- codedoc:wikitext((item.summary or '') .. sep .. item.description):newline()
+ codedoc:wikitext((item.summary or '') .. separator .. item.description):newline()
end
elseif item.type == 'function' then
@@ -1482,10 +1490,9 @@ function p.doclet(data, options)
elseif
item.type == 'table' or
- item.type ~= nil and (
- item.type:find('^member') or
- item.type:find('^variable')
- )
+ item.type:find('^member') or
+ item.type:find('^variable')
+
then
render_item(codedoc, item, options)
if not options.simple and item.tags['field'] then
@@ -1662,5 +1669,19 @@ p.tags._generic_tags = {
['variable'] = true,
['member'] = true
}
+p.tags._subtype_tags = {
+ ['factory'] = true,
+ ['local'] = true,
+ ['private'] = true,
+ ['constructor'] = true,
+ ['static'] = true
+}
+p.tags._subtype_hierarchy = {
+ 'private',
+ 'local',
+ 'static',
+ 'factory',
+ 'constructor'
+}
return p