Repository: ycm-core/ycmd

Issue:
ycmd does not pass on many fixits
I'm finding that ycmd occasionally suggests fixits for things like missing semi-colons, but for many other things it does not suggest a fixit even though clang does. For instance, if I write `std::strng x;`, when compiling in clang it offers the correction to `string`, but ycmd does not offer this fixit. Is this a bug with ycmd, something wrong with my setup, or is the fixit feature constrained somehow?



Discussion:
@puremourning You'd probably know best.

Can you check you are on the latest commit? We recently (like last week) merged a change which reports FixIts for notes, which should include typo correction (which is the type of case to which you are referring as I understand it)

It is also possible that your client doesn't handle multiple fix it options. Which client are you using ?

> On 16 Jul 2016, at 01:41, Val Markovic notifications@github.com wrote:
> 
> @puremourning You'd probably know best.
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or mute the thread.

Just as

@puremourning I'm not exactly sure about being on the latest commit, the ycmd I use is built by someone else and distributed. The build is dated June 11 but I'm not necessarily sure whether they pulled that day. Is there some way I could tell from the released build whether that commit has been included?

I'm using the emacs-ycmd client. I'm not sure what you mean by multiple fixit options; I typed in `std::strng  x;` so the correction to string should have been the only fixit. Do you mean a situation whether I typed in `std::strng x`? It seems like emacs ycmd supports multi line fixits because when I do a fixit, it asks me to confirm whether I want to perform all fixits on that line.

@quicknir Could you provide the link to the build?

@micbou Sorry I should have been more clear. We have a sort of package manager where I work so developers can install things easily without needing root. ycmd is available through that package manager. So I'm afraid I can't provide the link. If you can tell me the commit hash that adds multiple fixits I can ask the guy who's in charge of it, or if there's another way to tell from the built project.

It's this commit: cc87a9c5fc659f488879c3cb5bce090a966cbb70

In fact, it doesn't look like typo correction is kicking in in the following test case:

``` c++
#include <string>

int main( int argc, char ** argv )
{
  std::strng this_is_a_string;
}
```

Diags reported:

```
fixit.cc|5 col 8 error| no type named 'strng' in namespace 'std'
fixit.cc|3 col 15 warning| unused parameter 'argc'
fixit.cc|3 col 29 warning| unused parameter 'argv'
```

And `:YcmShowDetailedDiagnostic`:

```
Forcing compilation, this will block Vim until done.
/Users/ben/Development/tmp/fixit.cc:5:8: error: no type named 'strng' in namespace 'std'
```

So, `libclang` isn't reporting a note or a FixIt (therefore there isn't much we can do).

Just to be sure, I checked and it is not related to the `Incomplete` flag that we use.

Maybe @r4nt or @d0k know if libclang would be expected to offer fixits for typo correction as in the above example? 

Compiling with clang `-fsyntax-only` gives this:

```
BeniMac:tmp ben$ clang -x c++ -fsyntax-only -Wall -Wextra -Werror fixit.cc
fixit.cc:5:8: error: no type named 'strng' in namespace 'std'; did you mean 'string'?
  std::strng this_is_a_string;
  ~~~~~^~~~~
       string
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iosfwd:194:65: note: 'string' declared here
typedef basic_string<char, char_traits<char>, allocator<char> > string;
                                                                ^
1 error generated.
```

Ahah. You need to add `-fspell-checking` to your flags to enable this.

![ycm-fixit-spell-check](https://cloud.githubusercontent.com/assets/10584846/16895966/6480ece6-4b7d-11e6-893d-42000b83b408.gif)

@puremourning Do we perhaps want to pass that by default? Something to consider.

I was considering it. 

The slight con is that typo correction is guesswork, whereas FixIts are generally not going to change the semantics of your program. I guess I sort of ignored that for fixits on notes, principally because there are usually multiple options. In this case, the user doesn't get a "choice", but it is clear in the diagnostic message "; Did you mean 'string'?" So we are probably good to go with it.

At least it is good enough to be on by default when using the `clang` driver, so i don't see why we can't always turn it on.

Seems like a plan; let's turn it on always. We already add some flags by default so it's not without precedent.

PR on its way to fix this and the other bug we had recently with flags (to do with the preprocessor flags: https://github.com/Valloric/ycmd/issues/534). 

Happy to do them together? They're really closely related.


Resolving patch:
diff --git a/ycmd/completers/cpp/flags.py b/ycmd/completers/cpp/flags.py
--- a/ycmd/completers/cpp/flags.py
+++ b/ycmd/completers/cpp/flags.py
@@ -99,7 +99,10 @@ def FlagsForFile( self,
 
       if add_extra_clang_flags:
         flags += self.extra_clang_flags
-      sanitized_flags = PrepareFlagsForClang( flags, filename )
+
+      sanitized_flags = PrepareFlagsForClang( flags,
+                                              filename,
+                                              add_extra_clang_flags )
 
       if results[ 'do_cache' ]:
         self.flags_for_file[ filename ] = sanitized_flags
@@ -172,10 +175,12 @@ def _CallExtraConfFlagsForFile( module, filename, client_data ):
     return module.FlagsForFile( filename )
 
 
-def PrepareFlagsForClang( flags, filename ):
+def PrepareFlagsForClang( flags, filename, add_extra_clang_flags = True ):
   flags = _CompilerToLanguageFlag( flags )
   flags = _RemoveXclangFlags( flags )
   flags = _RemoveUnusedFlags( flags, filename )
+  if add_extra_clang_flags:
+    flags = _EnableTypoCorrection( flags )
   flags = _SanitizeFlags( flags )
   return flags
 
@@ -401,6 +406,24 @@ def _ExtraClangFlags():
   return flags
 
 
+def _EnableTypoCorrection( flags ):
+  """Adds the -fspell-checking flag if the -fno-spell-checking flag is not
+  present"""
+
+  # "Typo correction" (aka spell checking) in clang allows it to produce
+  # hints (in the form of fix-its) in the case of certain diagnostics. A common
+  # example is "no type named 'strng' in namespace 'std'; Did you mean
+  # 'string'? (FixIt)". This is enabled by default in the clang driver (i.e. the
+  # 'clang' binary), but is not when using libclang (as we do). It's a useful
+  # enough feature that we just always turn it on unless the user explicitly
+  # turned it off in their flags (with -fno-spell-checking).
+  if '-fno-spell-checking' in flags:
+    return flags
+
+  flags.append( '-fspell-checking' )
+  return flags
+
+
 def _SpecialClangIncludes():
   libclang_dir = os.path.dirname( ycm_core.__file__ )
   path_to_includes = os.path.join( libclang_dir, 'clang_includes' )


Test patch:
diff --git a/ycmd/tests/clang/flags_test.py b/ycmd/tests/clang/flags_test.py
--- a/ycmd/tests/clang/flags_test.py
+++ b/ycmd/tests/clang/flags_test.py
@@ -185,6 +185,25 @@ def RemoveUnusedFlags_Depfiles_test():
                contains( *expected ) )
 
 
+def EnableTypoCorrection_Empty_test():
+  eq_( flags._EnableTypoCorrection( [] ), [ '-fspell-checking' ] )
+
+
+def EnableTypoCorrection_Trivial_test():
+  eq_( flags._EnableTypoCorrection( [ '-x', 'c++' ] ),
+                                    [ '-x', 'c++', '-fspell-checking' ] )
+
+
+def EnableTypoCorrection_Reciprocal_test():
+  eq_( flags._EnableTypoCorrection( [ '-fno-spell-checking' ] ),
+                                    [ '-fno-spell-checking' ] )
+
+
+def EnableTypoCorrection_ReciprocalOthers_test():
+  eq_( flags._EnableTypoCorrection( [ '-x', 'c++', '-fno-spell-checking' ] ),
+                                    [ '-x', 'c++', '-fno-spell-checking' ] )
+
+
 def RemoveUnusedFlags_RemoveFilenameWithoutPrecedingInclude_test():
   def tester( flag ):
     expected = [ 'clang', flag, '/foo/bar', '-isystem/zoo/goo' ]
diff --git a/ycmd/tests/clang/subcommands_test.py b/ycmd/tests/clang/subcommands_test.py
--- a/ycmd/tests/clang/subcommands_test.py
+++ b/ycmd/tests/clang/subcommands_test.py
@@ -727,12 +727,32 @@ def FixIt_Check_cpp11_Note( results ):
   } ) )
 
 
+def FixIt_Check_cpp11_SpellCheck( results ):
+  assert_that( results, has_entries( {
+    'fixits': contains(
+      # Change to SpellingIsNotMyStrongPoint
+      has_entries( {
+        'text': contains_string( "did you mean 'SpellingIsNotMyStrongPoint'" ),
+        'chunks': contains(
+          ChunkMatcher( 'SpellingIsNotMyStrongPoint',
+                        LineColMatcher( 72, 9 ),
+                        LineColMatcher( 72, 35 ) )
+        ),
+        'location': LineColMatcher( 72, 9 ),
+      } ) )
+  } ) )
+
+
 def Subcommands_FixIt_all_test():
   cfile = 'FixIt_Clang_cpp11.cpp'
   mfile = 'FixIt_Clang_objc.m'
   ufile = 'unicode.cc'
 
   tests = [
+    # L
+    # i   C
+    # n   o
+    # e   l   Lang     File,  Checker
     [ 16, 0,  'cpp11', cfile, FixIt_Check_cpp11_Ins ],
     [ 16, 1,  'cpp11', cfile, FixIt_Check_cpp11_Ins ],
     [ 16, 10, 'cpp11', cfile, FixIt_Check_cpp11_Ins ],
@@ -762,6 +782,9 @@ def Subcommands_FixIt_all_test():
 
     # FixIt attached to a "child" diagnostic (i.e. a Note)
     [ 60, 1,  'cpp11', cfile, FixIt_Check_cpp11_Note ],
+
+    # FixIt due to forced spell checking
+    [ 72, 9,  'cpp11', cfile, FixIt_Check_cpp11_SpellCheck ],
   ]
 
   for test in tests:
diff --git a/ycmd/tests/clang/testdata/FixIt_Clang_cpp11.cpp b/ycmd/tests/clang/testdata/FixIt_Clang_cpp11.cpp
--- a/ycmd/tests/clang/testdata/FixIt_Clang_cpp11.cpp
+++ b/ycmd/tests/clang/testdata/FixIt_Clang_cpp11.cpp
@@ -63,3 +63,11 @@ void z() {
 
   }
 }
+
+namespace Typo {
+    struct SpellingIsNotMyStrongPoint;
+}
+
+void typo() {
+  Typo::SpellingIsNotMyStringPiont *p;
+}