Fix to cache correct PathToNode in artifact graph (#6632)
* Add NodePath to artifact graph Since this is cached, this should make PathToNode computation correct even when code is formatted, whitespace changes, and source ranges are different. * Remove dead code * Add unit tests * Add tests for PathToNode conversion * Remove unused parameter * Add missing PathToNode cases * Fix to handle unlabeled arg * Cherry pick unlabeled arg fix * Change PathToNode comment to match TS implementation
This commit is contained in:
@ -15,7 +15,7 @@ use uuid::Uuid;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::KclErrorDetails,
|
errors::KclErrorDetails,
|
||||||
parsing::ast::types::{Node, Program},
|
parsing::ast::types::{Node, Program},
|
||||||
KclError, SourceRange,
|
KclError, NodePath, SourceRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -120,6 +120,7 @@ where
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CodeRef {
|
pub struct CodeRef {
|
||||||
pub range: SourceRange,
|
pub range: SourceRange,
|
||||||
|
pub node_path: NodePath,
|
||||||
// TODO: We should implement this in Rust.
|
// TODO: We should implement this in Rust.
|
||||||
#[serde(default, serialize_with = "serialize_dummy_path_to_node")]
|
#[serde(default, serialize_with = "serialize_dummy_path_to_node")]
|
||||||
#[ts(type = "Array<[string | number, string]>")]
|
#[ts(type = "Array<[string | number, string]>")]
|
||||||
@ -130,6 +131,7 @@ impl CodeRef {
|
|||||||
pub fn placeholder(range: SourceRange) -> Self {
|
pub fn placeholder(range: SourceRange) -> Self {
|
||||||
Self {
|
Self {
|
||||||
range,
|
range,
|
||||||
|
node_path: Default::default(),
|
||||||
path_to_node: Vec::new(),
|
path_to_node: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -825,15 +827,21 @@ fn artifacts_to_update(
|
|||||||
artifact_command: &ArtifactCommand,
|
artifact_command: &ArtifactCommand,
|
||||||
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
responses: &FnvHashMap<Uuid, OkModelingCmdResponse>,
|
||||||
path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
|
path_to_plane_id_map: &FnvHashMap<Uuid, Uuid>,
|
||||||
_ast: &Node<Program>,
|
ast: &Node<Program>,
|
||||||
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
exec_artifacts: &IndexMap<ArtifactId, Artifact>,
|
||||||
) -> Result<Vec<Artifact>, KclError> {
|
) -> Result<Vec<Artifact>, KclError> {
|
||||||
// TODO: Build path-to-node from artifact_command source range. Right now,
|
// TODO: Build path-to-node from artifact_command source range. Right now,
|
||||||
// we're serializing an empty array, and the TS wrapper fills it in with the
|
// we're serializing an empty array, and the TS wrapper fills it in with the
|
||||||
// correct value.
|
// correct value based on NodePath.
|
||||||
let path_to_node = Vec::new();
|
let path_to_node = Vec::new();
|
||||||
|
|
||||||
let range = artifact_command.range;
|
let range = artifact_command.range;
|
||||||
|
let node_path = NodePath::from_range(ast, range).unwrap_or_default();
|
||||||
|
let code_ref = CodeRef {
|
||||||
|
range,
|
||||||
|
node_path,
|
||||||
|
path_to_node,
|
||||||
|
};
|
||||||
|
|
||||||
let uuid = artifact_command.cmd_id;
|
let uuid = artifact_command.cmd_id;
|
||||||
let id = ArtifactId::new(uuid);
|
let id = ArtifactId::new(uuid);
|
||||||
|
|
||||||
@ -855,7 +863,7 @@ fn artifacts_to_update(
|
|||||||
return Ok(vec![Artifact::Plane(Plane {
|
return Ok(vec![Artifact::Plane(Plane {
|
||||||
id,
|
id,
|
||||||
path_ids: Vec::new(),
|
path_ids: Vec::new(),
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
})]);
|
})]);
|
||||||
}
|
}
|
||||||
ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
|
ModelingCmd::EnableSketchMode(EnableSketchMode { entity_id, .. }) => {
|
||||||
@ -891,7 +899,7 @@ fn artifacts_to_update(
|
|||||||
return Ok(vec![Artifact::Plane(Plane {
|
return Ok(vec![Artifact::Plane(Plane {
|
||||||
id: entity_id.into(),
|
id: entity_id.into(),
|
||||||
path_ids,
|
path_ids,
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
})]);
|
})]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -912,15 +920,15 @@ fn artifacts_to_update(
|
|||||||
seg_ids: Vec::new(),
|
seg_ids: Vec::new(),
|
||||||
sweep_id: None,
|
sweep_id: None,
|
||||||
solid2d_id: None,
|
solid2d_id: None,
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
}));
|
}));
|
||||||
let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
|
let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
|
||||||
if let Some(Artifact::Plane(plane)) = plane {
|
if let Some(Artifact::Plane(plane)) = plane {
|
||||||
let code_ref = plane.code_ref.clone();
|
let plane_code_ref = plane.code_ref.clone();
|
||||||
return_arr.push(Artifact::Plane(Plane {
|
return_arr.push(Artifact::Plane(Plane {
|
||||||
id: (*current_plane_id).into(),
|
id: (*current_plane_id).into(),
|
||||||
path_ids: vec![id],
|
path_ids: vec![id],
|
||||||
code_ref,
|
code_ref: plane_code_ref,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if let Some(Artifact::Wall(wall)) = plane {
|
if let Some(Artifact::Wall(wall)) = plane {
|
||||||
@ -960,7 +968,7 @@ fn artifacts_to_update(
|
|||||||
surface_id: None,
|
surface_id: None,
|
||||||
edge_ids: Vec::new(),
|
edge_ids: Vec::new(),
|
||||||
edge_cut_id: None,
|
edge_cut_id: None,
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
common_surface_ids: Vec::new(),
|
common_surface_ids: Vec::new(),
|
||||||
}));
|
}));
|
||||||
let path = artifacts.get(&path_id);
|
let path = artifacts.get(&path_id);
|
||||||
@ -1001,7 +1009,7 @@ fn artifacts_to_update(
|
|||||||
path_id: target,
|
path_id: target,
|
||||||
surface_ids: Vec::new(),
|
surface_ids: Vec::new(),
|
||||||
edge_ids: Vec::new(),
|
edge_ids: Vec::new(),
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
}));
|
}));
|
||||||
let path = artifacts.get(&target);
|
let path = artifacts.get(&target);
|
||||||
if let Some(Artifact::Path(path)) = path {
|
if let Some(Artifact::Path(path)) = path {
|
||||||
@ -1029,7 +1037,7 @@ fn artifacts_to_update(
|
|||||||
})?),
|
})?),
|
||||||
surface_ids: Vec::new(),
|
surface_ids: Vec::new(),
|
||||||
edge_ids: Vec::new(),
|
edge_ids: Vec::new(),
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
}));
|
}));
|
||||||
for section_id in &loft_cmd.section_ids {
|
for section_id in &loft_cmd.section_ids {
|
||||||
let path = artifacts.get(&ArtifactId::new(*section_id));
|
let path = artifacts.get(&ArtifactId::new(*section_id));
|
||||||
@ -1095,6 +1103,7 @@ fn artifacts_to_update(
|
|||||||
path_ids: Vec::new(),
|
path_ids: Vec::new(),
|
||||||
face_code_ref: CodeRef {
|
face_code_ref: CodeRef {
|
||||||
range: sketch_on_face_source_range,
|
range: sketch_on_face_source_range,
|
||||||
|
node_path: NodePath::from_range(ast, sketch_on_face_source_range).unwrap_or_default(),
|
||||||
path_to_node: Vec::new(),
|
path_to_node: Vec::new(),
|
||||||
},
|
},
|
||||||
cmd_id: artifact_command.cmd_id,
|
cmd_id: artifact_command.cmd_id,
|
||||||
@ -1147,6 +1156,7 @@ fn artifacts_to_update(
|
|||||||
path_ids: Vec::new(),
|
path_ids: Vec::new(),
|
||||||
face_code_ref: CodeRef {
|
face_code_ref: CodeRef {
|
||||||
range: sketch_on_face_source_range,
|
range: sketch_on_face_source_range,
|
||||||
|
node_path: NodePath::from_range(ast, sketch_on_face_source_range).unwrap_or_default(),
|
||||||
path_to_node: Vec::new(),
|
path_to_node: Vec::new(),
|
||||||
},
|
},
|
||||||
cmd_id: artifact_command.cmd_id,
|
cmd_id: artifact_command.cmd_id,
|
||||||
@ -1255,7 +1265,7 @@ fn artifacts_to_update(
|
|||||||
consumed_edge_id: cmd.edge_id.into(),
|
consumed_edge_id: cmd.edge_id.into(),
|
||||||
edge_ids: Vec::new(),
|
edge_ids: Vec::new(),
|
||||||
surface_id: None,
|
surface_id: None,
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
}));
|
}));
|
||||||
let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id));
|
let consumed_edge = artifacts.get(&ArtifactId::new(cmd.edge_id));
|
||||||
if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
|
if let Some(Artifact::Segment(consumed_edge)) = consumed_edge {
|
||||||
@ -1271,7 +1281,7 @@ fn artifacts_to_update(
|
|||||||
let return_arr = vec![Artifact::Helix(Helix {
|
let return_arr = vec![Artifact::Helix(Helix {
|
||||||
id,
|
id,
|
||||||
axis_id: None,
|
axis_id: None,
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
})];
|
})];
|
||||||
return Ok(return_arr);
|
return Ok(return_arr);
|
||||||
}
|
}
|
||||||
@ -1280,7 +1290,7 @@ fn artifacts_to_update(
|
|||||||
let return_arr = vec![Artifact::Helix(Helix {
|
let return_arr = vec![Artifact::Helix(Helix {
|
||||||
id,
|
id,
|
||||||
axis_id: Some(edge_id),
|
axis_id: Some(edge_id),
|
||||||
code_ref: CodeRef { range, path_to_node },
|
code_ref,
|
||||||
})];
|
})];
|
||||||
// We could add the reverse graph edge connecting from the edge to
|
// We could add the reverse graph edge connecting from the edge to
|
||||||
// the helix here, but it's not useful right now.
|
// the helix here, but it's not useful right now.
|
||||||
@ -1357,10 +1367,7 @@ fn artifacts_to_update(
|
|||||||
sub_type,
|
sub_type,
|
||||||
solid_ids: solid_ids.clone(),
|
solid_ids: solid_ids.clone(),
|
||||||
tool_ids: tool_ids.clone(),
|
tool_ids: tool_ids.clone(),
|
||||||
code_ref: CodeRef {
|
code_ref: code_ref.clone(),
|
||||||
range,
|
|
||||||
path_to_node: path_to_node.clone(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -95,7 +95,7 @@ pub use lsp::{
|
|||||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||||
};
|
};
|
||||||
pub use modules::ModuleId;
|
pub use modules::ModuleId;
|
||||||
pub use parsing::ast::types::FormatOptions;
|
pub use parsing::ast::types::{FormatOptions, NodePath};
|
||||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
||||||
pub use source_range::SourceRange;
|
pub use source_range::SourceRange;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@ -236,6 +236,10 @@ impl Program {
|
|||||||
self.ast.lint(rule)
|
self.ast.lint(rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn node_path_from_range(&self, range: SourceRange) -> Option<NodePath> {
|
||||||
|
NodePath::from_range(&self.ast, range)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recast(&self) -> String {
|
pub fn recast(&self) -> String {
|
||||||
// Use the default options until we integrate into the UI the ability to change them.
|
// Use the default options until we integrate into the UI the ability to change them.
|
||||||
self.ast.recast(&Default::default(), 0)
|
self.ast.recast(&Default::default(), 0)
|
||||||
|
@ -11,6 +11,7 @@ use std::{
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
|
pub use path::NodePath;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_lsp::lsp_types::{
|
use tower_lsp::lsp_types::{
|
||||||
@ -35,6 +36,7 @@ use crate::{
|
|||||||
mod condition;
|
mod condition;
|
||||||
mod literal_value;
|
mod literal_value;
|
||||||
mod none;
|
mod none;
|
||||||
|
mod path;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Definition<'a> {
|
pub enum Definition<'a> {
|
||||||
@ -159,6 +161,10 @@ impl<T> Node<T> {
|
|||||||
self.start <= pos && pos <= self.end
|
self.start <= pos && pos <= self.end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
self.as_source_range().contains_range(range)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map<U>(self, f: impl Fn(T) -> U) -> Node<U> {
|
pub fn map<U>(self, f: impl Fn(T) -> U) -> Node<U> {
|
||||||
Node {
|
Node {
|
||||||
inner: f(self.inner),
|
inner: f(self.inner),
|
||||||
@ -818,6 +824,11 @@ impl BodyItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
let item_range = SourceRange::from(self);
|
||||||
|
item_range.contains_range(range)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn set_attrs(&mut self, attr: NodeList<Annotation>) {
|
pub(crate) fn set_attrs(&mut self, attr: NodeList<Annotation>) {
|
||||||
match self {
|
match self {
|
||||||
BodyItem::ImportStatement(node) => node.outer_attrs = attr,
|
BodyItem::ImportStatement(node) => node.outer_attrs = attr,
|
||||||
@ -1045,6 +1056,11 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
let expr_range = SourceRange::from(self);
|
||||||
|
expr_range.contains_range(range)
|
||||||
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||||
match self {
|
match self {
|
||||||
@ -1162,6 +1178,21 @@ impl From<&Expr> for SourceRange {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&BinaryPart> for Expr {
|
||||||
|
fn from(value: &BinaryPart) -> Self {
|
||||||
|
match value {
|
||||||
|
BinaryPart::Literal(literal) => Expr::Literal(literal.clone()),
|
||||||
|
BinaryPart::Name(name) => Expr::Name(name.clone()),
|
||||||
|
BinaryPart::BinaryExpression(binary_expression) => Expr::BinaryExpression(binary_expression.clone()),
|
||||||
|
BinaryPart::CallExpression(call_expression) => Expr::CallExpression(call_expression.clone()),
|
||||||
|
BinaryPart::CallExpressionKw(call_expression) => Expr::CallExpressionKw(call_expression.clone()),
|
||||||
|
BinaryPart::UnaryExpression(unary_expression) => Expr::UnaryExpression(unary_expression.clone()),
|
||||||
|
BinaryPart::MemberExpression(member_expression) => Expr::MemberExpression(member_expression.clone()),
|
||||||
|
BinaryPart::IfExpression(e) => Expr::IfExpression(e.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -2795,6 +2826,11 @@ impl MemberObject {
|
|||||||
MemberObject::Identifier(identifier) => identifier.end,
|
MemberObject::Identifier(identifier) => identifier.end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
let sr = SourceRange::from(self);
|
||||||
|
sr.contains_range(range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MemberObject> for SourceRange {
|
impl From<MemberObject> for SourceRange {
|
||||||
@ -2831,6 +2867,11 @@ impl LiteralIdentifier {
|
|||||||
LiteralIdentifier::Literal(literal) => literal.end,
|
LiteralIdentifier::Literal(literal) => literal.end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
let sr = SourceRange::from(self);
|
||||||
|
sr.contains_range(range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LiteralIdentifier> for SourceRange {
|
impl From<LiteralIdentifier> for SourceRange {
|
||||||
@ -3349,6 +3390,11 @@ impl Parameter {
|
|||||||
pub fn optional(&self) -> bool {
|
pub fn optional(&self) -> bool {
|
||||||
self.default_value.is_some()
|
self.default_value.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||||
|
let sr = SourceRange::from(self);
|
||||||
|
sr.contains_range(range)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Parameter> for SourceRange {
|
impl From<&Parameter> for SourceRange {
|
||||||
|
406
rust/kcl-lib/src/parsing/ast/types/path.rs
Normal file
406
rust/kcl-lib/src/parsing/ast/types/path.rs
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::{BodyItem, Expr, MemberObject, Node, Program};
|
||||||
|
use crate::SourceRange;
|
||||||
|
|
||||||
|
/// A traversal path through the AST to a node.
|
||||||
|
///
|
||||||
|
/// Similar to the idea of a `NodeId`, a `NodePath` uniquely identifies a node,
|
||||||
|
/// assuming you know the root node.
|
||||||
|
///
|
||||||
|
/// The implementation doesn't cover all parts of the tree. It currently only
|
||||||
|
/// works on parts of the tree that the frontend uses.
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, PartialEq, Eq, Hash, ts_rs::TS)]
|
||||||
|
#[ts(export_to = "NodePath.ts")]
|
||||||
|
pub struct NodePath {
|
||||||
|
pub steps: Vec<Step>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq, Hash, ts_rs::TS)]
|
||||||
|
#[ts(export_to = "NodePath.ts")]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Step {
|
||||||
|
ProgramBodyItem { index: usize },
|
||||||
|
CallCallee,
|
||||||
|
CallArg { index: usize },
|
||||||
|
CallKwCallee,
|
||||||
|
CallKwUnlabeledArg,
|
||||||
|
CallKwArg { index: usize },
|
||||||
|
BinaryLeft,
|
||||||
|
BinaryRight,
|
||||||
|
UnaryArg,
|
||||||
|
PipeBodyItem { index: usize },
|
||||||
|
ArrayElement { index: usize },
|
||||||
|
ArrayRangeStart,
|
||||||
|
ArrayRangeEnd,
|
||||||
|
ObjectProperty { index: usize },
|
||||||
|
ObjectPropertyKey,
|
||||||
|
ObjectPropertyValue,
|
||||||
|
ExpressionStatementExpr,
|
||||||
|
VariableDeclarationDeclaration,
|
||||||
|
VariableDeclarationInit,
|
||||||
|
FunctionExpressionParam { index: usize },
|
||||||
|
FunctionExpressionBody,
|
||||||
|
FunctionExpressionBodyItem { index: usize },
|
||||||
|
ReturnStatementArg,
|
||||||
|
MemberExpressionObject,
|
||||||
|
MemberExpressionProperty,
|
||||||
|
IfExpressionCondition,
|
||||||
|
IfExpressionThen,
|
||||||
|
IfExpressionElseIf { index: usize },
|
||||||
|
IfExpressionElseIfCond,
|
||||||
|
IfExpressionElseIfBody,
|
||||||
|
IfExpressionElse,
|
||||||
|
ImportStatementItem { index: usize },
|
||||||
|
ImportStatementItemName,
|
||||||
|
ImportStatementItemAlias,
|
||||||
|
LabeledExpressionExpr,
|
||||||
|
LabeledExpressionLabel,
|
||||||
|
AscribedExpressionExpr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodePath {
|
||||||
|
/// Given a program and a [`SourceRange`], return the path to the node that
|
||||||
|
/// contains the range.
|
||||||
|
pub(crate) fn from_range(program: &Node<Program>, range: SourceRange) -> Option<Self> {
|
||||||
|
Self::from_body(&program.body, range, NodePath::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_body(body: &[BodyItem], range: SourceRange, mut path: NodePath) -> Option<NodePath> {
|
||||||
|
for (i, item) in body.iter().enumerate() {
|
||||||
|
if item.contains_range(&range) {
|
||||||
|
path.push(Step::ProgramBodyItem { index: i });
|
||||||
|
return Self::from_body_item(item, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_body_item(body_item: &BodyItem, range: SourceRange, mut path: NodePath) -> Option<NodePath> {
|
||||||
|
match body_item {
|
||||||
|
BodyItem::ImportStatement(node) => match &node.selector {
|
||||||
|
super::ImportSelector::List { items } => {
|
||||||
|
for (i, item) in items.iter().enumerate() {
|
||||||
|
if item.contains_range(&range) {
|
||||||
|
path.push(Step::ImportStatementItem { index: i });
|
||||||
|
if item.name.contains_range(&range) {
|
||||||
|
path.push(Step::ImportStatementItemName);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
if let Some(alias) = &item.alias {
|
||||||
|
if alias.contains_range(&range) {
|
||||||
|
path.push(Step::ImportStatementItemAlias);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super::ImportSelector::Glob(_) => {
|
||||||
|
// TODO: Handle glob imports.
|
||||||
|
}
|
||||||
|
super::ImportSelector::None { .. } => {
|
||||||
|
// TODO: Handle whole-module imports.
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BodyItem::ExpressionStatement(node) => {
|
||||||
|
path.push(Step::ExpressionStatementExpr);
|
||||||
|
return Self::from_expr(&node.expression, range, path);
|
||||||
|
}
|
||||||
|
BodyItem::VariableDeclaration(node) => {
|
||||||
|
if node.declaration.contains_range(&range) {
|
||||||
|
path.push(Step::VariableDeclarationDeclaration);
|
||||||
|
if node.declaration.init.contains_range(&range) {
|
||||||
|
path.push(Step::VariableDeclarationInit);
|
||||||
|
return Self::from_expr(&node.declaration.init, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BodyItem::TypeDeclaration(_) => {}
|
||||||
|
BodyItem::ReturnStatement(node) => {
|
||||||
|
if node.argument.contains_range(&range) {
|
||||||
|
path.push(Step::ReturnStatementArg);
|
||||||
|
return Self::from_expr(&node.argument, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_expr(expr: &Expr, range: SourceRange, mut path: NodePath) -> Option<NodePath> {
|
||||||
|
match expr {
|
||||||
|
Expr::Literal(node) => {
|
||||||
|
if node.contains_range(&range) {
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Name(node) => {
|
||||||
|
if node.contains_range(&range) {
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::TagDeclarator(node) => {
|
||||||
|
if node.contains_range(&range) {
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::BinaryExpression(node) => {
|
||||||
|
let left = Expr::from(&node.left);
|
||||||
|
if left.contains_range(&range) {
|
||||||
|
path.push(Step::BinaryLeft);
|
||||||
|
return Self::from_expr(&left, range, path);
|
||||||
|
}
|
||||||
|
let right = Expr::from(&node.right);
|
||||||
|
if right.contains_range(&range) {
|
||||||
|
path.push(Step::BinaryRight);
|
||||||
|
return Self::from_expr(&right, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::FunctionExpression(node) => {
|
||||||
|
for (i, param) in node.params.iter().enumerate() {
|
||||||
|
// TODO: Check the type annotation and default value.
|
||||||
|
if param.contains_range(&range) {
|
||||||
|
path.push(Step::FunctionExpressionParam { index: i });
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.body.contains_range(&range) {
|
||||||
|
path.push(Step::FunctionExpressionBody);
|
||||||
|
for (i, item) in node.body.body.iter().enumerate() {
|
||||||
|
if item.contains_range(&range) {
|
||||||
|
path.push(Step::FunctionExpressionBodyItem { index: i });
|
||||||
|
return Self::from_body_item(item, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::CallExpression(node) => {
|
||||||
|
if node.callee.contains_range(&range) {
|
||||||
|
path.push(Step::CallCallee);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
for (i, arg) in node.arguments.iter().enumerate() {
|
||||||
|
if arg.contains_range(&range) {
|
||||||
|
path.push(Step::CallArg { index: i });
|
||||||
|
return Self::from_expr(arg, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::CallExpressionKw(node) => {
|
||||||
|
if node.callee.contains_range(&range) {
|
||||||
|
path.push(Step::CallKwCallee);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
if let Some(unlabeled) = &node.unlabeled {
|
||||||
|
if unlabeled.contains_range(&range) {
|
||||||
|
path.push(Step::CallKwUnlabeledArg);
|
||||||
|
return Self::from_expr(unlabeled, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i, arg) in node.arguments.iter().enumerate() {
|
||||||
|
if arg.arg.contains_range(&range) {
|
||||||
|
path.push(Step::CallKwArg { index: i });
|
||||||
|
return Self::from_expr(&arg.arg, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::PipeExpression(node) => {
|
||||||
|
for (i, expr) in node.body.iter().enumerate() {
|
||||||
|
if expr.contains_range(&range) {
|
||||||
|
path.push(Step::PipeBodyItem { index: i });
|
||||||
|
return Self::from_expr(expr, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::PipeSubstitution(_) => {}
|
||||||
|
Expr::ArrayExpression(node) => {
|
||||||
|
for (i, element) in node.elements.iter().enumerate() {
|
||||||
|
if element.contains_range(&range) {
|
||||||
|
path.push(Step::ArrayElement { index: i });
|
||||||
|
return Self::from_expr(element, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::ArrayRangeExpression(node) => {
|
||||||
|
if node.start_element.contains_range(&range) {
|
||||||
|
path.push(Step::ArrayRangeStart);
|
||||||
|
return Self::from_expr(&node.start_element, range, path);
|
||||||
|
}
|
||||||
|
if node.end_element.contains_range(&range) {
|
||||||
|
path.push(Step::ArrayRangeEnd);
|
||||||
|
return Self::from_expr(&node.end_element, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::ObjectExpression(node) => {
|
||||||
|
for (i, property) in node.properties.iter().enumerate() {
|
||||||
|
if property.contains_range(&range) {
|
||||||
|
path.push(Step::ObjectProperty { index: i });
|
||||||
|
if property.key.contains_range(&range) {
|
||||||
|
path.push(Step::ObjectPropertyKey);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
if property.value.contains_range(&range) {
|
||||||
|
path.push(Step::ObjectPropertyValue);
|
||||||
|
return Self::from_expr(&property.value, range, path);
|
||||||
|
}
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::MemberExpression(node) => {
|
||||||
|
if node.object.contains_range(&range) {
|
||||||
|
path.push(Step::MemberExpressionObject);
|
||||||
|
return Self::from_member_expr_object(&node.object, range, path);
|
||||||
|
}
|
||||||
|
if node.property.contains_range(&range) {
|
||||||
|
path.push(Step::MemberExpressionProperty);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::UnaryExpression(node) => {
|
||||||
|
let arg = Expr::from(&node.argument);
|
||||||
|
if arg.contains_range(&range) {
|
||||||
|
path.push(Step::UnaryArg);
|
||||||
|
return Self::from_expr(&arg, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::IfExpression(node) => {
|
||||||
|
if node.cond.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionCondition);
|
||||||
|
return Self::from_expr(&node.cond, range, path);
|
||||||
|
}
|
||||||
|
if node.then_val.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionThen);
|
||||||
|
return Self::from_body(&node.then_val.body, range, path);
|
||||||
|
}
|
||||||
|
for else_if in &node.else_ifs {
|
||||||
|
if else_if.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionElseIf { index: 0 });
|
||||||
|
if else_if.cond.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionElseIfCond);
|
||||||
|
return Self::from_expr(&else_if.cond, range, path);
|
||||||
|
}
|
||||||
|
if else_if.then_val.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionElseIfBody);
|
||||||
|
return Self::from_body(&else_if.then_val.body, range, path);
|
||||||
|
}
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.final_else.contains_range(&range) {
|
||||||
|
path.push(Step::IfExpressionElse);
|
||||||
|
return Self::from_body(&node.final_else.body, range, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::LabelledExpression(node) => {
|
||||||
|
if node.expr.contains_range(&range) {
|
||||||
|
path.push(Step::LabeledExpressionExpr);
|
||||||
|
return Self::from_expr(&node.expr, range, path);
|
||||||
|
}
|
||||||
|
if node.label.contains_range(&range) {
|
||||||
|
path.push(Step::LabeledExpressionLabel);
|
||||||
|
return Some(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::AscribedExpression(node) => {
|
||||||
|
if node.expr.contains_range(&range) {
|
||||||
|
path.push(Step::AscribedExpressionExpr);
|
||||||
|
return Self::from_expr(&node.expr, range, path);
|
||||||
|
}
|
||||||
|
// TODO: Check the type annotation.
|
||||||
|
}
|
||||||
|
Expr::None(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_member_expr_object(mut expr: &MemberObject, range: SourceRange, mut path: NodePath) -> Option<NodePath> {
|
||||||
|
while let MemberObject::MemberExpression(node) = expr {
|
||||||
|
if !node.object.contains_range(&range) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
path.push(Step::MemberExpressionObject);
|
||||||
|
expr = &node.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, step: Step) {
|
||||||
|
self.steps.push(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ModuleId;
|
||||||
|
|
||||||
|
fn range(start: usize, end: usize) -> SourceRange {
|
||||||
|
SourceRange::new(start, end, ModuleId::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_node_path_from_range() {
|
||||||
|
// Read the contents of the file.
|
||||||
|
let contents = std::fs::read_to_string("tests/misc/cube.kcl").unwrap();
|
||||||
|
let program = crate::Program::parse_no_errs(&contents).unwrap();
|
||||||
|
|
||||||
|
// fn cube(sideLength, center) {
|
||||||
|
// ^^^^
|
||||||
|
assert_eq!(
|
||||||
|
NodePath::from_range(&program.ast, range(38, 42)).unwrap(),
|
||||||
|
NodePath {
|
||||||
|
steps: vec![Step::ProgramBodyItem { index: 0 }, Step::VariableDeclarationDeclaration],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// fn cube(sideLength, center) {
|
||||||
|
// ^^^^^^
|
||||||
|
assert_eq!(
|
||||||
|
NodePath::from_range(&program.ast, range(55, 61)).unwrap(),
|
||||||
|
NodePath {
|
||||||
|
steps: vec![
|
||||||
|
Step::ProgramBodyItem { index: 0 },
|
||||||
|
Step::VariableDeclarationDeclaration,
|
||||||
|
Step::VariableDeclarationInit,
|
||||||
|
Step::FunctionExpressionParam { index: 1 }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// |> line(endAbsolute = p1)
|
||||||
|
// ^^
|
||||||
|
assert_eq!(
|
||||||
|
NodePath::from_range(&program.ast, range(293, 295)).unwrap(),
|
||||||
|
NodePath {
|
||||||
|
steps: vec![
|
||||||
|
Step::ProgramBodyItem { index: 0 },
|
||||||
|
Step::VariableDeclarationDeclaration,
|
||||||
|
Step::VariableDeclarationInit,
|
||||||
|
Step::FunctionExpressionBody,
|
||||||
|
Step::FunctionExpressionBodyItem { index: 7 },
|
||||||
|
Step::ReturnStatementArg,
|
||||||
|
Step::PipeBodyItem { index: 2 },
|
||||||
|
Step::CallKwArg { index: 0 },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// myCube = cube(sideLength = 40, center = [0, 0])
|
||||||
|
// ^
|
||||||
|
assert_eq!(
|
||||||
|
NodePath::from_range(&program.ast, range(485, 486)).unwrap(),
|
||||||
|
NodePath {
|
||||||
|
steps: vec![
|
||||||
|
Step::ProgramBodyItem { index: 1 },
|
||||||
|
Step::VariableDeclarationDeclaration,
|
||||||
|
Step::VariableDeclarationInit,
|
||||||
|
Step::CallKwArg { index: 1 },
|
||||||
|
Step::ArrayElement { index: 1 }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -99,6 +99,11 @@ impl SourceRange {
|
|||||||
pos >= self.start() && pos <= self.end()
|
pos >= self.start() && pos <= self.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the range contains another range. Modules must match.
|
||||||
|
pub(crate) fn contains_range(&self, other: &Self) -> bool {
|
||||||
|
self.module_id() == other.module_id() && self.start() <= other.start() && self.end() >= other.end()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
|
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
|
||||||
// Calculate the line and column of the error from the source range.
|
// Calculate the line and column of the error from the source range.
|
||||||
// Lines are zero indexed in vscode so we need to subtract 1.
|
// Lines are zero indexed in vscode so we need to subtract 1.
|
||||||
|
22
rust/kcl-lib/tests/misc/cube.kcl
Normal file
22
rust/kcl-lib/tests/misc/cube.kcl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
@settings(defaultLengthUnit = mm)
|
||||||
|
|
||||||
|
fn cube(sideLength, center) {
|
||||||
|
l = sideLength / 2
|
||||||
|
x = center[0]
|
||||||
|
y = center[1]
|
||||||
|
p0 = [-l + x, -l + y]
|
||||||
|
p1 = [-l + x, l + y]
|
||||||
|
p2 = [l + x, l + y]
|
||||||
|
p3 = [l + x, -l + y]
|
||||||
|
|
||||||
|
return startSketchOn(XY)
|
||||||
|
|> startProfile(at = p0)
|
||||||
|
|> line(endAbsolute = p1)
|
||||||
|
|> line(endAbsolute = p2)
|
||||||
|
|> line(endAbsolute = p3)
|
||||||
|
|> line(endAbsolute = p0)
|
||||||
|
|> close()
|
||||||
|
|> extrude(length = sideLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
myCube = cube(sideLength = 40, center = [0, 0])
|
@ -1,10 +1,10 @@
|
|||||||
//! Wasm bindings for `kcl`.
|
//! Wasm bindings for `kcl`.
|
||||||
|
|
||||||
use gloo_utils::format::JsValueSerdeExt;
|
use gloo_utils::format::JsValueSerdeExt;
|
||||||
use kcl_lib::{pretty::NumericSuffix, CoreDump, Program};
|
use kcl_lib::{pretty::NumericSuffix, CoreDump, Program, SourceRange};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
// wasm_bindgen wrapper for execute
|
// wasm_bindgen wrapper for lint
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
|
pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
@ -18,6 +18,17 @@ pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
|
|||||||
Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?)
|
Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn node_path_from_range(program_ast_json: &str, range_json: &str) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
|
||||||
|
let range: SourceRange = serde_json::from_str(range_json).map_err(|e| e.to_string())?;
|
||||||
|
let node_path = program.node_path_from_range(range);
|
||||||
|
|
||||||
|
JsValue::from_serde(&node_path).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn parse_wasm(kcl_program_source: &str) -> Result<JsValue, String> {
|
pub fn parse_wasm(kcl_program_source: &str) -> Result<JsValue, String> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
@ -2,7 +2,11 @@ import type { ImportStatement } from '@rust/kcl-lib/bindings/ImportStatement'
|
|||||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
import type { TypeDeclaration } from '@rust/kcl-lib/bindings/TypeDeclaration'
|
import type { TypeDeclaration } from '@rust/kcl-lib/bindings/TypeDeclaration'
|
||||||
|
|
||||||
import { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
|
import {
|
||||||
|
ARG_INDEX_FIELD,
|
||||||
|
LABELED_ARG_FIELD,
|
||||||
|
UNLABELED_ARG,
|
||||||
|
} from '@src/lang/queryAstConstants'
|
||||||
import type {
|
import type {
|
||||||
Expr,
|
Expr,
|
||||||
ExpressionStatement,
|
ExpressionStatement,
|
||||||
@ -38,6 +42,7 @@ function moreNodePathFromSourceRange(
|
|||||||
if (
|
if (
|
||||||
(_node.type === 'Name' ||
|
(_node.type === 'Name' ||
|
||||||
_node.type === 'Literal' ||
|
_node.type === 'Literal' ||
|
||||||
|
_node.type === 'Identifier' ||
|
||||||
_node.type === 'TagDeclarator') &&
|
_node.type === 'TagDeclarator') &&
|
||||||
isInRange
|
isInRange
|
||||||
) {
|
) {
|
||||||
@ -64,12 +69,20 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_node.type === 'CallExpressionKw' && isInRange) {
|
if (_node.type === 'CallExpressionKw' && isInRange) {
|
||||||
const { callee, arguments: args } = _node
|
const { callee, arguments: args, unlabeled } = _node
|
||||||
if (callee.type === 'Name' && callee.start <= start && callee.end >= end) {
|
if (callee.type === 'Name' && callee.start <= start && callee.end >= end) {
|
||||||
path.push(['callee', 'CallExpressionKw'])
|
path.push(['callee', 'CallExpressionKw'])
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
if (args.length > 0) {
|
if (
|
||||||
|
unlabeled !== null &&
|
||||||
|
unlabeled.start <= start &&
|
||||||
|
unlabeled.end >= end
|
||||||
|
) {
|
||||||
|
path.push(['unlabeled', UNLABELED_ARG])
|
||||||
|
return moreNodePathFromSourceRange(unlabeled, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (args && args.length > 0) {
|
||||||
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||||
const arg = args[argIndex].arg
|
const arg = args[argIndex].arg
|
||||||
if (arg.start <= start && arg.end >= end) {
|
if (arg.start <= start && arg.end >= end) {
|
||||||
@ -156,19 +169,6 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
|
||||||
const declaration = _node.declaration
|
|
||||||
|
|
||||||
if (declaration.start <= start && declaration.end >= end) {
|
|
||||||
const init = declaration.init
|
|
||||||
if (init.start <= start && init.end >= end) {
|
|
||||||
path.push(['declaration', 'VariableDeclaration'])
|
|
||||||
path.push(['init', ''])
|
|
||||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
if (_node.type === 'UnaryExpression' && isInRange) {
|
if (_node.type === 'UnaryExpression' && isInRange) {
|
||||||
const { argument } = _node
|
const { argument } = _node
|
||||||
if (argument.start <= start && argument.end >= end) {
|
if (argument.start <= start && argument.end >= end) {
|
||||||
@ -257,6 +257,29 @@ function moreNodePathFromSourceRange(
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'LabelledExpression' && isInRange) {
|
||||||
|
const { expr, label } = _node
|
||||||
|
if (expr.start <= start && expr.end >= end) {
|
||||||
|
path.push(['expr', 'LabelledExpression'])
|
||||||
|
return moreNodePathFromSourceRange(expr, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (label.start <= start && label.end >= end) {
|
||||||
|
path.push(['label', 'LabelledExpression'])
|
||||||
|
return moreNodePathFromSourceRange(label, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'AscribedExpression' && isInRange) {
|
||||||
|
const { expr } = _node
|
||||||
|
if (expr.start <= start && expr.end >= end) {
|
||||||
|
path.push(['expr', 'AscribedExpression'])
|
||||||
|
return moreNodePathFromSourceRange(expr, sourceRange, path)
|
||||||
|
}
|
||||||
|
// TODO: Check the type annotation.
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
if (_node.type === 'ImportStatement' && isInRange) {
|
if (_node.type === 'ImportStatement' && isInRange) {
|
||||||
if (_node.selector && _node.selector.type === 'List') {
|
if (_node.selector && _node.selector.type === 'List') {
|
||||||
path.push(['selector', 'ImportStatement'])
|
path.push(['selector', 'ImportStatement'])
|
||||||
|
@ -2,10 +2,18 @@ import type { Node } from '@rust/kcl-lib/bindings/Node'
|
|||||||
import type { Program } from '@rust/kcl-lib/bindings/Program'
|
import type { Program } from '@rust/kcl-lib/bindings/Program'
|
||||||
|
|
||||||
import type { ParseResult } from '@src/lang/wasm'
|
import type { ParseResult } from '@src/lang/wasm'
|
||||||
import { formatNumber, parse, errFromErrWithOutputs } from '@src/lang/wasm'
|
import {
|
||||||
|
formatNumber,
|
||||||
|
parse,
|
||||||
|
errFromErrWithOutputs,
|
||||||
|
rustImplPathToNode,
|
||||||
|
assertParse,
|
||||||
|
} from '@src/lang/wasm'
|
||||||
import { initPromise } from '@src/lang/wasmUtils'
|
import { initPromise } from '@src/lang/wasmUtils'
|
||||||
import { enginelessExecutor } from '@src/lib/testHelpers'
|
import { enginelessExecutor } from '@src/lib/testHelpers'
|
||||||
import { err } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
|
import { topLevelRange } from '@src/lang/util'
|
||||||
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -44,3 +52,17 @@ describe('test errFromErrWithOutputs', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('converts Rust NodePath to PathToNode', async () => {
|
||||||
|
// Convenience for making a SourceRange.
|
||||||
|
const sr = topLevelRange
|
||||||
|
|
||||||
|
const ast = assertParse(`x = 1 + 2
|
||||||
|
y = foo(center = [3, 4])`)
|
||||||
|
expect(await rustImplPathToNode(ast, sr(4, 5))).toStrictEqual(
|
||||||
|
getNodePathFromSourceRange(ast, sr(4, 5))
|
||||||
|
)
|
||||||
|
expect(await rustImplPathToNode(ast, sr(31, 32))).toStrictEqual(
|
||||||
|
getNodePathFromSourceRange(ast, sr(31, 32))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
186
src/lang/wasm.ts
186
src/lang/wasm.ts
@ -16,6 +16,7 @@ import type { MetaSettings } from '@rust/kcl-lib/bindings/MetaSettings'
|
|||||||
import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd'
|
import type { UnitAngle, UnitLength } from '@rust/kcl-lib/bindings/ModelingCmd'
|
||||||
import type { ModulePath } from '@rust/kcl-lib/bindings/ModulePath'
|
import type { ModulePath } from '@rust/kcl-lib/bindings/ModulePath'
|
||||||
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
|
import type { NodePath } from '@rust/kcl-lib/bindings/NodePath'
|
||||||
import type { NumericSuffix } from '@rust/kcl-lib/bindings/NumericSuffix'
|
import type { NumericSuffix } from '@rust/kcl-lib/bindings/NumericSuffix'
|
||||||
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
import type { Operation } from '@rust/kcl-lib/bindings/Operation'
|
||||||
import type { Program } from '@rust/kcl-lib/bindings/Program'
|
import type { Program } from '@rust/kcl-lib/bindings/Program'
|
||||||
@ -26,7 +27,6 @@ import type { UnitAngle as UnitAng } from '@rust/kcl-lib/bindings/UnitAngle'
|
|||||||
import type { UnitLen } from '@rust/kcl-lib/bindings/UnitLen'
|
import type { UnitLen } from '@rust/kcl-lib/bindings/UnitLen'
|
||||||
|
|
||||||
import { KCLError } from '@src/lang/errors'
|
import { KCLError } from '@src/lang/errors'
|
||||||
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
|
||||||
import {
|
import {
|
||||||
type Artifact,
|
type Artifact,
|
||||||
defaultArtifactGraph,
|
defaultArtifactGraph,
|
||||||
@ -54,6 +54,7 @@ import {
|
|||||||
is_points_ccw,
|
is_points_ccw,
|
||||||
kcl_lint,
|
kcl_lint,
|
||||||
kcl_settings,
|
kcl_settings,
|
||||||
|
node_path_from_range,
|
||||||
parse_app_settings,
|
parse_app_settings,
|
||||||
parse_project_settings,
|
parse_project_settings,
|
||||||
parse_wasm,
|
parse_wasm,
|
||||||
@ -61,6 +62,11 @@ import {
|
|||||||
serialize_configuration,
|
serialize_configuration,
|
||||||
serialize_project_configuration,
|
serialize_project_configuration,
|
||||||
} from '@src/lib/wasm_lib_wrapper'
|
} from '@src/lib/wasm_lib_wrapper'
|
||||||
|
import {
|
||||||
|
ARG_INDEX_FIELD,
|
||||||
|
LABELED_ARG_FIELD,
|
||||||
|
UNLABELED_ARG,
|
||||||
|
} from '@src/lang/queryAstConstants'
|
||||||
|
|
||||||
export type { ArrayExpression } from '@rust/kcl-lib/bindings/ArrayExpression'
|
export type { ArrayExpression } from '@rust/kcl-lib/bindings/ArrayExpression'
|
||||||
export type {
|
export type {
|
||||||
@ -294,19 +300,13 @@ export function emptyExecState(): ExecState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execStateFromRust(
|
export function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
||||||
execOutcome: RustExecOutcome,
|
|
||||||
program: Node<Program>
|
|
||||||
): ExecState {
|
|
||||||
const artifactGraph = rustArtifactGraphToMap(execOutcome.artifactGraph)
|
const artifactGraph = rustArtifactGraphToMap(execOutcome.artifactGraph)
|
||||||
// We haven't ported pathToNode logic to Rust yet, so we need to fill it in.
|
// Translate NodePath to PathToNode.
|
||||||
for (const [_id, artifact] of artifactGraph) {
|
for (const [_id, artifact] of artifactGraph) {
|
||||||
if (!artifact) continue
|
if (!artifact) continue
|
||||||
if (!('codeRef' in artifact)) continue
|
if (!('codeRef' in artifact)) continue
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
const pathToNode = pathToNodeFromRustNodePath(artifact.codeRef.nodePath)
|
||||||
program,
|
|
||||||
sourceRangeFromRust(artifact.codeRef.range)
|
|
||||||
)
|
|
||||||
artifact.codeRef.pathToNode = pathToNode
|
artifact.codeRef.pathToNode = pathToNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +406,35 @@ export const kclLint = async (ast: Program): Promise<Array<Discovered>> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function rustImplPathToNode(
|
||||||
|
ast: Program,
|
||||||
|
range: SourceRange
|
||||||
|
): Promise<PathToNode> {
|
||||||
|
const nodePath = await nodePathFromRange(ast, range)
|
||||||
|
if (!nodePath) {
|
||||||
|
// When a NodePath can't be found, we use an empty PathToNode.
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return pathToNodeFromRustNodePath(nodePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function nodePathFromRange(
|
||||||
|
ast: Program,
|
||||||
|
range: SourceRange
|
||||||
|
): Promise<NodePath | null> {
|
||||||
|
try {
|
||||||
|
const nodePath: NodePath | null = await node_path_from_range(
|
||||||
|
JSON.stringify(ast),
|
||||||
|
JSON.stringify(range)
|
||||||
|
)
|
||||||
|
return nodePath
|
||||||
|
} catch (e: any) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error('Caught error getting node path from range', { cause: e })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const recast = (ast: Program): string | Error => {
|
export const recast = (ast: Program): string | Error => {
|
||||||
return recast_wasm(JSON.stringify(ast))
|
return recast_wasm(JSON.stringify(ast))
|
||||||
}
|
}
|
||||||
@ -490,6 +519,143 @@ export async function coreDump(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pathToNodeFromRustNodePath(nodePath: NodePath): PathToNode {
|
||||||
|
const pathToNode: PathToNode = []
|
||||||
|
for (const step of nodePath.steps) {
|
||||||
|
switch (step.type) {
|
||||||
|
case 'ProgramBodyItem':
|
||||||
|
pathToNode.push(['body', ''])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'CallCallee':
|
||||||
|
pathToNode.push(['callee', 'CallExpression'])
|
||||||
|
break
|
||||||
|
case 'CallArg':
|
||||||
|
pathToNode.push(['arguments', 'CallExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'CallKwCallee':
|
||||||
|
pathToNode.push(['callee', 'CallExpressionKw'])
|
||||||
|
break
|
||||||
|
case 'CallKwUnlabeledArg':
|
||||||
|
pathToNode.push(['unlabeled', UNLABELED_ARG])
|
||||||
|
break
|
||||||
|
case 'CallKwArg':
|
||||||
|
pathToNode.push(['arguments', 'CallExpressionKw'])
|
||||||
|
pathToNode.push([step.index, ARG_INDEX_FIELD])
|
||||||
|
pathToNode.push(['arg', LABELED_ARG_FIELD])
|
||||||
|
break
|
||||||
|
case 'BinaryLeft':
|
||||||
|
pathToNode.push(['left', 'BinaryExpression'])
|
||||||
|
break
|
||||||
|
case 'BinaryRight':
|
||||||
|
pathToNode.push(['right', 'BinaryExpression'])
|
||||||
|
break
|
||||||
|
case 'UnaryArg':
|
||||||
|
pathToNode.push(['argument', 'UnaryExpression'])
|
||||||
|
break
|
||||||
|
case 'PipeBodyItem':
|
||||||
|
pathToNode.push(['body', 'PipeExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'ArrayElement':
|
||||||
|
pathToNode.push(['elements', 'ArrayExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'ArrayRangeStart':
|
||||||
|
pathToNode.push(['startElement', 'ArrayRangeExpression'])
|
||||||
|
break
|
||||||
|
case 'ArrayRangeEnd':
|
||||||
|
pathToNode.push(['endElement', 'ArrayRangeExpression'])
|
||||||
|
break
|
||||||
|
case 'ObjectProperty':
|
||||||
|
pathToNode.push(['properties', 'ObjectExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'ObjectPropertyKey':
|
||||||
|
pathToNode.push(['key', 'Property'])
|
||||||
|
break
|
||||||
|
case 'ObjectPropertyValue':
|
||||||
|
pathToNode.push(['value', 'Property'])
|
||||||
|
break
|
||||||
|
case 'ExpressionStatementExpr':
|
||||||
|
pathToNode.push(['expression', 'ExpressionStatement'])
|
||||||
|
break
|
||||||
|
case 'VariableDeclarationDeclaration':
|
||||||
|
pathToNode.push(['declaration', 'VariableDeclaration'])
|
||||||
|
break
|
||||||
|
case 'VariableDeclarationInit':
|
||||||
|
pathToNode.push(['init', ''])
|
||||||
|
break
|
||||||
|
case 'FunctionExpressionParam':
|
||||||
|
pathToNode.push(['params', 'FunctionExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'FunctionExpressionBody':
|
||||||
|
pathToNode.push(['body', 'FunctionExpression'])
|
||||||
|
break
|
||||||
|
case 'FunctionExpressionBodyItem':
|
||||||
|
pathToNode.push(['body', 'FunctionExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'ReturnStatementArg':
|
||||||
|
pathToNode.push(['argument', 'ReturnStatement'])
|
||||||
|
break
|
||||||
|
case 'MemberExpressionObject':
|
||||||
|
pathToNode.push(['object', 'MemberExpression'])
|
||||||
|
break
|
||||||
|
case 'MemberExpressionProperty':
|
||||||
|
pathToNode.push(['property', 'MemberExpression'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionCondition':
|
||||||
|
pathToNode.push(['cond', 'IfExpression'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionThen':
|
||||||
|
pathToNode.push(['then_val', 'IfExpression'])
|
||||||
|
pathToNode.push(['body', 'IfExpression'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionElseIf':
|
||||||
|
pathToNode.push(['else_ifs', 'IfExpression'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionElseIfCond':
|
||||||
|
pathToNode.push(['cond', 'IfExpression'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionElseIfBody':
|
||||||
|
pathToNode.push(['then_val', 'IfExpression'])
|
||||||
|
pathToNode.push(['body', 'IfExpression'])
|
||||||
|
break
|
||||||
|
case 'IfExpressionElse':
|
||||||
|
pathToNode.push(['final_else', 'IfExpression'])
|
||||||
|
pathToNode.push(['body', 'IfExpression'])
|
||||||
|
break
|
||||||
|
case 'ImportStatementItem':
|
||||||
|
pathToNode.push(['selector', 'ImportStatement'])
|
||||||
|
pathToNode.push(['items', 'ImportSelector'])
|
||||||
|
pathToNode.push([step.index, 'index'])
|
||||||
|
break
|
||||||
|
case 'ImportStatementItemName':
|
||||||
|
pathToNode.push(['name', 'ImportItem'])
|
||||||
|
break
|
||||||
|
case 'ImportStatementItemAlias':
|
||||||
|
pathToNode.push(['alias', 'ImportItem'])
|
||||||
|
break
|
||||||
|
case 'LabeledExpressionExpr':
|
||||||
|
pathToNode.push(['expr', 'LabeledExpression'])
|
||||||
|
break
|
||||||
|
case 'LabeledExpressionLabel':
|
||||||
|
pathToNode.push(['label', 'LabeledExpression'])
|
||||||
|
break
|
||||||
|
case 'AscribedExpressionExpr':
|
||||||
|
pathToNode.push(['expr', 'AscribedExpression'])
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
const _exhaustiveCheck: never = step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pathToNode
|
||||||
|
}
|
||||||
|
|
||||||
export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
||||||
return default_app_settings()
|
return default_app_settings()
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ export default class RustContext {
|
|||||||
JSON.stringify(settings)
|
JSON.stringify(settings)
|
||||||
)
|
)
|
||||||
/* Set the default planes, safe to call after execute. */
|
/* Set the default planes, safe to call after execute. */
|
||||||
const outcome = execStateFromRust(result, node)
|
const outcome = execStateFromRust(result)
|
||||||
|
|
||||||
this._defaultPlanes = outcome.defaultPlanes
|
this._defaultPlanes = outcome.defaultPlanes
|
||||||
|
|
||||||
@ -161,29 +161,13 @@ export default class RustContext {
|
|||||||
): Promise<ExecState> {
|
): Promise<ExecState> {
|
||||||
const instance = await this._checkInstance()
|
const instance = await this._checkInstance()
|
||||||
|
|
||||||
const ast: Node<Program> = {
|
|
||||||
body: [],
|
|
||||||
shebang: null,
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
moduleId: 0,
|
|
||||||
nonCodeMeta: {
|
|
||||||
nonCodeNodes: {},
|
|
||||||
startNodes: [],
|
|
||||||
},
|
|
||||||
innerAttrs: [],
|
|
||||||
outerAttrs: [],
|
|
||||||
preComments: [],
|
|
||||||
commentStart: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await instance.bustCacheAndResetScene(
|
const result = await instance.bustCacheAndResetScene(
|
||||||
JSON.stringify(settings),
|
JSON.stringify(settings),
|
||||||
path
|
path
|
||||||
)
|
)
|
||||||
/* Set the default planes, safe to call after execute. */
|
/* Set the default planes, safe to call after execute. */
|
||||||
const outcome = execStateFromRust(result, ast)
|
const outcome = execStateFromRust(result)
|
||||||
|
|
||||||
this._defaultPlanes = outcome.defaultPlanes
|
this._defaultPlanes = outcome.defaultPlanes
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import type {
|
|||||||
is_points_ccw as IsPointsCcw,
|
is_points_ccw as IsPointsCcw,
|
||||||
kcl_lint as KclLint,
|
kcl_lint as KclLint,
|
||||||
kcl_settings as KclSettings,
|
kcl_settings as KclSettings,
|
||||||
|
node_path_from_range as NodePathFromRange,
|
||||||
parse_app_settings as ParseAppSettings,
|
parse_app_settings as ParseAppSettings,
|
||||||
parse_project_settings as ParseProjectSettings,
|
parse_project_settings as ParseProjectSettings,
|
||||||
parse_wasm as ParseWasm,
|
parse_wasm as ParseWasm,
|
||||||
@ -60,6 +61,9 @@ export const format_number: typeof FormatNumber = (...args) => {
|
|||||||
export const kcl_lint: typeof KclLint = (...args) => {
|
export const kcl_lint: typeof KclLint = (...args) => {
|
||||||
return getModule().kcl_lint(...args)
|
return getModule().kcl_lint(...args)
|
||||||
}
|
}
|
||||||
|
export const node_path_from_range: typeof NodePathFromRange = (...args) => {
|
||||||
|
return getModule().node_path_from_range(...args)
|
||||||
|
}
|
||||||
export const is_points_ccw: typeof IsPointsCcw = (...args) => {
|
export const is_points_ccw: typeof IsPointsCcw = (...args) => {
|
||||||
return getModule().is_points_ccw(...args)
|
return getModule().is_points_ccw(...args)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user