diff --git a/src/lang/modifyAst.ts b/src/lang/modifyAst.ts index 0e8a1c705..65794afd9 100644 --- a/src/lang/modifyAst.ts +++ b/src/lang/modifyAst.ts @@ -964,6 +964,7 @@ export function createCallExpressionStdLibKw( end: 0, moduleId: 0, outerAttrs: [], + nonCodeMeta: nonCodeMetaEmpty(), callee: { type: 'Identifier', start: 0, @@ -1585,7 +1586,7 @@ export async function deleteFromSelection( return new Error('Selection not recognised, could not delete') } -const nonCodeMetaEmpty = () => { +export const nonCodeMetaEmpty = () => { return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 } } diff --git a/src/lang/std/sketch.ts b/src/lang/std/sketch.ts index ad0123c53..6b1a7d5b5 100644 --- a/src/lang/std/sketch.ts +++ b/src/lang/std/sketch.ts @@ -30,6 +30,7 @@ import { toolTips, ToolTip } from 'lang/langHelpers' import { createPipeExpression, mutateKwArg, + nonCodeMetaEmpty, splitPathAtPipeExpression, } from '../modifyAst' @@ -2509,6 +2510,7 @@ function addTagKw(): addTagFn { unlabeled: callExpr.node.arguments.length ? callExpr.node.arguments[0] : null, + nonCodeMeta: nonCodeMetaEmpty(), arguments: [], } const tagArg = findKwArg(ARG_TAG, primaryCallExp) diff --git a/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs b/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs index b857461df..8c3df8e14 100644 --- a/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs +++ b/src/wasm-lib/kcl/src/parsing/ast/types/mod.rs @@ -1581,7 +1581,7 @@ pub struct CallExpression { #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[ts(export)] -#[serde(tag = "type")] +#[serde(rename_all = "camelCase", tag = "type")] pub struct CallExpressionKw { pub callee: Node, pub unlabeled: Option, @@ -1591,6 +1591,9 @@ pub struct CallExpressionKw { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub digest: Option, + + #[serde(default, skip_serializing_if = "NonCodeMeta::is_empty")] + pub non_code_meta: NonCodeMeta, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] @@ -1714,6 +1717,7 @@ impl CallExpressionKw { unlabeled, arguments, digest: None, + non_code_meta: Default::default(), })) } diff --git a/src/wasm-lib/kcl/src/parsing/parser.rs b/src/wasm-lib/kcl/src/parsing/parser.rs index fea2d26e1..aa403f986 100644 --- a/src/wasm-lib/kcl/src/parsing/parser.rs +++ b/src/wasm-lib/kcl/src/parsing/parser.rs @@ -882,6 +882,17 @@ fn property_separator(i: &mut TokenSlice) -> PResult<()> { .parse_next(i) } +/// Match something that separates the labeled arguments of a fn call. +fn labeled_arg_separator(i: &mut TokenSlice) -> PResult<()> { + alt(( + // Normally you need a comma. + comma_sep, + // But, if the argument list is ending, no need for a comma. + peek(preceded(opt(whitespace), close_paren)).void(), + )) + .parse_next(i) +} + /// Parse a KCL object value. pub(crate) fn object(i: &mut TokenSlice) -> PResult> { let open = open_brace(i)?; @@ -2496,14 +2507,6 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult { .parse_next(i) } -/// Arguments are passed into a function, -/// preceded by the name of the parameter (the label). -fn labeled_arguments(i: &mut TokenSlice) -> PResult> { - separated(0.., labeled_argument, comma_sep) - .context(expected("function arguments")) - .parse_next(i) -} - /// A type of a function argument. /// This can be: /// - a primitive type, e.g. 'number' or 'string' or 'bool' @@ -2783,7 +2786,28 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult> { ignore_whitespace(i); let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?; - let args = labeled_arguments(i)?; + let args: Vec<_> = repeat( + 0.., + alt(( + terminated(non_code_node.map(NonCodeOr::NonCode), whitespace), + terminated(labeled_argument, labeled_arg_separator).map(NonCodeOr::Code), + )), + ) + .parse_next(i)?; + let (args, non_code_nodes): (Vec<_>, BTreeMap) = args.into_iter().enumerate().fold( + (Vec::new(), BTreeMap::new()), + |(mut args, mut non_code_nodes), (i, e)| { + match e { + NonCodeOr::NonCode(x) => { + non_code_nodes.insert(i, vec![x]); + } + NonCodeOr::Code(x) => { + args.push(x); + } + } + (args, non_code_nodes) + }, + ); if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) { let just_args: Vec<_> = args.iter().collect(); typecheck_all_kw(std_fn, &just_args)?; @@ -2792,6 +2816,10 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult> { opt(comma_sep).parse_next(i)?; let end = close_paren.parse_next(i)?.end; + let non_code_meta = NonCodeMeta { + non_code_nodes, + ..Default::default() + }; Ok(Node { start: fn_name.start, end, @@ -2801,6 +2829,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult> { unlabeled: initial_unlabeled_arg, arguments: args, digest: None, + non_code_meta, }, outer_attrs: Vec::new(), }) @@ -4390,14 +4419,6 @@ let myBox = box([0,0], -3, -16, -10) crate::parsing::top_level_parse(some_program_string).unwrap(); } - #[test] - fn arg_labels() { - let input = r#"length: 3"#; - let module_id = ModuleId::default(); - let tokens = crate::parsing::token::lex(input, module_id).unwrap(); - super::labeled_arguments(&mut tokens.as_slice()).unwrap(); - } - #[test] fn kw_fn() { for input in ["val = foo(x, y = z)", "val = foo(y = z)"] { @@ -4879,6 +4900,22 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2 r#"fn foo(x?: number = 2) { return 1 }"# ); snapshot_test!(kw_function_call_in_pipe, r#"val = 1 |> f(arg = x)"#); + snapshot_test!( + kw_function_call_multiline, + r#"val = f( + arg = x, + foo = x, + bar = x, + )"# + ); + snapshot_test!( + kw_function_call_multiline_with_comments, + r#"val = f( + arg = x, + // foo = x, + bar = x, + )"# + ); } #[allow(unused)] diff --git a/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline.snap b/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline.snap new file mode 100644 index 000000000..a96454ac4 --- /dev/null +++ b/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline.snap @@ -0,0 +1,86 @@ +--- +source: kcl/src/parsing/parser.rs +expression: actual +snapshot_kind: text +--- +{ + "body": [ + { + "declaration": { + "end": 87, + "id": { + "end": 3, + "name": "val", + "start": 0, + "type": "Identifier" + }, + "init": { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "type": "Identifier", + "name": "arg" + }, + "arg": { + "end": 29, + "name": "x", + "start": 28, + "type": "Identifier", + "type": "Identifier" + } + }, + { + "type": "LabeledArg", + "label": { + "type": "Identifier", + "name": "foo" + }, + "arg": { + "end": 51, + "name": "x", + "start": 50, + "type": "Identifier", + "type": "Identifier" + } + }, + { + "type": "LabeledArg", + "label": { + "type": "Identifier", + "name": "bar" + }, + "arg": { + "end": 73, + "name": "x", + "start": 72, + "type": "Identifier", + "type": "Identifier" + } + } + ], + "callee": { + "end": 7, + "name": "f", + "start": 6, + "type": "Identifier" + }, + "end": 87, + "start": 6, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": null + }, + "start": 0, + "type": "VariableDeclarator" + }, + "end": 87, + "kind": "const", + "start": 0, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + } + ], + "end": 87, + "start": 0 +} diff --git a/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline_with_comments.snap b/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline_with_comments.snap new file mode 100644 index 000000000..c2cf930b3 --- /dev/null +++ b/src/wasm-lib/kcl/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__kw_function_call_multiline_with_comments.snap @@ -0,0 +1,89 @@ +--- +source: kcl/src/parsing/parser.rs +expression: actual +snapshot_kind: text +--- +{ + "body": [ + { + "declaration": { + "end": 90, + "id": { + "end": 3, + "name": "val", + "start": 0, + "type": "Identifier" + }, + "init": { + "arguments": [ + { + "type": "LabeledArg", + "label": { + "type": "Identifier", + "name": "arg" + }, + "arg": { + "end": 29, + "name": "x", + "start": 28, + "type": "Identifier", + "type": "Identifier" + } + }, + { + "type": "LabeledArg", + "label": { + "type": "Identifier", + "name": "bar" + }, + "arg": { + "end": 76, + "name": "x", + "start": 75, + "type": "Identifier", + "type": "Identifier" + } + } + ], + "callee": { + "end": 7, + "name": "f", + "start": 6, + "type": "Identifier" + }, + "end": 90, + "nonCodeMeta": { + "nonCodeNodes": { + "1": [ + { + "end": 55, + "start": 44, + "type": "NonCodeNode", + "value": { + "type": "blockComment", + "value": "foo = x,", + "style": "line" + } + } + ] + }, + "startNodes": [] + }, + "start": 6, + "type": "CallExpressionKw", + "type": "CallExpressionKw", + "unlabeled": null + }, + "start": 0, + "type": "VariableDeclarator" + }, + "end": 90, + "kind": "const", + "start": 0, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + } + ], + "end": 90, + "start": 0 +}