fix(bindings): clear clippy pedantic lints and lint bindings in CI (#77)
* fix(bindings): clear clippy pedantic lints and lint bindings in CI Resolve the pedantic lints that only surfaced under a full `cargo clippy --workspace` (the CI clippy job covered only the core crates, so the Python/Node bindings drifted): - manual_midpoint: `(a + b) / 2.0` -> `f64::midpoint(a, b)` (node + python) - new_without_default: add `Default` impls for the six no-arg Node nodes - type_complexity: factor the pivot/Ichimoku return tuples into `PivotLevels` / `WoodieLevels` / `IchimokuLines` aliases (python) - many_single_char_names: allow at crate level — OHLCV batch helpers bind the conventional o/h/l/c/v column names Add a dedicated `clippy-bindings` CI job (ubuntu-only, with Python + Node toolchains) so future binding lints fail CI instead of slipping through. * ci: lint Python and Node bindings in a dedicated clippy job The main `rust` job's clippy step only covers wickra-core/wickra/ wickra-data/wickra-wasm, so pedantic lints in the PyO3/napi bindings slipped through. Add an ubuntu-only `clippy-bindings` job that provisions Python + Node (needed by the build scripts) and runs `cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings`.
This commit is contained in:
@@ -55,6 +55,37 @@ jobs:
|
||||
# streaming.
|
||||
run: cargo build -p wickra-examples --bins
|
||||
|
||||
# Clippy for the Python and Node bindings. These are kept out of the main
|
||||
# `rust` job because PyO3 / napi build scripts need a Python interpreter and
|
||||
# a Node toolchain on PATH, which the 3-OS matrix job does not provision.
|
||||
# Ubuntu-only is sufficient: the lints are platform-independent.
|
||||
clippy-bindings:
|
||||
name: Clippy bindings
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
|
||||
- name: Clippy (bindings, all targets)
|
||||
run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings
|
||||
|
||||
# Verify the crates still build and test on their declared minimum supported
|
||||
# Rust version. The workspace pins rust-version = "1.86" — that floor is
|
||||
# set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the
|
||||
|
||||
@@ -7310,6 +7310,12 @@ pub struct TdRangeProjectionNode {
|
||||
inner: wc::TdRangeProjection,
|
||||
}
|
||||
|
||||
impl Default for TdRangeProjectionNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl TdRangeProjectionNode {
|
||||
#[napi(constructor)]
|
||||
@@ -7379,6 +7385,12 @@ pub struct TdDifferentialNode {
|
||||
inner: wc::TdDifferential,
|
||||
}
|
||||
|
||||
impl Default for TdDifferentialNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl TdDifferentialNode {
|
||||
#[napi(constructor)]
|
||||
@@ -7434,6 +7446,12 @@ pub struct TdOpenNode {
|
||||
inner: wc::TdOpen,
|
||||
}
|
||||
|
||||
impl Default for TdOpenNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl TdOpenNode {
|
||||
#[napi(constructor)]
|
||||
@@ -7712,6 +7730,12 @@ pub struct HilbertDominantCycleNode {
|
||||
inner: wc::HilbertDominantCycle,
|
||||
}
|
||||
|
||||
impl Default for HilbertDominantCycleNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl HilbertDominantCycleNode {
|
||||
#[napi(constructor)]
|
||||
@@ -7747,6 +7771,12 @@ pub struct AdaptiveCycleNode {
|
||||
inner: wc::AdaptiveCycle,
|
||||
}
|
||||
|
||||
impl Default for AdaptiveCycleNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AdaptiveCycleNode {
|
||||
#[napi(constructor)]
|
||||
@@ -7782,6 +7812,12 @@ pub struct SineWaveNode {
|
||||
inner: wc::SineWave,
|
||||
}
|
||||
|
||||
impl Default for SineWaveNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl SineWaveNode {
|
||||
#[napi(constructor)]
|
||||
@@ -8138,7 +8174,7 @@ impl ValueAreaNode {
|
||||
low: f64,
|
||||
volume: f64,
|
||||
) -> napi::Result<Option<ValueAreaValue>> {
|
||||
let mid = (high + low) / 2.0;
|
||||
let mid = f64::midpoint(high, low);
|
||||
let candle = wc::Candle::new(mid, high, low, mid, volume, 0).map_err(map_err)?;
|
||||
Ok(self.inner.update(candle).map(|o| ValueAreaValue {
|
||||
poc: o.poc,
|
||||
@@ -8161,7 +8197,7 @@ impl ValueAreaNode {
|
||||
let n = high.len();
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for i in 0..n {
|
||||
let mid = (high[i] + low[i]) / 2.0;
|
||||
let mid = f64::midpoint(high[i], low[i]);
|
||||
let candle =
|
||||
wc::Candle::new(mid, high[i], low[i], mid, volume[i], 0).map_err(map_err)?;
|
||||
if let Some(o) = self.inner.update(candle) {
|
||||
@@ -8212,7 +8248,7 @@ impl InitialBalanceNode {
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<InitialBalanceValue>> {
|
||||
let mid = (high + low) / 2.0;
|
||||
let mid = f64::midpoint(high, low);
|
||||
let candle = wc::Candle::new(mid, high, low, mid, 0.0, 0).map_err(map_err)?;
|
||||
Ok(self.inner.update(candle).map(|o| InitialBalanceValue {
|
||||
high: o.high,
|
||||
@@ -8229,7 +8265,7 @@ impl InitialBalanceNode {
|
||||
let n = high.len();
|
||||
let mut out = vec![f64::NAN; n * 2];
|
||||
for i in 0..n {
|
||||
let mid = (high[i] + low[i]) / 2.0;
|
||||
let mid = f64::midpoint(high[i], low[i]);
|
||||
let candle = wc::Candle::new(mid, high[i], low[i], mid, 0.0, 0).map_err(map_err)?;
|
||||
if let Some(o) = self.inner.update(candle) {
|
||||
out[i * 2] = o.high;
|
||||
|
||||
+22
-23
@@ -8,6 +8,9 @@
|
||||
// mandatory even when its body does not read state (e.g. parameterless indicators
|
||||
// like `TypicalPrice`). Clippy's `unused_self` triggers on those signatures.
|
||||
#![allow(clippy::unused_self)]
|
||||
// OHLCV batch helpers bind the conventional single-letter column names
|
||||
// (o/h/l/c/v) that match the domain and the NumPy call sites.
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray1};
|
||||
use pyo3::exceptions::{PyTypeError, PyValueError};
|
||||
@@ -39,6 +42,19 @@ fn flatten(values: Vec<Option<f64>>) -> Vec<f64> {
|
||||
/// Raised instead of panicking when a `NumPy` input is not C-contiguous.
|
||||
const NON_CONTIGUOUS: &str = "array must be C-contiguous; pass np.ascontiguousarray(arr)";
|
||||
|
||||
/// `(pp, r1, r2, r3, s1, s2, s3)` pivot levels returned by Classic/Fibonacci pivots.
|
||||
type PivotLevels = (f64, f64, f64, f64, f64, f64, f64);
|
||||
/// `(pp, r1, r2, s1, s2)` pivot levels returned by Woodie pivots.
|
||||
type WoodieLevels = (f64, f64, f64, f64, f64);
|
||||
/// `(tenkan, kijun, senkou_a, senkou_b, chikou)` Ichimoku lines, each optional during warmup.
|
||||
type IchimokuLines = (
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
);
|
||||
|
||||
// ============================== SMA ==============================
|
||||
|
||||
#[pyclass(name = "SMA", module = "wickra._wickra", skip_from_py_object)]
|
||||
@@ -8064,10 +8080,7 @@ impl PyClassicPivots {
|
||||
}
|
||||
}
|
||||
/// Returns `(pp, r1, r2, r3, s1, s2, s3)` or None during warmup.
|
||||
fn update(
|
||||
&mut self,
|
||||
candle: &Bound<'_, PyAny>,
|
||||
) -> PyResult<Option<(f64, f64, f64, f64, f64, f64, f64)>> {
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<PivotLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self
|
||||
.inner
|
||||
@@ -8146,10 +8159,7 @@ impl PyFibonacciPivots {
|
||||
inner: wc::FibonacciPivots::new(),
|
||||
}
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
candle: &Bound<'_, PyAny>,
|
||||
) -> PyResult<Option<(f64, f64, f64, f64, f64, f64, f64)>> {
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<PivotLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self
|
||||
.inner
|
||||
@@ -8305,7 +8315,7 @@ impl PyWoodiePivots {
|
||||
}
|
||||
}
|
||||
/// Returns `(pp, r1, r2, s1, s2)` or None during warmup.
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<(f64, f64, f64, f64, f64)>> {
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<WoodieLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self.inner.update(c).map(|o| (o.pp, o.r1, o.r2, o.s1, o.s2)))
|
||||
}
|
||||
@@ -9949,18 +9959,7 @@ impl PyIchimoku {
|
||||
}
|
||||
/// Returns `(tenkan, kijun, senkou_a, senkou_b, chikou)` as a 5-tuple
|
||||
/// where each element is `float` or `None`.
|
||||
fn update(
|
||||
&mut self,
|
||||
candle: &Bound<'_, PyAny>,
|
||||
) -> PyResult<
|
||||
Option<(
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
Option<f64>,
|
||||
)>,
|
||||
> {
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<IchimokuLines>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self
|
||||
.inner
|
||||
@@ -10864,7 +10863,7 @@ impl PyValueArea {
|
||||
let mut out = vec![f64::NAN; n * 3];
|
||||
for i in 0..n {
|
||||
// open / close pinned to the midpoint so the candle validates.
|
||||
let mid = (h[i] + l[i]) / 2.0;
|
||||
let mid = f64::midpoint(h[i], l[i]);
|
||||
let candle = wc::Candle::new(mid, h[i], l[i], mid, v[i], 0).map_err(map_err)?;
|
||||
if let Some(o) = self.inner.update(candle) {
|
||||
out[i * 3] = o.poc;
|
||||
@@ -10940,7 +10939,7 @@ impl PyInitialBalance {
|
||||
let n = h.len();
|
||||
let mut out = vec![f64::NAN; n * 2];
|
||||
for i in 0..n {
|
||||
let mid = (h[i] + l[i]) / 2.0;
|
||||
let mid = f64::midpoint(h[i], l[i]);
|
||||
let candle = wc::Candle::new(mid, h[i], l[i], mid, 0.0, 0).map_err(map_err)?;
|
||||
if let Some(o) = self.inner.update(candle) {
|
||||
out[i * 2] = o.high;
|
||||
|
||||
Reference in New Issue
Block a user