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

Code before:
                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 virtual TNode WithAdditionalDiagnostics<TNode>(TNode node, params DiagnosticInfo[] diagnostics) where TNode : GreenNode
        {
            DiagnosticInfo[] existingDiags = node.GetDiagnostics();
            int existingLength = existingDiags.Length;
            if (existingLength == 0)
            {
                return node.WithDiagnosticsGreen(diagnostics);

Review comment (suggestion):
this is a move. now *all* code that calls into GetDiagnosticSpanForMissingNodeOrToken gets this behavior, whether it called into it directly, or called into AddError<T>

Code after:

            // Note: missingNodeOrToken.IsMissing means this is either a MissingToken itself, or a node comprised
            // (transitively) only from MissingTokens.  Missing tokens are guaranteed to have no text.  But they are
            // allowed to have trivia.  This is a common pattern the parser will follow when it encounters unexpected
            // tokens.  It will make a missing token of the expected kind for the current location, then attach the
            // unexpected tokens as missed tokens to it.

            // At this point, we have a node or token without real text in it.  The intuition we have here is that we
            // want to place the diagnostic on the token that "follows" this 'missing' entity.  There is a subtlety
            // here.  If the node or token contains skipped tokens, then we consider that skipped token the "following"
            // token, and we will want to place the diagnostic on it.  Otherwise, we want to place it on the true 'next
            // token' the parser is currently pointing at.

            if (!missingNodeOrToken.ContainsSkippedText)
            {
                // Simple case this node/token does not contain any skipped text.  Place the diagnostic at the start of
                // the token that follows.
                return getOffsetAndWidthBasedOnPriorAndNextTokens();
            }
            else
            {
                // 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: