diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index f5e51aac6..0c123d532 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -786,7 +786,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => { // error text on hover await page.hover('.cm-lint-marker-error') - await expect(page.getByText('syntax: Unexpected token')).toBeVisible() + await expect(page.getByText('Unexpected token')).toBeVisible() // select the line that's causing the error and delete it await page.getByText('$ error').click() diff --git a/src/lang/KclSingleton.ts b/src/lang/KclSingleton.ts index 685d96a6c..79604ab68 100644 --- a/src/lang/KclSingleton.ts +++ b/src/lang/KclSingleton.ts @@ -1,4 +1,4 @@ -import { executeAst } from 'useStore' +import { executeAst, lintAst } from 'useStore' import { Selections } from 'lib/selections' import { KCLError, kclErrorsToDiagnostics } from './errors' import { uuidv4 } from 'lib/utils' @@ -211,6 +211,9 @@ export class KclManager { ast, engineCommandManager: this.engineCommandManager, }) + + editorManager.addDiagnostics(await lintAst({ ast: ast })) + sceneInfra.modelingSend({ type: 'code edit during sketch' }) defaultSelectionFilter(programMemory, this.engineCommandManager) @@ -261,7 +264,10 @@ export class KclManager { return } const newAst = this.safeParse(newCode) - if (!newAst) return + if (!newAst) { + this.clearAst() + return + } codeManager.updateCodeEditor(newCode) // Write the file to disk. await codeManager.writeToFile() @@ -278,6 +284,9 @@ export class KclManager { engineCommandManager: this.engineCommandManager, useFakeExecutor: true, }) + + editorManager.addDiagnostics(await lintAst({ ast: ast })) + this._logs = logs this._kclErrors = errors this._programMemory = programMemory diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index b22670ff4..d1fe78af0 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -2,6 +2,7 @@ import init, { parse_wasm, recast_wasm, execute_wasm, + kcl_lint, lexer_wasm, modify_ast_for_sketch_wasm, is_points_ccw, @@ -20,6 +21,7 @@ import { KCLError } from './errors' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { EngineCommandManager } from './std/engineConnection' import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn' +import { Discovered } from '../wasm-lib/kcl/bindings/Discovered' import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem' import type { Program } from '../wasm-lib/kcl/bindings/Program' import type { Token } from '../wasm-lib/kcl/bindings/Token' @@ -205,6 +207,17 @@ export const _executor = async ( } } +export const kclLint = async (ast: Program): Promise> => { + try { + const discovered_findings: Array = await kcl_lint( + JSON.stringify(ast) + ) + return discovered_findings + } catch (e: any) { + return Promise.reject(e) + } +} + export const recast = (ast: Program): string | Error => { return recast_wasm(JSON.stringify(ast)) } diff --git a/src/useStore.ts b/src/useStore.ts index 0a1de16de..26953bb2c 100644 --- a/src/useStore.ts +++ b/src/useStore.ts @@ -5,11 +5,13 @@ import { _executor, ProgramMemory, programMemoryInit, + kclLint, } from './lang/wasm' import { enginelessExecutor } from './lib/testHelpers' import { EngineCommandManager } from './lang/std/engineConnection' import { KCLError } from './lang/errors' import { SidebarType } from 'components/ModelingSidebar/ModelingPanes' +import { Diagnostic } from '@codemirror/lint' export type ToolTip = | 'lineTo' @@ -187,3 +189,24 @@ export async function executeAst({ } } } + +export async function lintAst({ + ast, +}: { + ast: Program +}): Promise> { + try { + const discovered_findings = await kclLint(ast) + return discovered_findings.map((lint) => { + return { + message: lint.finding.title, + severity: 'info', + from: lint.pos[0], + to: lint.pos[1], + } + }) + } catch (e: any) { + console.log(e) + return [] + } +} diff --git a/src/wasm-lib/kcl/src/lint/rule.rs b/src/wasm-lib/kcl/src/lint/rule.rs index 4668a8036..20b729b0e 100644 --- a/src/wasm-lib/kcl/src/lint/rule.rs +++ b/src/wasm-lib/kcl/src/lint/rule.rs @@ -1,4 +1,6 @@ use anyhow::Result; +use schemars::JsonSchema; +use serde::Serialize; use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity}; use crate::{executor::SourceRange, lint::Node, lsp::IntoDiagnostic}; @@ -22,8 +24,10 @@ where } /// Specific discovered lint rule Violation of a particular Finding. -#[derive(Clone, Debug)] +#[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, @@ -83,8 +87,10 @@ impl IntoDiagnostic for Discovered { } /// Abstract lint problem type. -#[derive(Clone, Debug, PartialEq)] +#[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, diff --git a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs index 49a12c707..2566504e7 100644 --- a/src/wasm-lib/kcl/src/lsp/kcl/mod.rs +++ b/src/wasm-lib/kcl/src/lsp/kcl/mod.rs @@ -37,11 +37,13 @@ use super::backend::{InnerHandle, UpdateHandle}; use crate::{ ast::types::VariableKind, executor::SourceRange, - lint::checks, lsp::{backend::Backend as _, safemap::SafeMap, util::IntoDiagnostic}, parser::PIPE_OPERATOR, }; +#[cfg(not(target_arch = "wasm32"))] +use crate::lint::checks; + /// A subcommand for running the server. #[derive(Clone, Debug)] #[cfg_attr(feature = "cli", derive(Parser))] @@ -256,12 +258,14 @@ impl crate::lsp::backend::Backend for Backend { // This function automatically executes if we should & updates the diagnostics if we got // errors. if self.execute(¶ms, ast.clone()).await.is_err() { - // if there was an issue, let's bail and avoid trying to lint. return; } - for discovered_finding in ast.lint(checks::lint_variables).into_iter().flatten() { - self.add_to_diagnostics(¶ms, discovered_finding).await; + #[cfg(not(target_arch = "wasm32"))] + { + for discovered_finding in ast.lint(checks::lint_variables).into_iter().flatten() { + self.add_to_diagnostics(¶ms, discovered_finding).await; + } } } } diff --git a/src/wasm-lib/src/wasm.rs b/src/wasm-lib/src/wasm.rs index be4828d30..2a59ecf81 100644 --- a/src/wasm-lib/src/wasm.rs +++ b/src/wasm-lib/src/wasm.rs @@ -7,7 +7,7 @@ use std::{ use futures::stream::TryStreamExt; use gloo_utils::format::JsValueSerdeExt; -use kcl_lib::{coredump::CoreDump, engine::EngineManager, executor::ExecutorSettings}; +use kcl_lib::{coredump::CoreDump, engine::EngineManager, executor::ExecutorSettings, lint::checks}; use tower_lsp::{LspService, Server}; use wasm_bindgen::prelude::*; @@ -59,6 +59,20 @@ pub async fn execute_wasm( JsValue::from_serde(&memory).map_err(|e| e.to_string()) } +// wasm_bindgen wrapper for execute +#[wasm_bindgen] +pub async fn kcl_lint(program_str: &str) -> Result { + console_error_panic_hook::set_once(); + + let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?; + let mut findings = vec![]; + for discovered_finding in program.lint(checks::lint_variables).into_iter().flatten() { + findings.push(discovered_finding); + } + + Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?) +} + // wasm_bindgen wrapper for creating default planes #[wasm_bindgen] pub async fn make_default_planes(