如何使用Roslyn查找和替换SkippedTokensTrivia

时间:2014-04-14 00:24:05

标签: roslyn

我正在尝试修复以下VBA语句(转换一些旧代码只是为了好玩并学习Roslyn,而不是寻找任何完美的东西)来删除Set关键字,这样它就是一个有效的VB.NET语句:

Set f = New Foo()

当我通过Syntax Visualizer查看它时,我发现它变成了尾随琐事。

Syntax Visualizer

我正在试图弄清楚如何使用查询找到它。我尝试了几种方法,但以下所有方法都是空的:

var attempt1 = root.DescendantTokens().Where(t=>t.IsKind(SyntaxKind.SkippedTokensTrivia));
var attempt2 = root.DescendantTokens().Where(t => t.IsKind(SyntaxKind.SetKeyword));
var attempt3 = root.DescendantTrivia().Where(t => t.IsKind(SyntaxKind.SetKeyword));
var attempt4 = root.DescendantNodes()
                   .OfType<EmptyStatementSyntax>()
                   .Where(e => e.DescendantTokens().Any(t => t.IsKeyword()));

(是的,我正在使用C#来处理VisualBasicSyntaxTree

我似乎无法找到可视化工具中出现的SetKeyword令牌,所以我想也许它正在做一些更加繁重的工作来拼凑它的真实含义(结构性琐事是什么意思? )。我在文档中读到了一些内容,提到编译器可以选择用几种不同的方式来表示它,所以我认为这可能就是这里发生的事情。

查询只是我尝试的第一件事,但实际上我有一个SyntaxRewriter我正在使用访问代码来查找和修复所有这些问题(我已经能够修复丢失的括号例如,ArgumentLists)但在这种情况下,我似乎无法弄清楚要覆盖哪种Visit方法。

再次,1)如何从根查询这些,以及2)从重写器中选择的最佳覆盖。我一直在键盘上敲打我的脸两天,这会增加我有颅骨/直肠插入时刻的可能性,我需要你们中的一个善良的灵魂把我拉出来。

干杯! 布赖恩

编辑:修复了查询尝试1中的拼写错误

1 个答案:

答案 0 :(得分:2)

因此,当编译器达到错误条件时,它将跳过所有令牌,直到它可以恢复并继续解析的下一个点(在这种情况下为行的结尾)。表示此错误条件的节点是EmptyStatement,其尾随语法为trivia,其中包含文本的其余部分作为已解析的标记。

因此,如果您要重写节点,则需要重写EmptyStatements。但是你不想只写任何空语句,只写那些带有“BC30807”诊断代码的语句。

public override SyntaxNode VisitEmptyStatement(EmptyStatementSyntax node)
{
    var diagnostic = GetLetSetDiagnostic(node);
    if (diagnostic == null)
        return base.VisitEmptyStatement(node);
    return RewriteLetSetStatement(node);
}

private Diagnostic GetLetSetDiagnostic(EmptyStatementSyntax node)
{
    //'Let' and 'Set' assignment statements are no longer supported.
    const string code = "BC30807";
    return node.GetDiagnostics().SingleOrDefault(n => n.Id == code);
}

RewriteLetSetStatement()方法的实现对我来说有点神秘,我不确定它是如何有效地利用编译器服务实现的,我不认为这是一个用例它涵盖得很好。琐事保留了已解析的代币,但是你可以用这些代币AFAIK做很多事情。

理想情况下,我们只想忽略令牌中的Set令牌并将其重新放回解析器中进行重新解析。据我所知,这是不可能的,我们只能从文本中解析。

所以,我想下一个要做的最好的事情就是获取文本,重写它以删除Set并再次解析文本。

private SyntaxNode RewriteLetSetStatement(EmptyStatementSyntax node)
{
    var letSetTokens = node.GetTrailingTrivia()
        .Where(triv => triv.IsKind(SyntaxKind.SkippedTokensTrivia))
        .SelectMany(triv => triv.GetStructure().ChildTokens())
        .TakeWhile(tok => new[] {SyntaxKind.LetKeyword, SyntaxKind.SetKeyword}
                          .Contains(tok.VisualBasicKind()));
    var span = new RelativeTextSpan(node.FullSpan);
    var newText = node.GetText().WithChanges(
        // replacement spans must be relative to the text
        letSetTokens.Select(tok => new TextChange(span.GetSpan(tok.Span), ""))
    );
    return SyntaxFactory.ParseExecutableStatement(newText.ToString());
}

private class RelativeTextSpan(private TextSpan span)
{
    public TextSpan GetSpan(TextSpan token)
    {
        return new TextSpan(token.Start - span.Start, token.Length);
    }
}