Browse Source

typescript generator

Kira Boom 2 days ago
parent
commit
6967ed1f30
1 changed files with 480 additions and 0 deletions
  1. 480 0
      api/generators/typescript.lua

+ 480 - 0
api/generators/typescript.lua

@@ -0,0 +1,480 @@
+-- done:
+--
+-- - callbacks
+-- - module functions
+-- - object methods
+-- - rest parameters
+-- - uses of the name 'default'
+-- - uses of the name '*'
+-- - uses of the type '*'
+-- - rest returns
+-- - interface inheritance
+-- - arguments with defaults are optional
+-- - vector operators
+-- - vec3.up etc
+-- - table arguments with specified fields
+-- - fn variant documentation
+-- - type and interface documentation
+--
+-- todo:
+--
+-- - show call example in documentation (e.g. `const [x, y, z] = collider.getPosition()`)
+-- - lovr.graphics.getDevice() more specific return value and documentation
+--   (available in .table field of return value)
+-- - global vector constructors documentation
+-- - LuaTable specializations (e.g. arrays)
+-- - enet
+-- - http
+--
+-- some recommended changes to lovr-docs i found while working on this:
+--
+-- - mark draw return value as optional somehow?
+-- - make pass.setColor() explicitly accept vec3/vec4
+
+-- todo: the global constructors should be automated in order to include documentation
+local vector_ops = [[
+/** @noSelf **/ declare function vec2(x?: number, y?: number): Vec2
+/** @noSelf **/ declare function vec2(u: Vec2): Vec2
+/** @noSelf **/ declare function Vec2(x?: number, y?: number): Vec2
+/** @noSelf **/ declare function Vec2(u: Vec2): Vec2
+
+declare namespace vec2 {
+  const zero: Vec2
+  const one: Vec2
+}
+
+/** @noSelf **/ declare function vec3(x?: number, y?: number, z?: number): Vec3
+/** @noSelf **/ declare function vec3(u: Vec3): Vec3
+/** @noSelf **/ declare function vec3(m: Mat4): Vec3
+/** @noSelf **/ declare function vec3(q: Quat): Vec3
+/** @noSelf **/ declare function Vec3(x?: number, y?: number, z?: number): Vec3
+/** @noSelf **/ declare function Vec3(u: Vec3): Vec3
+/** @noSelf **/ declare function Vec3(m: Mat4): Vec3
+/** @noSelf **/ declare function Vec3(q: Quat): Vec3
+
+declare namespace vec3 {
+  const zero: Vec3
+  const one: Vec3
+  const left: Vec3
+  const right: Vec3
+  const up: Vec3
+  const down: Vec3
+  const back: Vec3
+  const forward: Vec3
+}
+
+/** @noSelf **/ declare function vec4(x?: number, y?: number, z?: number, w?: number): Vec4
+/** @noSelf **/ declare function vec4(u: Vec4): Vec4
+/** @noSelf **/ declare function Vec4(x?: number, y?: number, z?: number, w?: number): Vec4
+/** @noSelf **/ declare function Vec4(u: Vec4): Vec4
+
+declare namespace vec4 {
+  const zero: Vec4
+  const one: Vec4
+}
+
+/** @noSelf **/ declare function quat(angle?: number, ax?: number, ay?: number, az?: number, raw?: boolean): Quat
+/** @noSelf **/ declare function quat(r: Quat): Quat
+/** @noSelf **/ declare function quat(v: Vec3): Quat
+/** @noSelf **/ declare function quat(v: Vec3, u: Vec3): Quat
+/** @noSelf **/ declare function quat(m: Mat4): Quat
+/** @noSelf **/ declare function quat(): Quat
+/** @noSelf **/ declare function Quat(angle?: number, ax?: number, ay?: number, az?: number, raw?: boolean): Quat
+/** @noSelf **/ declare function Quat(r: Quat): Quat
+/** @noSelf **/ declare function Quat(v: Vec3): Quat
+/** @noSelf **/ declare function Quat(v: Vec3, u: Vec3): Quat
+/** @noSelf **/ declare function Quat(m: Mat4): Quat
+/** @noSelf **/ declare function Quat(): Quat
+
+declare namespace quat {
+  const identity: Quat
+}
+
+/** @noSelf **/ declare function mat4(): Mat4
+/** @noSelf **/ declare function mat4(n: Mat4): Mat4
+/** @noSelf **/ declare function mat4(position?: Vec3, scale?: Vec3, rotation?: Quat): Mat4
+/** @noSelf **/ declare function mat4(position?: Vec3, rotation?: Quat): Mat4
+/** @noSelf **/ declare function mat4(...rest: number[]): Mat4
+/** @noSelf **/ declare function mat4(d: number): Mat4
+/** @noSelf **/ declare function Mat4(): Mat4
+/** @noSelf **/ declare function Mat4(n: Mat4): Mat4
+/** @noSelf **/ declare function Mat4(position?: Vec3, scale?: Vec3, rotation?: Quat): Mat4
+/** @noSelf **/ declare function Mat4(position?: Vec3, rotation?: Quat): Mat4
+/** @noSelf **/ declare function Mat4(...rest: number[]): Mat4
+/** @noSelf **/ declare function Mat4(d: number): Mat4
+
+declare interface Vec2 {
+  add_temp: LuaAdditionMethod<Vec2, Vec2>
+  sub_temp: LuaSubtractionMethod<Vec2, Vec2>
+  mul_temp: LuaMultiplicationMethod<Vec2 | number, Vec2>
+  div_temp: LuaDivisionMethod<Vec2 | number, Vec2>
+
+  1: number
+  2: number
+
+  x: number
+  y: number
+
+  r: number
+  g: number
+
+  s: number
+  t: number
+}
+
+declare interface Vec3 {
+  add_temp: LuaAdditionMethod<Vec3, Vec3>
+  sub_temp: LuaSubtractionMethod<Vec3, Vec3>
+  mul_temp: LuaMultiplicationMethod<Vec3 | number, Vec3>
+  div_temp: LuaDivisionMethod<Vec3 | number, Vec3>
+
+  1: number
+  2: number
+  3: number
+
+  x: number
+  y: number
+  z: number
+
+  r: number
+  g: number
+  b: number
+
+  s: number
+  t: number
+  p: number
+}
+
+declare interface Vec4 {
+  add_temp: LuaAdditionMethod<Vec4, Vec4>
+  sub_temp: LuaSubtractionMethod<Vec4, Vec4>
+  mul_temp: LuaMultiplicationMethod<Vec4 | number, Vec4>
+  div_temp: LuaDivisionMethod<Vec4 | number, Vec4>
+
+  1: number
+  2: number
+  3: number
+  4: number
+
+  x: number
+  y: number
+  z: number
+  w: number
+
+  r: number
+  g: number
+  b: number
+  a: number
+
+  s: number
+  t: number
+  p: number
+  q: number
+}
+
+declare interface Quat {
+  add_temp: LuaAdditionMethod<Quat, Quat>
+  sub_temp: LuaSubtractionMethod<Quat, Quat>
+  mul_temp: LuaMultiplicationMethod<Quat, Quat> & LuaMultiplicationMethod<Vec3, Vec3>
+  div_temp: LuaDivisionMethod<Quat, Quat>
+
+  1: number
+  2: number
+  3: number
+  4: number
+
+  x: number
+  y: number
+  z: number
+  w: number
+}
+
+declare interface Mat4 {
+  add_temp: LuaAdditionMethod<Mat4, Mat4>
+  sub_temp: LuaSubtractionMethod<Mat4, Mat4>
+  mul_temp: LuaMultiplicationMethod<Mat4 | number, Mat4> & LuaMultiplicationMethod<Vec3, Vec3> & LuaMultiplicationMethod<Vec4, Vec4>
+  div_temp: LuaDivisionMethod<Mat4 | number, Mat4>
+
+  1: number
+  2: number
+  3: number
+  4: number
+  5: number
+  6: number
+  7: number
+  8: number
+  9: number
+  10: number
+  11: number
+  12: number
+  13: number
+  14: number
+  15: number
+  16: number
+}
+]]
+
+return function (api)
+  local path = lovr.filesystem.getSource() .. '/typescript'
+  os.execute("mkdir -p " .. path)
+  local out = io.open(path .. '/lovr-api.d.ts', 'w')
+  assert(out)
+
+  local function put (...)
+    out:write(...)
+  end
+
+  local indentation = ''
+
+  local function indent()
+    indentation = (' '):rep(#indentation + 2)
+  end
+
+  local function unindent()
+    indentation = (' '):rep(#indentation - 2)
+  end
+
+  local function quote_string (str)
+    return "'" .. str:gsub('\\', '\\\\'):gsub("'", "\\'") .. "'"
+  end
+
+  local function put_doc(doc)
+    if doc:match('\n') then
+      put(indentation, '/**\n',
+        indentation, ' * ', doc:gsub('\n', '\n' .. indentation .. ' * '), '\n',
+        indentation, ' */\n')
+    else
+      put(indentation, '/** ', doc, ' */\n')
+    end
+  end
+
+  -- todo: more specific types
+  local type_map = {
+    table = 'LuaTable',
+    userdata = 'any',
+    lightuserdata = 'any',
+    ['function'] = '(...args: any[]) => any',
+    ['*'] = 'any',
+    ['Object'] = 'LovrObject'
+  }
+
+  local convert_type
+
+  local function convert_table (table_fields)
+    local result = '{ '
+    for _, field in ipairs (table_fields) do
+      local optional = field.default and '?' or ''
+      result = result .. field.name .. optional .. ': ' .. convert_type(field.type, field.table) .. ', '
+    end
+    return result .. '}'
+  end
+
+  function convert_type (t, table_fields)
+    if t == 'table' and table_fields then
+      return convert_table (table_fields)
+    else
+      return type_map[t] or t
+    end
+  end
+
+  local name_map = {
+    ['*'] = 'rest',
+  }
+
+  local function convert_name (n)
+    return name_map[n] or n
+  end
+
+  local function convert_param (arg, optional)
+    local name = convert_name(arg.name)
+    local t = convert_type(arg.type, arg.table)
+    local pattern = '%.%.%.'
+    if name:match(pattern) then
+      name = '...' .. name:gsub(pattern, '')
+      if name == '...' then
+        name = name .. 'rest'
+      end
+      t = t .. '[]'
+    end
+    return name .. (optional and '?' or '') .. ': ' .. t
+  end
+
+  local function convert_return_values (returns)
+    local ret = 'void'
+    if #returns == 1 and (not returns[1].name:match('%.%.%.')) then
+      ret = convert_type(returns[1].type)
+    elseif #returns >= 1 then
+      ret = 'LuaMultiReturn<['
+      for n, ret_n in ipairs (returns) do
+        local name = convert_name(ret_n.name)
+        local t = convert_type(ret_n.type)
+        if name:match('%.%.%.') then
+          if name == '...' then
+            name = '...rest'
+          end
+          t = t .. '[]'
+        end
+        ret = ret .. name .. ': ' .. t
+        if n < #returns then
+          ret = ret .. ', '
+        end
+      end
+      ret = ret .. ']>'
+    end
+    return ret
+  end
+
+  local function put_fn_variant (fn, variant, is_interface)
+    -- docs
+    local doc = fn.description
+    if variant.description then
+      doc = doc .. '\n\n' .. variant.description
+    end
+    -- todo: show call example
+    if #variant.arguments > 0 or #variant.returns > 0 then
+      doc = doc .. '\n'
+      if #variant.arguments > 0 then
+        for _, arg in ipairs(variant.arguments) do
+          doc = doc .. '\n@param ' .. arg.name .. ' - ' .. arg.description
+        end
+      end
+      if #variant.returns > 0 then
+        doc = doc .. '\n@returns '
+        -- todo handle .table field in return value (for lovr.graphics.getDevice() it's there instead of description)
+        if #variant.returns == 1 then
+          doc = doc .. (variant.returns[1].description or '')
+        else
+          for _, ret in ipairs(variant.returns) do
+            doc = doc .. '\n' .. ret.name .. ' - ' .. (ret.description or '')
+          end
+        end
+      end
+    end
+
+    if fn.notes then
+      doc = doc .. '\n\n' .. fn.notes
+    end
+
+    if fn.related then
+      doc = doc .. '\n'
+      for _, rel in ipairs(fn.related) do
+        doc = doc .. '\n@see {@link ' .. rel:gsub(':', '.') .. '}'
+      end
+    end
+
+    put_doc(doc)
+
+    -- type
+    put(indentation)
+
+    if not is_interface then
+      put('function ')
+    end
+    put(convert_name(fn.name))
+    put('(')
+    local last_required = 0
+    for arg_index, arg in ipairs (variant.arguments) do
+      if not arg.default then
+        last_required = arg_index
+      end
+    end
+    for arg_index, arg in ipairs (variant.arguments) do
+      put(convert_param(arg, arg_index > last_required))
+      if arg_index < #variant.arguments then
+        put(', ')
+      end
+    end
+    put('): ')
+    put(convert_return_values(variant.returns))
+    put('\n\n')
+  end
+
+  put ('/** @noSelf **/\n')
+  put ('declare namespace lovr {\n')
+  indent()
+
+  -- callbacks
+  for _, callback in ipairs (api.callbacks) do
+    for _, variant in ipairs(callback.variants) do
+      put_fn_variant (callback, variant)
+    end
+  end
+  put('\n')
+
+  -- module functions
+  for module_index, module in ipairs(api.modules) do
+    put (indentation, 'namespace ', module.name, ' {\n')
+    indent()
+    for _, fn in ipairs(module.functions) do
+      for _, variant in ipairs (fn.variants) do
+        put_fn_variant (fn, variant)
+      end
+    end
+    unindent()
+    put (indentation, '}\n')
+    if module_index < #api.modules then
+      put('\n')
+    end
+  end
+
+  unindent()
+  put ('}\n\n')
+
+  -- module types
+  for _, module in ipairs(api.modules) do
+    for _, enum in ipairs(module.enums) do
+      -- doc
+      local doc = enum.description
+      if enum.notes then
+         doc = doc .. '\n\n' .. enum.notes
+      end
+      if enum.related then
+        doc = doc .. '\n'
+        for _, rel in ipairs(enum.related) do
+          doc = doc .. '\n@see {@link ' .. rel:gsub(':', '.') .. '}'
+        end
+      end
+      put_doc(doc)
+
+      -- type
+      put('declare type ', enum.name, ' = ')
+      for value_index, value in ipairs(enum.values) do
+        put(quote_string(value.name))
+        if value_index < #enum.values then
+          put(' | ')
+        end
+      end
+      put('\n\n')
+    end
+    for _, object in ipairs(module.objects) do
+      -- doc
+      local doc = object.description
+      if object.notes then
+         doc = doc .. '\n\n' .. object.notes
+      end
+      if object.related then
+        doc = doc .. '\n'
+        for _, rel in ipairs(object.related) do
+          doc = doc .. '\n@see {@link ' .. rel:gsub(':', '.') .. '}'
+        end
+      end
+      put_doc(doc)
+
+      -- interface
+      local extends = object.extends and (' extends ' .. convert_type(object.extends)) or ''
+      put ('declare interface ', convert_type(object.name), extends, ' {\n')
+      indent()
+      for _, method in ipairs (object.methods) do
+        for _, variant in ipairs (method.variants) do
+          put_fn_variant (method, variant, true)
+        end
+      end
+      unindent()
+      put ('}\n\n')
+    end
+  end
+
+  put(vector_ops)
+
+  out:close()
+end