Skip to content

Commit

Permalink
Add tsserver call hierarchy support
Browse files Browse the repository at this point in the history
  • Loading branch information
bstaletic committed Jun 13, 2024
1 parent 9e73042 commit bb99ee5
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 5 deletions.
82 changes: 82 additions & 0 deletions ycmd/completers/typescript/typescript_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,11 @@ def GetSubcommandsMap( self ):
self._RefactorRename( request_data, args ) ),
'Format' : ( lambda self, request_data, args:
self._Format( request_data ) ),
'CallHierarchy' : ( lambda self, request_data, args:
self._InitialHierarchy(
request_data, [ 'call' ] ) ),
'ResolveCallHierarchyItem': ( lambda self, request_data, args:
self._Hierarchy( request_data, args ) ),
}


Expand Down Expand Up @@ -751,6 +756,36 @@ def _GetSyntacticDiagnostics( self, filename ):
} )


def _InitialHierarchy( self, request_data, args ):
self._Reload( request_data )
response = self._SendRequest( 'prepareCallHierarchy', {
'file': request_data[ 'filepath' ],
'line': request_data[ 'line_num' ],
'offset': request_data[ 'column_codepoint' ]
} )

if isinstance( response, dict ):
response = [ response ]

assert len( response ) == 1, (
'Not available: Multiple hierarchies were received, '
'this is not currently supported.' )

response[ 0 ][ 'locations' ] = []
for loc in response:
start_position = response[ 0 ][ 'selectionSpan' ][ 'start' ]
goto_line = start_position[ 'line' ]
goto_column = utils.CodepointOffsetToByteOffset(
request_data[ 'line_value' ],
start_position[ 'offset' ] )
response[ 0 ][ 'locations' ].append( responses.BuildGoToResponse(
request_data[ 'filepath' ],
goto_line,
goto_column,
request_data[ 'line_value' ] ) )
return response


def _CallHierarchy( self, request_data, args ):
self._Reload( request_data )

Expand Down Expand Up @@ -784,6 +819,53 @@ def _CallHierarchy( self, request_data, args ):
raise RuntimeError( f'No { args[ 0 ].lower() } calls found.' )


def _Hierarchy( self, request_data, args ):
self._Reload( request_data )
preparation_item, direction = args
if item := ( preparation_item.get( 'from' ) or
preparation_item.get( 'to' ) ):
preparation_item = item
start_loc = preparation_item[ 'selectionSpan' ][ 'start' ]
start_loc[ 'file' ] = preparation_item[ 'file' ]
response = self._SendRequest(
f'provideCallHierarchy{ direction.title() }Calls',
start_loc )
if response:
for item in response:
root_object = item.get( 'to' ) or item[ 'from' ]
filepath = root_object[ 'file' ]
item[ 'locations' ] = []
item[ 'name' ] = root_object[ 'name' ]
item[ 'kind' ] = root_object[ 'kind' ]
for loc in item[ 'fromSpans' ]:
start_position = loc[ 'start' ]
goto_line = start_position[ 'line' ]
line_value = GetFileLines( request_data, filepath )[ goto_line - 1 ]
goto_column = utils.CodepointOffsetToByteOffset(
line_value,
start_position[ 'offset' ] )
item[ 'locations' ].append( responses.BuildGoToResponse(
filepath,
goto_line,
goto_column,
line_value
) )
if direction == 'incoming':
start_position = root_object[ 'selectionSpan' ][ 'start' ]
goto_line = start_position[ 'line' ]
line_value = GetFileLines( request_data, filepath )[ goto_line - 1 ]
goto_column = utils.CodepointOffsetToByteOffset(
line_value,
start_position[ 'offset' ] )
item[ 'root_location' ] = responses.BuildGoToResponse(
filepath,
goto_line,
goto_column,
line_value )
return response
raise RuntimeError( f'No { direction } calls found.' )


def _GoToDefinition( self, request_data ):
self._Reload( request_data )
filespans = self._SendRequest( 'definition', {
Expand Down
4 changes: 3 additions & 1 deletion ycmd/tests/javascript/subcommands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ def test_Subcommands_DefinedSubcommands( self, app ):
'FixIt',
'OrganizeImports',
'RefactorRename',
'RestartServer'
'RestartServer',
'CallHierarchy',
'ResolveCallHierarchyItem'
)
)

Expand Down
125 changes: 121 additions & 4 deletions ycmd/tests/typescript/subcommands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,38 @@ def RunTest( app, test ):

print( f'completer response: { pprint.pformat( response.json ) }' )

assert_that( response.status_code,
equal_to( test[ 'expect' ][ 'response' ] ) )

assert_that( response.json, test[ 'expect' ][ 'data' ] )
if 'expect' in test:
assert_that( response.status_code,
equal_to( test[ 'expect' ][ 'response' ] ) )

assert_that( response.json, test[ 'expect' ][ 'data' ] )
return response.json


def RunHierarchyTest( app, kind, direction, location, expected, code ):
file, line, column = location
request = {
'completer_target' : 'filetype_default',
'command': f'{ kind.title() }Hierarchy',
'line_num' : line,
'column_num' : column,
'filepath' : file,
}
test = { 'request': request,
'route': '/run_completer_command' }
prepare_hierarchy_response = RunTest( app, test )
request.update( {
'command': f'Resolve{ kind.title() }HierarchyItem',
'arguments': [
prepare_hierarchy_response[ 0 ],
direction
]
} )
test[ 'expect' ] = {
'response': code,
'data': expected
}
RunTest( app, test )


def Subcommands_GoTo_Basic( app, goto_command ):
Expand Down Expand Up @@ -140,6 +168,8 @@ def test_Subcommands_DefinedSubcommands( self, app ):
app.post_json( '/defined_subcommands', subcommands_data ).json,
contains_inanyorder(
'Format',
'CallHierarchy',
'ResolveCallHierarchyItem',
'GoTo',
'GoToCallees',
'GoToCallers',
Expand Down Expand Up @@ -1167,3 +1197,90 @@ def test_Subcommands_StopServer_Timeout( self, app ):
has_entry( 'is_running', False )
) )
) )


@SharedYcmd
def test_Subcommands_OutgoingCallHierarchy( self, app ):
filepath = PathToTestFile( 'hierarchies.ts' )
for location, response, code in [
[ ( filepath, 9, 10 ),
contains_inanyorder(
has_entry( 'locations',
contains_exactly(
LocationMatcher( filepath, 10, 11 ),
) ),
has_entry( 'locations',
contains_exactly(
LocationMatcher( filepath, 11, 14 )
) ) ),
requests.codes.ok ],
[ ( filepath, 5, 10 ),
contains_inanyorder(
has_entry( 'locations',
contains_exactly(
LocationMatcher( filepath, 6, 10 ),
LocationMatcher( filepath, 6, 16 )
) ) ),
requests.codes.ok ],
[ ( filepath, 1, 10 ),
ErrorMatcher( RuntimeError, 'No outgoing calls found.' ),
requests.codes.server_error ]
]:
with self.subTest( location = location, response = response ):
RunHierarchyTest( app, 'call', 'outgoing', location, response, code )


@SharedYcmd
def test_Subcommands_IncomingCallHierarchy( self, app ):
filepath = PathToTestFile( 'hierarchies.ts' )
for location, response, code in [
[ ( filepath, 1, 10 ),
contains_inanyorder(
has_entries( {
'locations': contains_exactly(
LocationMatcher( filepath, 6, 10 ),
LocationMatcher( filepath, 6, 16 ) ),
'root_location': LocationMatcher( filepath, 5, 10 )
} ),
has_entries( {
'locations': contains_exactly(
LocationMatcher( filepath, 11, 14 ) ),
'root_location': LocationMatcher( filepath, 9, 10 )
} )
),
requests.codes.ok ],
[ ( filepath, 5, 10 ),
contains_inanyorder(
has_entries( {
'locations': contains_exactly(
LocationMatcher( filepath, 10, 11 ) ),
'root_location': LocationMatcher( filepath, 9, 10 )
} )
),
requests.codes.ok ],
[ ( filepath, 9, 10 ),
ErrorMatcher( RuntimeError, 'No incoming calls found.' ),
requests.codes.server_error ]
]:
with self.subTest( location = location, response = response ):
RunHierarchyTest( app, 'call', 'incoming', location, response, code )


@SharedYcmd
def test_Subcommands_NoHierarchyFound( self, app ):
filepath = PathToTestFile( 'hierarchies.ts' )
request = {
'completer_target' : 'filetype_default',
'command': 'CallHierarchy',
'line_num' : 4,
'column_num' : 1,
'filepath' : filepath,
'filetype' : 'typescript'
}
test = { 'request': request,
'route': '/run_completer_command',
'expect': {
'response': requests.codes.server_error,
'data': ErrorMatcher(
RuntimeError, 'No content available.' ) } }
RunTest( app, test )
12 changes: 12 additions & 0 deletions ycmd/tests/typescript/testdata/hierarchies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function f(){
return 5;
}

function g() {
return f() + f();
}

function h() {
var a = g();
return a + f();
}

0 comments on commit bb99ee5

Please sign in to comment.