@gitlab/eslint-plugin 20.1.0 → 20.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [20.2.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v20.1.0...v20.2.0) (2024-09-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add rule for uninitialised apollo query properties ([f89eace](https://gitlab.com/gitlab-org/frontend/eslint-plugin/commit/f89eace7498900d90e06b0be214c51a589474d53)), closes [#74](https://gitlab.com/gitlab-org/frontend/eslint-plugin/issues/74)
|
|
7
|
+
|
|
1
8
|
# [20.1.0](https://gitlab.com/gitlab-org/frontend/eslint-plugin/compare/v20.0.0...v20.1.0) (2024-08-28)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# @gitlab/vue-no-undef-apollo-properties
|
|
2
|
+
|
|
3
|
+
Require Apollo query properties to be initialized in component data.
|
|
4
|
+
|
|
5
|
+
Sensible initial values for Apollo queries should be defined in component data.
|
|
6
|
+
This reduces the likelihood of unexpected behaviour, especially when running
|
|
7
|
+
under @vue/compat.
|
|
8
|
+
|
|
9
|
+
This rule does not detect whether the initial value is of the correct type.
|
|
10
|
+
|
|
11
|
+
## Rule Details
|
|
12
|
+
|
|
13
|
+
### Examples of **incorrect** code for this rule
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<script>
|
|
17
|
+
export default {
|
|
18
|
+
data() {
|
|
19
|
+
return {
|
|
20
|
+
foo: '',
|
|
21
|
+
qux: undefined,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
apollo: {
|
|
25
|
+
$subscribe: {
|
|
26
|
+
foo: {
|
|
27
|
+
// ...
|
|
28
|
+
},
|
|
29
|
+
bar: {
|
|
30
|
+
// This isn't defined in data.
|
|
31
|
+
// ...
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
qux: {
|
|
35
|
+
// ...
|
|
36
|
+
},
|
|
37
|
+
bbq: {
|
|
38
|
+
// This isn't defined in data.
|
|
39
|
+
// ...
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
render(h) {
|
|
43
|
+
return h();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Examples of **correct** code for this rule
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<script>
|
|
53
|
+
export default {
|
|
54
|
+
data() {
|
|
55
|
+
return {
|
|
56
|
+
foo: '',
|
|
57
|
+
qux: undefined,
|
|
58
|
+
bar: null,
|
|
59
|
+
bbq: [],
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
apollo: {
|
|
63
|
+
$subscribe: {
|
|
64
|
+
foo: {
|
|
65
|
+
// ...
|
|
66
|
+
},
|
|
67
|
+
bar: {
|
|
68
|
+
// ...
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
qux: {
|
|
72
|
+
// ...
|
|
73
|
+
},
|
|
74
|
+
bbq: {
|
|
75
|
+
// ...
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
render(h) {
|
|
79
|
+
return h();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Options
|
|
86
|
+
|
|
87
|
+
Nothing
|
|
88
|
+
|
|
89
|
+
## Related rules
|
|
90
|
+
|
|
91
|
+
Nothing
|
|
92
|
+
|
|
93
|
+
## When Not To Use It
|
|
94
|
+
|
|
95
|
+
If you don't use VueApollo with the Options API, don't use this rule.
|
package/docs/rules.md
CHANGED
|
@@ -8,15 +8,16 @@ Available rules:
|
|
|
8
8
|
- [no-runtime-template-compiler](./rules/no-runtime-template-compiler.md): Disallow components which rely on a runtime template compiler.
|
|
9
9
|
- [require-i18n-strings](./rules/require-i18n-strings.md): Detect a string which has been hard coded and requires externalization.
|
|
10
10
|
- [require-valid-i18n-helpers](./rules/require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in JavaScript.
|
|
11
|
-
- [tailwind](./rules/tailwind.md):
|
|
11
|
+
- [tailwind](./rules/tailwind.md): Ensures Tailwind CSS is used according to our internal guidelines.
|
|
12
12
|
- [vtu-no-explicit-wrapper-destroy](./rules/vtu-no-explicit-wrapper-destroy.md): Prevents redundant destroy calls and null asignment
|
|
13
13
|
- [vtu-no-wrapper-vm](./rules/vtu-no-wrapper-vm.md): Prevent direct access to `vm` internals for `@vue/test-utils` wrappers.
|
|
14
14
|
- [vue-no-data-toggle](./rules/vue-no-data-toggle.md): Restrict the use of `data-toggle` bootstrap behaviors within Vue templates
|
|
15
15
|
- [vue-no-new-non-primitive-in-template](./rules/vue-no-new-non-primitive-in-template.md): Prevents non-primitive values from being declared in templates
|
|
16
|
+
- [vue-no-undef-apollo-properties](./rules/vue-no-undef-apollo-properties.md): Require Apollo query properties to be initialized in component data.
|
|
16
17
|
- [vue-prefer-dollar-scopedslots](./rules/vue-prefer-dollar-scopedslots.md): Prefer $scopedSlots over $slots for Vue 2.x.
|
|
17
18
|
- [vue-require-i18n-attribute-strings](./rules/vue-require-i18n-attribute-strings.md): Detect non externalized strings in vue `<template>` attributes
|
|
18
19
|
- [vue-require-i18n-strings](./rules/vue-require-i18n-strings.md): enforce no bare strings in vue `<template>`
|
|
19
20
|
- [vue-require-required-key](./rules/vue-require-required-key.md): Require the required key to be set
|
|
20
21
|
- [vue-require-valid-i18n-helpers](./rules/vue-require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in Vue templates.
|
|
21
22
|
- [vue-slot-name-casing](./rules/vue-slot-name-casing.md): enforce specific casing for slot naming style in template
|
|
22
|
-
- [vue-tailwind](./rules/vue-tailwind.md):
|
|
23
|
+
- [vue-tailwind](./rules/vue-tailwind.md): Ensures Tailwind CSS is used according to our internal guidelines in Vue templates.
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
'vtu-no-wrapper-vm': require('./rules/vtu-no-wrapper-vm.js'),
|
|
13
13
|
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
14
14
|
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
15
|
+
'vue-no-undef-apollo-properties': require('./rules/vue-no-undef-apollo-properties.js'),
|
|
15
16
|
'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
|
|
16
17
|
'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
|
|
17
18
|
'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ------------------------------------------------------------------------------
|
|
2
|
+
// Requirements
|
|
3
|
+
// ------------------------------------------------------------------------------
|
|
4
|
+
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
+
const utils = require('eslint-plugin-vue/lib/utils');
|
|
6
|
+
|
|
7
|
+
// ------------------------------------------------------------------------------
|
|
8
|
+
// Helpers
|
|
9
|
+
// ------------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const DOLLAR_SUBSCRIBE = '$subscribe';
|
|
12
|
+
|
|
13
|
+
const VUE_APOLLO_SPECIAL_OPTIONS = new Set([
|
|
14
|
+
'$skipAll',
|
|
15
|
+
'$skipAllQueries',
|
|
16
|
+
'$skipAllSubscriptions',
|
|
17
|
+
'$deep',
|
|
18
|
+
'$error',
|
|
19
|
+
'$query',
|
|
20
|
+
|
|
21
|
+
// This one isn't special in the same way as the rest, but the normal code
|
|
22
|
+
// paths below need to be skipped for this case as well. It is handled in a
|
|
23
|
+
// separate code path.
|
|
24
|
+
DOLLAR_SUBSCRIBE,
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
function findProperty(objectExpression, name) {
|
|
28
|
+
return objectExpression.properties.find(
|
|
29
|
+
(p) =>
|
|
30
|
+
p.type === 'Property' &&
|
|
31
|
+
utils.getStaticPropertyName(p) === name &&
|
|
32
|
+
p.value.type === 'ObjectExpression',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getDataKeys(componentObject) {
|
|
37
|
+
return new Set(
|
|
38
|
+
Array.from(utils.iterateProperties(componentObject, new Set(['data']))).map(({ name }) => name),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function* getApolloKeyNodeWrappers(componentObject) {
|
|
43
|
+
const apolloNode = findProperty(componentObject, 'apollo');
|
|
44
|
+
|
|
45
|
+
if (!apolloNode) return;
|
|
46
|
+
|
|
47
|
+
for (const nodeWrapper of utils.iterateObjectExpression(apolloNode.value)) {
|
|
48
|
+
if (VUE_APOLLO_SPECIAL_OPTIONS.has(nodeWrapper.name)) {
|
|
49
|
+
// Future-proofing against VueApollo 4's special options:
|
|
50
|
+
// https://apollo.vuejs.org/guide-option/special-options.html#special-options
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
yield nodeWrapper;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const subscribeNode = findProperty(apolloNode.value, DOLLAR_SUBSCRIBE);
|
|
58
|
+
|
|
59
|
+
if (subscribeNode) {
|
|
60
|
+
yield* utils.iterateObjectExpression(subscribeNode.value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function* getApolloKeyNodes(componentObject) {
|
|
65
|
+
for (const nodeWrapper of getApolloKeyNodeWrappers(componentObject)) {
|
|
66
|
+
yield nodeWrapper.node;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ------------------------------------------------------------------------------
|
|
71
|
+
// Rule Definition
|
|
72
|
+
// ------------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
meta: {
|
|
76
|
+
type: 'error',
|
|
77
|
+
docs: {
|
|
78
|
+
description: 'Require Apollo query properties to be initialized in component data.',
|
|
79
|
+
url: DOCS_BASE_URL + '/vue-no-undef-apollo-properties.md',
|
|
80
|
+
},
|
|
81
|
+
messages: {
|
|
82
|
+
noInitialValue: 'Apollo query property {{name}} is not initialized in component data.',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
create(context) {
|
|
86
|
+
return utils.executeOnVue(context, (node) => {
|
|
87
|
+
const dataKeys = getDataKeys(node);
|
|
88
|
+
|
|
89
|
+
for (const apolloKeyNode of getApolloKeyNodes(node)) {
|
|
90
|
+
if (!dataKeys.has(apolloKeyNode.name)) {
|
|
91
|
+
context.report({
|
|
92
|
+
node: apolloKeyNode,
|
|
93
|
+
messageId: 'noInitialValue',
|
|
94
|
+
data: {
|
|
95
|
+
name: apolloKeyNode.name,
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
};
|