Skip to main content

harn_parser/
interpolation.rs

1//! Shared parsing for `${...}` expression holes.
2//!
3//! The lexer stores an interpolation hole as source text plus the line and
4//! column where it starts. Everything that needs the expression back — the
5//! typechecker, the linter, and `harn fix` — parses it through here, so every
6//! consumer sees spans in the containing file's coordinates rather than
7//! offsets relative to the hole (harn#5850).
8
9use harn_lexer::Lexer;
10
11use crate::{Parser, SNode};
12
13/// Parse one `${...}` hole into an expression whose spans address `source`.
14///
15/// `segment`, `line`, and `column` come from a
16/// [`harn_lexer::StringSegment::Expression`]; `source` is the whole file that
17/// segment was lexed from. Pass `None` only when the containing file is not
18/// available — the expression still parses, but its spans stay relative to the
19/// hole and must not be used to edit the file.
20pub fn parse_expression(
21    source: Option<&str>,
22    segment: &str,
23    line: usize,
24    column: usize,
25) -> Option<SNode> {
26    Parser::new(lexer(source, segment, line, column).tokenize().ok()?)
27        .parse_single_expression()
28        .ok()
29}
30
31/// Build the lexer `parse_expression` uses, for callers that need the tokens.
32pub fn lexer(source: Option<&str>, segment: &str, line: usize, column: usize) -> Lexer {
33    let offset = source
34        .and_then(|source| harn_lexer::byte_offset_for_position(source, line, column))
35        .unwrap_or_default();
36    Lexer::with_position_and_offset(segment, line, column, offset)
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::Node;
43
44    #[test]
45    fn parses_a_hole_into_spans_that_address_the_containing_source() {
46        let source = "const label = \"é ${platform()}\"\n";
47
48        let expression = parse_expression(Some(source), "platform()", 1, 20).expect("expression");
49
50        assert!(matches!(expression.node, Node::FunctionCall { .. }));
51        assert_eq!(
52            &source[expression.span.start..expression.span.end],
53            "platform()"
54        );
55    }
56}