Skip to content

chore: migrate to @nextcloud/eslint-config 9 - #248

Merged
oleksandr-nc merged 1 commit into
mainfrom
chore/eslint-config-9
Sep 29, 2026
Merged

oleksandr-nc merged 1 commit into
mainfrom
chore/eslint-config-9

Conversation

@oleksandr-nc

@oleksandr-nc oleksandr-nc commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

@nextcloud/eslint-config 9 brings ESLint 10, which reads a flat config only and ignores .eslintrc.*.

The config

.eslintrc.cjs becomes eslint.config.js, spreading recommendedJavascript because the components here are plain JavaScript. The overrides that still exist are kept, no-console is spelled out because the old config had it built in, and three things are dropped: import/extensions, since the shared config no longer ships eslint-plugin-import, the babel parser options, and the appVersion global the sources never use.

The config now knows the library

Version 9 adds @nextcloud/no-deprecated-library-props, which holds the prop renames of @nextcloud/vue and gates each one on the installed version. It declares itself fixable, but none of its forty reports carries a fixer — eslint --fix only camelCases the attribute and leaves the error standing — so it pointed at NcPopover's :focus-trap="false" and the rename to noFocusTrap, together with inverting the value, is by hand.

It was worth doing. The old spelling is not declared any more, so NcPopover built its focus trap, and the content of this popover is a name with nothing tabbable in it — focus-trap refuses to activate around that and throws. Every time the popover opened it logged Your focus-trap must have at least one container with at least one tabbable node in it at all times and left tabindex="-1" on the popper; with noFocusTrap the trap is never built, the popper keeps tabindex="0" and the errors are gone.

One attribute that said nothing at all

Three listeners of the issue and pull request reference widget carried the .native modifier. The Vue 3 compiler drops it and keeps the listener — @mouseenter.native compiles to onMouseenter — so these listeners worked and removing the modifier changes nothing beyond satisfying the rule. That holds for mouse events: on a key event the modifier is compiled as a key name and the handler never fires.

One spinner nobody could hear

NcLoadingIcon was handed the name of the spinner as title. The prop is name, and the component writes it into the aria-label of a <span role="img">, so the spinner of the author popover rendered as aria-label="" — an image with no accessible name — while the translated Loading data sat on a title attribute that only a mouse can reach. name puts it where assistive technology reads it, and adds the <title> inside the svg.

One prop that had stopped working

The widget passed its label as showMoreText. The prop of NcDashboardWidget is showMoreLabel, so the link under a full widget read More items … instead of the label the widget asked for.

Two more strings that could never appear go with it: a showMoreText entry in data() that the template never read, and :emptyContentMessage, which NcDashboardWidget only renders as the fallback of an empty-content slot this widget always supplies.

The sources

What the new rules ask for, which eslint --fix applied: imports sorted, blank lines between multi line component options, attributes and events camel cased in templates. By hand: unused callback parameters are gone, and the dashboard component is called GithubDashboard, since a component name of one word can collide with an HTML element.

One rule asks for the ellipsis character in a translatable string, which pointed at the widget's Loading.... NcDashboardWidget draws its own placeholder while loading is set and only renders the empty-content slot once it is not, so that branch can never be shown: it is gone rather than retranslated, and the app ends up with one source string fewer instead of one more.

Along the way

The rules of this app apply to its own sources, so the exemptions version 9 grants test files survive. The change filters of both workflows named .eslintrc.* and an .eslintignore this app never had, and name the config file now — which their **.js entry already matches, by the extension this app happens to use. webpack.js configured ESLint the old way and could not run at all, since nothing declares its dependencies, so it is gone. And eslint.config.js is excluded from the released archive, where neither the .eslintrc.js of the packaging lists nor the webpack.js and webpack.config.js they excluded exist.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@oleksandr-nc
oleksandr-nc force-pushed the chore/eslint-config-9 branch 8 times, most recently from 2d5abd6 to 6417d4e Compare September 29, 2026 13:00
Its ESLint 10 reads a flat config only, so .eslintrc.cjs becomes eslint.config.js,
whose rules apply to the app sources, leaving the exemptions version 9 grants test
files in place. The lint workflow watched .eslintrc.* and .eslintignore, neither of
which exists any more, and webpack.js was the last place configuring ESLint the old
way: nothing declares its dependencies, so it could not run at all.

The import rules are gone from the shared config, so 'import/extensions' goes with
them, as do the babel parser options and the appVersion global the sources do not
use, which the shared config declares anyway. 'no-console' was built into the old
config and is now spelled out.

The sources follow what the new rules ask for: sorted imports, blank lines between
multi line component options, and camel cased attributes and events in templates.
Unused callback parameters are gone, and the dashboard component is called
GithubDashboard, because a name of one word can collide with an HTML element. One rule
asks for the ellipsis character in a translatable string and so pointed at the widget's
'Loading...' message, which the dashboard component can never show, because it draws
its own placeholder while it loads: the branch is gone rather than retranslated.

The shared config brings @nextcloud/no-deprecated-library-props, which holds the prop
renames of the library, gates each on the installed version and reports them. It calls
itself fixable, but none of its forty reports carries a fixer, so it only pointed at
NcPopover's focus-trap: the rename to noFocusTrap, and inverting the value it carried,
are by hand. The old spelling was not declared any more, so the popover built a focus
trap around content that holds a name and nothing tabbable, and focus-trap threw every
time the popover opened. The .native modifier of three mouse listeners is one the Vue 3
compiler drops while keeping the listener, so removing it changes nothing; that holds
for these three, not for key events, where the modifier is compiled as a key name and
swallows the handler.

NcLoadingIcon was given the name of its spinner as 'title'. The prop is called name,
and the component writes it into the aria-label of a span with role img, so the spinner
of the author popover carried an empty accessible name while the translated string sat
on a tooltip.

The widget passed its label to the dashboard component as 'showMoreText'. The prop is
called showMoreLabel, so the link under a full widget read 'More items …' instead of
what the widget asked for. Two more strings the widget cannot show go with it: a data
property of the old name that the template never read, and the emptyContentMessage the
dashboard component only uses as the fallback of a slot this widget always fills.

Folding a comment, the popover of a comment author, the name of its spinner and the
link under a full widget had no tests, and now have them.

eslint.config.js has no place in the released archive, and neither the .eslintrc.js
the packaging lists named nor the webpack.js and webpack.config.js they excluded
exist here. The lint scripts passed --ext, which ESLint 10 ignores. The change filters
named .eslintrc.* and an .eslintignore this app never had, and name the config file
now, which their '**.js' entry matches only by the extension this app happens to use.

Signed-off-by: Oleksander Piskun <oleksandr2088@icloud.com>
@oleksandr-nc
oleksandr-nc merged commit 5d8002b into main Sep 29, 2026
39 checks passed
@oleksandr-nc
oleksandr-nc deleted the chore/eslint-config-9 branch September 29, 2026 17:02
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