Skip to content

Commit

Permalink
Add custom lints to the language server (#129)
Browse files Browse the repository at this point in the history
* custom lint rules

* allow lsp to run even if the lints directory is missing
  • Loading branch information
mattwparas authored Dec 31, 2023
1 parent 02614ba commit 39b2d61
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 27 deletions.
98 changes: 86 additions & 12 deletions crates/steel-core/src/parser/tryfrom_visitor.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use steel_parser::parser::SyntaxObject;

use crate::gc::Gc;
use crate::values::lists::List;

use crate::{parser::ast::ExprKind, rvals::Syntax};
Expand Down Expand Up @@ -231,6 +234,14 @@ impl SyntaxObjectFromExprKindRef {
}
}

fn convert_location(s: &SyntaxObject) -> Result<SteelVal> {
let span = s.span;

let atom = SteelVal::try_from(s.clone())?;

Ok(Syntax::proto(atom.clone(), atom, span).into())
}

impl VisitorMut for SyntaxObjectFromExprKindRef {
type Output = Result<SteelVal>;

Expand All @@ -242,7 +253,7 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {
let span = f.location.span;

let expr = [
SteelVal::try_from(f.location.clone())?,
convert_location(&f.location)?,
self.visit(&f.test_expr)?,
self.visit(&f.then_expr)?,
self.visit(&f.else_expr)?,
Expand All @@ -260,7 +271,7 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {
let span = define.location.span;

let expr = [
SteelVal::try_from(define.location.clone())?,
convert_location(&define.location)?,
self.visit(&define.name)?,
self.visit(&define.body)?,
];
Expand All @@ -283,9 +294,22 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {
.map(|x| self.visit(&x))
.collect::<Result<List<_>>>()?;

let span_vec = args
.iter()
.map(|x| {
if let SteelVal::SyntaxObject(s) = x {
s.syntax_loc()
} else {
unreachable!()
}
})
.collect::<Vec<_>>();

let args_span = Span::coalesce_span(&span_vec);

let expr = [
SteelVal::try_from(lambda_function.location.clone())?,
SteelVal::ListV(args),
convert_location(&lambda_function.location)?,
Syntax::new(SteelVal::ListV(args), args_span).into(),
self.visit(&lambda_function.body)?,
];

Expand All @@ -297,7 +321,7 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {
TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::Begin(begin.clone()))?;

let span = begin.location.span;
let mut exprs = vec![SteelVal::try_from(begin.location.clone())?];
let mut exprs = vec![convert_location(&begin.location)?];
for expr in &begin.exprs {
exprs.push(self.visit(&expr)?);
}
Expand All @@ -306,10 +330,7 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {

fn visit_return(&mut self, r: &steel_parser::ast::Return) -> Self::Output {
let span = r.location.span;
let expr = [
SteelVal::try_from(r.location.clone())?,
self.visit(&r.expr)?,
];
let expr = [convert_location(&r.location)?, self.visit(&r.expr)?];
Ok(Syntax::new_with_source(SteelVal::ListV(expr.into_iter().collect()), span).into())
}

Expand Down Expand Up @@ -395,7 +416,7 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {

let span = s.location.span;
let expr = [
SteelVal::try_from(s.location.clone())?,
convert_location(&s.location)?,
self.visit(&s.variable)?,
self.visit(&s.expr)?,
];
Expand All @@ -406,8 +427,61 @@ impl VisitorMut for SyntaxObjectFromExprKindRef {
stop!(Generic => "internal compiler error - could not translate require to steel value")
}

fn visit_let(&mut self, _l: &steel_parser::ast::Let) -> Self::Output {
todo!()
fn visit_let(&mut self, l: &steel_parser::ast::Let) -> Self::Output {
let raw: SteelVal = TryFromExprKindForSteelVal::try_from_expr_kind_quoted(ExprKind::Let(
Box::new(l.clone()),
))?;

let span = l.location.span;

let items = l
.bindings
.iter()
.map(|(x, y)| {
let items = vec![self.visit(x)?, self.visit(y)?];

let span_vec = items
.iter()
.map(|x| {
if let SteelVal::SyntaxObject(s) = x {
s.syntax_loc()
} else {
unreachable!()
}
})
.collect::<Vec<_>>();

let items_span = Span::coalesce_span(&span_vec);

Ok(SteelVal::SyntaxObject(Gc::new(Syntax::new(
SteelVal::ListV(items.into()),
items_span,
))))
})
.collect::<std::result::Result<List<SteelVal>, SteelErr>>()?;
// .into(),

let span_vec = items
.iter()
.map(|x| {
if let SteelVal::SyntaxObject(s) = x {
s.syntax_loc()
} else {
unreachable!()
}
})
.collect::<Vec<_>>();

let items_span = Span::coalesce_span(&span_vec);

let expr = [
convert_location(&l.location)?,
// Bindings
Syntax::new(items.into(), items_span).into(),
self.visit(&l.body_expr)?,
];

Ok(Syntax::proto(raw, SteelVal::ListV(expr.into_iter().collect()), span).into())
}
}

Expand Down
41 changes: 26 additions & 15 deletions crates/steel-language-server/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,10 +772,17 @@ impl Backend {

free_identifiers_and_unused.append(&mut static_arity_checking);

let now = std::time::Instant::now();

// TODO: Enable this once the syntax object let conversion is implemented
// let mut user_defined_lints =
// LINT_ENGINE.with_borrow_mut(|x| x.diagnostics(&rope, &analysis.exprs));
// free_identifiers_and_unused.append(&mut user_defined_lints);
let mut user_defined_lints =
LINT_ENGINE.with_borrow_mut(|x| x.diagnostics(&rope, &analysis.exprs));

log::debug!("Lints found: {:#?}", user_defined_lints);

free_identifiers_and_unused.append(&mut user_defined_lints);

log::debug!("User defined lints time taken: {:?}", now.elapsed());

// All the diagnostics total
free_identifiers_and_unused
Expand Down Expand Up @@ -858,7 +865,7 @@ impl steel::steel_vm::engine::ModuleResolver for ExternalModuleResolver {

thread_local! {
pub static ENGINE: RefCell<Engine> = RefCell::new(Engine::new());
// pub static LINT_ENGINE: RefCell<UserDefinedLintEngine> = RefCell::new(configure_lints().unwrap());
pub static LINT_ENGINE: RefCell<UserDefinedLintEngine> = RefCell::new(configure_lints().unwrap());
pub static DIAGNOSTICS: RefCell<Vec<SteelDiagnostic>> = RefCell::new(Vec::new());
}

Expand All @@ -880,9 +887,11 @@ impl UserDefinedLintEngine {
for lint in lints.iter() {
for object in &syntax_objects {
if let Ok(o) = object.clone() {
self.engine
.call_function_by_name_with_args(lint, vec![o])
.ok();
log::debug!("calling: {} with {}", lint, o);

let res = self.engine.call_function_by_name_with_args(lint, vec![o]);

log::debug!("{:?}", res);
}
}
}
Expand Down Expand Up @@ -916,6 +925,7 @@ fn configure_lints() -> std::result::Result<UserDefinedLintEngine, Box<dyn Error
let lints = Arc::new(RwLock::new(HashSet::new()));

diagnostics.register_fn("suggest", move |span: Span, message: SteelString| {
log::debug!("Adding suggestion at: {:?} - {:?}", span, message);
DIAGNOSTICS.with_borrow_mut(|x| x.push(SteelDiagnostic { span, message }));
});

Expand All @@ -932,17 +942,18 @@ fn configure_lints() -> std::result::Result<UserDefinedLintEngine, Box<dyn Error

engine.register_module(diagnostics);

// Load all of the lints - we'll want to grab
// all functions that get registered via define-lint - which means
// just give me a name
for file in std::fs::read_dir(&directory)? {
let file = file?;
if let Ok(directory) = std::fs::read_dir(directory) {
for file in directory {
let file = file?;

if file.path().is_file() {
let contents = std::fs::read_to_string(file.path())?;
if file.path().is_file() {
let contents = std::fs::read_to_string(file.path())?;

engine.compile_and_run_raw_program(&contents)?;
engine.compile_and_run_raw_program(&contents)?;
}
}
} else {
log::info!("Missing lints directory in $STEEL_LSP_HOME");
}

Ok(UserDefinedLintEngine { engine, lints })
Expand Down

0 comments on commit 39b2d61

Please sign in to comment.