My approach is usually ... just to do it by hand.
But depending on the language I also like to throw in the smallest amount of parsing by matching parentheses/brackets/braces at the same time. It's not very elegant but it makes the output immediately useful.
By "just to do it by hand", I mean it's usually a big loop with a switch statement with a getc-equivalent, manually keeping track of line/column numbers ... it's great. 😿
It's nice because you only need to keep track of the byte offset through the rest of the compiler, which can cut down on the size of AST nodes. You do however need to then keep a list of the line-start positions for each file for later on.