在源生成器(IIncrementalGenerator)中获取列号的方法
解决拦截器中获取调用语句正确列号的问题
核心思路
要获取考虑空格/制表符的真实显示列号,必须基于源代码文本的行内容计算,而非直接使用语法节点的Span全局索引,同时要处理制表符到显示空格的转换逻辑。
具体实现步骤
1. 获取源代码文本与目标行内容
通过SyntaxTree.GetText()拿到完整源代码文本,再提取目标节点所在行的具体内容:
// 假设context.Node是目标InvocationExpressionSyntax(即foo.Bar()调用节点) var syntaxTree = context.SemanticModel.SyntaxTree; var sourceText = syntaxTree.GetText(); var lineSpan = syntaxTree.GetLineSpan(context.Node.Span); var lineText = sourceText.Lines[lineSpan.StartLinePosition.Line].ToString();
2. 定位节点在行内的起始字符偏移
用节点的起始位置减去该行的起始位置,得到节点在行内的局部字符索引:
var lineStart = sourceText.Lines[lineSpan.StartLinePosition.Line].Start; // 如果需要精准定位"Bar"标识符而非整个调用语句,先找到对应节点 var barIdentifier = context.Node.DescendantNodes() .OfType<IdentifierNameSyntax>() .First(n => n.Identifier.Text == "Bar"); var inlineCharOffset = barIdentifier.Span.Start - lineStart;
3. 处理制表符转换,计算真实显示列号
由于制表符在编辑器中通常显示为2/4个空格,需要将行内从开头到节点起始位置的文本中的制表符按显示规则替换,再统计长度:
// 可根据实际场景配置制表符显示长度,默认4个空格 int tabSize = 4; var prefixText = lineText.Substring(0, inlineCharOffset); // 计算制表符转换后的显示列号(列号从1开始计数) int displayColumn = prefixText.Aggregate(0, (current, c) => c == '\t' ? current + tabSize - (current % tabSize) : current + 1 ) + 1;
完整代码示例
// 定位到foo.Bar()调用节点 var invocationNode = context.Node as InvocationExpressionSyntax; if (invocationNode == null) return; // 精准定位Bar标识符节点 var memberAccess = invocationNode.Expression as MemberAccessExpressionSyntax; if (memberAccess?.Name.Identifier.Text != "Bar") return; var barIdentifier = memberAccess.Name; var syntaxTree = context.SemanticModel.SyntaxTree; var sourceText = syntaxTree.GetText(); var lineSpan = syntaxTree.GetLineSpan(barIdentifier.Span); var lineIndex = lineSpan.StartLinePosition.Line; var line = sourceText.Lines[lineIndex]; var lineStart = line.Start; var inlineCharOffset = barIdentifier.Span.Start - lineStart; // 计算带制表符转换的真实列号 int tabSize = 4; var prefixContent = line.ToString().Substring(0, inlineCharOffset); int displayColumn = prefixContent.Aggregate(0, (col, c) => c == '\t' ? col + tabSize - (col % tabSize) : col + 1 ) + 1; // 最终结果 var lineNumber = lineIndex + 1; var filePath = GetInterceptorFilePath(syntaxTree, context.SemanticModel.Compilation);
关键说明
- 直接使用
Span.Start.Character得到的是整个文档的全局字符索引,并非行内列号,因此会出现错误结果。 - 制表符的显示长度需要适配实际场景(比如编辑器的制表符设置),上述代码默认采用4个空格的规则,可提供配置项让用户自定义。
- 精准定位
Bar标识符节点,能确保列号指向Bar单词的起始位置,符合拦截调用的需求。
内容的提问来源于stack exchange,提问作者Link
相关产品推荐
相关产品推荐

