@nextcloud/eslint-config 9.0.0-rc.5 → 9.0.0-rc.7

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.
package/CHANGELOG.md CHANGED
@@ -26,6 +26,14 @@ Please refer to the README on how to adjust your configuration for flat config.
26
26
  * feat(imports): add custom plugin to suggest file extensions [\#1110](https://github.com/nextcloud-libraries/eslint-config/pull/1110) \([susnux](https://github.com/susnux)\)
27
27
  * feat(filesystem): ignore all files within the `.gitignore` [\#1108](https://github.com/nextcloud-libraries/eslint-config/pull/1108) \([susnux](https://github.com/susnux)\)
28
28
  * feat(l10n-plugin): also handle vue templates [\#1113](https://github.com/nextcloud-libraries/eslint-config/pull/1113) \([susnux](https://github.com/susnux)\)
29
+ * feat(nextcloud-vue-plugin): deprecate additional props [\#1163](https://github.com/nextcloud-libraries/eslint-config/pull/1163) \([Antreesy](https://github.com/Antreesy)\)
30
+ * feat(nextcloud-vue-plugin): deprecate additional exports [\#1162](https://github.com/nextcloud-libraries/eslint-config/pull/1162) \([Antreesy](https://github.com/Antreesy)\)
31
+ * feat(nextcloud-vue-plugin): deprecate NcPopover props [\#1165](https://github.com/nextcloud-libraries/eslint-config/pull/1165) \([Antreesy](https://github.com/Antreesy)\)
32
+ * feat(vue): add vue/component-options-name-casing with PascalCase [\#1261](https://github.com/nextcloud-libraries/eslint-config/pull/1261) \([ShGKme](https://github.com/ShGKme)\)
33
+ * feat(vue3): force camelCase for events in `<script>` [\#1262](https://github.com/nextcloud-libraries/eslint-config/pull/1262) \([ShGKme](https://github.com/ShGKme)\)
34
+ * feat(vue3): force camelCase for events in `<template>` [\#1263](https://github.com/nextcloud-libraries/eslint-config/pull/1263) \([ShGKme](https://github.com/ShGKme)\)
35
+ * feat(vue3): force camelCase for slot names [\#1264](https://github.com/nextcloud-libraries/eslint-config/pull/1264) \([ShGKme](https://github.com/ShGKme)\)
36
+ * feat(vue3): force camelCase for props in template [\#1266](https://github.com/nextcloud-libraries/eslint-config/pull/1266) \([ShGKme](https://github.com/ShGKme)\)
29
37
 
30
38
  ### Fixed
31
39
  * fix(codestyle): do not require splitting chains [\#951](https://github.com/nextcloud-libraries/eslint-config/pull/951)
@@ -58,6 +66,9 @@ Please refer to the README on how to adjust your configuration for flat config.
58
66
  * fix(filesystem): relax ignored files [\#1114](https://github.com/nextcloud-libraries/eslint-config/pull/1114) \([susnux](https://github.com/susnux)\)
59
67
  * fix(globs): adjust globs for test related files [\#1128](https://github.com/nextcloud-libraries/eslint-config/pull/1128) \([susnux](https://github.com/susnux)\)
60
68
  * fix(vue): use vue variant of `no-irregular-whitespace` [\#1129](https://github.com/nextcloud-libraries/eslint-config/pull/1129) \([susnux](https://github.com/susnux)\)
69
+ * fix(nextcloud): add missing deprecations and removals [\#1206](https://github.com/nextcloud-libraries/eslint-config/pull/1206) \([susnux](https://github.com/susnux)\)
70
+ * fix(plugin:nextcloud-vue): use resolved dependency for detecting nextcloud-vue version [\#1220](https://github.com/nextcloud-libraries/eslint-config/pull/1220) \([susnux](https://github.com/susnux)\)
71
+ * 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)\)
61
72
 
62
73
  ### Changed
63
74
  * Add SPDX header [#802](https://github.com/nextcloud-libraries/eslint-config/pull/802)
@@ -75,13 +86,22 @@ Please refer to the README on how to adjust your configuration for flat config.
75
86
  * chore: add active node version (24) to supported engines [#1066](https://github.com/nextcloud-libraries/eslint-config/pull/1066) ([susnux](https://github.com/susnux))
76
87
  * build: add common changelog formatting and documentation for it [#1067](https://github.com/nextcloud-libraries/eslint-config/pull/1067) ([susnux](https://github.com/susnux))
77
88
  * chore: lint this project as a library [\#1130](https://github.com/nextcloud-libraries/eslint-config/pull/1130) \([susnux](https://github.com/susnux)\)
78
- * Updated `@eslint/json` to 0.12.0
79
- * Updated `@stylistic/eslint-plugin` 4.2.0
80
- * Updated `eslint-plugin-jsdoc` to 50.6.11
81
- * Updated `eslint-plugin-vue` to 10.0.0
82
- * Updated `fast-xml-parser` to 5.2.1
83
- * Updated `sort-package-json` to 3.0.0
84
- * Updated `typescript-eslint` to 8.31.0
89
+ * chore: update devEngines to align with apps [\#1204](https://github.com/nextcloud-libraries/eslint-config/pull/1204) \([susnux](https://github.com/susnux)\)
90
+ * build: disable libcheck for Typescript [\#1205](https://github.com/nextcloud-libraries/eslint-config/pull/1205) \([susnux](https://github.com/susnux)\)
91
+ * chore(gitignore): ignore IDE configs [\#1265](https://github.com/nextcloud-libraries/eslint-config/pull/1265) \([ShGKme](https://github.com/ShGKme)\)
92
+ * test(vue3): add some formatting tests [\#1267](https://github.com/nextcloud-libraries/eslint-config/pull/1267) \([ShGKme](https://github.com/ShGKme)\)
93
+ * Updated `@eslint/json` to 0.14.0
94
+ * Updated `@stylistic/eslint-plugin` to 5.7.0
95
+ * Updated `eslint-config-flat-gitignore` to 2.1.0
96
+ * Updated `eslint-plugin-antfu` to 3.1.3
97
+ * Updated `eslint-plugin-jsdoc` to 62.0.0
98
+ * Updated `eslint-plugin-perfectionist` to 5.3.1
99
+ * Updated `eslint-plugin-vue` to 10.6.2
100
+ * Updated `fast-xml-parser` to 5.3.3
101
+ * Updated `globals` to 17.0.0
102
+ * Updated `semver` to 7.7.3
103
+ * Updated `sort-package-json` to 3.6.0
104
+ * Updated `typescript-eslint` to 8.53.0
85
105
 
86
106
  ## [v8.4.2](https://github.com/nextcloud-libraries/eslint-config/tree/v8.4.2) (2025-02-16)
87
107
  ### Fixed
@@ -10,4 +10,4 @@ import type { ConfigOptions } from '../types.d.ts';
10
10
  *
11
11
  * @param options options defining the config preset flavor
12
12
  */
13
- export declare function codeStyle(options: ConfigOptions): (Linter.Config | Linter.BaseConfig)[];
13
+ export declare function codeStyle(options: ConfigOptions): Linter.Config[];
@@ -75,25 +75,24 @@ export function codeStyle(options) {
75
75
  'properties',
76
76
  { avoidQuotes: true },
77
77
  ],
78
- // Enforce consistent new lines after [ and before ]
79
- '@stylistic/array-bracket-newline': [
80
- 'error',
81
- 'consistent',
82
- ],
83
- // Enforce new lines between array elements (better git diff) but allow to have single line arrays
84
- '@stylistic/array-element-newline': ['error', 'consistent'],
85
- // Same for objects as for arrays
86
- '@stylistic/object-curly-newline': [
87
- 'error',
88
- {
89
- consistent: true,
90
- multiline: true,
91
- },
92
- ],
93
- '@stylistic/object-property-newline': [
94
- 'error',
95
- { allowAllPropertiesOnSameLine: true },
96
- ],
78
+ // Enforce consistent new lines after brackets
79
+ '@stylistic/array-bracket-newline': 'off',
80
+ '@stylistic/array-bracket-spacing': 'off',
81
+ '@stylistic/array-element-newline': 'off',
82
+ '@stylistic/jsx-function-call-newline': 'off',
83
+ '@stylistic/object-curly-newline': 'off',
84
+ '@stylistic/object-curly-spacing': 'off',
85
+ '@stylistic/object-property-newline': 'off',
86
+ '@stylistic/exp-list-style': ['error', {
87
+ singleLine: {
88
+ spacing: 'never',
89
+ },
90
+ overrides: {
91
+ '{}': {
92
+ singleLine: { spacing: 'always' },
93
+ },
94
+ },
95
+ }],
97
96
  // No space between function name and parenthesis. Enforce fn() instead of fn ()
98
97
  '@stylistic/function-call-spacing': [
99
98
  'error',
@@ -1,4 +1,4 @@
1
- import jsdocPlugin from 'eslint-plugin-jsdoc';
1
+ import { jsdoc } from 'eslint-plugin-jsdoc';
2
2
  import { GLOB_FILES_JAVASCRIPT, GLOB_FILES_TESTING, GLOB_FILES_TYPESCRIPT, GLOB_FILES_VUE, } from "../globs.js";
3
3
  const TS_FUNCTION_CONTEXTS = [
4
4
  'FunctionDeclaration:has(TSTypeAnnotation)',
@@ -12,6 +12,12 @@ const JS_FUNCTION_CONTEXTS = [
12
12
  'ArrowFunctionExpression:not(:has(TSTypeAnnotation))',
13
13
  'MethodDefinition:not(:has(TSTypeAnnotation))',
14
14
  ];
15
+ const SHARED_JSDOC_SETTINGS = {
16
+ // We use the alias for legacy reasons to prevent unnecessary noise
17
+ tagNamePreference: {
18
+ returns: 'return',
19
+ },
20
+ };
15
21
  /**
16
22
  * Config factory for code documentation related rules (JSDoc)
17
23
  *
@@ -20,30 +26,26 @@ const JS_FUNCTION_CONTEXTS = [
20
26
  export function documentation(options) {
21
27
  return [
22
28
  {
23
- files: [
24
- ...GLOB_FILES_JAVASCRIPT,
25
- ...GLOB_FILES_TYPESCRIPT,
26
- ...GLOB_FILES_VUE,
27
- ],
28
- plugins: {
29
- jsdoc: jsdocPlugin,
30
- },
31
- },
32
- {
33
- ...jsdocPlugin.configs['flat/recommended-typescript-flavor'],
29
+ ...jsdoc({
30
+ config: 'flat/recommended-typescript-flavor',
31
+ settings: {
32
+ ...SHARED_JSDOC_SETTINGS,
33
+ mode: 'permissive',
34
+ },
35
+ }),
36
+ name: 'nextcloud/documentation/javascript',
34
37
  files: [
35
38
  ...GLOB_FILES_JAVASCRIPT,
36
39
  ...(options.vueIsTypescript ? [] : GLOB_FILES_VUE),
37
40
  ],
38
- settings: {
39
- jsdoc: {
40
- mode: 'permissive',
41
- },
42
- },
43
41
  ignores: GLOB_FILES_TESTING,
44
42
  },
45
43
  {
46
- ...jsdocPlugin.configs['flat/recommended-typescript'],
44
+ ...jsdoc({
45
+ config: 'flat/recommended-typescript',
46
+ settings: SHARED_JSDOC_SETTINGS,
47
+ }),
48
+ name: 'nextcloud/documentation/typescript',
47
49
  files: [
48
50
  ...GLOB_FILES_TYPESCRIPT,
49
51
  ...(options.vueIsTypescript ? GLOB_FILES_VUE : []),
@@ -71,14 +73,6 @@ export function documentation(options) {
71
73
  { startLines: 1 },
72
74
  ],
73
75
  },
74
- settings: {
75
- jsdoc: {
76
- // We use the alias for legacy reasons to prevent unnecessary noise
77
- tagNamePreference: {
78
- returns: 'return',
79
- },
80
- },
81
- },
82
76
  },
83
77
  {
84
78
  name: 'nextcloud/documentation/rules-typescript',
@@ -1,8 +1,8 @@
1
- import type { Linter } from 'eslint';
2
1
  /*!
3
2
  * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
4
3
  * SPDX-License-Identifier: AGPL-3.0-or-later
5
4
  */
5
+ import type { Linter } from 'eslint';
6
6
  import type { ConfigOptions } from '../types.d.ts';
7
7
  /**
8
8
  * Generate imports and exports related ESLint rules.
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
1
5
  import perfectionist from 'eslint-plugin-perfectionist';
2
6
  import { GLOB_FILES_JAVASCRIPT, GLOB_FILES_TYPESCRIPT, GLOB_FILES_VUE, } from "../globs.js";
3
7
  import importExtensions from "../plugins/import-extensions/index.js";
@@ -32,24 +36,23 @@ export function imports(options) {
32
36
  'error',
33
37
  {
34
38
  type: 'natural',
35
- newlinesBetween: 'never',
39
+ newlinesBetween: 0,
36
40
  groups: [
37
41
  // type first
38
- 'external-type',
39
- 'type',
40
- { newlinesBetween: 'always' },
42
+ ['type-external', 'type-builtin'],
43
+ 'type-import',
44
+ { newlinesBetween: 1 },
41
45
  // external things
42
46
  [
43
47
  'builtin',
44
48
  'external',
45
- 'object',
46
49
  ],
47
50
  // Vue components
48
51
  'vue', // external modules (e.g. nextcloud-vue)
49
52
  'internalVue', // internal local vue components
50
53
  // everything else which is everything internal
51
54
  'unknown',
52
- { newlinesBetween: 'always' },
55
+ { newlinesBetween: 1 },
53
56
  // side effect only: import 'sideeffect.js'
54
57
  'side-effect',
55
58
  // import style from 'my.module.css'
@@ -93,7 +96,7 @@ export function imports(options) {
93
96
  function createSortingConfig(type) {
94
97
  return {
95
98
  type: 'natural',
96
- newlinesBetween: 'always',
99
+ newlinesBetween: 1,
97
100
  partitionByNewLine: false,
98
101
  groups: [
99
102
  `type-${type}`,
@@ -37,11 +37,10 @@ export function vue(options) {
37
37
  files: GLOB_FILES_VUE,
38
38
  name: 'nextcloud/vue/rules',
39
39
  rules: {
40
- // PascalCase components names for vuejs
41
- 'vue/component-name-in-template-casing': [
42
- 'error',
43
- 'PascalCase',
44
- ],
40
+ // PascalCase components names in template tag names
41
+ 'vue/component-name-in-template-casing': ['error', 'PascalCase'],
42
+ // Also in registration
43
+ 'vue/component-options-name-casing': ['error', 'PascalCase'],
45
44
  // space before self-closing elements
46
45
  'vue/html-closing-bracket-spacing': 'error',
47
46
  // no ending html tag on a new line
@@ -126,7 +125,8 @@ export function vue(options) {
126
125
  'vue/prefer-separate-static-class': 'error',
127
126
  // For consistent layout of components
128
127
  'vue/define-macros-order': [
129
- 'error', {
128
+ 'error',
129
+ {
130
130
  order: [
131
131
  'defineOptions',
132
132
  'defineModel',
@@ -13,12 +13,12 @@ export function vue2(option) {
13
13
  ...vue(option),
14
14
  {
15
15
  rules: {
16
- // custom event naming convention
16
+ // Force kebab-case for custom event name definitions (recommended by Vue 2 documentation)
17
17
  'vue/custom-event-name-casing': [
18
18
  'error',
19
19
  'kebab-case',
20
20
  {
21
- // allows custom xxxx:xxx events formats
21
+ // Allow namespace formats namespace:event
22
22
  ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'],
23
23
  },
24
24
  ],
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
1
5
  import vuePlugin from 'eslint-plugin-vue';
2
6
  import { GLOB_FILES_VUE } from "../globs.js";
3
7
  import { restrictConfigFiles } from "../utils.js";
@@ -15,6 +19,24 @@ export function vue3(options) {
15
19
  {
16
20
  files: GLOB_FILES_VUE,
17
21
  rules: {
22
+ // Force camelCase in props/attrs for consistency with <script> and prevent tooling issues
23
+ 'vue/attribute-hyphenation': ['error', 'never'],
24
+ // Force camelCase for custom event name definitions (recommended by Vue 3 documentation and consistent with JS)
25
+ 'vue/custom-event-name-casing': [
26
+ 'error',
27
+ 'camelCase',
28
+ {
29
+ // Allow namespace formats namespace:event
30
+ ignores: ['/^[a-z]+:[a-z]+$/iu'],
31
+ },
32
+ ],
33
+ // Force camelCase for events in template for consistency with <script>
34
+ 'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }],
35
+ // Force camelCase for slot names.
36
+ // Changing case is breaking for component users.
37
+ // For libraries it may result in a breaking change. Warn to prevent unintended breaking change.
38
+ // TODO: allow namespace:slotName format like in events
39
+ 'vue/slot-name-casing': [options.isLibrary ? 'warn' : 'error', 'camelCase'],
18
40
  // Deprecated thus we should not use it
19
41
  'vue/no-deprecated-delete-set': 'error',
20
42
  // When using script-setup the modern approach should be used
package/dist/index.d.ts CHANGED
@@ -1,27 +1,32 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import type { Linter } from 'eslint';
1
6
  /**
2
7
  * Nextcloud shared configuration for projects using Vue 2 with Javascript <script> blocks
3
8
  */
4
- export declare const recommendedVue2Javascript: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
9
+ export declare const recommendedVue2Javascript: Linter.Config[];
5
10
  /**
6
11
  * Nextcloud shared configuration for projects using Vue 2 with Typescript <script> blocks
7
12
  */
8
- export declare const recommendedVue2: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
13
+ export declare const recommendedVue2: Linter.Config[];
9
14
  /**
10
15
  * Nextcloud shared configuration for projects using Vue 3 with Javascript <script> blocks
11
16
  */
12
- export declare const recommendedJavascript: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
17
+ export declare const recommendedJavascript: Linter.Config[];
13
18
  /**
14
19
  * Nextcloud shared configuration for projects using Vue 3 with Typescript <script> blocks
15
20
  */
16
- export declare const recommended: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
21
+ export declare const recommended: Linter.Config[];
17
22
  /**
18
23
  * Nextcloud shared configuration for projects using Vue 3 with Typescript <script> blocks
19
24
  */
20
- export declare const recommendedLibrary: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
25
+ export declare const recommendedLibrary: Linter.Config[];
21
26
  /**
22
27
  * Nextcloud shared configuration for projects using Vue 3 with Typescript <script> blocks
23
28
  */
24
- export declare const recommendedVue2Library: (import("eslint").Linter.Config<import("eslint").Linter.RulesRecord> | import("eslint").Linter.BaseConfig<import("eslint").Linter.RulesRecord, import("eslint").Linter.RulesRecord>)[];
29
+ export declare const recommendedVue2Library: Linter.Config[];
25
30
  export { default as packageJsonPlugin } from './plugins/packageJson.ts';
26
31
  export { default as nextcloudPlugin } from './plugins/nextcloud/index.ts';
27
32
  export { default as l10nPlugin } from './plugins/l10n/index.ts';
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
1
5
  import { codeStyle } from "./configs/codeStyle.js";
2
6
  import { documentation } from "./configs/documentation.js";
3
7
  import { filesystem } from "./configs/filesystem.js";
@@ -31,15 +31,18 @@ const global = {
31
31
  relative_modified_date: '16.0.0',
32
32
  };
33
33
  const oc = {
34
+ AppConfig: '16.0.0',
34
35
  L10n: '26.0.0',
36
+ SystemTags: '32.0.0',
35
37
  _capabilities: '17.0.0',
36
38
  addTranslations: '17.0.0',
37
39
  basename: '18.0.0',
38
40
  coreApps: '17.0.0',
39
41
  currentUser: '19.0.0',
42
+ dialogs: '30.0.0',
40
43
  dirname: '18.0.0',
41
44
  encodePath: '18.0.0',
42
- fileIsBlacklisted: '17.0.0',
45
+ fileIsBlacklisted: '18.0.0',
43
46
  filePath: '19.0.0',
44
47
  generateUrl: '19.0.0',
45
48
  get: '19.0.0',
@@ -71,6 +74,13 @@ const ocNested = {
71
74
  humanFileSize: '20.0.0',
72
75
  relativeModifiedDate: '20.0.0',
73
76
  },
77
+ dialogs: {
78
+ fileexists: '29.0.0',
79
+ },
80
+ config: {
81
+ blacklist_files_regex: '30.0.0',
82
+ forbidden_filename_characters: '30.0.0',
83
+ },
74
84
  };
75
85
  const rule = {
76
86
  meta: {
@@ -11,14 +11,26 @@ const global = {
11
11
  getURLParameter: '19.0.0',
12
12
  humanFileSize: '19.0.0',
13
13
  marked: '19.0.0',
14
+ md5: '32.0.0',
14
15
  relative_modified_date: '19.0.0',
15
16
  };
16
17
  const oc = {
18
+ AppConfig: '33.0.0',
19
+ SystemTags: '33.0.0',
17
20
  getScrollBarWidth: '15.0.0',
18
21
  addTranslations: '26.0.0',
19
22
  appSettings: '28.0.0',
23
+ fileIsBlacklisted: '33.0.0',
24
+ get: '33.0.0',
25
+ getHost: '33.0.0',
26
+ getHostName: '33.0.0',
27
+ getPort: '33.0.0',
28
+ getProtocol: '33.0.0',
20
29
  loadScript: '28.0.0',
21
30
  loadStyle: '28.0.0',
31
+ redirect: '33.0.0',
32
+ reload: '33.0.0',
33
+ set: '33.0.0',
22
34
  };
23
35
  const ocNested = {
24
36
  AppConfig: {
@@ -36,6 +48,12 @@ const ocNested = {
36
48
  const oca = {
37
49
  // ref: https://github.com/nextcloud/server/commit/6eced42b7a40f5b0ea0489244583219d0ee2e7af
38
50
  Search: '20.0.0',
51
+ FilesSharingDrop: '31.0.0',
52
+ };
53
+ const ocaNested = {
54
+ Sharing: {
55
+ ExternalLinkActions: '33.0.0',
56
+ },
39
57
  };
40
58
  // TODO: handle OC.x.y.z like OC.Share.ShareConfigModel.areAvatarsEnabled()
41
59
  // ref https://github.com/nextcloud/server/issues/11045
@@ -83,6 +101,27 @@ const rule = {
83
101
  message: `The property or function OCA.${node.property.name} was removed in Nextcloud ${oca[node.property.name]}`,
84
102
  });
85
103
  }
104
+ // OCA.x.y
105
+ if (node.object.type === 'MemberExpression'
106
+ && 'name' in node.object.object
107
+ && node.object.object.name === 'OCA'
108
+ && 'name' in node.object.property
109
+ && ocaNested[node.object.property.name]
110
+ && 'name' in node.property
111
+ && ocaNested[node.object.property.name][node.property.name]) {
112
+ const version = ocaNested[node.object.property.name][node.property.name];
113
+ if (checkTargetVersion(version)) {
114
+ const prop = [
115
+ 'OC',
116
+ node.object.property.name,
117
+ node.property.name,
118
+ ].join('.');
119
+ context.report({
120
+ node,
121
+ message: `The property or function ${prop} was removed in Nextcloud ${version}`,
122
+ });
123
+ }
124
+ }
86
125
  // OC.x
87
126
  if ('name' in node.object
88
127
  && 'name' in node.property
@@ -21,13 +21,21 @@ declare const _default: {
21
21
  useVerboseStatusInstead: string;
22
22
  useNoPlaceholderInstead: string;
23
23
  useFormatInstead: string;
24
+ useLocaleInstead: string;
24
25
  useTypeDateRangeInstead: string;
25
26
  useNoCloseInstead: string;
27
+ useNoCloseOnClickOutsideInstead: string;
26
28
  useDisableSwipeForModalInstead: string;
27
29
  useNoFocusTrapInstead: string;
28
30
  useKeepOpenInstead: string;
29
31
  useNcSelectUsersInstead: string;
32
+ useArrowEndInstead: string;
30
33
  removeAriaHidden: string;
34
+ removeLimitWidth: string;
35
+ removeExact: string;
36
+ useCloseButtonOutsideInstead: string;
37
+ useModelValueInsteadChecked: string;
38
+ useModelValueInsteadValue: string;
31
39
  };
32
40
  };
33
41
  create(context: import("eslint").Rule.RuleContext): any;
@@ -16,13 +16,21 @@ export declare const rules: {
16
16
  useVerboseStatusInstead: string;
17
17
  useNoPlaceholderInstead: string;
18
18
  useFormatInstead: string;
19
+ useLocaleInstead: string;
19
20
  useTypeDateRangeInstead: string;
20
21
  useNoCloseInstead: string;
22
+ useNoCloseOnClickOutsideInstead: string;
21
23
  useDisableSwipeForModalInstead: string;
22
24
  useNoFocusTrapInstead: string;
23
25
  useKeepOpenInstead: string;
24
26
  useNcSelectUsersInstead: string;
27
+ useArrowEndInstead: string;
25
28
  removeAriaHidden: string;
29
+ removeLimitWidth: string;
30
+ removeExact: string;
31
+ useCloseButtonOutsideInstead: string;
32
+ useModelValueInsteadChecked: string;
33
+ useModelValueInsteadValue: string;
26
34
  };
27
35
  };
28
36
  create(context: import("eslint").Rule.RuleContext): any;
@@ -16,18 +16,59 @@ const rule = {
16
16
  messages: {
17
17
  outdatedVueLibrary: 'Installed @nextcloud/vue library is outdated and does not support all reported errors. Install latest compatible version',
18
18
  deprecatedDist: 'Import from "@nextcloud/vue/dist" is deprecated',
19
+ deprecatedMixin: 'Mixins are no longer recommended by Vue. Consider using available alternatives',
20
+ deprecatedNcSettingsInputText: 'NcSettingsInputText is deprecated. Consider using available alternatives',
21
+ deprecatedTooltip: 'Tooltip directive is deprecated. use native title attribute or NcPopover instead',
19
22
  },
20
23
  },
21
24
  create(context) {
22
25
  const versionSatisfies = createLibVersionValidator(context);
23
- const isVersionValid = versionSatisfies('8.23.0');
26
+ const isVersionValidForDist = versionSatisfies('8.23.0');
24
27
  const oldPattern = '@nextcloud/vue/dist/([^/]+)/([^/.]+)';
28
+ const mixinPattern = '@nextcloud/vue/mixins/([^/.]+)';
29
+ const isVersionValidForTooltip = versionSatisfies('8.25.0');
30
+ const tooltipPattern = '@nextcloud/vue/directives/Tooltip';
31
+ const isVersionValidForNcSettingsInputText = versionSatisfies('8.31.0');
32
+ const patternForNcSettingsInputText = '@nextcloud/vue/components/NcSettingsInputText';
25
33
  return {
26
34
  ImportDeclaration: function (node) {
27
35
  const importPath = node.source.value;
36
+ const matchForNcSettingsInputText = importPath.match(new RegExp(patternForNcSettingsInputText));
37
+ if (matchForNcSettingsInputText) {
38
+ if (!isVersionValidForNcSettingsInputText) {
39
+ context.report({ node, messageId: 'outdatedVueLibrary' });
40
+ return;
41
+ }
42
+ context.report({
43
+ node,
44
+ messageId: 'deprecatedNcSettingsInputText',
45
+ });
46
+ }
47
+ const mixinMatch = importPath.match(new RegExp(mixinPattern, 'i'));
48
+ if (mixinMatch) {
49
+ if (!isVersionValidForDist) {
50
+ context.report({ node, messageId: 'outdatedVueLibrary' });
51
+ return;
52
+ }
53
+ context.report({
54
+ node,
55
+ messageId: 'deprecatedMixin',
56
+ });
57
+ }
58
+ const tooltipMatch = importPath.match(new RegExp(tooltipPattern));
59
+ if (tooltipMatch) {
60
+ if (!isVersionValidForTooltip) {
61
+ context.report({ node, messageId: 'outdatedVueLibrary' });
62
+ return;
63
+ }
64
+ context.report({
65
+ node,
66
+ messageId: 'deprecatedTooltip',
67
+ });
68
+ }
28
69
  const match = importPath.match(new RegExp(oldPattern));
29
70
  if (match) {
30
- if (!isVersionValid) {
71
+ if (!isVersionValidForDist) {
31
72
  context.report({ node, messageId: 'outdatedVueLibrary' });
32
73
  return;
33
74
  }
@@ -19,13 +19,21 @@ declare const _default: {
19
19
  useVerboseStatusInstead: string;
20
20
  useNoPlaceholderInstead: string;
21
21
  useFormatInstead: string;
22
+ useLocaleInstead: string;
22
23
  useTypeDateRangeInstead: string;
23
24
  useNoCloseInstead: string;
25
+ useNoCloseOnClickOutsideInstead: string;
24
26
  useDisableSwipeForModalInstead: string;
25
27
  useNoFocusTrapInstead: string;
26
28
  useKeepOpenInstead: string;
27
29
  useNcSelectUsersInstead: string;
30
+ useArrowEndInstead: string;
28
31
  removeAriaHidden: string;
32
+ removeLimitWidth: string;
33
+ removeExact: string;
34
+ useCloseButtonOutsideInstead: string;
35
+ useModelValueInsteadChecked: string;
36
+ useModelValueInsteadValue: string;
29
37
  };
30
38
  };
31
39
  create(context: Rule.RuleContext): any;
@@ -16,18 +16,28 @@ export default {
16
16
  useVerboseStatusInstead: 'Using `show-user-status-compact` is deprecated - use `verbose-status` instead',
17
17
  useNoPlaceholderInstead: 'Using `allow-placeholder` is deprecated - use `no-placeholder` instead',
18
18
  useFormatInstead: 'Using `formatter` is deprecated - use `format` instead',
19
+ useLocaleInstead: 'Using `lang` is deprecated - use `locale` instead',
19
20
  useTypeDateRangeInstead: 'Using `range` is deprecated - use `type` with `date-range` or `datetime-range` instead',
20
21
  useNoCloseInstead: 'Using `can-close` is deprecated - use `no-close` instead',
22
+ useNoCloseOnClickOutsideInstead: 'Using `close-on-click-outside` is deprecated - use `no-close-on-click-outside` instead',
21
23
  useDisableSwipeForModalInstead: 'Using `enable-swipe` is deprecated - use `disable-swipe` instead',
22
24
  useNoFocusTrapInstead: 'Using `focus-trap` is deprecated - use `no-focus-trap` instead',
23
25
  useKeepOpenInstead: 'Using `close-on-select` is deprecated - use `keep-open` instead',
24
26
  useNcSelectUsersInstead: 'Using `user-select` is deprecated - use `NcSelectUsers` component instead',
27
+ useArrowEndInstead: 'Using `arrow-right` is deprecated - use `arrow-end` instead',
25
28
  removeAriaHidden: 'Using `aria-hidden` is deprecated - remove prop from components, otherwise root element will inherit incorrect attribute.',
29
+ removeLimitWidth: 'Using `limit-width` is deprecated - remove prop from components, otherwise root element will inherit incorrect attribute.',
30
+ removeExact: 'Using `exact` is deprecated - consult Vue Router documentation for alternatives.',
31
+ useCloseButtonOutsideInstead: 'Using `close-button-contained` is deprecated - use `close-button-outside` instead',
32
+ useModelValueInsteadChecked: 'Using `checked` is deprecated - use `model-value` or `v-model` instead',
33
+ useModelValueInsteadValue: 'Using `value` is deprecated - use `model-value` or `v-model` instead',
26
34
  },
27
35
  },
28
36
  create(context) {
29
37
  const versionSatisfies = createLibVersionValidator(context);
38
+ const isVue3Valid = versionSatisfies('9.0.0'); // #6651
30
39
  const isAriaHiddenValid = versionSatisfies('8.2.0'); // #4835
40
+ const isModelValueValid = versionSatisfies('8.20.0'); // #6172
31
41
  const isDisableSwipeValid = versionSatisfies('8.23.0'); // #6452
32
42
  const isVariantTypeValid = versionSatisfies('8.24.0'); // #6472
33
43
  const isDefaultBooleanFalseValid = versionSatisfies('8.24.0'); // #6656
@@ -35,7 +45,17 @@ export default {
35
45
  const isNcSelectKeepOpenValid = versionSatisfies('8.25.0'); // #6791
36
46
  const isNcPopoverNoFocusTrapValid = versionSatisfies('8.26.0'); // #6808
37
47
  const isNcSelectUsersValid = versionSatisfies('8.27.1'); // #7032
38
- const legacyTypes = ['primary', 'error', 'warning', 'success', 'secondary', 'tertiary', 'tertiary-no-background'];
48
+ const isNcTextFieldArrowEndValid = versionSatisfies('8.28.0'); // #7002
49
+ const isCloseButtonOutsideValid = versionSatisfies('8.32.0'); // #7553
50
+ const legacyTypes = [
51
+ 'primary',
52
+ 'error',
53
+ 'warning',
54
+ 'success',
55
+ 'secondary',
56
+ 'tertiary',
57
+ 'tertiary-no-background',
58
+ ];
39
59
  return vueUtils.defineTemplateBodyVisitor(context, {
40
60
  'VElement VAttribute:has(VIdentifier[name="type"])': function (node) {
41
61
  if (![
@@ -165,6 +185,16 @@ export default {
165
185
  messageId: 'useFormatInstead',
166
186
  });
167
187
  },
188
+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="lang"])': function (node) {
189
+ if (!isVue3Valid) {
190
+ // Do not throw for v8.X.X
191
+ return;
192
+ }
193
+ context.report({
194
+ node,
195
+ messageId: 'useLocaleInstead',
196
+ });
197
+ },
168
198
  'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="range"])': function (node) {
169
199
  if (!isDateTimePickerFormatValid) {
170
200
  context.report({ node, messageId: 'outdatedVueLibrary' });
@@ -191,6 +221,16 @@ export default {
191
221
  messageId: 'useNoCloseInstead',
192
222
  });
193
223
  },
224
+ 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="close-on-click-outside"])': function (node) {
225
+ if (!isVue3Valid) {
226
+ // Do not throw for v8.X.X
227
+ return;
228
+ }
229
+ context.report({
230
+ node,
231
+ messageId: 'useNoCloseOnClickOutsideInstead',
232
+ });
233
+ },
194
234
  'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="enable-swipe"])': function (node) {
195
235
  if (!isDisableSwipeValid) {
196
236
  context.report({ node, messageId: 'outdatedVueLibrary' });
@@ -201,6 +241,16 @@ export default {
201
241
  messageId: 'useDisableSwipeForModalInstead',
202
242
  });
203
243
  },
244
+ 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="close-button-contained"])': function (node) {
245
+ if (!isCloseButtonOutsideValid) {
246
+ context.report({ node, messageId: 'outdatedVueLibrary' });
247
+ return;
248
+ }
249
+ context.report({
250
+ node,
251
+ messageId: 'useCloseButtonOutsideInstead',
252
+ });
253
+ },
204
254
  'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="focus-trap"])': function (node) {
205
255
  if (!isNcPopoverNoFocusTrapValid) {
206
256
  context.report({ node, messageId: 'outdatedVueLibrary' });
@@ -231,6 +281,142 @@ export default {
231
281
  messageId: 'useNcSelectUsersInstead',
232
282
  });
233
283
  },
284
+ 'VElement VAttribute:has(VIdentifier[name="trailing-button-icon"])': function (node) {
285
+ if (node.parent.parent.name !== 'nctextfield') {
286
+ return;
287
+ }
288
+ if (!isNcTextFieldArrowEndValid) {
289
+ context.report({ node, messageId: 'outdatedVueLibrary' });
290
+ return;
291
+ }
292
+ const isLiteral = node.value.type === 'VLiteral' && node.value.value === 'arrowRight';
293
+ const isExpression = node.value.type === 'VExpressionContainer' && node.value.expression?.type === 'ConditionalExpression'
294
+ && (node.value.expression.consequent.value === 'arrowRight' || node.value.expression.alternate.value === 'arrowRight');
295
+ /**
296
+ * if it is a literal with a deprecated value -> we migrate
297
+ * if it is an expression with a defined deprecated value -> we migrate
298
+ */
299
+ if (isLiteral || isExpression) {
300
+ context.report({
301
+ node,
302
+ messageId: 'useArrowEndInstead',
303
+ fix: (fixer) => {
304
+ if (node.key.type === 'VIdentifier') {
305
+ return fixer.replaceTextRange(node.value.range, '"arrowEnd"');
306
+ }
307
+ else if (node.key.type === 'VDirectiveKey') {
308
+ return (node.value.expression.consequent.value === 'arrowRight')
309
+ ? fixer.replaceTextRange(node.value.expression.consequent.range, '\'arrowEnd\'')
310
+ : fixer.replaceTextRange(node.value.expression.alternate.range, '\'arrowEnd\'');
311
+ }
312
+ },
313
+ });
314
+ }
315
+ },
316
+ 'VElement[name="ncsettingssection"] VAttribute:has(VIdentifier[name="limit-width"])': function (node) {
317
+ // This was deprecated in 8.13.0 (Nextcloud 30+), before first supported version by plugin
318
+ context.report({
319
+ node,
320
+ messageId: 'removeLimitWidth',
321
+ });
322
+ },
323
+ 'VElement VAttribute:has(VIdentifier[name="exact"])': function (node) {
324
+ if (![
325
+ 'ncactionrouter',
326
+ 'ncappnavigationitem',
327
+ 'ncbreadcrumb',
328
+ 'ncbutton',
329
+ 'nclistitem',
330
+ ].includes(node.parent.parent.name)) {
331
+ return;
332
+ }
333
+ if (!isVue3Valid) {
334
+ // Do not throw for v8.X.X
335
+ return;
336
+ }
337
+ context.report({
338
+ node,
339
+ messageId: 'removeExact',
340
+ });
341
+ },
342
+ 'VElement VAttribute:has(VIdentifier[name="checked"])': function (node) {
343
+ if (![
344
+ 'ncactioncheckbox',
345
+ 'ncactionradio',
346
+ 'nccheckboxradioswitch',
347
+ ].includes(node.parent.parent.name)) {
348
+ return;
349
+ }
350
+ if (!isModelValueValid) {
351
+ context.report({ node, messageId: 'outdatedVueLibrary' });
352
+ return;
353
+ }
354
+ context.report({
355
+ node,
356
+ messageId: 'useModelValueInsteadChecked',
357
+ fix: (fixer) => {
358
+ if (node.key.type === 'VIdentifier') {
359
+ return fixer.replaceTextRange(node.key.range, 'model-value');
360
+ }
361
+ else if (node.key.type === 'VDirectiveKey') {
362
+ if (node.key.name.name === 'model') {
363
+ return fixer.replaceTextRange(node.key.range, 'v-model');
364
+ }
365
+ else if (node.key.modifiers.some((m) => m.name === 'sync')) {
366
+ return fixer.replaceTextRange(node.key.range, 'v-model');
367
+ }
368
+ else {
369
+ return fixer.replaceTextRange(node.key.argument.range, 'model-value');
370
+ }
371
+ }
372
+ },
373
+ });
374
+ },
375
+ 'VElement VAttribute:has(VIdentifier[name="value"])': function (node) {
376
+ if (![
377
+ 'ncactioninput',
378
+ 'ncactiontexteditable',
379
+ 'nccolorpicker',
380
+ 'ncdatetimepicker',
381
+ 'ncdatetimepickernative',
382
+ 'ncinputfield',
383
+ 'nctextfield',
384
+ 'ncpasswordfield',
385
+ 'ncrichcontenteditable',
386
+ 'ncselecttags',
387
+ 'ncselect',
388
+ 'ncsettingsinputtext',
389
+ 'ncsettingsselectgroup',
390
+ 'nctextarea',
391
+ 'nctimezonepicker',
392
+ ].includes(node.parent.parent.name)) {
393
+ return;
394
+ }
395
+ if (!isModelValueValid) {
396
+ context.report({ node, messageId: 'outdatedVueLibrary' });
397
+ return;
398
+ }
399
+ context.report({
400
+ node,
401
+ messageId: 'useModelValueInsteadValue',
402
+ fix: (fixer) => {
403
+ if (node.key.type === 'VIdentifier') {
404
+ return fixer.replaceTextRange(node.key.range, 'model-value');
405
+ }
406
+ else if (node.key.type === 'VDirectiveKey') {
407
+ if (node.key.name.name === 'model') {
408
+ return fixer.replaceTextRange(node.key.range, 'v-model');
409
+ }
410
+ else if (node.key.modifiers.some((m) => m.name === 'sync')) {
411
+ return fixer.replaceTextRange(node.key.range, 'v-model');
412
+ }
413
+ else {
414
+ return fixer.replaceTextRange(node.key.argument.range, 'model-value');
415
+ }
416
+ }
417
+ },
418
+ });
419
+ },
234
420
  });
235
421
  },
236
422
  };
@@ -1,23 +1,3 @@
1
- /**
2
- * Check if a given path exists and is a file
3
- *
4
- * @param filePath The path
5
- */
6
- export declare function isFile(filePath: string): boolean;
7
- /**
8
- * Find the path of nearest `package.json` relative to given path
9
- *
10
- * @param currentPath Path to lookup
11
- * @return Either the full path where `package.json` is located or `undefined` if no found
12
- */
13
- export declare function findPackageJsonDir(currentPath: string): string | undefined;
14
- /**
15
- * Make sure that versions like '25' can be handled by semver
16
- *
17
- * @param version The pure version string
18
- * @return Sanitized version string
19
- */
20
- export declare function sanitizeTargetVersion(version: string): string;
21
1
  /**
22
2
  * Create a callback that takes a version number and checks if the version
23
3
  * is valid compared to configured version / detected version.
@@ -25,9 +5,14 @@ export declare function sanitizeTargetVersion(version: string): string;
25
5
  * @param options Options
26
6
  * @param options.cwd The current working directory
27
7
  * @param options.physicalFilename The real filename where ESLint is linting currently
8
+ * @param importResolve Optional custom import resolver function (for testing purposes)
28
9
  * @return Function validator, return a boolean whether current version satisfies minimal required for the rule
29
10
  */
30
11
  export declare function createLibVersionValidator({ cwd, physicalFilename }: {
31
12
  cwd: any;
32
13
  physicalFilename: any;
33
- }): ((version: string) => boolean);
14
+ }, importResolve?: (specifier: string, parent?: string | import("node:url").URL) => string): ((version: string) => boolean);
15
+ /**
16
+ * Clear the module cache
17
+ */
18
+ export declare function clearCache(): void;
@@ -2,56 +2,14 @@
2
2
  * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
3
  * SPDX-License-Identifier: AGPL-3.0-or-later
4
4
  */
5
- import { lstatSync, readFileSync } from 'node:fs';
6
- import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
7
- import { gte, minVersion, valid } from 'semver';
5
+ import { readFileSync } from 'node:fs';
6
+ import { isAbsolute, join, resolve } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+ import { gte } from 'semver';
8
9
  /**
9
10
  * Cached map of paths: Reco <path_to_package.json, validator>
10
11
  */
11
- const cachedMap = {};
12
- /**
13
- * Check if a given path exists and is a file
14
- *
15
- * @param filePath The path
16
- */
17
- export function isFile(filePath) {
18
- const stats = lstatSync(filePath, { throwIfNoEntry: false });
19
- return stats !== undefined && stats.isFile();
20
- }
21
- /**
22
- * Find the path of nearest `package.json` relative to given path
23
- *
24
- * @param currentPath Path to lookup
25
- * @return Either the full path where `package.json` is located or `undefined` if no found
26
- */
27
- export function findPackageJsonDir(currentPath) {
28
- for (const cachedDirPath of Object.keys(cachedMap)) {
29
- if (currentPath.startsWith(cachedDirPath)) {
30
- return cachedDirPath;
31
- }
32
- }
33
- while (currentPath && currentPath !== sep) {
34
- if (isFile(join(currentPath, 'package.json'))) {
35
- return currentPath;
36
- }
37
- currentPath = resolve(currentPath, '..');
38
- }
39
- return undefined;
40
- }
41
- /**
42
- * Make sure that versions like '25' can be handled by semver
43
- *
44
- * @param version The pure version string
45
- * @return Sanitized version string
46
- */
47
- export function sanitizeTargetVersion(version) {
48
- const sanitizedVersion = minVersion(version)?.version;
49
- // now version should look like '23.0.0'
50
- if (!valid(sanitizedVersion)) {
51
- throw Error(`[@nextcloud/eslint-plugin] Invalid target version ${version} found`);
52
- }
53
- return sanitizedVersion;
54
- }
12
+ const cachedMap = new Map();
55
13
  /**
56
14
  * Create a callback that takes a version number and checks if the version
57
15
  * is valid compared to configured version / detected version.
@@ -59,36 +17,33 @@ export function sanitizeTargetVersion(version) {
59
17
  * @param options Options
60
18
  * @param options.cwd The current working directory
61
19
  * @param options.physicalFilename The real filename where ESLint is linting currently
20
+ * @param importResolve Optional custom import resolver function (for testing purposes)
62
21
  * @return Function validator, return a boolean whether current version satisfies minimal required for the rule
63
22
  */
64
- export function createLibVersionValidator({ cwd, physicalFilename }) {
65
- // Try to find package.json and parse the supported version
66
- // Current working directory, either the filename (can be empty) or the cwd property
67
- const currentDirectory = isAbsolute(physicalFilename)
68
- ? resolve(dirname(physicalFilename))
69
- : dirname(resolve(cwd, physicalFilename));
70
- // The nearest package.json
71
- const packageJsonDir = findPackageJsonDir(currentDirectory);
72
- if (!packageJsonDir) {
73
- // Skip the rule
74
- return () => false;
23
+ export function createLibVersionValidator({ cwd, physicalFilename }, importResolve = import.meta.resolve) {
24
+ // Try to find package.json of the nextcloud-vue package
25
+ const sourceFile = isAbsolute(physicalFilename)
26
+ ? resolve(physicalFilename)
27
+ : resolve(cwd, physicalFilename);
28
+ let packageJsonDir;
29
+ try {
30
+ const modulePath = fileURLToPath(importResolve('@nextcloud/vue', pathToFileURL(sourceFile)));
31
+ const idx = modulePath.lastIndexOf('/dist/');
32
+ packageJsonDir = modulePath.substring(0, idx);
75
33
  }
76
- else if (cachedMap[packageJsonDir]) {
77
- return cachedMap[packageJsonDir];
78
- }
79
- const json = JSON.parse(readFileSync(join(packageJsonDir, 'package.json'), 'utf-8'));
80
- const libVersions = [
81
- json?.dependencies?.['@nextcloud/vue'],
82
- json?.devDependencies?.['@nextcloud/vue'],
83
- json?.peerDependencies?.['@nextcloud/vue'],
84
- ]
85
- .filter((version) => typeof version === 'string' && !!version)
86
- .map(sanitizeTargetVersion)
87
- .sort((a, b) => gte(a, b) ? 1 : -1);
88
- if (!libVersions.length) {
89
- // Skip the rule
34
+ catch {
90
35
  return () => false;
91
36
  }
92
- // Return, whether given version satisfies minimal version from dependencies
93
- return (version) => gte(libVersions[0], version);
37
+ if (!cachedMap[packageJsonDir]) {
38
+ const json = JSON.parse(readFileSync(join(packageJsonDir, 'package.json'), 'utf-8'));
39
+ const libVersion = json.version;
40
+ cachedMap[packageJsonDir] = (version) => gte(libVersion, version);
41
+ }
42
+ return cachedMap[packageJsonDir];
43
+ }
44
+ /**
45
+ * Clear the module cache
46
+ */
47
+ export function clearCache() {
48
+ cachedMap.clear();
94
49
  }
@@ -43,6 +43,7 @@ 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
46
47
  return fixer.replaceText(body, sortedPackageJsonText);
47
48
  },
48
49
  });
package/dist/utils.d.ts CHANGED
@@ -9,16 +9,4 @@ import type { Linter } from 'eslint';
9
9
  * @param configs The configs to restrict
10
10
  * @param files The glob pattern to assign
11
11
  */
12
- export declare function restrictConfigFiles(configs: Linter.Config[], files: string[]): {
13
- files: (string | string[])[];
14
- name?: string;
15
- basePath?: string;
16
- ignores?: string[];
17
- language?: string;
18
- languageOptions?: Linter.LanguageOptions;
19
- linterOptions?: Linter.LinterOptions;
20
- processor?: string | Linter.Processor;
21
- plugins?: Record<string, import("eslint").ESLint.Plugin>;
22
- rules?: Partial<Linter.RulesRecord>;
23
- settings?: Record<string, unknown>;
24
- }[];
12
+ export declare function restrictConfigFiles(configs: Linter.Config[], files: string[]): Linter.Config[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextcloud/eslint-config",
3
- "version": "9.0.0-rc.5",
3
+ "version": "9.0.0-rc.7",
4
4
  "description": "Eslint shared config for nextcloud apps and libraries",
5
5
  "keywords": [
6
6
  "eslint",
@@ -37,31 +37,31 @@
37
37
  "build": "npm run build:cleanup && npm run build:source",
38
38
  "build:cleanup": "tsc --build --clean",
39
39
  "build:source": "tsc",
40
- "lint": "npx --node-options='--experimental-strip-types' eslint --flag unstable_native_nodejs_ts_config",
41
- "lint:fix": "npx --node-options='--experimental-strip-types' eslint --flag unstable_native_nodejs_ts_config --fix",
40
+ "lint": "eslint --flag unstable_native_nodejs_ts_config",
41
+ "lint:fix": "eslint --flag unstable_native_nodejs_ts_config --fix",
42
42
  "prerelease:format-changelog": "node build/format-changelog.mjs",
43
43
  "test": "vitest run"
44
44
  },
45
45
  "dependencies": {
46
- "@eslint/json": "^0.13.1",
47
- "@stylistic/eslint-plugin": "^5.2.3",
46
+ "@eslint/json": "^0.14.0",
47
+ "@stylistic/eslint-plugin": "^5.7.0",
48
48
  "eslint-config-flat-gitignore": "^2.1.0",
49
- "eslint-plugin-antfu": "^3.1.1",
50
- "eslint-plugin-jsdoc": "^53.0.1",
51
- "eslint-plugin-perfectionist": "^4.15.0",
52
- "eslint-plugin-vue": "^10.4.0",
53
- "fast-xml-parser": "^5.2.5",
54
- "globals": "^16.3.0",
55
- "semver": "^7.7.2",
56
- "sort-package-json": "^3.4.0",
57
- "typescript-eslint": "^8.39.0"
49
+ "eslint-plugin-antfu": "^3.1.3",
50
+ "eslint-plugin-jsdoc": "^62.0.0",
51
+ "eslint-plugin-perfectionist": "^5.3.1",
52
+ "eslint-plugin-vue": "^10.6.2",
53
+ "fast-xml-parser": "^5.3.3",
54
+ "globals": "^17.0.0",
55
+ "semver": "^7.7.3",
56
+ "sort-package-json": "^3.6.0",
57
+ "typescript-eslint": "^8.53.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@types/node": "^24.2.1",
61
- "@types/semver": "^7.7.0",
62
- "eslint": "^9.33.0",
63
- "memfs": "^4.36.0",
64
- "vitest": "^3.2.4"
60
+ "@types/node": "^25.0.6",
61
+ "@types/semver": "^7.7.1",
62
+ "eslint": "^9.39.2",
63
+ "memfs": "^4.51.1",
64
+ "vitest": "^4.0.17"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "eslint": ">=9"
@@ -73,12 +73,12 @@
73
73
  "packageManager": [
74
74
  {
75
75
  "name": "npm",
76
- "version": "^10"
76
+ "version": "^11.3.0"
77
77
  }
78
78
  ],
79
79
  "runtime": {
80
80
  "name": "node",
81
- "version": "^22.10"
81
+ "version": "^24.3.0"
82
82
  }
83
83
  }
84
84
  }