Repository: ycm-core/ycmd

Issue:
Go completion kicking in even when built without --gocode-completer
Leading on from https://github.com/Valloric/YouCompleteMe/issues/1603#issuecomment-135237881, it seems that ycmd is enabling Go completion even when the `--gocode-completer` flag isn't provided on build; this is because if `gocode` and `godef` aren't found where they would be built given that flag, if they're in `PATH` they'll still be found and used regardless.

This seems like a bug, since I may want to make use of another plugin (vim-go in this case) for providing completion for Go files, while still making use of YCM for other languages.



Discussion:
Have you tried using [this YCM option](https://github.com/Valloric/YouCompleteMe#the-gycm_filetype_specific_completion_to_disable-option)?

Turning semantic completion on/off is a client-level decision (so done in YCM for instance).

@Valloric he doesn't want to disable semantic completion for `go` entirely but use the `omnifunc` which is backed by `gocode` as well but `vim-go` add more stuff to it like parameter completion. IMHO we should not allow our implementation to use binary found on the PATH, but only use the one we ship.


Resolving patch:
diff --git a/ycmd/completers/go/gocode_completer.py b/ycmd/completers/go/gocode_completer.py
--- a/ycmd/completers/go/gocode_completer.py
+++ b/ycmd/completers/go/gocode_completer.py
@@ -30,10 +30,9 @@
 
 from ycmd import responses
 from ycmd import utils
-from ycmd.utils import ToBytes, ToUnicode
+from ycmd.utils import ToBytes, ToUnicode, ExecutableName
 from ycmd.completers.completer import Completer
 
-GO_FILETYPES = set( [ 'go' ] )
 BINARY_NOT_FOUND_MESSAGE = ( '{0} binary not found. Did you build it? ' +
                              'You can do so by running ' +
                              '"./install.py --gocode-completer".' )
@@ -42,43 +41,67 @@
 NO_COMPLETIONS_MESSAGE = 'Gocode returned empty JSON response.'
 GOCODE_PANIC_MESSAGE = ( 'Gocode panicked trying to find completions, ' +
                          'you likely have a syntax error.' )
-PATH_TO_GOCODE_BINARY = os.path.join(
+DIR_OF_THIRD_PARTY = os.path.join(
   os.path.abspath( os.path.dirname( __file__ ) ),
-  '..', '..', '..', 'third_party', 'gocode',
-  'gocode' + ( '.exe' if utils.OnWindows() else '' ) )
-PATH_TO_GODEF_BINARY = os.path.join(
-  os.path.abspath( os.path.dirname( __file__ ) ),
-  '..', '..', '..', 'third_party', 'godef',
-  'godef' + ( '.exe' if utils.OnWindows() else '' ) )
+  '..', '..', '..', 'third_party' )
+GO_BINARIES = dict( {
+  'gocode': os.path.join( DIR_OF_THIRD_PARTY,
+                          'gocode',
+                          ExecutableName( 'gocode' ) ),
+  'godef': os.path.join( DIR_OF_THIRD_PARTY,
+                         'godef',
+                         ExecutableName( 'godef' ) )
+} )
 
 _logger = logging.getLogger( __name__ )
 
 
-class GoCodeCompleter( Completer ):
+def FindBinary( binary, user_options ):
+  """ Find the path to the gocode/godef binary.
 
-  def __init__( self, user_options ):
-    super( GoCodeCompleter, self ).__init__( user_options )
-    self._popener = utils.SafePopen # Overridden in test.
-    self._binary_gocode = self.FindBinary( 'gocode', user_options )
-    self._binary_godef = self.FindBinary( 'godef', user_options )
+  If 'gocode_binary_path' or 'godef_binary_path'
+  in the options is blank, use the version installed
+  with YCM, if it exists.
 
-    if not self._binary_gocode:
-      _logger.error( BINARY_NOT_FOUND_MESSAGE.format( 'Gocode' ) )
-      raise RuntimeError( BINARY_NOT_FOUND_MESSAGE.format( 'Gocode' ) )
+  If the 'gocode_binary_path' or 'godef_binary_path' is
+  specified, use it as an absolute path.
 
-    _logger.info( 'Enabling go completion using %s binary',
-                  self._binary_gocode )
+  If the resolved binary exists, return the path,
+  otherwise return None. """
 
-    if not self._binary_godef:
-      _logger.error( BINARY_NOT_FOUND_MESSAGE.format( 'Godef' ) )
-      raise RuntimeError( BINARY_NOT_FOUND_MESSAGE.format( 'Godef' ) )
+  def _FindPath():
+    key = '{0}_binary_path'.format( binary )
+    if user_options.get( key ):
+      return user_options[ key ]
+    return GO_BINARIES.get( binary )
 
-    _logger.info( 'Enabling go definitions using %s binary',
-                   self._binary_godef )
+  binary_path = _FindPath()
+  if os.path.isfile( binary_path ):
+    return binary_path
+  return None
+
+
+def ShouldEnableGoCompleter( user_options ):
+  def _HasBinary( binary ):
+    binary_path = FindBinary( binary, user_options )
+    if not binary_path:
+      _logger.error( BINARY_NOT_FOUND_MESSAGE.format( binary ) )
+    return binary_path
+
+  return all( _HasBinary( binary ) for binary in [ 'gocode', 'godef' ] )
+
+
+class GoCodeCompleter( Completer ):
+
+  def __init__( self, user_options ):
+    super( GoCodeCompleter, self ).__init__( user_options )
+    self._popener = utils.SafePopen # Overridden in test.
+    self._binary_gocode = FindBinary( 'gocode', user_options )
+    self._binary_godef = FindBinary( 'godef', user_options )
 
 
   def SupportedFiletypes( self ):
-    return GO_FILETYPES
+    return [ 'go' ]
 
 
   def ComputeCandidatesInner( self, request_data ):
@@ -127,34 +150,6 @@ def GetSubcommandsMap( self ):
     }
 
 
-  def FindBinary( self, binary, user_options ):
-    """ Find the path to the gocode/godef binary.
-
-    If 'gocode_binary_path' or 'godef_binary_path'
-    in the options is blank, use the version installed
-    with YCM, if it exists, then the one on the path, if not.
-
-    If the 'gocode_binary_path' or 'godef_binary_path' is
-    specified, use it as an absolute path.
-
-    If the resolved binary exists, return the path,
-    otherwise return None. """
-    if user_options.get( '%s_binary_path' % binary ):
-      # The user has explicitly specified a path.
-      if os.path.isfile( user_options[ '%s_binary_path' % binary] ):
-        return user_options[ '%s_binary_path' % binary]
-      else:
-        return None
-    # Try to use the bundled binary or one on the path.
-    if binary == 'gocode':
-      if os.path.isfile( PATH_TO_GOCODE_BINARY ):
-        return PATH_TO_GOCODE_BINARY
-    elif binary == 'godef':
-      if os.path.isfile( PATH_TO_GODEF_BINARY ):
-        return PATH_TO_GODEF_BINARY
-    return utils.PathToFirstExistingExecutable( [ binary ] )
-
-
   def OnFileReadyToParse( self, request_data ):
     self._StartServer()
 
diff --git a/ycmd/completers/go/hook.py b/ycmd/completers/go/hook.py
--- a/ycmd/completers/go/hook.py
+++ b/ycmd/completers/go/hook.py
@@ -23,7 +23,12 @@
 standard_library.install_aliases()
 from builtins import *  # noqa
 
-from ycmd.completers.go.gocode_completer import GoCodeCompleter
+from ycmd.completers.go.gocode_completer import (
+    GoCodeCompleter, ShouldEnableGoCompleter )
+
 
 def GetCompleter( user_options ):
+  if not ShouldEnableGoCompleter( user_options ):
+    return None
+
   return GoCodeCompleter( user_options )
diff --git a/ycmd/utils.py b/ycmd/utils.py
--- a/ycmd/utils.py
+++ b/ycmd/utils.py
@@ -194,6 +194,10 @@ def FindExecutable( executable ):
   return None
 
 
+def ExecutableName( executable ):
+  return executable + ( '.exe' if OnWindows() else '' )
+
+
 def OnWindows():
   return sys.platform == 'win32'
 


Test patch:
diff --git a/ycmd/tests/go/gocode_completer_test.py b/ycmd/tests/go/gocode_completer_test.py
--- a/ycmd/tests/go/gocode_completer_test.py
+++ b/ycmd/tests/go/gocode_completer_test.py
@@ -27,8 +27,8 @@
 
 import os
 from nose.tools import eq_, raises
-from ycmd.completers.go.gocode_completer import ( GoCodeCompleter,
-                                                  PATH_TO_GOCODE_BINARY )
+from ycmd.completers.go.gocode_completer import ( GoCodeCompleter, GO_BINARIES,
+                                                  FindBinary )
 from ycmd.request_wrap import RequestWrap
 from ycmd import user_options_store
 from ycmd.utils import ReadFile
@@ -71,16 +71,13 @@ def _BuildRequest( self, line_num, column_num ):
   def FindGoCodeBinary_test( self ):
     user_options = user_options_store.DefaultOptions()
 
-    eq_( PATH_TO_GOCODE_BINARY,
-         self._completer.FindBinary( "gocode", user_options ) )
+    eq_( GO_BINARIES.get( "gocode" ), FindBinary( "gocode", user_options ) )
 
     user_options[ 'gocode_binary_path' ] = DUMMY_BINARY
-    eq_( DUMMY_BINARY,
-         self._completer.FindBinary( "gocode", user_options ) )
+    eq_( DUMMY_BINARY, FindBinary( "gocode", user_options ) )
 
     user_options[ 'gocode_binary_path' ] = DATA_DIR
-    eq_( None,
-         self._completer.FindBinary( "gocode", user_options ) )
+    eq_( None, FindBinary( "gocode", user_options ) )
 
 
   # Test line-col to offset in the file before any unicode occurrences.