feat: add enums to array args

This commit is contained in:
Ruben Fiszel
2023-06-18 21:01:53 +02:00
parent 256843329f
commit 1060d3271c
13 changed files with 290 additions and 110 deletions
@@ -14,8 +14,9 @@ use swc_common::{sync::Lrc, FileName, SourceMap, SourceMapper, Span, Spanned};
use swc_ecma_ast::{
ArrayLit, AssignPat, BigInt, BindingIdent, Bool, Decl, ExportDecl, Expr, FnDecl, Ident, Lit,
ModuleDecl, ModuleItem, Number, ObjectLit, Param, Pat, Str, TsArrayType, TsEntityName,
TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType, TsPropertySignature,
TsType, TsTypeElement, TsTypeLit, TsTypeRef, TsUnionOrIntersectionType, TsUnionType,
TsKeywordType, TsKeywordTypeKind, TsLit, TsLitType, TsOptionalType, TsParenthesizedType,
TsPropertySignature, TsType, TsTypeElement, TsTypeLit, TsTypeRef, TsUnionOrIntersectionType,
TsUnionType,
};
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsConfig};
@@ -151,7 +152,7 @@ fn binding_ident_to_arg(BindingIdent { id, type_ann }: &BindingIdent) -> (String
}
fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
//println!("{:?}", ts_type);
// log(&format!("{:?}", ts_type));
match ts_type {
TsType::TsKeywordType(t) => (
match t.kind {
@@ -187,6 +188,9 @@ fn tstype_to_typ(ts_type: &TsType) -> (Typ, bool) {
.collect();
(Typ::Object(properties), false)
}
TsType::TsParenthesizedType(TsParenthesizedType { type_ann, .. }) => {
tstype_to_typ(type_ann)
}
// TODO: we can do better here and extract the inner type of array
TsType::TsArrayType(TsArrayType { elem_type, .. }) => {
(Typ::List(Box::new(tstype_to_typ(&**elem_type).0)), false)
@@ -3,7 +3,7 @@
"collaborators": [
"Ruben Fiszel <ruben@windmill.dev>"
],
"version": "1.114.2",
"version": "1.115.0",
"files": [
"windmill_parser_wasm_bg.wasm",
"windmill_parser_wasm.js",
@@ -342,7 +342,7 @@ function __wbg_get_imports() {
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
};
imports.wbg.__wbg_eval_d9c460c7b796e0b4 = function(arg0, arg1) {
imports.wbg.__wbg_eval_1acc8bc05c9160ad = function(arg0, arg1) {
const ret = eval(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
@@ -211,3 +211,31 @@ export function main(foo: FooBar) {
Ok(())
}
#[wasm_bindgen_test]
fn test_parse_enum_list() -> anyhow::Result<()> {
let code = "
export function main(foo: (\"foo\" | \"bar\")[]) {
}
";
assert_eq!(
parse_deno_signature(code, false)?,
MainArgSignature {
star_args: false,
star_kwargs: false,
args: vec![Arg {
name: "foo".to_string(),
otyp: None,
typ: Typ::List(Box::new(Typ::Str(Some(vec![
"foo".to_string(),
"bar".to_string()
])))),
default: None,
has_default: false
}]
}
);
Ok(())
}
+5 -1
View File
@@ -23,7 +23,11 @@ export interface SchemaProperty {
enum?: string[]
contentEncoding?: 'base64' | 'binary'
format?: string
items?: { type?: 'string' | 'number' | 'bytes' | 'object'; contentEncoding?: 'base64' }
items?: {
type?: 'string' | 'number' | 'bytes' | 'object'
contentEncoding?: 'base64'
enum?: string[]
}
properties?: { [name: string]: SchemaProperty }
required?: string[]
}
@@ -0,0 +1,56 @@
<script lang="ts">
import { Pen } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
export let customValue: boolean
export let disabled: boolean
export let value: any
export let enum_: string[] | undefined
export let autofocus: boolean
export let defaultValue: string | undefined
export let valid: boolean
const dispatch = createEventDispatcher()
</script>
{#if !customValue}
<select
on:focus={(e) => {
dispatch('focus')
}}
{disabled}
class="px-6"
bind:value
>
{#each enum_ ?? [] as e}
<option>{e}</option>
{/each}
</select>
{:else}
<input
{autofocus}
on:focus
type="text"
class={twMerge(
'secondaryBackground',
valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'
)}
placeholder={defaultValue ?? ''}
bind:value
/>
{/if}
{#if !disabled}
<button
class="min-w-min !px-2 items-center text-gray-800 bg-gray-100 border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
on:click={() => {
customValue = !customValue
}}
title="Custom Value"
>
<Pen size={14} />
</button>
{/if}
+56 -82
View File
@@ -3,7 +3,7 @@
import type { SchemaProperty } from '$lib/common'
import { setInputCat as computeInputCat } from '$lib/utils'
import { DollarSign, Pen, X } from 'lucide-svelte'
import { DollarSign, X } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import autosize from 'svelte-autosize'
import Icon from 'svelte-awesome'
@@ -24,6 +24,8 @@
import Toggle from './Toggle.svelte'
import type VariableEditor from './VariableEditor.svelte'
import { twMerge } from 'tailwind-merge'
import ArgEnum from './ArgEnum.svelte'
import ArrayTypeNarrowing from './ArrayTypeNarrowing.svelte'
export let label: string = ''
export let value: any
@@ -41,7 +43,11 @@
export let disabled = false
export let editableSchema = false
export let itemsType:
| { type?: 'string' | 'number' | 'bytes' | 'object'; contentEncoding?: 'base64' }
| {
type?: 'string' | 'number' | 'bytes' | 'object'
contentEncoding?: 'base64'
enum?: string[]
}
| undefined = undefined
export let displayHeader = true
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
@@ -178,52 +184,47 @@
<FieldHeader prettify={prettifyHeader} {label} {required} {type} {contentEncoding} {format} />
{/if}
{#if editableSchema}
<div class="p-2 my-1 text-xs border-solid border border-gray-400">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="underline"
on:click={() => {
seeEditable = !seeEditable
}}
>
Customize property
<Icon class="ml-2" data={seeEditable ? faChevronUp : faChevronDown} scale={0.7} />
</span>
<label class="text-gray-700">
Description
<textarea
class="mb-1"
use:autosize
rows="1"
bind:value={description}
on:keydown={onKeyDown}
placeholder="Field description"
/>
</label>
{#if seeEditable}
<div class="mt-2">
<label class="text-gray-700">
Description
<textarea
class="mb-1"
use:autosize
rows="1"
bind:value={description}
on:keydown={onKeyDown}
placeholder="Field description"
/>
{#if type == 'array'}
<ArrayTypeNarrowing bind:itemsType />
{:else if (type == 'string' && format != 'date-time') || ['number', 'object'].includes(type ?? '')}
<div class="p-2 my-1 text-xs border-solid border border-gray-200 rounded-lg">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<span
class="underline"
on:click={() => {
seeEditable = !seeEditable
}}
>
Customize
<Icon class="ml-2" data={seeEditable ? faChevronUp : faChevronDown} scale={0.7} />
</span>
{#if seeEditable}
<div class="mt-2">
{#if type == 'string' && format != 'date-time'}
<StringTypeNarrowing bind:format bind:pattern bind:enum_ bind:contentEncoding />
{:else if type == 'number'}
<NumberTypeNarrowing bind:min={extra['min']} bind:max={extra['max']} />
{:else if type == 'object'}
<ObjectTypeNarrowing bind:format />
{:else if type == 'array'}
<select bind:value={itemsType}>
<option value={undefined}>No specific item type</option>
<option value={{ type: 'string' }}> Items are strings</option>
<option value={{ type: 'object' }}> Items are objects (JSON)</option>
<option value={{ type: 'number' }}>Items are numbers</option>
<option value={{ type: 'string', contentEncoding: 'base64' }}
>Items are bytes</option
>
</select>
{/if}
</label>
</div>
{/if}
</div>
<span class="text-2xs">Input preview:</span>
</div>
{/if}
</div>
{/if}
<span class="text-2xs font-semibold">Preview:</span>
{/if}
{#if description}
@@ -293,12 +294,24 @@
/>
{:else if itemsType?.type == 'object'}
<JsonEditor code={JSON.stringify(v, null, 2)} bind:value={v} />
{:else if Array.isArray(itemsType?.enum)}
<select
on:focus={(e) => {
dispatch('focus')
}}
class="px-6"
bind:value={v}
>
{#each itemsType?.enum ?? [] as e}
<option>{e}</option>
{/each}
</select>
{:else}
<input type="text" bind:value={v} />
{/if}
<button
transition:fade|local={{ duration: 100 }}
class="rounded-full p-1 bg-white/60 duration-200 hover:bg-gray-200"
class="rounded-full p-1 bg-white/60 duration-200 hover:bg-gray-200 ml-2"
aria-label="Clear"
on:click={() => {
value.splice(i, 1)
@@ -361,46 +374,7 @@
{/if}
{:else if inputCat == 'enum'}
<div class="flex flex-row w-full gap-1">
{#if !customValue}
<select
on:focus={(e) => {
dispatch('focus')
}}
{disabled}
class="px-6"
bind:value
>
{#each enum_ ?? [] as e}
<option>{e}</option>
{/each}
</select>
{:else}
<input
{autofocus}
on:focus
type="text"
class={twMerge(
'secondaryBackground',
valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'
)}
placeholder={defaultValue ?? ''}
bind:value
/>
{/if}
{#if !disabled}
<button
class="min-w-min !px-2 items-center text-gray-800 bg-gray-100 border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
on:click={() => {
customValue = !customValue
}}
title="Custom Value"
>
<Pen size={14} />
</button>
{/if}
<ArgEnum {defaultValue} {valid} {customValue} {disabled} bind:value {enum_} {autofocus} />
</div>
{:else if inputCat == 'date'}
<input {autofocus} class="inline-block" type="datetime-local" bind:value />
@@ -449,7 +423,7 @@
type="text"
{disabled}
class={twMerge(
'w-full secondaryBackground',
'w-full',
valid
? ''
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'
@@ -0,0 +1,83 @@
<script lang="ts">
import { Button } from './common'
export let itemsType:
| {
type?: 'string' | 'number' | 'bytes' | 'object'
contentEncoding?: 'base64'
enum?: string[]
}
| undefined
let selected: 'string' | 'number' | 'object' | 'bytes' | 'enum' | undefined =
itemsType?.type != 'string'
? itemsType?.type
: Array.isArray(itemsType?.enum)
? 'enum'
: 'string'
</script>
<select
bind:value={selected}
on:change={() => {
if (selected == 'enum') {
itemsType = { type: 'string', enum: [] }
} else if (selected == 'string') {
itemsType = { type: 'string' }
} else if (selected == 'number') {
itemsType = { type: 'number' }
} else if (selected == 'object') {
itemsType = { type: 'object' }
} else if (selected == 'bytes') {
itemsType = { type: 'string', contentEncoding: 'base64' }
} else {
itemsType = undefined
}
}}
>
<option value="string"> Items are strings</option>
<option value="enum">Items are strings from an enum</option>
<option value="object"> Items are objects (JSON)</option>
<option value="number">Items are numbers</option>
<option value="bytes">Items are bytes</option>
</select>
{#if Array.isArray(itemsType?.enum)}
<div class="pt-1" />
<label for="input" class="mb-2 text-gray-700 text-xs">
Enums
<div class="flex flex-col gap-1">
{#each itemsType?.enum || [] as e}
<div class="flex flex-row max-w-md">
<input id="input" type="text" bind:value={e} />
<Button
size="sm"
btnClasses="ml-6"
on:click={() => {
if (itemsType?.enum) {
itemsType.enum = (itemsType.enum || []).filter((el) => el !== e)
}
}}>-</Button
>
</div>
{/each}
</div>
<div class="flex flex-row my-1">
<Button
size="sm"
on:click={() => {
if (itemsType?.enum) {
itemsType.enum = itemsType.enum ? itemsType.enum.concat('') : ['']
}
}}>+</Button
>
<Button
variant="border"
size="sm"
btnClasses="ml-2"
on:click={() => itemsType?.enum && (itemsType.enum = undefined)}
>
Clear
</Button>
</div>
</label>
{/if}
@@ -15,6 +15,8 @@
import LightweightSchemaForm from './LightweightSchemaForm.svelte'
import type { ComponentCustomCSS } from './apps/types'
import { twMerge } from 'tailwind-merge'
import { fade } from 'svelte/transition'
import { X } from 'lucide-svelte'
export let css: ComponentCustomCSS<'schemaformcomponent'> | undefined = undefined
export let label: string = ''
@@ -32,7 +34,11 @@
export let maxRows = 10
export let enum_: string[] | undefined = undefined
export let itemsType:
| { type?: 'string' | 'number' | 'bytes' | 'object'; contentEncoding?: 'base64' }
| {
type?: 'string' | 'number' | 'bytes' | 'object'
contentEncoding?: 'base64'
enum?: string[]
}
| undefined = undefined
export let displayHeader = true
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
@@ -202,10 +208,10 @@
<span>&nbsp; Not set</span>
{/if}
{:else if inputCat == 'list'}
<div>
<div>
<div class="w-full">
<div class="w-full">
{#each value ?? [] as v, i}
<div class="flex flex-row max-w-md mt-1">
<div class="flex flex-row max-w-md mt-1 w-full">
{#if itemsType?.type == 'number'}
<input type="number" bind:value={v} />
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'}
@@ -215,14 +221,25 @@
on:change={(x) => fileChanged(x, (val) => (value[i] = val))}
multiple={false}
/>
{:else if Array.isArray(itemsType?.enum)}
<select
on:focus={(e) => {
dispatch('focus')
}}
class="px-6"
bind:value={v}
>
{#each itemsType?.enum ?? [] as e}
<option>{e}</option>
{/each}
</select>
{:else}
<input type="text" bind:value={v} />
{/if}
<Button
variant="border"
color="red"
size="sm"
btnClasses="mx-6"
<button
transition:fade|local={{ duration: 100 }}
class="rounded-full p-1 bg-white/60 duration-200 hover:bg-gray-200 ml-2"
aria-label="Clear"
on:click={() => {
value = value.filter((el) => el != v)
if (value.length == 0) {
@@ -230,15 +247,15 @@
}
}}
>
<Icon data={faMinus} />
</Button>
<X size={14} />
</button>
</div>
{/each}
</div>
<div class="flex">
<div class="flex my-2">
<Button
variant="border"
color="blue"
color="light"
size="sm"
btnClasses="mt-1"
on:click={() => {
@@ -249,7 +266,7 @@
}}
>
<Icon data={faPlus} class="mr-2" />
Add item
Add
</Button>
</div>
<span class="ml-2">
@@ -53,6 +53,7 @@
import Toggle from '$lib/components/Toggle.svelte'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Drawer from './common/drawer/Drawer.svelte'
import ArrayTypeNarrowing from './ArrayTypeNarrowing.svelte'
export let error = ''
export let editing = false
@@ -150,6 +151,11 @@
property.contentEncoding = undefined
property.enum_ = undefined
property.pattern = undefined
if (argType == 'array') {
property.items = { type: 'string' }
} else {
property.items = undefined
}
}}
>
{argType}
@@ -176,6 +182,7 @@
bind:value={property.default}
type={property.selectedType}
pattern={property.pattern}
itemsType={property.items}
/>
<Toggle
options={{ right: 'Required' }}
@@ -203,11 +210,7 @@
bind:contentEncoding={property.contentEncoding}
/>
{:else if property.selectedType == 'array'}
<select bind:value={property.items}>
<option value={undefined}>No specific item type</option>
<option value={{ type: 'string' }}> Items are strings</option>
<option value={{ type: 'number' }}>Items are numbers</option>
</select>
<ArrayTypeNarrowing bind:itemsType={property.items} />
{:else if property.selectedType == 'object'}
<h3 class="mb-2 font-bold mt-4">Resource type</h3>
<ObjectTypeNarrowing bind:format={property.format} />
+12 -2
View File
@@ -43,7 +43,6 @@ export async function inferArgs(
schema.required = []
const oldProperties = JSON.parse(JSON.stringify(schema.properties))
schema.properties = {}
for (const arg of inferedSchema.args) {
@@ -120,8 +119,10 @@ function argSigToJsonSchemaType(
newS.items = { type: 'number' }
} else if (t.list === 'bytes') {
newS.items = { type: 'string', contentEncoding: 'base64' }
} else if (t.list == 'string' || (t.list && typeof t.list == 'object' && 'str' in t.list)) {
} else if (t.list == 'string') {
newS.items = { type: 'string' }
} else if (t.list && typeof t.list == 'object' && 'str' in t.list) {
newS.items = { type: 'string', enum: t.list.str }
} else {
newS.items = { type: 'object' }
}
@@ -141,7 +142,16 @@ function argSigToJsonSchemaType(
delete oldS.items
}
let sameItems = oldS.items?.type == 'string' && newS.items?.type == 'string'
let savedItems: any = undefined
if (sameItems) {
savedItems = JSON.parse(JSON.stringify(oldS.items))
}
Object.assign(oldS, newS)
if (sameItems) {
oldS.items = savedItems
}
if (oldS.format?.startsWith('resource-') && newS.type != 'object') {
oldS.format = undefined
}
@@ -87,6 +87,7 @@
if (script) {
initialPath = script.path
scriptBuilder?.setCode(script.content)
script.parent_hash = topHash
}
}
@@ -98,5 +99,5 @@
</script>
{#if script}
<ScriptBuilder bind:this={scriptBuilder} bind:topHash {initialPath} {script} {initialArgs} />
<ScriptBuilder bind:this={scriptBuilder} {topHash} {initialPath} {script} {initialArgs} />
{/if}