pub struct RuleTester { /* private fields */ }Expand description
A rule under test, with a throwaway project to run it in.
The project is removed when the tester is dropped.
Implementations§
Source§impl RuleTester
impl RuleTester
Sourcepub fn new(name: &str, rule_source: &str) -> Result<Self, TestError>
pub fn new(name: &str, rule_source: &str) -> Result<Self, TestError>
Build a tester for a rule’s source.
name labels the temporary directory and need not be unique — every tester gets its
own directory regardless, so a test file with a fn tester() helper shared across
cases works. It has to: two testers sharing a directory would delete each other’s
project mid-run, and the resulting error would point at the config rather than at
the collision.
§Errors
Returns TestError::Setup if the temporary project cannot be written.
Sourcepub fn with_extension(
name: &str,
rule_source: &str,
extension: &str,
) -> Result<Self, TestError>
pub fn with_extension( name: &str, rule_source: &str, extension: &str, ) -> Result<Self, TestError>
Build a tester whose subject files use a given extension.
Needed for a rule targeting tsx, since which grammar parses a file is decided by
its extension — a TSX rule tested against a .ts file would never match.
§Errors
As RuleTester::new.
Sourcepub fn configured(
name: &str,
rule_source: &str,
options: &str,
) -> Result<Self, TestError>
pub fn configured( name: &str, rule_source: &str, options: &str, ) -> Result<Self, TestError>
Build a tester for a factory rule — one whose default export returns a rule when called with options — using the given options expression.
options is JavaScript, spliced into the generated config as rule(<options>).
Passing the options as source rather than as a serialized value is deliberate: a
factory takes whatever its author designed, and a harness that only accepted JSON
could not test one taking a function or a regular expression.
let tester = RuleTester::configured(
"restricted",
RULE_SOURCE,
"{ restrictions: [{ module: 'lodash' }] }",
)
.expect("builds");§Errors
As RuleTester::new.
Sourcepub fn configured_with_extension(
name: &str,
rule_source: &str,
extension: &str,
options: &str,
) -> Result<Self, TestError>
pub fn configured_with_extension( name: &str, rule_source: &str, extension: &str, options: &str, ) -> Result<Self, TestError>
Build a tester for a factory rule whose subject files use a given extension.
RuleTester::configured and RuleTester::with_extension each vary one axis, and
a rule that is both parameterized and non-TypeScript needs both — every built-in
targeting Rust, Go or Python is in that position the moment it takes an option. The
absence was not a decision: configured predates any parameterized rule outside
TypeScript, and the two Rust-targeting built-ins that document an allow option had
no test reaching them here at all.
§Errors
As RuleTester::new.
Sourcepub fn for_component(
name: &str,
bytes: &[u8],
extension: &str,
) -> Result<Self, TestError>
pub fn for_component( name: &str, bytes: &[u8], extension: &str, ) -> Result<Self, TestError>
Build a tester for a rule compiled to a WebAssembly component.
bytes are the component itself — lanekeep_rules::component("no-unwrap"), or whatever
a project’s own build produced. They are written to rules/rule.wasm inside the
throwaway project, because a component reference is confined to the rules root.
§It generates a lanekeep.json, and it has to
Every other constructor writes a lanekeep.config.ts that imports the rule. A .wasm is
not a value a TypeScript module can import: a component is resolved in Rust, by path, and
answers metadata for itself. So the component path is the JSON config path, and that is
forced by what a component is rather than chosen for convenience.
§extension is required rather than defaulted
RuleTester::new defaults to ts and RuleTester::with_extension varies it, which
is right when TypeScript is the overwhelmingly common case. It is not the common case
here — a rule authored as a component is one written in the language it inspects, and the
two that exist target Rust — so a default would be wrong more often than not, and the
pair of *_with_extension variants it would need doubles the constructor count for
nothing.
§Errors
As RuleTester::new.
Sourcepub fn for_component_configured(
name: &str,
bytes: &[u8],
extension: &str,
options: &str,
) -> Result<Self, TestError>
pub fn for_component_configured( name: &str, bytes: &[u8], extension: &str, options: &str, ) -> Result<Self, TestError>
Build a tester for a component rule configured with options.
options is JSON, and the difference from RuleTester::configured is the whole
point rather than an inconvenience. That one splices JavaScript source into a config, so
a factory rule can be handed a function or a regular expression. A component cannot close
over a host-supplied value at all: its options cross the boundary as data, through the
world’s configure(options-json). Accepting source here would suggest otherwise, and
would test a shape no real config can produce.
§Errors
As RuleTester::new, plus TestError::Setup if options is not valid JSON — which
is caught here rather than left to surface as a config parse error naming a generated
file the caller never wrote.
Sourcepub fn for_built_in(
name: &str,
extension: &str,
components: BuiltinComponent,
) -> Result<Self, TestError>
pub fn for_built_in( name: &str, extension: &str, components: BuiltinComponent, ) -> Result<Self, TestError>
Build a tester for a built-in rule, named the way a real config names it.
name is the bare rule name — "no-default-export" — and the generated lanekeep.json
carries "lanekeep/no-default-export". components is the lookup that answers it, which
for lanekeep’s own rules is lanekeep_rules::component; it is a parameter rather than a
dependency because lanekeep-rules dev-depends on this crate, and an edge the other way
would put each crate ahead of the other in the publication order.
§What this exists for, and what RuleTester::for_component cannot do
for_component writes an artifact to a path, and a path reference contributes every
rule the artifact hosts. That is right for a component built from one rust-rules/
crate and wrong for a shared one: the four TypeScript built-ins live in a single
typescript-builtins.wasm, so pointing a tester at its bytes runs all four and there is
no way to say which one is under test. Naming the specifier is how a config says it —
resolution goes through the embedded table, which carries the rule’s index, and the
engine is handed one rule. Until this constructor existed, a rule of a shared component
could not be tested through RuleTester at all.
§It is slower than every other constructor, by a lot
A built-in that ships as a component is compiled at load, and the shared TypeScript one
is 12.4 MiB. The first run on a tester pays that — seconds, not milliseconds — and
later runs on the same tester map what it wrote into the throwaway project. Prefer one
tester over a table of cases to a tester per case.
§Errors
As RuleTester::new.
Sourcepub fn for_built_in_configured(
name: &str,
extension: &str,
components: BuiltinComponent,
options: &str,
) -> Result<Self, TestError>
pub fn for_built_in_configured( name: &str, extension: &str, components: BuiltinComponent, options: &str, ) -> Result<Self, TestError>
Build a tester for a built-in rule configured with options.
options is JSON, for the reason RuleTester::for_component_configured gives: a
built-in that ships as a component takes its options as data through configure, and one
that ships as a module is reached from a lanekeep.json here too, where JSON is all a
config can write. Neither form can close over a host-supplied value.
§Errors
Sourcepub const fn with_component_maps(self, maps: BuiltinComponentMap) -> Self
pub const fn with_component_maps(self, maps: BuiltinComponentMap) -> Self
Serve source maps for built-in components too.
Diagnostics only, and separate from the constructor because of it: a map decides where a
thrown rule is reported and nothing about what a rule finds, so a test asserting
behavior needs none. lanekeep_rules::component_source_map is the lookup for lanekeep’s
own rules.
Sourcepub const fn with_builtins(self, builtins: BuiltinSource) -> Self
pub const fn with_builtins(self, builtins: BuiltinSource) -> Self
Serve the built-in modules a rule imports — lanekeep/patterns and friends.
The lookup for lanekeep’s own rules is lanekeep_rules::source; it is a parameter
rather than a dependency for the same reason RuleTester::for_built_in’s
components is — lanekeep-rules dev-depends on this crate, and an edge the other
way would put each crate ahead of the other in the publication order.
Sourcepub fn write_fixture(&self, path: &str, contents: &str) -> Result<(), TestError>
pub fn write_fixture(&self, path: &str, contents: &str) -> Result<(), TestError>
Write a fixture file into the tester’s project, at a path relative to it.
A cross-file rule — one whose reduce reads files other than the subject — needs a
corpus to read, and this is how a test builds it. The subject itself is still written
by RuleTester::run; this is for the other files the rule reaches through
ctx.files().
§Errors
Returns TestError::Setup if the file cannot be written.
Sourcepub fn run(&self, source: &str) -> Result<Vec<Violation>, TestError>
pub fn run(&self, source: &str) -> Result<Vec<Violation>, TestError>
Run the rule over a single source file and return what it reported.
§Errors
Returns TestError::Load if the rule does not load, or TestError::Run if it
throws or breaches a budget.
Sourcepub fn accepts(&self, source: &str) -> Result<(), TestError>
pub fn accepts(&self, source: &str) -> Result<(), TestError>
Assert the rule reports nothing for this source.
§Errors
Returns TestError::Mismatch listing what was reported, since “expected none,
got some” is only actionable if you can see which.
Sourcepub fn reports_at(
&self,
source: &str,
expected: &[(u32, u32)],
) -> Result<(), TestError>
pub fn reports_at( &self, source: &str, expected: &[(u32, u32)], ) -> Result<(), TestError>
Assert the rule reports at exactly these one-based positions, in order.
Positions rather than a count, because a rule reporting the right number of violations in the wrong places is a rule that is wrong — and a count-only assertion is exactly what lets that through.
§Errors
Returns TestError::Mismatch showing expected and actual side by side.
Sourcepub fn reports_messages(
&self,
source: &str,
expected: &[&str],
) -> Result<(), TestError>
pub fn reports_messages( &self, source: &str, expected: &[&str], ) -> Result<(), TestError>
Assert the rule reports exactly these messages, in order.
For a rule that substitutes its own message per match — the position alone would not show whether the right one was chosen.
§Errors
Returns TestError::Mismatch showing both lists.
Trait Implementations§
Source§impl Debug for RuleTester
impl Debug for RuleTester
Source§impl Drop for RuleTester
impl Drop for RuleTester
Auto Trait Implementations§
impl Freeze for RuleTester
impl RefUnwindSafe for RuleTester
impl Send for RuleTester
impl Sync for RuleTester
impl Unpin for RuleTester
impl UnsafeUnpin for RuleTester
impl UnwindSafe for RuleTester
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more