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

core: fix NodeDetails type mismatch #11752

Merged
merged 2 commits into from
Dec 2, 2020
Merged

core: fix NodeDetails type mismatch #11752

merged 2 commits into from
Dec 2, 2020

Conversation

brendankenny
Copy link
Member

I was reviewing some code recently, noticed we're being very aggressive in some type casts of audit details to /** @type {LH.Audit.Details.NodeValue} */, removed some of them, and found a type error due to the NodeDetails changes in #11405 that wasn't flagged due to the casts hiding the difference between types (in this case, a null property in the artifact being used for an optional (but not nullable) property in the audit details).

We were using that cast because if strings are nested in an object, they're typically widened by tsc from the literal value to just string, which means they're no longer valid AuditDetails (which need a specific literal). But casts are dangerous (this is a pretty mild example of how they can go wrong), so we should avoid if we can.

There are two main options instead of casting:

  1. cast just the string

    type: /** @type {'node'} */ ('node')

    This is annoying, but it's about as painful as in the typescript world (there you could add as const to all of these strings)

  2. type the container these are going into.

    /** @type {Array<{node: LH.Audit.Details.NodeValue}>} */
    const items = rule.nodes.map(node => ({
      node: {
        type: 'node',
        // ...
      },
    };
    

    This has improved since we originally added these types, where even if the string literal is several layers deep in an object literal, if it's going into e.g. this example array that's typed with that audit details, the string won't be widened

(2) seems like they better solution, and with details items like /** @type {Array<{node: LH.Audit.Details.NodeValue}>} */ it's really not so bad. But several of these have more properties (href, target, rel...) and so the type starts getting excessive compared to just letting it be inferred.

As a result, I went with option 1, but completely happy to go the other way instead.

Either way, hopefully this will set the template for how to items in future audits and we can stamp out some future casting :)

@brendankenny brendankenny requested a review from a team as a code owner December 2, 2020 22:32
@brendankenny brendankenny requested review from connorjclark and removed request for a team December 2, 2020 22:32
@google-cla google-cla bot added the cla: yes label Dec 2, 2020
@@ -29,7 +29,7 @@ function collectAllScriptElements() {
id: script.id || null,
async: script.async,
defer: script.defer,
source: /** @type {'head'|'body'} */ (script.closest('head') ? 'head' : 'body'),
Copy link
Member Author

Choose a reason for hiding this comment

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

this cast does nothing because the untyped ...getNodeDetails() below is spreading ...any which just spreads into the whole object being any :)

@@ -161,7 +161,7 @@ declare global {
lhId?: string,
devtoolsNodePath: string,
selector: string,
boundingRect: Rect | null,
boundingRect?: Rect,
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the functional change to the PR. In practice it shouldn't be observable because I believe null could only ever come from link-elements and script-elements and we wouldn't have been trying to put their boundingRects in the LHR in the first place, but you never know where some code is checking against undefined instead of falsiness or something.

Copy link
Collaborator

@connorjclark connorjclark left a comment

Choose a reason for hiding this comment

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

I came to the same conclusion, although my analysis only went surface level: Wrapping multi-line object literals in a cast is much more awkward than for a single string. Stylistically alone I prefer doing this for all detail value types.

@brendankenny
Copy link
Member Author

Stylistically alone I prefer doing this for all detail value types

have I got a follow up for you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants