Skip to content

Commit

Permalink
chore: fix super calls of lit element lifecycle methods (#2644)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB authored May 8, 2024
1 parent 22cf58d commit 62eb0d5
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 20 deletions.
13 changes: 11 additions & 2 deletions src/components/alert/alert/alert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { type CSSResultGroup, html, LitElement, nothing, type TemplateResult } from 'lit';
import {
type CSSResultGroup,
html,
LitElement,
nothing,
type PropertyValues,
type TemplateResult,
} from 'lit';
import { customElement, property } from 'lit/decorators.js';

import type { LinkTargetType } from '../../core/base-elements.js';
Expand Down Expand Up @@ -99,7 +106,9 @@ export class SbbAlertElement extends SbbIconNameMixin(LitElement) {

private _language = new SbbLanguageController(this);

protected override async firstUpdated(): Promise<void> {
protected override async firstUpdated(changedProperties: PropertyValues<this>): Promise<void> {
super.firstUpdated(changedProperties);

this._open();
}

Expand Down
4 changes: 3 additions & 1 deletion src/components/checkbox/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ export class SbbCheckboxElement extends SbbUpdateSchedulerMixin(
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

// We need to wait for the selection-panel to be fully initialized
this.startUpdate();
setTimeout(() => {
Expand Down
6 changes: 4 additions & 2 deletions src/components/clock/clock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSResultGroup, TemplateResult } from 'lit';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';
Expand Down Expand Up @@ -264,7 +264,9 @@ export class SbbClockElement extends LitElement {
return new Date();
}

protected override async firstUpdated(): Promise<void> {
protected override async firstUpdated(changedProperties: PropertyValues<this>): Promise<void> {
super.firstUpdated(changedProperties);

this._addEventListeners();

if (this._hasDataNow()) {
Expand Down
12 changes: 10 additions & 2 deletions src/components/container/sticky-bar/sticky-bar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { type CSSResultGroup, html, LitElement, type TemplateResult } from 'lit';
import {
type CSSResultGroup,
html,
LitElement,
type PropertyValues,
type TemplateResult,
} from 'lit';
import { customElement, property } from 'lit/decorators.js';

import { hostAttributes } from '../../core/decorators.js';
Expand Down Expand Up @@ -44,7 +50,9 @@ export class SbbStickyBarElement extends LitElement {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

if (!this._intersector) {
this._intersector = this.shadowRoot!.querySelector('.sbb-sticky-bar__intersector')!;
this._observer.observe(this._intersector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ export class SbbDatepickerToggleElement extends SbbNegativeMixin(LitElement) {
return undefined;
}

protected override updated(): void {
protected override updated(changedProperties: PropertyValues<this>): void {
super.updated(changedProperties);

this._popoverElement.trigger = this._triggerElement;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ export class SbbExpansionPanelElement extends SbbHydrationMixin(LitElement) {
this.removeAttribute('data-accordion');
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

this._initialized = true;
}

Expand Down
6 changes: 4 additions & 2 deletions src/components/image/image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
SbbBreakpointUltraMax,
SbbTypoScaleDefault,
} from '@sbb-esta/lyne-design-tokens';
import type { CSSResultGroup, TemplateResult } from 'lit';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement, nothing } from 'lit';
import { customElement, eventOptions, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
Expand Down Expand Up @@ -539,7 +539,9 @@ export class SbbImageElement extends LitElement {
`;
}

protected override updated(): void {
protected override updated(changedProperties: PropertyValues<this>): void {
super.updated(changedProperties);

if (!this._captionElement) {
return;
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/notification/notification.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSResultGroup, TemplateResult } from 'lit';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement, nothing } from 'lit';
import { customElement, property } from 'lit/decorators.js';

Expand Down Expand Up @@ -141,7 +141,9 @@ export class SbbNotificationElement extends LitElement {
super.connectedCallback();
}

protected override async firstUpdated(): Promise<void> {
protected override async firstUpdated(changedProperties: PropertyValues<this>): Promise<void> {
super.firstUpdated(changedProperties);

this._notificationElement = this.shadowRoot?.querySelector(
'.sbb-notification__wrapper',
) as HTMLElement;
Expand Down
4 changes: 3 additions & 1 deletion src/components/popover/popover/popover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ export class SbbPopoverElement extends LitElement {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

if (this._hoverTrigger) {
this._overlay.addEventListener('mouseenter', () => this._onOverlayMouseEnter());
this._overlay.addEventListener('mouseleave', () => this._onOverlayMouseLeave());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ export class SbbRadioButtonGroupElement extends SbbDisabledMixin(LitElement) {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

this._didLoad = true;
this._updateRadios(this.value);
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/radio-button/radio-button/radio-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ export class SbbRadioButtonElement extends SbbUpdateSchedulerMixin(LitElement) {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

// We need to wait for the selection-panel to be fully initialized
this.startUpdate();
setTimeout(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ export class SbbSelectElement extends SbbUpdateSchedulerMixin(
this._stateChange.emit({ type: 'value', value: newValue });
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

// Override the default focus behavior
this.focus = () => this._triggerElement.focus();
this.blur = () => this._triggerElement.blur();
Expand Down
4 changes: 3 additions & 1 deletion src/components/selection-panel/selection-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ export class SbbSelectionPanelElement extends LitElement {
}
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

this._initialized = true;
}

Expand Down
6 changes: 4 additions & 2 deletions src/components/tabs/tab-group/tab-group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CSSResultGroup, TemplateResult } from 'lit';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ref } from 'lit/directives/ref.js';
Expand Down Expand Up @@ -143,7 +143,9 @@ export class SbbTabGroupElement extends LitElement {
this.toggleAttribute('data-nested', !!this.parentElement?.closest('sbb-tab-group'));
}

protected override firstUpdated(): void {
protected override firstUpdated(changedProperties: PropertyValues<this>): void {
super.firstUpdated(changedProperties);

this._tabs = this._getTabs();
this._tabs.forEach((tab) => this._configure(tab));
this._initSelection();
Expand Down
1 change: 1 addition & 0 deletions src/storybook/helpers/spread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class SbbSpreadDirective extends Directive {
}

public override update(part: Part, [spreadData]: { [key: string]: unknown }[]): void {
super.update(part, [spreadData]);
if (this._element !== (part as ElementPart).element) {
this._element = (part as ElementPart).element;
}
Expand Down
2 changes: 2 additions & 0 deletions tools/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as customElementDecoratorPosition from './custom-element-decorator-posi
import * as importExtensionRule from './import-extension-rule.js';
import * as useLocalName from './local-name-rule.js';
import * as missingComponentDocumentation from './missing-component-documentation-rule.js';
import * as needsSuperCall from './needs-super-call-rule.js';

const plugin: Omit<Required<TSESLint.FlatConfig.Plugin>, 'processors'> = {
meta: {
Expand All @@ -17,6 +18,7 @@ const plugin: Omit<Required<TSESLint.FlatConfig.Plugin>, 'processors'> = {
[importExtensionRule.name]: importExtensionRule.rule,
[useLocalName.name]: useLocalName.rule,
[customElementDecoratorPosition.name]: customElementDecoratorPosition.rule,
[needsSuperCall.name]: needsSuperCall.rule,
},
};

Expand Down
88 changes: 88 additions & 0 deletions tools/eslint/needs-super-call-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
import { ESLintUtils } from '@typescript-eslint/utils';

const createRule = ESLintUtils.RuleCreator(
(name) =>
`https://github.com/lyne-design-system/lyne-components/blob/main/tools/eslint/${name}.ts`,
);

type MessageIds = 'needsSuperCall' | 'needsSuperCallSuggestion';

const hasChangedProperties = ['shouldUpdate', 'willUpdate', 'update', 'firstUpdated', 'updated'];

const methodsToCheckForSuperCall = [
'connectedCallback',
'disconnectedCallback',
'requestUpdate',
'performUpdate',
...hasChangedProperties,
];

export const name = 'needs-super-call-rule';
export const rule: TSESLint.RuleModule<MessageIds, never[]> = createRule<never[], MessageIds>({
create(context) {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
MethodDefinition(methodDefinitionNode) {
if (
methodDefinitionNode.key.type === 'Identifier' &&
methodsToCheckForSuperCall.includes(methodDefinitionNode.key.name)
) {
const name = methodDefinitionNode.key.name;
const hasSuperCall = methodDefinitionNode.value.body?.body?.some(
(node: TSESTree.Statement) => {
if (
node.type === 'ExpressionStatement' &&
node.expression.type === 'CallExpression' &&
node.expression.callee.type === 'MemberExpression' &&
node.expression.callee.property.type === 'Identifier'
) {
const memberExpression = node.expression.callee;
return (
(memberExpression.property as TSESTree.Identifier).name === name &&
memberExpression.object.type === 'Super'
);
}
},
);

if (!hasSuperCall) {
context.report({
node: methodDefinitionNode,
messageId: 'needsSuperCall',
suggest: [
{
messageId: 'needsSuperCallSuggestion',
data: {
name: name,
params: hasChangedProperties.includes(name) ? 'changedProperties' : '',
},
fix: (fixer) =>
fixer.insertTextBefore(
methodDefinitionNode.value.body!.body![0],
`super.${name}(${hasChangedProperties.includes(name) ? 'changedProperties' : ''});\n\n `,
),
},
],
});
}
}
},
};
},
name,
meta: {
docs: {
description: 'method needs super call',
recommended: 'recommended',
},
messages: {
needsSuperCall: 'Method needs super call.',
needsSuperCallSuggestion: 'Insert super call: super.{{ name }}({{ params }});',
},
type: 'problem',
schema: [],
hasSuggestions: true,
},
defaultOptions: [],
});

0 comments on commit 62eb0d5

Please sign in to comment.