@nextcloud/eslint-config 9.0.0-rc.8 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +26 -12
  2. package/README.md +7 -14
  3. package/dist/configs/codeStyle.js +2 -6
  4. package/dist/configs/imports.js +3 -1
  5. package/dist/configs/javascript.js +2 -2
  6. package/dist/configs/vue.js +6 -6
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.js +0 -1
  9. package/dist/plugins/import-extensions/index.d.ts +1 -0
  10. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.d.ts +7 -0
  11. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.js +176 -0
  12. package/dist/plugins/import-extensions/rules/index.d.ts +1 -0
  13. package/dist/plugins/import-extensions/rules/index.js +2 -0
  14. package/dist/plugins/nextcloud/rules/index.js +16 -4
  15. package/dist/plugins/{l10n/rules/enforce-ellipsis.js → nextcloud/rules/l10n-enforce-ellipsis.js} +7 -1
  16. package/dist/plugins/{l10n/rules/non-breaking-space.js → nextcloud/rules/l10n-non-breaking-space.js} +7 -1
  17. package/dist/plugins/nextcloud/rules/{no-deprecations.js → no-deprecated-globals.js} +5 -2
  18. package/dist/plugins/{nextcloud-vue/rules/no-deprecated-exports.js → nextcloud/rules/no-deprecated-library-exports.js} +4 -3
  19. package/dist/plugins/nextcloud/rules/{no-removed-apis.js → no-removed-globals.js} +5 -2
  20. package/dist/plugins/{nextcloud-vue → nextcloud}/utils/vue-template-visitor.js +3 -1
  21. package/dist/plugins/packageJson.js +0 -1
  22. package/package.json +20 -19
  23. package/dist/plugins/l10n/index.d.ts +0 -10
  24. package/dist/plugins/l10n/index.js +0 -17
  25. package/dist/plugins/l10n/rules/enforce-ellipsis-vue.js +0 -13
  26. package/dist/plugins/l10n/rules/index.d.ts +0 -10
  27. package/dist/plugins/l10n/rules/index.js +0 -14
  28. package/dist/plugins/l10n/rules/non-breaking-space-vue.d.ts +0 -7
  29. package/dist/plugins/l10n/rules/non-breaking-space-vue.js +0 -13
  30. package/dist/plugins/l10n/rules/non-breaking-space.d.ts +0 -7
  31. package/dist/plugins/nextcloud-vue/index.d.ts +0 -49
  32. package/dist/plugins/nextcloud-vue/index.js +0 -13
  33. package/dist/plugins/nextcloud-vue/rules/index.d.ts +0 -38
  34. package/dist/plugins/nextcloud-vue/rules/index.js +0 -10
  35. /package/dist/plugins/{l10n/rules/enforce-ellipsis-vue.d.ts → nextcloud/rules/l10n-enforce-ellipsis.d.ts} +0 -0
  36. /package/dist/plugins/{l10n/rules/enforce-ellipsis.d.ts → nextcloud/rules/l10n-non-breaking-space.d.ts} +0 -0
  37. /package/dist/plugins/{nextcloud-vue/rules/no-deprecated-exports.d.ts → nextcloud/rules/no-deprecated-globals.d.ts} +0 -0
  38. /package/dist/plugins/nextcloud/rules/{no-deprecations.d.ts → no-deprecated-library-exports.d.ts} +0 -0
  39. /package/dist/plugins/{nextcloud-vue/rules/no-deprecated-props.d.ts → nextcloud/rules/no-deprecated-library-props.d.ts} +0 -0
  40. /package/dist/plugins/{nextcloud-vue/rules/no-deprecated-props.js → nextcloud/rules/no-deprecated-library-props.js} +0 -0
  41. /package/dist/plugins/nextcloud/rules/{no-removed-apis.d.ts → no-removed-globals.d.ts} +0 -0
  42. /package/dist/plugins/nextcloud/utils/{version-parser.d.ts → app-version-parser.d.ts} +0 -0
  43. /package/dist/plugins/nextcloud/utils/{version-parser.js → app-version-parser.js} +0 -0
  44. /package/dist/plugins/{nextcloud-vue → nextcloud}/utils/lib-version-parser.d.ts +0 -0
  45. /package/dist/plugins/{nextcloud-vue → nextcloud}/utils/lib-version-parser.js +0 -0
  46. /package/dist/plugins/{nextcloud-vue → nextcloud}/utils/vue-template-visitor.d.ts +0 -0
package/CHANGELOG.md CHANGED
@@ -4,13 +4,23 @@
4
4
  -->
5
5
  # Changelog
6
6
 
7
- ## [v9.0.0](https://github.com/nextcloud-libraries/eslint-config/tree/v9.0.0) (unreleased)
7
+ ## [v9.0.0](https://github.com/nextcloud-libraries/eslint-config/tree/v9.0.0) (2026-06-26)
8
8
 
9
9
  ### Breaking
10
- This package now is using ESLint v9 and requires ESLint flat configurations.
10
+ This package now is using ESLint v10 and requires ESLint flat configurations.
11
11
  Please refer to the README on how to adjust your configuration for flat config.
12
12
 
13
+ #### Potential pitfalls
14
+ Some of the used eslint plugins still keep a ESLint v8 compatibility,
15
+ this can cause some issues if not all packages are updated to the ESLint v10 compatible version.
16
+
17
+ If you see a linter error like the one below make sure to update all nested dependencies,
18
+ either using the sledge hammer method (remove the lock file and run `npm i`) or by running `npm update`.
19
+
20
+ > TypeError: scopeManager.addGlobals is not a function
21
+
13
22
  ### Added
23
+ * feat: migrate to ESLint v10 [\#1323](https://github.com/nextcloud-libraries/eslint-config/pull/1323) \([susnux](https://github.com/susnux)\)
14
24
  * feat: new modular config for (and with) ESLint v9 support [#887](https://github.com/nextcloud-libraries/eslint-config/pull/887)
15
25
  * feat: merge plugin repository [#899](https://github.com/nextcloud-libraries/eslint-config/pull/899)
16
26
  * feat: enforce non-breaking spaces before ellipsis for translations [\#948](https://github.com/nextcloud-libraries/eslint-config/pull/948)
@@ -34,6 +44,7 @@ Please refer to the README on how to adjust your configuration for flat config.
34
44
  * feat(vue3): force camelCase for events in `<template>` [\#1263](https://github.com/nextcloud-libraries/eslint-config/pull/1263) \([ShGKme](https://github.com/ShGKme)\)
35
45
  * feat(vue3): force camelCase for slot names [\#1264](https://github.com/nextcloud-libraries/eslint-config/pull/1264) \([ShGKme](https://github.com/ShGKme)\)
36
46
  * feat(vue3): force camelCase for props in template [\#1266](https://github.com/nextcloud-libraries/eslint-config/pull/1266) \([ShGKme](https://github.com/ShGKme)\)
47
+ * feat(import): enforce consistent types imports and ban usage of inline type specifiers [\#1382](https://github.com/nextcloud-libraries/eslint-config/pull/1382) \([susnux](https://github.com/susnux)\)
37
48
 
38
49
  ### Fixed
39
50
  * fix(codestyle): do not require splitting chains [\#951](https://github.com/nextcloud-libraries/eslint-config/pull/951)
@@ -71,6 +82,7 @@ Please refer to the README on how to adjust your configuration for flat config.
71
82
  * fix: switch to `@stylistic/exp-list-style` to resolve array edge-cases [\#1203](https://github.com/nextcloud-libraries/eslint-config/pull/1203) \([susnux](https://github.com/susnux)\)
72
83
  * fix(nextcloud-plugin): add more removed API in Nextcloud 33 [\#1269](https://github.com/nextcloud-libraries/eslint-config/pull/1269) \([susnux](https://github.com/susnux)\)
73
84
  * fix: use vue-eslint-parser directly [\#1277](https://github.com/nextcloud-libraries/eslint-config/pull/1277) \([susnux](https://github.com/susnux)\)
85
+ * fix(imports): no newline inside import statements [\#1379](https://github.com/nextcloud-libraries/eslint-config/pull/1379) \([max-nextcloud](https://github.com/max-nextcloud)\)
74
86
 
75
87
  ### Changed
76
88
  * Add SPDX header [#802](https://github.com/nextcloud-libraries/eslint-config/pull/802)
@@ -79,6 +91,7 @@ Please refer to the README on how to adjust your configuration for flat config.
79
91
  * refactor: use camelCase for internal variables [#971](https://github.com/nextcloud-libraries/eslint-config/pull/971) ([susnux](https://github.com/susnux))
80
92
  * chore: do not bundle config but just use Typescript [#967](https://github.com/nextcloud-libraries/eslint-config/pull/967)
81
93
  * chore: require node ^20.19 [\#982](https://github.com/nextcloud-libraries/eslint-config/pull/982)
94
+ * chore: remove EOL Node version and add new future LTS [\#1381](https://github.com/nextcloud-libraries/eslint-config/pull/1381) \([susnux](https://github.com/susnux)\)
82
95
  * chore: allow to run `npm lint` without compile step [#975](https://github.com/nextcloud-libraries/eslint-config/pull/975) ([susnux](https://github.com/susnux))
83
96
  * chore: prepare v9.0.0-rc.1 [#1010](https://github.com/nextcloud-libraries/eslint-config/pull/1010) ([susnux](https://github.com/susnux))
84
97
  * docs: consistent badge style with the reuse one [#1060](https://github.com/nextcloud-libraries/eslint-config/pull/1060) ([susnux](https://github.com/susnux))
@@ -92,18 +105,19 @@ Please refer to the README on how to adjust your configuration for flat config.
92
105
  * build: disable libcheck for Typescript [\#1205](https://github.com/nextcloud-libraries/eslint-config/pull/1205) \([susnux](https://github.com/susnux)\)
93
106
  * chore(gitignore): ignore IDE configs [\#1265](https://github.com/nextcloud-libraries/eslint-config/pull/1265) \([ShGKme](https://github.com/ShGKme)\)
94
107
  * test(vue3): add some formatting tests [\#1267](https://github.com/nextcloud-libraries/eslint-config/pull/1267) \([ShGKme](https://github.com/ShGKme)\)
95
- * Updated `@eslint/json` to 0.14.0
108
+ * refactor: merge Nextcloud specific rules into the `@nextcloud` plugin [\#1278](https://github.com/nextcloud-libraries/eslint-config/pull/1278) \([susnux](https://github.com/susnux)\)
109
+ * Updated `@eslint/json` to 2.0.0
96
110
  * Updated `@stylistic/eslint-plugin` to 5.7.0
97
111
  * Updated `eslint-config-flat-gitignore` to 2.1.0
98
- * Updated `eslint-plugin-antfu` to 3.1.3
99
- * Updated `eslint-plugin-jsdoc` to 62.0.0
100
- * Updated `eslint-plugin-perfectionist` to 5.3.1
101
- * Updated `eslint-plugin-vue` to 10.6.2
102
- * Updated `fast-xml-parser` to 5.3.3
103
- * Updated `globals` to 17.0.0
104
- * Updated `semver` to 7.7.3
105
- * Updated `sort-package-json` to 3.6.0
106
- * Updated `typescript-eslint` to 8.53.0
112
+ * Updated `eslint-plugin-antfu` to 3.2.3
113
+ * Updated `eslint-plugin-jsdoc` to 63.0.4
114
+ * Updated `eslint-plugin-perfectionist` to 5.9.1
115
+ * Updated `eslint-plugin-vue` to 10.9.2
116
+ * Updated `fast-xml-parser` to 5.9.0
117
+ * Updated `globals` to 17.7.0
118
+ * Updated `semver` to 7.8.4
119
+ * Updated `sort-package-json` to 4.0.0
120
+ * Updated `typescript-eslint` to 8.62.0
107
121
 
108
122
  ## [v8.4.2](https://github.com/nextcloud-libraries/eslint-config/tree/v8.4.2) (2025-02-16)
109
123
  ### Fixed
package/README.md CHANGED
@@ -28,7 +28,7 @@ npm install --save-dev @nextcloud/eslint-config
28
28
  ### Usage
29
29
 
30
30
  > [!NOTE]
31
- > Since version 9 this package depends on ESLint 9, which uses the new flat config system.
31
+ > Since version 9 this package depends on ESLint 10, which uses the new flat config system.
32
32
 
33
33
  This package provides some predefined configurations you can choose from.
34
34
  For the recommended setup add a file `eslint.config.js` in the root directory of your app repository with the following content:
@@ -80,7 +80,7 @@ export default [
80
80
  files: ['**/*.js'],
81
81
  rules: {
82
82
  // Make deprecations error instead of warning level
83
- '@nextcloud/no-deprecations': ['error'],
83
+ '@nextcloud/no-deprecated-globals': ['error'],
84
84
  }
85
85
  }
86
86
  ]
@@ -97,7 +97,7 @@ export default [
97
97
  '@nextcloud': nextcloudPlugin,
98
98
  },
99
99
  rules: {
100
- '@nextcloud/no-removed-apis': ['error', { targetVersion: '29.0.0' }],
100
+ '@nextcloud/no-removed-globals': ['error', { targetVersion: '29.0.0' }],
101
101
  },
102
102
  }
103
103
  ]
@@ -111,7 +111,7 @@ Rules:
111
111
 
112
112
  ##### `@nextcloud` plugin
113
113
  Rules:
114
- - `no-deprecations`
114
+ - `no-deprecated-globals`
115
115
  - Report usage of deprecated Nextcloud API
116
116
  - Included as `warn` level in recommended configuration
117
117
  - Available options
@@ -130,7 +130,7 @@ Rules:
130
130
  parseAppInfo?: boolean
131
131
  }
132
132
  ```
133
- - `no-removed-apis`
133
+ - `no-removed-globals`
134
134
  - Report usage of removed Nextcloud API
135
135
  - Included as `error` level in recommended configuration
136
136
  - Available options
@@ -149,17 +149,10 @@ Rules:
149
149
  parseAppInfo?: boolean
150
150
  }
151
151
  ```
152
-
153
- ##### `@nextcloud-l10n`
154
- ```ts
155
- import { l10nPlugin } from '@nextcloud/eslint-config'
156
- ```
157
-
158
- Rules:
159
- - `enforce-ellipsis`
152
+ - `l10n-enforce-ellipsis`
160
153
  - Enforce consistent usageof ellipsis instead of tripple dots
161
154
  - Included as `error` level in recommended configuration
162
- - `non-breaking-space`
155
+ - `l10n-non-breaking-space`
163
156
  - Enforce non-breaking spaces before ellipsis
164
157
  - Included as `error` level in recommended configuration
165
158
 
@@ -1,7 +1,6 @@
1
1
  import stylistic from '@stylistic/eslint-plugin';
2
2
  import eslintAntfuPlugin from 'eslint-plugin-antfu';
3
3
  import { GLOB_FILES_JAVASCRIPT, GLOB_FILES_TYPESCRIPT, GLOB_FILES_VUE, } from "../globs.js";
4
- import l10nPlugin from "../plugins/l10n/index.js";
5
4
  /**
6
5
  * Config factory for general code style related rules
7
6
  * See also: https://docs.nextcloud.com/server/latest/developer_manual/getting_started/coding_standards/javascript.html#code-style
@@ -167,13 +166,10 @@ export function codeStyle(options) {
167
166
  ...GLOB_FILES_TYPESCRIPT,
168
167
  ...GLOB_FILES_VUE,
169
168
  ],
170
- plugins: {
171
- '@nextcloud-l10n': l10nPlugin,
172
- },
173
169
  // Enforce that translations use ellipsis instead of tripple dots
174
170
  rules: {
175
- '@nextcloud-l10n/non-breaking-space': 'error',
176
- '@nextcloud-l10n/enforce-ellipsis': 'error',
171
+ '@nextcloud/l10n-non-breaking-space': 'error',
172
+ '@nextcloud/l10n-enforce-ellipsis': 'error',
177
173
  },
178
174
  },
179
175
  ];
@@ -30,6 +30,8 @@ export function imports(options) {
30
30
  rules: {
31
31
  // Require file extensions
32
32
  'import-extensions/extensions': 'error',
33
+ // enforce explicit type imports (no inline types)
34
+ 'import-extensions/ban-inline-type-imports': 'error',
33
35
  // Sorting of imports
34
36
  'sort-imports': 'off',
35
37
  'perfectionist/sort-imports': [
@@ -96,7 +98,7 @@ export function imports(options) {
96
98
  function createSortingConfig(type) {
97
99
  return {
98
100
  type: 'natural',
99
- newlinesBetween: 1,
101
+ newlinesBetween: 0,
100
102
  partitionByNewLine: false,
101
103
  groups: [
102
104
  `type-${type}`,
@@ -62,8 +62,8 @@ export function javascript(options) {
62
62
  '@nextcloud': nextcloudPlugin,
63
63
  },
64
64
  rules: {
65
- '@nextcloud/no-deprecations': ['warn'],
66
- '@nextcloud/no-removed-apis': ['error'],
65
+ '@nextcloud/no-deprecated-globals': ['warn'],
66
+ '@nextcloud/no-removed-globals': ['error'],
67
67
  },
68
68
  files: [
69
69
  ...GLOB_FILES_JAVASCRIPT,
@@ -1,6 +1,6 @@
1
1
  import vuePlugin from 'eslint-plugin-vue';
2
2
  import { GLOB_FILES_JAVASCRIPT, GLOB_FILES_TYPESCRIPT, GLOB_FILES_VUE, } from "../globs.js";
3
- import nextcloudVuePlugin from "../plugins/nextcloud-vue/index.js";
3
+ import { nextcloudPlugin } from "../index.js";
4
4
  import { codeStyle } from "./codeStyle.js";
5
5
  const stylisticRules = codeStyle({
6
6
  isLibrary: false,
@@ -110,8 +110,8 @@ export function vue(options) {
110
110
  // same as the stylistic rules but for the <template> in Vue files
111
111
  ...Object.fromEntries(vueStylisticRules),
112
112
  // same as in the codeStyle config but for the <template> in Vue files
113
- '@nextcloud-l10n/non-breaking-space-vue': 'error',
114
- '@nextcloud-l10n/enforce-ellipsis-vue': 'error',
113
+ '@nextcloud/l10n-non-breaking-space': 'error',
114
+ '@nextcloud/l10n-enforce-ellipsis': 'error',
115
115
  // Also enforce tabs for template
116
116
  'vue/html-indent': [
117
117
  'error',
@@ -146,11 +146,11 @@ export function vue(options) {
146
146
  ...GLOB_FILES_VUE,
147
147
  ],
148
148
  plugins: {
149
- '@nextcloud/vue': nextcloudVuePlugin,
149
+ '@nextcloud': nextcloudPlugin,
150
150
  },
151
151
  rules: {
152
- '@nextcloud/vue/no-deprecated-exports': 'error',
153
- '@nextcloud/vue/no-deprecated-props': 'error',
152
+ '@nextcloud/no-deprecated-library-exports': 'error',
153
+ '@nextcloud/no-deprecated-library-props': 'error',
154
154
  },
155
155
  name: 'nextcloud/vue/migration-rules',
156
156
  },
package/dist/index.d.ts CHANGED
@@ -29,4 +29,3 @@ export declare const recommendedLibrary: Linter.Config[];
29
29
  export declare const recommendedVue2Library: Linter.Config[];
30
30
  export { default as packageJsonPlugin } from './plugins/packageJson.ts';
31
31
  export { default as nextcloudPlugin } from './plugins/nextcloud/index.ts';
32
- export { default as l10nPlugin } from './plugins/l10n/index.ts';
package/dist/index.js CHANGED
@@ -59,7 +59,6 @@ export const recommendedVue2Library = createConfig({
59
59
  });
60
60
  export { default as packageJsonPlugin } from "./plugins/packageJson.js";
61
61
  export { default as nextcloudPlugin } from "./plugins/nextcloud/index.js";
62
- export { default as l10nPlugin } from "./plugins/l10n/index.js";
63
62
  /**
64
63
  * Generate a configuration based on given options
65
64
  *
@@ -4,6 +4,7 @@
4
4
  */
5
5
  declare const _default: {
6
6
  rules: {
7
+ 'ban-inline-type-imports': import("eslint").Rule.RuleModule;
7
8
  extensions: import("eslint").Rule.RuleModule;
8
9
  };
9
10
  meta: {
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-FileCopyrightText: 2015 Ben Mosher
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { Rule } from 'eslint';
7
+ export declare const rule: Rule.RuleModule;
@@ -0,0 +1,176 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-FileCopyrightText: 2015 Ben Mosher
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export const rule = {
7
+ meta: {
8
+ fixable: 'code',
9
+ type: 'suggestion',
10
+ docs: {
11
+ dialects: ['typescript'],
12
+ description: 'Ban the use of inline type-only markers for named imports.',
13
+ },
14
+ messages: {
15
+ preferTopLevel: 'Prefer using a top-level type-only import instead of inline type specifiers.',
16
+ },
17
+ },
18
+ create(context) {
19
+ return {
20
+ ImportDeclaration(node) {
21
+ if (!('importKind' in node)
22
+ || node.importKind === 'type'
23
+ // no specifiers (import {} from '') cannot have inline - so is valid
24
+ || node.specifiers.length === 0
25
+ || (node.specifiers.length === 1
26
+ // default imports are both "inline" and "top-level"
27
+ && (node.specifiers[0].type === 'ImportDefaultSpecifier'
28
+ // namespace imports are both "inline" and "top-level"
29
+ || node.specifiers[0].type === 'ImportNamespaceSpecifier'))) {
30
+ return;
31
+ }
32
+ const typeSpecifiers = [];
33
+ const valueSpecifiers = [];
34
+ let defaultSpecifier;
35
+ for (const specifier of node.specifiers) {
36
+ if (specifier.type === 'ImportDefaultSpecifier') {
37
+ defaultSpecifier = specifier;
38
+ continue;
39
+ }
40
+ if (!('importKind' in specifier) || !specifier.importKind) {
41
+ valueSpecifiers.push(specifier);
42
+ continue;
43
+ }
44
+ if (specifier.importKind === 'type') {
45
+ typeSpecifiers.push(specifier);
46
+ }
47
+ else if (specifier.importKind === 'value') {
48
+ valueSpecifiers.push(specifier);
49
+ }
50
+ }
51
+ const typeImport = getImportText(node, context.sourceCode, typeSpecifiers);
52
+ const newImports = typeImport.trim();
53
+ if (typeSpecifiers.length === node.specifiers.length) {
54
+ // all specifiers have inline specifiers - so we replace the entire import
55
+ context.report({
56
+ node,
57
+ messageId: 'preferTopLevel',
58
+ fix(fixer) {
59
+ return fixer.replaceText(node, newImports);
60
+ },
61
+ });
62
+ }
63
+ else {
64
+ // remove specific specifiers and insert new imports for them
65
+ for (const specifier of typeSpecifiers) {
66
+ context.report({
67
+ node: specifier,
68
+ messageId: 'preferTopLevel',
69
+ fix(fixer) {
70
+ const fixes = [];
71
+ // if there are no value specifiers, then the other report fixer will be called, not this one
72
+ if (valueSpecifiers.length > 0) {
73
+ // import { Value, type Type } from 'mod';
74
+ // we can just remove the type specifiers
75
+ removeSpecifiers(fixes, fixer, context.sourceCode, typeSpecifiers);
76
+ // make the import nicely formatted by also removing the trailing comma after the last value import
77
+ // eg
78
+ // import { Value, type Type } from 'mod';
79
+ // to
80
+ // import { Value } from 'mod';
81
+ // not
82
+ // import { Value, } from 'mod';
83
+ removeCommaAfterNode(fixes, fixer, context.sourceCode, valueSpecifiers[valueSpecifiers.length - 1]);
84
+ }
85
+ else if (defaultSpecifier) {
86
+ // import Default, { type Type } from 'mod';
87
+ // remove the entire curly block so we don't leave an empty one behind
88
+ // NOTE - the default specifier *must* be the first specifier always!
89
+ // so a comma exists that we also have to clean up or else it's bad syntax
90
+ const comma = context.sourceCode.getTokenAfter(defaultSpecifier, isComma);
91
+ const closingBrace = context.sourceCode.getTokenAfter(node.specifiers[node.specifiers.length - 1], (token) => token.type === 'Punctuator' && token.value === '}');
92
+ if (comma && closingBrace) {
93
+ fixes.push(fixer.removeRange([
94
+ comma.range[0],
95
+ closingBrace.range[1],
96
+ ]));
97
+ }
98
+ }
99
+ // insert the new imports after the old declaration
100
+ return fixes.concat(fixer.insertTextAfter(node, `\n${newImports}`));
101
+ },
102
+ });
103
+ }
104
+ }
105
+ },
106
+ };
107
+ },
108
+ };
109
+ /**
110
+ * Check if the given token is a comma
111
+ *
112
+ * @param token - The token to check
113
+ */
114
+ function isComma(token) {
115
+ return token.type === 'Punctuator' && token.value === ',';
116
+ }
117
+ /**
118
+ * Remove the given specifiers from the import declaration, along with any trailing commas if necessary.
119
+ *
120
+ * @param fixes - The array to which the generated fixes will be added
121
+ * @param fixer - The fixer object used to create the fixes
122
+ * @param sourceCode - The source code object used to analyze the code and find tokens
123
+ * @param specifiers - The specifiers to remove from the import declaration
124
+ */
125
+ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
126
+ for (const specifier of specifiers) {
127
+ removeCommaAfterNode(fixes, fixer, sourceCode, specifier);
128
+ fixes.push(fixer.remove(specifier));
129
+ }
130
+ }
131
+ /**
132
+ * Remove the trailing comma
133
+ *
134
+ * @param fixes - The array to which the generated fixes will be added
135
+ * @param fixer - The fixer object used to create the fixes
136
+ * @param sourceCode - The source code object used to analyze the code and find tokens
137
+ * @param node - The node after which to check for a comma and remove it if exists
138
+ */
139
+ function removeCommaAfterNode(fixes, fixer, sourceCode, node) {
140
+ const token = sourceCode.getTokenAfter(node);
141
+ if (token && isComma(token)) {
142
+ const nextToken = sourceCode.getTokenAfter(token);
143
+ // get the empty space to remove double whitespace after removing the comma
144
+ const emptySpace = sourceCode.text
145
+ .slice(token.range[1], nextToken?.range[0] ?? token.range[1])
146
+ .match(/^[ \t]*[\n\r]*/)?.[0] ?? '';
147
+ if (emptySpace) {
148
+ fixes.push(fixer.removeRange([token.range[0], token.range[1] + emptySpace.length]));
149
+ }
150
+ else {
151
+ fixes.push(fixer.remove(token));
152
+ }
153
+ }
154
+ }
155
+ /**
156
+ * Get the text for a new top-level type-only import based on the given inline type specifiers.
157
+ *
158
+ * @param node - The original import declaration node containing the inline type specifiers
159
+ * @param sourceCode - The source code object used to analyze the code and find tokens
160
+ * @param specifiers - The inline type specifiers for which to generate the new import text
161
+ */
162
+ function getImportText(node, sourceCode, specifiers) {
163
+ const sourceString = sourceCode.getText(node.source);
164
+ if (specifiers.length === 0) {
165
+ return '';
166
+ }
167
+ const names = specifiers.map((s) => {
168
+ const name = 'name' in s.imported ? s.imported.name : s.imported.value;
169
+ if (name === s.local.name) {
170
+ return name;
171
+ }
172
+ return `${name} as ${s.local.name}`;
173
+ });
174
+ // insert a fresh top-level import
175
+ return `import type {${names.join(', ')}} from ${sourceString};`;
176
+ }
@@ -3,5 +3,6 @@
3
3
  * SPDX-License-Identifier: AGPL-3.0-or-later
4
4
  */
5
5
  export declare const rules: {
6
+ 'ban-inline-type-imports': import("eslint").Rule.RuleModule;
6
7
  extensions: import("eslint").Rule.RuleModule;
7
8
  };
@@ -2,7 +2,9 @@
2
2
  * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
3
  * SPDX-License-Identifier: AGPL-3.0-or-later
4
4
  */
5
+ import { rule as banInlineTypeImports } from "./ban-inline-type-imports.js";
5
6
  import { rule as extensions } from "./extensions.js";
6
7
  export const rules = {
8
+ 'ban-inline-type-imports': banInlineTypeImports,
7
9
  extensions,
8
10
  };
@@ -1,6 +1,18 @@
1
- import noDeprecations from "./no-deprecations.js";
2
- import noRemovedApis from "./no-removed-apis.js";
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import l10nEnforceEllipsis from "./l10n-enforce-ellipsis.js";
6
+ import l10nNonBreakingSpace from "./l10n-non-breaking-space.js";
7
+ import noDeprecatedGlobals from "./no-deprecated-globals.js";
8
+ import noDeprecatedLibraryExports from "./no-deprecated-library-exports.js";
9
+ import noDeprecatedLibraryProps from "./no-deprecated-library-props.js";
10
+ import noRemovedGlobals from "./no-removed-globals.js";
3
11
  export const rules = {
4
- 'no-deprecations': noDeprecations,
5
- 'no-removed-apis': noRemovedApis,
12
+ 'l10n-enforce-ellipsis': l10nEnforceEllipsis,
13
+ 'l10n-non-breaking-space': l10nNonBreakingSpace,
14
+ 'no-deprecated-globals': noDeprecatedGlobals,
15
+ 'no-removed-globals': noRemovedGlobals,
16
+ 'no-deprecated-library-exports': noDeprecatedLibraryExports,
17
+ 'no-deprecated-library-props': noDeprecatedLibraryProps,
6
18
  };
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { defineTemplateBodyVisitor } from "../utils/vue-template-visitor.js";
1
6
  const defineRule = (r) => r;
2
7
  export default defineRule({
3
8
  meta: {
@@ -12,7 +17,7 @@ export default defineRule({
12
17
  },
13
18
  },
14
19
  create(context) {
15
- return {
20
+ const visitor = {
16
21
  CallExpression(node) {
17
22
  if (node.callee.type !== 'Identifier'
18
23
  || (node.callee.name !== 't' && node.callee.name !== 'n')) {
@@ -34,5 +39,6 @@ export default defineRule({
34
39
  }
35
40
  },
36
41
  };
42
+ return defineTemplateBodyVisitor(context, visitor, visitor);
37
43
  },
38
44
  });
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { defineTemplateBodyVisitor } from "../utils/vue-template-visitor.js";
1
6
  const defineRule = (r) => r;
2
7
  export default defineRule({
3
8
  meta: {
@@ -12,7 +17,7 @@ export default defineRule({
12
17
  },
13
18
  },
14
19
  create(context) {
15
- return {
20
+ const visitor = {
16
21
  Literal(node) {
17
22
  if (typeof node.value !== 'string') {
18
23
  return;
@@ -29,5 +34,6 @@ export default defineRule({
29
34
  }
30
35
  },
31
36
  };
37
+ return defineTemplateBodyVisitor(context, visitor, visitor);
32
38
  },
33
39
  });
@@ -1,4 +1,8 @@
1
- import { createVersionValidator } from "../utils/version-parser.js";
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { createVersionValidator } from "../utils/app-version-parser.js";
2
6
  // ------------------------------------------------------------------------------
3
7
  // Rule Definition
4
8
  // ------------------------------------------------------------------------------
@@ -86,7 +90,6 @@ const rule = {
86
90
  meta: {
87
91
  docs: {
88
92
  description: 'Deprecated Nextcloud APIs',
89
- category: 'Nextcloud',
90
93
  recommended: true,
91
94
  },
92
95
  // fixable: null or "code" or "whitespace"
@@ -1,7 +1,8 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
1
5
  import { createLibVersionValidator } from "../utils/lib-version-parser.js";
2
- // ------------------------------------------------------------------------------
3
- // Rule Definition
4
- // ------------------------------------------------------------------------------
5
6
  /*
6
7
  Introduced in @nextcloud/vue v8.23.0
7
8
  https://github.com/nextcloud-libraries/nextcloud-vue/pull/6385
@@ -1,4 +1,8 @@
1
- import { createVersionValidator } from "../utils/version-parser.js";
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { createVersionValidator } from "../utils/app-version-parser.js";
2
6
  // ------------------------------------------------------------------------------
3
7
  // Rule Definition
4
8
  // ------------------------------------------------------------------------------
@@ -70,7 +74,6 @@ const rule = {
70
74
  meta: {
71
75
  docs: {
72
76
  description: 'Removed Nextcloud APIs',
73
- category: 'Nextcloud',
74
77
  recommended: true,
75
78
  },
76
79
  // fixable: "code" or "whitespace"
@@ -29,7 +29,9 @@ export function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVi
29
29
  message: 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.',
30
30
  });
31
31
  }
32
- return {};
32
+ else {
33
+ return scriptVisitor ?? {};
34
+ }
33
35
  }
34
36
  return sourceCode.parserServices
35
37
  .defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor, options);
@@ -43,7 +43,6 @@ const SortPackageJsonRule = {
43
43
  node: body,
44
44
  message: 'package.json is not sorted correctly',
45
45
  fix(fixer) {
46
- // @ts-expect-error its always an object node
47
46
  return fixer.replaceText(body, sortedPackageJsonText);
48
47
  },
49
48
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextcloud/eslint-config",
3
- "version": "9.0.0-rc.8",
3
+ "version": "9.0.0",
4
4
  "description": "Eslint shared config for nextcloud apps and libraries",
5
5
  "keywords": [
6
6
  "eslint",
@@ -43,31 +43,32 @@
43
43
  "test": "vitest run"
44
44
  },
45
45
  "dependencies": {
46
- "@eslint/json": "^0.14.0",
47
- "@stylistic/eslint-plugin": "^5.7.1",
48
- "eslint-config-flat-gitignore": "^2.1.0",
49
- "eslint-plugin-antfu": "^3.1.3",
50
- "eslint-plugin-jsdoc": "^62.4.1",
51
- "eslint-plugin-perfectionist": "^5.4.0",
52
- "eslint-plugin-vue": "^10.7.0",
53
- "fast-xml-parser": "^5.3.3",
54
- "globals": "^17.1.0",
55
- "semver": "^7.7.3",
56
- "sort-package-json": "^3.6.1",
57
- "typescript-eslint": "^8.54.0"
46
+ "@eslint/js": "^10.0.1",
47
+ "@eslint/json": "^2.0.0",
48
+ "@stylistic/eslint-plugin": "^5.10.0",
49
+ "eslint-config-flat-gitignore": "^2.3.0",
50
+ "eslint-plugin-antfu": "^3.2.3",
51
+ "eslint-plugin-jsdoc": "^63.0.7",
52
+ "eslint-plugin-perfectionist": "^5.9.1",
53
+ "eslint-plugin-vue": "^10.9.2",
54
+ "fast-xml-parser": "^5.9.3",
55
+ "globals": "^17.7.0",
56
+ "semver": "^7.8.5",
57
+ "sort-package-json": "^4.0.0",
58
+ "typescript-eslint": "^8.62.0"
58
59
  },
59
60
  "devDependencies": {
60
- "@types/node": "^25.0.10",
61
+ "@types/node": "^26.0.0",
61
62
  "@types/semver": "^7.7.1",
62
- "eslint": "^9.39.2",
63
- "memfs": "^4.56.10",
64
- "vitest": "^4.0.18"
63
+ "eslint": "^10.5.0",
64
+ "memfs": "^4.57.8",
65
+ "vitest": "^4.1.9"
65
66
  },
66
67
  "peerDependencies": {
67
- "eslint": ">=9"
68
+ "eslint": ">=10"
68
69
  },
69
70
  "engines": {
70
- "node": "^20.19 || ^22 || ^24"
71
+ "node": "^22.13 || ^24 || >=26"
71
72
  },
72
73
  "devEngines": {
73
74
  "packageManager": [
@@ -1,10 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import type { ESLint } from 'eslint';
6
- /**
7
- * ESLint plugin to enforce consistent translations
8
- */
9
- declare const Plugin: ESLint.Plugin;
10
- export default Plugin;
@@ -1,17 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import { packageVersion } from "../../version.js";
6
- import { rules } from "./rules/index.js";
7
- /**
8
- * ESLint plugin to enforce consistent translations
9
- */
10
- const Plugin = {
11
- meta: {
12
- name: '@nextcloud/l10n-plugin',
13
- version: packageVersion,
14
- },
15
- rules,
16
- };
17
- export default Plugin;
@@ -1,13 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import { defineTemplateBodyVisitor } from "../../nextcloud-vue/utils/vue-template-visitor.js";
6
- import enforceEllipsis from "./enforce-ellipsis.js";
7
- const defineRule = (r) => r;
8
- export default defineRule({
9
- ...enforceEllipsis,
10
- create(context) {
11
- return defineTemplateBodyVisitor(context, enforceEllipsis.create(context));
12
- },
13
- });
@@ -1,10 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- export declare const rules: {
6
- 'enforce-ellipsis': import("eslint").Rule.RuleModule;
7
- 'enforce-ellipsis-vue': import("eslint").Rule.RuleModule;
8
- 'non-breaking-space': import("eslint").Rule.RuleModule;
9
- 'non-breaking-space-vue': import("eslint").Rule.RuleModule;
10
- };
@@ -1,14 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import enforceEllipsisVue from "./enforce-ellipsis-vue.js";
6
- import enforceEllipsis from "./enforce-ellipsis.js";
7
- import nonBreakingSpaceVue from "./non-breaking-space-vue.js";
8
- import nonBreakingSpace from "./non-breaking-space.js";
9
- export const rules = {
10
- 'enforce-ellipsis': enforceEllipsis,
11
- 'enforce-ellipsis-vue': enforceEllipsisVue,
12
- 'non-breaking-space': nonBreakingSpace,
13
- 'non-breaking-space-vue': nonBreakingSpaceVue,
14
- };
@@ -1,7 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import type { Rule } from 'eslint';
6
- declare const _default: Rule.RuleModule;
7
- export default _default;
@@ -1,13 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import { defineTemplateBodyVisitor } from "../../nextcloud-vue/utils/vue-template-visitor.js";
6
- import nonBreakingSpace from "./non-breaking-space.js";
7
- const defineRule = (r) => r;
8
- export default defineRule({
9
- ...nonBreakingSpace,
10
- create(context) {
11
- return defineTemplateBodyVisitor(context, nonBreakingSpace.create(context));
12
- },
13
- });
@@ -1,7 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import type { Rule } from 'eslint';
6
- declare const _default: Rule.RuleModule;
7
- export default _default;
@@ -1,49 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- declare const _default: {
6
- rules: {
7
- 'no-deprecated-exports': import("eslint").Rule.RuleModule;
8
- 'no-deprecated-props': {
9
- meta: {
10
- docs: {
11
- description: string;
12
- };
13
- type: "problem";
14
- fixable: "code";
15
- messages: {
16
- outdatedVueLibrary: string;
17
- useTypeInstead: string;
18
- useVariantInstead: string;
19
- useDisableSwipeForNavInstead: string;
20
- useHideStatusInstead: string;
21
- useVerboseStatusInstead: string;
22
- useNoPlaceholderInstead: string;
23
- useFormatInstead: string;
24
- useLocaleInstead: string;
25
- useTypeDateRangeInstead: string;
26
- useNoCloseInstead: string;
27
- useNoCloseOnClickOutsideInstead: string;
28
- useDisableSwipeForModalInstead: string;
29
- useNoFocusTrapInstead: string;
30
- useKeepOpenInstead: string;
31
- useNcSelectUsersInstead: string;
32
- useArrowEndInstead: string;
33
- removeAriaHidden: string;
34
- removeLimitWidth: string;
35
- removeExact: string;
36
- useCloseButtonOutsideInstead: string;
37
- useModelValueInsteadChecked: string;
38
- useModelValueInsteadValue: string;
39
- };
40
- };
41
- create(context: import("eslint").Rule.RuleContext): import("eslint").Rule.RuleListener;
42
- };
43
- };
44
- meta: {
45
- name: string;
46
- version: string;
47
- };
48
- };
49
- export default _default;
@@ -1,13 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import { packageVersion } from "../../version.js";
6
- import { rules } from "./rules/index.js";
7
- export default {
8
- rules,
9
- meta: {
10
- name: '@nextcloud/eslint-plugin',
11
- version: packageVersion,
12
- },
13
- };
@@ -1,38 +0,0 @@
1
- export declare const rules: {
2
- 'no-deprecated-exports': import("eslint").Rule.RuleModule;
3
- 'no-deprecated-props': {
4
- meta: {
5
- docs: {
6
- description: string;
7
- };
8
- type: "problem";
9
- fixable: "code";
10
- messages: {
11
- outdatedVueLibrary: string;
12
- useTypeInstead: string;
13
- useVariantInstead: string;
14
- useDisableSwipeForNavInstead: string;
15
- useHideStatusInstead: string;
16
- useVerboseStatusInstead: string;
17
- useNoPlaceholderInstead: string;
18
- useFormatInstead: string;
19
- useLocaleInstead: string;
20
- useTypeDateRangeInstead: string;
21
- useNoCloseInstead: string;
22
- useNoCloseOnClickOutsideInstead: string;
23
- useDisableSwipeForModalInstead: string;
24
- useNoFocusTrapInstead: string;
25
- useKeepOpenInstead: string;
26
- useNcSelectUsersInstead: string;
27
- useArrowEndInstead: string;
28
- removeAriaHidden: string;
29
- removeLimitWidth: string;
30
- removeExact: string;
31
- useCloseButtonOutsideInstead: string;
32
- useModelValueInsteadChecked: string;
33
- useModelValueInsteadValue: string;
34
- };
35
- };
36
- create(context: import("eslint").Rule.RuleContext): import("eslint").Rule.RuleListener;
37
- };
38
- };
@@ -1,10 +0,0 @@
1
- /*!
2
- * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
- * SPDX-License-Identifier: AGPL-3.0-or-later
4
- */
5
- import noDeprecatedExports from "./no-deprecated-exports.js";
6
- import noDeprecatedProps from "./no-deprecated-props.js";
7
- export const rules = {
8
- 'no-deprecated-exports': noDeprecatedExports,
9
- 'no-deprecated-props': noDeprecatedProps,
10
- };