KCL: Allow comments in CallExpressionKw (#5381)

Before, this would not parse:

```
line(
  end = [44.09, 306.95],
  // tag this for later
  tag = $hello
)
```

Now it does.
This commit is contained in:
Adam Chalmers
2025-02-13 17:18:54 -06:00
committed by GitHub
parent e27e9ecc63
commit f37fc357af
6 changed files with 238 additions and 19 deletions

View File

@ -964,6 +964,7 @@ export function createCallExpressionStdLibKw(
end: 0, end: 0,
moduleId: 0, moduleId: 0,
outerAttrs: [], outerAttrs: [],
nonCodeMeta: nonCodeMetaEmpty(),
callee: { callee: {
type: 'Identifier', type: 'Identifier',
start: 0, start: 0,
@ -1585,7 +1586,7 @@ export async function deleteFromSelection(
return new Error('Selection not recognised, could not delete') return new Error('Selection not recognised, could not delete')
} }
const nonCodeMetaEmpty = () => { export const nonCodeMetaEmpty = () => {
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 } return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
} }

View File

@ -30,6 +30,7 @@ import { toolTips, ToolTip } from 'lang/langHelpers'
import { import {
createPipeExpression, createPipeExpression,
mutateKwArg, mutateKwArg,
nonCodeMetaEmpty,
splitPathAtPipeExpression, splitPathAtPipeExpression,
} from '../modifyAst' } from '../modifyAst'
@ -2509,6 +2510,7 @@ function addTagKw(): addTagFn {
unlabeled: callExpr.node.arguments.length unlabeled: callExpr.node.arguments.length
? callExpr.node.arguments[0] ? callExpr.node.arguments[0]
: null, : null,
nonCodeMeta: nonCodeMetaEmpty(),
arguments: [], arguments: [],
} }
const tagArg = findKwArg(ARG_TAG, primaryCallExp) const tagArg = findKwArg(ARG_TAG, primaryCallExp)

View File

@ -1581,7 +1581,7 @@ pub struct CallExpression {
#[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(rename_all = "camelCase", tag = "type")]
pub struct CallExpressionKw { pub struct CallExpressionKw {
pub callee: Node<Identifier>, pub callee: Node<Identifier>,
pub unlabeled: Option<Expr>, pub unlabeled: Option<Expr>,
@ -1591,6 +1591,9 @@ pub struct CallExpressionKw {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)] #[ts(optional)]
pub digest: Option<Digest>, pub digest: Option<Digest>,
#[serde(default, skip_serializing_if = "NonCodeMeta::is_empty")]
pub non_code_meta: NonCodeMeta,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1714,6 +1717,7 @@ impl CallExpressionKw {
unlabeled, unlabeled,
arguments, arguments,
digest: None, digest: None,
non_code_meta: Default::default(),
})) }))
} }

View File

@ -882,6 +882,17 @@ fn property_separator(i: &mut TokenSlice) -> PResult<()> {
.parse_next(i) .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. /// Parse a KCL object value.
pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> { pub(crate) fn object(i: &mut TokenSlice) -> PResult<Node<ObjectExpression>> {
let open = open_brace(i)?; let open = open_brace(i)?;
@ -2496,14 +2507,6 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
.parse_next(i) .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<Vec<LabeledArg>> {
separated(0.., labeled_argument, comma_sep)
.context(expected("function arguments"))
.parse_next(i)
}
/// A type of a function argument. /// A type of a function argument.
/// This can be: /// This can be:
/// - a primitive type, e.g. 'number' or 'string' or 'bool' /// - a primitive type, e.g. 'number' or 'string' or 'bool'
@ -2783,7 +2786,28 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
ignore_whitespace(i); ignore_whitespace(i);
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(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<usize, _>) = 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) { if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) {
let just_args: Vec<_> = args.iter().collect(); let just_args: Vec<_> = args.iter().collect();
typecheck_all_kw(std_fn, &just_args)?; typecheck_all_kw(std_fn, &just_args)?;
@ -2792,6 +2816,10 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
opt(comma_sep).parse_next(i)?; opt(comma_sep).parse_next(i)?;
let end = close_paren.parse_next(i)?.end; let end = close_paren.parse_next(i)?.end;
let non_code_meta = NonCodeMeta {
non_code_nodes,
..Default::default()
};
Ok(Node { Ok(Node {
start: fn_name.start, start: fn_name.start,
end, end,
@ -2801,6 +2829,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
unlabeled: initial_unlabeled_arg, unlabeled: initial_unlabeled_arg,
arguments: args, arguments: args,
digest: None, digest: None,
non_code_meta,
}, },
outer_attrs: Vec::new(), 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(); 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] #[test]
fn kw_fn() { fn kw_fn() {
for input in ["val = foo(x, y = z)", "val = foo(y = z)"] { 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 }"# 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_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)] #[allow(unused)]

View File

@ -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
}

View File

@ -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
}