content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  module Glean.Glass.SymbolMap
    (
      SymbolIndex
    , toSymbolIndex
  
    -- * helpers
    , toReferences
    , toDefinitions
  
    ) where
  
  import qualified Data.Map.Strict as Map
  
  import Data.Maybe ( fromMaybe )
  import Data.Int ( Int64 )
  import Data.List as List ( sortBy, groupBy )
  import Data.Ord ( comparing )
  import Data.Function as List ( on )
  
  import Glean.Glass.Types
      ( Range(range_lineBegin),
        SymbolX(..),
        DefinitionSymbolX(..),
        ReferenceRangeSymbolX(..) )
  
  import Glean.Glass.Attributes as Attributes
      ( attrMapToList, attrListToMap )
  
  --
  -- Resolved range version of the API. No bytespans to leak to client
  --
  
  type SymbolIndex = Map.Map Int64 [SymbolX]
  
  -- | Build a sparse map of line numbers to all symbols that span that line
  --
  -- This is a line-indexed map to symbols whose spans cover that line
  -- Lines are 1-indexed.
  --
  -- This structure is useful for clients that use cursor / pointer navigation
  -- to find the symbol under the pointer
  --
  -- In most indexers we have "invisible" symbols whose span is a container, e.g.
  -- in Python, the module span is the entire file. For symbol maps such symbols
  -- are not useful, so we only anchor symbols to the first line they appear on,
  -- when doing navigation maps.
  --
  -- We can have mulitple symbols with identical spans
  --
  toSymbolIndex :: [ReferenceRangeSymbolX] -> [DefinitionSymbolX] -> SymbolIndex
  toSymbolIndex theRefs theDefs = Map.unionWith (++) defs refs
    where
      refs = toLineMap $ map referenceToSymbolX theRefs
      defs = toLineMap $ map definitionToSymbolX theDefs
  
      toLineMap :: [SymbolX] -> SymbolIndex
      toLineMap syms = Map.fromAscList keyvals
        where
          keyvals :: [(Int64, [SymbolX])]
          keyvals =
            map (\xs -> (fst (head xs), map snd xs)) $
            groupBy ((==) `on` fst) $
            sortBy (comparing fst) $
              [ (symbolXToStartLine sym, sym)
              | sym <- syms
              ]
  
  definitionToSymbolX :: DefinitionSymbolX -> SymbolX
  definitionToSymbolX DefinitionSymbolX{..} =
    SymbolX {
      symbolX_sym = definitionSymbolX_sym,
      symbolX_range = fromMaybe definitionSymbolX_range
        definitionSymbolX_nameRange,
      symbolX_target = Nothing,
      symbolX_attributes = Attributes.attrListToMap definitionSymbolX_attributes
    }
  
  referenceToSymbolX :: ReferenceRangeSymbolX -> SymbolX
  referenceToSymbolX ReferenceRangeSymbolX{..} =
    SymbolX {
      symbolX_sym = referenceRangeSymbolX_sym,
      symbolX_range = referenceRangeSymbolX_range,
      symbolX_target = Just referenceRangeSymbolX_target,
      symbolX_attributes =
        Attributes.attrListToMap referenceRangeSymbolX_attributes
    }
  
  -- | Symbols can span multiple lines (e.g. containers). However for the
  -- line-index map, we simply tie symbols to the identifier first line if
  -- available, or just to the first line if not.
  symbolXToStartLine :: SymbolX -> Int64
  symbolXToStartLine SymbolX{..} = range_lineBegin symbolX_range
  
  --
  -- Elimination functions for testing
  --
  
  -- | Extract references (xrefs) from the symbol map
  toReferences :: SymbolIndex -> [ReferenceRangeSymbolX]
  toReferences m =
    [ ReferenceRangeSymbolX {
        referenceRangeSymbolX_sym = symbolX_sym,
        referenceRangeSymbolX_range = symbolX_range,
        referenceRangeSymbolX_target = actual_target,
        referenceRangeSymbolX_attributes =
          Attributes.attrMapToList symbolX_attributes
      }
    | lines <- Map.elems m
    , SymbolX { symbolX_target = Just actual_target, .. } <- lines
    ]
  
  -- | Extract definitions from the symbol map
  toDefinitions :: SymbolIndex -> [DefinitionSymbolX]
  toDefinitions m =
    [ DefinitionSymbolX {
        definitionSymbolX_sym = symbolX_sym,
        definitionSymbolX_range = symbolX_range,
        definitionSymbolX_nameRange = Just symbolX_range,
        definitionSymbolX_attributes =
          Attributes.attrMapToList symbolX_attributes
      }
    | lines <- Map.elems m
    , SymbolX { symbolX_target = Nothing, .. } <- lines
    ]
  