1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//! Line-based file scan against the forbidden-regex engine's batch API.
//!
//! Stage two of the engine swap (#384) replaces the aho-corasick/resharp per-file
//! scan with the engine's buffer-batch face.
//! A file's bytes are split into lines and
//! handed to `RegexSet::line_matches(buf, starts)`,
//! which resolves per-line rule ids
//! in one SIMD prefilter sweep.
//! Findings emit as `PATH:LINE rule=N`,
//! one per
//! line-and-rule pair,
//! with no column segment (the engine reports per-line rule
//! indices,
//! not spans).
//!
//! Line mechanics mirror the engine's contract:
//! split on `\n`,
//! one trailing `\r` and
//! the terminator excluded by the matcher,
//! empty lines skipped.
//! The batch call runs
//! under a `catch_unwind` boundary so an engine panic fails closed as a synthetic
//! finding rather than aborting the scan,
//! preserving the scanner's fail-closed
//! guarantee against a secret-scanning gate exiting clean on an engine fault.
/// Imports the SIMD newline scan used to build the line-start offsets.
use memchr_iter;
/// Imports the unwind boundary that turns an engine panic into a fail-closed finding.
use ;
/// Imports the loaded rule sets scanned against each file.
use crateLoadedRules;
/// Import the canonical result model shared by standalone and embedded callers.
use crateScanFinding;
/// Builds the line-start offsets `RegexSet::line_matches` requires for `buf`.
///
/// The engine's precondition is that `starts` ascends,
/// begins at 0,
/// and every offset
/// indexes within `buf`.
/// This returns 0 followed by the offset just past each `\n`,
/// dropping a final offset equal to `buf.len()` (the empty line after a trailing
/// newline):
/// the matcher would skip it anyway,
/// and omitting it keeps every offset a
/// valid in-bounds index.
/// Interior empty lines keep their offset and the matcher
/// skips them,
/// so line numbering stays aligned with the file.
/// Renders the stable opaque identity of one matching rule.
///
/// Named rules use their section name;
/// unnamed rules use the set's base offset.
/// This shared formatter keeps content and pathname findings consistent.
pub
/// Runs one set's batch matcher under a fail-closed unwind boundary.
///
/// Normal `(line index, rule id)` pairs become typed content findings with one-based
/// lines and the shared opaque rule identity.
/// A caught panic becomes an explicit
/// EngineError record,
/// so neither embedded nor standalone callers can treat it as
/// a clean scan.
/// Terminal rendering happens separately.
/// Scan exact file bytes against each loaded set,
/// returning only redacted structured records.
///
/// Line splitting happens once.
/// Runtime rules precede the builtin baseline,
/// and each set retains its rule-id offset and configured non-secret names.
/// Empty input yields no findings.
/// The caller supplies candidate identity and
/// a sanitized display path outside this content-matching boundary.
pub
/// What:
/// Preserve the standalone text protocol by rendering canonical content records.
/// Why:
/// Embedded callers use scan_content directly,
/// without parsing terminal strings.
///
/// In TS you\'d write (pseudocode):
/// ```ts
/// function scanFile(safeDisplayPath, bytes, loaded): string[];
/// ```
/// Registers the line-splitting edge-case and fail-closed tests (sidecar,
/// lint-exempt).