dmc-parser 0.3.4

Typed AST parser for the dmc MDX compiler
Documentation
use dmc_diagnostic::Code;
use dmc_diagnostic::metadata::{Origin, SourceMeta};
use dmc_lexer::Lexer;
use dmc_parser::Parser;
use duck_diagnostic::{DiagnosticCode, DiagnosticEngine};
use pretty_assertions::assert_eq;
use std::sync::Arc;

fn parse_with_diagnostics(src: &str) -> DiagnosticEngine<Code> {
  let meta = Arc::new(SourceMeta { path: Arc::from("<test>"), origin: Origin::Inline("<test>") });
  let mut lex_diag = DiagnosticEngine::new();
  let mut lexer = Lexer::new(src, meta.clone(), &mut lex_diag);
  let _ = lexer.scan_tokens();
  let tokens = std::mem::take(&mut lexer.tokens);
  drop(lexer);

  let mut parse_diag = DiagnosticEngine::new();
  let mut parser = Parser::new(tokens, meta, &mut parse_diag);
  let _ = parser.parse();
  parse_diag
}

#[test]
fn malformed_link_emits_one_diagnostic() {
  let diag = parse_with_diagnostics("[text](\n");
  assert_eq!(diag.iter().count(), 1);
  assert_eq!(diag.get_diagnostics()[0].code.code(), "P001");
}

#[test]
fn unterminated_fence_emits_one_diagnostic() {
  let diag = parse_with_diagnostics("```\n");
  assert_eq!(diag.iter().count(), 1);
  assert_eq!(diag.get_diagnostics()[0].code.code(), "P004");
}

#[test]
fn orphan_close_tag_emits_one_diagnostic() {
  let diag = parse_with_diagnostics("</div>\n");
  assert_eq!(diag.iter().count(), 1);
  assert_eq!(diag.get_diagnostics()[0].code.code(), "P010");
}

#[test]
fn unterminated_jsx_attr_emits_one_diagnostic() {
  let diag = parse_with_diagnostics("<Foo bar=\n");
  assert_eq!(diag.iter().count(), 1);
  assert_eq!(diag.get_diagnostics()[0].code.code(), "P013");
}

#[test]
fn diagnostic_output_snapshots_are_stable() {
  let link = parse_with_diagnostics("[text](\n").format_all_plain("[text](\n");
  assert!(link.contains(
    "error: [P001]: inline link destination did not close before the end of the line; treating it as literal text"
  ));
  assert!(link.contains("--> <test>:1:1"));
  assert!(
    link.contains("= help: add a closing `)` to finish `[text](...)`, or escape the `[` if this should stay literal")
  );

  let fence = parse_with_diagnostics("```\n").format_all_plain("```\n");
  assert!(fence.contains(
    "error: [P004]: fenced code block never found a matching closing fence; treating the rest of the file as code"
  ));
  assert!(fence.contains("--> <test>:1:1"));
  assert!(fence.contains("= help: add a closing fence with at least 3 ``` characters"));

  let jsx = parse_with_diagnostics("<Foo bar=\n").format_all_plain("<Foo bar=\n");
  assert!(jsx.contains(
    "error: [P013]: JSX attribute `bar` is missing a value before the tag ended; preserving the text literally"
  ));
  assert!(jsx.contains("--> <test>:1:6"));
  assert!(jsx.contains("= help: add a quoted string, `{expression}`, or remove the trailing `=`"));
}