ldoc icon indicating copy to clipboard operation
ldoc copied to clipboard

Add tags from JSDoc

Open serisitas opened this issue 4 years ago • 1 comments

Inspired by something I stumbled on - https://dev.fandom.com/wiki/Global_Lua_Modules/Docbunto#Tags

Tags that could be added:

  • @member/@property for class members
  • @variable for local variables (accepts type)
  • @note developer notes
  • @image for adding an image to a doc item
  • @example for example files
  • @file for non executable generics or datastores
  • @require for dependencies

Features to add:

  • @error line number can be specified by [number] affixed to the tag.

serisitas avatar Aug 19 '21 11:08 serisitas

@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

serisitas avatar Aug 27 '21 16:08 serisitas