-
Notifications
You must be signed in to change notification settings - Fork 6
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
Pattern for nestedOptions in optionize
#127
Comments
Thanks for bringing this to my attention, I was unclear on how that worked. @zepumph do we need to do anything about this? Is this the desired long-term plan here? Do we need documentation or anything? Please close if there is nothing else to do. |
At this time I do not know if it is possible in Typescript to determine that keys ending in
I don't think either are feasible, but if we could figure out something like that, then it would be easy to have the I created an example with a TODO back to #128 about this in WilderOptionsPatterns. Anything else here for now? I'm going to notify the typescript channel of the (hopefully temporary) convention. |
I pinged slack:
|
Note also that this patterns seems to be most common with nested options, but can apply to any options field. |
I don't plan to work on this issue further at the moment. Not sure whether to close or self-unassign. Leaning toward "close", but anyone please reopen for any reason. |
Up until 3 days ago this patter was completely unsolved. We had no way for optionize to know that a nested key was provided. Then I experiemented with @jessegreenberg with a new pattern that I would like feedback on. The main forces that brought me to this code is that Typescript cannot mutate the type of a variable once it is declared, so I wanted the right type to begin with (instead of what we have been doing like @samreid what do you think? |
I think that's the way of the future. I want to test drive it in some cases. I don't use nested options too much in my sim-specific code but I should find something. Maybe convert: public constructor( providedOptions?: PreferencesModelOptions ) {
providedOptions = providedOptions || {}; to public constructor( providedOptions: PreferencesModelOptions = {} ) { Also, I discovered the proposed plan is not detecting excess keys. For instance: Index: js/preferences/PreferencesModel.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/preferences/PreferencesModel.ts b/js/preferences/PreferencesModel.ts
--- a/js/preferences/PreferencesModel.ts (revision a5064217d68bd28986e9b6db82ea9d7df53cc6dc)
+++ b/js/preferences/PreferencesModel.ts (date 1661480576044)
@@ -169,7 +169,8 @@
phetioReadOnly: true
}, providedOptions ) ),
generalOptions: optionize<GeneralPreferencesOptions>()( {
- customPreferences: []
+ customPreferences: [],
+ hello: true // We would like this to be a type error, but it isn't
}, providedOptions.generalOptions ),
visualOptions: optionize<VisualPreferencesOptions>()( {
supportsProjectorMode: false,
|
Also, I tried to apply this pattern in CCK but got this far before I found that it does not flag excess properties: Index: js/model/ACVoltage.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/model/ACVoltage.ts b/js/model/ACVoltage.ts
--- a/js/model/ACVoltage.ts (revision c384d99c91789ee63e20e65c31dea3ae82c97769)
+++ b/js/model/ACVoltage.ts (date 1661480500635)
@@ -9,6 +9,7 @@
import BooleanProperty from '../../../axon/js/BooleanProperty.js';
import NumberProperty from '../../../axon/js/NumberProperty.js';
import Property from '../../../axon/js/Property.js';
+import { NumberPropertyOptions } from '../../../axon/js/NumberProperty.js';
import Range from '../../../dot/js/Range.js';
import optionize, { EmptySelfOptions } from '../../../phet-core/js/optionize.js';
import IntentionalAny from '../../../phet-core/js/types/IntentionalAny.js';
@@ -48,18 +49,21 @@
public static FREQUENCY_RANGE = new Range( 0.1, 2.0 );
public static MAX_VOLTAGE_RANGE = new Range( 0, MAX_VOLTAGE );
- public constructor( startVertex: Vertex, endVertex: Vertex, internalResistanceProperty: Property<number>, tandem: Tandem, providedOptions?: ACVoltageOptions ) {
+ public constructor( startVertex: Vertex, endVertex: Vertex, internalResistanceProperty: Property<number>, tandem: Tandem, providedOptions: ACVoltageOptions = {} ) {
assert && assert( internalResistanceProperty, 'internalResistanceProperty should be defined' );
- const options = optionize<ACVoltageOptions, SelfOptions, VoltageSourceOptions>()( {
- initialOrientation: 'right',
- voltage: 9.0,
- isFlammable: true,
- numberOfDecimalPlaces: 2,
- voltagePropertyOptions: {
+ const options = {
+ ...optionize<ACVoltageOptions, SelfOptions, VoltageSourceOptions>()( {
+ initialOrientation: 'right',
+ voltage: 9.0,
+ isFlammable: true,
+ numberOfDecimalPlaces: 2
+ }, providedOptions ),
+
+ voltagePropertyOptions: optionize<NumberPropertyOptions, EmptySelfOptions>()( {
range: ACVoltage.VOLTAGE_RANGE
- }
- }, providedOptions );
+ }, providedOptions.voltagePropertyOptions )
+ };
super( startVertex, endVertex, internalResistanceProperty, CCKCConstants.BATTERY_LENGTH, tandem, options );
this.maximumVoltageProperty = new NumberProperty( options.voltage, {
Index: js/model/VoltageSource.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/model/VoltageSource.ts b/js/model/VoltageSource.ts
--- a/js/model/VoltageSource.ts (revision c384d99c91789ee63e20e65c31dea3ae82c97769)
+++ b/js/model/VoltageSource.ts (date 1661480295564)
@@ -6,8 +6,7 @@
* @author Sam Reid (PhET Interactive Simulations)
*/
-import NumberProperty from '../../../axon/js/NumberProperty.js';
-import Range from '../../../dot/js/Range.js';
+import NumberProperty, { NumberPropertyOptions } from '../../../axon/js/NumberProperty.js';
import Property from '../../../axon/js/Property.js';
import Tandem from '../../../tandem/js/Tandem.js';
import circuitConstructionKitCommon from '../circuitConstructionKitCommon.js';
@@ -23,10 +22,7 @@
type SelfOptions = {
initialOrientation?: string; // TODO: enum
voltage?: number;
- voltagePropertyOptions?: {
- range?: Range;
- tandem?: Tandem;
- };
+ voltagePropertyOptions?: NumberPropertyOptions;
};
export type VoltageSourceOptions = SelfOptions & FixedCircuitElementOptions;
|
Hmm, strange. I do seem to be getting the excess property checking: Index: js/preferences/PreferencesModel.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/preferences/PreferencesModel.ts b/js/preferences/PreferencesModel.ts
--- a/js/preferences/PreferencesModel.ts (revision 0f2668684a76091bc771b646ff70faf66643f965)
+++ b/js/preferences/PreferencesModel.ts (date 1664922787098)
@@ -212,7 +212,8 @@
}, providedOptions ) ),
simulationOptions: optionize<SimulationPreferencesOptions, SimulationPreferencesOptions, BaseModelType>()( {
tandemName: 'simulationModel',
- customPreferences: []
+ customPreferences: [],
+ hello: true // We would like this to be a type error, but it isn't
}, providedOptions.simulationOptions ),
visualOptions: optionize<VisualPreferencesOptions, VisualPreferencesOptions, BaseModelType>()( {
tandemName: VISUAL_MODEL_TANDEM,
|
I am seeing the excess property checking in both examples above. Nice! Not sure what was failing before. So should we document and share the new pattern with the team? const options = {
...optionize<ACVoltageOptions, SelfOptions, VoltageSourceOptions>()( { |
Steps for this issue:
|
I have been doing a bit more exploring around this feature. Here is what I've found:
Index: js/wilder/model/WilderOptionsPatterns.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/wilder/model/WilderOptionsPatterns.ts b/js/wilder/model/WilderOptionsPatterns.ts
--- a/js/wilder/model/WilderOptionsPatterns.ts (revision a9cc0dc442ed8542df4078fb6ca7d8c03909628f)
+++ b/js/wilder/model/WilderOptionsPatterns.ts (date 1668543602709)
@@ -204,7 +204,7 @@
////////
// Example Three Prime: nested options where you do not provide defaults for them.
type ItemContainer2Options = {
- nodeOptions?: ItemOptions;
+ nodeOptions?: Partial<ItemOptions>;
};
class ItemContainer2 {
@@ -212,8 +212,9 @@
public constructor( providedOptions: ItemContainer2Options ) {
- // TODO: Explicitly omit here until we can work out a way for optionize to detect nested options directly. https://github.com/phetsims/phet-core/issues/128
- const options = optionize<ItemContainer2Options, StrictOmit<ItemContainer2Options, 'nodeOptions'>>()( {}, providedOptions );
+ const options = optionize<ItemContainer2Options, ItemContainer2Options>()( {
+ nodeOptions: combineOptions<ItemOptions>( providedOptions.nodeOptions || {} )
+ }, providedOptions );
this.node = new Item( options.nodeOptions );
} |
This is going to need more time to explore. In the meantime if anyone get's bit by nested options + optionize please note in this issue. |
In phetsims/scenery-phet#737 (comment), @samreid asked about this pattern of using
optionize
where you don't want to require a default value for one or more nested options:We discussed this in Slack and decided that
Omit<SelfOptions, 'someNestedOptions'>
was a good pattern. See discussion below. It sounds like this pattern needs to be communicated to the PhET dev team.Slack discussion 3/29/2022
Chris Malley 9:22 AM
I’m wondering if providedOptions should generally be providedOptions?: MyClassOptions | null in order to support composition and nested options. I’ve been running into this quite a bit while converting sun/scenery-phet, where it’s impossible to provide a default like (for example) numberDisplayOptions: null , then propagate to new NumberDisplay( .., options.numberDisplayOptions ). (edited)
Michael Kauzmann 9:35 AM
Totally fine with me, but shouldn't that null be internal to the type defining the nested options?
The text was updated successfully, but these errors were encountered: