* git mv src/wasm-lib rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv wasm-lib to workspace Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv kcl-lib Signed-off-by: Jess Frazelle <github@jessfraz.com> * mv derive docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * resolve file paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * move more shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more paths Signed-off-by: Jess Frazelle <github@jessfraz.com> * make yarn build:wasm work Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix scripts Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> * better references Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix cargo ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix reference Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix more ci Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cargo sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix script Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix a dep Signed-off-by: Jess Frazelle <github@jessfraz.com> * sort Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove unused deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * Revert "remove unused deps" This reverts commit fbabdb062e275fd5cbc1476f8480a1afee15d972. * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * deps; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
230 lines
6.0 KiB
Rust
230 lines
6.0 KiB
Rust
use anyhow::Result;
|
|
use schemars::JsonSchema;
|
|
use serde::Serialize;
|
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
|
|
|
use crate::{lsp::IntoDiagnostic, walk::Node, SourceRange};
|
|
|
|
/// Check the provided AST for any found rule violations.
|
|
///
|
|
/// The Rule trait is automatically implemented for a few other types,
|
|
/// but it can also be manually implemented as required.
|
|
pub trait Rule<'a> {
|
|
/// Check the AST at this specific node for any Finding(s).
|
|
fn check(&self, node: Node<'a>) -> Result<Vec<Discovered>>;
|
|
}
|
|
|
|
impl<'a, FnT> Rule<'a> for FnT
|
|
where
|
|
FnT: Fn(Node<'a>) -> Result<Vec<Discovered>>,
|
|
{
|
|
fn check(&self, n: Node<'a>) -> Result<Vec<Discovered>> {
|
|
self(n)
|
|
}
|
|
}
|
|
|
|
/// Specific discovered lint rule Violation of a particular Finding.
|
|
#[derive(Clone, Debug, ts_rs::TS, Serialize, JsonSchema)]
|
|
#[ts(export)]
|
|
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Discovered {
|
|
/// Zoo Lint Finding information.
|
|
pub finding: Finding,
|
|
|
|
/// Further information about the specific finding.
|
|
pub description: String,
|
|
|
|
/// Source code location.
|
|
pub pos: SourceRange,
|
|
|
|
/// Is this discovered issue overridden by the programmer?
|
|
pub overridden: bool,
|
|
}
|
|
|
|
#[cfg(feature = "pyo3")]
|
|
#[pyo3::pymethods]
|
|
impl Discovered {
|
|
#[getter]
|
|
pub fn finding(&self) -> Finding {
|
|
self.finding.clone()
|
|
}
|
|
|
|
#[getter]
|
|
pub fn description(&self) -> String {
|
|
self.description.clone()
|
|
}
|
|
|
|
#[getter]
|
|
pub fn pos(&self) -> (usize, usize) {
|
|
(self.pos.start(), self.pos.end())
|
|
}
|
|
|
|
#[getter]
|
|
pub fn overridden(&self) -> bool {
|
|
self.overridden
|
|
}
|
|
}
|
|
|
|
impl IntoDiagnostic for Discovered {
|
|
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
|
|
(&self).to_lsp_diagnostics(code)
|
|
}
|
|
|
|
fn severity(&self) -> DiagnosticSeverity {
|
|
(&self).severity()
|
|
}
|
|
}
|
|
|
|
impl IntoDiagnostic for &Discovered {
|
|
fn to_lsp_diagnostics(&self, code: &str) -> Vec<Diagnostic> {
|
|
let message = self.finding.title.to_owned();
|
|
let source_range = self.pos;
|
|
|
|
vec![Diagnostic {
|
|
range: source_range.to_lsp_range(code),
|
|
severity: Some(self.severity()),
|
|
code: None,
|
|
// TODO: this is neat we can pass a URL to a help page here for this specific error.
|
|
code_description: None,
|
|
source: Some("lint".to_string()),
|
|
message,
|
|
related_information: None,
|
|
tags: None,
|
|
data: None,
|
|
}]
|
|
}
|
|
|
|
fn severity(&self) -> DiagnosticSeverity {
|
|
DiagnosticSeverity::INFORMATION
|
|
}
|
|
}
|
|
|
|
/// Abstract lint problem type.
|
|
#[derive(Clone, Debug, PartialEq, ts_rs::TS, Serialize, JsonSchema)]
|
|
#[ts(export)]
|
|
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Finding {
|
|
/// Unique identifier for this particular issue.
|
|
pub code: &'static str,
|
|
|
|
/// Short one-line description of this issue.
|
|
pub title: &'static str,
|
|
|
|
/// Long human-readable description of this issue.
|
|
pub description: &'static str,
|
|
|
|
/// Is this discovered issue experimental?
|
|
pub experimental: bool,
|
|
}
|
|
|
|
impl Finding {
|
|
/// Create a new Discovered finding at the specific Position.
|
|
pub fn at(&self, description: String, pos: SourceRange) -> Discovered {
|
|
Discovered {
|
|
description,
|
|
finding: self.clone(),
|
|
pos,
|
|
overridden: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "pyo3")]
|
|
#[pyo3::pymethods]
|
|
impl Finding {
|
|
#[getter]
|
|
pub fn code(&self) -> &'static str {
|
|
self.code
|
|
}
|
|
|
|
#[getter]
|
|
pub fn title(&self) -> &'static str {
|
|
self.title
|
|
}
|
|
|
|
#[getter]
|
|
pub fn description(&self) -> &'static str {
|
|
self.description
|
|
}
|
|
|
|
#[getter]
|
|
pub fn experimental(&self) -> bool {
|
|
self.experimental
|
|
}
|
|
}
|
|
|
|
macro_rules! def_finding {
|
|
( $code:ident, $title:expr, $description:expr ) => {
|
|
/// Generated Finding
|
|
pub const $code: Finding = $crate::lint::rule::finding!($code, $title, $description);
|
|
};
|
|
}
|
|
pub(crate) use def_finding;
|
|
|
|
macro_rules! finding {
|
|
( $code:ident, $title:expr, $description:expr ) => {
|
|
$crate::lint::rule::Finding {
|
|
code: stringify!($code),
|
|
title: $title,
|
|
description: $description,
|
|
experimental: false,
|
|
}
|
|
};
|
|
}
|
|
pub(crate) use finding;
|
|
#[cfg(test)]
|
|
pub(crate) use test::{assert_finding, assert_no_finding, test_finding, test_no_finding};
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
|
|
macro_rules! assert_no_finding {
|
|
( $check:expr, $finding:expr, $kcl:expr ) => {
|
|
let prog = $crate::parsing::top_level_parse($kcl).unwrap();
|
|
for discovered_finding in prog.lint($check).unwrap() {
|
|
if discovered_finding.finding == $finding {
|
|
assert!(false, "Finding {:?} was emitted", $finding.code);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! assert_finding {
|
|
( $check:expr, $finding:expr, $kcl:expr ) => {
|
|
let prog = $crate::parsing::top_level_parse($kcl).unwrap();
|
|
|
|
for discovered_finding in prog.lint($check).unwrap() {
|
|
if discovered_finding.finding == $finding {
|
|
return;
|
|
}
|
|
}
|
|
assert!(false, "Finding {:?} was not emitted", $finding.code);
|
|
};
|
|
}
|
|
|
|
macro_rules! test_finding {
|
|
( $name:ident, $check:expr, $finding:expr, $kcl:expr ) => {
|
|
#[test]
|
|
fn $name() {
|
|
$crate::lint::rule::assert_finding!($check, $finding, $kcl);
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! test_no_finding {
|
|
( $name:ident, $check:expr, $finding:expr, $kcl:expr ) => {
|
|
#[test]
|
|
fn $name() {
|
|
$crate::lint::rule::assert_no_finding!($check, $finding, $kcl);
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use assert_finding;
|
|
pub(crate) use assert_no_finding;
|
|
pub(crate) use test_finding;
|
|
pub(crate) use test_no_finding;
|
|
}
|