Skip to content

build(lint): migrate to eslint 10 + @nextcloud/eslint-config 9 - #146

Merged
rubenvdlinde merged 1 commit into
developmentfrom
feat/eslint-10
Aug 14, 2026
Merged

build(lint): migrate to eslint 10 + @nextcloud/eslint-config 9#146
rubenvdlinde merged 1 commit into
developmentfrom
feat/eslint-10

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Brings the template onto the stack Nextcloud's own apps run: eslint 10 + @nextcloud/eslint-config@9, flat config, ESM. New apps scaffolded from this repo now start there instead of on eslint 8. eslint.config.mjs here is the fleet's canonical copy.

Follows ConductionNL/larpingapp#325 (the pilot) and ConductionNL/.github#450 (Node 22 default).

Four things that must be right together

1. Node 22. @nextcloud/eslint-config@9 declares engines.node: ^22.14 || ^24 || >=26 and imports findPackageJSON from node:module (first available in 22.14). On Node 20 eslint dies before linting a single file.

2. The two peer dependencies. vue-eslint-parser is not a dependency of the NC config — it is a peer of the eslint-plugin-vue@10 it bundles, so the app supplies it at ^10; @typescript-eslint/parser must be resolvable at top level because vue-eslint-parser requires it by name. Get this wrong and typescript-eslint/base — the only other config object claiming **/*.vue — parses every SFC as TypeScript. Every .vue file then fails with Parsing error, and eslint reports a parse failure as one finding and lints nothing else in that file, so the entire Vue layer goes unchecked while the problem count looks small.

3. An overrides entry for nc-vue's eslint peer. nc-vue declares eslint: "^8.56.0 || ^9.0.0" as an optional peer — optional means npm won't install it, not that a mismatched version is accepted — so a fresh npm install ERESOLVEs against eslint 10. "@conduction/nextcloud-vue": { "eslint": "$eslint" } fixes it declaratively: no --force, no --legacy-peer-deps. The constraint is inert for us — it exists only because nc-vue ships an eslint preset this config no longer imports. ⚠️ An existing lockfile hides this, because npm ci doesn't re-resolve peers.

4. The jsdoc override must carry NC's own ignores. v9 registers the jsdoc plugin only inside nextcloud/documentation/*, and every one of those blocks ignores **/tests/**, **/*.spec.* etc. — Nextcloud doesn't require JSDoc in tests. Naming a jsdoc/* rule for a test file references a plugin that isn't registered there, and eslint refuses to run at all: this took out all 12 files under tests/ while src/ was fine.

conductionVue3Fixes and FlatCompat are gone

Measured with --print-config against v9: 21 of 21 vue/no-deprecated-* enabled, and vue/no-v-model-argument / vue/no-v-for-template-key — the two inverted Vue-2 rules the preset had to switch off — aren't enabled at all. Nothing left to correct.

Code fixed rather than rules relaxed

NC sets no-console: [2,{}] with no allowances. Policy is stricter-than-Nextcloud is fine, different is not:

  • 5 console statements → src/logger.js (@nextcloud/logger), as NC's own apps do
  • let x = null before a try/catch that always assigns → no initialiser
  • v == nullv === null || v === undefined
  • a rethrow now carries { cause: err }, so the original stack survives
  • ./_app-url./_app-url.ts, verified with playwright test --list (still 10 tests in 3 files)
  • ExampleWidget's title prop was declared, passed by src/exampleWidget.js, and never read — now bound to the table's ariaLabel, giving the list an accessible name it lacked under hideHeader
  • 9 template attributes kebab → camelCase, NC's v9 house style (vue/attribute-hyphenation: [2,"never"])

The only widening is _/__ throwaways in tests: varsIgnorePattern: '^_+$' is underscores only, so a real name starting with _ is still reported; argsIgnorePattern: '^_' matches NC's own TypeScript block.

⚠️ perfectionist/sort-imports moved two SPDX headers

It carries a leading comment along with its import, which pushed the SPDX header into the middle of the import block in src/store/modules/{object,settings}.js. Restored. Worth checking on every app that runs --fix. The two other files whose SPDX sits below line 3 were verified as pre-existing and legitimate (inside a <script> block and a docblock).

Verified

check result
npm run lint PASS — eslint src tests --max-warnings 0, so zero warnings too
npm run format PASS
npm run stylelint PASS
npm run check:specs PASS
npm run build PASS
coverage 30 files linted, all 8 of 8 .vue parsed, 0 fatal
positive control 7 planted violations in a .vue probe caught

Brings the template onto the stack Nextcloud's own apps run (nextcloud/forms
is the reference): eslint 10, flat config, ESM. New apps scaffolded from
this repo now start there instead of on eslint 8.

Follows ConductionNL/larpingapp#325, which piloted this. `eslint.config.mjs`
here is the fleet's canonical copy — the only parts an app should change are
the app-specific globals and file-scoped exemptions at the end.

FOUR THINGS THAT HAVE TO BE RIGHT TOGETHER
------------------------------------------
1. Node 22. `@nextcloud/eslint-config@9` declares
   `engines.node: ^22.14 || ^24 || >=26` and imports `findPackageJSON` from
   `node:module`, first available in 22.14. On Node 20 eslint dies before
   linting a single file. The shared workflows now default to Node 22
   (ConductionNL/.github#450); `.nvmrc` and `engines.node` say the same here.

2. The two peer dependencies. `vue-eslint-parser` is NOT a dependency of
   @nextcloud/eslint-config — it is a peer of the `eslint-plugin-vue@10` it
   bundles, so the app must supply it at ^10, and `@typescript-eslint/parser`
   must be resolvable at the top level because vue-eslint-parser requires it
   by name. Get this wrong and `typescript-eslint/base` — the only other
   config object claiming `**/*.vue` — parses every SFC as TypeScript, every
   `.vue` file fails with `Parsing error`, and since eslint reports a parse
   failure as ONE finding and lints nothing else in the file, the whole Vue
   layer goes unchecked while the problem count looks small.

3. An `overrides` entry for nc-vue's eslint peer. It declares
   `eslint: "^8.56.0 || ^9.0.0"` as an OPTIONAL peer — optional means npm
   will not install it, NOT that a mismatched version is accepted — so a
   fresh `npm install` fails with ERESOLVE against eslint 10.
   `"@conduction/nextcloud-vue": { "eslint": "$eslint" }` resolves it
   declaratively, with no --force and no --legacy-peer-deps. The constraint
   is inert for us: it exists because nc-vue ships an eslint preset this
   config no longer imports. Remove the override once nc-vue widens the
   range.

4. The jsdoc override must carry NC's own `ignores`. v9 registers the jsdoc
   plugin ONLY inside `nextcloud/documentation/*`, and every one of those
   blocks ignores `**/tests/**`, `**/*.spec.*` and friends — Nextcloud does
   not require JSDoc in tests. Naming a `jsdoc/*` rule for a test file
   therefore references a plugin that is not registered there and eslint
   refuses to run at all: with `lint: "eslint src tests"` this took out ALL
   12 files under tests/ while src/ was fine.

conductionVue3Fixes AND FlatCompat ARE GONE
-------------------------------------------
Measured with --print-config against v9: 21 of 21 `vue/no-deprecated-*` are
enabled, and `vue/no-v-model-argument` / `vue/no-v-for-template-key` — the
two Vue-2 rules the preset had to switch off because they are inverted under
Vue 3 — are not enabled at all. Nothing left for it to correct.

CODE FIXED RATHER THAN RULES RELAXED
------------------------------------
NC sets `no-console: [2,{}]` with no allowances and `no-unused-vars` with
`ignoreRestSiblings: false` / `caughtErrors: 'all'`. Fleet policy is that we
may be STRICTER than Nextcloud, never merely different:

- 5 console statements -> `src/logger.js` (@nextcloud/logger), the logger
  Nextcloud's own apps use; it tags each line with app id and acting user.
- `let x = null` before a try/catch that always assigns -> no initialiser
  (`no-useless-assignment`).
- `v == null` -> `v === null || v === undefined` (`eqeqeq`).
- a rethrow inside catch now carries `{ cause: err }`
  (`preserve-caught-error`), so the original stack survives.
- `./_app-url` -> `./_app-url.ts` (`import-extensions/extensions`), verified
  with `playwright test --list`: still 10 tests in 3 files.
- ExampleWidget's `title` prop was declared, passed by src/exampleWidget.js
  and never read (`vue/no-unused-properties`). It is now bound to the
  table's `ariaLabel`, which gives the list an accessible name it lacked
  under `hideHeader` — the dashboard chrome already draws the visible
  heading, so rendering it again would duplicate it.
- 9 template attributes kebab -> camelCase, by --fix. This is NC's v9 house
  style, verified with --print-config:
  `vue/attribute-hyphenation: [2,"never"]`.

The only widening is in the tests block: `_`/`__` throwaway bindings.
varsIgnorePattern is `^_+$` — underscores ONLY, so a real name that merely
starts with `_` is still reported — while argsIgnorePattern is `^_`, which
is exactly what NC's own TypeScript block uses.

⚠️ `perfectionist/sort-imports` MOVED TWO SPDX HEADERS into the middle of an
import block (it carries a leading comment with its import). Restored to the
top of `src/store/modules/{object,settings}.js`. Worth checking on every app
that runs --fix; the two other files whose SPDX sits below line 3 were
verified as pre-existing and legitimate (inside a `<script>` block and a
docblock).

VERIFIED
--------
  npm run lint         PASS (eslint src tests --max-warnings 0 — no warnings)
  npm run format       PASS
  npm run stylelint    PASS
  npm run check:specs  PASS
  npm run build        PASS
  30 files linted, all 8 .vue parsed, 0 fatal
  positive control     7 planted violations in a .vue probe are caught
@github-actions

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/nextcloud-app-template @ 3a9306c

Check PHP Vue Security License Tests
lint
phpcs
phpmd
psalm
phpstan
phpmetrics
eslint
stylelint
build
check-specs
check-manifest
format
composer ✅ 103/103
npm ✅ 721/721
app:check-code ⏭️
info.xml
REUSE
PHPUnit
Newman
Playwright
Hydra gates

Quality workflow — 2026-08-14 09:16 UTC

Download the full PDF report from the workflow artifacts.

@rubenvdlinde
rubenvdlinde merged commit b8dd0a7 into development Aug 14, 2026
36 of 38 checks passed
This was referenced Aug 14, 2026
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