daml_lint/lib.rs
1//! daml-lint as a library.
2//!
3//! The binary (`src/main.rs`) is a thin CLI over these modules. The parser
4//! pipeline — `lexer` → `layout` → `parse` over the AST types — lives in the
5//! separate [`daml_parser`] crate. This crate lowers that AST into a
6//! rule-facing IR ([`ir`], via [`parser`]) and runs [`detectors`] over it.
7//! Start at [`parser::parse_daml_with_diagnostics`].
8//! The IR is public so custom rules and library callers can inspect it; parser
9//! lowering is the supported construction path.
10//!
11//! ## API posture
12//!
13//! This crate is pre-1.0. Its public IR is also intentionally data-oriented;
14//! adding/removing fields, changing enum variants, or altering shapes is
15//! SemVer-relevant. Use constructors in the same crate for creation and treat
16//! these as versioned contracts with downstream rules/detectors.
17//! Matching contract for rule-facing IR:
18//! `TypeNode`, `LiteralKind`, `Expr`, `Consuming`, `Statement`, and
19//! `ImportStyle` are intentionally `#[non_exhaustive]`.
20//! Read-mostly IR/report DTO structs (`DamlModule`, `Template`, `Finding`, and
21//! related nodes) are also `#[non_exhaustive]` so fields can evolve in 0.x
22//! without breaking downstream field reads.
23//! Downstream code should include wildcard arms when matching any of these
24//! enums; adding variants is a compatible evolution for new Daml syntax and
25//! recovery paths. Construct IR through [`parser::parse_daml_with_diagnostics`]
26//! or documented constructors such as [`detector::Finding::new`].
27//!
28//! Parse diagnostics use [`parser::ParseDiagnosticCategory`] (not the parser
29//! crate's internal category enum) and [`parser::ParseResult`] (`module` +
30//! `diagnostics`) as the supported lowering entry point. Rust-facing locations
31//! and spans use [`daml_syntax::LineNumber`], [`daml_syntax::CharColumn`],
32//! [`daml_syntax::Utf16Offset`], and [`daml_syntax::ByteOffset`] so coordinate
33//! spaces cannot be mixed accidentally; JSON, SARIF, and custom-rule JavaScript
34//! output still serialize those coordinates as numbers. For severity thresholds
35//! and report ordering, use [`detector::Severity::rank`] and
36//! [`detector::Severity::meets_or_exceeds`]; `Severity` does not implement
37//! `Ord` because declaration order does not match risk rank.
38//!
39//! # Example
40//!
41//! ```
42//! use std::path::Path;
43//!
44//! let source = "\
45//! module M where
46//!
47//! template T
48//! with
49//! owner : Party
50//! where
51//! signatory owner
52//! ";
53//!
54//! let parse_result =
55//! daml_lint::parser::parse_daml_with_diagnostics(source, Path::new("M.daml"));
56//! let module = parse_result.module;
57//! let diagnostics = parse_result.diagnostics;
58//!
59//! assert!(diagnostics.is_empty());
60//! assert_eq!(module.name, "M");
61//! assert_eq!(module.templates.len(), 1);
62//! ```
63//!
64//! Loss-tolerant parsing always returns a module; reject scans when diagnostics
65//! are present. [`parser::ParseDiagnosticCategory`] parses stable category tags:
66//!
67//! ```
68//! use std::path::Path;
69//! use std::str::FromStr;
70//!
71//! use daml_lint::parser::{ParseDiagnosticCategory, parse_daml_with_diagnostics};
72//!
73//! let broken = parse_daml_with_diagnostics("module M where\n%%% junk\n", Path::new("M.daml"));
74//! assert!(!broken.diagnostics.is_empty());
75//! assert_eq!(broken.module.name, "M");
76//!
77//! assert_eq!(
78//! ParseDiagnosticCategory::from_str("malformed").unwrap(),
79//! ParseDiagnosticCategory::Malformed,
80//! );
81//! assert!(ParseDiagnosticCategory::from_str("not-a-category").is_err());
82//! ```
83
84#![warn(missing_docs)]
85
86/// Detector traits, finding DTOs, severity values, and configured-detector wrappers.
87pub mod detector;
88/// Built-in and JavaScript-backed detector implementations.
89pub mod detectors;
90pub mod ir;
91/// Lowering: `daml-parser`'s typed AST → rule-facing IR ([`ir`]).
92pub mod parser;
93/// Report formatting and scan exit-code helpers.
94pub mod reporter;