Repository: dotnet/roslyn
File: src/VisualStudio/Core/Def/TaskList/ProjectExternalErrorReporter.cs (C#)

Code before:
        ReportError2(bstrErrorMessage, bstrErrorId, nPriority, iLine, iColumn, iLine, iColumn, bstrFileName);
        return VSConstants.S_OK;
    }

    // TODO: Use PreserveSig instead of throwing these exceptions for common cases.
    public void ReportError2(
        string bstrErrorMessage,
        string bstrErrorId,
        [ComAliasName("VsShell.VSTASKPRIORITY")] VSTASKPRIORITY nPriority,
        int iStartLine,
        int iStartColumn,
        int iEndLine,
        int iEndColumn,
        string bstrFileName)
    {

        // make sure we have error id, otherwise, we simple don't support
        // this error
        if (bstrErrorId == null)
        {
            // record NFW to see who violates contract.
            FatalError.ReportAndCatch(new Exception("errorId is null"));
            return;
        }

        Task.Run(() => ReportErrorAsync()).Forget();

        async Task ReportErrorAsync()
        {

            if (!bstrErrorId.StartsWith(_errorCodePrefix) &&
                !await DiagnosticProvider.IsSupportedDiagnosticIdAsync(
                    _projectId, bstrErrorId, CancellationToken.None).ConfigureAwait(false))
            {
                return;
            }

            if ((iEndLine >= 0 && iEndColumn >= 0) &&
               ((iEndLine < iStartLine) ||
                (iEndLine == iStartLine && iEndColumn < iStartColumn)))
            {
                throw new ArgumentException(ServicesVSResources.End_position_must_be_start_position);
            }

            var severity = nPriority switch
            {
                VSTASKPRIORITY.TP_HIGH => DiagnosticSeverity.Error,
                VSTASKPRIORITY.TP_NORMAL => DiagnosticSeverity.Warning,
                VSTASKPRIORITY.TP_LOW => DiagnosticSeverity.Info,
                _ => throw new ArgumentException(ServicesVSResources.Not_a_valid_value, nameof(nPriority))
            };

Review comment (suggestion):
I believe making this async breaks the removal of errors, because now if the project system calls Report and then calls Clear, you can end up with the diagnostics stuck.

Code after:

    // TODO: Use PreserveSig instead of throwing these exceptions for common cases.
    public void ReportError2(
        string bstrErrorMessage,
        string bstrErrorId,
        [ComAliasName("VsShell.VSTASKPRIORITY")] VSTASKPRIORITY nPriority,
        int iStartLine,
        int iStartColumn,
        int iEndLine,
        int iEndColumn,
        string bstrFileName)
    {
        // make sure we have error id, otherwise, we simple don't support
        // this error
        if (string.IsNullOrEmpty(bstrErrorId))
        {
            if (bstrErrorId is null)
            {
                // record NFW to see who violates contract.
                FatalError.ReportAndCatch(new Exception("errorId is null"));
            }

            throw new NotImplementedException();
        }

        if (!bstrErrorId.StartsWith(_errorCodePrefix) &&
            DiagnosticProvider.IsUnsupportedDiagnosticId(_projectId, bstrErrorId))
        {
            throw new NotImplementedException();
        }

        if ((iEndLine >= 0 && iEndColumn >= 0) &&
            ((iEndLine < iStartLine) ||
            (iEndLine == iStartLine && iEndColumn < iStartColumn)))
        {
            throw new ArgumentException(ServicesVSResources.End_position_must_be_start_position);
        }

        var severity = nPriority switch
        {
            VSTASKPRIORITY.TP_HIGH => DiagnosticSeverity.Error,
            VSTASKPRIORITY.TP_NORMAL => DiagnosticSeverity.Warning,
            VSTASKPRIORITY.TP_LOW => DiagnosticSeverity.Info,
            _ => throw new ArgumentException(ServicesVSResources.Not_a_valid_value, nameof(nPriority))
        };

        DocumentId? documentId;
        if (iStartLine < 0 || iStartColumn < 0)
        {
            documentId = null;
            iStartLine = iStartColumn = iEndLine = iEndColumn = 0;