fix(gcal): add LuaCATS annotations and resolve type errors
Problem: gcal.lua had ~10 LuaLS errors from untyped credential and token tables, string|osdate casts, and untyped _gcal_event_id field access. Solution: add pending.GcalCredentials and pending.GcalTokens class definitions, annotate all local functions with @param/@return, add --[[@as string]] casts on os.date returns, and fix _gcal_event_id access to use bracket notation with casts.
This commit is contained in:
parent
ce8decc2b0
commit
00ea92ce33
1 changed files with 76 additions and 16 deletions
|
|
@ -8,20 +8,36 @@ local TOKEN_URL = 'https://oauth2.googleapis.com/token'
|
|||
local AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth'
|
||||
local SCOPE = 'https://www.googleapis.com/auth/calendar'
|
||||
|
||||
---@class pending.GcalCredentials
|
||||
---@field client_id string
|
||||
---@field client_secret string
|
||||
---@field redirect_uris? string[]
|
||||
|
||||
---@class pending.GcalTokens
|
||||
---@field access_token string
|
||||
---@field refresh_token string
|
||||
---@field expires_in? integer
|
||||
---@field obtained_at? integer
|
||||
|
||||
---@return table<string, any>
|
||||
local function gcal_config()
|
||||
local cfg = config.get()
|
||||
return cfg.gcal or {}
|
||||
end
|
||||
|
||||
---@return string
|
||||
local function token_path()
|
||||
return vim.fn.stdpath('data') .. '/pending/gcal_tokens.json'
|
||||
end
|
||||
|
||||
---@return string
|
||||
local function credentials_path()
|
||||
local gc = gcal_config()
|
||||
return gc.credentials_path or (vim.fn.stdpath('data') .. '/pending/gcal_credentials.json')
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@return table?
|
||||
local function load_json_file(path)
|
||||
local f = io.open(path, 'r')
|
||||
if not f then
|
||||
|
|
@ -39,6 +55,9 @@ local function load_json_file(path)
|
|||
return decoded
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@param data table
|
||||
---@return boolean
|
||||
local function save_json_file(path, data)
|
||||
local dir = vim.fn.fnamemodify(path, ':h')
|
||||
if vim.fn.isdirectory(dir) == 0 then
|
||||
|
|
@ -54,31 +73,43 @@ local function save_json_file(path, data)
|
|||
return true
|
||||
end
|
||||
|
||||
---@return pending.GcalCredentials?
|
||||
local function load_credentials()
|
||||
local creds = load_json_file(credentials_path())
|
||||
if not creds then
|
||||
return nil
|
||||
end
|
||||
if creds.installed then
|
||||
return creds.installed
|
||||
return creds.installed --[[@as pending.GcalCredentials]]
|
||||
end
|
||||
return creds
|
||||
return creds --[[@as pending.GcalCredentials]]
|
||||
end
|
||||
|
||||
---@return pending.GcalTokens?
|
||||
local function load_tokens()
|
||||
return load_json_file(token_path())
|
||||
return load_json_file(token_path()) --[[@as pending.GcalTokens?]]
|
||||
end
|
||||
|
||||
---@param tokens pending.GcalTokens
|
||||
---@return boolean
|
||||
local function save_tokens(tokens)
|
||||
return save_json_file(token_path(), tokens)
|
||||
end
|
||||
|
||||
---@param str string
|
||||
---@return string
|
||||
local function url_encode(str)
|
||||
return str:gsub('([^%w%-%.%_%~])', function(c)
|
||||
return string.format('%%%02X', string.byte(c))
|
||||
end)
|
||||
end
|
||||
|
||||
---@param method string
|
||||
---@param url string
|
||||
---@param headers? string[]
|
||||
---@param body? string
|
||||
---@return table? result
|
||||
---@return string? err
|
||||
local function curl_request(method, url, headers, body)
|
||||
local args = { 'curl', '-s', '-X', method }
|
||||
for _, h in ipairs(headers or {}) do
|
||||
|
|
@ -107,6 +138,8 @@ local function curl_request(method, url, headers, body)
|
|||
return decoded, nil
|
||||
end
|
||||
|
||||
---@param access_token string
|
||||
---@return string[]
|
||||
local function auth_headers(access_token)
|
||||
return {
|
||||
'Authorization: Bearer ' .. access_token,
|
||||
|
|
@ -114,6 +147,9 @@ local function auth_headers(access_token)
|
|||
}
|
||||
end
|
||||
|
||||
---@param creds pending.GcalCredentials
|
||||
---@param tokens pending.GcalTokens
|
||||
---@return pending.GcalTokens?
|
||||
local function refresh_access_token(creds, tokens)
|
||||
local body = 'client_id='
|
||||
.. url_encode(creds.client_id)
|
||||
|
|
@ -142,13 +178,14 @@ local function refresh_access_token(creds, tokens)
|
|||
if not ok or not decoded.access_token then
|
||||
return nil
|
||||
end
|
||||
tokens.access_token = decoded.access_token
|
||||
tokens.expires_in = decoded.expires_in
|
||||
tokens.access_token = decoded.access_token --[[@as string]]
|
||||
tokens.expires_in = decoded.expires_in --[[@as integer?]]
|
||||
tokens.obtained_at = os.time()
|
||||
save_tokens(tokens)
|
||||
return tokens
|
||||
end
|
||||
|
||||
---@return string?
|
||||
local function get_access_token()
|
||||
local creds = load_credentials()
|
||||
if not creds then
|
||||
|
|
@ -260,6 +297,10 @@ function M.authorize()
|
|||
end)
|
||||
end
|
||||
|
||||
---@param creds pending.GcalCredentials
|
||||
---@param code string
|
||||
---@param code_verifier string
|
||||
---@param port integer
|
||||
function M._exchange_code(creds, code, code_verifier, port)
|
||||
local body = 'client_id='
|
||||
.. url_encode(creds.client_id)
|
||||
|
|
@ -303,6 +344,9 @@ function M._exchange_code(creds, code, code_verifier, port)
|
|||
vim.notify('pending.nvim: Google Calendar authorized successfully.')
|
||||
end
|
||||
|
||||
---@param access_token string
|
||||
---@return string? calendar_id
|
||||
---@return string? err
|
||||
local function find_or_create_calendar(access_token)
|
||||
local gc = gcal_config()
|
||||
local cal_name = gc.calendar or 'Pendings'
|
||||
|
|
@ -329,18 +373,25 @@ local function find_or_create_calendar(access_token)
|
|||
return created and created.id, nil
|
||||
end
|
||||
|
||||
---@param date_str string
|
||||
---@return string
|
||||
local function next_day(date_str)
|
||||
local y, m, d = date_str:match('^(%d%d%d%d)-(%d%d)-(%d%d)$')
|
||||
local t = os.time({ year = tonumber(y) or 0, month = tonumber(m) or 0, day = tonumber(d) or 0 })
|
||||
+ 86400
|
||||
return os.date('%Y-%m-%d', t)
|
||||
return os.date('%Y-%m-%d', t) --[[@as string]]
|
||||
end
|
||||
|
||||
---@param access_token string
|
||||
---@param calendar_id string
|
||||
---@param task pending.Task
|
||||
---@return string? event_id
|
||||
---@return string? err
|
||||
local function create_event(access_token, calendar_id, task)
|
||||
local event = {
|
||||
summary = task.description,
|
||||
start = { date = task.due },
|
||||
['end'] = { date = next_day(task.due) },
|
||||
['end'] = { date = next_day(task.due or '') },
|
||||
transparency = 'transparent',
|
||||
extendedProperties = {
|
||||
private = { taskId = tostring(task.id) },
|
||||
|
|
@ -358,11 +409,16 @@ local function create_event(access_token, calendar_id, task)
|
|||
return data and data.id, nil
|
||||
end
|
||||
|
||||
---@param access_token string
|
||||
---@param calendar_id string
|
||||
---@param event_id string
|
||||
---@param task pending.Task
|
||||
---@return string? err
|
||||
local function update_event(access_token, calendar_id, event_id, task)
|
||||
local event = {
|
||||
summary = task.description,
|
||||
start = { date = task.due },
|
||||
['end'] = { date = next_day(task.due) },
|
||||
['end'] = { date = next_day(task.due or '') },
|
||||
}
|
||||
local _, err = curl_request(
|
||||
'PATCH',
|
||||
|
|
@ -373,6 +429,10 @@ local function update_event(access_token, calendar_id, event_id, task)
|
|||
return err
|
||||
end
|
||||
|
||||
---@param access_token string
|
||||
---@param calendar_id string
|
||||
---@param event_id string
|
||||
---@return string? err
|
||||
local function delete_event(access_token, calendar_id, event_id)
|
||||
local _, err = curl_request(
|
||||
'DELETE',
|
||||
|
|
@ -399,25 +459,25 @@ function M.sync()
|
|||
|
||||
for _, task in ipairs(tasks) do
|
||||
local extra = task._extra or {}
|
||||
local event_id = extra._gcal_event_id
|
||||
local event_id = extra['_gcal_event_id'] --[[@as string?]]
|
||||
|
||||
local should_delete = event_id
|
||||
local should_delete = event_id ~= nil
|
||||
and (
|
||||
task.status == 'done'
|
||||
or task.status == 'deleted'
|
||||
or (task.status == 'pending' and not task.due)
|
||||
)
|
||||
|
||||
if should_delete then
|
||||
local del_err = delete_event(access_token, calendar_id, event_id)
|
||||
if should_delete and event_id then
|
||||
local del_err = delete_event(access_token, calendar_id, event_id) --[[@as string]]
|
||||
if not del_err then
|
||||
extra._gcal_event_id = nil
|
||||
extra['_gcal_event_id'] = nil
|
||||
if next(extra) == nil then
|
||||
task._extra = nil
|
||||
else
|
||||
task._extra = extra
|
||||
end
|
||||
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
|
||||
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||
deleted = deleted + 1
|
||||
end
|
||||
elseif task.status == 'pending' and task.due then
|
||||
|
|
@ -432,8 +492,8 @@ function M.sync()
|
|||
if not task._extra then
|
||||
task._extra = {}
|
||||
end
|
||||
task._extra._gcal_event_id = new_id
|
||||
task.modified = tostring(os.date('!%Y-%m-%dT%H:%M:%SZ'))
|
||||
task._extra['_gcal_event_id'] = new_id
|
||||
task.modified = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
|
||||
created = created + 1
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue