public class FcsFileCapturedInfo()

in ReSharper.FSharp/src/FSharp/FSharp.Psi/src/Resolve/FcsFileCapturedInfo.cs [27:375]


  public class FcsFileCapturedInfo([NotNull] IPsiSourceFile sourceFile) : IFcsFileCapturedInfo
  {
    private ResolvedSymbols mySymbols;
    private IDictionary<Position, FSharpDiagnostic> myDiagnostics;
    private readonly object myLock = new();

    [NotNull] public IPsiSourceFile SourceFile { get; } = sourceFile;

    private ResolvedSymbols GetResolvedSymbols()
    {
      using var cookie = MonitorInterruptibleCookie.EnterOrThrow(myLock);
      return mySymbols ??= CreateFileResolvedSymbols();
    }

    private IDictionary<Position, FSharpDiagnostic> GetCachedDiagnostics()
    {
      using var cookie = MonitorInterruptibleCookie.EnterOrThrow(myLock);
      return myDiagnostics ??= CreateDiagnostics();
    }

    public FSharpSymbolUse GetSymbolUse(int offset)
    {
      var resolvedSymbols = GetResolvedSymbols();

      var resolvedSymbol = resolvedSymbols.Uses.TryGetValue(offset);
      if (resolvedSymbol == null)
        return null;

      return resolvedSymbols.Declarations.TryGetValue(offset) == null
        ? resolvedSymbol.SymbolUse
        : null;
    }

    public FSharpSymbolUse GetSymbolDeclaration(int offset)
    {
      var resolvedSymbols = GetResolvedSymbols();
      return resolvedSymbols.Declarations.TryGetValue(offset)?.SymbolUse;
    }

    public IReadOnlyList<FcsResolvedSymbolUse> GetAllDeclaredSymbols()
    {
      var resolvedSymbols = GetResolvedSymbols();
      return resolvedSymbols.Declarations.Values.AsChunkIReadOnlyList();
    }

    public IReadOnlyList<FcsResolvedSymbolUse> GetAllResolvedSymbols()
    {
      var resolvedSymbols = GetResolvedSymbols();
      return resolvedSymbols.Uses.Values.AsChunkIReadOnlyList();
    }

    public FSharpSymbol GetSymbol(int offset) =>
      GetSymbolDeclaration(offset)?.Symbol ?? GetSymbolUse(offset)?.Symbol;

    public FSharpDiagnostic GetDiagnostic(Position pos) =>
      GetCachedDiagnostics().TryGetValue(pos);

    public void SetCachedDiagnostics(IDictionary<Position, FSharpDiagnostic> diagnostics)
    {
      using var cookie = MonitorInterruptibleCookie.EnterOrThrow(myLock);
      myDiagnostics ??= diagnostics;
    }

    [NotNull]
    private IDictionary<Position, FSharpDiagnostic> CreateDiagnostics()
    {
      const string opName = "FcsFileCapturedInfo.CreateDiagnostics";

      var fsFile = SourceFile.GetPrimaryPsiFile() as IFSharpFile;
      var checkResults = fsFile?.GetParseAndCheckResults(false, opName)?.Value.CheckResults;
      if (checkResults == null)
        return EmptyDictionary<Position, FSharpDiagnostic>.Instance;

      var result = new Dictionary<Position, FSharpDiagnostic>();
      foreach (var diagnostic in checkResults.Diagnostics)
      {
        if (FcsCachedDiagnosticInfo.CanBeCached(diagnostic))
          result[diagnostic.Range.Start] = diagnostic;
      }

      return result;
    }

    [NotNull]
    private ResolvedSymbols CreateFileResolvedSymbols()
    {
      const string opName = "FcsFileCapturedInfo.CreateFileResolvedSymbols";

      var fsFile = SourceFile.GetPrimaryPsiFile() as IFSharpFile;
      var checkResults = fsFile?.GetParseAndCheckResults(false, opName)?.Value.CheckResults;
      var symbolUses = checkResults?.GetAllUsesOfAllSymbolsInFile(null);
      if (symbolUses == null)
        return ResolvedSymbols.Empty;

      var document = SourceFile.Document;
      var lexer = fsFile.CachingLexer;
      var buffer = document.Buffer;
      var resolvedSymbols = new ResolvedSymbols(); // todo: set length to avoid copying
      foreach (var symbolUse in symbolUses)
      {
        var symbol = symbolUse.Symbol;
        var range = symbolUse.Range;
        if (range.IsSynthetic)
          continue;

        var startOffset = document.GetOffset(range.Start);
        var endOffset = document.GetOffset(range.End);
        var mfv = symbol as FSharpMemberOrFunctionOrValue;
        var activePatternCase = symbol as FSharpActivePatternCase;

        if (symbolUse.IsFromDefinition)
        {
          if (mfv != null)
          {
            try
            {
              // workaround for auto-properties, see visualfsharp#3939
              var mfvLogicalName = mfv.LogicalName;
              if (mfvLogicalName.EndsWith("@", StringComparison.Ordinal))
                continue;

              // visualfsharp#3939
              if (mfvLogicalName == "v" &&
                  resolvedSymbols.Declarations.ContainsKey(startOffset))
                continue;

              if (mfvLogicalName == StandardMemberNames.ClassConstructor)
                continue;

              // visualfsharp#3943, visualfsharp#3933
              if (mfvLogicalName != StandardMemberNames.Constructor &&
                  !(lexer.FindTokenAt(endOffset - 1) && (lexer.TokenType?.IsIdentifier ?? false) || mfv.IsActivePattern))
                continue;

              if (mfvLogicalName == "Invoke" && (mfv.DeclaringEntity?.Value?.IsDelegate ?? false))
                continue;

              var len = endOffset - startOffset;
              if (mfvLogicalName == "op_Multiply" && len == 3)
              {
                // The `*` pattern includes parens and is parsed as special token
                // let (*) (_, _) = ()
                startOffset++;
                endOffset--;
              }
            }
            catch (Exception)
            {
              continue;
            }
          }
          else if (activePatternCase != null)
          {
            // Skip active pattern cases bindings as these have incorrect ranges.
            // Active pattern cases uses inside bindings are currently marked as bindings so check the range.
            // https://github.com/Microsoft/visualfsharp/issues/4423
            if (RangeModule.equals(activePatternCase.DeclarationLocation, range))
            {
              var activePatternId = fsFile.GetContainingNodeAt<ActivePatternId>(new TreeOffset(endOffset - 1));
              if (activePatternId == null)
                continue;

              var cases = activePatternId.Cases;
              var caseIndex = activePatternCase.Index;
              if (caseIndex < 0 || caseIndex >= cases.Count)
                continue;

              if (!(cases[caseIndex] is IActivePatternNamedCaseDeclaration caseDeclaration))
                continue;

              var (caseStart, caseEnd) = caseDeclaration.GetTreeTextRange();
              var caseStartOffset = caseStart.Offset;
              var caseTextRange = new TextRange(caseStartOffset, caseEnd.Offset);
              resolvedSymbols.Declarations[caseStartOffset] = new FcsResolvedSymbolUse(symbolUse, caseTextRange);
              continue;
            }

            var caseUseInBindingRange = new TextRange(startOffset, endOffset);
            resolvedSymbols.Uses[startOffset] = new FcsResolvedSymbolUse(symbolUse, caseUseInBindingRange);
            continue;
          }
          else
          {
            // workaround for compiler generated symbols (e.g. fields auto-properties)
            if (!(lexer.FindTokenAt(endOffset - 1) && (lexer.TokenType?.IsIdentifier ?? false)))
              continue;
          }

          var textRange = mfv != null
            ? new TextRange(startOffset, endOffset)
            : FixRange(startOffset, endOffset, null, buffer, lexer);
          startOffset = textRange.StartOffset;

          if (IsPropertyGetter(resolvedSymbols.Declarations.TryGetValue(startOffset)?.SymbolUse))
            continue;

          resolvedSymbols.Declarations[startOffset] = new FcsResolvedSymbolUse(symbolUse, textRange);
          resolvedSymbols.Uses.Remove(startOffset);
        }
        else
        {
          if (mfv is { IsBaseValue: true })
            continue;
          
          // workaround for indexer properties, visualfsharp#3933
          if (startOffset == endOffset || mfv is { IsProperty: true } && buffer[endOffset - 1] == ']')
            continue;

          var entity =
            symbol as FSharpEntity ?? (mfv is { IsConstructor: true } ? mfv.DeclaringEntity?.Value : null);

          // we need `foo` in
          // inherit mod.foo<bar.baz>()
          if (entity != null)
          {
            var isStaticInstantiation = entity.IsStaticInstantiation;
            if (!entity.GenericParameters.IsEmpty() || isStaticInstantiation)
            {
              if (lexer.FindTokenAt(endOffset - 1) && lexer.TokenType == FSharpTokenType.GREATER)
              {
                if (new ParenMatcher().FindMatchingBracket(lexer) && lexer.TokenStart >= startOffset)
                {
                  lexer.Advance(-1);
                  if (lexer.TokenType != null)
                  {
                    if (isStaticInstantiation && resolvedSymbols.Uses.ContainsKey(startOffset))
                      continue;

                    startOffset = lexer.TokenStart;
                    endOffset = lexer.TokenEnd;
                  }
                }
              }
            }
          }

          var mfvLogicalName = mfv?.LogicalName;
          var nameRange = FixRange(startOffset, endOffset, mfvLogicalName, buffer, lexer);
          startOffset = nameRange.StartOffset;

          var isCtor = mfv is { IsConstructor: true };

          // workaround for implicit type usages (e.g. in members with optional params), visualfsharp#3933
          if ((CanIgnoreSymbol(symbol, isCtor) || CanIgnoreMfv(mfvLogicalName)) &&
              !(lexer.FindTokenAt(nameRange.EndOffset - 1) && (lexer.TokenType?.IsIdentifier ?? false)))
            continue;

          if (mfvLogicalName == "GetReverseIndex" && lexer.TokenType == FSharpTokenType.SYMBOLIC_OP)
            continue;

          // IsFromPattern helps in cases where fake value is created at range,
          // e.g. `fun Literal -> ()` has both pattern and binding symbols at pattern range.
          if (symbolUse.IsFromPattern || !resolvedSymbols.Declarations.ContainsKey(startOffset))
          {
            if (resolvedSymbols.Uses.TryGetValue(startOffset, out var existingSymbol) &&
                existingSymbol.SymbolUse.Symbol is FSharpEntity && !isCtor && symbol is not FSharpEntity)
              continue;

            resolvedSymbols.Uses[startOffset] = new FcsResolvedSymbolUse(symbolUse, nameRange);
          }

          if (symbolUse.IsFromPattern)
            resolvedSymbols.Declarations.Remove(startOffset);
        }

        Interruption.Current.CheckAndThrow();
      }

      return resolvedSymbols;
    }

    private static bool IsPropertyGetter(FSharpSymbolUse fcsSymbolUse)
    {
      if (fcsSymbolUse?.Symbol is FSharpMemberOrFunctionOrValue mfv)
      {
        return mfv.AccessorProperty != null;
      }

      return false;
    }

    private static bool CanIgnoreSymbol([NotNull] FSharpSymbol symbol, bool isCtor) =>
      isCtor || symbol is FSharpEntity;

    private static bool CanIgnoreMfv([CanBeNull] string n) =>
      n == "op_Range" || n == "op_RangeStep" || n == "GetReverseIndex" || n == "GetSlice";

    private TextRange FixRange(int startOffset, int endOffset, [CanBeNull] string logicalName, IBuffer buffer,
      CachingLexer lexer)
    {
      // todo: remove when visualfsharp#3920 is implemented

      // trim foo.``bar`` to ``bar``
      const int minimumEscapedNameLength = 5;
      if (endOffset >= minimumEscapedNameLength && buffer.Length >= minimumEscapedNameLength &&
          buffer[endOffset - 1] == '`' && buffer[endOffset - 2] == '`')
        for (var i = endOffset - 4; i >= startOffset; i--)
          if (buffer[i] == '`' && buffer[i + 1] == '`')
            return new TextRange(i, endOffset);

      if (logicalName != null && PrettyNaming.IsLogicalOpName(logicalName))
      {
        var sourceName = PrettyNaming.ConvertValLogicalNameToDisplayNameCore(logicalName);
        var isUnary = sourceName.StartsWith("~", StringComparison.Ordinal);
        var sourceLength = isUnary ? sourceName.Length - 1 : sourceName.Length;

        if (sourceLength == endOffset - startOffset)
          return new TextRange(startOffset, endOffset);

        // todo: use lexer buffer
        if (lexer.FindTokenAt(endOffset - 1) && lexer.TokenType is { } tokenType)
        {
          if (tokenType == FSharpTokenType.LPAREN_STAR_RPAREN && sourceName == "*")
            return new TextRange(endOffset - 3, endOffset);

          var opText = FSharpTokenType.Operators[tokenType] ? sourceName : logicalName;
          return new TextRange(endOffset - opText.Length, endOffset);
        }
      }

      // We need symbol use start offset to be synchronized with FCS; trim qualifiers:
      // `foo.bar` -> `bar`
      // `foo.(+) -> +
      // `foo.(|A|) -> (|A|)
      // todo: align operators and active patterns handling in the parser
      for (var i = endOffset - 1; i > startOffset; i--)
      {
        var c = buffer[i];
        if (c.Equals('.') || c.Equals('(') && i < endOffset - 1)
          return new TextRange(i + 1, endOffset);
      }

      return new TextRange(startOffset, endOffset);
    }

    private class ResolvedSymbols(int symbolUsesCount = 0)
    {
      public static readonly ResolvedSymbols Empty = new();

      [NotNull] internal readonly CompactMap<int, FcsResolvedSymbolUse> Declarations = new(symbolUsesCount / 4);
      [NotNull] internal readonly CompactMap<int, FcsResolvedSymbolUse> Uses = new(symbolUsesCount);
    }

    private class ParenMatcher() : BracketMatcher(ourParens)
    {
      private static readonly Pair<TokenNodeType, TokenNodeType>[] ourParens =
        [new(FSharpTokenType.LESS, FSharpTokenType.GREATER)];
    }
  }