cuke_dedup/lib.rs
1//! CukeDedup's reusable static-analysis library.
2//!
3//! The CLI is the primary integration for repository-wide analysis. Embedders can also parse
4//! definitions and feature steps into the shared representation and run the same rules directly:
5//!
6//! ```
7//! use cuke_dedup::analysis::analyze;
8//! use cuke_dedup::config::{Config, ConfigOverrides};
9//! use cuke_dedup::gherkin;
10//! use cuke_dedup::source_adapter::{SourceFile, SourceLanguage};
11//! use cuke_dedup::typescript;
12//! use std::path::Path;
13//!
14//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! let root = std::env::current_dir()?;
16//! let config = Config::load(&root, ConfigOverrides::default())?;
17//! let definition_file = SourceFile {
18//! path: root.join("steps.ts"),
19//! language: SourceLanguage::TypeScript,
20//! };
21//! let definitions = typescript::extract(
22//! r#"import { Given } from "@cucumber/cucumber";
23//! Given("a user exists", async () => { await createUser(); });"#,
24//! &definition_file,
25//! )?;
26//! let feature_steps = gherkin::extract(
27//! "Feature: Users\n Scenario: Existing user\n Given a user exists\n",
28//! Path::new("features/users.feature"),
29//! )?;
30//!
31//! let result = analyze(definitions, feature_steps, &config)?;
32//! assert!(result.findings.is_empty());
33//! # Ok(())
34//! # }
35//! ```
36//!
37//! ## Public API stability
38//!
39//! Extensibility follows the direction the data travels, and the two halves are deliberately
40//! different.
41//!
42//! Types the analyzer *produces* carry `#[non_exhaustive]`, so a later release can add a field
43//! without breaking embedders. Where a caller still needs to build one — to assemble a partial
44//! result, a diagnostic, or a baseline — the type also exposes a `new` constructor that takes the
45//! fields required at that version.
46//!
47//! Types embedders *construct* to feed the analyzer stay exhaustive, because `#[non_exhaustive]`
48//! would make them impossible to build from outside this crate. That covers
49//! [`source_adapter::SourceFile`], [`model::SourceLocation`], [`model::StepDefinition`],
50//! [`model::HandlerFingerprint`], [`model::InlineSuppression`], [`model::FeatureStep`],
51//! [`config::Config`], [`config::ConfigOverrides`] and [`config::SuppressionConfig`]. Adding a
52//! field to any of those is a breaking change and needs a major release.
53//!
54//! When adding a public type, follow that rule rather than copying a neighbour: mark it
55//! `#[non_exhaustive]` if the analyzer returns it, leave it exhaustive if a caller has to build it,
56//! and give it a constructor if it travels both ways.
57//!
58//! Machine-readable output is versioned independently of the crate, so a schema change is visible
59//! to consumers that never read the crate version: [`reporters::JSON_SCHEMA_VERSION`],
60//! [`reporters::JSONL_SCHEMA_VERSION`] and [`modes::BASELINE_SCHEMA_VERSION`]. Rendering always
61//! goes through [`reporters::ReportContext`] and the [`reporters::render_json`],
62//! [`reporters::render_jsonl`], [`reporters::render_html`], [`reporters::render_sarif`] and
63//! [`reporters::write_terminal`] wrappers, which are the only supported rendering entry points.
64
65#![warn(missing_docs)]
66#![forbid(unsafe_code)]
67
68pub mod analysis;
69pub mod cli;
70pub mod config;
71pub mod discovery;
72mod framework_config;
73pub mod gherkin;
74pub mod model;
75pub mod modes;
76pub mod reporters;
77mod resource_limits;
78pub mod source_adapter;
79mod source_filter;
80pub mod typescript;