Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit aa9aa50

Browse files
authored
feat: for optional parameters allow using the default value as constant value (#916)
* feat(gui): add omitted variant to value forms * feat(backend): process OmittedAnnotation * feat(parser): generate value annotations with variant "omitted" * chore(data): generate annotations * style: apply automatic fixes of linters Co-authored-by: lars-reimann <[email protected]>
1 parent 472982d commit aa9aa50

File tree

19 files changed

+3781
-4196
lines changed

19 files changed

+3781
-4196
lines changed

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/model/EditorAnnotations.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ data class MoveAnnotation(val destination: String) : EditorAnnotation() {
8484
override val validTargets = GLOBAL_DECLARATIONS
8585
}
8686

87+
@Serializable
88+
object OmittedAnnotation : EditorAnnotation() {
89+
@Transient
90+
override val validTargets = PARAMETERS
91+
}
92+
8793
@Serializable
8894
data class OptionalAnnotation(val defaultValue: DefaultValue) : EditorAnnotation() {
8995

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/transformation/ValueAnnotationProcessor.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.larsreimann.apiEditor.model.DefaultNone
66
import com.larsreimann.apiEditor.model.DefaultNumber
77
import com.larsreimann.apiEditor.model.DefaultString
88
import com.larsreimann.apiEditor.model.DefaultValue
9+
import com.larsreimann.apiEditor.model.OmittedAnnotation
910
import com.larsreimann.apiEditor.model.OptionalAnnotation
1011
import com.larsreimann.apiEditor.model.PythonParameterAssignment
1112
import com.larsreimann.apiEditor.model.RequiredAnnotation
@@ -39,6 +40,7 @@ private fun PythonParameter.processValueAnnotations() {
3940
.forEach {
4041
when (it) {
4142
is ConstantAnnotation -> processConstantAnnotation(it)
43+
is OmittedAnnotation -> processOmittedAnnotation(it)
4244
is OptionalAnnotation -> processOptionalAnnotation(it)
4345
is RequiredAnnotation -> processRequiredAnnotation(it)
4446
else -> {}
@@ -61,6 +63,30 @@ private fun PythonParameter.processConstantAnnotation(annotation: ConstantAnnota
6163

6264
// Remove parameter
6365
this.release()
66+
67+
// Remove annotation
68+
this.annotations.remove(annotation)
69+
}
70+
71+
private fun PythonParameter.processOmittedAnnotation(annotation: OmittedAnnotation) {
72+
// Update argument that references this parameter
73+
val arguments = crossReferencesToThis()
74+
.mapNotNull { (it.parent as? PythonReference)?.closest<PythonArgument>() }
75+
.toList()
76+
77+
require(arguments.size == 1) {
78+
"Expected parameter to be referenced in exactly one argument but was used in $arguments."
79+
}
80+
81+
// Remove argument
82+
val argument = arguments[0]
83+
argument.release()
84+
85+
// Remove parameter
86+
this.release()
87+
88+
// Remove annotation
89+
this.annotations.remove(annotation)
6490
}
6591

6692
private fun PythonParameter.processOptionalAnnotation(annotation: OptionalAnnotation) {

api-editor/backend/src/test/kotlin/com/larsreimann/apiEditor/transformation/ValueAnnotationProcessorTest.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.larsreimann.apiEditor.transformation
33
import com.larsreimann.apiEditor.model.ConstantAnnotation
44
import com.larsreimann.apiEditor.model.DefaultBoolean
55
import com.larsreimann.apiEditor.model.DefaultNone
6+
import com.larsreimann.apiEditor.model.OmittedAnnotation
67
import com.larsreimann.apiEditor.model.OptionalAnnotation
78
import com.larsreimann.apiEditor.model.PythonParameterAssignment
89
import com.larsreimann.apiEditor.model.RequiredAnnotation
@@ -97,11 +98,32 @@ class ValueAnnotationProcessorTest {
9798

9899
testPackage.processValueAnnotations()
99100

100-
testPackage.annotations
101+
testParameter.annotations
101102
.filterIsInstance<ConstantAnnotation>()
102103
.shouldBeEmpty()
103104
}
104105

106+
@Test
107+
fun `should process OmittedAnnotations`() {
108+
testParameter.annotations += OmittedAnnotation
109+
110+
testPackage.processValueAnnotations()
111+
112+
testMethod.parameters.shouldBeEmpty()
113+
testMethod.callToOriginalAPI?.arguments.shouldBeEmpty()
114+
}
115+
116+
@Test
117+
fun `should remove OmittedAnnotations`() {
118+
testParameter.annotations += OmittedAnnotation
119+
120+
testPackage.processValueAnnotations()
121+
122+
testParameter.annotations
123+
.filterIsInstance<OmittedAnnotation>()
124+
.shouldBeEmpty()
125+
}
126+
105127
@Test
106128
fun `should process OptionalAnnotations`() {
107129
testParameter.annotations += OptionalAnnotation(DefaultBoolean(true))
@@ -118,7 +140,7 @@ class ValueAnnotationProcessorTest {
118140

119141
testPackage.processValueAnnotations()
120142

121-
testPackage.annotations
143+
testParameter.annotations
122144
.filterIsInstance<OptionalAnnotation>()
123145
.shouldBeEmpty()
124146
}
@@ -141,7 +163,7 @@ class ValueAnnotationProcessorTest {
141163

142164
testPackage.processValueAnnotations()
143165

144-
testPackage.annotations
166+
testParameter.annotations
145167
.filterIsInstance<RequiredAnnotation>()
146168
.shouldBeEmpty()
147169
}

api-editor/gui/src/app/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,9 @@ export const App: React.FC = function () {
160160
<RenameForm target={userActionTarget || rawPythonPackage} />
161161
)}
162162
{currentUserAction.type === 'todo' && <TodoForm target={userActionTarget || rawPythonPackage} />}
163-
{currentUserAction.type === 'value' && <ValueForm target={userActionTarget || rawPythonPackage} />}
163+
{currentUserAction.type === 'value' && userActionTarget instanceof PythonParameter && (
164+
<ValueForm target={userActionTarget} />
165+
)}
164166
</GridItem>
165167
<GridItem gridArea="middlePane" overflow="auto" display="flex">
166168
<Box flexGrow={1} overflowY="auto" width="100%">

api-editor/gui/src/features/achievements/AchievementDisplay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const AchievementDisplay: React.FC = function () {
4545
<AchievementCard
4646
currentCount={auditorCount}
4747
achievement={auditorAchievement}
48-
description={`${pluralize(auditorCount, 'annotation')} marked as correct`}
48+
description={`${pluralize(auditorCount, 'annotation')} reviewed`}
4949
/>
5050
<AchievementCard
5151
currentCount={authorCount}

api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export class AnnotatedPythonPackageBuilder {
145145
'Enum',
146146
'Groups',
147147
'Move',
148+
'Omitted',
148149
'Optional',
149150
'Pure',
150151
'Remove',
@@ -215,10 +216,16 @@ export class AnnotatedPythonPackageBuilder {
215216
return new InferableMoveAnnotation(moveAnnotation);
216217
}
217218
break;
218-
case 'Optional':
219+
case 'Omitted':
219220
const valueAnnotation2 = this.annotationStore.valueAnnotations[target];
220-
if (annotationShouldBeProcessed(valueAnnotation2) && valueAnnotation2.variant === 'optional') {
221-
return new InferableOptionalAnnotation(valueAnnotation2);
221+
if (annotationShouldBeProcessed(valueAnnotation2) && valueAnnotation2.variant === 'omitted') {
222+
return new InferableRequiredAnnotation();
223+
}
224+
break;
225+
case 'Optional':
226+
const valueAnnotation3 = this.annotationStore.valueAnnotations[target];
227+
if (annotationShouldBeProcessed(valueAnnotation3) && valueAnnotation3.variant === 'optional') {
228+
return new InferableOptionalAnnotation(valueAnnotation3);
222229
}
223230
break;
224231
case 'Pure':
@@ -240,8 +247,8 @@ export class AnnotatedPythonPackageBuilder {
240247
}
241248
break;
242249
case 'Required':
243-
const valueAnnotation3 = this.annotationStore.valueAnnotations[target];
244-
if (annotationShouldBeProcessed(valueAnnotation3) && valueAnnotation3.variant === 'required') {
250+
const valueAnnotation4 = this.annotationStore.valueAnnotations[target];
251+
if (annotationShouldBeProcessed(valueAnnotation4) && valueAnnotation4.variant === 'required') {
245252
return new InferableRequiredAnnotation();
246253
}
247254
break;
@@ -257,5 +264,5 @@ export class AnnotatedPythonPackageBuilder {
257264
}
258265

259266
const annotationShouldBeProcessed = function (annotation: Annotation): boolean {
260-
return annotation && !annotation.isRemoved && annotation.reviewResult === ReviewResult.Wrong;
267+
return annotation && !annotation.isRemoved && annotation.reviewResult !== ReviewResult.Wrong;
261268
};

api-editor/gui/src/features/annotatedPackageData/model/InferableAnnotation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ export class InferableMoveAnnotation extends InferableAnnotation {
123123
}
124124
}
125125

126+
export class InferableOmittedAnnotation extends InferableAnnotation {
127+
constructor() {
128+
super(dataPathPrefix + 'OmittedAnnotation');
129+
}
130+
}
131+
126132
export class InferableOptionalAnnotation extends InferableAnnotation {
127133
readonly defaultValue: { type: string; value?: DefaultValue };
128134

api-editor/gui/src/features/annotations/AnnotationView.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -251,26 +251,28 @@ export const AnnotationView: React.FC<AnnotationViewProps> = function ({ target
251251
};
252252

253253
const valueAnnotationToString = (valueAnnotation: ValueAnnotation): string => {
254+
if (valueAnnotation.variant === 'omitted') {
255+
return 'omitted';
256+
} else if (valueAnnotation.variant === 'required') {
257+
return 'required';
258+
}
259+
254260
let result = '';
255261

256262
if (valueAnnotation.variant === 'constant') {
257263
result += 'constant';
258264
} else if (valueAnnotation.variant === 'optional') {
259265
result += 'optional with default';
260-
} else if (valueAnnotation.variant === 'required') {
261-
result += 'required';
262266
}
263267

264-
if (valueAnnotation.variant !== 'required') {
265-
if (valueAnnotation.defaultValueType === 'string') {
266-
result += ` "${valueAnnotation.defaultValue}"`;
267-
} else if (valueAnnotation.defaultValueType === 'number') {
268-
result += ' ' + String(valueAnnotation.defaultValue);
269-
} else if (valueAnnotation.defaultValueType === 'boolean') {
270-
result += valueAnnotation.defaultValue === true ? ' True' : ' False';
271-
} else if (valueAnnotation.defaultValueType === 'none') {
272-
result += ' None';
273-
}
268+
if (valueAnnotation.defaultValueType === 'string') {
269+
result += ` "${valueAnnotation.defaultValue}"`;
270+
} else if (valueAnnotation.defaultValueType === 'number') {
271+
result += ' ' + String(valueAnnotation.defaultValue);
272+
} else if (valueAnnotation.defaultValueType === 'boolean') {
273+
result += valueAnnotation.defaultValue === true ? ' True' : ' False';
274+
} else if (valueAnnotation.defaultValueType === 'none') {
275+
result += ' None';
274276
}
275277

276278
return result;

api-editor/gui/src/features/annotations/batchforms/ValueBatchForm.tsx

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
Stack,
2626
Text as ChakraText,
2727
Textarea,
28+
Tooltip,
2829
} from '@chakra-ui/react';
2930
import { useForm } from 'react-hook-form';
3031
import { booleanPattern, numberPattern } from '../../../common/validation';
@@ -39,7 +40,7 @@ interface ValueBatchFormProps {
3940

4041
export const ValueBatchForm: React.FC<ValueBatchFormProps> = function ({ targets }) {
4142
//only parameters can have optional annotations
42-
const filteredTargets = targets.filter((t) => t instanceof PythonParameter);
43+
const filteredTargets = targets.filter((t) => t instanceof PythonParameter) as PythonParameter[];
4344
const targetPaths = filteredTargets.map((t) => t.id);
4445

4546
// Hooks -----------------------------------------------------------------------------------------------------------
@@ -73,7 +74,7 @@ export const ValueBatchForm: React.FC<ValueBatchFormProps> = function ({ targets
7374
};
7475

7576
interface TypeValueBatchFormProps {
76-
targets: PythonDeclaration[];
77+
targets: PythonParameter[];
7778
description: string;
7879
onUpsertAnnotation: (data: TypeValueBatchFormState) => void;
7980
}
@@ -174,19 +175,49 @@ export const TypeValueBatchForm: React.FC<TypeValueBatchFormProps> = function ({
174175
<FormLabel>Choose the variant of this annotation:</FormLabel>
175176
<RadioGroup defaultValue={'optional'} onChange={handleVariantChange}>
176177
<Stack direction="column">
177-
<Radio value="required">Required (parameter must always be set explicitly)</Radio>
178-
<Radio value="optional">Optional (parameter has a default value that can be overwritten)</Radio>
179-
<Radio value="constant">Constant (parameter is replaced by a constant value)</Radio>
178+
<Radio value="required">
179+
<Tooltip
180+
label="Users of the wrapper must set this parameter explicitly. This value is passed to
181+
the original API."
182+
>
183+
Required
184+
</Tooltip>
185+
</Radio>
186+
<Radio value="optional">
187+
<Tooltip
188+
label="Users of the wrapper can set this parameter explicitly. If they do, this value is
189+
passed to the original API. Otherwise, a default value is used."
190+
>
191+
Optional
192+
</Tooltip>
193+
</Radio>
194+
<Radio value="constant">
195+
<Tooltip
196+
label="This parameter no longer appears in the wrapper. Instead, a constant value is passed
197+
to the original API."
198+
>
199+
Constant
200+
</Tooltip>
201+
</Radio>
202+
<Radio value="omitted">
203+
<Tooltip
204+
label="This parameter no longer appears in the wrapper. Moreover, no value is passed to the
205+
original API, so the original default value is used. This option is only available for
206+
parameters that are optional in the original API."
207+
>
208+
Omitted
209+
</Tooltip>
210+
</Radio>
180211
</Stack>
181212
</RadioGroup>
182213

183-
{watchVariant !== 'required' && (
214+
{watchVariant !== 'required' && watchVariant !== 'omitted' && (
184215
<>
185216
{watchVariant === 'optional' && (
186-
<FormLabel>Type of default value of matched elements:</FormLabel>
217+
<FormLabel>Type of the default value for matched elements:</FormLabel>
187218
)}
188219
{watchVariant === 'constant' && (
189-
<FormLabel>Type of constant value of matched elements:</FormLabel>
220+
<FormLabel>Type of the constant value for matched elements:</FormLabel>
190221
)}
191222
<RadioGroup defaultValue={'string'} onChange={handleTypeChange}>
192223
<Stack direction="column">
@@ -199,7 +230,7 @@ export const TypeValueBatchForm: React.FC<TypeValueBatchFormProps> = function ({
199230
</>
200231
)}
201232

202-
{watchVariant !== 'required' && watchDefaultValueType !== 'none' && (
233+
{watchVariant !== 'required' && watchVariant !== 'omitted' && watchDefaultValueType !== 'none' && (
203234
<FormControl isInvalid={Boolean(errors?.defaultValue)}>
204235
{watchVariant === 'optional' && <FormLabel>Default value for matched elements:</FormLabel>}
205236
{watchVariant === 'constant' && <FormLabel>Constant value for matched elements:</FormLabel>}
@@ -244,7 +275,7 @@ export const TypeValueBatchForm: React.FC<TypeValueBatchFormProps> = function ({
244275
</AnnotationBatchForm>
245276
{confirmWindowVisible && (
246277
<ConfirmAnnotations
247-
targets={targets}
278+
targets={watchVariant === 'omitted' ? targets.filter((it) => it.defaultValue !== null) : targets}
248279
handleSave={() => handleSave(data)}
249280
setConfirmVisible={setConfirmWindowVisible}
250281
/>

0 commit comments

Comments
 (0)