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

[Security solutions] Adds linter rule to forbid usage of no-non-null-assertion (TypeScript ! bang operator) #114375

Merged

Conversation

FrankHassanabad
Copy link
Contributor

@FrankHassanabad FrankHassanabad commented Oct 7, 2021

Summary

Fixes: #114535

What this linter rule does:

If you try to use the ! operator you get an error and nice helper message that tries to encourage better practices such as this one:

Screen Shot 2021-10-07 at 11 26 14 AM

Why are we deciding to set this linter rule?

  • Recommended from Kibana styleguide for ~2 years now and still recommended.
  • A lot of TypeScript has evolved and has operators such as ? which can replace the ! in most cases. Other cases can use a throw explicitly or other ways to manage this.
  • Some types can change instead of using this operator and we should just change the types.
  • TypeScript flows have improved and when we upgrade the linter will cause errors where we can remove the ! operator which is 👍 better than leaving them when they're not needed anymore.
  • Newer programmers and team members sometimes mistake it for the ? when it is not the same thing.
  • We have had past bugs and bugs recently because of these.
  • It's easier to use the linter to find bugs than to rely on manual tests.

How did Frank fix all the 403 areas in which the linter goes off?

  • Anywhere I could remove the ! operator without side effects or type script errors I just removed the ! operator.
  • Anywhere in test code where I could replace the code with a ? or a throw I went through that route.
  • Within the code areas (non test code) where I saw what looks like a simple bug that I could fix using a ?? [] or ? operator or String(value) or fixing a simple type I would choose that route first. These areas I marked in the code review with the words NOTE: for anyone to look at.
  • Within all other areas of the code and tests where anything looked more involved I just disabled the linter for that particular line. When in doubt I chose this route.

Checklist

@FrankHassanabad FrankHassanabad changed the title Avoid non null assertions [Security solutions] Adds linter rule to forbid usage of no-non-null-assertion (TypeScript ! bang operator) Oct 7, 2021
@FrankHassanabad FrankHassanabad self-assigned this Oct 7, 2021
export const TIMELINE = (id: string | undefined) => {
if (id == null) {
throw new TypeError('id should never be null or undefined');
}
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Zero risk as this is just test code, but using a throw within the tests is preferred over upstream turning off typescript assertions.

@@ -57,7 +57,7 @@ const LOGIN_API_ENDPOINT = '/internal/security/login';
*/
export const getUrlWithRoute = (role: ROLES, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(url!);
const kibana = new URL(String(url));
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Zero risk as this is just test code, but using a String(value) will return value, using a String(null) will return the string value of null and String(undefined) will return the string undefined. All of which will fail the test.

@@ -83,7 +83,7 @@ interface User {
*/
export const constructUrlWithUser = (user: User, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(url!);
const kibana = new URL(String(url));
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Zero risk as this is just test code, but using a String(value) will return value, using a String(null) will return the string value of null and String(undefined) will return the string undefined. All of which will fail the test.

@@ -94,7 +94,7 @@ export const BarChartBaseComponent = ({
yAccessors={yAccessors}
timeZone={timeZone}
splitSeriesAccessors={splitSeriesAccessors}
data={series.value!}
data={series.value ?? []}
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: 🐛 Low risk fix here as we now pass an empty array if the series.value is undefined

@@ -127,7 +127,7 @@ export const getColumnsWithTimestamp = ({
export const getExampleText = (example: string | number | null | undefined): string =>
!isEmpty(example) ? `Example: ${example}` : '';

export const getIconFromType = (type: string | null) => {
export const getIconFromType = (type: string | null | undefined) => {
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Low risk type fix where I change this to allow an undefined so I could remove anywhere we turn off type assertions. This function should operate the same as before.

@@ -236,7 +236,7 @@ const EventsViewerComponent: React.FC<Props> = ({
useTimelineEvents({
docValueFields,
fields,
filterQuery: combinedQueries!.filterQuery,
filterQuery: combinedQueries?.filterQuery,
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: 🐛 Fix low risk, high impact here because if you look below this you see the code at line 246 which says:

skip: !canQueryTimeline || combinedQueries?.filterQuery === undefined, // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped

If this can be undefined then I would assume we want to allow it to be undefined here. Either way, this code written as is will BLOW UP if combinedQueries is undefined. So I figured it's best to use ? operator here.

@@ -300,7 +300,7 @@ const EventsViewerComponent: React.FC<Props> = ({
height={headerFilterGroup ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT}
subtitle={utilityBar ? undefined : subtitle}
title={globalFullScreen ? titleWithExitFullScreen : justTitle}
isInspectDisabled={combinedQueries!.filterQuery === undefined}
isInspectDisabled={combinedQueries?.filterQuery === undefined}
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: 🐛 Low risk fix here because the ! will cause a failure if combinedQueries is undefined. This will ensure we are getting back an undefined for sure instead of blowing up.

has('lastSuccess.timestamp', node) && node.lastSuccess!.timestamp != null ? (
<FormattedRelativePreferenceDate value={node.lastSuccess!.timestamp} />
has('lastSuccess.timestamp', node) && node.lastSuccess?.timestamp != null ? (
<FormattedRelativePreferenceDate value={node.lastSuccess?.timestamp} />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NOTE: Low risk 🐛 fix here as the has before this should guarantee it's there but the ? allows us to remove the non-assertion operator. In reality we can probably use more ? operator and remove more has in these places but I figured this would be good enough for us.

has('lastFailure.timestamp', node) && node.lastFailure!.timestamp != null ? (
<FormattedRelativePreferenceDate value={node.lastFailure!.timestamp} />
has('lastFailure.timestamp', node) && node.lastFailure?.timestamp != null ? (
<FormattedRelativePreferenceDate value={node.lastFailure?.timestamp} />
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NOTE: Low risk 🐛 fix here as the has before this should guarantee it's there but the ? allows us to remove the non-assertion operator. In reality we can probably use more ? operator and remove more has in these places but I figured this would be good enough for us.

@@ -76,12 +76,12 @@ export const DraggableZeekElement = React.memo<{
and: [],
enabled: true,
id: escapeDataProviderId(`draggable-zeek-element-draggable-wrapper-${id}-${field}-${value}`),
name: value!,
name: String(value),
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Small risk 🐛 fix. I don't think we expect null/undefined but using String(null) or String(undefined) will display null or undefined but it won't blow things up. Using String(value) if value is a string will return a value.

excluded: false,
kqlQuery: '',
queryMatch: {
field,
value: value!,
value: String(value),
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Small risk 🐛 fix. I don't think we expect null/undefined but using String(null) or String(undefined will display null or undefined but it won't blow things up. Using String(value) if value is a string will return a value.

@@ -97,7 +97,7 @@ export const DraggableZeekElement = React.memo<{
) : (
<EuiToolTip data-test-subj="badge-tooltip" content={field}>
<Badge iconType="tag" color="hollow" title="">
{stringRenderer(value!)}
{stringRenderer(String(value))}
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Small risk 🐛 fix. I don't think we expect null/undefined but using String(null) or String(undefined will display null or undefined but it won't blow things up. Using String(value) if value is a string will return a value.

<UsernameWithAvatar
key={user.updatedBy === null ? undefined : user.updatedBy}
username={String(user.updatedBy)}
/>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

NOTE: Medium 🐛 fix. I don't think we expect user.updatedBy to be null but it can be either null | undefined according to its type. To be safe here I decided to check explicitly against null and then set it to undefined if it is null. Otherwise I fall back on using the regular value which is user.updatedBy.

The other way I could have fixed this was String(user.updatedBy) but since undefined can be valid key I decided to implement the fix this way.

@@ -271,13 +271,13 @@ export const EventsTrData = styled.div.attrs(({ className = '' }) => ({
const TIMELINE_EVENT_DETAILS_OFFSET = 40;

interface WidthProp {
width?: number;
width: number;
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Low risk type fix here as it seems to want it to be required and no where is it optionally being set to undefined or null so the typing and code is better to just use the expected type here.

@@ -74,7 +75,7 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface {

async deleteArtifact(id: string) {
// Ignoring the `id` not being in the type until we can refactor the types in endpoint.
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: zero risk as the @ts-ignore wasn't needed here and now we only turn off the no-non-null-assertion

@@ -237,7 +237,7 @@ const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
endDate: end,
entityType,
fields,
filterQuery: combinedQueries!.filterQuery,
filterQuery: combinedQueries?.filterQuery,
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Low risk 🐛 fix here since combinedQueries can be undefined and this is similar to the other areas of the code base we fixed the combinedQueries since it can be undefined and we don't want to blow up here.

@@ -217,7 +217,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
entityType,
excludeEcsData: true,
fields,
filterQuery: combinedQueries!.filterQuery,
filterQuery: combinedQueries?.filterQuery,
Copy link
Contributor Author

@FrankHassanabad FrankHassanabad Oct 7, 2021

Choose a reason for hiding this comment

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

NOTE: Low risk 🐛 fix here since combinedQueries can be undefined and this is similar to the other areas of the code base we fixed the combinedQueries since it can be undefined and we don't want to blow up here.

@FrankHassanabad
Copy link
Contributor Author

@elasticmachine merge upstream

@jonathan-buttner
Copy link
Contributor

Is it possible to disable the no-non-null-assertion linter by default for test files? I think there'd be less risk of having the ! in a test files since the test will fail anyway if the value is undefined.

Copy link
Contributor

@banderror banderror left a comment

Choose a reason for hiding this comment

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

That's a great linting improvement 🙌 Thank you @FrankHassanabad!

I'm not familiar with most of the code that has been changed in this PR, so I'm not sure if it's legitimate for me to approve the PR, but it all looks good at first glance. I see that most of the ! in production code is left with the corresponding eslint-disable comments, so the logic should stay the same.

Specific areas of code for timelines, endpoint integration, etc should probably be reviewed closely by the corresponding owners.

Left 2 minor comments.

@FrankHassanabad
Copy link
Contributor Author

FrankHassanabad commented Oct 12, 2021

Is it possible to disable the no-non-null-assertion linter by default for test files? I think there'd be less risk of having the ! in a test files since the test will fail anyway if the value is undefined.

Yep, totally possible. I did it by adding this chunk you should see in the PR now:

    {
      // typescript only for front and back end, but excludes the test files.
      // We use this section to add rules in which we do not want to apply to test files.
      // This should be a very small set as most linter rules are useful for tests as well.
      files: [
        'x-pack/plugins/security_solution/**/*.{ts,tsx}',
        'x-pack/plugins/timelines/**/*.{ts,tsx}',
      ],
      excludedFiles: [
        'x-pack/plugins/security_solution/**/*.{test,mock,test_helper}.{ts,tsx}',
        'x-pack/plugins/timelines/**/*.{test,mock,test_helper}.{ts,tsx}',
      ],
      rules: {
        '@typescript-eslint/no-non-null-assertion': 'error',
      },
    },

And moving the rule into there. Also I removed all the additional workaround code in the tests to make them mostly the same as they were before except for where I could remove ! for ? or remove ! completely. So 👍

@jonathan-buttner
Copy link
Contributor

Is it possible to disable the no-non-null-assertion linter by default for test files? I think there'd be less risk of having the ! in a test files since the test will fail anyway if the value is undefined.

Yep, totally possible. I did it by adding this chunk you should see in the PR now:

Thanks Frank!

@FrankHassanabad
Copy link
Contributor Author

@elasticmachine merge upstream

// This should be a very small set as most linter rules are useful for tests as well.
files: [
'x-pack/plugins/security_solution/**/*.{ts,tsx}',
'x-pack/plugins/timelines/**/*.{ts,tsx}',
Copy link
Contributor

Choose a reason for hiding this comment

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

@cnasikas @semd @jamster10 maybe we should do a follow up PR to add cases here as well?

Copy link
Member

Choose a reason for hiding this comment

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

Thanks, @jonathan-buttner! Let's do it!

Copy link
Member

Choose a reason for hiding this comment

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

I opened an issue to track it here: #115152

],
excludedFiles: [
'x-pack/plugins/security_solution/**/*.{test,mock,test_helper}.{ts,tsx}',
'x-pack/plugins/timelines/**/*.{test,mock,test_helper}.{ts,tsx}',
Copy link
Contributor

Choose a reason for hiding this comment

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

@FrankHassanabad @MadameSheema should we exclude cypress .spec files? I'm fine with leaving them as linted.

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'm fine leaving them as is.

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 4.6MB 4.6MB +318.0B
timelines 240.3KB 240.3KB +32.0B
total +350.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @FrankHassanabad

@FrankHassanabad FrankHassanabad merged commit ed9859a into elastic:master Oct 15, 2021
kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Oct 15, 2021
…assertion (TypeScript ! bang operator) (elastic#114375)

## Summary

Fixes: elastic#114535

**What this linter rule does:**
* Sets the [@typescript-eslint/no-non-null-assertion](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md) linter rule to become an error if seen.

If you try to use the `!` operator you get an error and nice helper message that tries to encourage better practices such as this one:

<img width="1635" alt="Screen Shot 2021-10-07 at 11 26 14 AM" src="https://user-images.githubusercontent.com/1151048/136474207-f38d3461-0af9-4cdc-885b-632cb49d8a24.png">

**Why are we deciding to set this linter rule?**
* Recommended from Kibana [styleguide](https://github.com/elastic/kibana/blob/master/STYLEGUIDE.mdx#avoid-non-null-assertions) for ~2 years now and still recommended.
* A lot of TypeScript has evolved and has operators such as `?` which can replace the `!` in most cases. Other cases can use a throw explicitly or other ways to manage this.
* Some types can change instead of using this operator and we should just change the types.
* TypeScript flows have improved and when we upgrade the linter will cause errors where we can remove the `!` operator which is 👍 better than leaving them when they're not needed anymore.
* Newer programmers and team members sometimes mistake it for the `?` when it is not the same thing.
* We have had past bugs and bugs recently because of these.
* It's easier to use the linter to find bugs than to rely on manual tests. 

**How did Frank fix all the 403 areas in which the linter goes off?**
* Anywhere I could remove the `!` operator without side effects or type script errors I just removed the `!` operator.
* Anywhere in test code where I could replace the code with a `?` or a `throw` I went through that route.
* Within the code areas (non test code) where I saw what looks like a simple bug that I could fix using a `?? []` or `?` operator or `String(value)` or fixing a simple type I would choose that route first. These areas I marked in the code review with the words `NOTE:` for anyone to look at.
* Within all other areas of the code and tests where anything looked more involved I just disabled the linter for that particular line. When in doubt I chose this route.

### Checklist

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
@kibanamachine
Copy link
Contributor

💚 Backport successful

Status Branch Result
7.x

This backport PR will be merged automatically after passing CI.

@FrankHassanabad FrankHassanabad deleted the avoid-non-null-assertions branch October 15, 2021 02:27
kibanamachine added a commit that referenced this pull request Oct 15, 2021
…assertion (TypeScript ! bang operator) (#114375) (#115126)

## Summary

Fixes: #114535

**What this linter rule does:**
* Sets the [@typescript-eslint/no-non-null-assertion](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md) linter rule to become an error if seen.

If you try to use the `!` operator you get an error and nice helper message that tries to encourage better practices such as this one:

<img width="1635" alt="Screen Shot 2021-10-07 at 11 26 14 AM" src="https://user-images.githubusercontent.com/1151048/136474207-f38d3461-0af9-4cdc-885b-632cb49d8a24.png">

**Why are we deciding to set this linter rule?**
* Recommended from Kibana [styleguide](https://github.com/elastic/kibana/blob/master/STYLEGUIDE.mdx#avoid-non-null-assertions) for ~2 years now and still recommended.
* A lot of TypeScript has evolved and has operators such as `?` which can replace the `!` in most cases. Other cases can use a throw explicitly or other ways to manage this.
* Some types can change instead of using this operator and we should just change the types.
* TypeScript flows have improved and when we upgrade the linter will cause errors where we can remove the `!` operator which is 👍 better than leaving them when they're not needed anymore.
* Newer programmers and team members sometimes mistake it for the `?` when it is not the same thing.
* We have had past bugs and bugs recently because of these.
* It's easier to use the linter to find bugs than to rely on manual tests. 

**How did Frank fix all the 403 areas in which the linter goes off?**
* Anywhere I could remove the `!` operator without side effects or type script errors I just removed the `!` operator.
* Anywhere in test code where I could replace the code with a `?` or a `throw` I went through that route.
* Within the code areas (non test code) where I saw what looks like a simple bug that I could fix using a `?? []` or `?` operator or `String(value)` or fixing a simple type I would choose that route first. These areas I marked in the code review with the words `NOTE:` for anyone to look at.
* Within all other areas of the code and tests where anything looked more involved I just disabled the linter for that particular line. When in doubt I chose this route.

### Checklist

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios

Co-authored-by: Frank Hassanabad <frank.hassanabad@elastic.co>
artem-shelkovnikov pushed a commit to artem-shelkovnikov/kibana that referenced this pull request Oct 20, 2021
…assertion (TypeScript ! bang operator) (elastic#114375)

## Summary

Fixes: elastic#114535

**What this linter rule does:**
* Sets the [@typescript-eslint/no-non-null-assertion](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md) linter rule to become an error if seen.

If you try to use the `!` operator you get an error and nice helper message that tries to encourage better practices such as this one:

<img width="1635" alt="Screen Shot 2021-10-07 at 11 26 14 AM" src="https://user-images.githubusercontent.com/1151048/136474207-f38d3461-0af9-4cdc-885b-632cb49d8a24.png">

**Why are we deciding to set this linter rule?**
* Recommended from Kibana [styleguide](https://github.com/elastic/kibana/blob/master/STYLEGUIDE.mdx#avoid-non-null-assertions) for ~2 years now and still recommended.
* A lot of TypeScript has evolved and has operators such as `?` which can replace the `!` in most cases. Other cases can use a throw explicitly or other ways to manage this.
* Some types can change instead of using this operator and we should just change the types.
* TypeScript flows have improved and when we upgrade the linter will cause errors where we can remove the `!` operator which is 👍 better than leaving them when they're not needed anymore.
* Newer programmers and team members sometimes mistake it for the `?` when it is not the same thing.
* We have had past bugs and bugs recently because of these.
* It's easier to use the linter to find bugs than to rely on manual tests. 

**How did Frank fix all the 403 areas in which the linter goes off?**
* Anywhere I could remove the `!` operator without side effects or type script errors I just removed the `!` operator.
* Anywhere in test code where I could replace the code with a `?` or a `throw` I went through that route.
* Within the code areas (non test code) where I saw what looks like a simple bug that I could fix using a `?? []` or `?` operator or `String(value)` or fixing a simple type I would choose that route first. These areas I marked in the code review with the words `NOTE:` for anyone to look at.
* Within all other areas of the code and tests where anything looked more involved I just disabled the linter for that particular line. When in doubt I chose this route.

### Checklist

- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Deprecated - use backport:version if exact versions are needed release_note:skip Skip the PR/issue when compiling release notes Team:Security Solution Platform Security Solution Platform Team v7.16.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Security Solution][Platform] - Clean up es-lint disables (require-atomic-updates)
6 participants