Skip to content
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

#6526: Make more background APIs strict (strictNullChecks) #7667

Merged
merged 18 commits into from
Feb 26, 2024

Conversation

fregante
Copy link
Contributor

@fregante fregante commented Feb 18, 2024

@fregante fregante marked this pull request as ready for review February 18, 2024 19:51
@@ -50,11 +51,11 @@ export type UserData = Partial<{
/**
* The user's primary organization.
*/
organizationId: UUID;
organizationId: UUID | null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These types are already inside a Partial<> that set them to X | undefined, so this effectively doesn't change anything at runtime.

/**
* The partner principals. Currently, just the Automation Anywhere Control Room principal if applicable.
* @since 1.7.16
*/
readonly partnerPrincipals?: Me["partner_principals"];
readonly partnerPrincipals: Me["partner_principals"];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already part of Partial. ? is implied

organizationId: organization?.id,
telemetryOrganizationId: telemetryOrganization?.id,
organizationId: organization?.id ?? null,
telemetryOrganizationId: telemetryOrganization?.id ?? null,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could not figure out why undefined is not accepted in this place, so I just made null explicit.

Copy link
Collaborator

@grahamlangford grahamlangford Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserDataUpdate wraps UserData in Required<>. Apparently that doesn't just strip ? but also prevents setting the var to undefined while still allowing null so long as null is included in the typing. I created a simplified reproduction in TS Playground to confirm.

Given:

type Foo = {
    a?: string | null | undefined;
    b?: number;
}

type Bar = Required<Foo>;

This throws a ts error:

const baz: Bar = {
    a: undefined,
    b: 0,
}

This doesn't:

const baz: Bar = {
    a: null,
    b: 0,
}

TIL

Copy link
Contributor Author

@fregante fregante Feb 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. Indeed, because "null is a value", so "baz.a is set"

So our use of Required is meaningless here, i.e. ?? null is only hiding the NPE.

We'll have to use a stricter version of Required:

Will mention this in #7725

@@ -160,7 +160,7 @@ export async function isLinked(): Promise<boolean> {
* Return non-sensitive PixieBrix user profile data.
* @see getExtensionAuth
*/
export async function getUserData(): Promise<Partial<UserData>> {
export async function getUserData(): Promise<UserData> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserData is already Partial, so I made this change everywhere else.

@@ -18,7 +18,7 @@
import { type Deployment, type Me } from "@/types/contract";
import { isEmpty, partition } from "lodash";
import reportError from "@/telemetry/reportError";
import { getUID } from "@/background/messenger/api";
import { getUID } from "@/telemetry/telemetryHelpers";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to go through messenger. Allow direct access of getUID from anywhere since it's storage-based. Currently it's only called by the background page and the deployments page (which probably won't be reached before this is generated and stored)

import reportEvent from "@/telemetry/reportEvent";
import { initTelemetry } from "@/background/telemetry";
import { getUID } from "@/background/messenger/api";
import { initTelemetry, recordEvent } from "@/background/telemetry";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to go through messenger. Allow direct access for recordEvent from the background.

import {
ensureContextMenu,
preloadContextMenus,
uninstallContextMenu,
} from "@/background/contextMenus";
} from "@/background/contextMenus"; // 300 errors
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some comments to make it easier to tell which files will take more time to make strict and not waste further time trying and trying again.

The lines with "messenger import" means that they will cause another context's Messenger registration to be required, so they'd have to be fixed first.

src/types/contract.ts Outdated Show resolved Hide resolved
@fregante fregante changed the title #6526: Make more background APIs strict #6526: Make more background APIs strict (strictNullChecks) Feb 18, 2024
@fregante fregante marked this pull request as draft February 19, 2024 06:29
@@ -150,7 +151,7 @@ export type OrganizationAuthState = {
/**
* The package scope of the organization, or null if not set.
*/
readonly scope?: string;
readonly scope?: string | null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've brought this up before, but I don't remember what we decided.

Functionally type foo?: string | null is different from type foo: string | null | undefined. (<key> in <object> behaves differently for instance)

Do we think it's worth the effort to enforce the difference with type checking? There's a tsconfig option: https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes

Copy link
Contributor Author

@fregante fregante Feb 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try enabling exactOptionalPropertyTypes in a future PR if you'd like to see what that entails. There are "only" 100 errors right now, and it only applies to the strictNullCheck'd files.

Functionally type foo?: string | null is different from type foo: string | null | undefined. (<key> in <object> behaves differently for instance)

I don't particularly care as long as TypeScript handles it correctly (i.e. after key in object, object.key can still be undefined | null).

object.key or object.key == null already covers regular null checking, whereas key in object should only be needed in type guards, e.g. if key in obj, then obj is Foo

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason I really think it might be useful is because we've decided to try to treat null and undefined interchangeably as much as possible (hence Nullishable and foo == null vs foo === null).

I can't think of a time it's hurt us not being explicit in the differences. But if it's simple to enforce the distinction, it could potentially make our type guards safer. If it's a pain to implement, I would rather disregard.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -116,5 +116,5 @@ export function selectExtensionAuthState({
*/
export async function flagOn(flag: string): Promise<boolean> {
const authData = await readAuthData();
return authData.flags?.includes(flag);
return Boolean(authData.flags?.includes(flag));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if this is better than

authData.flags?.includes(flag) ?? false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like ?? false more. Fixed.

@@ -224,8 +223,11 @@ export async function handleInstall({
const { version } = browser.runtime.getManifest();

if (reason === "install") {
reportEvent(Events.PIXIEBRIX_INSTALL, {
version,
void recordEvent({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is recordEvent wrongly deprecated? Should we remove the deprecation and instead prevent it from being imported outside the background script/service worker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prevent it from being imported outside the background script/service worker?

That's what noCrossBoundaryImports does but that's not a guarantee due the many many exceptions: pixiebrix/eslint-config-pixiebrix#193 (comment)

All it takes is a single import in @/bricks and it appears in every bundle.

The deprecation makes it "painful" to use this function so you're forced to read its JSDoc. I just updated it to:

/**
 * @deprecate Only allowed in @/background files. Otherwise use: `import reportEvent from "@/telemetry/reportEvent"`
 */

If you don't like it at all I can revert the changes to this file and have it go through the messenger anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think your explanation is enough. Just wanted to make sure. We've used deprecations in other places to flag usages we want to warn against even though we don't intend to ever remove the function or type. I think this is a valid example as well.

Comment on lines 92 to 93
// Use this instead: `import reportError from "@/telemetry/reportError"`
// export const recordError = getNotifier("RECORD_ERROR", bg);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Use this instead: `import reportError from "@/telemetry/reportError"`
// export const recordError = getNotifier("RECORD_ERROR", bg);

Copy link
Contributor Author

@fregante fregante Feb 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep it there so that nobody thinks we just forgot to add the method. Keeping the full export line also makes it appear when grepping RECORD_ERROR

@fregante fregante marked this pull request as ready for review February 25, 2024 09:58
@fregante
Copy link
Contributor Author

Ready for review again since #7480 was closed. I opened #7725 to review the Me type

Copy link

codecov bot commented Feb 25, 2024

Codecov Report

Attention: Patch coverage is 59.01639% with 25 lines in your changes are missing coverage. Please review.

Project coverage is 72.13%. Comparing base (d8bec45) to head (a0ca675).

❗ Current head a0ca675 differs from pull request most recent head 0f5bfdb. Consider uploading reports for the commit 0f5bfdb to get more accurate results

Files Patch % Lines
src/telemetry/telemetryHelpers.ts 35.71% 9 Missing ⚠️
src/telemetry/initErrorReporter.ts 0.00% 6 Missing ⚠️
src/background/installer.ts 50.00% 3 Missing ⚠️
src/data/service/errorService.ts 0.00% 3 Missing ⚠️
src/telemetry/logging.ts 57.14% 3 Missing ⚠️
src/telemetry/performance.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7667      +/-   ##
==========================================
- Coverage   72.16%   72.13%   -0.04%     
==========================================
  Files        1267     1267              
  Lines       39691    39701      +10     
  Branches     7359     7368       +9     
==========================================
- Hits        28642    28637       -5     
- Misses      11049    11064      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

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.

@fregante fregante merged commit 4bfba3f into main Feb 26, 2024
14 checks passed
@fregante fregante deleted the F/types/slice-19 branch February 26, 2024 17:04
@grahamlangford grahamlangford added this to the 1.8.10 milestone Feb 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants