Repository: ycm-core/ycmd

Issue:
Tern completer: Recognise ~/.tern-config
## Problem

Tern supports a "global" project file (`~/.tern-config`) which contains global defaults. In theory this can be used instead of `.tern-project`.

The ycmd tern completer does not check for this file and issues its warning anyway if no `.tern-project` file could be found.
## Requirement

Don't issue a warning when a `.tern-config` file exists, as this could well contain enough info for useful javascript completion for many projects.



Discussion:

Resolving patch:
diff --git a/ycmd/completers/javascript/tern_completer.py b/ycmd/completers/javascript/tern_completer.py
--- a/ycmd/completers/javascript/tern_completer.py
+++ b/ycmd/completers/javascript/tern_completer.py
@@ -70,6 +70,13 @@ def ShouldEnableTernCompleter():
   return True
 
 
+def GlobalConfigExists( tern_config ):
+  """Returns whether or not the global config file with the supplied path
+  exists. This method primarily exists to allow testability and simply returns
+  whether the supplied file exists."""
+  return os.path.exists( tern_config )
+
+
 def FindTernProjectFile( starting_directory ):
   # We use a dummy_file here because AncestorFolders requires a file name and we
   # don't have one. Something like '.' doesn't work because, while
@@ -81,6 +88,16 @@ def FindTernProjectFile( starting_directory ):
     if os.path.exists( tern_project ):
       return tern_project
 
+  # As described here: http://ternjs.net/doc/manual.html#server a global
+  # .tern-config file is also supported for the Tern server. This can provide
+  # meaningful defaults (for libs, and possibly also for require paths), so
+  # don't warn if we find one. The point is that if the user has a .tern-config
+  # set up, then she has deliberately done so and a ycmd warning is unlikely
+  # to be anything other than annoying.
+  tern_config = os.path.expanduser( '~/.tern-config' )
+  if GlobalConfigExists( tern_config ):
+    return tern_config
+
   return None
 
 
@@ -107,6 +124,11 @@ def __init__( self, user_options ):
 
 
   def _WarnIfMissingTernProject( self ):
+    # The Tern server will operate without a .tern-project file. However, it
+    # does not operate optimally, and will likely lead to issues reported that
+    # JavaScript completion is not working properly. So we raise a warning if we
+    # aren't able to detect some semblance of manual Tern configuration.
+
     # We do this check after the server has started because the server does
     # have nonzero use without a project file, however limited. We only do this
     # check once, though because the server can only handle one project at a
@@ -120,7 +142,8 @@ def _WarnIfMissingTernProject( self ):
       if not tern_project:
         _logger.warning( 'No .tern-project file detected: ' + os.getcwd() )
         raise RuntimeError( 'Warning: Unable to detect a .tern-project file '
-                            'in the hierarchy before ' + os.getcwd() + '. '
+                            'in the hierarchy before ' + os.getcwd() +
+                            ' and no global .tern-config file was found. '
                             'This is required for accurate JavaScript '
                             'completion. Please see the User Guide for '
                             'details.' )


Test patch:
diff --git a/ycmd/tests/javascript/event_notification_test.py b/ycmd/tests/javascript/event_notification_test.py
--- a/ycmd/tests/javascript/event_notification_test.py
+++ b/ycmd/tests/javascript/event_notification_test.py
@@ -26,7 +26,7 @@
 from pprint import pformat
 import httplib
 import os
-
+from mock import patch
 
 class Javascript_EventNotification_test( Javascript_Handlers_test ):
 
@@ -60,7 +60,9 @@ def OnFileReadyToParse_ProjectFile_parentdir_test( self ):
     assert_that( response.json, empty() )
 
 
-  def OnFileReadyToParse_NoProjectFile_test( self ):
+  @patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists',
+          return_value = False )
+  def OnFileReadyToParse_NoProjectFile_test( self, *args ):
     # We raise an error if we can't detect a .tern-project file.
     # We only do this on the first OnFileReadyToParse event after a
     # server startup.
@@ -83,7 +85,8 @@ def OnFileReadyToParse_NoProjectFile_test( self ):
       response.json,
       self._ErrorMatcher( RuntimeError,
                           'Warning: Unable to detect a .tern-project file '
-                          'in the hierarchy before ' + os.getcwd() + '. '
+                          'in the hierarchy before ' + os.getcwd() +
+                          ' and no global .tern-config file was found. '
                           'This is required for accurate JavaScript '
                           'completion. Please see the User Guide for '
                           'details.' )
@@ -136,8 +139,28 @@ def OnFileReadyToParse_NoProjectFile_test( self ):
       response.json,
       self._ErrorMatcher( RuntimeError,
                           'Warning: Unable to detect a .tern-project file '
-                          'in the hierarchy before ' + os.getcwd() + '. '
+                          'in the hierarchy before ' + os.getcwd() +
+                          ' and no global .tern-config file was found. '
                           'This is required for accurate JavaScript '
                           'completion. Please see the User Guide for '
                           'details.' )
     )
+
+
+  @patch( 'ycmd.completers.javascript.tern_completer.GlobalConfigExists',
+          return_value = True )
+  def OnFileReadyToParse_UseGlobalConfig_test( self, *args ):
+    os.chdir( self._PathToTestFile( '..' ) )
+
+    contents = open( self._PathToTestFile( 'simple_test.js' ) ).read()
+
+    response = self._app.post_json( '/event_notification',
+                                    self._BuildRequest(
+                                      event_name = 'FileReadyToParse',
+                                      contents = contents,
+                                      filetype = 'javascript' ),
+                                    expect_errors = True )
+
+    print( 'event response: {0}'.format( pformat( response.json ) ) )
+
+    eq_( response.status_code, httplib.OK )