From eaf6c82af8f6f00b5231ea5254db3fb3a2569ad8 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Thu, 23 Jul 2015 18:11:38 +0200 Subject: [PATCH] ruby: Fix keyword matching After an identifier there can be anything but an identifier character. --- tagmanager/ctags/ruby.c | 62 +++++++++++++++++++++++++++---------- tests/ctags/Makefile.am | 1 + tests/ctags/ruby-block-call.rb | 10 ++++++ tests/ctags/ruby-block-call.rb.tags | 3 ++ 4 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 tests/ctags/ruby-block-call.rb create mode 100644 tests/ctags/ruby-block-call.rb.tags diff --git a/tagmanager/ctags/ruby.c b/tagmanager/ctags/ruby.c index 053b33f7d..ec9b9ec0c 100644 --- a/tagmanager/ctags/ruby.c +++ b/tagmanager/ctags/ruby.c @@ -78,7 +78,8 @@ static vString* stringListToScope (const stringList* list) * Returns TRUE if it did, FALSE (and leaves 's' where * it was) otherwise. */ -static boolean canMatch (const unsigned char** s, const char* literal) +static boolean canMatch (const unsigned char** s, const char* literal, + boolean (*end_check) (int)) { const int literal_length = strlen (literal); const int s_length = strlen ((const char *)*s); @@ -92,7 +93,7 @@ static boolean canMatch (const unsigned char** s, const char* literal) return FALSE; } /* Additionally check that we're at the end of a token. */ - if ( ! (next_char == 0 || isspace (next_char) || next_char == '(' || next_char == ';')) + if (! end_check (next_char)) { return FALSE; } @@ -100,6 +101,31 @@ static boolean canMatch (const unsigned char** s, const char* literal) return TRUE; } +static boolean notIdentChar (int c) +{ + return ! (isalnum (c) || c == '_'); +} + +static boolean notOperatorChar (int c) +{ + return ! (c == '[' || c == ']' || + c == '=' || c == '!' || c == '~' || + c == '+' || c == '-' || + c == '@' || c == '*' || c == '/' || c == '%' || + c == '<' || c == '>' || + c == '&' || c == '^' || c == '|'); +} + +static boolean isWhitespace (int c) +{ + return c == 0 || isspace (c); +} + +static boolean canMatchKeyword (const unsigned char** s, const char* literal) +{ + return canMatch (s, literal, notIdentChar); +} + /* * Attempts to advance 'cp' past a Ruby operator method name. Returns * TRUE if successful (and copies the name into 'name'), FALSE otherwise. @@ -123,7 +149,7 @@ static boolean parseRubyOperator (vString* name, const unsigned char** cp) int i; for (i = 0; RUBY_OPERATORS[i] != NULL; ++i) { - if (canMatch (cp, RUBY_OPERATORS[i])) + if (canMatch (cp, RUBY_OPERATORS[i], notOperatorChar)) { vStringCatS (name, RUBY_OPERATORS[i]); return TRUE; @@ -321,12 +347,12 @@ static void findRubyTags (void) * separators are "do", ";" or newline */ boolean expect_separator = FALSE; - if (canMatch (&cp, "=begin")) + if (canMatch (&cp, "=begin", isWhitespace)) { inMultiLineComment = TRUE; continue; } - if (canMatch (&cp, "=end")) + if (canMatch (&cp, "=end", isWhitespace)) { inMultiLineComment = FALSE; continue; @@ -353,14 +379,16 @@ static void findRubyTags (void) * puts("hello") \ * unless */ - if (canMatch (&cp, "for") || canMatch (&cp, "until") || - canMatch (&cp, "while")) + if (canMatchKeyword (&cp, "for") || + canMatchKeyword (&cp, "until") || + canMatchKeyword (&cp, "while")) { expect_separator = TRUE; enterUnnamedScope (); } - else if (canMatch (&cp, "case") || canMatch (&cp, "if") || - canMatch (&cp, "unless")) + else if (canMatchKeyword (&cp, "case") || + canMatchKeyword (&cp, "if") || + canMatchKeyword (&cp, "unless")) { enterUnnamedScope (); } @@ -369,23 +397,23 @@ static void findRubyTags (void) * "module M", "class C" and "def m" should only be at the beginning * of a line. */ - if (canMatch (&cp, "module")) + if (canMatchKeyword (&cp, "module")) { readAndEmitTag (&cp, K_MODULE); } - else if (canMatch (&cp, "class")) + else if (canMatchKeyword (&cp, "class")) { readAndEmitTag (&cp, K_CLASS); } - else if (canMatch (&cp, "def")) + else if (canMatchKeyword (&cp, "def")) { readAndEmitTag (&cp, K_METHOD); } - else if (canMatch (&cp, "describe")) + else if (canMatchKeyword (&cp, "describe")) { readAndEmitTag (&cp, K_DESCRIBE); } - else if (canMatch (&cp, "context")) + else if (canMatchKeyword (&cp, "context")) { readAndEmitTag (&cp, K_CONTEXT); } @@ -410,18 +438,18 @@ static void findRubyTags (void) */ break; } - else if (canMatch (&cp, "begin")) + else if (canMatchKeyword (&cp, "begin")) { enterUnnamedScope (); } - else if (canMatch (&cp, "do")) + else if (canMatchKeyword (&cp, "do")) { if (! expect_separator) enterUnnamedScope (); else expect_separator = FALSE; } - else if (canMatch (&cp, "end") && stringListCount (nesting) > 0) + else if (canMatchKeyword (&cp, "end") && stringListCount (nesting) > 0) { /* Leave the most recent scope. */ vStringDelete (stringListLast (nesting)); diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index 8b187e26a..e790cb258 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -255,6 +255,7 @@ test_sources = \ regexp.js \ return-hint.zep \ return-types.go \ + ruby-block-call.rb \ ruby-doc.rb \ ruby-sf-bug-364.rb \ rules.t2t \ diff --git a/tests/ctags/ruby-block-call.rb b/tests/ctags/ruby-block-call.rb new file mode 100644 index 000000000..db9ccb154 --- /dev/null +++ b/tests/ctags/ruby-block-call.rb @@ -0,0 +1,10 @@ + +def plop + [ 1, 2, 3, 4 ].each do |x| + x > 0 + end.all? +end + +def nothing + puts "nothing" +end diff --git a/tests/ctags/ruby-block-call.rb.tags b/tests/ctags/ruby-block-call.rb.tags new file mode 100644 index 000000000..6573bd2a5 --- /dev/null +++ b/tests/ctags/ruby-block-call.rb.tags @@ -0,0 +1,3 @@ +# format=tagmanager +nothingÌ128Ö0 +plopÌ128Ö0 -- 2.11.4.GIT