Skip to content

chore: upgrade to ESLint 10 flat config and lint all packages - #5220

Open
Frank3K wants to merge 13 commits into
google:masterfrom
Frank3K:chore/eslint-v10-lint-all-packages
Open

Frank3K wants to merge 13 commits into
google:masterfrom
Frank3K:chore/eslint-v10-lint-all-packages

Conversation

@Frank3K

@Frank3K Frank3K commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Reference Issue

None. Linting has effectively been off: ESLint 9 no longer reads .eslintrc.yaml / .eslintignore, and the ignore file excluded every package anyway (it was set up in #992 to lint only the since-removed 3DOM package). The lint step in CI was commented out.

Summary

Upgrades to ESLint 10 with a flat config, lints every package, fixes all errors, and turns lint back on in CI. clang-format stays the formatter; ESLint only checks correctness.

Tooling

  • eslint.config.mjs replaces .eslintrc.yaml and .eslintignore. It uses the recommended presets: @eslint/js, typescript-eslint, eslint-plugin-wc (best-practice) and eslint-plugin-mocha for tests. These presets contain no formatting rules, so they don't conflict with clang-format.
  • Dropped the deprecated eslint-config-google.
  • npm run lint now covers all packages, including JS/MJS build scripts and examples. Ignored: .gitignored output, *.d.ts, shared-assets and vendored third_party/ code.
  • Rule choices:
    • no-explicit-any is a warning for now (216 left), with ignoreRestArgs because TypeScript mixins need ...args: any[].
    • mocha/no-mocha-arrows is off, to match the test style.
    • no-unused-expressions is off in tests, for chai assertions like expect(x).to.be.true.
    • no-unexpected-multiline is off: clang-format itself puts call arguments and tagged templates on the next line.
  • Adds an .editorconfig (2-space indent, LF, final newline, trim trailing whitespace except in Markdown) and a whitespace-only cleanup commit (git diff -w is empty). These are the first two commits and can be split into their own PR if preferred.
  • CI: lint runs in unit-tests.yml right after npm ci. Warnings don't fail the build.

Fixes for lint errors
Mostly mechanical: braces, const, strict equality, removed useless escapes, block-scoped case bodies, Object.prototype.hasOwnProperty.call, specific types instead of Function / {}, and removed unused code. Changed lines were reformatted with git clang-format only.

Lint also found real bugs, fixed in their own commit:

  • Interaction prompt: the elapsed time was reset on every visibility change, so the animation never resumed where it paused when the tab was hidden.
  • space-opera validation: if (this.severityTitle = 'Converted') assigned instead of compared, so the model URL was revoked on every model change.
  • exportScene(): if the setup before the export failed, the returned promise never settled. Only exporter.parse is now wrapped in the Promise.
  • custom-select.js example: listeners were added again on every reconnect, and the document listener was never removed.
  • render-fidelity-tools compare-results.ts: used require() in ESM output, so it couldn't run.
  • scene-graph-spec.ts: two variant tests returned from inside their search loop and never asserted anything. With break, the Inverse test passes. The Yellow Red test fails (yellow instead of red), so it is test.skipped with a TODO; it needs a separate look.

No breaking changes

I built master and this branch (production build) and compared the unminified dist bundles of model-viewer and model-viewer-effects:

  • ESM, module and UMD bundles: all changes are equivalent rewrites plus the bug fixes above. The ES2017 output contains no ?., since TypeScript compiles it away. The == → === changes only compare values of the same type, including where public properties are involved (glitch strength is clamped to a number first). GLSL template strings only lost trailing whitespace.
  • Size: unminified bundles grow by 0.5–1 KB; model-viewer.min.js is 23 bytes smaller.
  • Types (lib/*.d.ts is published): updated(changedProperties) is now typed Map<…, unknown> instead of any. A consumer file overriding updated with Map<string, any>, Lit's PropertyValues or the new type compiles against both versions. Helper signatures that would have become stricter (throttle, debounce, getValueOfEnum) were kept as they were.

Testing

  • npm run lint: 0 errors, 216 warnings (no-explicit-any).
  • tsc --noEmit passes for all five packages.
  • Ran the affected model-viewer specs locally in Chromium (scene-graph, controls, styles). The only change from master is the newly asserting variant tests described above. The full suite was not run locally.

Notes

  • The npm clang-format package ships an x86-only macOS binary, so formatting on Apple Silicon used Homebrew clang-format 23, and only on changed lines.
  • The husky pre-commit hook in package.json still uses the husky v4 format, which husky 9 ignores. Not changed here.

Keep line endings, final newlines, trailing whitespace and indentation
consistent across editors for files clang-format does not cover.
Strip trailing whitespace and convert two CRLF files to LF. Markdown is
left untouched since trailing spaces can be significant there.
Whitespace-only change: git diff -w shows nothing.
Replace the legacy .eslintrc.yaml/.eslintignore, which ESLint 9+ no
longer reads and which ignored every package, with eslint.config.mjs.

- eslint 10, @eslint/js and typescript-eslint recommended presets,
  eslint-plugin-wc best-practice, eslint-plugin-mocha recommended for
  tests
- drop the deprecated eslint-config-google; clang-format owns formatting
- no-explicit-any is a warning for now
- mocha/no-mocha-arrows and no-unused-expressions are off in tests to
  match the arrow-function style and chai assertions
- npm run lint now lints every package, including JS/MJS files
Output of `eslint . --fix` (curly, prefer-const, no-var,
explicit-member-accessibility, no-wrapper-object-types, prefer-as-const,
no-extra-boolean-cast, ...). Changed lines were reformatted with
git-clang-format. Vendored third_party code is now ignored.
- drop unnecessary backslashes in regular expressions and strings
- use strict equality; comparisons against undefined become == null to
  keep matching both null and undefined
- dropdown compares Number(selected), since paper-listbox's selected
  may be a string
- super.x && super.x() becomes super.x?.()
- use optional catch bindings where the error is unused
- replace comma operators with statements in camera_settings
- drop type-only $CombinedState imports; declarations still emit
- use the Category interface instead of a placeholder constant
- comment out imports only used by commented-out test-runner config
- controls: keep the elapsed interaction-prompt time across visibility
  changes; it was reset on every call
- validation: compare severityTitle instead of assigning it, so the
  glTF URL is only revoked after a conversion
- exportScene: only wrap exporter.parse in the Promise, so errors from
  the awaited setup reject instead of leaving it pending; drop async
  from Promise executors that never await
- custom-select example: set up shadow-DOM listeners once in the
  constructor and remove the document listener on disconnect
- compare-results: read JSON with fs, as require() is unavailable in
  the ESM output
- scene-graph-spec: the variant-revert tests returned from inside the
  search loop and never asserted; use break. The 'Yellow Red' case
  fails once it runs, so it is skipped with a TODO
- wrap lexical declarations in case blocks
- Object.prototype.hasOwnProperty.call instead of obj.hasOwnProperty
- replace Function and {} types with specific types
- assert non-null instead of mixing ?. with ! in tests
- attach caught errors as cause (adds es2022.error lib to
  render-fidelity-tools)
- drop unneeded @ts-ignore comments, this aliases, dead initial
  assignments and a full-width space
- comment empty catch blocks
- turn off no-unexpected-multiline, which conflicts with clang-format's
  layout of calls and tagged templates
Lint runs right after npm ci so it fails fast. Warnings
(no-explicit-any) do not fail the build.
- Lit update/updated overrides take Map<string|number|symbol, unknown>,
  matching existing usage; one caller now casts the old value
- throttle/debounce are generic over the wrapped function's arguments
- type render-fidelity configs as ImageComparisonConfig and let
  puppeteer/child_process handler types be inferred
- catch bindings are unknown and cast to Error where .message is read
- allow `...args: any[]` (ignoreRestArgs), which TypeScript mixins
  require

no-explicit-any warnings: 269 -> 217.
User-Agent Client Hints are missing from TypeScript's DOM lib, so
declare the one method we use and move the XR form-factor check into a
helper. Behavior is unchanged.
lib/ is published and its .d.ts files are public. Restore the original
throttle/debounce (any) and getValueOfEnum (T extends Object)
signatures so callers that deep-import them are not broken by stricter
types.
The zizmor check fails on pull requests that change a workflow file
containing actions referenced by tag. Pin every action to the commit
its current tag points to, with the version in a comment, and give the
read-only workflows an explicit `contents: read` permission.

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.

1 participant