Allow people to set format options (#389)

* better naming

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* up[dates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump version

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* whitespace

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2023-09-06 10:36:03 -07:00
committed by GitHub
parent 1181f33e9d
commit 84d08bad16
10 changed files with 334 additions and 188 deletions

View File

@ -1564,7 +1564,7 @@ const key = 'c'`
start: code.indexOf('\n// this is a comment'), start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'), end: code.indexOf('const key'),
value: { value: {
type: 'block', type: 'blockComment',
value: 'this is a comment', value: 'this is a comment',
}, },
} }
@ -1602,7 +1602,7 @@ const key = 'c'`
start: 106, start: 106,
end: 166, end: 166,
value: { value: {
type: 'block', type: 'blockComment',
value: 'this is\n a comment\n spanning a few lines', value: 'this is\n a comment\n spanning a few lines',
}, },
}) })
@ -1625,7 +1625,7 @@ const key = 'c'`
start: 125, start: 125,
end: 141, end: 141,
value: { value: {
type: 'block', type: 'blockComment',
value: 'a comment', value: 'a comment',
}, },
}) })

View File

@ -318,9 +318,9 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %) |> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|> line([2.77, -1.24], %) |> line([2.77, -1.24], %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle: 201, angle: 201,
offset: -1.35, offset: -1.35,
intersectTag: 'seg01' intersectTag: 'seg01'
}, %) }, %)
|> line([-0.42, -1.72], %) |> line([-0.42, -1.72], %)
show(part001)` show(part001)`

View File

@ -59,19 +59,19 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`, ` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`, ` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
` |> angledLine({`, ` |> angledLine({`,
` angle: 157,`, ` angle: 157,`,
` length: 1.69,`, ` length: 1.69,`,
` tag: 'abc3'`, ` tag: 'abc3'`,
` }, %)`, ` }, %)`,
` |> angledLineOfXLength({`, ` |> angledLineOfXLength({`,
` angle: 217,`, ` angle: 217,`,
` length: 0.86,`, ` length: 0.86,`,
` tag: 'abc4'`, ` tag: 'abc4'`,
` }, %)`, ` }, %)`,
` |> angledLineOfYLength({`, ` |> angledLineOfYLength({`,
` angle: 104,`, ` angle: 104,`,
` length: 1.58,`, ` length: 1.58,`,
` tag: 'abc5'`, ` tag: 'abc5'`,
` }, %)`, ` }, %)`,
` |> angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)`, ` |> angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)`,
` |> angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)`, ` |> angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)`,
@ -144,9 +144,9 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
inputCode: bigExample, inputCode: bigExample,
callToSwap: [ callToSwap: [
`angledLine({`, `angledLine({`,
` angle: 157,`, ` angle: 157,`,
` length: 1.69,`, ` length: 1.69,`,
` tag: 'abc3'`, ` tag: 'abc3'`,
` }, %)`, ` }, %)`,
].join('\n'), ].join('\n'),
constraintType: 'horizontal', constraintType: 'horizontal',
@ -172,9 +172,9 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
inputCode: bigExample, inputCode: bigExample,
callToSwap: [ callToSwap: [
`angledLineOfXLength({`, `angledLineOfXLength({`,
` angle: 217,`, ` angle: 217,`,
` length: 0.86,`, ` length: 0.86,`,
` tag: 'abc4'`, ` tag: 'abc4'`,
` }, %)`, ` }, %)`,
].join('\n'), ].join('\n'),
constraintType: 'horizontal', constraintType: 'horizontal',
@ -201,9 +201,9 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
inputCode: bigExample, inputCode: bigExample,
callToSwap: [ callToSwap: [
`angledLineOfYLength({`, `angledLineOfYLength({`,
` angle: 104,`, ` angle: 104,`,
` length: 1.58,`, ` length: 1.58,`,
` tag: 'abc5'`, ` tag: 'abc5'`,
` }, %)`, ` }, %)`,
].join('\n'), ].join('\n'),
constraintType: 'vertical', constraintType: 'vertical',

View File

@ -133,64 +133,64 @@ const myAng2 = 134
const part001 = startSketchAt([0, 0]) const part001 = startSketchAt([0, 0])
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag |> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> angledLineToX([ |> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %), -angleToMatchLengthX('seg01', myVar, %),
myVar myVar
], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper ], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> angledLineToY([ |> angledLineToY([
-angleToMatchLengthY('seg01', myVar, %), -angleToMatchLengthY('seg01', myVar, %),
myVar myVar
], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper ], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|> angledLine([45, segLen('seg01', %)], %) // ln-lineTo-free should become angledLine |> angledLine([45, segLen('seg01', %)], %) // ln-lineTo-free should become angledLine
|> angledLine([45, segLen('seg01', %)], %) // ln-angledLineToX-free should become angledLine |> angledLine([45, segLen('seg01', %)], %) // ln-angledLineToX-free should become angledLine
|> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineToX-angle should become angledLine |> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineToX-angle should become angledLine
|> angledLineToX([ |> angledLineToX([
angleToMatchLengthX('seg01', myVar2, %), angleToMatchLengthX('seg01', myVar2, %),
myVar2 myVar2
], %) // ln-angledLineToX-xAbsolute should use angleToMatchLengthX to get angle ], %) // ln-angledLineToX-xAbsolute should use angleToMatchLengthX to get angle
|> angledLine([-45, segLen('seg01', %)], %) // ln-angledLineToY-free should become angledLine |> angledLine([-45, segLen('seg01', %)], %) // ln-angledLineToY-free should become angledLine
|> angledLine([myAng2, segLen('seg01', %)], %) // ln-angledLineToY-angle should become angledLine |> angledLine([myAng2, segLen('seg01', %)], %) // ln-angledLineToY-angle should become angledLine
|> angledLineToY([ |> angledLineToY([
angleToMatchLengthY('seg01', myVar3, %), angleToMatchLengthY('seg01', myVar3, %),
myVar3 myVar3
], %) // ln-angledLineToY-yAbsolute should use angleToMatchLengthY to get angle ], %) // ln-angledLineToY-yAbsolute should use angleToMatchLengthY to get angle
|> line([ |> line([
min(segLen('seg01', %), myVar), min(segLen('seg01', %), myVar),
legLen(segLen('seg01', %), myVar) legLen(segLen('seg01', %), myVar)
], %) // ln-should use legLen for y ], %) // ln-should use legLen for y
|> line([ |> line([
min(segLen('seg01', %), myVar), min(segLen('seg01', %), myVar),
-legLen(segLen('seg01', %), myVar) -legLen(segLen('seg01', %), myVar)
], %) // ln-legLen but negative ], %) // ln-legLen but negative
|> angledLine([-112, segLen('seg01', %)], %) // ln-should become angledLine |> angledLine([-112, segLen('seg01', %)], %) // ln-should become angledLine
|> angledLine([myVar, segLen('seg01', %)], %) // ln-use segLen for secound arg |> angledLine([myVar, segLen('seg01', %)], %) // ln-use segLen for secound arg
|> angledLine([45, segLen('seg01', %)], %) // ln-segLen again |> angledLine([45, segLen('seg01', %)], %) // ln-segLen again
|> angledLine([54, segLen('seg01', %)], %) // ln-should be transformed to angledLine |> angledLine([54, segLen('seg01', %)], %) // ln-should be transformed to angledLine
|> angledLineOfXLength([ |> angledLineOfXLength([
legAngX(segLen('seg01', %), myVar), legAngX(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-should use legAngX to calculate angle ], %) // ln-should use legAngX to calculate angle
|> angledLineOfXLength([ |> angledLineOfXLength([
180 + legAngX(segLen('seg01', %), myVar), 180 + legAngX(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-same as above but should have + 180 to match original quadrant ], %) // ln-same as above but should have + 180 to match original quadrant
|> line([ |> line([
legLen(segLen('seg01', %), myVar), legLen(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-legLen again but yRelative ], %) // ln-legLen again but yRelative
|> line([ |> line([
-legLen(segLen('seg01', %), myVar), -legLen(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-negative legLen yRelative ], %) // ln-negative legLen yRelative
|> angledLine([58, segLen('seg01', %)], %) // ln-angledLineOfYLength-free should become angledLine |> angledLine([58, segLen('seg01', %)], %) // ln-angledLineOfYLength-free should become angledLine
|> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineOfYLength-angle should become angledLine |> angledLine([myAng, segLen('seg01', %)], %) // ln-angledLineOfYLength-angle should become angledLine
|> angledLineOfXLength([ |> angledLineOfXLength([
legAngY(segLen('seg01', %), myVar), legAngY(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-angledLineOfYLength-yRelative use legAngY ], %) // ln-angledLineOfYLength-yRelative use legAngY
|> angledLineOfXLength([ |> angledLineOfXLength([
270 + legAngY(segLen('seg01', %), myVar), 270 + legAngY(segLen('seg01', %), myVar),
min(segLen('seg01', %), myVar) min(segLen('seg01', %), myVar)
], %) // ln-angledLineOfYLength-yRelative with angle > 90 use binExp ], %) // ln-angledLineOfYLength-yRelative with angle > 90 use binExp
|> xLine(segLen('seg01', %), %) // ln-xLine-free should sub in segLen |> xLine(segLen('seg01', %), %) // ln-xLine-free should sub in segLen
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen |> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine |> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
@ -406,9 +406,9 @@ show(part001)`
'setVertDistance' 'setVertDistance'
) )
expect(expectedCode).toContain(`|> lineTo([ expect(expectedCode).toContain(`|> lineTo([
lastSegX(%) + myVar, lastSegX(%) + myVar,
segEndY('seg01', %) + 2.93 segEndY('seg01', %) + 2.93
], %) // xRelative`) ], %) // xRelative`)
}) })
it('testing for yRelative to horizontal distance', async () => { it('testing for yRelative to horizontal distance', async () => {
const expectedCode = await helperThing( const expectedCode = await helperThing(
@ -417,9 +417,9 @@ show(part001)`
'setHorzDistance' 'setHorzDistance'
) )
expect(expectedCode).toContain(`|> lineTo([ expect(expectedCode).toContain(`|> lineTo([
segEndX('seg01', %) + 2.6, segEndX('seg01', %) + 2.6,
lastSegY(%) + myVar lastSegY(%) + myVar
], %) // yRelative`) ], %) // yRelative`)
}) })
}) })
}) })

View File

@ -1094,7 +1094,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.20" version = "0.1.21"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language" description = "KittyCAD Language"
version = "0.1.20" version = "0.1.21"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"

View File

@ -27,12 +27,16 @@ pub struct Program {
} }
impl Program { impl Program {
pub fn recast(&self, indentation: &str, is_with_block: bool) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
self.body let indentation = options.get_indentation(indentation_level);
let result = self
.body
.iter() .iter()
.map(|statement| match statement.clone() { .map(|statement| match statement.clone() {
BodyItem::ExpressionStatement(expression_statement) => { BodyItem::ExpressionStatement(expression_statement) => {
expression_statement.expression.recast(indentation, false) expression_statement
.expression
.recast(options, indentation_level, false)
} }
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
.declarations .declarations
@ -43,56 +47,44 @@ impl Program {
indentation, indentation,
variable_declaration.kind, variable_declaration.kind,
declaration.id.name, declaration.id.name,
declaration.init.recast("", false) declaration.init.recast(options, 0, false)
) )
}) })
.collect::<String>(), .collect::<String>(),
BodyItem::ReturnStatement(return_statement) => { BodyItem::ReturnStatement(return_statement) => {
format!("{}return {}", indentation, return_statement.argument.recast("", false)) format!(
"{}return {}",
indentation,
return_statement.argument.recast(options, 0, false)
)
} }
}) })
.enumerate() .enumerate()
.map(|(index, recast_str)| { .map(|(index, recast_str)| {
let is_legit_custom_whitespace_or_comment = |s: String| s != " " && s != "\n" && s != " " && s != "\t"; let start_string = if index == 0 {
// We need to indent.
// determine the value of startString
let last_white_space_or_comment = if index > 0 {
let tmp = if let Some(non_code_node) = self.non_code_meta.none_code_nodes.get(&(index - 1)) {
non_code_node.format(indentation)
} else {
" ".to_string()
};
tmp
} else {
" ".to_string()
};
// indentation of this line will be covered by the previous if we're using a custom whitespace or comment
let mut start_string = if is_legit_custom_whitespace_or_comment(last_white_space_or_comment) {
String::new()
} else {
indentation.to_owned()
};
if index == 0 {
if let Some(start) = self.non_code_meta.start.clone() { if let Some(start) = self.non_code_meta.start.clone() {
start_string = start.format(indentation); start.format(&indentation)
} else { } else {
start_string = indentation.to_owned(); indentation.to_string()
} }
} } else {
// Do nothing, we already applied the indentation elsewhere.
String::new()
};
// determine the value of endString // determine the value of the end string
let maybe_line_break: String = if index == self.body.len() - 1 && !is_with_block { // basically if we are inside a nested function we want to end with a new line
let maybe_line_break: String = if index == self.body.len() - 1 && indentation_level == 0 {
String::new() String::new()
} else { } else {
"\n".to_string() "\n".to_string()
}; };
let mut custom_white_space_or_comment = match self.non_code_meta.none_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(indentation), let custom_white_space_or_comment = match self.non_code_meta.none_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation),
None => String::new(), None => String::new(),
}; };
if !is_legit_custom_whitespace_or_comment(custom_white_space_or_comment.clone()) {
custom_white_space_or_comment = String::new();
}
let end_string = if custom_white_space_or_comment.is_empty() { let end_string = if custom_white_space_or_comment.is_empty() {
maybe_line_break maybe_line_break
} else { } else {
@ -103,7 +95,14 @@ impl Program {
}) })
.collect::<String>() .collect::<String>()
.trim() .trim()
.to_string() .to_string();
// Insert a final new line if the user wants it.
if options.insert_final_newline {
format!("{}\n", result)
} else {
result
}
} }
/// Returns the body item that includes the given character position. /// Returns the body item that includes the given character position.
@ -249,19 +248,18 @@ pub enum Value {
} }
impl Value { impl Value {
fn recast(&self, indentation: &str, is_in_pipe_expression: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let indentation = indentation.to_string() + if is_in_pipe_expression { " " } else { "" };
match &self { match &self {
Value::BinaryExpression(bin_exp) => bin_exp.recast(), Value::BinaryExpression(bin_exp) => bin_exp.recast(options),
Value::ArrayExpression(array_exp) => array_exp.recast(&indentation, is_in_pipe_expression), Value::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, is_in_pipe),
Value::ObjectExpression(ref obj_exp) => obj_exp.recast(&indentation, is_in_pipe_expression), Value::ObjectExpression(ref obj_exp) => obj_exp.recast(options, indentation_level, is_in_pipe),
Value::MemberExpression(mem_exp) => mem_exp.recast(), Value::MemberExpression(mem_exp) => mem_exp.recast(),
Value::Literal(literal) => literal.recast(), Value::Literal(literal) => literal.recast(),
Value::FunctionExpression(func_exp) => func_exp.recast(&indentation), Value::FunctionExpression(func_exp) => func_exp.recast(options, indentation_level),
Value::CallExpression(call_exp) => call_exp.recast(&indentation, is_in_pipe_expression), Value::CallExpression(call_exp) => call_exp.recast(options, indentation_level, is_in_pipe),
Value::Identifier(ident) => ident.name.to_string(), Value::Identifier(ident) => ident.name.to_string(),
Value::PipeExpression(pipe_exp) => pipe_exp.recast(&indentation), Value::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
Value::UnaryExpression(unary_exp) => unary_exp.recast(), Value::UnaryExpression(unary_exp) => unary_exp.recast(options),
Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(), Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
} }
} }
@ -355,13 +353,13 @@ impl From<&BinaryPart> for crate::executor::SourceRange {
} }
impl BinaryPart { impl BinaryPart {
fn recast(&self, indentation: &str) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
match &self { match &self {
BinaryPart::Literal(literal) => literal.recast(), BinaryPart::Literal(literal) => literal.recast(),
BinaryPart::Identifier(identifier) => identifier.name.to_string(), BinaryPart::Identifier(identifier) => identifier.name.to_string(),
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(), BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
BinaryPart::CallExpression(call_expression) => call_expression.recast(indentation, false), BinaryPart::CallExpression(call_expression) => call_expression.recast(options, indentation_level, false),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(), BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
} }
} }
@ -436,17 +434,17 @@ pub struct NoneCodeNode {
impl NoneCodeNode { impl NoneCodeNode {
pub fn value(&self) -> String { pub fn value(&self) -> String {
match &self.value { match &self.value {
NoneCodeValue::Inline { value } => value.clone(), NoneCodeValue::InlineComment { value } => value.clone(),
NoneCodeValue::Block { value } => value.clone(), NoneCodeValue::BlockComment { value } => value.clone(),
NoneCodeValue::NewLineBlock { value } => value.clone(), NoneCodeValue::NewLineBlockComment { value } => value.clone(),
NoneCodeValue::NewLine => "\n\n".to_string(), NoneCodeValue::NewLine => "\n\n".to_string(),
} }
} }
pub fn format(&self, indentation: &str) -> String { pub fn format(&self, indentation: &str) -> String {
match &self.value { match &self.value {
NoneCodeValue::Inline { value } => format!(" // {}\n", value), NoneCodeValue::InlineComment { value } => format!(" // {}\n", value),
NoneCodeValue::Block { value } => { NoneCodeValue::BlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n" }; let add_start_new_line = if self.start == 0 { "" } else { "\n" };
if value.contains('\n') { if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value) format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -454,7 +452,7 @@ impl NoneCodeNode {
format!("{}{}// {}\n", add_start_new_line, indentation, value) format!("{}{}// {}\n", add_start_new_line, indentation, value)
} }
} }
NoneCodeValue::NewLineBlock { value } => { NoneCodeValue::NewLineBlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" }; let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
if value.contains('\n') { if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value) format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -471,9 +469,29 @@ impl NoneCodeNode {
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub enum NoneCodeValue { pub enum NoneCodeValue {
Inline { value: String }, /// An inline comment.
Block { value: String }, /// An example of this is the following: `1 + 1 // This is an inline comment`.
NewLineBlock { value: String }, InlineComment {
value: String,
},
/// A block comment.
/// An example of this is the following:
/// ```python,no_run
/// /* This is a
/// block comment */
/// 1 + 1
/// ```
/// Now this is important. The block comment is attached to the next line.
/// This is always the case. Also the block comment doesnt have a new line above it.
/// If it did it would be a `NewLineBlockComment`.
BlockComment {
value: String,
},
/// A block comment that has a new line above it.
/// The user explicitly added a new line above the block comment.
NewLineBlockComment {
value: String,
},
// A new line like `\n\n` NOT a new line like `\n`. // A new line like `\n\n` NOT a new line like `\n`.
// This is also not a comment. // This is also not a comment.
NewLine, NewLine,
@ -539,13 +557,13 @@ pub struct CallExpression {
impl_value_meta!(CallExpression); impl_value_meta!(CallExpression);
impl CallExpression { impl CallExpression {
fn recast(&self, indentation: &str, is_in_pipe_expression: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
format!( format!(
"{}({})", "{}({})",
self.callee.name, self.callee.name,
self.arguments self.arguments
.iter() .iter()
.map(|arg| arg.recast(indentation, is_in_pipe_expression)) .map(|arg| arg.recast(options, indentation_level, is_in_pipe))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
) )
@ -923,27 +941,35 @@ pub struct ArrayExpression {
impl_value_meta!(ArrayExpression); impl_value_meta!(ArrayExpression);
impl ArrayExpression { impl ArrayExpression {
fn recast(&self, indentation: &str, is_in_pipe_expression: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!( let flat_recast = format!(
"[{}]", "[{}]",
self.elements self.elements
.iter() .iter()
.map(|el| el.recast("", false)) .map(|el| el.recast(options, 0, false))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
); );
let max_array_length = 40; let max_array_length = 40;
if flat_recast.len() > max_array_length { if flat_recast.len() > max_array_length {
let indentation = indentation.to_string() + " "; let inner_indentation = if is_in_pipe {
options.get_indentation_offset_pipe(indentation_level + 1)
} else {
options.get_indentation(indentation_level + 1)
};
format!( format!(
"[\n{}{}\n{}]", "[\n{}{}\n{}]",
indentation, inner_indentation,
self.elements self.elements
.iter() .iter()
.map(|el| el.recast(&indentation, false)) .map(|el| el.recast(options, indentation_level, false))
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(format!(",\n{}", indentation).as_str()), .join(format!(",\n{}", inner_indentation).as_str()),
if is_in_pipe_expression { " " } else { "" } if is_in_pipe {
options.get_indentation_offset_pipe(indentation_level)
} else {
options.get_indentation(indentation_level)
},
) )
} else { } else {
flat_recast flat_recast
@ -1031,27 +1057,35 @@ pub struct ObjectExpression {
} }
impl ObjectExpression { impl ObjectExpression {
fn recast(&self, indentation: &str, is_in_pipe_expression: bool) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!( let flat_recast = format!(
"{{ {} }}", "{{ {} }}",
self.properties self.properties
.iter() .iter()
.map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast("", false)) }) .map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast(options, 0, false)) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
); );
let max_array_length = 40; let max_array_length = 40;
if flat_recast.len() > max_array_length { if flat_recast.len() > max_array_length {
let indentation = indentation.to_owned() + " "; let inner_indentation = if is_in_pipe {
options.get_indentation_offset_pipe(indentation_level + 1)
} else {
options.get_indentation(indentation_level + 1)
};
format!( format!(
"{{\n{}{}\n{}}}", "{{\n{}{}\n{}}}",
indentation, inner_indentation,
self.properties self.properties
.iter() .iter()
.map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast("", is_in_pipe_expression)) }) .map(|prop| { format!("{}: {}", prop.key.name, prop.value.recast(options, 0, false)) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(format!(",\n{}", indentation).as_str()), .join(format!(",\n{}", inner_indentation).as_str()),
if is_in_pipe_expression { " " } else { "" } if is_in_pipe {
options.get_indentation_offset_pipe(indentation_level)
} else {
options.get_indentation(indentation_level)
},
) )
} else { } else {
flat_recast flat_recast
@ -1370,7 +1404,7 @@ impl BinaryExpression {
self.operator.precedence() self.operator.precedence()
} }
fn recast(&self) -> String { fn recast(&self, options: &FormatOptions) -> String {
let maybe_wrap_it = |a: String, doit: bool| -> String { let maybe_wrap_it = |a: String, doit: bool| -> String {
if doit { if doit {
format!("({})", a) format!("({})", a)
@ -1393,9 +1427,9 @@ impl BinaryExpression {
format!( format!(
"{} {} {}", "{} {} {}",
maybe_wrap_it(self.left.recast(""), should_wrap_left), maybe_wrap_it(self.left.recast(options, 0), should_wrap_left),
self.operator, self.operator,
maybe_wrap_it(self.right.recast(""), should_wrap_right) maybe_wrap_it(self.right.recast(options, 0), should_wrap_right)
) )
} }
@ -1532,8 +1566,8 @@ pub struct UnaryExpression {
impl_value_meta!(UnaryExpression); impl_value_meta!(UnaryExpression);
impl UnaryExpression { impl UnaryExpression {
fn recast(&self) -> String { fn recast(&self, options: &FormatOptions) -> String {
format!("{}{}", &self.operator, self.argument.recast("")) format!("{}{}", &self.operator, self.argument.recast(options, 0))
} }
pub fn get_result( pub fn get_result(
@ -1595,13 +1629,13 @@ pub struct PipeExpression {
impl_value_meta!(PipeExpression); impl_value_meta!(PipeExpression);
impl PipeExpression { impl PipeExpression {
fn recast(&self, indentation: &str) -> String { fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
self.body self.body
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, statement)| { .map(|(index, statement)| {
let indentation = indentation.to_string() + " "; let indentation = options.get_indentation(indentation_level + 1);
let mut s = statement.recast(&indentation, true); let mut s = statement.recast(options, indentation_level + 1, true);
let non_code_meta = self.non_code_meta.clone(); let non_code_meta = self.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index) { if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index) {
s += non_code_meta_value.format(&indentation).trim_end_matches('\n') s += non_code_meta_value.format(&indentation).trim_end_matches('\n')
@ -1706,17 +1740,16 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression); impl_value_meta!(FunctionExpression);
impl FunctionExpression { impl FunctionExpression {
pub fn recast(&self, indentation: &str) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
format!( format!(
"({}) => {{\n{}{}{}\n}}", "({}) => {{\n{}{}\n}}",
self.params self.params
.iter() .iter()
.map(|param| param.name.clone()) .map(|param| param.name.clone())
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "), .join(", "),
indentation, options.get_indentation(indentation_level + 1),
" ", self.body.recast(options, indentation_level + 1)
self.body.recast(" ", true)
) )
} }
@ -1756,6 +1789,58 @@ pub enum Hover {
}, },
} }
/// Format options.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct FormatOptions {
/// Size of a tab in spaces.
pub tab_size: usize,
/// Prefer tabs over spaces.
pub use_tabs: bool,
/// How to handle the final newline in the file.
/// If true, ensure file ends with a newline.
/// If false, ensure file does not end with a newline.
pub insert_final_newline: bool,
}
impl Default for FormatOptions {
fn default() -> Self {
Self::new()
}
}
impl FormatOptions {
/// Define the default format options.
/// We use 2 spaces for indentation.
pub fn new() -> Self {
Self {
tab_size: 2,
use_tabs: false,
insert_final_newline: false,
}
}
/// Get the indentation string for the given level.
pub fn get_indentation(&self, level: usize) -> String {
if self.use_tabs {
"\t".repeat(level)
} else {
" ".repeat(level * self.tab_size)
}
}
/// Get the indentation string for the given level.
/// But offset the pipe operator (and a space) by one level.
pub fn get_indentation_offset_pipe(&self, level: usize) -> String {
if self.use_tabs {
"\t".repeat(level + 1)
} else {
" ".repeat(level * self.tab_size) + " ".repeat(PIPE_OPERATOR.len() + 1).as_str()
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1797,7 +1882,7 @@ show(part001)"#;
let some_program: crate::abstract_syntax_tree_types::Program = let some_program: crate::abstract_syntax_tree_types::Program =
serde_json::from_str(some_program_string).unwrap(); serde_json::from_str(some_program_string).unwrap();
let recasted = some_program.recast("", false); let recasted = some_program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"const part001 = startSketchAt('default') r#"const part001 = startSketchAt('default')
@ -1816,7 +1901,7 @@ show(part001)"#
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
@ -1834,7 +1919,7 @@ show(part001)"#
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
@ -1852,7 +1937,7 @@ show(part001)"#
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
@ -1877,7 +1962,7 @@ show(part001)"#
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"const myFn = () => { r#"const myFn = () => {
@ -1913,7 +1998,7 @@ const mySk1 = startSketchAt([0, 0])
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"// comment at start r#"// comment at start
@ -1940,9 +2025,9 @@ a comment between pipe expression statements */
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %) |> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|> line([2.77, -1.24], %) |> line([2.77, -1.24], %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle: 201, angle: 201,
offset: -1.35, offset: -1.35,
intersectTag: 'seg01' intersectTag: 'seg01'
}, %) }, %)
|> line([-0.42, -1.72], %) |> line([-0.42, -1.72], %)
@ -1951,7 +2036,7 @@ show(part001)"#;
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string); assert_eq!(recasted, some_program_string);
} }
@ -1964,12 +2049,19 @@ const yo = {
anum: 2, anum: 2,
identifier: three, identifier: three,
binExp: 4 + 5 binExp: 4 + 5
}"#; }
const yo = [
1,
" 2,",
"three",
4 + 5,
" hey oooooo really long long long"
]"#;
let tokens = crate::tokeniser::lexer(some_program_string); let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string); assert_eq!(recasted, some_program_string);
} }
@ -1987,7 +2079,7 @@ const things = "things"
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string.trim()); assert_eq!(recasted, some_program_string.trim());
} }
@ -2005,7 +2097,65 @@ const things = "things"
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast("", false); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string.trim()); assert_eq!(recasted, some_program_string.trim());
} }
#[test]
fn test_recast_array_new_line_in_pipe() {
let some_program_string = r#"const myVar = 3
const myVar2 = 5
const myVar3 = 6
const myAng = 40
const myAng2 = 134
const part001 = startSketchAt([0, 0])
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
myVar
], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> angledLineToY([
-angleToMatchLengthY('seg01', myVar, %),
myVar
], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string);
}
#[test]
fn test_recast_array_new_line_in_pipe_custom() {
let some_program_string = r#"const myVar = 3
const myVar2 = 5
const myVar3 = 6
const myAng = 40
const myAng2 = 134
const part001 = startSketchAt([0, 0])
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
myVar
], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> angledLineToY([
-angleToMatchLengthY('seg01', myVar, %),
myVar
], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(
&FormatOptions {
tab_size: 3,
use_tabs: false,
insert_final_newline: true,
},
0,
);
assert_eq!(recasted, some_program_string);
}
} }

View File

@ -336,11 +336,11 @@ impl Parser {
value: if start_end_string.starts_with("\n\n") && is_new_line_comment { value: if start_end_string.starts_with("\n\n") && is_new_line_comment {
// Preserve if they want a whitespace line before the comment. // Preserve if they want a whitespace line before the comment.
// But let's just allow one. // But let's just allow one.
NoneCodeValue::NewLineBlock { value: full_string } NoneCodeValue::NewLineBlockComment { value: full_string }
} else if is_new_line_comment { } else if is_new_line_comment {
NoneCodeValue::Block { value: full_string } NoneCodeValue::BlockComment { value: full_string }
} else { } else {
NoneCodeValue::Inline { value: full_string } NoneCodeValue::InlineComment { value: full_string }
}, },
}; };
Ok((Some(node), end_index - 1)) Ok((Some(node), end_index - 1))
@ -1665,7 +1665,7 @@ const key = 'c'"#,
Some(NoneCodeNode { Some(NoneCodeNode {
start: 38, start: 38,
end: 60, end: 60,
value: NoneCodeValue::Block { value: NoneCodeValue::BlockComment {
value: "this is a comment".to_string(), value: "this is a comment".to_string(),
}, },
}), }),
@ -1687,7 +1687,7 @@ const key = 'c'"#,
Some(NoneCodeNode { Some(NoneCodeNode {
start: 106, start: 106,
end: 166, end: 166,
value: NoneCodeValue::Block { value: NoneCodeValue::BlockComment {
value: "this is\n a comment\n spanning a few lines".to_string(), value: "this is\n a comment\n spanning a few lines".to_string(),
}, },
}), }),

View File

@ -552,19 +552,14 @@ impl LanguageServer for Backend {
return Ok(None); return Ok(None);
}; };
// Now recast it. // Now recast it.
// Make spaces for the tab size. let recast = ast.recast(
/*let mut tab_size = String::new(); &crate::abstract_syntax_tree_types::FormatOptions {
for _ in 0..params.options.tab_size { tab_size: params.options.tab_size as usize,
tab_size.push(' '); insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
}*/ use_tabs: !params.options.insert_spaces,
// TODO: use the tab size. },
let mut recast = ast.recast("", false).trim().to_string(); 0,
if let Some(insert_final_newline) = params.options.insert_final_newline { );
if insert_final_newline {
recast.push('\n');
}
}
let source_range = SourceRange([0, current_code.len() - 1]); let source_range = SourceRange([0, current_code.len() - 1]);
let range = source_range.to_lsp_range(&current_code); let range = source_range.to_lsp_range(&current_code);
Ok(Some(vec![TextEdit { Ok(Some(vec![TextEdit {

View File

@ -76,7 +76,8 @@ pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
let program: kcl_lib::abstract_syntax_tree_types::Program = let program: kcl_lib::abstract_syntax_tree_types::Program =
serde_json::from_str(json_str).map_err(JsError::from)?; serde_json::from_str(json_str).map_err(JsError::from)?;
let result = program.recast("", false); // Use the default options until we integrate into the UI the ability to change them.
let result = program.recast(&Default::default(), 0);
Ok(JsValue::from_serde(&result)?) Ok(JsValue::from_serde(&result)?)
} }