@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 +27 -7
- package/dist/configs/codeStyle.d.ts +1 -1
- package/dist/configs/codeStyle.js +18 -19
- package/dist/configs/documentation.js +20 -26
- package/dist/configs/imports.d.ts +1 -1
- package/dist/configs/imports.js +10 -7
- package/dist/configs/vue.js +6 -6
- package/dist/configs/vue2.js +2 -2
- package/dist/configs/vue3.js +22 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +4 -0
- package/dist/plugins/nextcloud/rules/no-deprecations.js +11 -1
- package/dist/plugins/nextcloud/rules/no-removed-apis.js +39 -0
- package/dist/plugins/nextcloud-vue/index.d.ts +8 -0
- package/dist/plugins/nextcloud-vue/rules/index.d.ts +8 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-exports.js +43 -2
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-props.d.ts +8 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-props.js +187 -1
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.d.ts +6 -21
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.js +29 -74
- package/dist/plugins/packageJson.js +1 -0
- package/dist/utils.d.ts +1 -13
- package/package.json +21 -21
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
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Updated `
|
|
83
|
-
* Updated
|
|
84
|
-
* Updated `
|
|
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):
|
|
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
|
|
79
|
-
'@stylistic/array-bracket-newline':
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'@stylistic/
|
|
85
|
-
|
|
86
|
-
'@stylistic/
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
...
|
|
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.
|
package/dist/configs/imports.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 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:
|
|
39
|
+
newlinesBetween: 0,
|
|
36
40
|
groups: [
|
|
37
41
|
// type first
|
|
38
|
-
'external-
|
|
39
|
-
'type',
|
|
40
|
-
{ newlinesBetween:
|
|
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:
|
|
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:
|
|
99
|
+
newlinesBetween: 1,
|
|
97
100
|
partitionByNewLine: false,
|
|
98
101
|
groups: [
|
|
99
102
|
`type-${type}`,
|
package/dist/configs/vue.js
CHANGED
|
@@ -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
|
|
41
|
-
'vue/component-name-in-template-casing': [
|
|
42
|
-
|
|
43
|
-
|
|
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',
|
package/dist/configs/vue2.js
CHANGED
|
@@ -13,12 +13,12 @@ export function vue2(option) {
|
|
|
13
13
|
...vue(option),
|
|
14
14
|
{
|
|
15
15
|
rules: {
|
|
16
|
-
// custom event
|
|
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
|
-
//
|
|
21
|
+
// Allow namespace formats namespace:event
|
|
22
22
|
ignores: ['/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'],
|
|
23
23
|
},
|
|
24
24
|
],
|
package/dist/configs/vue3.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 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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: '
|
|
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
|
|
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 (!
|
|
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
|
|
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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
}
|
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.
|
|
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": "
|
|
41
|
-
"lint: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.
|
|
47
|
-
"@stylistic/eslint-plugin": "^5.
|
|
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.
|
|
50
|
-
"eslint-plugin-jsdoc": "^
|
|
51
|
-
"eslint-plugin-perfectionist": "^
|
|
52
|
-
"eslint-plugin-vue": "^10.
|
|
53
|
-
"fast-xml-parser": "^5.
|
|
54
|
-
"globals": "^
|
|
55
|
-
"semver": "^7.7.
|
|
56
|
-
"sort-package-json": "^3.
|
|
57
|
-
"typescript-eslint": "^8.
|
|
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": "^
|
|
61
|
-
"@types/semver": "^7.7.
|
|
62
|
-
"eslint": "^9.
|
|
63
|
-
"memfs": "^4.
|
|
64
|
-
"vitest": "^
|
|
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": "^
|
|
76
|
+
"version": "^11.3.0"
|
|
77
77
|
}
|
|
78
78
|
],
|
|
79
79
|
"runtime": {
|
|
80
80
|
"name": "node",
|
|
81
|
-
"version": "^
|
|
81
|
+
"version": "^24.3.0"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|