diff options
Diffstat (limited to 'script/cli/doc/export.lua')
-rw-r--r-- | script/cli/doc/export.lua | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/script/cli/doc/export.lua b/script/cli/doc/export.lua new file mode 100644 index 00000000..5a8c3239 --- /dev/null +++ b/script/cli/doc/export.lua @@ -0,0 +1,354 @@ +local ws = require 'workspace' +local vm = require 'vm' +local guide = require 'parser.guide' + +local getDesc = require 'core.hover.description' +local getLabel = require 'core.hover.label' +local jsonb = require 'json-beautify' +local util = require 'utility' +local markdown = require 'provider.markdown' + +---@alias doctype +---| 'doc.alias' +---| 'doc.class' +---| 'doc.field' +---| 'doc.field.name' +---| 'doc.type.arg.name' +---| 'doc.type.function' +---| 'doc.type.table' +---| 'funcargs' +---| 'function' +---| 'function.return' +---| 'global.type' +---| 'global.variable' +---| 'local' +---| 'luals.config' +---| 'self' +---| 'setfield' +---| 'setglobal' +---| 'setindex' +---| 'setmethod' +---| 'tableindex' +---| 'type' + +---@class docUnion broadest possible collection of exported docs, these are never all together. +---@field [1] string in name when table, always the same as view +---@field args docUnion[] list of argument docs passed to function +---@field async boolean has @async tag +---@field defines docUnion[] list of places where this is doc is defined and how its defined there +---@field deprecated boolean has @deprecated tag +---@field desc string code commentary +---@field extends string | docUnion ? what type this 'is'. string:<Parent_Class> for type: 'type', docUnion for type: 'function', string<primative> for other type 's +---@field fields docUnion[] class's fields +---@field file string path to where this token is defined +---@field finish [integer, integer] 0-indexed [line, column] position of end of token +---@field name string canonical name +---@field rawdesc string same as desc, but may have other things for types doc.retun andr doc.param (unused?) +---@field returns docUnion | docUnion[] list of docs for return values. if singluar, then always {type: 'undefined'}? might be a bug. +---@field start [integer, integer] 0-indexed [line, column] position of start of token +---@field type doctype role that this token plays in documentation. different from the 'type'/'class' this token is +---@field types docUnion[] type union? unclear. seems to be related to alias, maybe +---@field view string full method name, class, basal type, or unknown. in name table same as [1] +---@field visible 'package'|'private'|'protected'|'public' visibilty tag + +local export = {} + +function export.getLocalPath(uri) + --remove uri root (and prefix) + local local_file_uri = uri + local i, j = local_file_uri:find(DOC) + if not j then + return '[FORIEGN]'..uri + end + return local_file_uri:sub( j + 1 ) +end + +function export.positionOf(rowcol) + return type(rowcol) == 'table' and guide.positionOf(rowcol[1], rowcol[2]) or -1 +end + +function export.sortDoc(a,b) + if a.name ~= b.name then + return a.name < b.name + end + + if a.file ~= b.file then + return a.file < b.file + end + + return export.positionOf(a.start) < export.positionOf(b.start) +end + + +--- recursively generate documentation all parser objects downstream of `source` +---@async +---@param source parser.object | vm.global +---@param has_seen table? keeps track of visited nodes in documentation tree +---@return docUnion | [docUnion] | string | number | boolean | nil +function export.documentObject(source, has_seen) + --is this a primative type? then we dont need to process it. + if type(source) ~= 'table' then return source end + + --set up/check recursion + if not has_seen then has_seen = {} end + if has_seen[source] then + return nil + end + has_seen[source] = true + + --is this an array type? then process each array item and collect it + if (#source > 0 and next(source, #source) == nil) then + local objs = {} --make a pure numerical array + for i, child in ipairs(source) do + objs[i] = export.documentObject(child, has_seen) + end + return objs + end + + --if neither, then this is a singular docUnion + local obj = export.makeDocObject['INIT'](source, has_seen) + + --check if this source has a type (no type sources are usually autogen'd anon functions's return values that are not explicitly stated) + if not obj.type then return obj end + + local res = export.makeDocObject[obj.type](source, obj, has_seen) + if res == false then + return nil + end + return res or obj +end + +---Switch statement table. functions can be overriden by user file. +---@table +export.makeDocObject = setmetatable({}, {__index = function(t, k) + return function() + --print('DocError: no type "'..k..'"') + end +end}) + +export.makeDocObject['INIT'] = function(source, has_seen) + ---@as docUnion + local ok, desc = pcall(getDesc, source) + local rawok, rawdesc = pcall(getDesc, source, true) + return { + type = source.cate or source.type, + name = export.documentObject((source.getCodeName and source:getCodeName()) or source.name, has_seen), + start = source.start and {guide.rowColOf(source.start)}, + finish = source.finish and {guide.rowColOf(source.finish)}, + types = export.documentObject(source.types, has_seen), + view = vm.getInfer(source):view(ws.rootUri), + desc = ok and desc or nil, + rawdesc = rawok and rawdesc or nil, + } +end + +export.makeDocObject['doc.alias'] = function(source, obj, has_seen) + +end + +export.makeDocObject['doc.field'] = function(source, obj, has_seen) + if source.field.type == 'doc.field.name' then + obj.name = source.field[1] + else + obj.name = ('[%s]'):format(vm.getInfer(source.field):view(ws.rootUri)) + end + obj.file = export.getLocalPath(guide.getUri(source)) + obj.extends = source.extends and export.documentObject(source.extends, has_seen) --check if bug? + obj.async = vm.isAsync(source, true) and true or false --if vm.isAsync(set, true) then result.defines[#result.defines].extends['async'] = true end + obj.deprecated = vm.getDeprecated(source) and true or false -- if (depr and not depr.versions) the result.defines[#result.defines].extends['deprecated'] = true end + obj.visible = vm.getVisibleType(source) +end + +export.makeDocObject['doc.class'] = function(source, obj, has_seen) + local extends = source.extends or source.value --doc.class or other + local field = source.field or source.method + obj.name = type(field) == 'table' and field[1] or nil + obj.file = export.getLocalPath(guide.getUri(source)) + obj.extends = extends and export.documentObject(extends, has_seen) + obj.async = vm.isAsync(source, true) and true or false + obj.deprecated = vm.getDeprecated(source) and true or false + obj.visible = vm.getVisibleType(source) +end + +export.makeDocObject['doc.field.name'] = function(source, obj, has_seen) + obj['[1]'] = export.documentObject(source[1], has_seen) + obj.view = source[1] +end + +export.makeDocObject['doc.type.arg.name'] = export.makeDocObject['doc.field.name'] + +export.makeDocObject['doc.type.function'] = function(source, obj, has_seen) + obj.args = export.documentObject(source.args, has_seen) + obj.returns = export.documentObject(source.returns, has_seen) +end + +export.makeDocObject['doc.type.table'] = function(source, obj, has_seen) + obj.fields = export.documentObject(source.fields, has_seen) +end + +export.makeDocObject['funcargs'] = function(source, obj, has_seen) + local objs = {} --make a pure numerical array + for i, child in ipairs(source) do + objs[i] = export.documentObject(child, has_seen) + end + return objs +end + +export.makeDocObject['function'] = function(source, obj, has_seen) + obj.args = export.documentObject(source.args, has_seen) + obj.view = getLabel(source, source.parent.type == 'setmethod') + local _, _, max = vm.countReturnsOfFunction(source) + if max > 0 then obj.returns = {} end + for i = 1, max do + obj.returns[i] = export.documentObject(vm.getReturnOfFunction(source, i), has_seen) --check if bug? + end +end + +export.makeDocObject['function.return'] = function(source, obj, has_seen) + obj.desc = source.comment and getDesc(source.comment) + obj.rawdesc = source.comment and getDesc(source.comment, true) +end + +export.makeDocObject['local'] = function(source, obj, has_seen) + obj.name = source[1] +end + +export.makeDocObject['luals.config'] = function(source, obj, has_seen) + +end + +export.makeDocObject['self'] = export.makeDocObject['local'] + +export.makeDocObject['setfield'] = export.makeDocObject['doc.class'] + +export.makeDocObject['setglobal'] = export.makeDocObject['doc.class'] + +export.makeDocObject['setindex'] = export.makeDocObject['doc.class'] + +export.makeDocObject['setmethod'] = export.makeDocObject['doc.class'] + +export.makeDocObject['tableindex'] = function(source, obj, has_seen) + obj.name = source.index[1] +end + +export.makeDocObject['type'] = function(source, obj, has_seen) + if export.makeDocObject['variable'](source, obj, has_seen) == false then + return false + end + obj.fields = {} + vm.getClassFields(ws.rootUri, source, vm.ANY, function (next_source, mark) + if next_source.type == 'doc.field' + or next_source.type == 'setfield' + or next_source.type == 'setmethod' + or next_source.type == 'tableindex' + then + table.insert(obj.fields, export.documentObject(next_source, has_seen)) + end + end) + table.sort(obj.fields, export.sortDoc) +end + +export.makeDocObject['variable'] = function(source, obj, has_seen) + obj.defines = {} + for _, set in ipairs(source:getSets(ws.rootUri)) do + if set.type == 'setglobal' + or set.type == 'setfield' + or set.type == 'setmethod' + or set.type == 'setindex' + or set.type == 'doc.alias' + or set.type == 'doc.class' + then + table.insert(obj.defines, export.documentObject(set, has_seen)) + end + end + if #obj.defines == 0 then return false end + table.sort(obj.defines, export.sortDoc) +end + +---gathers the globals that are to be exported in documentation +---@async +---@return table globals +function export.gatherGlobals() + local all_globals = vm.getAllGlobals() + local globals = {} + for _, g in pairs(all_globals) do + table.insert(globals, g) + end + return globals +end + +---builds a lua table of based on `globals` and their elements +---@async +---@param globals table +---@param callback fun(i, max) +function export.makeDocs(globals, callback) + local docs = {} + + for i, global in ipairs(globals) do + table.insert(docs, export.documentObject(global)) + callback(i, #globals) + end + + table.sort(docs, export.sortDoc) + + return docs +end + +---takes the table from `makeDocs`, serializes it, and exports it +---@async +---@param docs table +---@param outputDir string +---@return boolean ok, string[] outputPaths, (string|nil)[]? errs +function export.serializeAndExport(docs, outputDir) + local jsonPath = outputDir .. '/doc.json' + local mdPath = outputDir .. '/doc.md' + + --export to json + local old_jsonb_supportSparseArray = jsonb.supportSparseArray + jsonb.supportSparseArray = true + local jsonOk, jsonErr = util.saveFile(jsonPath, jsonb.beautify(docs)) + jsonb.supportSparseArray = old_jsonb_supportSparseArray + + + --export to markdown + local md = markdown() + for _, class in ipairs(docs) do + md:add('md', '# ' .. class.name) + md:emptyLine() + md:add('md', class.desc) + md:emptyLine() + if class.defines then + for _, define in ipairs(class.defines) do + if define.extends then + md:add('lua', define.extends.view) + md:emptyLine() + end + end + end + if class.fields then + local mark = {} + for _, field in ipairs(class.fields) do + if not mark[field.name] then + mark[field.name] = true + md:add('md', '## ' .. field.name) + md:emptyLine() + md:add('lua', field.extends.view) + md:emptyLine() + md:add('md', field.desc) + md:emptyLine() + end + end + end + md:splitLine() + end + local mdOk, mdErr = util.saveFile(mdPath, md:string()) + + --error checking save file + if( not (jsonOk and mdOk) ) then + return false, {jsonPath, mdPath}, {jsonErr, mdErr} + end + + return true, {jsonPath, mdPath} +end + +return export
\ No newline at end of file |