From 908256aacab10e189b04b7471cd7bf813723d76a Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 16 Aug 2021 21:19:47 +0300 Subject: [PATCH] update lint rules, fix lint errors --- .eslintrc.json | 52 ++++++++++++- .../circle-button/circle-button.component.ts | 10 +-- .../icon-button/icon-button.component.ts | 2 +- src/lib/common-ui.module.ts | 14 ++-- src/lib/filtering/filter-utils.ts | 75 ++++++++++--------- src/lib/filtering/filter.service.ts | 15 ++-- .../filtering/models/filter-group.model.ts | 8 +- .../editable-input.component.ts | 4 +- .../round-checkbox.component.ts | 2 +- src/lib/search/search.service.ts | 6 +- .../sorting/models/sorting-option.model.ts | 2 +- src/lib/sorting/models/sorting-order.type.ts | 10 +-- src/lib/sorting/sort-by.pipe.ts | 2 +- src/lib/sorting/sorting.service.ts | 4 +- src/lib/tables/entities.service.ts | 12 +-- src/lib/tables/listing-component.directive.ts | 10 +-- .../models/table-column-config.model.ts | 2 +- src/lib/tables/sync-width.directive.ts | 28 ++++--- .../table-column-name.component.ts | 4 +- .../table-header/table-header.component.ts | 4 +- src/lib/utils/auto-unsubscribe.directive.ts | 4 +- src/lib/utils/decorators/bind.decorator.ts | 12 +-- .../utils/decorators/debounce.decorator.ts | 10 +-- .../utils/decorators/required.decorator.ts | 4 +- src/lib/utils/types/utility-types.ts | 10 ++- src/test-setup.ts | 1 + 26 files changed, 184 insertions(+), 123 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1393c66..6b93cff 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,18 @@ "overrides": [ { "files": ["*.ts"], - "extends": ["plugin:@nrwl/nx/angular", "plugin:@angular-eslint/template/process-inline-templates"], + "extends": [ + "plugin:@nrwl/nx/angular", + "plugin:@angular-eslint/template/process-inline-templates", + "plugin:@angular-eslint/recommended", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@angular-eslint/recommended--extra", + "airbnb-typescript/base", + "prettier", + "plugin:prettier/recommended" + ], "parserOptions": { "project": ["libs/common-ui/tsconfig.*?.json"] }, @@ -25,13 +36,48 @@ "style": "kebab-case" } ], - "no-param-reassign": "error" + "@angular-eslint/prefer-on-push-component-change-detection": "error", + "@angular-eslint/use-lifecycle-interface": "error", + "@angular-eslint/no-input-prefix": "error", + "@angular-eslint/no-input-rename": "error", + "@angular-eslint/no-output-on-prefix": "error", + "@angular-eslint/no-output-rename": "error", + "@angular-eslint/prefer-output-readonly": "error", + "@typescript-eslint/unbound-method": "error", + "@typescript-eslint/lines-between-class-members": "off", + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "memberLike", + "modifiers": ["private"], + "format": ["camelCase"], + "leadingUnderscore": "require" + }, + { + "selector": "memberLike", + "modifiers": ["protected"], + "format": ["camelCase"], + "leadingUnderscore": "require" + }, + { + "selector": "memberLike", + "modifiers": ["private"], + "format": ["UPPER_CASE", "camelCase"], + "leadingUnderscore": "require" + } + ], + "import/prefer-default-export": "off", + "no-underscore-dangle": "off", + "no-param-reassign": "error", + "consistent-return": "off", + "class-methods-use-this": "off" }, "plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"] }, { "files": ["*.html"], - "extends": ["plugin:@nrwl/nx/angular-template"], + "extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"], + "plugins": ["prettier"], "rules": {} } ] diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 54fdea5..0976335 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -24,18 +24,18 @@ export class CircleButtonComponent implements OnInit { @Input() isSubmit = false; @Input() size = 34; @Input() iconSize = 14; - @Output() action = new EventEmitter(); + @Output() readonly action = new EventEmitter(); @ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip; constructor(private readonly _elementRef: ElementRef) {} - ngOnInit() { - this._elementRef.nativeElement.style.setProperty('--size', this.size + 'px'); - this._elementRef.nativeElement.style.setProperty('--iconSize', this.iconSize + 'px'); + ngOnInit(): void { + (this._elementRef.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`); + (this._elementRef.nativeElement as HTMLElement).style.setProperty('--iconSize', `${this.iconSize}px`); } - performAction($event: MouseEvent) { + performAction($event: MouseEvent): void { if (this.disabled) return; if (this.removeTooltip) { diff --git a/src/lib/buttons/icon-button/icon-button.component.ts b/src/lib/buttons/icon-button/icon-button.component.ts index 1d8d64a..2d1f24c 100644 --- a/src/lib/buttons/icon-button/icon-button.component.ts +++ b/src/lib/buttons/icon-button/icon-button.component.ts @@ -16,5 +16,5 @@ export class IconButtonComponent { @Input() showDot = false; @Input() disabled = false; @Input() type: IconButtonType = IconButtonTypes.default; - @Output() action = new EventEmitter(); + @Output() readonly action = new EventEmitter(); } diff --git a/src/lib/common-ui.module.ts b/src/lib/common-ui.module.ts index f76436a..a0d40b8 100644 --- a/src/lib/common-ui.module.ts +++ b/src/lib/common-ui.module.ts @@ -1,23 +1,23 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { IconButtonComponent } from './buttons/icon-button/icon-button.component'; import { MatIconModule, MatIconRegistry } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; -import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component'; import { DomSanitizer } from '@angular/platform-browser'; -import { CircleButtonComponent } from './buttons/circle-button/circle-button.component'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslateModule } from '@ngx-translate/core'; +import { FormsModule } from '@angular/forms'; +import { IconButtonComponent } from './buttons/icon-button/icon-button.component'; +import { ChevronButtonComponent } from './buttons/chevron-button/chevron-button.component'; +import { CircleButtonComponent } from './buttons/circle-button/circle-button.component'; import { RoundCheckboxComponent } from './inputs/round-checkbox/round-checkbox.component'; import { SortByPipe } from './sorting/sort-by.pipe'; import { HumanizePipe } from './utils/pipes/humanize.pipe'; import { TableColumnNameComponent } from './tables/table-column-name/table-column-name.component'; import { QuickFiltersComponent } from './filtering/quick-filters/quick-filters.component'; import { TableHeaderComponent } from './tables/table-header/table-header.component'; -import { TranslateModule } from '@ngx-translate/core'; import { SyncWidthDirective } from './tables/sync-width.directive'; import { StatusBarComponent } from './misc/status-bar/status-bar.component'; import { EditableInputComponent } from './inputs/editable-input/editable-input.component'; -import { FormsModule } from '@angular/forms'; const buttons = [IconButtonComponent, ChevronButtonComponent, CircleButtonComponent]; @@ -40,8 +40,8 @@ export class CommonUiModule { constructor(private readonly _iconRegistry: MatIconRegistry, private readonly _sanitizer: DomSanitizer) { const icons = ['arrow-down', 'check', 'close', 'edit', 'sort-asc', 'sort-desc']; - for (const icon of icons) { + icons.forEach(icon => { _iconRegistry.addSvgIconInNamespace('iqser', icon, _sanitizer.bypassSecurityTrustResourceUrl(`/assets/icons/${icon}.svg`)); - } + }); } } diff --git a/src/lib/filtering/filter-utils.ts b/src/lib/filtering/filter-utils.ts index 05bdb94..239cc19 100644 --- a/src/lib/filtering/filter-utils.ts +++ b/src/lib/filtering/filter-utils.ts @@ -1,31 +1,22 @@ +/* eslint-disable no-param-reassign */ import { NestedFilter } from './models/nested-filter.model'; import { FilterGroup } from './models/filter-group.model'; import { Filter } from './models/filter.model'; -export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]) { - copySettings(oldFilters, newFilters); - if (newFilters) { - newFilters.forEach(filter => { - handleCheckedValue(filter); - }); - } - return newFilters; -} - function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) { - if (oldFilters && newFilters) { - for (const oldFilter of oldFilters) { - const newFilter = newFilters.find(f => f.key === oldFilter.key); - if (newFilter) { - newFilter.checked = oldFilter.checked; - newFilter.indeterminate = oldFilter.indeterminate; - if (oldFilter.children && newFilter.children) copySettings(oldFilter.children, newFilter.children); - } + if (!oldFilters || !newFilters) return; + + oldFilters.forEach(filter => { + const newFilter = newFilters.find(f => f.key === filter.key); + if (newFilter) { + newFilter.checked = filter.checked; + newFilter.indeterminate = filter.indeterminate; + if (filter.children && newFilter.children) copySettings(filter.children, newFilter.children); } - } + }); } -export function handleCheckedValue(filter: NestedFilter) { +export function handleCheckedValue(filter: NestedFilter): void { if (filter.children && filter.children.length) { filter.checked = filter.children.reduce((acc, next) => acc && !!next.checked, true); if (filter.checked) { @@ -38,38 +29,54 @@ export function handleCheckedValue(filter: NestedFilter) { } } -export function checkFilter(entity: any, filters: NestedFilter[], validate: Function, validateArgs: any = [], matchAll: boolean = false) { +export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]): NestedFilter[] { + copySettings(oldFilters, newFilters); + if (newFilters) { + newFilters.forEach(filter => { + handleCheckedValue(filter); + }); + } + return newFilters; +} + +export function checkFilter( + entity: unknown, + filters: NestedFilter[], + validate?: (...args: unknown[]) => boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + validateArgs: any = [], + matchAll = false +): boolean { const hasChecked = filters.find(f => f.checked); if (!hasChecked) return true; let filterMatched = matchAll; - for (const filter of filters) { + filters.forEach(filter => { if (filter.checked) { if (matchAll) { - filterMatched = filterMatched && validate(entity, filter, ...validateArgs); + filterMatched = filterMatched && !!validate?.(entity, filter, ...validateArgs); } else { - filterMatched = filterMatched || validate(entity, filter, ...validateArgs); + filterMatched = filterMatched || !!validate?.(entity, filter, ...validateArgs); } } - } + }); return filterMatched; } -export const keyChecker = (key: string) => (entity: any, filter: NestedFilter) => entity[key] === filter.key; +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const keyChecker = (key: string) => (entity: Record, filter: NestedFilter) => entity[key] === filter.key; -export function getFilteredEntities(entities: T[], filters: FilterGroup[]) { +export function getFilteredEntities(entities: T[], filters: FilterGroup[]): T[] { const filteredEntities: T[] = []; - for (const entity of entities) { + entities.forEach(entity => { let add = true; - for (const filter of filters) { + filters.forEach(filter => { add = add && checkFilter(entity, filter.filters, filter.checker, filter.checkerArgs, filter.matchAll); - } - if (add) { - filteredEntities.push(entity); - } - } + }); + if (add) filteredEntities.push(entity); + }); return filteredEntities; } diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index ab30e5e..60de9d2 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; -import { processFilters, toFlatFilters } from './filter-utils'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; +import { processFilters, toFlatFilters } from './filter-utils'; import { FilterGroup } from './models/filter-group.model'; import { NestedFilter } from './models/nested-filter.model'; @@ -36,7 +36,7 @@ export class FilterService { if (!filters) return console.error(`Cannot find filter group "${filterGroupSlug}"`); let found = filters.find(f => f.key === key); - if (!found) found = filters.map(f => f.children?.find(ff => ff.key === key))[0]; + if (!found) [found] = filters.map(f => f.children?.find(ff => ff.key === key)); if (!found) return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`); found.checked = !found.checked; @@ -48,8 +48,8 @@ export class FilterService { const oldFilters = this.getGroup(value.slug)?.filters; if (!oldFilters) return this._filterGroups$.next([...this.filterGroups, value]); - value.filters = processFilters(oldFilters, value.filters); - this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== value.slug), value]); + const newGroup = { ...value, filters: processFilters(oldFilters, value.filters) }; + this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== newGroup.slug), newGroup]); } getGroup(slug: string): FilterGroup | undefined { @@ -67,9 +67,14 @@ export class FilterService { reset(): void { this.filterGroups.forEach(group => { group.filters.forEach(filter => { + // eslint-disable-next-line no-param-reassign filter.checked = false; + // eslint-disable-next-line no-param-reassign filter.indeterminate = false; - filter.children?.forEach(f => (f.checked = false)); + filter.children?.forEach(f => { + // eslint-disable-next-line no-param-reassign + f.checked = false; + }); }); }); diff --git a/src/lib/filtering/models/filter-group.model.ts b/src/lib/filtering/models/filter-group.model.ts index 1132af6..d1d6789 100644 --- a/src/lib/filtering/models/filter-group.model.ts +++ b/src/lib/filtering/models/filter-group.model.ts @@ -1,14 +1,14 @@ -import { NestedFilter } from './nested-filter.model'; import { TemplateRef } from '@angular/core'; +import { NestedFilter } from './nested-filter.model'; export interface FilterGroup { filters: NestedFilter[]; readonly slug: string; readonly label?: string; readonly icon?: string; - readonly filterTemplate?: TemplateRef; + readonly filterTemplate?: TemplateRef; readonly hide?: boolean; - readonly checker?: Function; + readonly checker?: (...args: unknown[]) => boolean; readonly matchAll?: boolean; - readonly checkerArgs?: any[]; + readonly checkerArgs?: unknown[]; } diff --git a/src/lib/inputs/editable-input/editable-input.component.ts b/src/lib/inputs/editable-input/editable-input.component.ts index 2d402ea..c8391bc 100644 --- a/src/lib/inputs/editable-input/editable-input.component.ts +++ b/src/lib/inputs/editable-input/editable-input.component.ts @@ -17,7 +17,7 @@ export class EditableInputComponent { @Input() class?: string; @Input() showPreview = true; @Input() buttonsType?: CircleButtonType; - @Output() save = new EventEmitter(); + @Output() readonly save = new EventEmitter(); newValue = ''; private _editing = false; @@ -32,7 +32,7 @@ export class EditableInputComponent { this.newValue = this.value; } - saveValue() { + saveValue(): void { this.save.emit(this.newValue); this.editing = false; } diff --git a/src/lib/inputs/round-checkbox/round-checkbox.component.ts b/src/lib/inputs/round-checkbox/round-checkbox.component.ts index 1986040..05c3339 100644 --- a/src/lib/inputs/round-checkbox/round-checkbox.component.ts +++ b/src/lib/inputs/round-checkbox/round-checkbox.component.ts @@ -15,6 +15,6 @@ export class RoundCheckboxComponent implements OnInit { @ViewChild('wrapper', { static: true }) private readonly _wrapper!: ElementRef; ngOnInit(): void { - this._wrapper.nativeElement.style.setProperty('--size', this.size + 'px'); + (this._wrapper.nativeElement as HTMLElement).style.setProperty('--size', `${this.size}px`); } } diff --git a/src/lib/search/search.service.ts b/src/lib/search/search.service.ts index 3f1d4f3..9df6116 100644 --- a/src/lib/search/search.service.ts +++ b/src/lib/search/search.service.ts @@ -10,7 +10,7 @@ const controlsConfig = { type FormControls = { [key in KeysOf]: string }; @Injectable() -export class SearchService { +export class SearchService { readonly searchForm = this._formBuilder.group(controlsConfig); readonly valueChanges$ = this.searchForm.valueChanges.pipe( startWith(''), @@ -21,14 +21,14 @@ export class SearchService { constructor(private readonly _formBuilder: FormBuilder) {} get searchValue(): string { - return this.searchForm.get('query')?.value; + return this.searchForm.get('query')?.value as string; } set searchValue(value: string) { this.searchForm.patchValue({ query: value }); } - searchIn(entities: T[]) { + searchIn(entities: T[]): T[] { if (!this._searchKey) return entities; const searchValue = this.searchValue.toLowerCase(); diff --git a/src/lib/sorting/models/sorting-option.model.ts b/src/lib/sorting/models/sorting-option.model.ts index 910c2c1..cfdd87b 100644 --- a/src/lib/sorting/models/sorting-option.model.ts +++ b/src/lib/sorting/models/sorting-option.model.ts @@ -1,7 +1,7 @@ import { SortingOrder } from './sorting-order.type'; import { KeysOf } from '../../utils/types/utility-types'; -export interface SortingOption { +export interface SortingOption { readonly order: SortingOrder; readonly column: KeysOf; } diff --git a/src/lib/sorting/models/sorting-order.type.ts b/src/lib/sorting/models/sorting-order.type.ts index afb5738..587d266 100644 --- a/src/lib/sorting/models/sorting-order.type.ts +++ b/src/lib/sorting/models/sorting-order.type.ts @@ -1,14 +1,12 @@ import { NonFunctionKeys } from '../../utils/types/utility-types'; -function inverseOf(order?: SortingOrder) { - if (order === undefined) return SortingOrders.asc; - return order === SortingOrders.asc ? SortingOrders.desc : SortingOrders.asc; -} - export const SortingOrders = { asc: 'asc', desc: 'desc', - inverseOf: inverseOf + inverseOf: (order?: 'asc' | 'desc') => { + if (order === undefined) return 'asc'; + return order === 'asc' ? 'desc' : 'asc'; + } } as const; export type SortingOrder = NonFunctionKeys; diff --git a/src/lib/sorting/sort-by.pipe.ts b/src/lib/sorting/sort-by.pipe.ts index 5462d9a..c19a517 100644 --- a/src/lib/sorting/sort-by.pipe.ts +++ b/src/lib/sorting/sort-by.pipe.ts @@ -5,7 +5,7 @@ import { KeysOf } from '../utils/types/utility-types'; @Pipe({ name: 'sortBy' }) export class SortByPipe implements PipeTransform { - transform(values: T[], order: SortingOrder, column: KeysOf): T[] { + transform(values: T[], order: SortingOrder, column: KeysOf): T[] { return SortingService.sort(values, order, column); } } diff --git a/src/lib/sorting/sorting.service.ts b/src/lib/sorting/sorting.service.ts index 3ca9cfe..7011051 100644 --- a/src/lib/sorting/sorting.service.ts +++ b/src/lib/sorting/sorting.service.ts @@ -6,7 +6,7 @@ import { SortingOrder, SortingOrders } from './models/sorting-order.type'; import { KeysOf } from '../utils/types/utility-types'; @Injectable() -export class SortingService { +export class SortingService { private readonly _sortingOption$ = new BehaviorSubject | undefined>(undefined); readonly sortingOption$ = this._sortingOption$.asObservable(); @@ -14,7 +14,7 @@ export class SortingService { return this._sortingOption$.getValue(); } - static sort(values: T[], order?: SortingOrder, column?: KeysOf): T[] { + static sort(values: T[], order?: SortingOrder, column?: KeysOf): T[] { if (!values || values.length <= 1 || !order) return values; if (!column) { diff --git a/src/lib/tables/entities.service.ts b/src/lib/tables/entities.service.ts index 15f43e0..99754b9 100644 --- a/src/lib/tables/entities.service.ts +++ b/src/lib/tables/entities.service.ts @@ -9,7 +9,7 @@ const toLengthValue = (entities: unknown[]) => entities?.length ?? 0; const getLength = pipe(map(toLengthValue), distinctUntilChanged()); @Injectable() -export class EntitiesService { +export class EntitiesService { private readonly _all$ = new BehaviorSubject([]); readonly all$ = this._all$.asObservable(); readonly allLength$ = this._all$.pipe(getLength); @@ -38,13 +38,15 @@ export class EntitiesService { } private get _getDisplayed$(): Observable { - const filterGroups$ = this._filterService.filterGroups$; - const searchValue$ = this._searchService.valueChanges$; + const { filterGroups$ } = this._filterService; + const { valueChanges$ } = this._searchService; - return combineLatest([this.all$, filterGroups$, searchValue$]).pipe( + return combineLatest([this.all$, filterGroups$, valueChanges$]).pipe( map(([entities, filterGroups]) => getFilteredEntities(entities, filterGroups)), map(entities => this._searchService.searchIn(entities)), - tap(displayed => (this._displayed = displayed)) + tap(displayed => { + this._displayed = displayed; + }) ); } diff --git a/src/lib/tables/listing-component.directive.ts b/src/lib/tables/listing-component.directive.ts index 5fdb8b1..fc5b894 100644 --- a/src/lib/tables/listing-component.directive.ts +++ b/src/lib/tables/listing-component.directive.ts @@ -14,7 +14,7 @@ import { EntitiesService } from './entities.service'; export const DefaultListingServices = [FilterService, SearchService, EntitiesService, SortingService] as const; @Directive() -export abstract class ListingComponent extends AutoUnsubscribe implements OnDestroy { +export abstract class ListingComponent extends AutoUnsubscribe implements OnDestroy { readonly filterService = this._injector.get(FilterService); readonly searchService = this._injector.get>(SearchService); readonly sortingService = this._injector.get>(SortingService); @@ -53,7 +53,7 @@ export abstract class ListingComponent extends AutoUnsubscribe ); } - setInitialConfig() { + setInitialConfig(): void { this.sortingService.setSortingOption({ column: this._primaryKey, order: SortingOrders.asc @@ -61,9 +61,9 @@ export abstract class ListingComponent extends AutoUnsubscribe this.searchService.setSearchKey(this._primaryKey); } - toggleEntitySelected(event: MouseEvent, entity: T) { + toggleEntitySelected(event: MouseEvent, entity: T): void { event.stopPropagation(); - return this.entitiesService.select(entity); + this.entitiesService.select(entity); } isSelected(entity: T): boolean { @@ -71,7 +71,7 @@ export abstract class ListingComponent extends AutoUnsubscribe } @Bind() - trackByPrimaryKey(index: number, item: T) { + trackByPrimaryKey(index: number, item: T): unknown { return item[this._primaryKey]; } } diff --git a/src/lib/tables/models/table-column-config.model.ts b/src/lib/tables/models/table-column-config.model.ts index b88940e..e2f7039 100644 --- a/src/lib/tables/models/table-column-config.model.ts +++ b/src/lib/tables/models/table-column-config.model.ts @@ -1,6 +1,6 @@ import { KeysOf } from '../../utils/types/utility-types'; -export interface TableColumnConfig { +export interface TableColumnConfig { readonly label: string; readonly sortByKey?: KeysOf; readonly class?: string; diff --git a/src/lib/tables/sync-width.directive.ts b/src/lib/tables/sync-width.directive.ts index caaeeec..e624a9b 100644 --- a/src/lib/tables/sync-width.directive.ts +++ b/src/lib/tables/sync-width.directive.ts @@ -18,36 +18,40 @@ export class SyncWidthDirective implements OnDestroy { } private _matchWidth() { - const headerItems = this._elementRef.nativeElement.children; - const tableRows = this._elementRef.nativeElement.parentElement.parentElement.getElementsByClassName(this.iqserSyncWidth); + const headerItems = (this._elementRef.nativeElement as HTMLElement).children; + const tableRows = (this._elementRef.nativeElement as HTMLElement).parentElement?.parentElement?.getElementsByClassName( + this.iqserSyncWidth + ); if (!tableRows || !tableRows.length) { return; } - this._elementRef.nativeElement.setAttribute('synced', true); + (this._elementRef.nativeElement as HTMLElement).setAttribute('synced', 'true'); const { tableRow, length } = this._sampleRow(tableRows); if (!tableRow) return; const hasExtraColumns = headerItems.length !== length ? 1 : 0; - for (let idx = 0; idx < length - hasExtraColumns - 1; ++idx) { - if (headerItems[idx]) { - headerItems[idx].style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`; - headerItems[idx].style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`; + for (let idx = 0; idx < length - hasExtraColumns - 1; idx += 1) { + const item = headerItems[idx] as HTMLElement; + if (item) { + item.style.width = `${tableRow.children[idx].getBoundingClientRect().width}px`; + item.style.minWidth = `${tableRow.children[idx].getBoundingClientRect().width}px`; } } - for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; ++idx) { - if (headerItems[idx]) { - headerItems[idx].style.minWidth = `0`; + for (let idx = length - hasExtraColumns - 1; idx < headerItems.length; idx += 1) { + const item = headerItems[idx] as HTMLElement; + if (item) { + item.style.minWidth = `0`; } } } @HostListener('window:resize') - onResize() { + onResize(): void { this._matchWidth(); } @@ -58,7 +62,7 @@ export class SyncWidthDirective implements OnDestroy { let length = 0; let tableRow: Element | undefined; - for (let idx = 0; idx < tableRows.length; ++idx) { + for (let idx = 0; idx < tableRows.length; idx += 1) { const row = tableRows.item(idx); if (row && row.children.length > length) { length = row.children.length; diff --git a/src/lib/tables/table-column-name/table-column-name.component.ts b/src/lib/tables/table-column-name/table-column-name.component.ts index 0414bac..1bb53a2 100644 --- a/src/lib/tables/table-column-name/table-column-name.component.ts +++ b/src/lib/tables/table-column-name/table-column-name.component.ts @@ -4,7 +4,7 @@ import { Required } from '../../utils/decorators/required.decorator'; import { KeysOf } from '../../utils/types/utility-types'; import { SortingService } from '../../sorting/sorting.service'; -const ifHasRightIcon = (thisArg: TableColumnNameComponent) => !!thisArg.rightIcon; +const ifHasRightIcon = (thisArg: TableColumnNameComponent) => !!thisArg.rightIcon; @Component({ selector: 'iqser-table-column-name', @@ -12,7 +12,7 @@ const ifHasRightIcon = (thisArg: TableColumnNameComponent) styleUrls: ['./table-column-name.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TableColumnNameComponent { +export class TableColumnNameComponent { readonly sortingOrders = SortingOrders; @Input() @Required() label!: string; diff --git a/src/lib/tables/table-header/table-header.component.ts b/src/lib/tables/table-header/table-header.component.ts index 84e4c46..0ee011d 100644 --- a/src/lib/tables/table-header/table-header.component.ts +++ b/src/lib/tables/table-header/table-header.component.ts @@ -10,12 +10,12 @@ import { EntitiesService } from '../entities.service'; styleUrls: ['./table-header.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class TableHeaderComponent { +export class TableHeaderComponent { @Input() @Required() tableHeaderLabel!: string; @Input() @Required() tableColumnConfigs!: readonly TableColumnConfig[]; @Input() hasEmptyColumn = false; @Input() selectionEnabled = false; - @Input() bulkActions?: TemplateRef; + @Input() bulkActions?: TemplateRef; readonly quickFilters$ = this.filterService.getFilterModels$('quickFilters'); diff --git a/src/lib/utils/auto-unsubscribe.directive.ts b/src/lib/utils/auto-unsubscribe.directive.ts index 38db9a3..f912ca1 100644 --- a/src/lib/utils/auto-unsubscribe.directive.ts +++ b/src/lib/utils/auto-unsubscribe.directive.ts @@ -1,5 +1,5 @@ -import { Directive, OnDestroy } from "@angular/core"; -import { Subscription } from "rxjs"; +import { Directive, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; /** * Inherit this class when you need to subscribe to observables in your components diff --git a/src/lib/utils/decorators/bind.decorator.ts b/src/lib/utils/decorators/bind.decorator.ts index d5eba6a..66ee6e1 100644 --- a/src/lib/utils/decorators/bind.decorator.ts +++ b/src/lib/utils/decorators/bind.decorator.ts @@ -6,20 +6,16 @@ * */ -export function Bind() { - return function _bind( - _target: Object, - propertyKey: PropertyKey, - descriptor: TypedPropertyDescriptor - ): TypedPropertyDescriptor | never { +export function Bind(): MethodDecorator { + return function _bind(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor): PropertyDescriptor | never { if (typeof descriptor.value !== 'function') { throw new TypeError(`@Bind() decorator can only be applied to methods, not to ${typeof descriptor.value}`); } return { configurable: true, - get(): T { - const boundMethod: T = descriptor.value?.bind(this); + get() { + const boundMethod = (descriptor.value as (...args: unknown[]) => unknown).bind(this); Object.defineProperty(this, propertyKey, { value: boundMethod, configurable: true, diff --git a/src/lib/utils/decorators/debounce.decorator.ts b/src/lib/utils/decorators/debounce.decorator.ts index c6d1fdc..0da4c4d 100644 --- a/src/lib/utils/decorators/debounce.decorator.ts +++ b/src/lib/utils/decorators/debounce.decorator.ts @@ -1,14 +1,14 @@ export function Debounce(delay = 300): MethodDecorator { - return function (target: Object, propertyKey: PropertyKey, descriptor: PropertyDescriptor) { + return function _debounce(_target: unknown, propertyKey: PropertyKey, descriptor: PropertyDescriptor) { let timeout: number | undefined; - const original = descriptor.value; + const descriptorCopy = { ...descriptor }; - descriptor.value = function (...args: unknown[]) { + descriptorCopy.value = function _new(...args: unknown[]) { clearTimeout(timeout); - timeout = setTimeout(() => original.apply(this, args), delay); + timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay); }; - return descriptor; + return descriptorCopy; }; } diff --git a/src/lib/utils/decorators/required.decorator.ts b/src/lib/utils/decorators/required.decorator.ts index 4200891..ab043da 100644 --- a/src/lib/utils/decorators/required.decorator.ts +++ b/src/lib/utils/decorators/required.decorator.ts @@ -1,12 +1,12 @@ export type Condition = (thisArg: T) => boolean; export function Required(condition: Condition = () => true): PropertyDecorator { - return function (target: Object, propertyKey: PropertyKey) { + return function _required(target: unknown, propertyKey: PropertyKey) { Object.defineProperty(target, propertyKey, { get() { if (condition(this)) throw new Error(`Attribute ${String(propertyKey)} is required`); }, - set(value) { + set(value: unknown) { Object.defineProperty(this, propertyKey, { value, writable: true diff --git a/src/lib/utils/types/utility-types.ts b/src/lib/utils/types/utility-types.ts index 88435c7..7d680c2 100644 --- a/src/lib/utils/types/utility-types.ts +++ b/src/lib/utils/types/utility-types.ts @@ -7,7 +7,7 @@ * // Expect: "name | setName | someKeys | someFn" * type Keys = KeysOf; */ -export type KeysOf = { +export type KeysOf = { [K in keyof T]: K; }[keyof T]; @@ -20,7 +20,7 @@ export type KeysOf = { * // Expect: "some bar | some foo" * type Values = ValuesOf; */ -export type ValuesOf = T[KeysOf]; +export type ValuesOf = T[KeysOf]; /** * NonUndefined @@ -40,7 +40,8 @@ export type NonUndefined = T extends undefined ? never : T; * // Expect: "setName | someFn" * type Keys = FunctionKeys; */ -export type FunctionKeys = { +export type FunctionKeys = { + // eslint-disable-next-line @typescript-eslint/ban-types [K in keyof T]-?: NonUndefined extends Function ? K : never; }[keyof T]; @@ -53,6 +54,7 @@ export type FunctionKeys = { * // Expect: "name | someKey" * type Keys = NonFunctionKeys; */ -export type NonFunctionKeys = { +export type NonFunctionKeys = { + // eslint-disable-next-line @typescript-eslint/ban-types [K in keyof T]-?: NonUndefined extends Function ? never : K; }[keyof T]; diff --git a/src/test-setup.ts b/src/test-setup.ts index 1100b3e..fb9e8ea 100644 --- a/src/test-setup.ts +++ b/src/test-setup.ts @@ -1 +1,2 @@ +// eslint-disable-next-line import/no-extraneous-dependencies import 'jest-preset-angular/setup-jest';