Annotations syntax and per-file default units preparatory work (#4822)
* Parse annotations Signed-off-by: Nick Cameron <nrc@ncameron.org> * Propagate settings from annotations to exec_state Signed-off-by: Nick Cameron <nrc@ncameron.org> --------- Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
73
src/wasm-lib/kcl/src/execution/annotations.rs
Normal file
73
src/wasm-lib/kcl/src/execution/annotations.rs
Normal file
@ -0,0 +1,73 @@
|
||||
//! Data on available annotations.
|
||||
|
||||
use super::kcl_value::{UnitAngle, UnitLen};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
parsing::ast::types::{Expr, Node, NonCodeValue, ObjectProperty},
|
||||
KclError, SourceRange,
|
||||
};
|
||||
|
||||
pub(super) const SETTINGS: &str = "settings";
|
||||
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
||||
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||
|
||||
pub(super) fn expect_properties<'a>(
|
||||
for_key: &'static str,
|
||||
annotation: &'a NonCodeValue,
|
||||
source_range: SourceRange,
|
||||
) -> Result<&'a [Node<ObjectProperty>], KclError> {
|
||||
match annotation {
|
||||
NonCodeValue::Annotation { name, properties } => {
|
||||
assert_eq!(name.name, for_key);
|
||||
Ok(&**properties.as_ref().ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Empty `{for_key}` annotation"),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
|
||||
match expr {
|
||||
Expr::Identifier(id) => Ok(&id.name),
|
||||
e => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
|
||||
source_ranges: vec![e.into()],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
impl UnitLen {
|
||||
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||
match s {
|
||||
"mm" => Ok(UnitLen::Mm),
|
||||
"cm" => Ok(UnitLen::Cm),
|
||||
"m" => Ok(UnitLen::M),
|
||||
"inch" | "in" => Ok(UnitLen::Inches),
|
||||
"ft" => Ok(UnitLen::Feet),
|
||||
"yd" => Ok(UnitLen::Yards),
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unexpected settings value: `{value}`; expected one of `mm`, `cm`, `m`, `inch`, `ft`, `yd`"
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UnitAngle {
|
||||
pub(super) fn from_str(s: &str, source_range: SourceRange) -> Result<Self, KclError> {
|
||||
match s {
|
||||
"deg" => Ok(UnitAngle::Degrees),
|
||||
"rad" => Ok(UnitAngle::Radians),
|
||||
value => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Unexpected settings value: `{value}`; expected one of `deg`, `rad`"),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,10 @@ use crate::{
|
||||
errors::KclErrorDetails,
|
||||
exec::{ProgramMemory, Sketch},
|
||||
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||
parsing::{
|
||||
ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
std::{args::Arg, FnAsArg},
|
||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
@ -561,3 +564,52 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO called UnitLen so as not to clash with UnitLength in settings)
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnitLen {
|
||||
Mm,
|
||||
Cm,
|
||||
M,
|
||||
Inches,
|
||||
Feet,
|
||||
Yards,
|
||||
}
|
||||
|
||||
impl TryFrom<NumericSuffix> for UnitLen {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
|
||||
match suffix {
|
||||
NumericSuffix::Mm => Ok(Self::Mm),
|
||||
NumericSuffix::Cm => Ok(Self::Cm),
|
||||
NumericSuffix::M => Ok(Self::M),
|
||||
NumericSuffix::Inch => Ok(Self::Inches),
|
||||
NumericSuffix::Ft => Ok(Self::Feet),
|
||||
NumericSuffix::Yd => Ok(Self::Yards),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum UnitAngle {
|
||||
Degrees,
|
||||
Radians,
|
||||
}
|
||||
|
||||
impl TryFrom<NumericSuffix> for UnitAngle {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
|
||||
match suffix {
|
||||
NumericSuffix::Deg => Ok(Self::Degrees),
|
||||
NumericSuffix::Rad => Ok(Self::Radians),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub use function_param::FunctionParam;
|
||||
pub use kcl_value::{KclObjectFields, KclValue};
|
||||
use uuid::Uuid;
|
||||
|
||||
mod annotations;
|
||||
pub(crate) mod cache;
|
||||
mod cad_op;
|
||||
mod exec_ast;
|
||||
@ -36,8 +37,8 @@ use crate::{
|
||||
execution::cache::{CacheInformation, CacheResult},
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::types::{
|
||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, Program as AstProgram,
|
||||
TagDeclarator, TagNode,
|
||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
|
||||
Program as AstProgram, TagDeclarator, TagNode,
|
||||
},
|
||||
settings::types::UnitLength,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
@ -88,6 +89,8 @@ pub struct ModuleState {
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
pub operations: Vec<Operation>,
|
||||
/// Settings specified from annotations.
|
||||
pub settings: MetaSettings,
|
||||
}
|
||||
|
||||
impl Default for ExecState {
|
||||
@ -186,6 +189,56 @@ impl GlobalState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetaSettings {
|
||||
pub default_length_units: kcl_value::UnitLen,
|
||||
pub default_angle_units: kcl_value::UnitAngle,
|
||||
}
|
||||
|
||||
impl Default for MetaSettings {
|
||||
fn default() -> Self {
|
||||
MetaSettings {
|
||||
default_length_units: kcl_value::UnitLen::Mm,
|
||||
default_angle_units: kcl_value::UnitAngle::Degrees,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetaSettings {
|
||||
fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
|
||||
|
||||
for p in properties {
|
||||
match &*p.inner.key.name {
|
||||
annotations::SETTINGS_UNIT_LENGTH => {
|
||||
let value = annotations::expect_ident(&p.inner.value)?;
|
||||
let value = kcl_value::UnitLen::from_str(value, source_range)?;
|
||||
self.default_length_units = value;
|
||||
}
|
||||
annotations::SETTINGS_UNIT_ANGLE => {
|
||||
let value = annotations::expect_ident(&p.inner.value)?;
|
||||
let value = kcl_value::UnitAngle::from_str(value, source_range)?;
|
||||
self.default_angle_units = value;
|
||||
}
|
||||
name => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
|
||||
annotations::SETTINGS_UNIT_LENGTH,
|
||||
annotations::SETTINGS_UNIT_ANGLE
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -2020,6 +2073,22 @@ impl ExecutorContext {
|
||||
exec_state: &mut ExecState,
|
||||
body_type: BodyType,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
if let Some((annotation, source_range)) = program
|
||||
.non_code_meta
|
||||
.start_nodes
|
||||
.iter()
|
||||
.filter_map(|n| {
|
||||
n.annotation(annotations::SETTINGS)
|
||||
.map(|result| (result, n.as_source_range()))
|
||||
})
|
||||
.next()
|
||||
{
|
||||
exec_state
|
||||
.mod_local
|
||||
.settings
|
||||
.update_from_annotation(annotation, source_range)?;
|
||||
}
|
||||
|
||||
let mut last_expr = None;
|
||||
// Iterate over the body of the program.
|
||||
for statement in &program.body {
|
||||
|
@ -1000,52 +1000,22 @@ pub struct NonCodeNode {
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl Node<NonCodeNode> {
|
||||
pub fn format(&self, indentation: &str) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Line,
|
||||
} => format!(" // {}\n", value),
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Block,
|
||||
} => format!(" /* {} */", value),
|
||||
NonCodeValue::BlockComment { value, style } => match style {
|
||||
CommentStyle::Block => format!("{}/* {} */", indentation, value),
|
||||
CommentStyle::Line => {
|
||||
if value.trim().is_empty() {
|
||||
format!("{}//\n", indentation)
|
||||
} else {
|
||||
format!("{}// {}\n", indentation, value.trim())
|
||||
}
|
||||
}
|
||||
},
|
||||
NonCodeValue::NewLineBlockComment { value, style } => {
|
||||
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
||||
match style {
|
||||
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
||||
CommentStyle::Line => {
|
||||
if value.trim().is_empty() {
|
||||
format!("{}{}//\n", add_start_new_line, indentation)
|
||||
} else {
|
||||
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NonCodeNode {
|
||||
#[cfg(test)]
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||
NonCodeValue::Annotation { name, .. } => name.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn annotation(&self, expected_name: &str) -> Option<&NonCodeValue> {
|
||||
match &self.value {
|
||||
a @ NonCodeValue::Annotation { name, .. } if name.name == expected_name => Some(a),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1063,6 +1033,7 @@ pub enum CommentStyle {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum NonCodeValue {
|
||||
/// An inline comment.
|
||||
/// Here are examples:
|
||||
@ -1095,6 +1066,10 @@ pub enum NonCodeValue {
|
||||
// A new line like `\n\n` NOT a new line like `\n`.
|
||||
// This is also not a comment.
|
||||
NewLine,
|
||||
Annotation {
|
||||
name: Node<Identifier>,
|
||||
properties: Option<Vec<Node<ObjectProperty>>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
|
@ -283,38 +283,86 @@ fn non_code_node(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
|
||||
}
|
||||
|
||||
fn annotation(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
let at = at_sign.parse_next(i)?;
|
||||
let name = binding_name.parse_next(i)?;
|
||||
let mut end = name.end;
|
||||
|
||||
let properties = if peek(open_paren).parse_next(i).is_ok() {
|
||||
open_paren(i)?;
|
||||
ignore_whitespace(i);
|
||||
let properties: Vec<_> = separated(
|
||||
0..,
|
||||
separated_pair(
|
||||
terminated(identifier, opt(whitespace)),
|
||||
terminated(one_of((TokenType::Operator, "=")), opt(whitespace)),
|
||||
expression,
|
||||
)
|
||||
.map(|(key, value)| Node {
|
||||
start: key.start,
|
||||
end: value.end(),
|
||||
module_id: key.module_id,
|
||||
inner: ObjectProperty {
|
||||
key,
|
||||
value,
|
||||
digest: None,
|
||||
},
|
||||
}),
|
||||
comma_sep,
|
||||
)
|
||||
.parse_next(i)?;
|
||||
ignore_trailing_comma(i);
|
||||
ignore_whitespace(i);
|
||||
end = close_paren(i)?.end;
|
||||
Some(properties)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let value = NonCodeValue::Annotation { name, properties };
|
||||
Ok(Node::new(
|
||||
NonCodeNode { value, digest: None },
|
||||
at.start,
|
||||
end,
|
||||
at.module_id,
|
||||
))
|
||||
}
|
||||
|
||||
// Matches remaining three cases of NonCodeValue
|
||||
fn non_code_node_no_leading_whitespace(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
any.verify_map(|token: Token| {
|
||||
if token.is_code_token() {
|
||||
None
|
||||
} else {
|
||||
let value = match token.token_type {
|
||||
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
|
||||
TokenType::LineComment => NonCodeValue::BlockComment {
|
||||
value: token.value.trim_start_matches("//").trim().to_owned(),
|
||||
style: CommentStyle::Line,
|
||||
},
|
||||
TokenType::BlockComment => NonCodeValue::BlockComment {
|
||||
style: CommentStyle::Block,
|
||||
value: token
|
||||
.value
|
||||
.trim_start_matches("/*")
|
||||
.trim_end_matches("*/")
|
||||
.trim()
|
||||
.to_owned(),
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
Some(Node::new(
|
||||
NonCodeNode { value, digest: None },
|
||||
token.start,
|
||||
token.end,
|
||||
token.module_id,
|
||||
))
|
||||
}
|
||||
})
|
||||
.context(expected("Non-code token (comments or whitespace)"))
|
||||
alt((
|
||||
annotation,
|
||||
any.verify_map(|token: Token| {
|
||||
if token.is_code_token() {
|
||||
None
|
||||
} else {
|
||||
let value = match token.token_type {
|
||||
TokenType::Whitespace if token.value.contains("\n\n") => NonCodeValue::NewLine,
|
||||
TokenType::LineComment => NonCodeValue::BlockComment {
|
||||
value: token.value.trim_start_matches("//").trim().to_owned(),
|
||||
style: CommentStyle::Line,
|
||||
},
|
||||
TokenType::BlockComment => NonCodeValue::BlockComment {
|
||||
style: CommentStyle::Block,
|
||||
value: token
|
||||
.value
|
||||
.trim_start_matches("/*")
|
||||
.trim_end_matches("*/")
|
||||
.trim()
|
||||
.to_owned(),
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
Some(Node::new(
|
||||
NonCodeNode { value, digest: None },
|
||||
token.start,
|
||||
token.end,
|
||||
token.module_id,
|
||||
))
|
||||
}
|
||||
})
|
||||
.context(expected("Non-code token (comments or whitespace)")),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
@ -1191,6 +1239,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
x @ NonCodeValue::InlineComment { .. } => x,
|
||||
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
||||
x @ NonCodeValue::NewLine => x,
|
||||
x @ NonCodeValue::Annotation { .. } => x,
|
||||
};
|
||||
Node::new(
|
||||
NonCodeNode { value, ..nc.inner },
|
||||
@ -1211,6 +1260,7 @@ fn noncode_just_after_code(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
x @ NonCodeValue::InlineComment { .. } => x,
|
||||
x @ NonCodeValue::NewLineBlockComment { .. } => x,
|
||||
x @ NonCodeValue::NewLine => x,
|
||||
x @ NonCodeValue::Annotation { .. } => x,
|
||||
};
|
||||
Node::new(NonCodeNode { value, ..nc.inner }, nc.start, nc.end, nc.module_id)
|
||||
}
|
||||
@ -1250,7 +1300,7 @@ fn body_items_within_function(i: &mut TokenSlice) -> PResult<WithinFunction> {
|
||||
(import_stmt.map(BodyItem::ImportStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
||||
Token { ref value, .. } if value == "return" =>
|
||||
(return_stmt.map(BodyItem::ReturnStatement), opt(noncode_just_after_code)).map(WithinFunction::BodyItem),
|
||||
token if !token.is_code_token() => {
|
||||
token if !token.is_code_token() || token.token_type == TokenType::At => {
|
||||
non_code_node.map(WithinFunction::NonCode)
|
||||
},
|
||||
_ =>
|
||||
@ -2267,9 +2317,8 @@ fn question_mark(i: &mut TokenSlice) -> PResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn at_sign(i: &mut TokenSlice) -> PResult<()> {
|
||||
TokenType::At.parse_from(i)?;
|
||||
Ok(())
|
||||
fn at_sign(i: &mut TokenSlice) -> PResult<Token> {
|
||||
TokenType::At.parse_from(i)
|
||||
}
|
||||
|
||||
fn fun(i: &mut TokenSlice) -> PResult<Token> {
|
||||
@ -3626,6 +3675,22 @@ height = [obj["a"] -1, 0]"#;
|
||||
crate::parsing::top_level_parse("foo(42, fn(x) { return x + 1 })").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_annotation_fn() {
|
||||
crate::parsing::top_level_parse(
|
||||
r#"fn foo() {
|
||||
@annotated
|
||||
return 1
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_annotation_settings() {
|
||||
crate::parsing::top_level_parse("@settings(units = mm)").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anon_fn_no_fn() {
|
||||
assert_err_contains("foo(42, (x) { return x + 1 })", "Anonymous function requires `fn`");
|
||||
|
@ -56,14 +56,13 @@ impl NumericSuffix {
|
||||
impl FromStr for NumericSuffix {
|
||||
type Err = CompilationError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"_" => Ok(NumericSuffix::Count),
|
||||
"mm" => Ok(NumericSuffix::Mm),
|
||||
"cm" => Ok(NumericSuffix::Cm),
|
||||
"m" => Ok(NumericSuffix::M),
|
||||
"inch" => Ok(NumericSuffix::Inch),
|
||||
"in" => Ok(NumericSuffix::Inch),
|
||||
"inch" | "in" => Ok(NumericSuffix::Inch),
|
||||
"ft" => Ok(NumericSuffix::Ft),
|
||||
"yd" => Ok(NumericSuffix::Yd),
|
||||
"deg" => Ok(NumericSuffix::Deg),
|
||||
|
@ -3,10 +3,10 @@ use std::fmt::Write;
|
||||
use crate::parsing::{
|
||||
ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
CallExpressionKw, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression, IfExpression,
|
||||
ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue,
|
||||
MemberExpression, MemberObject, Node, NonCodeValue, ObjectExpression, Parameter, PipeExpression, Program,
|
||||
TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FnArgType, FormatOptions, FunctionExpression,
|
||||
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
|
||||
PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
},
|
||||
PIPE_OPERATOR,
|
||||
};
|
||||
@ -55,7 +55,7 @@ impl Program {
|
||||
self.non_code_meta
|
||||
.start_nodes
|
||||
.iter()
|
||||
.map(|start| start.format(&indentation))
|
||||
.map(|start| start.recast(options, indentation_level))
|
||||
.collect()
|
||||
}
|
||||
} else {
|
||||
@ -76,7 +76,7 @@ impl Program {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, custom_white_space_or_comment)| {
|
||||
let formatted = custom_white_space_or_comment.format(&indentation);
|
||||
let formatted = custom_white_space_or_comment.recast(options, indentation_level);
|
||||
if i == 0 && !formatted.trim().is_empty() {
|
||||
if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
|
||||
format!("\n{}", formatted)
|
||||
@ -115,7 +115,75 @@ impl NonCodeValue {
|
||||
fn should_cause_array_newline(&self) -> bool {
|
||||
match self {
|
||||
Self::InlineComment { .. } => false,
|
||||
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine => true,
|
||||
Self::BlockComment { .. } | Self::NewLineBlockComment { .. } | Self::NewLine | Self::Annotation { .. } => {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<NonCodeNode> {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
match &self.value {
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Line,
|
||||
} => format!(" // {}\n", value),
|
||||
NonCodeValue::InlineComment {
|
||||
value,
|
||||
style: CommentStyle::Block,
|
||||
} => format!(" /* {} */", value),
|
||||
NonCodeValue::BlockComment { value, style } => match style {
|
||||
CommentStyle::Block => format!("{}/* {} */", indentation, value),
|
||||
CommentStyle::Line => {
|
||||
if value.trim().is_empty() {
|
||||
format!("{}//\n", indentation)
|
||||
} else {
|
||||
format!("{}// {}\n", indentation, value.trim())
|
||||
}
|
||||
}
|
||||
},
|
||||
NonCodeValue::NewLineBlockComment { value, style } => {
|
||||
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
||||
match style {
|
||||
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
||||
CommentStyle::Line => {
|
||||
if value.trim().is_empty() {
|
||||
format!("{}{}//\n", add_start_new_line, indentation)
|
||||
} else {
|
||||
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||
NonCodeValue::Annotation { name, properties } => {
|
||||
let mut result = "@".to_owned();
|
||||
result.push_str(&name.name);
|
||||
if let Some(properties) = properties {
|
||||
result.push('(');
|
||||
result.push_str(
|
||||
&properties
|
||||
.iter()
|
||||
.map(|prop| {
|
||||
format!(
|
||||
"{} = {}",
|
||||
prop.key.name,
|
||||
prop.value
|
||||
.recast(options, indentation_level + 1, ExprContext::Other)
|
||||
.trim()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
);
|
||||
result.push(')');
|
||||
result.push('\n');
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -343,7 +411,7 @@ impl ArrayExpression {
|
||||
.iter()
|
||||
.map(|nc| {
|
||||
found_line_comment |= nc.value.should_cause_array_newline();
|
||||
nc.format("")
|
||||
nc.recast(options, 0)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
@ -481,7 +549,7 @@ impl ObjectExpression {
|
||||
let format_items: Vec<_> = (0..num_items)
|
||||
.flat_map(|i| {
|
||||
if let Some(noncode) = self.non_code_meta.non_code_nodes.get(&i) {
|
||||
noncode.iter().map(|nc| nc.format("")).collect::<Vec<_>>()
|
||||
noncode.iter().map(|nc| nc.recast(options, 0)).collect::<Vec<_>>()
|
||||
} else {
|
||||
let prop = props.next().unwrap();
|
||||
// Use a comma unless it's the last item
|
||||
@ -617,10 +685,13 @@ impl Node<PipeExpression> {
|
||||
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
||||
for val in non_code_meta_value {
|
||||
let formatted = if val.end == self.end {
|
||||
let indentation = options.get_indentation(indentation_level);
|
||||
val.format(&indentation).trim_end_matches('\n').to_string()
|
||||
val.recast(options, indentation_level)
|
||||
.trim_end_matches('\n')
|
||||
.to_string()
|
||||
} else {
|
||||
val.format(&indentation).trim_end_matches('\n').to_string()
|
||||
val.recast(options, indentation_level + 1)
|
||||
.trim_end_matches('\n')
|
||||
.to_string()
|
||||
};
|
||||
if let NonCodeValue::BlockComment { .. } = val.value {
|
||||
s += "\n";
|
||||
@ -1252,7 +1323,8 @@ part001 = startSketchOn('XY')
|
||||
|
||||
#[test]
|
||||
fn test_recast_large_file() {
|
||||
let some_program_string = r#"// define nts
|
||||
let some_program_string = r#"@settings(units=mm)
|
||||
// define nts
|
||||
radius = 6.0
|
||||
width = 144.0
|
||||
length = 83.0
|
||||
@ -1376,7 +1448,8 @@ tabs_l = startSketchOn({
|
||||
// Its VERY important this comes back with zero new lines.
|
||||
assert_eq!(
|
||||
recasted,
|
||||
r#"// define nts
|
||||
r#"@settings(units = mm)
|
||||
// define nts
|
||||
radius = 6.0
|
||||
width = 144.0
|
||||
length = 83.0
|
||||
|
Reference in New Issue
Block a user