web: update to ESLint 9 (#10812)

* web: update to ESLint 9

ESLint 9 has been out for awhile now, and all of the plug-ins that we use have caught up, so it is
time to bite the bullet and upgrade.  This commit:

- upgrades to ESLint 9, and upgrades all associated plugins
- Replaces the `.eslintrc` and `.eslintignore` files with the new, "flat" configuration file,
  "eslint.config.mjs".
- Places the previous "precommit" and "nightmare" rules in `./scripts/eslint.precommit.mjs` and
  `./scripts/eslint.nightmare.mjs`, respectively
- Replaces the scripted wrappers for eslint (`eslint`, `eslint-precommit`) with a single executable
  that takes the arguments `--precommit`, which applies a stricter set of rules, and `--nightmare`,
  which applies an even more terrifyingly strict set of rules.
- Provides the scripted wrapper `./scripts/eslint.mjs` so that eslint can be run from `bun`, if one
  so chooses.
- Fixes *all* of the lint `eslint.config.mjs` now finds, including removing all of the `eslint`
  styling rules and overrides because Eslint now proudly leaves that entirely up to Prettier.

To shut Dependabot up about ESLint.

* Added explanation for no-console removal.

* web: did not need the old and unmaintained nightmare mode; it can be configured directly.
This commit is contained in:
Ken Sternberg
2024-08-07 15:04:18 -07:00
committed by GitHub
parent 322ae4c4ed
commit 79c01ca473
34 changed files with 5658 additions and 5076 deletions

View File

@ -73,9 +73,10 @@ export class CodeMirrorTextarea<T> extends AKElement {
}
@property()
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
set value(v: T | string) {
if (v === null || v === undefined) return;
if (v === null || v === undefined) {
return;
}
// Value might be an object if within an iron-form, as that calls the getter of value
// in the beginning and the calls this setter on reset
let textValue = v;
@ -114,7 +115,7 @@ export class CodeMirrorTextarea<T> extends AKElement {
default:
return this.getInnerValue();
}
} catch (e) {
} catch (_e: unknown) {
return this.getInnerValue();
}
}

View File

@ -136,7 +136,6 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
constructor() {
super();
this.onClick = this.onClick.bind(this);
this.dataset.akControl = "true";
}
onClick(ev: Event) {
@ -173,6 +172,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
connectedCallback() {
super.connectedCallback();
this.dataset.akControl = "true";
if (this.name && !this.internals) {
this.internals = this.attachInternals();
}

View File

@ -56,7 +56,6 @@ const container = (testItem: TemplateResult) =>
const displayMessage = (result: any) => {
const doc = new DOMParser().parseFromString(`<li><i>Event</i>: ${result}</li>`, "text/xml");
const target = document.querySelector("#action-button-message-pad");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
target!.appendChild(doc.firstChild!);
};

View File

@ -42,7 +42,6 @@ const container = (testItem: TemplateResult) =>
const displayMessage = (result: any) => {
const doc = new DOMParser().parseFromString(`<p><i>Content</i>: ${result}</p>`, "text/xml");
const target = document.querySelector("#action-button-message-pad");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
target!.replaceChildren(doc.firstChild!);
};
@ -51,7 +50,6 @@ const displayMessage2 = (result: any) => {
console.debug("Huh.");
const doc = new DOMParser().parseFromString(`<p><i>Behavior</i>: ${result}</p>`, "text/xml");
const target = document.querySelector("#action-button-message-pad-2");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
target!.replaceChildren(doc.firstChild!);
};

View File

@ -52,7 +52,6 @@ const displayMessage = (result: any) => {
"text/xml",
);
const target = document.querySelector("#action-button-message-pad");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
target!.appendChild(doc.firstChild!);
};

View File

@ -6,25 +6,33 @@ import { customElement } from "lit/decorators.js";
@customElement("ak-dropdown")
export class DropdownButton extends AKElement {
menu: HTMLElement | null;
menu: HTMLElement | null = null;
constructor() {
super();
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
btn.addEventListener("click", () => {
if (!this.menu) return;
this.menu.hidden = !this.menu.hidden;
});
});
window.addEventListener(EVENT_REFRESH, this.clickHandler);
}
clickHandler = (): void => {
if (!this.menu) return;
if (!this.menu) {
return;
}
this.menu.hidden = true;
};
connectedCallback() {
super.connectedCallback();
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
btn.addEventListener("click", () => {
if (!this.menu) {
return;
}
this.menu.hidden = !this.menu.hidden;
});
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener(EVENT_REFRESH, this.clickHandler);

View File

@ -51,7 +51,6 @@ const displayMessage = (result: any) => {
"text/xml",
);
const target = document.querySelector("#action-button-message-pad");
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
target!.appendChild(doc.firstChild!);
};

View File

@ -98,7 +98,6 @@ export class ModalOrchestrationController implements ReactiveController {
// Pop off modals until you find the first live one, schedule it to be closed, and make that
// cleaned list the current state. Since this is our *only* state object, this has the
// effect of creating a new "knownModals" collection with some semantics.
// eslint-disable-next-line no-constant-condition
while (true) {
const modal = knownModals.pop();
if (!modal) {

View File

@ -48,7 +48,9 @@ function assignValue(element: HTMLNamedElement, value: unknown, json: KeyUnknown
for (let index = 0; index < nameElements.length - 1; index++) {
const nameEl = nameElements[index];
// Ensure all nested structures exist
if (!(nameEl in parent)) parent[nameEl] = {};
if (!(nameEl in parent)) {
parent[nameEl] = {};
}
parent = parent[nameEl] as { [key: string]: unknown };
}
parent[nameElements[nameElements.length - 1]] = value;
@ -103,7 +105,7 @@ export function serializeForm<T extends KeyUnknown>(
} else if (
inputElement.tagName.toLowerCase() === "input" &&
"type" in inputElement.dataset &&
inputElement.dataset["type"] === "datetime-local"
inputElement.dataset.type === "datetime-local"
) {
// Workaround for Firefox <93, since 92 and older don't support
// datetime-local fields
@ -122,6 +124,9 @@ export function serializeForm<T extends KeyUnknown>(
return json as unknown as T;
}
const HTTP_BAD_REQUEST = 400;
const HTTP_INTERNAL_SERVICE_ERROR = 500;
/**
* Form
*
@ -188,7 +193,7 @@ export abstract class Form<T> extends AKElement {
*/
get isInViewport(): boolean {
const rect = this.getBoundingClientRect();
return !(rect.x + rect.y + rect.width + rect.height === 0);
return rect.x + rect.y + rect.width + rect.height !== 0;
}
getSuccessMessage(): string {
@ -275,7 +280,6 @@ export abstract class Form<T> extends AKElement {
}
return serializeForm(elements) as T;
}
/**
* Serialize and send the form to the destination. The `send()` method must be overridden for
* this to work. If processing the data results in an error, we catch the error, distribute
@ -304,9 +308,14 @@ export abstract class Form<T> extends AKElement {
} catch (ex) {
if (ex instanceof ResponseError) {
let msg = ex.response.statusText;
if (ex.response.status > 399 && ex.response.status < 500) {
if (
ex.response.status >= HTTP_BAD_REQUEST &&
ex.response.status < HTTP_INTERNAL_SERVICE_ERROR
) {
const errorMessage = ValidationErrorFromJSON(await ex.response.json());
if (!errorMessage) return errorMessage;
if (!errorMessage) {
return errorMessage;
}
if (errorMessage instanceof Error) {
throw errorMessage;
}
@ -318,7 +327,9 @@ export abstract class Form<T> extends AKElement {
elements.forEach((element) => {
element.requestUpdate();
const elementName = element.name;
if (!elementName) return;
if (!elementName) {
return;
}
if (camelToSnake(elementName) in errorMessage) {
element.errorMessages = errorMessage[camelToSnake(elementName)];
element.invalid = true;

View File

@ -20,7 +20,8 @@ import {
SearchSelectSelectEvent,
SearchSelectSelectMenuEvent,
} from "./SearchSelectEvents.js";
import type { SearchOptions, SearchTuple } from "./types.js";
import type { SearchOptions } from "./types.js";
import { optionsToOptionsMap } from "./utils.js";
/**
* @class SearchSelectView
@ -225,8 +226,8 @@ export class SearchSelectView extends AKElement {
}
updated() {
if (!(this.inputRef?.value && this.inputRef?.value?.value === this.displayValue)) {
this.inputRef.value && (this.inputRef.value.value = this.displayValue);
if (this.inputRef?.value && this.inputRef?.value?.value !== this.displayValue) {
this.inputRef.value.value = this.displayValue;
}
}
@ -264,21 +265,6 @@ export class SearchSelectView extends AKElement {
}
}
type Pair = [string, string];
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
const pairs: Pair[] = Array.isArray(options)
? options.map(justThePair)
: options.grouped
? options.options.reduce(
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
[] as Pair[],
)
: options.options.map(justThePair);
return new Map(pairs);
}
declare global {
interface HTMLElementTagNameMap {
"ak-search-select-view": SearchSelectView;

View File

@ -97,11 +97,6 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
@state()
error?: APIErrorTypes;
constructor() {
super();
this.dataset.akControl = "true";
}
toForm(): unknown {
if (!this.objects) {
throw new PreventFormSubmit(msg("Loading options..."));
@ -113,9 +108,9 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
return this.toForm();
}
updateData() {
async updateData() {
if (this.isFetchingData) {
return;
return Promise.resolve();
}
this.isFetchingData = true;
return this.fetchObjects(this.query)
@ -140,6 +135,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
connectedCallback(): void {
super.connectedCallback();
this.dataset.akControl = "true";
this.updateData();
this.addEventListener(EVENT_REFRESH, this.updateData);
}

View File

@ -29,7 +29,6 @@ const metadata: Meta<SearchSelectMenu> = {
export default metadata;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onClick = (event: SearchSelectSelectMenuEvent) => {
const target = document.querySelector("#action-button-message-pad");
target!.innerHTML = "";

View File

@ -0,0 +1,16 @@
import type { SearchOptions, SearchTuple } from "./types.js";
type Pair = [string, string];
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
export function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
const pairs: Pair[] = Array.isArray(options)
? options.map(justThePair)
: options.grouped
? options.options.reduce(
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
[] as Pair[],
)
: options.options.map(justThePair);
return new Map(pairs);
}