From 1ed29f1d7b948e7d7fde70499491673e65a04954 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Thu, 23 Jul 2015 22:50:24 +0200 Subject: [PATCH] ruby: Fix parsing qualified identifiers The implementation is a bit hacky, but avoids the need for complex logic to pop several scopes at once. Closes universal-ctags/ctags#452. --- tagmanager/ctags/ruby.c | 40 ++++++++++++++++++++--- tests/ctags/Makefile.am | 1 + tests/ctags/ruby-namespaced-class.rb | 8 +++++ tests/ctags/ruby-namespaced-class.rb.tags | 4 +++ 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/ctags/ruby-namespaced-class.rb create mode 100644 tests/ctags/ruby-namespaced-class.rb.tags diff --git a/tagmanager/ctags/ruby.c b/tagmanager/ctags/ruby.c index ec9b9ec0..f9a987bd 100644 --- a/tagmanager/ctags/ruby.c +++ b/tagmanager/ctags/ruby.c @@ -43,6 +43,8 @@ static kindOption RubyKinds [] = { static stringList* nesting = NULL; +#define SCOPE_SEPARATOR '.' + /* * FUNCTION DEFINITIONS */ @@ -66,7 +68,8 @@ static vString* stringListToScope (const stringList* list) vString* chunk = stringListItem (list, i); if (vStringLength (chunk) > 0) { - vStringCatS (result, (chunks_output++ > 0) ? "." : ""); + if (chunks_output++ > 0) + vStringPut (result, SCOPE_SEPARATOR); vStringCatS (result, vStringValue (chunk)); } } @@ -165,6 +168,8 @@ static void emitRubyTag (vString* name, rubyKind kind) { tagEntryInfo tag; vString* scope; + const char *unqualified_name; + const char *qualified_name; if (!RubyKinds[kind].enabled) { return; @@ -173,7 +178,23 @@ static void emitRubyTag (vString* name, rubyKind kind) vStringTerminate (name); scope = stringListToScope (nesting); - initTagEntry (&tag, vStringValue (name)); + qualified_name = vStringValue (name); + unqualified_name = strrchr (qualified_name, SCOPE_SEPARATOR); + if (unqualified_name && unqualified_name[1]) + { + if (unqualified_name > qualified_name) + { + if (vStringLength (scope) > 0) + vStringPut (scope, SCOPE_SEPARATOR); + vStringNCatS (scope, qualified_name, + unqualified_name - qualified_name); + } + unqualified_name++; + } + else + unqualified_name = qualified_name; + + initTagEntry (&tag, unqualified_name); if (vStringLength (scope) > 0) { tag.extensionFields.scope [0] = "class"; tag.extensionFields.scope [1] = vStringValue (scope); @@ -215,6 +236,7 @@ static rubyKind parseIdentifier ( * point or equals sign. These are all part of the name. * A method name may also contain a period if it's a singleton method. */ + boolean had_sep = FALSE; const char* also_ok; if (kind == K_METHOD) { @@ -251,11 +273,21 @@ static rubyKind parseIdentifier ( } /* Copy the identifier into 'name'. */ - while (**cp != 0 && (isalnum (**cp) || charIsIn (**cp, also_ok))) + while (**cp != 0 && (**cp == ':' || isalnum (**cp) || charIsIn (**cp, also_ok))) { char last_char = **cp; - vStringPut (name, last_char); + if (last_char == ':') + had_sep = TRUE; + else + { + if (had_sep) + { + vStringPut (name, SCOPE_SEPARATOR); + had_sep = FALSE; + } + vStringPut (name, last_char); + } ++*cp; if (kind == K_METHOD) diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index e790cb25..c0670124 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -257,6 +257,7 @@ test_sources = \ return-types.go \ ruby-block-call.rb \ ruby-doc.rb \ + ruby-namespaced-class.rb \ ruby-sf-bug-364.rb \ rules.t2t \ sample.t2t \ diff --git a/tests/ctags/ruby-namespaced-class.rb b/tests/ctags/ruby-namespaced-class.rb new file mode 100644 index 00000000..17c11c36 --- /dev/null +++ b/tests/ctags/ruby-namespaced-class.rb @@ -0,0 +1,8 @@ +module A + module B + end +end + +class A::B::C; end + +puts A::B::C diff --git a/tests/ctags/ruby-namespaced-class.rb.tags b/tests/ctags/ruby-namespaced-class.rb.tags new file mode 100644 index 00000000..0c272115 --- /dev/null +++ b/tests/ctags/ruby-namespaced-class.rb.tags @@ -0,0 +1,4 @@ +# format=tagmanager +AÌ256Ö0 +BÌ256ÎAÖ0 +CÌ1ÎA.BÖ0