branch: externals/javaimp commit 21c5c8355ffdeab860af3380ae5e4a32ac45693d Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
Parsing fixes and improvements --- javaimp-parse.el | 305 ++++++++++++++++++++++++++++++++----------------------- javaimp.el | 61 +++++------ tests/imenu.el | 42 ++++---- tests/parse.el | 147 +++++++++++++-------------- 4 files changed, 300 insertions(+), 255 deletions(-) diff --git a/javaimp-parse.el b/javaimp-parse.el index f1f45a43d9..bf2a453f1a 100644 --- a/javaimp-parse.el +++ b/javaimp-parse.el @@ -22,39 +22,54 @@ (require 'cl-lib) (require 'seq) + (cl-defstruct javaimp-scope - type + (type + nil + :documentation "Scope type, a symbol. See `javaimp-scope-all-types'.") name start open-brace - parent) - + (parent + nil + :documentation "Reference to parent scope struct.")) -(defconst javaimp-scope-classlike-types - '(class interface enum)) (defconst javaimp-scope-all-types - (append - '(anon-class - array - method - simple-statement - statement - array) - javaimp-scope-classlike-types)) + '(anon-class + array-init + class + enum + interface + local-class + method + simple-statement + statement) + "All known scope types.") + +(defconst javaimp-parse--class-keywords-regexp + (regexp-opt (mapcar #'symbol-name + '(class interface enum)) 'symbols)) -(defconst javaimp-parse--classlike-keywords - (mapcar #'symbol-name - javaimp-scope-classlike-types)) - (defconst javaimp-parse--stmt-keywords - '("if" "else" "for" "while" "do" "switch" "try" "catch" "finally" - "static" ; static initializer block + '("if" "else" "for" "while" "do" "switch" + "try" "catch" "finally" + ;; synchronized block (not method modifier) + "synchronized" + ;; static initializer block (not field modifier) + "static" )) +(defconst javaimp-parse--stmt-keywords-regexp + (regexp-opt javaimp-parse--stmt-keywords 'words)) (defconst javaimp-parse--stmt-keyword-maxlen (seq-max (mapcar #'length javaimp-parse--stmt-keywords))) +(defsubst javaimp-parse--scope-type-defun-p (s) + (and (javaimp-scope-type s) + (not (memq (javaimp-scope-type s) + '(array-init simple-statement statement))))) + (defun javaimp-parse--directive-regexp (directive) "Return regexp suitable for matching package-like DIRECTIVE, a @@ -170,7 +185,8 @@ skipping further backwards is done by the caller." (error "Cannot parse argument type")))) (defun javaimp-parse--skip-back-until (&optional stop-p) - "Goes backwards until position at which STOP-P returns non-nil, or reaching bob. + "Goes backwards until position at which STOP-P returns non-nil, +or reaching bob. STOP-P is invoked with two arguments which describe the last non-ws thing skipped: LAST-WHAT (symbol - either 'list' or @@ -255,8 +271,7 @@ is unchanged." "Attempt to parse defun declaration prefix backwards from point (but not farther than BOUND). Matches inside comments / strings are skipped. Return the beginning of the match (then the -point is also at that position) or nil (then the point is left -unchanged)." +point is also at that position) or nil." (javaimp-parse--skip-back-until) ;; If we skip a previous scope (including unnamed initializers), or ;; reach enclosing scope start, we'll fail the check in the below @@ -279,7 +294,8 @@ unchanged)." (or (not bound) (>= pos bound)) (or (memql (char-after pos) '(?@ ?\( ;annotation type / args - ?<)) ;generic type + ?< ;generic type + ?\[)) ;array ;; keyword / identifier first char (= (syntax-class (syntax-after pos)) 2))) ;word (goto-char (setq res pos)))) @@ -298,16 +314,15 @@ unchanged)." (setq tmp (javaimp-scope-parent tmp))) res)) -(defun javaimp-scope-filter-parents (scope pred) +(defun javaimp-scope-filter-parents (pred scope) "Rewrite SCOPE's parents so that only those matching PRED are left." (while scope - (let ((parent (javaimp-scope-parent scope))) - (if (and parent - (not (funcall pred parent))) - ;; leave out this parent - (setf (javaimp-scope-parent scope) (javaimp-scope-parent parent)) - (setq scope (javaimp-scope-parent scope)))))) + (if-let ((parent (javaimp-scope-parent scope)) + ((not (funcall pred parent)))) + ;; Leave out this parent + (setf (javaimp-scope-parent scope) (javaimp-scope-parent parent)) + (setq scope (javaimp-scope-parent scope))))) (defun javaimp-scope-concat-parents (scope) (let (parents) @@ -323,17 +338,21 @@ left." (setq res (memq (javaimp-scope-type scope) parent-types))) res)) -(defun javaimp-scope-defun-p (&optional additional) - "Return predicate which matches scopes in -`javaimp-scope-classlike-types'. ADDITIONAL is a list of scope -types. If it includes `method', then also method leafs are -included. If it includes `anon-class', then also leafs and -parents may be anonymous classes." - (let ((leaf-types (append javaimp-scope-classlike-types - (when (memq 'method additional) '(method)) - (when (memq 'anon-class additional) '(anon-class)))) - (parent-types (append javaimp-scope-classlike-types - (when (memq 'anon-class additional) '(anon-class))))) +(defun javaimp-scope-defun-p (&optional include-also) + "Return predicate which matches defun scopes, which are usually +type definitions. If INCLUDE-ALSO is 'method' then also include + methods. If INCLUDE-ALSO is t, include methods, as well as + local / anonymous classes and their methods." + (let ((leaf-types + (append '(class interface enum) + (when include-also + '(method)) + (when (eq include-also t) + '(anon-class local-class)))) + (parent-types + (if (eq include-also t) + javaimp-scope-all-types + '(class interface enum)))) (lambda (s) (javaimp-scope-test-type s leaf-types parent-types)))) @@ -349,27 +368,30 @@ parents may be anonymous classes." ;; Scope parsing +(defsubst javaimp-parse--wrap-parser (parser) + (lambda (arg) + (save-excursion + (funcall parser arg)))) + (defvar javaimp-parse--scope-hook - (mapcar (lambda (parser) - (lambda (arg) - (save-excursion - (funcall parser arg)))) - '(javaimp-parse--scope-array - ;; anon-class should be before method/stmt because it - ;; looks similar, but with "new" in front + (mapcar #'javaimp-parse--wrap-parser + '(;; Should be before method/stmt because it looks similar, + ;; but with "new" in front javaimp-parse--scope-anon-class javaimp-parse--scope-class javaimp-parse--scope-simple-stmt javaimp-parse--scope-method-or-stmt + ;; Should be after simple/stmt, and preferably last + javaimp-parse--scope-array-init )) "List of parser functions, each of which is called in `save-excursion' and with one argument, the position of opening brace.") (defun javaimp-parse--scope-class (brace-pos) - "Attempts to parse 'class' / 'interface' / 'enum' scope." + "Attempts to parse class scope." (when (javaimp-parse--preceding - (regexp-opt javaimp-parse--classlike-keywords 'symbols) + javaimp-parse--class-keywords-regexp brace-pos ;; closest preceding closing paren is a good bound ;; because there _will be_ such char in frequent case @@ -397,20 +419,21 @@ brace.") (defun javaimp-parse--scope-simple-stmt (_brace-pos) "Attempts to parse 'simple-statement' scope. Currently block lambdas are also recognized as such." - (and (javaimp-parse--skip-back-until) - (or (and (= (char-before (1- (point))) ?-) ; -> - (= (char-before) ?>)) - (looking-back (regexp-opt javaimp-parse--stmt-keywords 'words) - (- (point) javaimp-parse--stmt-keyword-maxlen) nil)) - (make-javaimp-scope - :type 'simple-statement - :name (or (match-string 1) - "lambda") - :start (or (match-beginning 1) - (- (point) 2))))) + (javaimp-parse--skip-back-until) + (cond ((looking-back "->" (- (point) 2)) + (make-javaimp-scope + :type 'simple-statement + :name "lambda" + :start (- (point) 2))) + ((looking-back javaimp-parse--stmt-keywords-regexp + (- (point) javaimp-parse--stmt-keyword-maxlen) nil) + (make-javaimp-scope + :type 'simple-statement + :name (match-string 1) + :start (match-beginning 1))))) (defun javaimp-parse--scope-anon-class (brace-pos) - "Attempts to parse 'anon-class' scope." + "Attempts to parse anonymous class scope." ;; skip arg-list and ws (when (and (progn (javaimp-parse--skip-back-until) @@ -474,13 +497,20 @@ lambdas are also recognized as such." name) :start (point))))))))) -(defun javaimp-parse--scope-array (_brace-pos) - "Attempts to parse 'array' scope." - (and (javaimp-parse--skip-back-until) - (member (char-before) '(?, ?\])) - (make-javaimp-scope :type 'array - :name "" - :start nil))) +(defun javaimp-parse--scope-array-init (_brace-pos) + "Attempts to parse 'array-init' scope." + (javaimp-parse--skip-back-until) + (let (decl-prefix-beg) + (when (or + ;; Special case for array-valued single-element annotation + (= (preceding-char) ?\() + ;; This will be non-nil for "top-level" array initializer. + ;; Nested array initializers are handled by a special rule + ;; in `javaimp-parse--scopes'. + (setq decl-prefix-beg (javaimp-parse--decl-prefix))) + (make-javaimp-scope :type 'array-init + :name "" + :start decl-prefix-beg)))) (defun javaimp-parse--scopes (count) @@ -490,10 +520,15 @@ COUNT is nil then goes all the way up. Examines and sets property 'javaimp-parse-scope' at each scope's open brace. If neither of functions in -`javaimp-parse--scope-hook' return non-nil then the property -value is set to the symbol `unknown'. Additionally, if a scope -is recognized, but any of its parents is 'unknown', then it's set -to 'unknown' too. +`javaimp-parse--scope-hook' return non-nil then a dummy scope is +created, with `javaimp-scope-type' set to nil. + +A couple of additional special rules, which may apply only when +we have full chain of scope nesting: +- If a `class' is nested in `method' (possibly within +statements), then it's changed to `local-class'. +- If an unrecognized scope (with nil type) is nested directly in +`array-init' then it's changed to `array-init'. If point is inside of any comment/string then this function does nothing." @@ -503,41 +538,47 @@ nothing." (while (and (nth 1 state) (or (not count) (>= (setq count (1- count)) 0))) - ;; find innermost enclosing open-bracket + ;; Find innermost enclosing open-bracket (goto-char (nth 1 state)) (when (= (char-after) ?{) (let ((scope (get-text-property (point) 'javaimp-parse-scope))) (unless scope - (setq scope (run-hook-with-args-until-success - 'javaimp-parse--scope-hook (point))) - (if scope - (setf (javaimp-scope-open-brace scope) (point)) - (setq scope 'unknown)) + (setq scope (or (run-hook-with-args-until-success + 'javaimp-parse--scope-hook (point)) + (make-javaimp-scope))) + (setf (javaimp-scope-open-brace scope) (point)) (put-text-property (point) (1+ (point)) 'javaimp-parse-scope scope)) (push scope res) - (when (and (javaimp-scope-p scope) - (javaimp-scope-start scope)) + (when (javaimp-scope-start scope) (goto-char (javaimp-scope-start scope))))) (setq state (syntax-ppss)))) - (let (parent reset-tail) + (let (curr parent in-method) (while res - (if reset-tail - ;; overwrite property value with `unknown' - (when (javaimp-scope-p (car res)) - (let ((pos (javaimp-scope-open-brace (car res)))) - (put-text-property pos (1+ pos) 'javaimp-parse-scope 'unknown))) - (if (javaimp-scope-p (car res)) - (progn - (setf (javaimp-scope-parent (car res)) parent) - (setq parent (car res))) - ;; Just reset remaining scopes, and return nil - (setq reset-tail t) - (setq parent nil))) - (setq res (cdr res))) + (setq curr (car res)) + ;; Additional special rules. Note that we modify the object + ;; which is the value of text property, so this will work only + ;; once. + (cond ((and (eq 'class (javaimp-scope-type curr)) + in-method) + ;; Class nested in method is "local class" + (setf (javaimp-scope-type curr) 'local-class)) + ((and parent + (eq 'array-init (javaimp-scope-type parent)) + (not (javaimp-scope-type curr))) + (setf (javaimp-scope-type curr) 'array-init))) + (if (eq 'method (javaimp-scope-type curr)) + (setq in-method t) + (if (memq (javaimp-scope-type curr) + ;; all non-method defuns + '(anon-class class enum interface local-class)) + (setq in-method nil))) + ;; + (setf (javaimp-scope-parent curr) parent) + (setq parent curr + res (cdr res))) parent))) - (defun javaimp-parse--all-scopes () "Parse all scopes in this buffer which are after `javaimp-parse--dirty-pos', if it points anywhere. Makes it @@ -573,18 +614,18 @@ call this function first." (catch 'found (let ((state (syntax-ppss))) (while t - (let ((res (save-excursion - (javaimp-parse--scopes nil)))) - (when (and (javaimp-scope-p res) - (or (null pred) - (funcall pred res))) - (throw 'found res)) - ;; Go up until we get something - (if (nth 1 state) - (progn - (goto-char (nth 1 state)) - (setq state (syntax-ppss))) - (throw 'found nil))))))) + (when-let* ((res (save-excursion + (javaimp-parse--scopes nil))) + ((javaimp-scope-type res)) + ((or (null pred) + (funcall pred res)))) + (throw 'found res)) + ;; Go up + (if (nth 1 state) + (progn + (goto-char (nth 1 state)) + (setq state (syntax-ppss))) + (throw 'found nil)))))) (defun javaimp-parse--class-abstract-methods () (goto-char (point-max)) @@ -599,8 +640,8 @@ call this function first." (backward-char) ;skip semicolon ;; now parse as normal method scope (when-let ((scope (javaimp-parse--scope-method-or-stmt (point))) - ;; note that an abstract method with no - ;; parents will be ignored + ;; note that an abstract method with no parents + ;; will be ignored (parent (javaimp-parse--scopes nil))) (setf (javaimp-scope-parent scope) (javaimp-scope-copy parent)) (push scope res)))))) @@ -660,14 +701,19 @@ either of symbols `normal' or 'static'." (cons (and start-pos end-pos (cons start-pos end-pos)) class-alist))) -(defun javaimp-parse-get-all-scopes (&optional beg end pred parent-pred) +(defun javaimp-parse-get-all-scopes (&optional beg end pred no-filter) "Return all scopes in the current buffer between positions BEG -and END, both exclusive, optionally filtering them with PRED, and -their parents with PARENT-PRED. Neither of PRED or PARENT-PRED -should move point. Note that parents may be outside of region -given by BEG and END. BEG is the LIMIT argument to +and END, both exclusive, optionally filtering them with PRED. +PRED should not move point. Note that parents may be outside of +region given by BEG and END. BEG is the LIMIT argument to `previous-single-property-change', and so may be nil. END -defaults to end of accessible portion of the buffer." +defaults to end of accessible portion of the buffer. + +The returned objects are copies, and so may be freely modified. + +Scope parents are filtered according to +`javaimp-parse--scope-type-defun-p', but if NO-FILTER is non-nil +then no filtering is done." (javaimp-parse--all-scopes) (let ((pos (or end (point-max))) scope res) @@ -676,24 +722,32 @@ defaults to end of accessible portion of the buffer." (or (not beg) (/= pos beg))) (setq scope (get-text-property pos 'javaimp-parse-scope)) - (when (and (javaimp-scope-p scope) + (when (and scope + (javaimp-scope-type scope) (or (null pred) (funcall pred scope))) (setq scope (javaimp-scope-copy scope)) - (when parent-pred - (javaimp-scope-filter-parents scope parent-pred)) + (unless no-filter + (javaimp-scope-filter-parents + #'javaimp-parse--scope-type-defun-p scope)) (push scope res))) res)) -(defun javaimp-parse-get-enclosing-scope (&optional pred parent-pred) - "Return innermost enclosing scope at point, optionally checking -it with PRED, and its parents with PARENT-PRED." +(defun javaimp-parse-get-enclosing-scope (&optional pred) + "Return innermost enclosing scope at point. If PRED is non-nil +then the scope must satisfy it, otherwise the next outer scope is +tried. + +The returned objects are copies, and so may be freely modified. + +Scope parents are filtered according to +`javaimp-parse--scope-type-defun-p'." (save-excursion (javaimp-parse--all-scopes)) (when-let ((scope (javaimp-parse--enclosing-scope pred))) (setq scope (javaimp-scope-copy scope)) - (when parent-pred - (javaimp-scope-filter-parents scope parent-pred)) + (javaimp-scope-filter-parents + #'javaimp-parse--scope-type-defun-p scope) scope)) (defun javaimp-parse-get-defun-decl-start (&optional bound) @@ -712,11 +766,12 @@ outside paren constructs like arg-list." (defun javaimp-parse-get-interface-abstract-methods () "Return all scopes which are abstract methods in interfaces." + (javaimp-parse--all-scopes) (let ((interfaces (javaimp-parse-get-all-scopes nil nil (lambda (s) (javaimp-scope-test-type s - '(interface) javaimp-scope-classlike-types))))) + '(interface) '(class interface enum)))))) (seq-mapcat #'javaimp-parse--interface-abstract-methods interfaces))) diff --git a/javaimp.el b/javaimp.el index 5e9b2fb7f5..2480744bd4 100644 --- a/javaimp.el +++ b/javaimp.el @@ -238,15 +238,6 @@ https://docs.gradle.org/current/userguide/java_library_plugin.html\ #sec:java_library_classes_usage ") -(defconst javaimp-show-scopes-type-abbrevs - '((anon-class . "ac") - (statement . "st") - (simple-statement . "ss") - (array . "ar") - (method . "me") - (class . "cl") - (interface . "in") - (enum . "en"))) ;; Subroutines @@ -805,9 +796,9 @@ in a major mode hook." (javaimp-tree-map-nodes (lambda (scope) (if (eq (javaimp-scope-type scope) 'method) - ;; entry + ;; leaf entry for method (cons nil (javaimp-imenu--make-entry scope)) - ;; sub-alist + ;; sub-alist for class-like (cons t (javaimp-scope-name scope)))) (lambda (res) (or (functionp (nth 2 res)) ; imenu entry @@ -825,8 +816,8 @@ in a major mode hook." (setf (alist-get (car entry) alist 0 nil #'equal) (1+ (alist-get (car entry) alist 0 nil #'equal)))) entries) + ;; Append parents to equal names to disambiguate them (mapc (lambda (entry) - ;; disambiguate same method names (when (> (alist-get (car entry) alist 0 nil #'equal) 1) (setcar entry (format "%s [%s]" @@ -836,27 +827,29 @@ in a major mode hook." entries))))) (defun javaimp-imenu--get-forest () - (let* ((defun-scopes - (javaimp-parse-get-all-scopes - nil nil (javaimp-scope-defun-p '(method)))) - (methods (seq-filter - (lambda (scope) - (eq (javaimp-scope-type scope) 'method)) - defun-scopes)) - (classes (seq-filter - (lambda (scope) - (not (eq (javaimp-scope-type scope) 'method))) - defun-scopes)) - (top-classes (seq-filter (lambda (s) - (null (javaimp-scope-parent s))) - classes)) + "Subroutine of `javaimp-imenu-create-index'." + (let* ((scopes + (javaimp-parse-get-all-scopes nil nil + (javaimp-scope-defun-p 'method))) (abstract-methods (append (javaimp-parse-get-class-abstract-methods) - (javaimp-parse-get-interface-abstract-methods)))) + (javaimp-parse-get-interface-abstract-methods))) + + methods classes top-classes) + (dolist (s scopes) + (if (eq (javaimp-scope-type s) 'method) + (push s methods) + (push s classes) + (when (null (javaimp-scope-parent s)) + (push s top-classes)))) + (setq methods (nreverse methods) + classes (nreverse classes) + top-classes (nreverse top-classes)) (mapcar (lambda (top-class) (when javaimp-verbose - (message "Building tree for top-level class-like scope: %s" + (message "Building tree for top-level %s %s" + (javaimp-scope-type top-class) (javaimp-scope-name top-class))) (javaimp-tree-build top-class (append methods @@ -884,7 +877,7 @@ in a major mode hook." (defun javaimp-xref--backend () 'javaimp) (defun javaimp-xref--ident-completion-table () - (let ((scope-pred (javaimp-scope-defun-p '(method))) + (let ((scope-pred (javaimp-scope-defun-p 'method)) (module (javaimp--detect-module))) (if module (nconc @@ -995,7 +988,7 @@ buffer." (save-restriction (widen) (javaimp-parse-get-all-scopes - nil nil (javaimp-scope-defun-p '(method anon-class))))))) + nil nil (javaimp-scope-defun-p t)))))) (default-dir (with-current-buffer source-buf default-directory)) @@ -1024,8 +1017,8 @@ buffer." (with-current-buffer source-buf (line-number-at-pos (javaimp-scope-start scope))) depth - (cdr (assq (javaimp-scope-type scope) - javaimp-show-scopes-type-abbrevs)) + (substring (symbol-name (javaimp-scope-type scope)) + 0 2) (make-string (* 2 depth) ? ) (javaimp-scope-name scope)) 'mouse-face 'highlight @@ -1126,7 +1119,7 @@ PREV-INDEX gives the index of the method itself." (save-restriction (widen) (let* ((pos (point)) - (defun-pred (javaimp-scope-defun-p '(method anon-class))) + (defun-pred (javaimp-scope-defun-p t)) (enc (javaimp-parse-get-enclosing-scope defun-pred)) (parent (if (and enc (eq (javaimp-scope-type enc) 'method)) @@ -1196,7 +1189,7 @@ PREV-INDEX gives the index of the method itself." (save-restriction (widen) (let ((s (javaimp-parse-get-enclosing-scope - (javaimp-scope-defun-p '(method anon-class)))) + (javaimp-scope-defun-p 'method))) names) (while s (push (javaimp-scope-name s) names) diff --git a/tests/imenu.el b/tests/imenu.el index cbf7fb3639..0ae4f3c64d 100644 --- a/tests/imenu.el +++ b/tests/imenu.el @@ -13,14 +13,13 @@ (dolist (elt alist) (if (and (= (length elt) 4) (functionp (nth 2 elt))) - (setcdr elt (nth 1 elt)) + (setcdr elt nil) (javaimp-test-imenu--simplify-entries (cdr elt))))) (ert-deftest javaimp-imenu-create-index () (let ((actual (javaimp-with-temp-buffer "test1.java" - (let ((imenu-use-markers nil)) - (javaimp-imenu-create-index)))) + (javaimp-imenu-create-index))) (expected-names '("foo() [Top.CInner1]" "foo() [Top.CInner1.CInner1_CInner1]" @@ -45,36 +44,35 @@ (ert-deftest javaimp-imenu-create-index-use-sub-alists () (let ((actual (javaimp-with-temp-buffer "test1.java" - (let ((imenu-use-markers nil) - (javaimp-imenu-use-sub-alists t)) + (let ((javaimp-imenu-use-sub-alists t)) (javaimp-imenu-create-index)))) (expected '(("Top" ("CInner1" - ("foo()" . 98) + ("foo()") ("CInner1_CInner1" - ("foo()" . 1099) - ("abstract_method()" . 1148) - ("bar()" . 1192) - ("baz()" . 1281))) + ("foo()") + ("abstract_method()") + ("bar()") + ("baz()"))) ("IInner1" - ("foo()" . 1603) - ("abstract_method()" . 1715) + ("foo()") + ("abstract_method()") ("IInner1_CInner1" - ("foo()" . 1798)) - ("baz()" . 1934) - ("defaultMethod(String)" . 1963) + ("foo()")) + ("baz()") + ("defaultMethod(String)") ("IInner1_IInner1" - ("foo()" . 2122) - ("defaultMethod(String)" . 2157) - ("baz()" . 2258))) + ("foo()") + ("defaultMethod(String)") + ("baz()"))) ("EnumInner1" - ("EnumInner1()" . 2353) - ("foo()" . 2399) + ("EnumInner1()") + ("foo()") ;; "EnumInner1_EInner1" omitted because no methods inside )) ("ColocatedTop" - ("foo()" . 2554) - ("bar(String,String)" . 2578))))) + ("foo()") + ("bar(String,String)"))))) (javaimp-test-imenu--simplify-entries actual) (should (equal expected actual)))) diff --git a/tests/parse.el b/tests/parse.el index 86f8820c0f..d023005d46 100644 --- a/tests/parse.el +++ b/tests/parse.el @@ -103,6 +103,9 @@ Exception4<? super Exception5>>") ;; don't go into incomplete generic ("S> T " . 4) + ;; array + ("String[] " . 1) + ;; don't go over semicolon ("foo(); void " . 8))) (javaimp-with-temp-buffer nil @@ -119,104 +122,106 @@ Exception4<? super Exception5>>") (dolist (item test-items) (javaimp-with-temp-buffer nil (insert (nth 0 item)) - (let* ((javaimp-parse--scope-hook - (lambda (arg) - (save-excursion - (funcall parser arg)))) - (scopes (javaimp-parse-get-all-scopes))) - (should (= 1 (length scopes))) - (should (eq (javaimp-scope-type (car scopes)) (nth 1 item))) - (should (equal (javaimp-scope-name (car scopes)) (nth 2 item))))))) - + (let* ((javaimp-parse--scope-hook (javaimp-parse--wrap-parser parser)) + (scopes (mapcar #'javaimp-test-parse--simplify-scope + (javaimp-parse-get-all-scopes nil nil nil t)))) + (should (equal (cdr item) scopes)))))) (ert-deftest javaimp-parse-scope-class () (javaimp-test-parse--scope #'javaimp-parse--scope-class '("class Foo {" - class "Foo") + ((class "Foo"))) '("class Foo extends Bar {" - class "Foo") - '("class Foo implements Bar {" - class "Foo") - '("class Foo implements Bar, Baz {" - class "Foo") + ((class "Foo"))) '("public class Foo extends Bar implements Baz1 , Baz2 {" - class "Foo") + ((class "Foo"))) `(,(subst-char-in-string ? ?\n "public class Foo extends Bar implements Baz1 , Baz2 {") - class "Foo") + ((class "Foo"))) '("class Foo<Bar, Baz> extends FooSuper<Bar, Baz> \ implements Interface1<Bar, Baz>, Interface2 {" - class "Foo") + ((class "Foo"))) '("class Foo<E extends Bar> {" - class "Foo") + ((class "Foo"))) + ;; enum is also a keyword '("class Foo<Enum<?>> {" - class "Foo") + ((class "Foo"))) '("class Foo<T extends Baz<? extends Baz2>> \ extends Bar<? extends Baz<? extends Baz2>> {" - class "Foo") + ((class "Foo"))) '("interface Foo<Bar, Baz> {" - interface "Foo") + ((interface "Foo"))) '("private enum Foo {" - enum "Foo") + ((enum "Foo"))) )) (ert-deftest javaimp-parse-scope-anon-class () (javaimp-test-parse--scope #'javaimp-parse--scope-anon-class '(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {" - anon-class "<anon>Object") + ((anon-class "<anon>Object"))) `(,(subst-char-in-string ? ?\n " = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {") - anon-class "<anon>Object") - '(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {" - anon-class "<anon>Object") + ((anon-class "<anon>Object"))) + '(" = (obj.getField()) .new Object<Class1, Class2>(1, baz) {" + ((anon-class "<anon>Object"))) '(" = obj.new Object<>(1, baz) {" - anon-class "<anon>Object") + ((anon-class "<anon>Object"))) )) (ert-deftest javaimp-parse-scope-method-or-stmt () (javaimp-test-parse--scope #'javaimp-parse--scope-method-or-stmt '("static void foo_bar ( String a , int b ) {" - method "foo_bar(String,int)") + ((method "foo_bar(String,int)"))) `(,(subst-char-in-string ? ?\n "static void foo_bar ( String a , int b ) {") - method "foo_bar(String,int)") + ((method "foo_bar(String,int)"))) '("void foo_bar(String a, int b) throws E1, E2 {" - method "foo_bar(String,int)") + ((method "foo_bar(String,int)"))) '("void foo_bar() throws E1 {" - method "foo_bar()") + ((method "foo_bar()"))) '("if (foo_bar(a, b) < 2) {" - statement "if") + ((statement "if"))) )) (ert-deftest javaimp-parse-scope-simple-stmt () (javaimp-test-parse--scope #'javaimp-parse--scope-simple-stmt '(" try {" - simple-statement "try") + ((simple-statement "try"))) `(,(subst-char-in-string ? ?\n " try {") - simple-statement "try") - ;; static initializer + ((simple-statement "try"))) '("static {" - simple-statement "static") - ;; lambda + ((simple-statement "static"))) '("it -> {" - simple-statement "lambda") + ((simple-statement "lambda"))) '("(x, y) -> {" - simple-statement "lambda") + ((simple-statement "lambda"))) )) -(ert-deftest javaimp-parse-scope-array () - (javaimp-test-parse--scope #'javaimp-parse--scope-array +(ert-deftest javaimp-parse-scope-array-init () + (javaimp-test-parse--scope #'javaimp-parse--scope-array-init '("new String[] {" - array "") - ;; TODO fix - ;; '("new Object[][] { {" - ;; array "") - ;; '("new int[] {{1, 2}, {" - ;; array "") + ((array-init ""))) + '("new Object[i][] { {" + ((array-init "")) + ((array-init nil) (array-init ""))) + '("new int[] {{1, 2}, {" + ((array-init "")) + ((array-init nil) (array-init "")) + ((array-init nil) (array-init ""))) + '("new int[] {{{1," + ((array-init "")) + ((array-init nil) (array-init "")) + ((array-init nil) (array-init nil) (array-init ""))) + `(,(subst-char-in-string ? ?\n "new int[] {{1, 2}, {") + ((array-init "")) + ((array-init nil) (array-init "")) + ((array-init nil) (array-init ""))) + '("@FooContainer( {" + ((array-init ""))) )) @@ -225,13 +230,7 @@ throws E1 {" (defun javaimp-test-parse--get-all-scopes-defuns () (let* ((scopes (javaimp-parse-get-all-scopes - nil nil - (lambda (s) - (memq (javaimp-scope-type s) - '(class interface enum method))) - (lambda (s) - (memq (javaimp-scope-type s) - '(class interface enum method))))) + nil nil (javaimp-scope-defun-p t))) (actual (mapcar #'javaimp-test-parse--simplify-scope scopes)) (expected '(((class "Top")) @@ -240,34 +239,37 @@ throws E1 {" ((method "foo()") (class "CInner1") (class "Top")) - ((class "CInner1_CLocal1") + ((local-class "CInner1_CLocal1") (method "foo()") (class "CInner1") (class "Top")) ((method "foo()") - (class "CInner1_CLocal1") + (local-class "CInner1_CLocal1") (method "foo()") (class "CInner1") (class "Top")) - ((class "CInner1_CLocal1_CLocal1") + ((local-class "CInner1_CLocal1_CLocal1") (method "foo()") - (class "CInner1_CLocal1") + (local-class "CInner1_CLocal1") (method "foo()") (class "CInner1") (class "Top")) ((method "foo()") - (class "CInner1_CLocal1_CLocal1") + (local-class "CInner1_CLocal1_CLocal1") (method "foo()") - (class "CInner1_CLocal1") + (local-class "CInner1_CLocal1") (method "foo()") (class "CInner1") (class "Top")) - ((class "CInner1_CLocal2") + ((local-class "CInner1_CLocal2") (method "foo()") (class "CInner1") (class "Top")) ((method "foo()") - (class "CInner1_CLocal2") + (local-class "CInner1_CLocal2") (method "foo()") (class "CInner1") (class "Top")) - ((method "toString()") + ((anon-class "<anon>Object") (class "CInner1") (class "Top")) + ((method "toString()") + (anon-class "<anon>Object") (class "CInner1") (class "Top")) + ((class "CInner1_CInner1") (class "CInner1") (class "Top")) ((method "foo()") @@ -309,18 +311,19 @@ throws E1 {" (should (= (length expected) (length actual))) (dotimes (i (length expected)) (should (equal (nth i expected) (nth i actual)))) - ;; selectively check positions + ;; Selectively check positions (let ((data `((,(nth 0 scopes) "Top" 26 36) - (,(nth 16 scopes) "foo()" 1798 1804) - (,(nth 23 scopes) "EnumInner1_EInner1" 2462 2486) - (,(nth 25 scopes) "foo()" 2554 2560)))) + (,(nth 17 scopes) "foo()" 1798 1804) + (,(nth 24 scopes) "EnumInner1_EInner1" 2462 2486) + (,(nth 26 scopes) "foo()" 2554 2560)))) (dolist (elt data) (let ((scope (nth 0 elt))) (should (equal (nth 1 elt) (javaimp-scope-name scope))) (should (equal (nth 2 elt) (javaimp-scope-start scope))) (should (equal (nth 3 elt) (javaimp-scope-open-brace scope)))))))) + (defun javaimp-test-parse--simplify-scope (s) (let (res) (while s @@ -380,6 +383,7 @@ import static some_class.fun_2; // comment ;; don't reparse (javaimp-test-parse--get-all-scopes-defuns))) + (ert-deftest javaimp-parse-get-enclosing-scope () (let ((testcases '(;; bob @@ -416,9 +420,4 @@ import static some_class.fun_2; // comment (equal (cdr testcase) (javaimp-test-parse--simplify-scope (javaimp-parse-get-enclosing-scope - (lambda (s) - (memq (javaimp-scope-type s) - '(class interface enum method))) - (lambda (s) - (memq (javaimp-scope-type s) - '(class interface enum method))))))))))) + (javaimp-scope-defun-p 'method)))))))))