Skip to content

Frontend: fix all ESLint findings and add CI enforcement - #1375

Merged
gusthoff merged 7 commits into
AdaCore:mainfrom
gusthoff:dev/topic/infrastructure/pnpm/eslint-fixes/2026-07-24
Jul 24, 2026
Merged

Frontend: fix all ESLint findings and add CI enforcement#1375
gusthoff merged 7 commits into
AdaCore:mainfrom
gusthoff:dev/topic/infrastructure/pnpm/eslint-fixes/2026-07-24

Conversation

@gusthoff

Copy link
Copy Markdown
Collaborator

Summary

pnpm run eslint went from 276 problems (71 errors, 205 warnings) to 0,
and is now enforced in CI so regressions get caught instead of
accumulating silently again.

  • eslint.config.js had an unscoped jsdoc rule block, so ESLint was
    linting tests/html/ — a gitignored, regenerated Sphinx build output
    directory containing furo theme JavaScript, not project code. That
    accounted for 93% of all warnings. Scoped it to match the sibling
    TypeScript config block.
  • Added eslint-plugin-chai-friendly so chai assertions
    (expect(x).to.be.true) aren't misread as dead code by
    no-unused-expressions (26 false-positive errors).
  • Fixed the remaining 71 real errors and 15 real warnings: dead
    variables/imports (including one chai-dom registration that was
    silently relying on Mocha's alphabetical spec-file load order),
    unwrapped long lines, a missing no-explicit-any suppression
    comment, and jsdoc formatting issues.
  • Added .github/workflows/eslint.js.yml, a fast dedicated lint check
    (mirrors code-projects-type-check.yml's shape) rather than folding
    it into typescript-tests.js.yml, which installs a full GNAT/Sphinx
    toolchain unrelated to a lint check.

Test plan

  • pnpm run eslint exits 0
  • pnpm run test — 126 passing, coverage unchanged (99.94%)
  • pnpm install --frozen-lockfile succeeds against the updated lockfile
  • New ESLint CI check passes on this PR

gusthoff and others added 7 commits July 24, 2026 21:11
eslint.config.js registered eslint-plugin-jsdoc's flat/recommended
config with no `files` restriction, unlike the sibling TypeScript
config block that is scoped to src/**/*.ts and tests/**/*.ts. Because
ESLint's flat config does not consult .gitignore, this meant that any
file ESLint happened to find under tests/ was linted with the jsdoc
rules, including frontend/tests/html/ -- a Sphinx documentation build
output directory (gitignored, regenerated by the test suite) that
contains furo theme JavaScript (search stemmers, sphinx_highlight.js,
etc.) copied there by Sphinx itself, not written by this project.

This accounted for 190 of 205 eslint warnings (93%) whenever that
directory happened to exist on disk from a prior local build/test run.

Fixed by adding a global `ignores: ['tests/html/**']` entry and scoping
the jsdoc config block to the same files glob as the TypeScript block.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@typescript-eslint/no-unused-expressions doesn't know that a chai
assertion chain like expect(x).to.be.true is a meaningful statement --
it just sees a property-access expression whose result is discarded,
and flags it as dead code. This produced 26 false-positive errors
across the test spec files, none of which were real bugs.

Added eslint-plugin-chai-friendly and swapped in its chai-aware
no-unused-expressions rule, scoped to tests/**/*.ts, in place of the
plain rule. src/**/*.ts keeps the plain rule since it has no chai
assertions to misread.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Every *.spec.ts file called chai's use() to register a plugin
  (chai-dom, chai-as-promised) but captured the return value in an
  unused `chai` binding; only the side effect of registering the
  plugin matters, so the binding is dropped.
- download.spec.ts imported chai-as-promised but never registered it
  nor used any of its assertions (no .eventually, .rejectedWith, etc.
  anywhere in the file) -- removed as genuinely dead.
- widget.spec.ts had chai-dom's registration commented out
  (`// const chai = use(chaiDom);`) even though this file's own tests
  use the chai-dom `.to.have.class()` matcher; it only worked because
  Mocha loads spec files alphabetically and every other spec file
  registers chai-dom first. Restored the registration so this file
  doesn't silently depend on load order, and dropped its unused
  `Client` import from mock-socket (never referenced).
- Removed two copies of `const identifier = 123;` and one
  `let receivedMessages: Array<string> = [];` in widget.spec.ts that
  were declared but never read anywhere in their scope (verified
  against their sibling blocks, which do use same-named variables for
  real).
- Renamed unused `(event)` callback parameters to `(_event)` in the
  mock-socket message handlers across server.spec.ts and
  widget.spec.ts, and added `argsIgnorePattern: '^_'` to the
  no-unused-vars rule in eslint.config.js -- widget.spec.ts already had
  one `_event` occurrence that was still being flagged because this
  option was never set.
- Dropped `@typescript-eslint/no-empty-function` from an
  eslint-disable comment in widget.spec.ts: the rule was never
  reporting a problem there in the first place.

Verified with `pnpm run test`: 126 passing, no change in behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
23 lines across the test spec files exceeded the 80-column limit.
Reflowed each with the codebase's existing continuation style
(indent the wrapped remainder relative to the statement, or break
chained assertions at the dot), and split one nested type assertion
into an intermediate id variable + as-cast rather than wrapping across
the "as" keyword, which the TypeScript parser used here does not
accept split across lines.

Verified with `pnpm run test`: 126 passing, no change in behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
download.spec.ts stubs JSZip.prototype.generateAsync to capture the
list of files being zipped, and already suppresses no-explicit-any at
the two other (JSZip.prototype as any) casts used to install and type
that stub -- JSZip's public TypeScript types don't expose this
internal method. The after() hook that restores the original function
used the same cast but was missing the matching suppression comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- resource.ts: dropped the @export tag from two type declarations.
  @export is a Closure Compiler annotation, not a recognized JSDoc/TSDoc
  tag, and is redundant here anyway since TypeScript's own `export`
  keyword already marks these types as exported.
- scrolltop.spec.ts, server.spec.ts, widget.spec.ts: removed blank
  comment lines between a function's description and its @param tags
  (jsdoc/tag-lines wants none), added missing @param descriptions on
  scrollTo's x/y parameters, and fixed two copies of the
  removeListeners doc comment whose `*` continuation lines were missing
  their leading space (jsdoc/check-alignment).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pnpm run eslint was never wired into any CI workflow, so lint
regressions could accumulate indefinitely without anyone noticing
(this is exactly how frontend/eslint.config.js ended up silently
linting generated build output, and how 71 real errors piled up in
the test spec files).

Added as its own workflow rather than a step in typescript-tests.js.yml:
that workflow's setup installs the full GNAT/gnatprove/gprbuild
toolchain and builds the Sphinx HTML test fixtures before any test
runs, none of which ESLint needs. A dedicated workflow mirrors the
existing code-projects-type-check.yml (pyright) -- a fast, narrow
static-analysis check that fails in seconds and is unambiguous about
what broke.

Verified locally: `pnpm install --frozen-lockfile && pnpm run eslint`
exits 0 against the current lockfile and source tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gusthoff
gusthoff merged commit b2d72a0 into AdaCore:main Jul 24, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant