chore(eslint): add strict-boolean-expressions rule (#25768)

This commit is contained in:
Amanda Johnston
2022-08-23 11:50:02 -05:00
committed by GitHub
parent d75386baef
commit ae6aa0cb8e
38 changed files with 101 additions and 82 deletions

View File

@ -1,2 +1,5 @@
src/components/slides/swiper/swiper.bundle.js
src/components.d.ts
**/test/**/*.spec.ts
**/test/**/*.spec.tsx
**/test/**/e2e.ts

View File

@ -13,7 +13,8 @@ module.exports = {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
"sourceType": "module",
"project": "tsconfig.json"
},
"plugins": [
"@typescript-eslint"
@ -29,6 +30,14 @@ module.exports = {
],
"no-useless-catch": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"no-case-declarations": "off"
"no-case-declarations": "off",
"@typescript-eslint/strict-boolean-expressions": [
"warn",
{
"allowNullableBoolean": true,
"allowNullableString": true,
"allowAny": true
}
]
}
};

View File

@ -173,7 +173,7 @@ export class AccordionGroup implements ComponentInterface {
* to the array.
*/
if (multiple) {
const groupValue = value || [];
const groupValue = value ?? [];
const processedValue = Array.isArray(groupValue) ? groupValue : [groupValue];
const valueExists = processedValue.find((v) => v === accordionValue);
if (valueExists === undefined && accordionValue !== undefined) {
@ -188,7 +188,7 @@ export class AccordionGroup implements ComponentInterface {
* out of the values array or unset the value.
*/
if (multiple) {
const groupValue = value || [];
const groupValue = value ?? [];
const processedValue = Array.isArray(groupValue) ? groupValue : [groupValue];
this.value = processedValue.filter((v) => v !== accordionValue);
} else {

View File

@ -157,11 +157,9 @@ export class Accordion implements ComponentInterface {
}
// This is not defined in unit tests
const ionItem =
slot.assignedElements &&
(slot.assignedElements().find((el) => el.tagName === 'ION-ITEM') as HTMLIonItemElement | undefined);
if (slot.assignedElements === undefined) return;
return ionItem;
return slot.assignedElements().find((el) => el.tagName === 'ION-ITEM') as HTMLIonItemElement | undefined;
};
private setAria = (expanded = false) => {

View File

@ -241,7 +241,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
handler: i.handler,
min: i.min,
max: i.max,
cssClass: i.cssClass || '',
cssClass: i.cssClass ?? '',
attributes: i.attributes || {},
tabindex: i.type === 'radio' && i !== focusable ? -1 : 0,
} as AlertInput)

View File

@ -182,7 +182,7 @@ Please upvote https://github.com/ionic-team/ionic-framework/issues/25668 if you
* Both ion-datetime and ion-datetime-button default
* to today's date and time if no value is set.
*/
const parsedDatetime = parseDate(value || getToday()) as DatetimeParts;
const parsedDatetime = parseDate(value ?? getToday()) as DatetimeParts;
const use24Hour = is24Hour(locale, hourCycle);
// TODO(FW-1865) - Remove once FW-1831 is fixed.

View File

@ -1150,8 +1150,8 @@ export class Datetime implements ComponentInterface {
}
private processValue = (value?: string | string[] | null) => {
this.highlightActiveParts = !!value;
let valueToProcess = parseDate(value || getToday());
this.highlightActiveParts = value !== null && value !== undefined;
let valueToProcess = parseDate(value ?? getToday());
const { minParts, maxParts, multiple } = this;
if (!multiple && Array.isArray(value)) {
@ -1438,7 +1438,8 @@ export class Datetime implements ComponentInterface {
* If we have selected a day already, then default the column
* to that value. Otherwise, default it to today.
*/
const todayString = workingParts.day
const todayString =
workingParts.day !== null
? `${workingParts.year}-${workingParts.month}-${workingParts.day}`
: `${todayParts.year}-${todayParts.month}-${todayParts.day}`;
@ -1566,7 +1567,7 @@ export class Datetime implements ComponentInterface {
class="day-column"
color={this.color}
items={days}
value={(workingParts.day || this.todayParts.day) ?? undefined}
value={(workingParts.day !== null ? workingParts.day : this.todayParts.day) ?? undefined}
onIonChange={(ev: CustomEvent) => {
// TODO(FW-1823) Remove this when iOS 14 support is dropped.
// Due to a Safari 14 issue we need to destroy
@ -1689,12 +1690,14 @@ export class Datetime implements ComponentInterface {
return [];
}
const valueIsDefined = this.value !== null && this.value !== undefined;
const { hoursData, minutesData, dayPeriodData } = getTimeColumnsData(
this.locale,
this.workingParts,
this.hourCycle,
this.value ? this.minParts : undefined,
this.value ? this.maxParts : undefined,
valueIsDefined ? this.minParts : undefined,
valueIsDefined ? this.maxParts : undefined,
this.parsedHourValues,
this.parsedMinuteValues
);

View File

@ -14,12 +14,12 @@ export const isSameDay = (baseParts: DatetimeParts, compareParts: DatetimeParts)
* Returns true is the selected day is before the reference day.
*/
export const isBefore = (baseParts: DatetimeParts, compareParts: DatetimeParts) => {
return (
return !!(
baseParts.year < compareParts.year ||
(baseParts.year === compareParts.year && baseParts.month < compareParts.month) ||
(baseParts.year === compareParts.year &&
baseParts.month === compareParts.month &&
baseParts.day &&
baseParts.day !== null &&
baseParts.day < compareParts.day!)
);
};
@ -28,12 +28,12 @@ export const isBefore = (baseParts: DatetimeParts, compareParts: DatetimeParts)
* Returns true is the selected day is after the reference day.
*/
export const isAfter = (baseParts: DatetimeParts, compareParts: DatetimeParts) => {
return (
return !!(
baseParts.year > compareParts.year ||
(baseParts.year === compareParts.year && baseParts.month > compareParts.month) ||
(baseParts.year === compareParts.year &&
baseParts.month === compareParts.month &&
baseParts.day &&
baseParts.day !== null &&
baseParts.day > compareParts.day!)
);
};
@ -45,7 +45,7 @@ export const warnIfValueOutOfBounds = (
) => {
const valueArray = Array.isArray(value) ? value : [value];
for (const val of valueArray) {
if ((min && isBefore(val, min)) || (max && isAfter(val, max))) {
if ((min !== undefined && isBefore(val, min)) || (max !== undefined && isAfter(val, max))) {
printIonWarning(
'The value provided to ion-datetime is out of bounds.\n\n' +
`Min: ${JSON.stringify(min)}\n` +

View File

@ -353,8 +353,14 @@ export const getDayColumnData = (
* Otherwise, fallback to the max/min days in a month.
*/
const numDaysInMonth = getNumDaysInMonth(month, year);
const maxDay = maxParts?.day && maxParts.year === year && maxParts.month === month ? maxParts.day : numDaysInMonth;
const minDay = minParts?.day && minParts.year === year && minParts.month === month ? minParts.day : 1;
const maxDay =
maxParts?.day !== null && maxParts?.day !== undefined && maxParts.year === year && maxParts.month === month
? maxParts.day
: numDaysInMonth;
const minDay =
minParts?.day !== null && minParts?.day !== undefined && minParts.year === year && minParts.month === month
? minParts.day
: 1;
if (dayValues !== undefined) {
let processedDays = dayValues;
@ -394,8 +400,8 @@ export const getYearColumnData = (
}
} else {
const { year } = refParts;
const maxYear = maxParts?.year || year;
const minYear = minParts?.year || year - 100;
const maxYear = maxParts?.year ?? year;
const minYear = minParts?.year ?? year - 100;
for (let i = maxYear; i >= minYear; i--) {
processedYears.push(i);

View File

@ -129,7 +129,8 @@ export const getLocalizedDateTime = (
refParts: DatetimeParts,
options: Intl.DateTimeFormatOptions
): string => {
const timeString = !!refParts.hour && !!refParts.minute ? ` ${refParts.hour}:${refParts.minute}` : '';
const timeString =
refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
return new Intl.DateTimeFormat(locale, { ...options, timeZone: 'UTC' }).format(date);
};

View File

@ -40,17 +40,16 @@ export const createHeaderIndex = (headerEl: HTMLElement | undefined): HeaderInde
return {
el: headerEl,
toolbars:
Array.from(toolbars).map((toolbar: any) => {
toolbars: Array.from(toolbars).map((toolbar: any) => {
const ionTitleEl = toolbar.querySelector('ion-title');
return {
el: toolbar,
background: toolbar.shadowRoot!.querySelector('.toolbar-background'),
ionTitleEl,
innerTitleEl: ionTitleEl ? ionTitleEl.shadowRoot!.querySelector('.toolbar-title') : null,
ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')) || [],
ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')),
} as ToolbarIndex;
}) || [],
}),
} as HeaderIndex;
};

View File

@ -58,7 +58,9 @@ export class InfiniteScrollContent implements ComponentInterface {
<ion-spinner name={this.loadingSpinner} />
</div>
)}
{this.loadingText && <div class="infinite-loading-text" innerHTML={sanitizeDOMString(this.loadingText)} />}
{this.loadingText !== undefined && (
<div class="infinite-loading-text" innerHTML={sanitizeDOMString(this.loadingText)} />
)}
</div>
</Host>
);

View File

@ -210,7 +210,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
// appear as clickable to screen readers
// https://github.com/ionic-team/ionic-framework/issues/22011
const input = this.getFirstInput();
if (input && !this.clickListener) {
if (input !== undefined && !this.clickListener) {
this.clickListener = (ev: Event) => this.delegateFocus(ev, input);
this.el.addEventListener('click', this.clickListener);
}
@ -218,7 +218,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
disconnectedCallback() {
const input = this.getFirstInput();
if (input && this.clickListener) {
if (input !== undefined && this.clickListener) {
this.el.removeEventListener('click', this.clickListener);
this.clickListener = undefined;
}

View File

@ -214,7 +214,7 @@ export class Loading implements ComponentInterface, OverlayInterface {
</div>
)}
{message && <div class="loading-content" innerHTML={sanitizeDOMString(message)}></div>}
{message !== undefined && <div class="loading-content" innerHTML={sanitizeDOMString(message)}></div>}
</div>
<div tabindex="0"></div>

View File

@ -704,7 +704,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, {
presentingEl: this.presentingElement,
currentBreakpoint: this.currentBreakpoint || this.initialBreakpoint,
currentBreakpoint: this.currentBreakpoint !== undefined || this.initialBreakpoint,
backdropBreakpoint: this.backdropBreakpoint,
});

View File

@ -794,7 +794,7 @@ export class Nav implements NavOutlet {
destroyQueue = [];
for (let i = removeStart; i < removeStart + removeCount; i++) {
const view = this.views[i];
if (view && view !== enteringView && view !== leavingView) {
if (view !== undefined && view !== enteringView && view !== leavingView) {
destroyQueue.push(view);
}
}

View File

@ -351,7 +351,7 @@ export class PickerColumnCmp implements ComponentInterface {
return;
}
const selectedIndex = clamp(min, this.col.selectedIndex || 0, max);
const selectedIndex = clamp(min, this.col.selectedIndex ?? 0, max);
if (this.col.prevSelected !== selectedIndex || forceRefresh) {
const y = selectedIndex * this.optHeight * -1;
this.velocity = 0;

View File

@ -320,7 +320,7 @@ export class Range implements ComponentInterface {
this.ionKnobMoveEnd.emit({ value: ensureValueInBounds(this.value) });
};
private getValue(): RangeValue {
const value = this.value || 0;
const value = this.value ?? 0;
if (this.dualKnobs) {
if (typeof value === 'object') {
return value;

View File

@ -93,7 +93,7 @@ export class RefresherContent implements ComponentInterface {
<ion-icon icon={this.pullingIcon} lazy={false}></ion-icon>
</div>
)}
{this.pullingText && (
{this.pullingText !== undefined && (
<div class="refresher-pulling-text" innerHTML={sanitizeDOMString(this.pullingText)}></div>
)}
</div>
@ -103,7 +103,7 @@ export class RefresherContent implements ComponentInterface {
<ion-spinner name={this.refreshingSpinner}></ion-spinner>
</div>
)}
{this.refreshingText && (
{this.refreshingText !== undefined && (
<div class="refresher-refreshing-text" innerHTML={sanitizeDOMString(this.refreshingText)}></div>
)}
</div>

View File

@ -577,7 +577,7 @@ export class Refresher implements ComponentInterface {
// best to do any DOM read/writes only when absolutely necessary
// if multi-touch then get out immediately
const ev = detail.event as TouchEvent;
if (ev.touches && ev.touches.length > 1) {
if (ev.touches !== undefined && ev.touches.length > 1) {
return;
}

View File

@ -13,9 +13,7 @@ export const moveReorderItem = async (
) => {
try {
const reorderItem =
parentSelectors && parentSelectors.length > 0
? await (await queryDeep(page, ...parentSelectors)).$(id)
: await page.$(id);
parentSelectors.length > 0 ? await (await queryDeep(page, ...parentSelectors)).$(id) : await page.$(id);
if (!reorderItem) {
throw new Error('Reorder Item is undefined');

View File

@ -396,7 +396,7 @@ export class Searchbar implements ComponentInterface {
const cancelButton = (this.el.shadowRoot || this.el).querySelector('.searchbar-cancel-button') as HTMLElement;
const shouldShowCancel = this.shouldShowCancelButton();
if (cancelButton && shouldShowCancel !== this.isCancelVisible) {
if (cancelButton !== null && shouldShowCancel !== this.isCancelVisible) {
const cancelStyle = cancelButton.style;
this.isCancelVisible = shouldShowCancel;
if (shouldShowCancel) {

View File

@ -447,9 +447,9 @@ export class Segment implements ComponentInterface {
case 'last':
return buttons[buttons.length - 1];
case 'next':
return buttons[currIndex + 1] || buttons[0];
return buttons[currIndex + 1] ?? buttons[0];
case 'previous':
return buttons[currIndex - 1] || buttons[buttons.length - 1];
return buttons[currIndex - 1] ?? buttons[buttons.length - 1];
default:
return null;
}

View File

@ -50,7 +50,7 @@ export class Spinner implements ComponentInterface {
const self = this;
const mode = getIonMode(self);
const spinnerName = self.getName();
const spinner = SPINNERS[spinnerName] || SPINNERS['lines'];
const spinner = SPINNERS[spinnerName] ?? SPINNERS['lines'];
const duration = typeof self.duration === 'number' && self.duration > 10 ? self.duration : spinner.dur;
const svgs: any[] = [];

View File

@ -97,7 +97,7 @@ export const doRender = (
nodeRender(child, cell, i);
} else {
const newChild = createNode(el, cell.type);
child = nodeRender(newChild, cell, i) || newChild;
child = nodeRender(newChild, cell, i) ?? newChild;
child.classList.add('virtual-item');
el.appendChild(child!);
}
@ -132,7 +132,7 @@ export const doRender = (
const createNode = (el: HTMLElement, type: CellType): HTMLElement | null => {
const template = getTemplate(el, type);
if (template && el.ownerDocument) {
if (template && el.ownerDocument !== null) {
return el.ownerDocument.importNode(template.content, true).children[0] as HTMLElement;
}
return null;

View File

@ -306,7 +306,7 @@ export class VirtualScroll implements ComponentInterface {
const { contentEl, scrollEl, el } = this;
let topOffset = 0;
let node: HTMLElement | null = el;
while (node && node !== contentEl) {
while (node !== null && node !== contentEl) {
topOffset += node.offsetTop;
node = node.offsetParent as HTMLElement;
}
@ -386,7 +386,7 @@ export class VirtualScroll implements ComponentInterface {
}
private updateState() {
const shouldEnable = !!(this.scrollEl && this.cells);
const shouldEnable = !!(this.scrollEl && this.cells.length > 0);
if (shouldEnable !== this.isEnabled) {
this.enableScrollEvents(shouldEnable);
if (shouldEnable) {

View File

@ -131,7 +131,7 @@ export const createKeyframeStylesheet = (
return existingStylesheet;
}
const stylesheet = (element.ownerDocument || document).createElement('style');
const stylesheet = (element.ownerDocument ?? document).createElement('style');
stylesheet.id = keyframeName;
stylesheet.textContent = `@${keyframePrefix}keyframes ${keyframeName} { ${keyframeRules} } @${keyframePrefix}keyframes ${keyframeName}-alt { ${keyframeRules} }`;

View File

@ -13,7 +13,7 @@ export const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host';
*/
const ION_CONTENT_SELECTOR = `${ION_CONTENT_ELEMENT_SELECTOR}, ${ION_CONTENT_CLASS_SELECTOR}`;
export const isIonContent = (el: Element) => el && el.tagName === ION_CONTENT_TAG_NAME;
export const isIonContent = (el: Element) => el.tagName === ION_CONTENT_TAG_NAME;
/**
* Waits for the element host fully initialize before

View File

@ -39,7 +39,7 @@ export const startFocusVisible = (rootEl?: HTMLElement) => {
}
};
const onFocusin = (ev: Event) => {
if (keyboardMode && ev.composedPath) {
if (keyboardMode && ev.composedPath !== undefined) {
const toFocus = ev.composedPath().filter((el: any) => {
if (el.classList) {
return el.classList.contains(ION_FOCUSABLE);

View File

@ -9,7 +9,7 @@ class GestureController {
* Creates a gesture delegate based on the GestureConfig passed
*/
createGesture(config: GestureConfig): GestureDelegate {
return new GestureDelegate(this, this.newID(), config.name, config.priority || 0, !!config.disableScroll);
return new GestureDelegate(this, this.newID(), config.name, config.priority ?? 0, !!config.disableScroll);
}
/**

View File

@ -119,7 +119,7 @@ export const createGesture = (config: GestureConfig): Gesture => {
};
const tryToCapturePan = (): boolean => {
if (gesture && !gesture.capture()) {
if (!gesture.capture()) {
return false;
}
hasCapturedPan = true;

View File

@ -8,7 +8,7 @@ export interface ScrollData {
}
export const getScrollData = (componentEl: HTMLElement, contentEl: HTMLElement, keyboardHeight: number): ScrollData => {
const itemEl = (componentEl.closest('ion-item,[ion-item]') as HTMLElement) || componentEl;
const itemEl = (componentEl.closest('ion-item,[ion-item]') as HTMLElement) ?? componentEl;
return calcScrollData(
itemEl.getBoundingClientRect(),
contentEl.getBoundingClientRect(),

View File

@ -104,10 +104,10 @@ export const startTapClick = (config: Config) => {
const addActivated = (el: HTMLElement, x: number, y: number) => {
lastActivated = Date.now();
el.classList.add(ACTIVATED);
if (!useRippleEffect) return;
const rippleEffect = useRippleEffect && getRippleEffect(el);
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
if (rippleEffect && rippleEffect.addRipple) {
const rippleEffect = getRippleEffect(el);
if (rippleEffect !== null) {
removeRipple();
activeRipple = rippleEffect.addRipple(x, y);
}
@ -164,7 +164,7 @@ export const startTapClick = (config: Config) => {
};
const getActivatableTarget = (ev: UIEvent): any => {
if (ev.composedPath) {
if (ev.composedPath !== undefined) {
/**
* composedPath returns EventTarget[]. However,
* objects other than Element can be targets too.

View File

@ -1,7 +1,7 @@
import type { EventSpy } from '../page/event-spy';
export function toHaveReceivedEvent(eventSpy: EventSpy) {
if (!eventSpy) {
if (eventSpy === undefined || eventSpy === null) {
return {
message: () => `expected spy to have received event, but it was not defined`,
pass: false,

View File

@ -4,7 +4,7 @@ import deepEqual from 'fast-deep-equal';
import type { EventSpy } from '../page/event-spy';
export function toHaveReceivedEventDetail(eventSpy: EventSpy, eventDetail: any) {
if (!eventSpy) {
if (eventSpy === null || eventSpy === undefined) {
return {
message: () => `toHaveReceivedEventDetail event spy is null`,
pass: false,
@ -26,7 +26,7 @@ export function toHaveReceivedEventDetail(eventSpy: EventSpy, eventDetail: any)
};
}
if (!eventSpy.lastEvent) {
if (eventSpy.lastEvent === null || eventSpy.lastEvent === undefined) {
return {
message: () => `event "${eventSpy.eventName}" was not received`,
pass: false,

View File

@ -27,11 +27,11 @@ export class EventSpy {
}
get firstEvent() {
return this.events[0] || null;
return this.events[0] ?? null;
}
get lastEvent() {
return this.events[this.events.length - 1] || null;
return this.events[this.events.length - 1] ?? null;
}
public next() {
@ -39,7 +39,7 @@ export class EventSpy {
this.cursor++;
const next = this.events[cursor];
if (next) {
if (next !== undefined) {
return Promise.resolve(next);
} else {
/**

View File

@ -287,12 +287,12 @@ export const iosTransitionAnimation = (navEl: HTMLElement, opts: TransitionOptio
rootAnimation
.addElement(enteringEl)
.duration(opts.duration || DURATION)
.duration((opts.duration ?? 0) || DURATION)
.easing(opts.easing || EASING)
.fill('both')
.beforeRemoveClass('ion-page-invisible');
if (leavingEl && navEl) {
if (leavingEl && navEl !== null && navEl !== undefined) {
const navDecorAnimation = createAnimation();
navDecorAnimation.addElement(navEl);
rootAnimation.addAnimation(navDecorAnimation);

View File

@ -19,10 +19,10 @@ export const mdTransitionAnimation = (_: HTMLElement, opts: TransitionOptions):
// animate the component itself
if (backDirection) {
rootTransition.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
rootTransition.duration((opts.duration ?? 0) || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
} else {
rootTransition
.duration(opts.duration || 280)
.duration((opts.duration ?? 0) || 280)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.fromTo('transform', `translateY(${OFF_BOTTOM})`, `translateY(${CENTER})`)
.fromTo('opacity', 0.01, 1);
@ -38,7 +38,7 @@ export const mdTransitionAnimation = (_: HTMLElement, opts: TransitionOptions):
// setup leaving view
if (leavingEl && backDirection) {
// leaving content
rootTransition.duration(opts.duration || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
rootTransition.duration((opts.duration ?? 0) || 200).easing('cubic-bezier(0.47,0,0.745,0.715)');
const leavingPage = createAnimation();
leavingPage