Skip to content

[TASK] Fix typescript-eslint findings - #507

Open
maurerle wants to merge 6 commits into
mainfrom
fix/ts-lint-low-hanging
Open

maurerle wants to merge 6 commits into
mainfrom
fix/ts-lint-low-hanging

Conversation

@maurerle

@maurerle maurerle commented Oct 3, 2026

Copy link
Copy Markdown
Member

Description

Fixes some findings from a one-off typescript-eslint 8 run (recommendedTypeChecked) over lib/, reducing them from 601 to 337. Almost all of the remaining ones are any / no-unsafe-*. One commit per category:

  • prefer-const: 233 autofixed let → const.
  • Unused parameters / catch bindings: drop placeholder parameters like _a, _b, _nodeDict that were only there to match an interface (TypeScript allows implementations to omit trailing parameters); catch {} where the error is
    ignored.
  • Unnecessary type assertions: remove casts and ! that don't change the type; L.DomUtil.create<"canvas">(…) instead of as HTMLCanvasElement.
  • {} and redundant unions: object / Record<string, unknown> instead of {}; drop union members swallowed by unknown or string.
  • Floating promises:
    • Bug fix: a config.json that is served but not valid JSON used to leave the loader spinning forever with an unhandled rejection. It now shows "config.json is not valid JSON" with the parse error.
    • Periodic data update: catch failures so a network blip no longer raises an unhandled rejection every minute.
    • Locale download and reverse geocoding: log a warning on failure.
    • Geo layers and fullscreen: marked void (geo errors are already reported via onError).

Intentionally left out:

  • 8 prefer-const where variables are declared up front and assigned later (forcegraph.ts, gui.ts, map.ts); fixing them needs code reordering.
  • any / no-unsafe-* (~310): real typing work, better done incrementally.
  • no-base-to-string, unbound-method, require-await: false positives or test mocks.

Motivation and Context

The eslint config did only lint .js/.mjs files, so none of the 70 .ts files in lib/ are linted today. @typescript-eslint/parser was listed in devDependencies but never wired into the config.

typescript-eslint doesn't support TypeScript 7 yet (peer range <6.1.0, and TS 7 no longer ships the classic compiler API it needs), so adding it would block the TS 7 bump. This PR only cleans up the code so that enabling it later is a smaller step.

How Has This Been Tested?

  • tsc --noEmit, npm run build, npm run lint and vitest (72 tests) pass.
  • typescript-eslint re-run (installed with --no-save, not committed) confirms the listed rules are clean.
  • Invalid-JSON config.json path checked with a throwaway unit test.
  • Not tested manually in browsers or on mobile; apart from the error-handling paths there is no runtime behaviour change.

Screenshots/links:

None, no visual changes. Only the error text shown for an invalid config.json is new.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.

github-actions Bot pushed a commit that referenced this pull request Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployed: https://freifunk.github.io/meshviewer/pr-preview/pr-507/

Updated for commit 42b6917.

Mechanical fix from eslint's prefer-const (via typescript-eslint's
recommended preset), applied with --fix.

Assisted-by: claude:opus-5.5
Implementations may omit trailing parameters of the callback type they
satisfy, so the placeholder _a/_b/_n/_d/_nodeDict arguments are not
needed. Use optional catch binding where the error is ignored.

Assisted-by: claude:opus-5.5
Drop casts and non-null assertions that do not change the type, and use
an explicit type argument for L.DomUtil.create instead of a cast.

Assisted-by: claude:opus-5.5
{} admits any non-nullish value; use object / Record<string, unknown>
instead. Drop union members that are swallowed by unknown or string.

Assisted-by: claude:opus-5.5
- load: show a loader error when config.json is not valid JSON; before,
  the rejection was unhandled and the loader spun forever.
- main: catch failures of the periodic data update so a network blip
  does not raise an unhandled rejection every minute.
- language, infobox/location: log failed locale and reverse-geocoding
  requests.
- map, domUtils: mark fire-and-forget promises with void; geo layer
  errors are already reported through onError.

Assisted-by: claude:opus-5.5
Covers offline, non-ok HTTP status, invalid JSON in config.json and the
happy path, where config.json is merged onto the defaults before main()
starts.

Assisted-by: claude:opus-5.5
@maurerle
maurerle force-pushed the fix/ts-lint-low-hanging branch from 287cc29 to 42b6917 Compare October 4, 2026 07:01
github-actions Bot pushed a commit that referenced this pull request Oct 4, 2026
@maurerle
maurerle marked this pull request as ready for review October 4, 2026 07:02

@rotanid rotanid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few findings to consider:

1. HTML injection / XSS risk in lib/load.ts (showLoaderError)

In lib/load.ts:

  let config: unknown;
  try {
    config = await configResponse.json();
  } catch (e) {
    showLoaderError(
      "config.json is not valid JSON:<br>" + (e instanceof Error ? e.message : String(e)),
      "or report to your community",
    );
    return;
  }

showLoaderError injects message directly into innerHTML:

const showLoaderError = (message: string, hint = "") => {
  document.querySelector(".loader")!.innerHTML =
    message +
    "<br><br>" +
    '<button onclick="location.reload(true)" class="btn text" aria-label="Try to reload">' +
    "Try to reload" +
    "</button><br>" +
    hint;
};

In V8/Chromium, when response.json() fails to parse JSON, SyntaxError.message includes a snippet of the unexpected token input (e.g. Unexpected token '<', "<img src=x "... is not valid JSON). If config.json is served from an untrusted origin, or a captive portal / proxy returns an HTML page with HTTP 200, unescaped HTML tags will be parsed and rendered by .innerHTML.

Suggestion:
Escape e.message using the existing escape() helper from lib/utils/escape.ts:

import { escape } from "./utils/escape.js";

// ...
showLoaderError(
  "config.json is not valid JSON:<br>" + escape(e instanceof Error ? e.message : String(e)),
  "or report to your community",
);

2. void operator does not catch rejected promises in lib/utils/domUtils.ts

In lib/utils/domUtils.ts:

const enter =
  fel.requestFullscreen?.bind(fel) ?? fel.webkitRequestFullScreen?.bind(fel) ?? fel.mozRequestFullScreen?.bind(fel);
void enter?.();
// ...
if (exit) {
  void exit();
}

The void keyword discards the return value of an expression to satisfy @typescript-eslint/no-floating-promises, but it does not catch runtime Promise rejections. If requestFullscreen() or exitFullscreen() rejects (e.g. inside an <iframe> lacking allow="fullscreen", or if denied by browser permission policies), an unhandledrejection event is still fired on window.

Suggestion:
Attach a .catch() to explicitly handle the rejection:

enter?.().catch(console.warn);
// ...
exit?.().catch(console.warn);

3. Redundant union Node | object in lib/utils/nodeUtils.ts

In lib/utils/nodeUtils.ts:

export const hasLocation = function hasLocation(data: Node | object)
export const hasUplink = function hasUplink(data: Node | object)

Since Node is an interface, it is already a subtype of object, so Node | object collapses to just object.

Suggestion:
Simplify the parameter type to data: object or data: Record<string, unknown>.


Review conducted with AI assistance (Google Antigravity / Gemini 3.8 Flash).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants