Skip to content
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
6 changes: 6 additions & 0 deletions .changeset/beige-impalas-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@env-spec/parser": minor
"varlock": minor
---

allow multi-line fn calls, both in decorator and item values
55 changes: 49 additions & 6 deletions packages/env-spec-parser/grammar.peggy
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ DecoratorComment =
Decorator =
"@" name:DecoratorName
val:(
FunctionArgs
DecoratorFunctionArgs
/ "=" DecoratorValue
)?
{
Expand All @@ -114,7 +114,45 @@ Decorator =
// we allow an ending ":", because we don't want to choke on pre-existing comments
// but we will treat the entire line as a normal comment if we do see that
DecoratorName = $([a-zA-Z] [a-zA-Z0-9_]*)
DecoratorValue = FunctionCall / quotedString / unquotedStringWithoutSpaces
DecoratorValue = DecoratorFunctionCall / quotedString / unquotedStringWithoutSpaces

// Decorator-specific function call (supports multi-line with # continuation)
DecoratorFunctionCall =
name:FunctionName
args:DecoratorFunctionArgs
{
return new ParsedEnvSpecFunctionCall({
name,
args,
_location: location(),
});
}

// Function args within decorators - allows newline + # continuation
DecoratorFunctionArgs =
"(" _decWs
values:(
(
(key:FunctionArgKeyName "=" val:DecoratorFunctionArgValue { return new ParsedEnvSpecKeyValuePair({ key, val })})
/ DecoratorFunctionArgValue
)|1.., _decWs "," _decWs|
)?
(_decWs ",")? // optional trailing comma
_decWs ")"
{
return new ParsedEnvSpecFunctionArgs({
values: values || [],
_location: location(),
});
}

DecoratorFunctionArgValue =
DecoratorFunctionCall
/ quotedString
/ $([^ \n,)]+) { return new ParsedEnvSpecStaticValue({ rawValue: text(), _location: location() }) }

// Whitespace within decorator function args - allows newline followed by # continuation
_decWs = ([ \t] / "\n" [ \t]* "#" [ \t]*)*


FunctionCall =
Expand All @@ -132,15 +170,17 @@ FunctionName = $([a-zA-Z] [a-zA-Z0-9_]*)

// function args are a list, can be either array, or key/value, or mixed
// fn(arr1, arry2, key=val, key2=val2)
// Supports multi-line with plain newlines (for item values)
FunctionArgs =
"(" _
"(" _valWs
values:(
(
(key:FunctionArgKeyName "=" val:FunctionArgValue { return new ParsedEnvSpecKeyValuePair({ key, val })})
/ FunctionArgValue
)|1.., _ "," _|
)|1.., _valWs "," _valWs|
)?
_ ")"
(_valWs ",")? // optional trailing comma
_valWs ")"
{
return new ParsedEnvSpecFunctionArgs({
values: values || [],
Expand All @@ -152,9 +192,12 @@ FunctionArgKeyName = $([a-zA-Z] [a-zA-Z0-9_]*)
FunctionArgValue =
FunctionCall
/ quotedString
// slightly different here - can contain "#", cannot contain ")" or ","
// slightly different here - can contain "#", cannot contain ")" or "," or newline
/ $([^ \n,)]+) { return new ParsedEnvSpecStaticValue({ rawValue: text(), _location: location() }) }

// Whitespace within item value function args - allows plain newlines
_valWs = [ \t\n]*

Divider =
"#" leadingSpace:$_ contents:$([-=*#]|3..| [^\n]*) _n
{
Expand Down
1 change: 1 addition & 0 deletions packages/env-spec-parser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@types/node": "catalog:",
"ansis": "catalog:",
"npm-run-all": "^4.1.5",
"outdent": "catalog:",
"peggy": "^5.0.6",
"tsup": "catalog:",
"vitest": "catalog:"
Expand Down
102 changes: 95 additions & 7 deletions packages/env-spec-parser/test/decorators.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect } from 'vitest';
import outdent from 'outdent';
import {
ParsedEnvSpecFunctionCall, ParsedEnvSpecFunctionArgs, ParsedEnvSpecStaticValue, parseEnvSpecDotEnvFile,
} from '../src';
Expand Down Expand Up @@ -87,12 +88,99 @@ describe('decorator parsing', () => {
// obj args
['# @dec=decFn(k1=v1)', { dec: { fnName: 'decFn', fnArgs: { k1: 'v1' } } }],
['# @dec=decFn(k1=v1, k2="v2")', { dec: { fnName: 'decFn', fnArgs: { k1: 'v1', k2: 'v2' } } }],
// bar fn calls - ex: `@import(some/path)`
// bare fn calls - ex: `@import(some/path)`
['# @enableFoo()', { enableFoo: { fnName: undefined, fnArgs: [] } }],
['# @enableFoo(bar)', { enableFoo: { fnName: undefined, fnArgs: ['bar'] } }],
['# @import(../some/path)', { import: { fnName: undefined, fnArgs: ['../some/path'] } }],
]));

describe('multi-line function calls', basicDecoratorTests([
{
label: 'multi-line @dec() call',
comments: outdent`
# @import(
# ./.env.import,
# ITEM1,
# ITEM2,
# )
`,
expected: { import: { fnName: undefined, fnArgs: ['./.env.import', 'ITEM1', 'ITEM2'] } },
},
{
label: 'multi-line @dec=fn()',
comments: outdent`
# @dec=someFn(
# arg1,
# arg2
# )
`,
expected: { dec: { fnName: 'someFn', fnArgs: ['arg1', 'arg2'] } },
},
{
label: 'multi-line with key=value args',
comments: outdent`
# @config(
# key1=val1,
# key2="val2"
# )
`,
expected: { config: { fnName: undefined, fnArgs: { key1: 'val1', key2: 'val2' } } },
},
{
label: 'multi-line with varying indentation',
comments: outdent`
# @import(
#ITEM1,
# ITEM2,
# ITEM3
#)
`,
expected: { import: { fnName: undefined, fnArgs: ['ITEM1', 'ITEM2', 'ITEM3'] } },
},
{
label: 'multi-line decorator followed by another decorator on same closing line',
comments: outdent`
# @import(
# ITEM1
# ) @required
`,
expected: {
import: { fnName: undefined, fnArgs: ['ITEM1'] },
required: true,
},
},
{
label: 'empty multi-line fn call',
comments: outdent`
# @doSomething(
# )
`,
expected: { doSomething: { fnName: undefined, fnArgs: [] } },
},
{
label: 'bad multi-line dec fn call (missing #)',
comments: outdent`
# @import(
./.env.import,
# ITEM1,
# ITEM2,
# )
`,
expected: new Error(),
},
{
label: 'multi-line dec fn with commented interior line',
comments: outdent`
# @import(
# ./.env.import,
# # ITEM1,
# ITEM2,
# )
`,
expected: new Error(),
},
]));

describe('whitespace handling', basicDecoratorTests([
['#@dec=1', { dec: 1 }],
['#\t@dec=1', { dec: 1 }],
Expand Down Expand Up @@ -126,12 +214,12 @@ describe('decorator parsing', () => {
describe('comments and line breaks', basicDecoratorTests([
{
label: 'mixed with comments ',
comments: [
'# comment before',
'# @dec1',
'# comment after',
'#@dec2',
].join('\n'),
comments: outdent`
# comment before
# @dec1
# comment after
#@dec2
`,
expected: { dec1: true, dec2: true },
},
{
Expand Down
Loading