You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Describe the bug
When someone registers a command or a network object with a function that has a default value for a parameter, that default value is not honored if undefined is passed as the argument. This is due to the fact that JSON doesn't support undefined, so it's converted to null over the websocket. As such, the parameter thinks the argument is null and therefore doesn't use the default parameter (JavaScript only uses the default parameter if an argument is not provided or is undefined. null doesn't satisfy those conditions).
To Reproduce
Steps to reproduce the behavior:
In web-view.service-host.ts, remove the first few lines that provide a default value for layout if it is not provided (a forced default value)
Open the Resource Viewer (it runs getWebView(stuff, undefined, stuff), so the middle parameter layout is undefined and will be converted to null)
See the error that type doesn't exist on null in Chrome DevTools
Expected behavior undefined should properly trigger default values on parameters for commands and network object functions (and everything else on the papi; these are just the first examples that came to mind).
In a call on 11/14/23, we decided that we should make a serializer and deserializer function in JS that always revives null to undefined. This is due to null being an object and not triggering default parameters (among other issues - see this article on null for more reasons not to use it). Since we are very significantly interacting with other languages that do not have the distinction of undefined vs null (and JSON doesn't even have it), it seemed best to us to somewhat enforce the same in our JS where possible. We should do the following:
Create deserialize and serialize functions in papi-util.ts that we use at the lowest level of our network connectors instead of JSON.stringify and JSON.parse directly that use JSON.stringify with a replacer that converts undefined to null and then JSON.parse with a reviver that converts null to undefined. See this playground for an example of a replacer and a reviver. This is not quite what we are intending to use as our replacer and reviver, but it shows the general idea of what we want to do.
These deserialize and serialize functions should mirror the corresponding JSON versions and have the same arguments. If someone passes in a replacer, run that function inside our replacerbefore doing our conversion between null and undefined. If someone passes in a reviver, run that function inside our reviverafter doing our conversion between null and undefined.
The JSDocs for these should explain what they do, why we decided to do that, and when to use these functions. These functions should generally be used when interacting with JSON that goes around the papi. Since these functions do not round-trip JSON (meaning they irreversibly modify JSON if you deserialize and serialize) because they change null to undefined, they should not be used in situations where data integrity is critical like with some user data.
Update papi-util.ts's isSerializable to the new functions and update its JSDoc accordingly
Create unit tests for the serialization functions that make sure that null comes out to undefined within objects and in the middle of arrays. See the playground linked above for some more context on what we're testing
Change JSON.stringify to the new papi serialize everywhere in our code
Change JSON.parse to the new papi deserialize everywhere in our code except the following location:
check-native-dep.js
Change our style guide to indicate we always use undefined in JavaScript when we have control over our code unless we need to use null for some reason like interfacing with someone else's API. Note: null is appropriate in languages that do not have both null and undefined such as C# and JSON.
Install and enable no-null/no-null eslint rule in core and all extension repos that disallows using null - that way, we have to comment why when we use it
Rework all of our JavaScript APIs that use null as a significantly different value than undefined
Rework resource-viewer.ts's openResourceViewer (and the resourceViewer.open command) so it returns undefined if the user cancels the dialog
Rework text collection's open command so it returns undefined if the user cancels the dialog
Rework word list's open command so it returns undefined if the user cancels the dialog
Rework all dialog calls so they return undefined instead of null if the user cancels the dialog. Make sure to get all calls to resolveDialogRequest that call null like in platform-dock-layout.component.tsx. Note: you will need to remove the note in the dialog-service.model.ts JSDoc mentioning that null is used so undefined can be a meaningful value. There are also a number of other JSDocs to update like cancelDialog in dialog-base.data.ts
Rework snackbar.component.tsxautoHideDuration - default to 0 which would mean "do not autohide", and convert 0 to null internally since that's what the mui snackbar wants
Remove null on all our public-facing type signatures. However, unfortunately, the way the data grid component we use internally is typed, you will still have to figure out a way to pass in null on these fields. Maybe you can just made another internal type that extends the current public column type we make that adds null on those specific fields.
Currently, in our manifest.json, we use null to indicate that an extension does not and should not have a JavaScript main file to run. We throw an error if main is not specified to help extension developers to understand what they are missing. Change the value to use from null to empty string ''. This will need to be updated in a few manifest.json files, in extension.service.ts, and in webpack.util.ts
In use-promise.hook.ts, no longer do the special null return.
For settings service and use-settings.hook.ts, use undefined instead of null. Make sure to properly interface with localstorage which can return null but not undefined
In client-network-connector.service.ts, in notifyClientConnected, we can change the localstorage call line to const reconnectingClientGuid = localStorage.getItem(CLIENT_GUID_KEY) ?? undefined; (add ?? undefined), we can remove null from reconnectingClientGuid in ClientConnect in network-connector.model.ts and related places
Replace all remaining nulls with undefined in JS code unless there would be a problem with an API we don't control if you did so
For example, do not replace the nulls in evil.js because they are using document.getElementById which may return null
Do not replace nulls in useRef when the ref is being assigned to a component's ref (like useref<HTMLDivElement>(null)) for the same reason as above.
For another example, do not change the null in edit-papi-d-ts.ts because RegExp.exec returns null in some cases. There are also other uses of null in relation to RegExp that should be left alone
Leave null in ChildProcessByStdio in extension-host.service.ts as that is from an API we are using
Leave null in local-storage.polyfill.ts as I think that is a standard way to polyfill
Leave null in platform-dock-layout.component.tsx where we are interacting with the dockLayout specifically
Leave null in util.ts's isErrorWithMessage since that is specific utility functionality that interfaces with JavaScript APIs
Describe the bug
When someone registers a command or a network object with a function that has a default value for a parameter, that default value is not honored if
undefined
is passed as the argument. This is due to the fact that JSON doesn't supportundefined
, so it's converted tonull
over the websocket. As such, the parameter thinks the argument isnull
and therefore doesn't use the default parameter (JavaScript only uses the default parameter if an argument is not provided or isundefined
.null
doesn't satisfy those conditions).To Reproduce
Steps to reproduce the behavior:
web-view.service-host.ts
, remove the first few lines that provide a default value forlayout
if it is not provided (a forced default value)getWebView(stuff, undefined, stuff)
, so the middle parameterlayout
isundefined
and will be converted tonull
)type
doesn't exist onnull
in Chrome DevToolsExpected behavior
undefined
should properly trigger default values on parameters for commands and network object functions (and everything else on the papi; these are just the first examples that came to mind).In a call on 11/14/23, we decided that we should make a serializer and deserializer function in JS that always revives
null
toundefined
. This is due tonull
being an object and not triggering default parameters (among other issues - see this article onnull
for more reasons not to use it). Since we are very significantly interacting with other languages that do not have the distinction ofundefined
vsnull
(and JSON doesn't even have it), it seemed best to us to somewhat enforce the same in our JS where possible. We should do the following:deserialize
andserialize
functions inpapi-util.ts
that we use at the lowest level of our network connectors instead ofJSON.stringify
andJSON.parse
directly that useJSON.stringify
with areplacer
that convertsundefined
tonull
and thenJSON.parse
with areviver
that convertsnull
toundefined
. See this playground for an example of areplacer
and areviver
. This is not quite what we are intending to use as our replacer and reviver, but it shows the general idea of what we want to do.deserialize
andserialize
functions should mirror the correspondingJSON
versions and have the same arguments. If someone passes in areplacer
, run that function inside ourreplacer
before doing our conversion betweennull
andundefined
. If someone passes in areviver
, run that function inside ourreviver
after doing our conversion betweennull
andundefined
.null
toundefined
, they should not be used in situations where data integrity is critical like with some user data.papi-util.ts
'sisSerializable
to the new functions and update its JSDoc accordinglynull
comes out toundefined
within objects and in the middle of arrays. See the playground linked above for some more context on what we're testingJSON.stringify
to the new papiserialize
everywhere in our codeJSON.parse
to the new papideserialize
everywhere in our code except the following location:check-native-dep.js
undefined
in JavaScript when we have control over our code unless we need to usenull
for some reason like interfacing with someone else's API. Note:null
is appropriate in languages that do not have bothnull
andundefined
such as C# and JSON.no-null/no-null
eslint rule in core and all extension repos that disallows usingnull
- that way, we have to comment why when we use itnull
as a significantly different value thanundefined
resource-viewer.ts
'sopenResourceViewer
(and theresourceViewer.open
command) so it returnsundefined
if the user cancels the dialogundefined
if the user cancels the dialogundefined
if the user cancels the dialogundefined
instead ofnull
if the user cancels the dialog. Make sure to get all calls toresolveDialogRequest
that callnull
like inplatform-dock-layout.component.tsx
. Note: you will need to remove the note in thedialog-service.model.ts
JSDoc mentioning thatnull
is used soundefined
can be a meaningful value. There are also a number of other JSDocs to update likecancelDialog
indialog-base.data.ts
snackbar.component.tsx
autoHideDuration
- default to0
which would mean "do not autohide", and convert 0 tonull
internally since that's what the mui snackbar wantsnull
on all our public-facing type signatures. However, unfortunately, the way the data grid component we use internally is typed, you will still have to figure out a way to pass innull
on these fields. Maybe you can just made another internal type that extends the current public column type we make that addsnull
on those specific fields.manifest.json
, we usenull
to indicate that an extension does not and should not have a JavaScript main file to run. We throw an error ifmain
is not specified to help extension developers to understand what they are missing. Change the value to use fromnull
to empty string''
. This will need to be updated in a fewmanifest.json
files, inextension.service.ts
, and inwebpack.util.ts
use-promise.hook.ts
, no longer do the specialnull
return.use-settings.hook.ts
, useundefined
instead ofnull
. Make sure to properly interface withlocalstorage
which can returnnull
but notundefined
client-network-connector.service.ts
, innotifyClientConnected
, we can change the localstorage call line toconst reconnectingClientGuid = localStorage.getItem(CLIENT_GUID_KEY) ?? undefined;
(add?? undefined
), we can removenull
fromreconnectingClientGuid
inClientConnect
innetwork-connector.model.ts
and related placesnull
s withundefined
in JS code unless there would be a problem with an API we don't control if you did sonull
s inevil.js
because they are usingdocument.getElementById
which may returnnull
null
s inuseRef
when the ref is being assigned to a component'sref
(likeuseref<HTMLDivElement>(null)
) for the same reason as above.null
inedit-papi-d-ts.ts
becauseRegExp.exec
returnsnull
in some cases. There are also other uses ofnull
in relation toRegExp
that should be left alonenull
inChildProcessByStdio
inextension-host.service.ts
as that is from an API we are usingnull
inlocal-storage.polyfill.ts
as I think that is a standard way to polyfillnull
inplatform-dock-layout.component.tsx
where we are interacting with thedockLayout
specificallynull
inutil.ts
'sisErrorWithMessage
since that is specific utility functionality that interfaces with JavaScript APIsSee discussion for more information.
The text was updated successfully, but these errors were encountered: