swiftlang / swift-syntax

A set of Swift libraries for parsing, inspecting, generating, and transforming Swift source code.
Apache License 2.0
3.22k stars 410 forks source link

trimming trivia nodes corrupts source ranges #2687

Closed tayloraswift closed 3 months ago

tayloraswift commented 3 months ago

Description

using the trimmed family of SyntaxProtocol methods corrupts the ByteSourceRanges when obtaining syntax classifications.

i was able to reproduce this on 510.0.2, and on 600.0.0-prerelease-2024-06-12.

Steps to Reproduce

import SwiftIDEUtils
import SwiftParser
import SwiftSyntax

let text:String = """
@_documentation(metadata: blah)
public mutating
func f() {}
"""
let utf8:[UInt8] = .init(text.utf8)

var parser:Parser = utf8.withUnsafeBufferPointer { .init($0) }
let decl:DeclSyntax = .parse(from: &parser)

let function:FunctionDeclSyntax = decl.as(FunctionDeclSyntax.self)!

for span:SyntaxClassifiedRange in function.modifiers.trimmed.classifications
{
    let range:Range<Int> = span.range.offset ..< span.range.offset + span.range.length
    print(String.init(decoding: utf8[range], as: Unicode.UTF8.self))
}

publi
c
 mutatin
ahoppen commented 3 months ago

This behaves as intended. Running trimmed modifies the syntax tree to remove the leading and trailing trivia of the first and last token, respectively. The trimmed node should probably be detached from the original tree, which would make the behavior more obvious.

I would suggest that you do something like the following

let trimmed = function.modifiers.detached.trimmed
let trimmedBytes = trimmed.syntaxTextBytes
for span in trimmed.classifications {
    let range = span.range.offset ..< span.range.offset + span.range.length
    print(String.init(decoding: syntaxTextBytes[range], as: Unicode.UTF8.self))
}