aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Leake2015-08-10 21:53:19 -0500
committerStephen Leake2015-08-10 21:55:37 -0500
commitd7df36e745a5ba480559b6c8b5ebc93a18fe9bd1 (patch)
tree736918633a929b4f88e871509b699f9a00dcf398
parent21e1673be3738fb79bd92cf8bd003d86ac51130b (diff)
downloademacs-d7df36e745a5ba480559b6c8b5ebc93a18fe9bd1.tar.gz
emacs-d7df36e745a5ba480559b6c8b5ebc93a18fe9bd1.zip
Rewrite elisp--xref-find-definitions to handle many more cases; add tests.
* lisp/progmodes/elisp-mode.el (elisp--xref-identifier-location): deleted (elisp--xref-format-cl-defmethod): new (find-feature-regexp): new (find-alias-regexp): new (elisp--xref-make-xref): new (elisp--xref-find-definitions): Rewrite using the above, handle many more cases. Always output all available definitions. (xref-location-marker): No need for special cases. * test/automated/elisp-mode-tests.el: Add more tests of elisp--xref-find-definitions, improve current tests.
-rw-r--r--lisp/progmodes/elisp-mode.el168
-rw-r--r--test/automated/elisp-mode-tests.el298
2 files changed, 393 insertions, 73 deletions
diff --git a/lisp/progmodes/elisp-mode.el b/lisp/progmodes/elisp-mode.el
index b7ae3c756de..41ca57f668d 100644
--- a/lisp/progmodes/elisp-mode.el
+++ b/lisp/progmodes/elisp-mode.el
@@ -28,6 +28,7 @@
28 28
29;;; Code: 29;;; Code:
30 30
31(require 'cl-generic)
31(require 'lisp-mode) 32(require 'lisp-mode)
32(eval-when-compile (require 'cl-lib)) 33(eval-when-compile (require 'cl-lib))
33 34
@@ -441,6 +442,7 @@ It can be quoted, or be inside a quoted form."
441 (string-match ".*$" doc) 442 (string-match ".*$" doc)
442 (match-string 0 doc)))) 443 (match-string 0 doc))))
443 444
445;; can't (require 'find-func) in a preloaded file
444(declare-function find-library-name "find-func" (library)) 446(declare-function find-library-name "find-func" (library))
445(declare-function find-function-library "find-func" (function &optional l-o v)) 447(declare-function find-function-library "find-func" (function &optional l-o v))
446 448
@@ -598,60 +600,122 @@ It can be quoted, or be inside a quoted form."
598 (`apropos 600 (`apropos
599 (elisp--xref-find-apropos id)))) 601 (elisp--xref-find-apropos id))))
600 602
601(defun elisp--xref-identifier-location (type sym) 603(defconst elisp--xref-format
602 (let ((file
603 (pcase type
604 (`defun (when (fboundp sym)
605 (let ((fun-lib
606 (find-function-library sym)))
607 (setq sym (car fun-lib))
608 (cdr fun-lib))))
609 (`defvar (and (boundp sym)
610 (let ((el-file (symbol-file sym 'defvar)))
611 (if el-file
612 (and
613 ;; Don't show minor modes twice.
614 ;; TODO: If TYPE ever becomes dependent on the
615 ;; context, move this check outside.
616 (not (and (fboundp sym)
617 (memq sym minor-mode-list)))
618 el-file)
619 (help-C-file-name sym 'var)))))
620 (`feature (and (featurep sym)
621 ;; Skip when a function with the same name
622 ;; is defined, because it's probably in the
623 ;; same file.
624 (not (fboundp sym))
625 (ignore-errors
626 (find-library-name (symbol-name sym)))))
627 (`defface (when (facep sym)
628 (symbol-file sym 'defface))))))
629 (when file
630 (when (string-match-p "\\.elc\\'" file)
631 (setq file (substring file 0 -1)))
632 (xref-make-elisp-location sym type file))))
633
634(defvar elisp--xref-format
635 (let ((str "(%s %s)")) 604 (let ((str "(%s %s)"))
636 (put-text-property 1 3 'face 'font-lock-keyword-face str) 605 (put-text-property 1 3 'face 'font-lock-keyword-face str)
637 (put-text-property 4 6 'face 'font-lock-function-name-face str) 606 (put-text-property 4 6 'face 'font-lock-function-name-face str)
638 str)) 607 str))
639 608
609(defconst elisp--xref-format-cl-defmethod
610 (let ((str "(%s %s %s)"))
611 (put-text-property 1 3 'face 'font-lock-keyword-face str)
612 (put-text-property 4 6 'face 'font-lock-function-name-face str)
613 str))
614
615(defcustom find-feature-regexp
616 (concat "(provide +'%s)")
617 "The regexp used by `xref-find-definitions' to search for a feature definition.
618Note it must contain a `%s' at the place where `format'
619should insert the feature name."
620 :type 'regexp
621 :group 'xref
622 :version "25.0")
623
624(defcustom find-alias-regexp
625 "(\\(defalias +'\\|def\\(const\\|face\\) +\\)%s"
626 "The regexp used by `xref-find-definitions' to search for an alias definition.
627Note it must contain a `%s' at the place where `format'
628should insert the feature name."
629 :type 'regexp
630 :group 'xref
631 :version "25.0")
632
633(with-eval-after-load 'find-func
634 (defvar find-function-regexp-alist)
635 (add-to-list 'find-function-regexp-alist (cons 'feature 'find-feature-regexp))
636 (add-to-list 'find-function-regexp-alist (cons 'defalias 'find-alias-regexp)))
637
638(defun elisp--xref-make-xref (type symbol file &optional summary)
639 "Return an xref for TYPE SYMBOL in FILE.
640TYPE must be a type in 'find-function-regexp-alist' (use nil for
641'defun). If SUMMARY is non-nil, use it for the summary;
642otherwise build the summary from TYPE and SYMBOL."
643 (xref-make (or summary
644 (format elisp--xref-format (or type 'defun) symbol))
645 (xref-make-elisp-location symbol type file)))
646
640(defun elisp--xref-find-definitions (symbol) 647(defun elisp--xref-find-definitions (symbol)
641 (save-excursion 648 ;; The file name is not known when `symbol' is defined via interactive eval.
642 (let (lst) 649 (let (xrefs)
643 (dolist (type '(feature defface defvar defun)) 650 ;; alphabetical by result type symbol
644 (let ((loc 651
645 (condition-case err 652 ;; FIXME: advised function; list of advice functions
646 (elisp--xref-identifier-location type symbol) 653
647 (error 654 ;; FIXME: aliased variable
648 (xref-make-bogus-location (error-message-string err)))))) 655
649 (when loc 656 (when (and (symbolp symbol)
650 (push 657 (symbol-function symbol)
651 (xref-make (format elisp--xref-format type symbol) 658 (symbolp (symbol-function symbol)))
652 loc) 659 ;; aliased function
653 lst)))) 660 (let* ((alias-symbol symbol)
654 lst))) 661 (alias-file (symbol-file alias-symbol))
662 (real-symbol (symbol-function symbol))
663 (real-file (find-lisp-object-file-name real-symbol 'defun)))
664
665 (when real-file
666 (push (elisp--xref-make-xref nil real-symbol real-file) xrefs))
667
668 (when alias-file
669 (push (elisp--xref-make-xref 'defalias alias-symbol alias-file) xrefs))))
670
671 (when (facep symbol)
672 (let ((file (find-lisp-object-file-name symbol 'defface)))
673 (when file
674 (push (elisp--xref-make-xref 'defface symbol file) xrefs))))
675
676 (when (fboundp symbol)
677 (let ((file (find-lisp-object-file-name symbol (symbol-function symbol)))
678 generic)
679 (when file
680 (cond
681 ((eq file 'C-source)
682 ;; First call to find-lisp-object-file-name (for this
683 ;; symbol?); C-source has not been cached yet.
684 ;; Second call will return "src/*.c" in file; handled by 't' case below.
685 (push (elisp--xref-make-xref nil symbol (help-C-file-name (symbol-function symbol) 'subr)) xrefs))
686
687 ((setq generic (cl--generic symbol))
688 (dolist (method (cl--generic-method-table generic))
689 (let* ((info (cl--generic-method-info method))
690 (met-name (cons symbol (cl--generic-method-specializers method)))
691 (descr (format elisp--xref-format-cl-defmethod 'cl-defmethod symbol (nth 1 info)))
692 (file (find-lisp-object-file-name met-name 'cl-defmethod)))
693 (when file
694 (push (elisp--xref-make-xref 'cl-defmethod met-name file descr) xrefs))
695 ))
696
697 (let ((descr (format elisp--xref-format 'cl-defgeneric symbol)))
698 (push (elisp--xref-make-xref nil symbol file descr) xrefs))
699 )
700
701 (t
702 (push (elisp--xref-make-xref nil symbol file) xrefs))
703 ))))
704
705 (when (boundp symbol)
706 (let ((file (find-lisp-object-file-name symbol 'defvar)))
707 (when file
708 (when (eq file 'C-source)
709 (setq file (help-C-file-name symbol 'var)))
710 (push (elisp--xref-make-xref 'defvar symbol file) xrefs))))
711
712 (when (featurep symbol)
713 (let ((file (ignore-errors
714 (find-library-name (symbol-name symbol)))))
715 (when file
716 (push (elisp--xref-make-xref 'feature symbol file) xrefs))))
717
718 xrefs))
655 719
656(declare-function project-search-path "project") 720(declare-function project-search-path "project")
657(declare-function project-current "project") 721(declare-function project-current "project")
@@ -689,13 +753,7 @@ It can be quoted, or be inside a quoted form."
689 753
690(cl-defmethod xref-location-marker ((l xref-elisp-location)) 754(cl-defmethod xref-location-marker ((l xref-elisp-location))
691 (pcase-let (((cl-struct xref-elisp-location symbol type file) l)) 755 (pcase-let (((cl-struct xref-elisp-location symbol type file) l))
692 (let ((buffer-point 756 (let ((buffer-point (find-function-search-for-symbol symbol type file)))
693 (pcase type
694 (`defun (find-function-search-for-symbol symbol nil file))
695 ((or `defvar `defface)
696 (find-function-search-for-symbol symbol type file))
697 (`feature
698 (cons (find-file-noselect file) 1)))))
699 (with-current-buffer (car buffer-point) 757 (with-current-buffer (car buffer-point)
700 (goto-char (or (cdr buffer-point) (point-min))) 758 (goto-char (or (cdr buffer-point) (point-min)))
701 (point-marker))))) 759 (point-marker)))))
diff --git a/test/automated/elisp-mode-tests.el b/test/automated/elisp-mode-tests.el
index 2581de46931..114b71cfc63 100644
--- a/test/automated/elisp-mode-tests.el
+++ b/test/automated/elisp-mode-tests.el
@@ -3,6 +3,7 @@
3;; Copyright (C) 2015 Free Software Foundation, Inc. 3;; Copyright (C) 2015 Free Software Foundation, Inc.
4 4
5;; Author: Dmitry Gutov <dgutov@yandex.ru> 5;; Author: Dmitry Gutov <dgutov@yandex.ru>
6;; Author: Stephen Leake <stephen_leake@member.fsf.org>
6 7
7;; This file is part of GNU Emacs. 8;; This file is part of GNU Emacs.
8 9
@@ -113,26 +114,287 @@
113 (should (member "backup-buffer" comps)) 114 (should (member "backup-buffer" comps))
114 (should-not (member "backup-inhibited" comps))))) 115 (should-not (member "backup-inhibited" comps)))))
115 116
116;;; Navigation 117;;; xref
117 118
118(ert-deftest elisp-xref-finds-both-function-and-variable () 119(defun xref-elisp-test-descr-to-target (xref)
119 ;; "system-name" is both: a variable and a function 120 "Return an appropiate `looking-at' match string for XREF."
120 (let ((defs (elisp-xref-find 'definitions "system-name"))) 121 (let* ((loc (xref-item-location xref))
121 (should (= (length defs) 2)) 122 (type (or (xref-elisp-location-type loc)
122 (should (string= (xref-item-summary (nth 0 defs)) 123 'defun)))
123 "(defun system-name)")) 124
124 (should (string= (xref-item-summary (nth 1 defs)) 125 (cl-case type
125 "(defvar system-name)"))) 126 (defalias
127 ;; summary: "(defalias xref)"
128 ;; target : "(defalias 'xref)"
129 (concat "(defalias '" (substring (xref-item-summary xref) 10 -1)))
130
131 (defun
132 (let ((summary (xref-item-summary xref))
133 (file (xref-elisp-location-file loc)))
134 (cond
135 ((string= "c" (file-name-extension file))
136 ;; summary: "(defun buffer-live-p)"
137 ;; target : "DEFUN (buffer-live-p"
138 (concat
139 (upcase (substring summary 1 6))
140 " (\""
141 (substring summary 7 -1)
142 "\""))
143
144 (t
145 (substring summary 0 -1))
146 )))
147
148 (defvar
149 (let ((summary (xref-item-summary xref))
150 (file (xref-elisp-location-file loc)))
151 (cond
152 ((string= "c" (file-name-extension file))
153 ;; summary: "(defvar system-name)"
154 ;; target : "DEFVAR_LISP ("system-name", "
155 ;; summary: "(defvar abbrev-mode)"
156 ;; target : DEFVAR_PER_BUFFER ("abbrev-mode"
157 (concat
158 (upcase (substring summary 1 7))
159 (if (bufferp (variable-binding-locus (xref-elisp-location-symbol loc)))
160 "_PER_BUFFER (\""
161 "_LISP (\"")
162 (substring summary 8 -1)
163 "\""))
164
165 (t
166 (substring summary 0 -1))
167 )))
168
169 (feature
170 ;; summary: "(feature xref)"
171 ;; target : "(provide 'xref)"
172 (concat "(provide '" (substring (xref-item-summary xref) 9 -1)))
173
174 (otherwise
175 (substring (xref-item-summary xref) 0 -1))
176 )))
177
178
179(defmacro xref-elisp-test (name computed-xrefs expected-xrefs)
180 "Define an ert test for an xref-elisp feature.
181COMPUTED-XREFS and EXPECTED-XREFS are lists of xrefs, except if
182an element of EXPECTED-XREFS is a cons (XREF . TARGET), TARGET is
183matched to the found location; otherwise, match
184to (xref-elisp-test-descr-to-target xref)."
185 (declare (indent defun))
186 (declare (debug (symbolp "name")))
187 `(ert-deftest ,(intern (concat "xref-elisp-test-" (symbol-name name))) ()
188 (let ((xrefs ,computed-xrefs)
189 (expecteds ,expected-xrefs))
190 (while xrefs
191 (let ((xref (pop xrefs))
192 (expected (pop expecteds)))
193
194 (should (equal xref
195 (or (when (consp expected) (car expected)) expected)))
196
197 (xref--goto-location (xref-item-location xref))
198 (should (looking-at (or (when (consp expected) (cdr expected))
199 (xref-elisp-test-descr-to-target expected)))))
200 ))
201 ))
202
203;; When tests are run from the Makefile, 'default-directory' is $HOME,
204;; so we must provide this dir to expand-file-name in the expected
205;; results. The Makefile sets EMACS_TEST_DIRECTORY.
206(defconst emacs-test-dir (getenv "EMACS_TEST_DIRECTORY"))
207
208;; alphabetical by test name
209
210;; FIXME: autoload
211
212;; FIXME: defalias-defun-c cmpl-prefix-entry-head
213;; FIXME: defalias-defvar-el allout-mode-map
214
215(xref-elisp-test find-defs-defalias-defun-el
216 (elisp--xref-find-definitions 'Buffer-menu-sort)
217 (list
218 (xref-make "(defalias Buffer-menu-sort)"
219 (xref-make-elisp-location
220 'Buffer-menu-sort 'defalias
221 (expand-file-name "../../lisp/buff-menu.elc" emacs-test-dir)))
222 (xref-make "(defun tabulated-list-sort)"
223 (xref-make-elisp-location
224 'tabulated-list-sort nil
225 (expand-file-name "../../lisp/emacs-lisp/tabulated-list.el" emacs-test-dir)))
226 ))
227
228;; FIXME: defconst
229
230(xref-elisp-test find-defs-defgeneric-el
231 (elisp--xref-find-definitions 'xref-location-marker)
232 (list
233 (xref-make "(cl-defgeneric xref-location-marker)"
234 (xref-make-elisp-location
235 'xref-location-marker nil
236 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))
237 (xref-make "(cl-defmethod xref-location-marker ((l xref-elisp-location)))"
238 (xref-make-elisp-location
239 '(xref-location-marker xref-elisp-location) 'cl-defmethod
240 (expand-file-name "../../lisp/progmodes/elisp-mode.el" emacs-test-dir)))
241 (xref-make "(cl-defmethod xref-location-marker ((l xref-file-location)))"
242 (xref-make-elisp-location
243 '(xref-location-marker xref-file-location) 'cl-defmethod
244 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))
245 (xref-make "(cl-defmethod xref-location-marker ((l xref-buffer-location)))"
246 (xref-make-elisp-location
247 '(xref-location-marker xref-buffer-location) 'cl-defmethod
248 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))
249 (xref-make "(cl-defmethod xref-location-marker ((l xref-bogus-location)))"
250 (xref-make-elisp-location
251 '(xref-location-marker xref-bogus-location) 'cl-defmethod
252 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))
253 (xref-make "(cl-defmethod xref-location-marker ((l xref-etags-location)))"
254 (xref-make-elisp-location
255 '(xref-location-marker xref-etags-location) 'cl-defmethod
256 (expand-file-name "../../lisp/progmodes/etags.el" emacs-test-dir)))
257 ))
258
259;; FIXME: constructor xref-make-elisp-location; location is
260;; cl-defstruct location. use :constructor in description.
261
262(xref-elisp-test find-defs-defgeneric-eval
263 (elisp--xref-find-definitions (eval '(cl-defgeneric stephe-leake-cl-defgeneric ())))
264 nil)
265
266(xref-elisp-test find-defs-defun-el
267 (elisp--xref-find-definitions 'xref-find-definitions)
268 (list
269 (xref-make "(defun xref-find-definitions)"
270 (xref-make-elisp-location
271 'xref-find-definitions nil
272 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))))
273
274(xref-elisp-test find-defs-defun-eval
275 (elisp--xref-find-definitions (eval '(defun stephe-leake-defun ())))
276 nil)
277
278(xref-elisp-test find-defs-defun-c
279 (elisp--xref-find-definitions 'buffer-live-p)
280 (list
281 (xref-make "(defun buffer-live-p)"
282 (xref-make-elisp-location 'buffer-live-p nil "src/buffer.c"))))
283
284;; FIXME: deftype
285
286(xref-elisp-test find-defs-defun-c-defvar-c
287 (elisp-xref-find 'definitions "system-name")
288 (list
289 (xref-make "(defvar system-name)"
290 (xref-make-elisp-location 'system-name 'defvar "src/editfns.c"))
291 (xref-make "(defun system-name)"
292 (xref-make-elisp-location 'system-name nil "src/editfns.c")))
293 )
294
295(xref-elisp-test find-defs-defun-el-defvar-c
296 (elisp-xref-find 'definitions "abbrev-mode")
126 ;; It's a minor mode, but the variable is defined in buffer.c 297 ;; It's a minor mode, but the variable is defined in buffer.c
127 (let ((defs (elisp-xref-find 'definitions "abbrev-mode"))) 298 (list
128 (should (= (length defs) 2)))) 299 (xref-make "(defvar abbrev-mode)"
129 300 (xref-make-elisp-location 'abbrev-mode 'defvar "src/buffer.c"))
130(ert-deftest elisp-xref-finds-only-function-for-minor-mode () 301 (cons
131 ;; Both variable and function are defined in the same place. 302 (xref-make "(defun abbrev-mode)"
132 (let ((defs (elisp-xref-find 'definitions "visible-mode"))) 303 (xref-make-elisp-location
133 (should (= (length defs) 1)) 304 'abbrev-mode nil
134 (should (string= (xref-item-summary (nth 0 defs)) 305 (expand-file-name "../../lisp/abbrev.el" emacs-test-dir)))
135 "(defun visible-mode)")))) 306 "(define-minor-mode abbrev-mode"))
307 )
308
309;; Source for both variable and defun is "(define-minor-mode
310;; compilation-minor-mode". There is no way to tell that from the
311;; symbol. find-function-regexp-alist uses find-function-regexp for
312;; this, but that matches too many things for use in this test.
313(xref-elisp-test find-defs-defun-defvar-el
314 (elisp--xref-find-definitions 'compilation-minor-mode)
315 (list
316 (cons
317 (xref-make "(defun compilation-minor-mode)"
318 (xref-make-elisp-location
319 'compilation-minor-mode nil
320 (expand-file-name "../../lisp/progmodes/compile.el" emacs-test-dir)))
321 "(define-minor-mode compilation-minor-mode")
322 (cons
323 (xref-make "(defvar compilation-minor-mode)"
324 (xref-make-elisp-location
325 'compilation-minor-mode 'defvar
326 (expand-file-name "../../lisp/progmodes/compile.el" emacs-test-dir)))
327 "(define-minor-mode compilation-minor-mode")
328 )
329 )
330
331(xref-elisp-test find-defs-defvar-el
332 (elisp--xref-find-definitions 'xref--marker-ring)
333 ;; This is a defconst, which creates an alias and a variable.
334 ;; FIXME: try not to show the alias in this case
335 (list
336 (xref-make "(defvar xref--marker-ring)"
337 (xref-make-elisp-location
338 'xref--marker-ring 'defvar
339 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))
340 (cons
341 (xref-make "(defalias xref--marker-ring)"
342 (xref-make-elisp-location
343 'xref--marker-ring 'defalias
344 (expand-file-name "../../lisp/progmodes/xref.elc" emacs-test-dir)))
345 "(defvar xref--marker-ring")
346 ))
347
348(xref-elisp-test find-defs-defvar-c
349 (elisp--xref-find-definitions 'default-directory)
350 (list
351 (cons
352 (xref-make "(defvar default-directory)"
353 (xref-make-elisp-location 'default-directory 'defvar "src/buffer.c"))
354 ;; IMPROVEME: we might be able to compute this target
355 "DEFVAR_PER_BUFFER (\"default-directory\"")))
356
357(xref-elisp-test find-defs-defvar-eval
358 (elisp--xref-find-definitions (eval '(defvar stephe-leake-defvar nil)))
359 nil)
360
361(xref-elisp-test find-defs-face-el
362 (elisp--xref-find-definitions 'font-lock-keyword-face)
363 ;; 'font-lock-keyword-face is both a face and a var
364 ;; defface creates both a face and an alias
365 ;; FIXME: try to not show the alias in this case
366 (list
367 (xref-make "(defvar font-lock-keyword-face)"
368 (xref-make-elisp-location
369 'font-lock-keyword-face 'defvar
370 (expand-file-name "../../lisp/font-lock.el" emacs-test-dir)))
371 (xref-make "(defface font-lock-keyword-face)"
372 (xref-make-elisp-location
373 'font-lock-keyword-face 'defface
374 (expand-file-name "../../lisp/font-lock.el" emacs-test-dir)))
375 (cons
376 (xref-make "(defalias font-lock-keyword-face)"
377 (xref-make-elisp-location
378 'font-lock-keyword-face 'defalias
379 (expand-file-name "../../lisp/font-lock.elc" emacs-test-dir)))
380 "(defface font-lock-keyword-face")
381 ))
382
383(xref-elisp-test find-defs-face-eval
384 (elisp--xref-find-definitions (eval '(defface stephe-leake-defface nil "")))
385 nil)
386
387(xref-elisp-test find-defs-feature-el
388 (elisp--xref-find-definitions 'xref)
389 (list
390 (xref-make "(feature xref)"
391 (xref-make-elisp-location
392 'xref 'feature
393 (expand-file-name "../../lisp/progmodes/xref.el" emacs-test-dir)))))
394
395(xref-elisp-test find-defs-feature-eval
396 (elisp--xref-find-definitions (eval '(provide 'stephe-leake-feature)))
397 nil)
136 398
137(provide 'elisp-mode-tests) 399(provide 'elisp-mode-tests)
138;;; elisp-mode-tests.el ends here 400;;; elisp-mode-tests.el ends here