branch: scratch/javaimp-wip commit fe080eba615ed90586a03f5e5c0b2515ddaab729 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
wip --- javaimp-parse.el | 120 ++++++++------- javaimp-tests.el | 272 ++++++++++++++++++---------------- javaimp.el | 3 +- testdata/test-get-file-classes-1.java | 60 -------- testdata/test1-misc-classes.java | 117 +++++++++++++++ 5 files changed, 331 insertions(+), 241 deletions(-) diff --git a/javaimp-parse.el b/javaimp-parse.el index 55c89f2..1a46b65 100644 --- a/javaimp-parse.el +++ b/javaimp-parse.el @@ -33,13 +33,20 @@ present." :type 'function) (cl-defstruct javaimp-scope - type ; one of anonymous-class, class, interface, enum, local-class, - ; method, statement, simple-statement, array, unknown + type ; see javaimp--parse-all-scope-types name start open-brace parent) +(defconst javaimp--parse-named-scope-types + '(class interface enum local-class method)) + +(defconst javaimp--parse-all-scope-types + (append + '(anonymous-class statement simple-statement array unknown) + javaimp--parse-named-scope-types)) + (defconst javaimp--parse-classlike-keywords '("class" "interface" "enum")) (defconst javaimp--parse-stmt-keywords @@ -48,8 +55,9 @@ present." )) (defsubst javaimp--parse-is-classlike (scope) - (member (symbol-name (javaimp-scope-type scope)) - javaimp--parse-classlike-keywords)) + (when scope + (member (symbol-name (javaimp-scope-type scope)) + javaimp--parse-classlike-keywords))) (defvar javaimp--arglist-syntax-table (let ((st (make-syntax-table java-mode-syntax-table))) ;TODO don't depend @@ -61,7 +69,8 @@ present." (defvar-local javaimp--parse-dirty-pos nil "Buffer position after which all parsed information should be -considered as stale. Usually set by modification change hooks.") +considered as stale. Usually set by modification change hooks. +Should be set to (point-min) in major mode hook.") (defmacro javaimp--parse-with-arglist-syntax (beg &rest body) (declare (debug t)) @@ -425,20 +434,21 @@ nil then goes all the way up. Examines and sets property res)) (defun javaimp--parse-all-scopes () - "Parses all scopes in this buffer, but only after -`javaimp--parse-dirty-pos' if it is non-nil." + "Parses scopes in this buffer which are after +`javaimp--parse-dirty-pos', if it is non-nil. Resets this +variable after parsing is done." (when javaimp--parse-dirty-pos (remove-text-properties javaimp--parse-dirty-pos (point-max) - '(javaimp-parse-scope nil))) - (goto-char (point-max)) - (let ((parse-sexp-ignore-comments t) ; FIXME remove with major mode - (parse-sexp-lookup-properties nil)) - (while (javaimp--parse-rsb-keyword "{" javaimp--parse-dirty-pos t) - (save-excursion - (forward-char) - ;; Set props at this brace and all the way up - (javaimp--parse-scopes nil)))) - (setq javaimp--parse-dirty-pos nil)) + '(javaimp-parse-scope nil)) + (goto-char (point-max)) + (let ((parse-sexp-ignore-comments t) ; FIXME remove with major mode + (parse-sexp-lookup-properties nil)) + (while (javaimp--parse-rsb-keyword "{" javaimp--parse-dirty-pos t) + (save-excursion + (forward-char) + ;; Set props at this brace and all the way up + (javaimp--parse-scopes nil)))) + (setq javaimp--parse-dirty-pos nil))) @@ -450,19 +460,41 @@ nil then goes all the way up. Examines and sets property "^\\s-*package\\s-+\\([^;\n]+\\)\\s-*;" nil t 1) (match-string 1))) -(defun javaimp--parse-get-file-classes () - (javaimp--parse-all-scopes) - (goto-char (point-max)) - (let (match res) - (while (setq match (text-property-search-backward - 'javaimp-parse-scope nil nil)) - (when (javaimp--parse-is-classlike (prop-match-value match)) - (push (mapconcat #'javaimp-scope-name - (javaimp--parse-scopes nil) - ".") - res))))) +(defun javaimp--parse-get-all-classlikes (&optional reparse) + (mapcar (lambda (scope) + (let ((name (javaimp-scope-name scope)) + (tmp scope)) + (while (setq tmp (javaimp-scope-parent tmp)) + (setq name (concat (javaimp-scope-name tmp) "." name))) + name)) + (javaimp--parse-get-all-scopes reparse #'javaimp--parse-is-classlike))) + +(defun javaimp--parse-get-forest-for-imenu (&optional reparse) + (let* ((methods + (javaimp--parse-get-all-scopes + reparse + (lambda (s) + (and (eq (javaimp-scope-type s) 'method) + (javaimp--parse-is-classlike (javaimp-scope-parent s)))))) + (classes (javaimp--parse-get-all-scopes + nil ;no need to reparse + #'javaimp--parse-is-classlike)) + (top-classes (mapcar (lambda (s) + (null (javaimp-scope-parent s))) + classes))) + (mapcar + (lambda (class) + (message "Building tree for top-level class-like scope: %s" + (javaimp-scope-name class)) + (javaimp--build-tree class (concat methods classes) + (lambda (el tested) + (equal el (javaimp-scope-parent tested))))) + top-classes))) -(defun javaimp--parse-get-all-scopes (&optional reparse) +(defun javaimp--parse-get-all-scopes (&optional reparse pred) + "Return all scopes in the current buffer for which PRED (if +given) returns non-nil. If REPARSE is non-nil then full +reparsing is done." (when reparse (setq javaimp--parse-dirty-pos (point-min))) (javaimp--parse-all-scopes) @@ -470,35 +502,11 @@ nil then goes all the way up. Examines and sets property (let (match res) (while (setq match (text-property-search-backward 'javaimp-parse-scope nil nil)) - (push (prop-match-value match) res)) + (when (or (null pred) + (funcall pred (prop-match-value match))) + (push (prop-match-value match) res))) res)) -(defun javaimp--parse-get-forest-for-imenu () - (javaimp--parse-all-scopes) - (goto-char (point-max)) - (let (match methods classes top-classes) - (while (setq match (text-property-search-backward - 'javaimp-parse-scope nil nil)) - (let* ((scope (prop-match-value match)) - (parent (javaimp-scope-parent scope))) - (cond (;; all methods - (and (eq (javaimp-scope-type scope) 'method) - (and parent (javaimp--parse-is-classlike parent))) - (push scope methods)) - (;; all classes - (javaimp--parse-is-classlike scope) - (push scope classes) - (when (not (javaimp-scope-parent scope)) - (push scope top-classes)))))) - (mapcar - (lambda (class) - (message "Building tree for top-level class: %s" - (javaimp-scope-name class)) - (javaimp--build-tree class (concat methods classes) - (lambda (el tested) - (equal el (javaimp-scope-parent tested))))) - top-classes))) - (defun javaimp--parse-update-dirty-pos (beg _end _old-len) "Function to add to `after-change-functions' hook." (when (or (not javaimp--parse-dirty-pos) diff --git a/javaimp-tests.el b/javaimp-tests.el index 0ca4593..7bb63b5 100644 --- a/javaimp-tests.el +++ b/javaimp-tests.el @@ -8,119 +8,7 @@ (require 'ert) (require 'javaimp) -;; (ert-deftest javaimp-test--maven-projects-from-xml--project () -;; (with-temp-buffer -;; (insert "<project/>") -;; (let ((projects (javaimp--maven-projects-from-xml -;; (xml-parse-region (point-min) (point-max))))) -;; (should (eql (length projects) 1))))) - -;; (ert-deftest javaimp-test--maven-projects-from-xml--projects () -;; (with-temp-buffer -;; (insert "<projects><project/><project/></projects>") -;; (let ((projects (javaimp--maven-projects-from-xml -;; (xml-parse-region (point-min) (point-max))))) -;; (should (eql (length projects) 2))))) - - -(defun javaimp-test--check-scope (parse-hook &rest test-items) - (declare (indent 1)) - (dolist (item test-items) - (with-temp-buffer - (insert (nth 0 item)) - (java-mode) - (let* ((parse-sexp-ignore-comments t) ;FIXME remove with major mode - (parse-sexp-lookup-properties nil) - (javaimp--parse-scope-hook parse-hook) - (scope (car (javaimp--parse-scopes 1)))) - (should-not (null scope)) - (should (eq (javaimp-scope-type scope) (nth 1 item))) - (should (equal (javaimp-scope-name scope) (nth 2 item))))))) - -(ert-deftest javaimp-test--parse-scope-class () - (javaimp-test--check-scope #'javaimp--parse-scope-class - '("class Foo {" - class "Foo") - '("class Foo extends Bar {" - class "Foo") - '("class Foo implements Bar {" - class "Foo") - '("class Foo implements Bar, Baz {" - class "Foo") - '("public class Foo extends Bar implements Baz1 , Baz2 {" - class "Foo") - `(,(subst-char-in-string - ? ?\n - "public class Foo extends Bar implements Baz1 , Baz2 {") - class "Foo") - '("class Foo<Bar, Baz> extends FooSuper<Bar, Baz> \ -implements Interface1<Bar, Baz>, Interface2 {" - class "Foo") - '("class Foo<E extends Bar> {" - class "Foo") - '("class Foo<Enum<?>> {" - class "Foo") - '("class Foo<T extends Baz<? extends Baz2>> \ -extends Bar<? extends Baz<? extends Baz2>> {" - class "Foo") - '("interface Foo<Bar, Baz> {" - interface "Foo") - '("private enum Foo {" - enum "Foo"))) - -(ert-deftest javaimp-test--parse-scope-anonymous-class () - (javaimp-test--check-scope #'javaimp--parse-scope-anonymous-class - '(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {" - anonymous-class "Object") - `(,(subst-char-in-string - ? ?\n - " = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {") - anonymous-class "Object") - '(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {" - anonymous-class "Object") - '(" = obj.new Object<>(1, baz) {" - anonymous-class "Object"))) - -(ert-deftest javaimp-test--parse-scope-method-or-stmt () - (javaimp-test--check-scope #'javaimp--parse-scope-method-or-stmt - '("static void foo_bar ( String a , int b ) {" - method "foo_bar(String a, int b)") - `(,(subst-char-in-string - ? ?\n - "static void foo_bar ( String a , int b ) {") - method "foo_bar(String a, int b)") - '("void foo_bar(String a, int b) throws E1, E2 {" - method "foo_bar(String a, int b) throws E1, E2") - '("void foo_bar() -throws E1 {" - method "foo_bar() throws E1") - '("if (foo_bar(a, b) < 2) {" - statement "if"))) - -(ert-deftest javaimp-test--parse-scope-simple-stmt () - (javaimp-test--check-scope #'javaimp--parse-scope-simple-stmt - '(" try {" - simple-statement "try") - `(,(subst-char-in-string ? ?\n " try {") - simple-statement "try") - ;; static initializer - '("static {" - simple-statement "static") - ;; lambda - '("it -> {" - simple-statement "lambda") - '("(x, y) -> {" - simple-statement "lambda") - )) - -(ert-deftest javaimp-test--parse-scope-array () - (javaimp-test--check-scope #'javaimp--parse-scope-array - '("new String[] {" - array "") - '("new Object[][] { {" - array "") - '("new int[] {{1, 2}, {" - array ""))) +;; Low-level helpers of scope parsers. (ert-deftest javaimp-test--parse-arglist () (dolist (data '(("") @@ -175,6 +63,113 @@ Exception4<? super Exception5>>") (cdr data)))))) + +;; Tests for scope parsers, which should be in +;; `javaimp--parse-scope-hook'. + +(ert-deftest javaimp-test--parse-scope-class () + (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-class)) + (javaimp-test--check-single-scope + '("class Foo {" + class "Foo") + '("class Foo extends Bar {" + class "Foo") + '("class Foo implements Bar {" + class "Foo") + '("class Foo implements Bar, Baz {" + class "Foo") + '("public class Foo extends Bar implements Baz1 , Baz2 {" + class "Foo") + `(,(subst-char-in-string + ? ?\n + "public class Foo extends Bar implements Baz1 , Baz2 {") + class "Foo") + '("class Foo<Bar, Baz> extends FooSuper<Bar, Baz> \ +implements Interface1<Bar, Baz>, Interface2 {" + class "Foo") + '("class Foo<E extends Bar> {" + class "Foo") + '("class Foo<Enum<?>> {" + class "Foo") + '("class Foo<T extends Baz<? extends Baz2>> \ +extends Bar<? extends Baz<? extends Baz2>> {" + class "Foo") + '("interface Foo<Bar, Baz> {" + interface "Foo") + '("private enum Foo {" + enum "Foo")))) + +(ert-deftest javaimp-test--parse-scope-anonymous-class () + (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-anonymous-class)) + (javaimp-test--check-single-scope + '(" = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {" + anonymous-class "Object") + `(,(subst-char-in-string + ? ?\n + " = new Object < Class1 , Class2 > ( 1 + 1 , baz ) {") + anonymous-class "Object") + '(" = (obj.getField()).new Object<Class1, Class2>(1, baz) {" + anonymous-class "Object") + '(" = obj.new Object<>(1, baz) {" + anonymous-class "Object")))) + +(ert-deftest javaimp-test--parse-scope-method-or-stmt () + (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-method-or-stmt)) + (javaimp-test--check-single-scope + '("static void foo_bar ( String a , int b ) {" + method "foo_bar(String a, int b)") + `(,(subst-char-in-string + ? ?\n + "static void foo_bar ( String a , int b ) {") + method "foo_bar(String a, int b)") + '("void foo_bar(String a, int b) throws E1, E2 {" + method "foo_bar(String a, int b) throws E1, E2") + '("void foo_bar() +throws E1 {" + method "foo_bar() throws E1") + '("if (foo_bar(a, b) < 2) {" + statement "if")))) + +(ert-deftest javaimp-test--parse-scope-simple-stmt () + (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-simple-stmt)) + (javaimp-test--check-single-scope + '(" try {" + simple-statement "try") + `(,(subst-char-in-string ? ?\n " try {") + simple-statement "try") + ;; static initializer + '("static {" + simple-statement "static") + ;; lambda + '("it -> {" + simple-statement "lambda") + '("(x, y) -> {" + simple-statement "lambda") + ))) + +(ert-deftest javaimp-test--parse-scope-array () + (let ((javaimp--parse-scope-hook #'javaimp--parse-scope-array)) + (javaimp-test--check-single-scope + '("new String[] {" + array "") + '("new Object[][] { {" + array "") + '("new int[] {{1, 2}, {" + array "")))) + +(defun javaimp-test--check-single-scope (&rest test-items) + (dolist (item test-items) + (with-temp-buffer + (insert (nth 0 item)) + (java-mode) + (let* ((scope (car (javaimp--parse-get-all-scopes t)))) + (should-not (null scope)) + (should (eq (javaimp-scope-type scope) (nth 1 item))) + (should (equal (javaimp-scope-name scope) (nth 2 item))))))) + + +;; Tests for javaimp-parse.el "package-private" API. + (ert-deftest javaimp-test--parse-get-package () (with-temp-buffer (insert "//package org.commented1; @@ -182,17 +177,46 @@ Exception4<? super Exception5>>") package org.foo;") (should (equal (javaimp--parse-get-package) "org.foo")))) +(ert-deftest javaimp-test--parse-get-all-classlikes () + (with-temp-buffer + (insert-file-contents + (concat javaimp--basedir "testdata/test1-misc-classes.java")) + (should (equal (javaimp--parse-get-all-classlikes t) + '("Top" + "Top.CInner1" + "Top.CInner1.CInner1_CInner1" + "Top.IInner1" + "Top.IInner1.IInner1_CInner1" + "Top.IInner1.IInner1_IInner1" + "Top.EInner1" + "Top.EInner1.EInner1_EInner1"))))) + +(ert-deftest javaimp-test--parse-get-forest-for-imenu () + ;; TODO + ) + + +(ert-deftest javaimp-test--parse-get-all-scopes () + (with-temp-buffer + (insert-file-contents + (concat javaimp--basedir "testdata/test1-misc-classes.java")) + ;; parse full buffer + (javaimp-test--check-scopes (javaimp--parse-get-all-scopes t)) + ;; reparse half buffer + (setq javaimp--parse-dirty-pos (/ (- (point-max) (point-min)) 2)) + (javaimp-test--check-scopes (javaimp--parse-get-all-scopes)) + ;; use cache + (javaimp-test--check-scopes (javaimp--parse-get-all-scopes)))) + +(defun javaimp--test-check-scopes (scopes) + ;; TODO check positions of first / most nested / last + + '((class "Top")) + '((class "CInner1") (class "Top")) + '((method "foo") (class "CInner1") (class "Top")) + + ) + -(ert-deftest javaimp-test--get-file-classes () - (should (equal (javaimp--get-file-classes - (concat javaimp--basedir "testdata/test-get-file-classes-1.java")) - '("org.foo.Top" - "org.foo.Top.CInner1" - "org.foo.Top.CInner1.CInner1_CInner1" - "org.foo.Top.IInner1" - "org.foo.Top.IInner1.IInner1_IInner1" - "org.foo.Top.IInner1.IInner1_CInner1" - "org.foo.Top.EInner1" - "org.foo.Top.EInner1.EInner1_EInner1")))) (provide 'javaimp-tests) diff --git a/javaimp.el b/javaimp.el index dade07e..5d9ef9d 100644 --- a/javaimp.el +++ b/javaimp.el @@ -449,7 +449,7 @@ prefix arg is given, don't do this filtering." (mapcar (lambda (class) (if package (concat package "." class))) - (javaimp--parse-get-file-classes))))) + (javaimp--parse-get-all-classlikes))))) ;; Organizing imports @@ -573,6 +573,7 @@ is `ordinary' or `static'. Interactively, NEW-IMPORTS is nil." ;;;###autoload (defun javaimp-imenu-create-index () + "Function to use as `imenu-create-index-function'." (let ((forest (save-excursion (javaimp--parse-get-forest-for-imenu)))) (cond ((not javaimp-imenu-group-methods) diff --git a/testdata/test-get-file-classes-1.java b/testdata/test-get-file-classes-1.java deleted file mode 100644 index ba36d31..0000000 --- a/testdata/test-get-file-classes-1.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.foo; - -public class Top { - public static class CInner1<T, S> { - public void foo_bar() { - if (true) { - class CInner1_CLocal1 implements Serializable { - void foo_bar() { - System.out.println(""); - if (true) { - class CInner1_CLocal1_CLocal1 extends Object { - public void foo_bar() { - System.out.println(""); - } - } - } - } - } - } - - class CInner1_CLocal2 extends Object { - void foo_bar() { - System.out.println(""); - } - } - } - - private Object obj = new Object(); - class CInner1_CInner1 { - } - } - - interface IInner1 { - interface IInner1_IInner1 extends A<B, C>, Serializable { - void foo_bar(); - } - - static class IInner1_CInner1 { - public void foo_bar() { - if (true) { - System.out.println(""); - } - } - } - } - - enum EInner1 { - A("a"), - B("b"); - private EInner1() { - } - public void foo_bar() { - System.out.println(""); - } - - enum EInner1_EInner1 { - C, D - } - } -} diff --git a/testdata/test1-misc-classes.java b/testdata/test1-misc-classes.java new file mode 100644 index 0000000..436d057 --- /dev/null +++ b/testdata/test1-misc-classes.java @@ -0,0 +1,117 @@ +package org.foo; + +public class Top { + public static class CInner1<T, S> { + public void foo() { + if (true) { + class CInner1_CLocal1 implements Serializable { + void foo() { + System.out.println(""); + if (true) { + class CInner1_CLocal1_CLocal1 extends Object { + public void foo() { + System.out.println(""); + } + } + } + } + } + } + + class CInner1_CLocal2 extends Object { + void foo() { + System.out.println(""); + } + } + } + + private Object obj = new Object(); + private Object obj = new Object() { + @Override + public String toString() { + return "asdf"; + } + }; + abstract class CInner1_CInner1 { + public void foo() { + } + + abstract void abstract_method(); + + public void bar() { + System.out.println(""); + } + + abstract void baz(); + } + } + + // public class LineCommentedClass { + // } + + /* + public class BlockCommentedClass { + } + */ + + /* + * public class BlockCommentedClass2 { + * } + */ + + /** + * public class JavadocCommentedClass { + * } + */ + + + interface IInner1 { + static void foo() { + if (true) { + System.out.println(""); + } + } + + String bar(String arg1, String arg2); + + static class IInner1_CInner1 { + public void foo() { + if (true) { + System.out.println(""); + } + } + } + + void baz(); + + default void defaultMethod() { + System.out.println(""); + } + + interface IInner1_IInner1 extends A<B, C>, Serializable { + void foo(); + + default String defaultMethod() { + System.out.println(""); + } + + void baz(); + } + } + + enum EnumInner1 { + A("a"), + B("b"); + + private EnumInner1() { + } + + public void foo() { + System.out.println(""); + } + + enum EnumInner1_EInner1 { + C, D + } + } +}