-
Notifications
You must be signed in to change notification settings - Fork 22
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
#6739: Incremental strict null checks adoption: Slice 10 #6808
Conversation
@@ -154,6 +154,7 @@ | |||
"slugify": "^1.6.6", | |||
"stemmer": "^2.0.1", | |||
"timezone-mock": "^1.3.6", | |||
"uint8array-extras": "^0.5.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR includes some deduplication around base64/data-url parsing, for which we had like 3 functions.
I merged them into one and started using this package for a safe base64/arraybuffer conversion:
Note: In the browser, do not use
globalThis.atob()
/globalThis.btoa()
because they do not support Unicode. This package does.
@@ -62,47 +62,62 @@ const resizerMap = new WeakMap<HTMLElement, IFrameComponent>(); | |||
function popoverFactory(initialUrl: URL): HTMLElement { | |||
const $container = $(ensureTooltipsContainer()); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Diffs in this file best seen without whitespace changes:
- I flipped a condition to allow for a early return
- I replaced some logic; This appeases the null checks and we will have to do more of it:
- bye
.length
+.push()
+.pop()
- hello
.pop()
+ null check
- bye
- I added the
html
helper to force Parcel to format the HTML snippet. However I don’t think our version is working correctly so I'll add code-tag
|
||
if (selectors.length === 0) { | ||
if (!nextSelector) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again here:
- bye
length
+[x, ...rest]
- hello
shift()
+ null check
label: extension.label, | ||
extensionLabel: extension.label, | ||
label: extension.label ?? undefined, | ||
extensionLabel: extension.label ?? undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the consequence of one type having optional properties ({x?: Type}
) and the other using {x: Type | null}
I can look into a better solution but I think it'd require changes to some higher type like ModComponent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Longer term, it might be nice to enforce https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That plus a linting rule to prefer null
could clean a lot of that up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think it's practical to enforce specifying every property on an object with null
if they're really meant to be optional. If anything, I'd enforce the opposite: use an optional key instead of T | null
, because it's much easier to handle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's my understanding that enforcing exactOptionalPropertyTypes
would still allow optional properties, but would prevent us from setting a property to undefined
. null
should be reserved for when we want to specify that we "know a value doesn't exist" while undefined
should mean that the value was never set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah I was answering to the second comment, regarding preferring null
, which I read as "Use {x: string | null}
instead of {x?: string}
"
exactOptionalPropertyTypes
is unrelated here because {x?: string}
still generates string | undefined
regardless of exactOptionalPropertyTypes
, so it would still conflict with string | null
.
This sort of mismatches are why I subscribe to sindresorhus/meta#7, which also cites this problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null
can be important. Especially with JSON. See JSON.stringify({ foo: undefined })
vs JSON.stringify({ foo: null })
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactOptionalPropertyTypes is unrelated here because {x?: string} still generates string | undefined regardless of exactOptionalPropertyTypes, so it would still conflict with string | null
Is that true? It would definitely still conflict withstring | null
, but{x: undefined}
would be flagged.exactOptionalPropertyTypes
would allow{ }
but error on{x: undefined}
.
To allow { x: undefined }
, we'd have to specify { x?: string | undefined }
, which we could then flag with a linting rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
json.foo
is still nullish, it's not important if you consider null
and undefined
the same.
JSON.stringify([undefined, 1])
will generate null
in cases where there was undefined
, but then again if you treat both the same way it doesn't matter if JSON.parse()
generate [null, 1]
and the expected type is Array<number | undefined>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON.stringify([undefined, 1]) will generate null in cases where there was undefined
TIL
this.cancelListeners.add(() => { | ||
mutationObserver.disconnect(); | ||
}); | ||
onAbort(this.cancelController, mutationObserver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This helper also comes from Refined GitHub. AbortController
/AbortSignal
has bad DX, but this makes it much nicer.
// https://stackoverflow.com/q/52612122/288906 | ||
// globalThis.crypto = { | ||
// getRandomValues: (array) => crypto.randomBytes(array.length), | ||
// }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was required on Node 16, but it already exists in Node 18. We'll never need it again.
return new Blob([ia], { type: mimeTypeEssence }); | ||
} | ||
|
||
export { convertDataUrl }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO:
- Add tests for
convertDataUrl
Our parseDataUrl
is already tested and so is the 3rd party base64ToUint8Array
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #6808 +/- ##
==========================================
+ Coverage 70.39% 70.41% +0.02%
==========================================
Files 1193 1193
Lines 37053 37049 -4
Branches 6935 6949 +14
==========================================
+ Hits 26082 26087 +5
+ Misses 10971 10962 -9
☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the code multiple times and tested the changed logic. Should be good to go.
@@ -86,7 +86,7 @@ export class CopyToClipboard extends EffectABC { | |||
} | |||
|
|||
try { | |||
blob = dataURItoBlob(text); | |||
blob = convertDataUrl(text, "Blob"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conversion tested via:
const image = new Image()
image.src = URL.createObjectURL(blob);
document.body.prepend(image);
But the brick is otherwise broken on main
:
async function getData(img: HTMLImageElement): Promise<ArrayBuffer> { | ||
// Adapted from https://github.com/exif-js/exif-js/blob/master/exif.js#L384 | ||
if (/^data:/i.test(img.src)) { | ||
// Data URI | ||
return base64ToArrayBuffer(img.src); | ||
return convertDataUrl(img.src, "ArrayBuffer"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BLoe or @twschiller, I've not used this brick. Do you have any context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's a super old brick, I don't know if anyone uses it. Looks like both image bricks are element-target bricks, where you don't explicitly pass an input, but you need to use them with element target mode. If you ask on Slack someone can probably link you to the PB developer docs on how to use them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to test it.
Steps:
- Navigate to https://github.com/ianare/exif-samples
- Create a Context Menu starter brick
- Add the EXIF brick
- Under Advanced Options, set Target Root Mode to Element
- Set Target Element to @input.element.ref
// TODO: replace callback with having caller pass AbortSignal | ||
export function onNodeRemoved(node: Node, callback: () => void): () => void { | ||
const ancestors = getAncestors(node); | ||
export function onNodeRemoved( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Greatly simplified, now uses a single MutationObserver, no sets/weaksets, no nulls.
@@ -237,6 +238,7 @@ | |||
"@types/webpack-env": "^1.18.3", | |||
"@types/whatwg-mimetype": "^3.0.1", | |||
"axios-mock-adapter": "^1.22.0", | |||
"blob-polyfill": "^7.0.20220408", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jest 🤷♂️
|
||
private uninstalled = false; | ||
|
||
// TODO: Rewrite this logic to use AbortController and to possibly unify the logic with the | ||
// global cancelController. The `onAbort` utility might be useful to link multiple controllers | ||
// together, but it's probably best to skip the duplicate `cancelController`. | ||
private readonly cancelRemovalMonitor: Map<string, () => void>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file has a weird setup where there's a main "cancel callbacks" Set and also a per-extension Map:
- The main Set is currently used, I turned it into an AbortController
- The Map is currently already commented out, so I left some context for the changes required.
e3de789
to
94cf919
Compare
@@ -145,3 +146,89 @@ describe("asyncMapValues", () => { | |||
}); | |||
}); | |||
}); | |||
|
|||
describe("onAbort", () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💜
No loom links were found in the first post. Please add one there if you'd like to it to appear on Slack. Do not edit this comment manually. |
What does this PR do?
Checklist