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

Review and guard all missing fields/attributes #4323

Merged
merged 6 commits into from
Oct 24, 2023
Merged

Conversation

colinrotherham
Copy link
Contributor

@colinrotherham colinrotherham commented Oct 10, 2023

Ahead of #4036 I've spent some time reviewing all optional things accessed without guards

We should follow this PR up with any extra errors we'd like to throw, although I've already split out:

  1. Assert Exit this page HTMLAnchorElement button and clearly guard all dynamic fields #4321
  2. Move getFragmentFromUrl() to common utilities #4320

For example, in PR #4320 we've identified tab links without hash fragments (already handled in Skip link)

Missing elements, attributes etc

  • Class fields with incorrect null default
  • Selector null return values not handled
  • Timeout and interval IDs cleared when null
  • Confg allows null as typeof value === 'object'

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 10, 2023 14:56 Inactive
@github-actions
Copy link

github-actions bot commented Oct 10, 2023

📋 Stats

File sizes

File Size
dist/govuk-frontend-4.7.0.min.css 120.66 KiB
dist/govuk-frontend-4.7.0.min.js 51.56 KiB
dist/govuk-frontend-ie8-4.7.0.min.css 81.59 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 76.76 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 72.07 KiB
packages/govuk-frontend/dist/govuk/all.mjs 3.8 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 359 B
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 113.99 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 37.83 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.3 KiB

Modules

File Size
all.mjs 68.4 KiB
components/accordion/accordion.mjs 21.05 KiB
components/button/button.mjs 4.18 KiB
components/character-count/character-count.mjs 20.49 KiB
components/checkboxes/checkboxes.mjs 5.31 KiB
components/error-summary/error-summary.mjs 5.49 KiB
components/exit-this-page/exit-this-page.mjs 15.49 KiB
components/header/header.mjs 3.39 KiB
components/notification-banner/notification-banner.mjs 4.02 KiB
components/radios/radios.mjs 4.31 KiB
components/skip-link/skip-link.mjs 3.3 KiB
components/tabs/tabs.mjs 9.07 KiB

View stats and visualisations on the review app


Action run for 50eb86e

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 10, 2023 15:32 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 10, 2023 15:47 Inactive
@colinrotherham colinrotherham self-assigned this Oct 10, 2023
@colinrotherham colinrotherham added this to the v5.0 milestone Oct 11, 2023
Copy link
Member

@romaricpascal romaricpascal left a comment

Choose a reason for hiding this comment

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

Most of the changes look good to me 🙌🏻 After a deeper look, this adds a lot of extra ifs and || so thinking this warrants a wider discussion across @alphagov/design-system-developers.

In quite a few places it looks like we're adding code branches that'll never run, though, so I've added a few questions to try and understand why they're needed. I'm worried that to please TypeScript's strict mode (in #4106), we're having to add ifs (or ||) of which we know the result for sure (but TypeScript doesn't) 😬

@@ -132,7 +132,7 @@ export class CharacterCount extends GOVUKFrontendComponent {
})

// Determine the limit attribute (characters or words)
this.maxLength = this.config.maxwords || this.config.maxlength
this.maxLength = this.config.maxwords || this.config.maxlength || Infinity
Copy link
Member

Choose a reason for hiding this comment

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

question Given how we validate the configuration, we'll never reach the || Infinity, I think. What's requiring us to keep it?

Copy link
Contributor Author

@colinrotherham colinrotherham Oct 12, 2023

Choose a reason for hiding this comment

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

Ah it's only moved it from the class field to here to make the compiler happy there's no undefined

Perhaps when we pick up #4230 we can look at CharacterCountConfig becoming Required<CharacterCountConfig> when all fields are validated?

Then the compiler will know a fallback is no longer needed

packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs Outdated Show resolved Hide resolved
Comment on lines +489 to +512
if (!this.$showAllButton || !this.$showAllText || !this.$showAllIcon) {
return
}
Copy link
Member

Choose a reason for hiding this comment

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

question We're creating these elements ourselves, what's requiring us to add this early return?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah we discussed via Slack thread to use silent returns (not errors) for self-created elements

I've been running ESLint @typescript-eslint/stylistic-type-checked with @domoscargin in #4106 so we can teach ESLint to be aware of types. These three are all null at instantiation

Copy link
Member

Choose a reason for hiding this comment

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

Returning rather than throwing is fine as we're creating these elements, as discussed on the Slack thread.

Shame TypeScript needs an extra nudge leading to us checking things we do know exist 😔

Copy link
Contributor Author

@colinrotherham colinrotherham Oct 12, 2023

Choose a reason for hiding this comment

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

Yeah it's a shame, but good to be consistent

Alternatively we could consider getters and avoid null defaults entirely?

Here's a diff of what that would look like:
null-checks...null-checks-getters

Comment on lines +121 to +118
if (!this.mql || !this.$menu || !this.$menuButton) {
return
}
Copy link
Member

Choose a reason for hiding this comment

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

question I think this will never happen:

  • We early return if this.$menu is falsy
  • We throw if this.$menu exists and this.$menuButton is falsy
  • And if we haven't thrown, we create this.mql ourselves

What's requiring us to add this early return?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as we discussed via Slack thread again

Don't forget that this.$menu being falsy causes this.mql to stay as null

Unlike in Tabs, the compiler spotted that this.mql isn't always set in the constructor

Can see all these with strict mode turned on in packages/govuk-frontend/tsconfig.build.json

{
  "extends": "../../tsconfig.base.json",
  "include": ["./src/govuk/**/*.mjs"],
  "exclude": ["**/*.test.*"],
  "compilerOptions": {
    "lib": ["ESNext", "DOM"],
+  "strict": true,
    "target": "ES2015",
    "types": ["node"]
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As an alternative, I've mentioned getters in #4323 (comment)

Changing this.mql to a getter (without a setter) ensures it only runs when first accessed:

get mql() {
  if (!this._mql) {
    this._mql = window.matchMedia('(min-width: 48.0625em)')
  }

  return this._mql
}

Base automatically changed from copying-assignment to main October 12, 2023 12:12
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 12, 2023 12:36 Inactive
@colinrotherham colinrotherham changed the base branch from main to instanceof October 17, 2023 11:02
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 17, 2023 11:02 Inactive
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 18, 2023 14:35 Inactive
Base automatically changed from instanceof to main October 18, 2023 14:36
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 19, 2023 11:09 Inactive
@colinrotherham
Copy link
Contributor Author

colinrotherham commented Oct 19, 2023

@romaricpascal I've split this PR into separate commits to show what each change is for

Agree that textContent being null is frustrating so I've suggested an alternative below:

- ariaLabelParts.push(($summary.textContent || '').trim())
+ ariaLabelParts.push(`${$summary.textContent}`.trim())

But this is only necessary when running the TypeScript compiler with strict or strictNullChecks enabled since the WHATWG DOM Living Standard shows the Node interface textContent property can be null

From the mdn Node page:

In addition, every kind of DOM node is represented by an interface based on Node. These include Attr, CharacterData (which Text, Comment, CDATASection and ProcessingInstruction are all based on), and DocumentType.

It's a shame the compiler can't filter out other Node types that aren't possible here

Update: I've moved these ones to the strict mode PR

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-4323 October 24, 2023 07:38 Inactive
@colinrotherham
Copy link
Contributor Author

@romaricpascal @domoscargin I've updated this PR to move anything "new" or for discussion into:

This PR now includes only guards we've used elsewhere

@colinrotherham colinrotherham linked an issue Oct 24, 2023 that may be closed by this pull request
3 tasks
Copy link
Member

@romaricpascal romaricpascal left a comment

Choose a reason for hiding this comment

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

Cheers for reducing the scope of it to the less controversial changes. Happy with that ⛵ .

I'm warming up to that getter pattern as well, it's quite neat. On your branch, it make the build of the elements of the accordion neatly encapsulated, on top of fixing the null issue.

@colinrotherham colinrotherham merged commit ffe1595 into main Oct 24, 2023
46 checks passed
@colinrotherham colinrotherham deleted the null-checks branch October 24, 2023 08:58
romaricpascal pushed a commit that referenced this pull request Oct 27, 2023
Review and guard all missing fields/attributes
@colinrotherham colinrotherham removed their assignment Dec 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

Review ElementErrors and tie up any loose ends
3 participants