Skip to content

Commit 254627e

Browse files
Copilottninja
andauthored
Fix function detection for TODO comments preceding method definitions (#41)
* Initial plan * Fix function detection for TODO comments Co-authored-by: tninja <714625+tninja@users.noreply.github.com> * Address code review feedback: remove redundant condition and clarify comments Co-authored-by: tninja <714625+tninja@users.noreply.github.com> * Refine function name detection with lookahead in comments * update HISTORY --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tninja <714625+tninja@users.noreply.github.com> Co-authored-by: tninja <tninja@gmail.com>
1 parent 29da5cb commit 254627e

File tree

4 files changed

+175
-2
lines changed

4 files changed

+175
-2
lines changed

HISTORY.org

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
** Main branch change
55

6+
- Fix function detection for TODO comments preceding method definitions
7+
- Addressing [[https://github.com/tninja/ai-code-interface.el/issues/40][Wrong function detection]], suggested by @Silex
68
- Refactor: error investigation prompt, run command with comint buffer
79
- Chore: Refactor error investigation prompts to improve context handling
810
- Chore: Add clipboard context option to ai-code-send-command function

ai-code-change.el

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,53 @@ ignoring leading whitespace."
3232
"+")
3333
(string-trim-left line)))))
3434

35+
(defun ai-code--get-function-name-for-comment ()
36+
"Get the appropriate function name when cursor is on a comment line.
37+
If the comment precedes a function definition or is inside a function body,
38+
returns that function's name. Otherwise returns the result of `which-function`."
39+
(interactive)
40+
(let* ((current-func (which-function))
41+
(resolved-func
42+
(save-excursion
43+
;; Move to next non-comment, non-blank line
44+
(forward-line 1)
45+
(while (and (not (eobp))
46+
(or (looking-at-p "^[ \t]*$")
47+
(ai-code--is-comment-line
48+
(buffer-substring-no-properties
49+
(line-beginning-position)
50+
(line-end-position)))))
51+
(forward-line 1))
52+
;; Get function name at this position, trying a short lookahead inside
53+
;; the function body when `which-function` cannot resolve the def line.
54+
(unless (eobp)
55+
(let ((lookahead 5)
56+
(next-func (which-function)))
57+
(while (and (> lookahead 0)
58+
(or (null next-func)
59+
(string= next-func current-func)))
60+
(forward-line 1)
61+
(setq lookahead (1- lookahead))
62+
(unless (or (eobp)
63+
(looking-at-p "^[ \t]*$")
64+
(ai-code--is-comment-line
65+
(buffer-substring-no-properties
66+
(line-beginning-position)
67+
(line-end-position))))
68+
(setq next-func (which-function))))
69+
(cond
70+
;; No current function, use the next if found.
71+
((not current-func) next-func)
72+
;; No next function, keep the current context.
73+
((not next-func) current-func)
74+
;; Prefer the forward definition when it differs from current.
75+
((not (string= next-func current-func)) next-func)
76+
;; Otherwise fall back to current.
77+
(t current-func)))))))
78+
;; (when resolved-func
79+
;; (message "Identified function: %s" resolved-func))
80+
resolved-func))
81+
3582
;;;###autoload
3683
(defun ai-code-code-change (arg)
3784
"Generate prompt to change code under cursor or in selected region.
@@ -100,7 +147,9 @@ Argument ARG is the prefix argument."
100147
(let* ((current-line (string-trim (thing-at-point 'line t)))
101148
(current-line-number (line-number-at-pos (point)))
102149
(is-comment (ai-code--is-comment-line current-line))
103-
(function-name (which-function))
150+
(function-name (if is-comment
151+
(ai-code--get-function-name-for-comment)
152+
(which-function)))
104153
(function-context (if function-name
105154
(format "\nFunction: %s" function-name)
106155
""))

ai-code-interface.el

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
;;; ai-code-interface.el --- AI code interface for editing AI prompt files -*- lexical-binding: t; -*-
22

33
;; Author: Kang Tu <tninja@gmail.com>
4-
;; Version: 0.50
4+
;; Version: 0.51
55
;; Package-Requires: ((emacs "26.1") (transient "0.8.0") (magit "2.1.0"))
66

77
;; SPDX-License-Identifier: Apache-2.0

test_ai-code-change.el

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
;;; test_ai-code-change.el --- Tests for ai-code-change.el -*- lexical-binding: t; -*-
2+
3+
;; Author: Kang Tu <tninja@gmail.com>
4+
;; SPDX-License-Identifier: Apache-2.0
5+
6+
;;; Commentary:
7+
;; Tests for the ai-code-change module, specifically testing
8+
;; the function detection logic for TODO comments.
9+
10+
;;; Code:
11+
12+
(require 'ert)
13+
(require 'ai-code-change)
14+
15+
(ert-deftest test-ai-code--get-function-name-for-comment-basic ()
16+
"Test function name detection when on a comment line before function body.
17+
This simulates the Ruby example from the issue where a TODO comment
18+
is between the function definition and its body."
19+
(with-temp-buffer
20+
;; Simulate Ruby mode comment syntax
21+
(setq-local comment-start "# ")
22+
(insert "module Foo\n")
23+
(insert " class Bar\n")
24+
(insert " def baz\n")
25+
(insert " end\n")
26+
(insert "\n")
27+
(insert " # TODO remove this function\n") ;; Line 6 - cursor will be here
28+
(insert " def click_first_available(driver, selectors)\n")
29+
(insert " wait = Selenium::WebDriver::Wait.new(timeout: 10)\n")
30+
(insert " end\n")
31+
(insert " end\n")
32+
(insert "end\n")
33+
;; Move cursor to the TODO comment line (line 6)
34+
(goto-char (point-min))
35+
(forward-line 5) ;; Move 5 lines forward from line 1 to reach line 6
36+
;; Mock which-function to simulate the actual behavior
37+
;; When on line 6, which-function might return "Bar" (class)
38+
;; When on line 7 (def line), it should return "Bar#click_first_available"
39+
(cl-letf (((symbol-function 'which-function)
40+
(lambda ()
41+
(save-excursion
42+
(let ((line-num (line-number-at-pos (point))))
43+
(cond
44+
((= line-num 6) "Bar") ;; On comment, returns class
45+
((= line-num 7) "Bar") ;; On def, still returns class
46+
((>= line-num 8) "Bar#click_first_available") ;; Inside method body
47+
(t nil)))))))
48+
;; Test that on the comment line, we get the correct function name
49+
(let ((result (ai-code--get-function-name-for-comment)))
50+
(should (string= result "Bar#click_first_available"))))))
51+
52+
(ert-deftest test-ai-code--get-function-name-for-comment-no-function ()
53+
"Test function name detection when comment is not followed by a function."
54+
(with-temp-buffer
55+
(setq-local comment-start "# ")
56+
(insert "# TODO some task\n")
57+
(insert "x = 1\n")
58+
(goto-char (point-min))
59+
(cl-letf (((symbol-function 'which-function) (lambda () nil)))
60+
(let ((result (ai-code--get-function-name-for-comment)))
61+
(should (null result))))))
62+
63+
(ert-deftest test-ai-code--get-function-name-for-comment-multiple-comments ()
64+
"Test function name detection with multiple comment lines before function."
65+
(with-temp-buffer
66+
(setq-local comment-start "# ")
67+
(insert " # TODO task 1\n") ;; Line 1 - cursor here
68+
(insert " # TODO task 2\n") ;; Line 2
69+
(insert " def my_function()\n") ;; Line 3
70+
(insert " x = 1\n")
71+
(insert " end\n")
72+
(goto-char (point-min))
73+
;; Mock which-function
74+
(cl-letf (((symbol-function 'which-function)
75+
(lambda ()
76+
(save-excursion
77+
(let ((line-num (line-number-at-pos (point))))
78+
(cond
79+
((<= line-num 2) nil) ;; On comments, no function context
80+
((>= line-num 3) "my_function") ;; On/in function
81+
(t nil)))))))
82+
(let ((result (ai-code--get-function-name-for-comment)))
83+
(should (string= result "my_function"))))))
84+
85+
(ert-deftest test-ai-code--get-function-name-for-comment-same-function ()
86+
"Test that when comment and next line are in same function, we get that function."
87+
(with-temp-buffer
88+
(setq-local comment-start "# ")
89+
(insert " def my_function()\n") ;; Line 1
90+
(insert " # TODO implement this\n") ;; Line 2 - cursor here
91+
(insert " x = 1\n") ;; Line 3
92+
(insert " end\n")
93+
(goto-char (point-min))
94+
(forward-line 1) ;; Move 1 line forward from line 1 to reach line 2 (the comment)
95+
;; Mock which-function - both lines return same function
96+
(cl-letf (((symbol-function 'which-function) (lambda () "my_function")))
97+
(let ((result (ai-code--get-function-name-for-comment)))
98+
(should (string= result "my_function"))))))
99+
100+
(ert-deftest test-ai-code--is-comment-line ()
101+
"Test comment line detection."
102+
;; Test with hash comment
103+
(let ((comment-start "# "))
104+
(should (ai-code--is-comment-line "# This is a comment"))
105+
(should (ai-code--is-comment-line " # This is an indented comment"))
106+
(should (ai-code--is-comment-line "## Multiple hashes"))
107+
(should-not (ai-code--is-comment-line "This is not a comment"))
108+
(should-not (ai-code--is-comment-line " x = 1 # inline comment")))
109+
;; Test with semicolon comment (Lisp)
110+
(let ((comment-start "; "))
111+
(should (ai-code--is-comment-line "; This is a comment"))
112+
(should (ai-code--is-comment-line " ;; This is a comment"))
113+
(should-not (ai-code--is-comment-line "This is not a comment")))
114+
;; Test with double slash comment (C/Java)
115+
(let ((comment-start "// "))
116+
(should (ai-code--is-comment-line "// This is a comment"))
117+
(should (ai-code--is-comment-line " // This is an indented comment"))
118+
(should-not (ai-code--is-comment-line "This is not a comment"))))
119+
120+
(provide 'test_ai-code-change)
121+
122+
;;; test_ai-code-change.el ends here

0 commit comments

Comments
 (0)