Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indented function parameters #816

Merged
merged 2 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 90 additions & 12 deletions source/parser.hera
Original file line number Diff line number Diff line change
Expand Up @@ -1230,9 +1230,53 @@ ArrowParameters
Parameters

NonEmptyParameters
# NOTE: BindingElement -> ParameterElement
TypeParameters?:tp OpenParen:open ThisType?:tt ParameterElement*:pes FunctionRestParameter?:rest ParameterElement*:after ( __ CloseParen ):close ->
const names = pes.flatMap(p => p.names)
TypeParameters?:tp OpenParen:open ParameterList:params ( __ CloseParen ):close ->
// Categorize arguments to put any ThisType in front, and split remaining
// arguments into before and after the rest parameter.
let tt, before = [], rest, after = [], errors = []
function append(p) {
(rest ? after : before).push(p)
}
for (const param of params) {
switch (param.type) {
case "ThisType":
if (tt) {
append({
type: "Error",
message: "Only one typed this parameter is allowed",
})
append(param)
} else {
tt = insertTrimmingSpace(param, "")
if (before.length || rest) { // moving ThisType to front
let delim = tt.children.at(-1)
if (Array.isArray(delim)) delim = delim.at(-1)
if (delim?.token !== ",") {
tt = {
...tt,
children: [...tt.children, ", "],
}
}
}
}
break
case "FunctionRestParameter":
if (rest) {
append({
type: "Error",
message: "Only one rest parameter is allowed",
})
append(param)
} else {
rest = param
}
break
default:
append(param)
}
}

const names = before.flatMap(p => p.names)
if (rest) {
const restIdentifier = rest.binding.ref || rest.binding
names.push(...rest.names || [])
Expand All @@ -1251,7 +1295,7 @@ NonEmptyParameters
tp,
open,
tt,
...pes,
...before,
// Remove delimiter
{...rest, children: rest.children.slice(0, -1)},
close,
Expand All @@ -1264,29 +1308,63 @@ NonEmptyParameters

return {
type: "Parameters",
children: [tp, open, tt, ...pes, close],
names: pes.flatMap((p) => p.names),
children: [tp, open, tt, ...before, close],
names,
tp,
}

ParameterList
# Nested case: Allow for one line of parameters followed by a nested list
Parameter* NestedParameterList ->
return [...$1, ...$2]
# Otherwise, try parsing while ignore indentation
( __ Parameter )* ->
return $1.map(([eos, p]) => ({
...p,
children: [eos, ...p.children],
}))

NestedParameterList
PushIndent NestedParameter*:params PopIndent ->
if (!params.length) return $skip
return params

NestedParameter
# Allow one or more parameters on one line
Nested:ws Parameter+:params ->
// Attach whitespace to first parameter
params = [...params]
params[0] = {
...params[0],
children: [ws, ...params[0].children],
}
return params

Parameter
ThisType
ParameterElement
FunctionRestParameter

# https://262.ecma-international.org/#prod-FunctionRestParameter
FunctionRestParameter
__ BindingRestElement:id TypeSuffix? ParameterElementDelimiter ->
# BindingRestElement has a leading _?
# but also sometimes invokes __ via BindingIdentifier
!EOS BindingRestElement:id TypeSuffix? ParameterElementDelimiter ->
return {
type: "FunctionRestParameter",
children: $0,
children: $0.slice(1),
names: id.names,
binding: id.binding,
}

# NOTE: Similar to BindingElement but appears in formal parameters list
ParameterElement
__ AccessModifier? ( BindingIdentifier / BindingPattern ) TypeSuffix? Initializer? ParameterElementDelimiter ->
_? AccessModifier?:accessModifier _? ( NWBindingIdentifier / BindingPattern ):binding TypeSuffix? Initializer? ParameterElementDelimiter ->
return {
type: "Parameter",
children: $0,
names: $3.names,
accessModifier: $2,
names: binding.names,
accessModifier,
}

ParameterElementDelimiter
Expand Down Expand Up @@ -6589,7 +6667,7 @@ TypeParameterDelimiter

# TypeScript's this: T syntax in function parameters
ThisType
( This / AtThis ) Colon Type ParameterElementDelimiter -> {
_? ( This / AtThis ) Colon Type ParameterElementDelimiter -> {
type: "ThisType",
ts: true,
children: $0
Expand Down
40 changes: 37 additions & 3 deletions test/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,13 @@ describe "function", ->
})
"""

throws """
double rest parameter
---
(a, ...b, ...c) ->
c
"""

testCase """
non-end rest parameter
---
Expand Down Expand Up @@ -904,7 +911,6 @@ describe "function", ->
) { return null }
"""

// TODO: wrong indentation of }s
testCase """
indented with types
---
Expand All @@ -918,10 +924,38 @@ describe "function", ->
function f<T>(
t: {
item: T
},
},
u: {
item: T
}
}
){}
"""

testCase """
indented with assignments
---
function f(
x = 0
y = 1
)
---
function f(
x = 0,
y = 1
){}
"""

testCase """
indented with assignments and types
---
function f(
x : number = 0
y : number = 1
)
---
function f(
x : number = 0,
y : number = 1
){}
"""

Expand Down
16 changes: 16 additions & 0 deletions test/types/function.civet
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,19 @@ describe "[TS] function", ->
---
(function (this: T) {})
"""

testCase """
move this type to front
---
function (x: T, @: T,) {}
function (x: T, @: T) {}
---
(function (this: T,x: T,) {});
(function (this: T, x: T,) {})
"""

throws """
double this type
---
function (this: T, @: T) {}
"""