protected void GetDiagnosticSpanForMissingToken(out int offset, out int width) { // 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 the diagnostic offset and width is set // to the corresponding values of the current token var trivia = this.prevTokenTrailingTrivia; if (trivia != null) { SyntaxList <CSharpSyntaxNode> triviaList = new SyntaxList <CSharpSyntaxNode>(trivia); bool prevTokenHasEndOfLineTrivia = triviaList.Any(SyntaxKind.EndOfLineTrivia); if (prevTokenHasEndOfLineTrivia) { offset = -trivia.FullWidth; width = 0; return; } } SyntaxToken ct = this.CurrentToken; offset = ct.GetLeadingTriviaWidth(); width = ct.Width; }
protected TNode AddError <TNode>(TNode node, ErrorCode code, params object[] args) where TNode : CSharpSyntaxNode { if (!node.IsMissing) { return(WithAdditionalDiagnostics(node, MakeError(node, code, args))); } int offset, width; SyntaxToken token = node as SyntaxToken; if (token != null && token.ContainsSkippedText) { // This code exists to clean up an anti-pattern: // 1) an undesirable token is parsed, // 2) a desirable missing token is created and the parsed token is appended as skipped text, // 3) an error is attached to the missing token describing the problem. // If this occurs, then this.previousTokenTrailingTrivia is still populated with the trivia // of the undesirable token (now skipped text). Since the trivia no longer precedes the // node to which the error is to be attached, the computed offset will be incorrect. offset = token.GetLeadingTriviaWidth(); // Should always be zero, but at least we'll do something sensible if it's not. Debug.Assert(offset == 0, "Why are we producing a missing token that has both skipped text and leading trivia?"); width = 0; bool seenSkipped = false; foreach (var trivia in token.TrailingTrivia) { if (trivia.Kind == SyntaxKind.SkippedTokensTrivia) { seenSkipped = true; width += trivia.Width; } else if (seenSkipped) { break; } else { offset += trivia.Width; } } } else { this.GetDiagnosticSpanForMissingToken(out offset, out width); } return(WithAdditionalDiagnostics(node, MakeError(offset, width, code, args))); }
/// <summary> /// Converts skippedSyntax node into tokens and adds these as trivia on the target token. /// Also adds the first error (in depth-first preorder) found in the skipped syntax tree to the target token. /// </summary> internal SyntaxToken AddSkippedSyntax(SyntaxToken target, CSharpSyntaxNode skippedSyntax, bool trailing) { var builder = new SyntaxListBuilder(4); // the error in we'll attach to the node SyntaxDiagnosticInfo diagnostic = null; // the position of the error within the skipedSyntax node full tree int diagnosticOffset = 0; int currentOffset = 0; foreach (var node in skippedSyntax.EnumerateNodes()) { SyntaxToken token = node as SyntaxToken; if (token != null) { builder.Add(token.GetLeadingTrivia()); if (token.Width > 0) { // separate trivia from the tokens SyntaxToken tk = token.WithLeadingTrivia(null).WithTrailingTrivia(null); // adjust relative offsets of diagnostics attached to the token: int leadingWidth = token.GetLeadingTriviaWidth(); if (leadingWidth > 0) { var tokenDiagnostics = tk.GetDiagnostics(); for (int i = 0; i < tokenDiagnostics.Length; i++) { var d = (SyntaxDiagnosticInfo)tokenDiagnostics[i]; tokenDiagnostics[i] = new SyntaxDiagnosticInfo(d.Offset - leadingWidth, d.Width, (ErrorCode)d.Code, d.Arguments); } } builder.Add(SyntaxFactory.SkippedTokensTrivia(tk)); } else { // do not create zero-width structured trivia, GetStructure doesn't work well for them var existing = (SyntaxDiagnosticInfo)token.GetDiagnostics().FirstOrDefault(); if (existing != null) { diagnostic = existing; diagnosticOffset = currentOffset; } } builder.Add(token.GetTrailingTrivia()); currentOffset += token.FullWidth; } else if (node.ContainsDiagnostics && diagnostic == null) { // only propagate the first error to reduce noise: var existing = (SyntaxDiagnosticInfo)node.GetDiagnostics().FirstOrDefault(); if (existing != null) { diagnostic = existing; diagnosticOffset = currentOffset; } } } int triviaWidth = currentOffset; var trivia = builder.ToListNode(); // total width of everything preceding the added trivia int triviaOffset; if (trailing) { var trailingTrivia = target.GetTrailingTrivia(); triviaOffset = target.FullWidth; //added trivia is full width (before addition) target = target.WithTrailingTrivia(SyntaxList.Concat(trailingTrivia, trivia)); } else { // Since we're adding triviaWidth before the token, we have to add that much to // the offset of each of its diagnostics. if (triviaWidth > 0) { var targetDiagnostics = target.GetDiagnostics(); for (int i = 0; i < targetDiagnostics.Length; i++) { var d = (SyntaxDiagnosticInfo)targetDiagnostics[i]; targetDiagnostics[i] = new SyntaxDiagnosticInfo(d.Offset + triviaWidth, d.Width, (ErrorCode)d.Code, d.Arguments); } } var leadingTrivia = target.GetLeadingTrivia(); target = target.WithLeadingTrivia(SyntaxList.Concat(trivia, leadingTrivia)); triviaOffset = 0; //added trivia is first, so offset is zero } if (diagnostic != null) { int newOffset = triviaOffset + diagnosticOffset + diagnostic.Offset; target = WithAdditionalDiagnostics(target, new SyntaxDiagnosticInfo(newOffset, diagnostic.Width, (ErrorCode)diagnostic.Code, diagnostic.Arguments) ); } return(target); }