Skip to content

Commit e3c99fb

Browse files
authored
fix: Replace raw in operator checks with keyof-validated type guards (#4306)
1 parent f5bcafe commit e3c99fb

File tree

10 files changed

+42
-26
lines changed

10 files changed

+42
-26
lines changed

src/autosuggest/autosuggest-option.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ const AutosuggestOption = (
108108
ref: React.Ref<HTMLDivElement>
109109
) => {
110110
const baseProps = getBaseProps(rest);
111-
const useEntered = option.type === 'use-entered';
112-
const isParent = option.type === 'parent';
113-
const isChild = option.type === 'child';
111+
const useEntered = option?.type === 'use-entered';
112+
const isParent = option?.type === 'parent';
113+
const isChild = option?.type === 'child';
114114
const { throughIndex, inGroupIndex, groupIndex } = getTestOptionIndexes(option) || {};
115115

116116
let optionContent;

src/autosuggest/utils/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AutosuggestItem } from '../interfaces';
55
type SearchableFields = 'value' | 'label' | 'description' | 'labelTag';
66
type SearchableTagFields = 'tags' | 'filteringTags';
77

8-
const isGroup = (option: AutosuggestItem) => option.type === 'parent';
8+
const isGroup = (option: AutosuggestItem) => option?.type === 'parent';
99

1010
const popLastGroup = (options: AutosuggestItem[]) => {
1111
if (options.length) {

src/button-dropdown/internal.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
GeneratedAnalyticsMetadataButtonDropdownCollapse,
2323
GeneratedAnalyticsMetadataButtonDropdownExpand,
2424
} from './analytics-metadata/interfaces.js';
25-
import { ButtonDropdownProps, InternalButtonDropdownProps } from './interfaces';
25+
import { ButtonDropdownProps, InternalButtonDropdownProps, InternalItem } from './interfaces';
2626
import ItemsList from './items-list';
2727
import { useButtonDropdown } from './utils/use-button-dropdown';
2828
import { isLinkItem } from './utils/utils.js';
@@ -180,18 +180,20 @@ const InternalButtonDropdown = React.forwardRef(
180180
const triggerId = useUniqueId('awsui-button-dropdown__trigger');
181181

182182
const triggerHasBadge = () => {
183+
const groupKey: keyof ButtonDropdownProps.ItemGroup = 'items';
183184
const flatItems = items.flatMap(item => {
184-
if ('items' in item) {
185+
if (groupKey in item) {
185186
return item.items;
186187
}
187188
return item;
188189
});
189190

191+
const badgeKey: keyof InternalItem = 'badge';
190192
return (
191193
variant === 'icon' &&
192194
!!flatItems?.find(item => {
193-
if ('badge' in item) {
194-
return item.badge;
195+
if (badgeKey in item) {
196+
return (item as InternalItem).badge;
195197
}
196198
})
197199
);

src/button-group/item-element.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ const ItemElement = forwardRef(
103103
};
104104

105105
const onClickHandler = (event: CustomEvent<ButtonGroupProps.ItemClickDetails>) => {
106-
const hasPopoverFeedback = 'popoverFeedback' in item && item.popoverFeedback;
106+
const feedbackKey: keyof ButtonGroupProps.IconButton = 'popoverFeedback';
107+
const hasPopoverFeedback = feedbackKey in item && item.popoverFeedback;
107108

108109
if (hasPopoverFeedback) {
109110
setTooltip({ item: item.id, feedback: true });

src/flashbar/collapsible-flashbar.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
getFlashTypeCount,
3636
getItemColor,
3737
getVisibleCollapsedItems,
38+
isRefObject,
39+
isStackableItem,
3840
StackableItem,
3941
} from './utils';
4042

@@ -214,11 +216,11 @@ export default function CollapsibleFlashbar({ items, style, ...restProps }: Inte
214216
// we need to use different, more custom and more controlled animations.
215217
const hasEntered = (item: StackableItem | FlashbarProps.MessageDefinition) =>
216218
enteringItems.some(_item => _item.id && _item.id === item.id);
217-
const hasLeft = (item: StackableItem | FlashbarProps.MessageDefinition) => !('expandedIndex' in item);
219+
const hasLeft = (item: StackableItem | FlashbarProps.MessageDefinition) => !isStackableItem(item);
218220
const hasEnteredOrLeft = (item: StackableItem | FlashbarProps.MessageDefinition) => hasEntered(item) || hasLeft(item);
219221

220222
const showInnerContent = (item: StackableItem | FlashbarProps.MessageDefinition) =>
221-
isFlashbarStackExpanded || hasLeft(item) || ('expandedIndex' in item && item.expandedIndex === 0);
223+
isFlashbarStackExpanded || hasLeft(item) || (isStackableItem(item) && item.expandedIndex === 0);
222224

223225
const shouldUseStandardAnimation = (item: StackableItem, index: number) => index === 0 && hasEnteredOrLeft(item);
224226

@@ -301,11 +303,7 @@ export default function CollapsibleFlashbar({ items, style, ...restProps }: Inte
301303
if (shouldUseStandardAnimation(item, index) && transitionRootElement) {
302304
if (typeof transitionRootElement === 'function') {
303305
transitionRootElement(el);
304-
} else if (
305-
transitionRootElement &&
306-
typeof transitionRootElement === 'object' &&
307-
'current' in transitionRootElement
308-
) {
306+
} else if (isRefObject(transitionRootElement)) {
309307
(transitionRootElement as React.MutableRefObject<HTMLDivElement | null>).current = el;
310308
}
311309
}

src/flashbar/non-collapsible-flashbar.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useFlashbar, useFlashbarVisibility } from './common';
1313
import { TIMEOUT_FOR_ENTERING_ANIMATION } from './constant';
1414
import { Flash } from './flash';
1515
import { FlashbarProps, InternalFlashbarProps } from './interfaces';
16+
import { isRefObject } from './utils';
1617

1718
import styles from './styles.css.js';
1819

@@ -121,11 +122,7 @@ export default function NonCollapsibleFlashbar({ items, i18nStrings, style, ...r
121122
// If there's a transition root element ref, update it too
122123
if (transitionRootElement && typeof transitionRootElement === 'function') {
123124
transitionRootElement(el);
124-
} else if (
125-
transitionRootElement &&
126-
typeof transitionRootElement === 'object' &&
127-
'current' in transitionRootElement
128-
) {
125+
} else if (transitionRootElement && isRefObject(transitionRootElement)) {
129126
(transitionRootElement as React.MutableRefObject<HTMLDivElement | null>).current = el;
130127
}
131128
}}

src/flashbar/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3+
import React from 'react';
4+
35
import { IconProps } from '../icon/interfaces';
46
import { FlashbarProps } from './interfaces';
57

@@ -13,6 +15,16 @@ export interface StackableItem extends FlashbarProps.MessageDefinition {
1315
collapsedIndex?: number;
1416
}
1517

18+
export function isStackableItem(item: StackableItem | FlashbarProps.MessageDefinition): item is StackableItem {
19+
const key: keyof StackableItem = 'expandedIndex';
20+
return key in item;
21+
}
22+
23+
export function isRefObject<T>(ref: unknown): ref is React.RefObject<T> {
24+
const key: keyof React.RefObject<T> = 'current';
25+
return ref !== null && typeof ref === 'object' && key in ref;
26+
}
27+
1628
const typesToColors: Record<FlashbarProps.Type, string> = {
1729
error: 'red',
1830
info: 'blue',

src/mixed-line-bar-chart/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,15 @@ export const getKeyValue = (key: ChartDataTypes) => (key instanceof Date ? key.g
100100
export function isYThreshold<T>(
101101
series: MixedLineBarChartProps.ChartSeries<T>
102102
): series is MixedLineBarChartProps.YThresholdSeries {
103-
return series.type === 'threshold' && 'y' in series;
103+
const key: keyof MixedLineBarChartProps.YThresholdSeries = 'y';
104+
return series.type === 'threshold' && key in series;
104105
}
105106

106107
export function isXThreshold<T>(
107108
series: MixedLineBarChartProps.ChartSeries<T>
108109
): series is MixedLineBarChartProps.XThresholdSeries<T> {
109-
return series.type === 'threshold' && 'x' in series;
110+
const key: keyof MixedLineBarChartProps.XThresholdSeries<T> = 'x';
111+
return series.type === 'threshold' && key in series;
110112
}
111113

112114
export function isDataSeries<T>(

src/side-navigation/util.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@ export function checkDuplicateHrefs(items: ReadonlyArray<SideNavigationProps.Ite
5050
const item = queue.shift()!;
5151

5252
// Check duplicated hrefs
53-
if ('href' in item) {
53+
const hrefKey: keyof SideNavigationProps.Link = 'href';
54+
if (hrefKey in item) {
5455
if (hrefs.has(item.href)) {
5556
warnOnce('SideNavigation', `duplicate href in "${item.text}": ${item.href}`);
5657
}
5758
hrefs.add(item.href);
5859
}
5960

60-
if ('items' in item) {
61+
const itemsKey: keyof SideNavigationProps.Section = 'items';
62+
if (itemsKey in item) {
6163
queue.push(...item.items);
6264
}
6365
}

src/top-navigation/parts/utility.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React from 'react';
44
import clsx from 'clsx';
55

66
import { InternalButton } from '../../button/internal';
7+
import { ButtonDropdownProps } from '../../button-dropdown/interfaces';
78
import { isLinkItem } from '../../button-dropdown/utils/utils';
89
import InternalIcon from '../../icon/internal';
910
import MenuDropdown, { MenuDropdownProps } from '../../internal/components/menu-dropdown';
@@ -133,7 +134,8 @@ function checkSafeUrlRecursively(itemOrGroup: MenuDropdownProps['items']) {
133134
checkSafeUrl('TopNavigation', item.href);
134135
}
135136

136-
if ('items' in item) {
137+
const itemsKey: keyof ButtonDropdownProps.ItemGroup = 'items';
138+
if (itemsKey in item) {
137139
checkSafeUrlRecursively(item.items);
138140
}
139141
}

0 commit comments

Comments
 (0)