Repository: dotnet/roslyn
File: src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs (C#)

Code before:

            (int offset, int width) getOffsetAndWidthOfSkippedToken()
            {
                var offset = 0;

                // Walk all the children of this nodeOrToken (including itself).  Note: this does not walk into trivia.
                // We are looking for the first token that has skipped text.  When we find that token (which must exist,
                // based on the check above), we will place the diagnostic on the skipped token within that token.
                foreach (var child in missingNodeOrToken.EnumerateNodes())
                {
                    Debug.Assert(child.IsMissing, "All children of a missing node or token should themselves be missing.");
                    if (!child.IsToken)
                        continue;

                    var childToken = (Syntax.InternalSyntax.SyntaxToken)child;
                    Debug.Assert(childToken.Text == "", "All missing tokens should have no text");
                    if (!child.ContainsSkippedText)
                    {
                        offset += child.FullWidth;
                        continue;
                    }

                    // Now, walk the trivia of this token, looking for the skipped tokens trivia.
                    var allTrivia = new SyntaxList<GreenNode>(SyntaxList.Concat(childToken.GetLeadingTrivia(), childToken.GetTrailingTrivia()));
                    Debug.Assert(allTrivia.Count > 0, "How can a token with skipped text not have trivia at all?");

                    foreach (var trivia in allTrivia)
                    {
                        if (!trivia.IsSkippedTokensTrivia)
                        {
                            offset += trivia.FullWidth;
                            continue;
                        }

                        // Found the skipped tokens trivia.  Place the diagnostic on it.
                        return (offset, trivia.Width);
                    }

                    Debug.Fail("This should not be reachable.  We should have hit a skipped token in the trivia of this token.");
                    return default;
                }

                Debug.Fail("This should not be reachable.  We should have hit a child token with skipped text within this node.");
                return default;
            }
        }

        protected TNode AddError<TNode>(TNode node, ErrorCode code) where TNode : GreenNode
        {
            return AddError(node, code, Array.Empty<object>());
        }

Review comment (suggestion):
this computation is different from what was on line 737, and is now more correct.  The prior comptuation just returned `-trivia.FullWidth` as the offset.  But this only is ok if the nodeOrToken we're on itself doesn't have skipped leading trivia.

It may be very hard (or currently not possible) to get into such a situation, but i still want the math to be correct.

Code after:
                // Complex case.  This node or token contains skipped text.  Place the diagnostic on the skipped text.
                return getOffsetAndWidthOfSkippedToken();
            }

            (int offset, int width) getOffsetAndWidthBasedOnPriorAndNextTokens()
            {
                // If the previous token has a trailing EndOfLineTrivia, the missing token diagnostic position is moved
                // to the end of line containing the previous token and its width is set to zero. Otherwise we squiggle
                // the token following the missing token (the token we're currently pointing at).

                var trivia = _prevTokenTrailingTrivia;
                var triviaList = new SyntaxList<CSharpSyntaxNode>(trivia);
                if (triviaList.Any((int)SyntaxKind.EndOfLineTrivia))
                {
                    // We have:
                    //
                    //   [previous token][previous token trailing trivia...][missing node leading trivia...][missing node or token]
                    //                                                                                      ^
                    //                                                                                      | here
                    //
                    // Update so we report diagnostic here:
                    //
                    //   [previous token][previous token trailing trivia...][missing node leading trivia...][missing node or token]
                    //                   ^
                    //                   | here
                    return (offset: -missingNodeOrToken.GetLeadingTriviaWidth() - trivia.FullWidth, width: 0);
                }
                else
                {
                    // We have:
                    //
                    //   [missing node leading trivia...][missing node or token][missing node or token trailing trivia..][current token leading trivia ...][current token]
                    //                                   ^
                    //                                   | here
                    //
                    // Update so we report diagnostic here:
                    //
                    //   [missing node leading trivia...][missing node or token][missing node or token trailing trivia..][current token leading trivia ...][current token]
                    //                                                                                                                                     ^             ^
                    //                                                                                                                                     | --- here -- |
                    var token = this.CurrentToken;
                    return (missingNodeOrToken.Width + missingNodeOrToken.GetTrailingTriviaWidth() + token.GetLeadingTriviaWidth(), token.Width);
                }
            }

            (int offset, int width) getOffsetAndWidthOfSkippedToken()
            {
                var offset = 0;

                // Walk all the children of this nodeOrToken (including itself).  Note: this does not walk into trivia.
                // We are looking for the first token that has skipped text.  When we find that token (which must exist,