Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Commit d266bdf

Browse files
committed
refactor(gitcommit): add types and input validation
Add EmmyLua annotations and nil-safety checks to release notes functions to ensure robust processing of commit data and version information.
1 parent d50829c commit d266bdf

1 file changed

Lines changed: 74 additions & 5 deletions

File tree

‎lua/codecompanion/_extensions/gitcommit/prompts/release_notes.lua‎

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
local M = {}
22

3+
---@alias ReleaseNotesStyle "detailed" | "concise" | "changelog" | "marketing"
4+
5+
---@class CommitInfo
6+
---@field hash string Full commit hash
7+
---@field subject string Commit subject line
8+
---@field body string|nil Commit body (optional)
9+
---@field author string|nil Author name (optional)
10+
---@field type string|nil Commit type from subject (feat, fix, etc.)
11+
12+
---@class CommitAnalysis
13+
---@field features CommitInfo[]
14+
---@field fixes CommitInfo[]
15+
---@field breaking_changes CommitInfo[]
16+
---@field performance CommitInfo[]
17+
---@field documentation CommitInfo[]
18+
---@field refactoring CommitInfo[]
19+
---@field tests CommitInfo[]
20+
---@field chore CommitInfo[]
21+
---@field other CommitInfo[]
22+
---@field contributors table<string, number> Author -> commit count
23+
324
M.style_guides = {
425
detailed = [[You are creating comprehensive release notes for developers.
526
Write thorough explanations of changes with technical context.
@@ -37,7 +58,14 @@ WRITING GUIDELINES:
3758
- Merge similar commits into single entries when appropriate
3859
]]
3960

61+
---@param commit CommitInfo Commit information to format
62+
---@param include_hash boolean Whether to include commit hash
63+
---@return string Formatted commit entry
4064
local function format_commit(commit, include_hash)
65+
if not commit or not commit.subject then
66+
return "- (invalid commit data)"
67+
end
68+
4169
local parts = { "- ", commit.subject }
4270
if include_hash and commit.hash then
4371
table.insert(parts, string.format(" (%s)", commit.hash:sub(1, 7)))
@@ -51,8 +79,12 @@ local function format_commit(commit, include_hash)
5179
return table.concat(parts)
5280
end
5381

82+
---@param name string Category name
83+
---@param commits CommitInfo[] List of commits in this category
84+
---@param include_hash boolean Whether to include commit hashes
85+
---@return string|nil Formatted category section or nil if empty
5486
local function format_category(name, commits, include_hash)
55-
if #commits == 0 then
87+
if not commits or #commits == 0 then
5688
return nil
5789
end
5890
local lines = { string.format("\n### %s", name) }
@@ -62,7 +94,24 @@ local function format_category(name, commits, include_hash)
6294
return table.concat(lines, "\n")
6395
end
6496

97+
---@param commits CommitInfo[] List of commits to analyze
98+
---@return CommitAnalysis Analysis results with categorized commits and contributor counts
6599
function M.analyze_commits(commits)
100+
if type(commits) ~= "table" then
101+
return {
102+
features = {},
103+
fixes = {},
104+
breaking_changes = {},
105+
performance = {},
106+
documentation = {},
107+
refactoring = {},
108+
tests = {},
109+
chore = {},
110+
other = {},
111+
contributors = {},
112+
}
113+
end
114+
66115
local analysis = {
67116
features = {},
68117
fixes = {},
@@ -77,11 +126,18 @@ function M.analyze_commits(commits)
77126
}
78127

79128
for _, commit in ipairs(commits) do
80-
analysis.contributors[commit.author] = (analysis.contributors[commit.author] or 0) + 1
129+
-- Track contributors (skip nil authors)
130+
if commit.author then
131+
analysis.contributors[commit.author] = (analysis.contributors[commit.author] or 0) + 1
132+
end
81133

82-
local is_breaking = commit.subject:match("^%w+!:") -- feat!: xxx
83-
or commit.subject:match("^%w+%b()!:") -- feat(scope)!: xxx
84-
or commit.subject:upper():match("BREAKING")
134+
-- Check for breaking changes (nil-safe)
135+
local is_breaking = false
136+
if commit.subject then
137+
is_breaking = commit.subject:match("^%w+!:") -- feat!: xxx
138+
or commit.subject:match("^%w+%b()!:") -- feat(scope)!: xxx
139+
or commit.subject:upper():match("BREAKING")
140+
end
85141

86142
if is_breaking then
87143
table.insert(analysis.breaking_changes, commit)
@@ -107,7 +163,20 @@ function M.analyze_commits(commits)
107163
return analysis
108164
end
109165

166+
---@param commits CommitInfo[] List of commits to analyze
167+
---@param style ReleaseNotesStyle Style of release notes to generate
168+
---@param version_info table { from: string, to: string } Version range information
169+
---@return string Generated prompt for AI release notes generation
110170
function M.create_smart_prompt(commits, style, version_info)
171+
-- Input validation
172+
if type(commits) ~= "table" then
173+
commits = {}
174+
end
175+
176+
if not version_info or type(version_info) ~= "table" or not version_info.from or not version_info.to then
177+
version_info = { from = "unknown", to = "unknown" }
178+
end
179+
111180
local analysis = M.analyze_commits(commits)
112181
local guide = M.style_guides[style] or M.style_guides.detailed
113182

0 commit comments

Comments
 (0)