@gitlab/eslint-plugin 21.5.0 → 23.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +299 -0
- package/README.md +16 -11
- package/docs/development.md +2 -1
- package/docs/usage.md +158 -47
- package/eslint9/index.js +6 -7
- package/lib/configs/base/best-practices.js +0 -4
- package/lib/configs/base/es6.js +1 -13
- package/lib/configs/base/imports.js +86 -106
- package/lib/configs/base/node.js +0 -4
- package/lib/confusing-browser-globals.js +68 -0
- package/lib/flat-configs/base.js +122 -0
- package/lib/flat-configs/default.js +13 -0
- package/lib/flat-configs/i18n.js +14 -0
- package/lib/flat-configs/jest.js +20 -0
- package/lib/flat-configs/tailwind.js +19 -0
- package/lib/flat-configs/typescript.js +136 -0
- package/lib/flat-configs/vue.js +16 -0
- package/lib/index.js +30 -39
- package/lib/plugin.js +32 -0
- package/lib/prettier-rules.js +10 -0
- package/lib/rules/no-runtime-template-compiler.js +6 -3
- package/lib/rules/require-i18n-strings.js +4 -4
- package/lib/rules/vtu-no-explicit-wrapper-destroy.js +1 -1
- package/lib/rules/vue-no-new-non-primitive-in-template.js +1 -1
- package/lib/rules/vue-no-undef-apollo-properties.js +1 -1
- package/lib/rules/vue-prefer-dollar-scopedslots.js +1 -1
- package/lib/rules/vue-require-required-key.js +1 -1
- package/lib/rules/vue-slot-name-casing.js +2 -3
- package/lib/utils/hardcoded-urls.js +1 -1
- package/lib/utils/string-utils.js +6 -8
- package/lib/vue-fragments.js +111 -0
- package/package.json +31 -12
- package/scripts/benchmarking/checkAllStrings.js +1 -1
- package/scripts/helpers/getConfigs.js +15 -6
- package/scripts/publish_npm_package.mjs +0 -2
- package/scripts/updateFiles.js +30 -6
- package/eslint9/configs/base.js +0 -140
- package/eslint9/configs/typescript.js +0 -134
- package/lib/configs/base.js +0 -115
- package/lib/configs/default.js +0 -17
- package/lib/configs/i18n.js +0 -10
- package/lib/configs/jest.js +0 -21
- package/lib/configs/tailwind.js +0 -10
- package/lib/configs/typescript.js +0 -95
- package/lib/configs/vue.js +0 -91
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const js = require('@eslint/js');
|
|
2
|
+
const tsPlugin = require('@typescript-eslint/eslint-plugin');
|
|
3
|
+
const tsParser = require('@typescript-eslint/parser');
|
|
4
|
+
const importPlugin = require('eslint-plugin-import-x');
|
|
5
|
+
const globals = require('globals');
|
|
6
|
+
|
|
7
|
+
const base = require('./base');
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
Rules related to the GitLab TypeScript style guide.
|
|
11
|
+
Base JavaScript rules live in lib/flat-configs/base.js
|
|
12
|
+
|
|
13
|
+
Two things here are load-bearing:
|
|
14
|
+
|
|
15
|
+
1. base is spread first and the `.ts` layer second, so eslint:recommended and
|
|
16
|
+
the TypeScript preset win over eslint-config-prettier inside .ts files,
|
|
17
|
+
as eslintrc's `overrides` did. Putting prettier last would not.
|
|
18
|
+
|
|
19
|
+
2. `flat/eslint-recommended` is spread alongside `strict`, because eslintrc's
|
|
20
|
+
`plugin:@typescript-eslint/strict` extended it. Without it, 19 core rules
|
|
21
|
+
that a TypeScript-aware replacement or the compiler already covers stay
|
|
22
|
+
enabled.
|
|
23
|
+
*/
|
|
24
|
+
const tsEslintRecommended = tsPlugin.configs['flat/eslint-recommended'];
|
|
25
|
+
const tsStrict = tsPlugin.configs.strict;
|
|
26
|
+
/*
|
|
27
|
+
Mirrors eslint-plugin-import's `typescript` preset. import-x's own
|
|
28
|
+
`flatConfigs.typescript` isn't used because its resolver needs
|
|
29
|
+
eslint-import-resolver-typescript.
|
|
30
|
+
|
|
31
|
+
Unlike base, this sets a resolver: import-x's default one doesn't know the
|
|
32
|
+
TypeScript extensions, so `import './foo'` wouldn't find `foo.ts`. Consumers
|
|
33
|
+
who need `paths` aliases replace it with eslint-import-resolver-typescript,
|
|
34
|
+
which also resolves everything this one does.
|
|
35
|
+
*/
|
|
36
|
+
const TS_EXTENSIONS = ['.ts', '.cts', '.mts', '.tsx'];
|
|
37
|
+
const ALL_EXTENSIONS = [...TS_EXTENSIONS, '.js', '.jsx', '.mjs', '.cjs'];
|
|
38
|
+
const importTypescriptSettings = {
|
|
39
|
+
'import-x/extensions': ALL_EXTENSIONS,
|
|
40
|
+
'import-x/external-module-folders': ['node_modules', 'node_modules/@types'],
|
|
41
|
+
'import-x/parsers': { '@typescript-eslint/parser': TS_EXTENSIONS },
|
|
42
|
+
'import-x/resolver-next': [importPlugin.createNodeResolver({ extensions: ALL_EXTENSIONS })],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = [
|
|
46
|
+
...base,
|
|
47
|
+
{
|
|
48
|
+
name: '@gitlab/typescript/setup',
|
|
49
|
+
languageOptions: {
|
|
50
|
+
globals: {
|
|
51
|
+
...globals.mocha,
|
|
52
|
+
...globals.jest,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
rules: {
|
|
56
|
+
'import-x/extensions': [
|
|
57
|
+
'error',
|
|
58
|
+
'ignorePackages',
|
|
59
|
+
{ js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: '@gitlab/typescript/ts',
|
|
65
|
+
files: ['**/*.ts'],
|
|
66
|
+
languageOptions: {
|
|
67
|
+
parser: tsParser,
|
|
68
|
+
parserOptions: {
|
|
69
|
+
project: ['./tsconfig.json'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
plugins: {
|
|
73
|
+
'@typescript-eslint': tsPlugin,
|
|
74
|
+
'import-x': importPlugin,
|
|
75
|
+
},
|
|
76
|
+
linterOptions: {
|
|
77
|
+
reportUnusedDisableDirectives: true,
|
|
78
|
+
},
|
|
79
|
+
settings: importTypescriptSettings,
|
|
80
|
+
rules: {
|
|
81
|
+
...js.configs.recommended.rules,
|
|
82
|
+
...tsEslintRecommended.rules,
|
|
83
|
+
...tsStrict.rules,
|
|
84
|
+
'import-x/named': 'off',
|
|
85
|
+
semi: [2, 'always'],
|
|
86
|
+
'max-params': 'off',
|
|
87
|
+
'no-throw-literal': 'off',
|
|
88
|
+
'no-shadow': 'off',
|
|
89
|
+
'no-empty-function': 'off',
|
|
90
|
+
'no-plusplus': 'off',
|
|
91
|
+
'unicorn/no-array-callback-reference': 'off',
|
|
92
|
+
'@typescript-eslint/only-throw-error': 'error',
|
|
93
|
+
'@typescript-eslint/no-unused-vars': 'error',
|
|
94
|
+
'@typescript-eslint/no-shadow': 'error',
|
|
95
|
+
'@typescript-eslint/return-await': 'error',
|
|
96
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
97
|
+
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
|
|
98
|
+
'class-methods-use-this': 'off',
|
|
99
|
+
'import-x/no-cycle': 'error',
|
|
100
|
+
'promise/param-names': 'off',
|
|
101
|
+
'no-use-before-define': ['warn', 'nofunc'],
|
|
102
|
+
'no-restricted-syntax': [
|
|
103
|
+
'error',
|
|
104
|
+
{
|
|
105
|
+
selector: ':matches(PropertyDefinition, MethodDefinition)[accessibility="private"]',
|
|
106
|
+
message: 'Use # prefix instead of `private` to indicate private class members.',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
selector: 'ForInStatement',
|
|
110
|
+
message:
|
|
111
|
+
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
selector: 'LabeledStatement',
|
|
115
|
+
message:
|
|
116
|
+
'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
selector: 'WithStatement',
|
|
120
|
+
message:
|
|
121
|
+
'`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
// Recursive: eslintrc matched a bare `*.test.ts` at any depth, flat globs
|
|
128
|
+
// are path-relative.
|
|
129
|
+
name: '@gitlab/typescript/tests',
|
|
130
|
+
files: ['**/*.test.ts', '**/*.test.js'],
|
|
131
|
+
rules: {
|
|
132
|
+
'max-classes-per-file': 'off',
|
|
133
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const prettierRules = require('../prettier-rules');
|
|
2
|
+
const { presetAndSetup, gitlabRules } = require('../vue-fragments');
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Rules related to the GitLab Vue style guide.
|
|
6
|
+
Base JavaScript rules live in lib/flat-configs/base.js
|
|
7
|
+
|
|
8
|
+
eslint-config-prettier sits between the preset and this package's rules,
|
|
9
|
+
switching the preset's formatting rules off. lib/flat-configs/default.js
|
|
10
|
+
orders it the other way -- see the note there.
|
|
11
|
+
*/
|
|
12
|
+
module.exports = [
|
|
13
|
+
...presetAndSetup,
|
|
14
|
+
{ name: '@gitlab/vue/prettier', rules: prettierRules },
|
|
15
|
+
...gitlabRules,
|
|
16
|
+
];
|
package/lib/index.js
CHANGED
|
@@ -1,46 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* This file is GENERATED, please run `yarn update` after adding or renaming a rule
|
|
3
3
|
*/
|
|
4
|
+
const plugin = require('./plugin.js');
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Getters, so requiring the plugin never loads a config the consumer is not
|
|
8
|
+
using: `typescript` pulls in optional peer dependencies, and eagerly
|
|
9
|
+
requiring it would throw for anyone without the TypeScript toolchain. Getters
|
|
10
|
+
in an object literal are enumerable, so `Object.keys(configs)` is unaffected.
|
|
11
|
+
*/
|
|
4
12
|
module.exports = {
|
|
5
|
-
|
|
6
|
-
'no-global-event-off': require('./rules/no-global-event-off.js'),
|
|
7
|
-
'no-hardcoded-urls': require('./rules/no-hardcoded-urls.js'),
|
|
8
|
-
'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
|
|
9
|
-
'require-i18n-strings': require('./rules/require-i18n-strings.js'),
|
|
10
|
-
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
11
|
-
'tailwind-no-interpolation': require('./rules/tailwind-no-interpolation.js'),
|
|
12
|
-
'tailwind-no-max-width-media-queries': require('./rules/tailwind-no-max-width-media-queries.js'),
|
|
13
|
-
'vtu-no-explicit-wrapper-destroy': require('./rules/vtu-no-explicit-wrapper-destroy.js'),
|
|
14
|
-
'vtu-no-wrapper-vm': require('./rules/vtu-no-wrapper-vm.js'),
|
|
15
|
-
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
16
|
-
'vue-no-hardcoded-urls': require('./rules/vue-no-hardcoded-urls.js'),
|
|
17
|
-
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
18
|
-
'vue-no-popover-target-dollar-el': require('./rules/vue-no-popover-target-dollar-el.js'),
|
|
19
|
-
'vue-no-undef-apollo-properties': require('./rules/vue-no-undef-apollo-properties.js'),
|
|
20
|
-
'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
|
|
21
|
-
'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
|
|
22
|
-
'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
|
|
23
|
-
'vue-require-required-key': require('./rules/vue-require-required-key.js'),
|
|
24
|
-
'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
|
|
25
|
-
'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
|
|
26
|
-
'vue-tailwind-no-interpolation': require('./rules/vue-tailwind-no-interpolation.js'),
|
|
27
|
-
'vue-tailwind-no-max-width-media-queries': require('./rules/vue-tailwind-no-max-width-media-queries.js'),
|
|
28
|
-
},
|
|
13
|
+
...plugin,
|
|
29
14
|
configs: {
|
|
30
|
-
base
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
tailwind
|
|
43
|
-
|
|
44
|
-
|
|
15
|
+
get base() {
|
|
16
|
+
return require('./flat-configs/base.js');
|
|
17
|
+
},
|
|
18
|
+
get default() {
|
|
19
|
+
return require('./flat-configs/default.js');
|
|
20
|
+
},
|
|
21
|
+
get i18n() {
|
|
22
|
+
return require('./flat-configs/i18n.js');
|
|
23
|
+
},
|
|
24
|
+
get jest() {
|
|
25
|
+
return require('./flat-configs/jest.js');
|
|
26
|
+
},
|
|
27
|
+
get tailwind() {
|
|
28
|
+
return require('./flat-configs/tailwind.js');
|
|
29
|
+
},
|
|
30
|
+
get typescript() {
|
|
31
|
+
return require('./flat-configs/typescript.js');
|
|
32
|
+
},
|
|
33
|
+
get vue() {
|
|
34
|
+
return require('./flat-configs/vue.js');
|
|
35
|
+
},
|
|
45
36
|
},
|
|
46
37
|
};
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is GENERATED, please run `yarn update` after adding or renaming a rule
|
|
3
|
+
*/
|
|
4
|
+
const { name, version } = require('../package.json');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: { name, version },
|
|
8
|
+
rules: {
|
|
9
|
+
'no-global-event-off': require('./rules/no-global-event-off.js'),
|
|
10
|
+
'no-hardcoded-urls': require('./rules/no-hardcoded-urls.js'),
|
|
11
|
+
'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
|
|
12
|
+
'require-i18n-strings': require('./rules/require-i18n-strings.js'),
|
|
13
|
+
'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
|
|
14
|
+
'tailwind-no-interpolation': require('./rules/tailwind-no-interpolation.js'),
|
|
15
|
+
'tailwind-no-max-width-media-queries': require('./rules/tailwind-no-max-width-media-queries.js'),
|
|
16
|
+
'vtu-no-explicit-wrapper-destroy': require('./rules/vtu-no-explicit-wrapper-destroy.js'),
|
|
17
|
+
'vtu-no-wrapper-vm': require('./rules/vtu-no-wrapper-vm.js'),
|
|
18
|
+
'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
|
|
19
|
+
'vue-no-hardcoded-urls': require('./rules/vue-no-hardcoded-urls.js'),
|
|
20
|
+
'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
|
|
21
|
+
'vue-no-popover-target-dollar-el': require('./rules/vue-no-popover-target-dollar-el.js'),
|
|
22
|
+
'vue-no-undef-apollo-properties': require('./rules/vue-no-undef-apollo-properties.js'),
|
|
23
|
+
'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
|
|
24
|
+
'vue-require-i18n-attribute-strings': require('./rules/vue-require-i18n-attribute-strings.js'),
|
|
25
|
+
'vue-require-i18n-strings': require('./rules/vue-require-i18n-strings.js'),
|
|
26
|
+
'vue-require-required-key': require('./rules/vue-require-required-key.js'),
|
|
27
|
+
'vue-require-valid-i18n-helpers': require('./rules/vue-require-valid-i18n-helpers.js'),
|
|
28
|
+
'vue-slot-name-casing': require('./rules/vue-slot-name-casing.js'),
|
|
29
|
+
'vue-tailwind-no-interpolation': require('./rules/vue-tailwind-no-interpolation.js'),
|
|
30
|
+
'vue-tailwind-no-max-width-media-queries': require('./rules/vue-tailwind-no-max-width-media-queries.js'),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const prettierConfig = require('eslint-config-prettier');
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
eslint-config-prettier v10 also turns off `@stylistic/*` rules. This package
|
|
5
|
+
does not register that plugin, so those 180 entries are dropped. Consumers
|
|
6
|
+
using `@stylistic` should apply eslint-config-prettier themselves.
|
|
7
|
+
*/
|
|
8
|
+
module.exports = Object.fromEntries(
|
|
9
|
+
Object.entries(prettierConfig.rules).filter(([rule]) => !rule.startsWith('@stylistic/')),
|
|
10
|
+
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Requirements
|
|
3
3
|
// ------------------------------------------------------------------------------
|
|
4
4
|
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
-
const utils = require('eslint-plugin-vue/
|
|
5
|
+
const utils = require('eslint-plugin-vue/dist/utils').default;
|
|
6
6
|
|
|
7
7
|
// ------------------------------------------------------------------------------
|
|
8
8
|
// Rule Definition
|
|
@@ -30,7 +30,10 @@ module.exports = {
|
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
32
|
...utils.executeOnVue(context, (node, type) => {
|
|
33
|
-
if (
|
|
33
|
+
if (
|
|
34
|
+
type === 'definition' &&
|
|
35
|
+
utils.getVueComponentDefinitionType(context, node) === 'mixin'
|
|
36
|
+
) {
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -43,7 +46,7 @@ module.exports = {
|
|
|
43
46
|
hasRenderFn,
|
|
44
47
|
});
|
|
45
48
|
}),
|
|
46
|
-
'Program:exit'(
|
|
49
|
+
'Program:exit'() {
|
|
47
50
|
for (const { node, templateOption, hasRenderFn } of vues) {
|
|
48
51
|
if (templateOption) {
|
|
49
52
|
// Components can define a render function _and_ a template option,
|
|
@@ -92,7 +92,7 @@ module.exports = {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function stripVariables(value) {
|
|
95
|
-
return value ? value.replace(/(
|
|
95
|
+
return value ? value.replace(/(%{[\w-]+})/g, '') : '';
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function getCallExpressionString(node, callee) {
|
|
@@ -143,7 +143,7 @@ module.exports = {
|
|
|
143
143
|
|
|
144
144
|
function isMemberExpressionIdentifier(expression) {
|
|
145
145
|
if (expression.type !== 'MemberExpression') return false;
|
|
146
|
-
if (
|
|
146
|
+
if (expression.property.quasis) {
|
|
147
147
|
return /^\w+$/.test(getTemplateLiteralString(expression.property));
|
|
148
148
|
}
|
|
149
149
|
return /^\w+$/.test(expression.property.value);
|
|
@@ -164,14 +164,14 @@ module.exports = {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
function isObjectMethod(node) {
|
|
167
|
-
if (
|
|
167
|
+
if (node.callee.property) {
|
|
168
168
|
return objectMethods.has(node.callee.property.name);
|
|
169
169
|
}
|
|
170
170
|
return false;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function isHtmlCall(node) {
|
|
174
|
-
if (
|
|
174
|
+
if (node.callee.property) {
|
|
175
175
|
return htmlCalls.has(node.callee.property.name);
|
|
176
176
|
}
|
|
177
177
|
return htmlCalls.has(node.callee.name);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ------------------------------------------------------------------------------
|
|
2
2
|
// Requirements
|
|
3
3
|
// ------------------------------------------------------------------------------
|
|
4
|
-
const utils = require('eslint-plugin-vue/
|
|
4
|
+
const utils = require('eslint-plugin-vue/dist/utils').default;
|
|
5
5
|
const { pickBy, identity } = require('lodash');
|
|
6
6
|
const { DOCS_BASE_URL } = require('../constants');
|
|
7
7
|
const { closest, NODE_TYPES } = require('../utils/rule-utils');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Requirements
|
|
3
3
|
// ------------------------------------------------------------------------------
|
|
4
4
|
const { DOCS_BASE_URL } = require('../constants');
|
|
5
|
-
const utils = require('eslint-plugin-vue/
|
|
5
|
+
const utils = require('eslint-plugin-vue/dist/utils').default;
|
|
6
6
|
|
|
7
7
|
// ------------------------------------------------------------------------------
|
|
8
8
|
// Helpers
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { DOCS_BASE_URL } = require('../constants');
|
|
6
6
|
const { defineTemplateBodyVisitor } = require('../utils/index');
|
|
7
|
-
const utils = require('eslint-plugin-vue/
|
|
7
|
+
const utils = require('eslint-plugin-vue/dist/utils').default;
|
|
8
8
|
|
|
9
9
|
// ------------------------------------------------------------------------------
|
|
10
10
|
// Helpers
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// Requirements
|
|
5
5
|
// ------------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
const utils = require('eslint-plugin-vue/
|
|
8
|
-
const casing = require('eslint-plugin-vue/
|
|
7
|
+
const utils = require('eslint-plugin-vue/dist/utils').default;
|
|
8
|
+
const casing = require('eslint-plugin-vue/dist/utils/casing');
|
|
9
9
|
const { DOCS_BASE_URL } = require('../constants');
|
|
10
10
|
|
|
11
11
|
// ------------------------------------------------------------------------------
|
|
@@ -24,7 +24,6 @@ module.exports = {
|
|
|
24
24
|
},
|
|
25
25
|
|
|
26
26
|
create(context) {
|
|
27
|
-
const sourceCode = context.getSourceCode();
|
|
28
27
|
const caseType = 'kebab-case';
|
|
29
28
|
|
|
30
29
|
let hasInvalidEOF = false;
|
|
@@ -96,7 +96,7 @@ function isHardCodedUrl(context, value) {
|
|
|
96
96
|
|
|
97
97
|
// Check for path-like patterns:
|
|
98
98
|
// Starts with / followed by word characters
|
|
99
|
-
const urlPathPattern = /^\/(?![#?])[
|
|
99
|
+
const urlPathPattern = /^\/(?![#?])[/.a-zA-Z0-9_-]+/;
|
|
100
100
|
|
|
101
101
|
return urlPathPattern.test(value);
|
|
102
102
|
}
|
|
@@ -6,13 +6,11 @@ function isFileName(value) {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function isPath(value) {
|
|
9
|
-
return /^([\w-_]*|\.{1,2})[
|
|
9
|
+
return /^([\w-_]*|\.{1,2})[/~]+[\w-_/]*/.test(value);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function isUrl(value) {
|
|
13
|
-
return (
|
|
14
|
-
/[a-zA-Z0-9]*(:\/\/)+[a-zA-Z0-9\.]*/.test(value) || /(http){1}[s:\/a-zA-Z0-9.]*/.test(value)
|
|
15
|
-
);
|
|
13
|
+
return /[a-zA-Z0-9]*(:\/\/)+[a-zA-Z0-9.]*/.test(value) || /(http){1}[s:/a-zA-Z0-9.]*/.test(value);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
function isMailto(str) {
|
|
@@ -39,11 +37,11 @@ function isListOfCssProperties(value) {
|
|
|
39
37
|
|
|
40
38
|
function isCssSelector(value) {
|
|
41
39
|
return (
|
|
42
|
-
/^,?\s?[
|
|
43
|
-
/^[a-zA-Z]+[a-zA-Z-]*[
|
|
40
|
+
/^,?\s?[.#*@]\w+.*/.test(value) ||
|
|
41
|
+
/^[a-zA-Z]+[a-zA-Z-]*[.#*]+[a-zA-Z]+[a-zA-Z-]*/.test(value) ||
|
|
44
42
|
/[a-zA-Z][a-zA-Z-]+[a-zA-Z]\.\w/.test(value) ||
|
|
45
43
|
/([a-zA-Z]+[a-zA-Z-]*|\*)\[.*\]/.test(value) ||
|
|
46
|
-
/^[a-zA-Z]+[a-zA-Z-]*[
|
|
44
|
+
/^[a-zA-Z]+[a-zA-Z-]*[#*]$/.test(value) ||
|
|
47
45
|
/^(?:[0-9]+[a-z]|[a-z]+[0-9])[a-z0-9]*\s[a-z0-9][a-z0-9]+-[a-z0-9-]*$/.test(value) ||
|
|
48
46
|
isListOfCssProperties(value) ||
|
|
49
47
|
isListOfHtmlElements(value)
|
|
@@ -104,7 +102,7 @@ function containsAssignment(value) {
|
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
function isSpecial(value) {
|
|
107
|
-
return /^[
|
|
105
|
+
return /^[:%_&#?[$].*[^.]/.test(value) || /.*[*+]+$/.test(value);
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
function isVariableName(value) {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
const vuePlugin = require('eslint-plugin-vue');
|
|
2
|
+
const vueParser = require('vue-eslint-parser');
|
|
3
|
+
const globals = require('globals');
|
|
4
|
+
const unicornPlugin = require('eslint-plugin-unicorn');
|
|
5
|
+
const promisePlugin = require('eslint-plugin-promise');
|
|
6
|
+
const importPlugin = require('eslint-plugin-import-x');
|
|
7
|
+
|
|
8
|
+
const gitlabPlugin = require('./plugin.js');
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
The Vue config in two halves, so lib/flat-configs/vue.js and
|
|
12
|
+
lib/flat-configs/default.js can each place eslint-config-prettier where it
|
|
13
|
+
belongs for them: after the preset in the former, before it in the latter.
|
|
14
|
+
|
|
15
|
+
vue2-recommended, not the identically-named 'flat/recommended', which targets
|
|
16
|
+
Vue 3. The two names have never pointed at the same preset, and retargeting
|
|
17
|
+
would move 33 rules onto consumers -- a product decision, not a config-format
|
|
18
|
+
one.
|
|
19
|
+
*/
|
|
20
|
+
const presetAndSetup = [
|
|
21
|
+
...vuePlugin.configs['flat/vue2-recommended'],
|
|
22
|
+
{
|
|
23
|
+
name: '@gitlab/vue/setup',
|
|
24
|
+
languageOptions: {
|
|
25
|
+
parser: vueParser,
|
|
26
|
+
parserOptions: {
|
|
27
|
+
parser: require.resolve('espree'),
|
|
28
|
+
},
|
|
29
|
+
globals: {
|
|
30
|
+
...globals.browser,
|
|
31
|
+
...globals.es2020,
|
|
32
|
+
},
|
|
33
|
+
ecmaVersion: 'latest',
|
|
34
|
+
sourceType: 'module',
|
|
35
|
+
},
|
|
36
|
+
plugins: {
|
|
37
|
+
unicorn: unicornPlugin,
|
|
38
|
+
promise: promisePlugin,
|
|
39
|
+
'import-x': importPlugin,
|
|
40
|
+
'@gitlab': gitlabPlugin,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const gitlabRules = [
|
|
46
|
+
{
|
|
47
|
+
name: '@gitlab/vue/rules',
|
|
48
|
+
rules: {
|
|
49
|
+
'vue/html-self-closing': [
|
|
50
|
+
'error',
|
|
51
|
+
{
|
|
52
|
+
html: { void: 'any', normal: 'never', component: 'always' },
|
|
53
|
+
svg: 'always',
|
|
54
|
+
math: 'always',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
'vue/block-order': ['error', { order: ['script', 'template', 'style'] }],
|
|
58
|
+
// Turning this off for now as we violate this rule in many places.
|
|
59
|
+
'vue/max-attributes-per-line': 'off',
|
|
60
|
+
'vue/component-options-name-casing': ['error', 'PascalCase'],
|
|
61
|
+
'vue/component-name-in-template-casing': ['error', 'kebab-case'],
|
|
62
|
+
'@gitlab/vue-require-required-key': 'error',
|
|
63
|
+
'vue/v-slot-style': ['error', { atComponent: 'shorthand' }],
|
|
64
|
+
'@gitlab/vue-no-data-toggle': 'error',
|
|
65
|
+
'@gitlab/vue-no-popover-target-dollar-el': 'warn',
|
|
66
|
+
'@gitlab/vue-prefer-dollar-scopedslots': 'error',
|
|
67
|
+
'@gitlab/vue-slot-name-casing': 'error',
|
|
68
|
+
'@gitlab/no-runtime-template-compiler': 'error',
|
|
69
|
+
'import-x/order': [
|
|
70
|
+
'error',
|
|
71
|
+
{
|
|
72
|
+
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
// See https://gitlab.com/gitlab-org/frontend/eslint-plugin/-/issues/52.
|
|
76
|
+
'vue/component-api-style': ['error', ['options']],
|
|
77
|
+
// We might not want to enable vue/singleline-html-element-content-newline and
|
|
78
|
+
// vue/multiline-html-element-content-newline just yet as they might cause
|
|
79
|
+
// regression depending on how the consumers compile Vue templates.
|
|
80
|
+
'vue/singleline-html-element-content-newline': 'off',
|
|
81
|
+
'vue/multiline-html-element-content-newline': 'off',
|
|
82
|
+
// Disabling these as they might conflict with prettier.
|
|
83
|
+
'vue/html-indent': 'off',
|
|
84
|
+
'vue/html-closing-bracket-newline': 'off',
|
|
85
|
+
// BEGIN rules to aid migration from Vue 2.x to 3.x.
|
|
86
|
+
// See https://gitlab.com/groups/gitlab-org/-/epics/3174 for more details.
|
|
87
|
+
'vue/no-deprecated-data-object-declaration': 'error',
|
|
88
|
+
'vue/no-deprecated-events-api': 'error',
|
|
89
|
+
'vue/no-deprecated-filter': 'error',
|
|
90
|
+
'vue/no-deprecated-functional-template': 'error',
|
|
91
|
+
'vue/no-deprecated-html-element-is': 'error',
|
|
92
|
+
'vue/no-deprecated-inline-template': 'error',
|
|
93
|
+
'vue/no-deprecated-props-default-this': 'error',
|
|
94
|
+
'vue/no-deprecated-scope-attribute': 'error',
|
|
95
|
+
'vue/no-deprecated-slot-attribute': 'error',
|
|
96
|
+
'vue/no-deprecated-slot-scope-attribute': 'error',
|
|
97
|
+
'vue/no-deprecated-v-on-number-modifiers': 'error',
|
|
98
|
+
'vue/no-deprecated-vue-config-keycodes': 'error',
|
|
99
|
+
// END rules to aid migration from Vue 2.x to 3.x.
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: '@gitlab/vue/sfc-overrides',
|
|
104
|
+
files: ['**/*.vue'],
|
|
105
|
+
rules: {
|
|
106
|
+
'import-x/no-default-export': 'off',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
module.exports = { presetAndSetup, gitlabRules };
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitlab/eslint-plugin",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "23.0.0",
|
|
4
4
|
"description": "GitLab package for our custom eslint rules",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"createRule": "./scripts/createRule.js && yarn update",
|
|
8
|
-
"update": "./scripts/updateFiles.js && prettier --write lib/index.js",
|
|
8
|
+
"update": "./scripts/updateFiles.js && prettier --write lib/plugin.js lib/index.js",
|
|
9
9
|
"commit": "npx git-cz",
|
|
10
|
+
"lint": "eslint .",
|
|
10
11
|
"test": "mocha"
|
|
11
12
|
},
|
|
12
13
|
"repository": "https://gitlab.com/gitlab-org/frontend/eslint-plugin.git",
|
|
@@ -27,30 +28,48 @@
|
|
|
27
28
|
},
|
|
28
29
|
"homepage": "https://gitlab.com/gitlab-org/frontend/eslint-plugin#readme",
|
|
29
30
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
31
|
+
"node": "^20.12.0 || ^22.0.0 || >=24.0.0"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"eslint-
|
|
36
|
-
"eslint-plugin-
|
|
37
|
-
"eslint-plugin-jest": "^28.6.0",
|
|
34
|
+
"@eslint/js": "^9.0.0",
|
|
35
|
+
"eslint-config-prettier": "^10.0.0",
|
|
36
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
37
|
+
"eslint-plugin-jest": "^29.0.0",
|
|
38
38
|
"eslint-plugin-promise": "^7.0.0",
|
|
39
39
|
"eslint-plugin-tailwindcss": "^3.18.3",
|
|
40
40
|
"eslint-plugin-unicorn": "^55.0.0",
|
|
41
|
-
"eslint-plugin-vue": "^
|
|
41
|
+
"eslint-plugin-vue": "^10.0.0",
|
|
42
|
+
"globals": "^17.0.0",
|
|
42
43
|
"lodash": "^4.18.1",
|
|
43
|
-
"
|
|
44
|
+
"semver": "^7.0.0",
|
|
45
|
+
"vue-eslint-parser": "^10.3.0"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
|
-
"eslint": "^8.
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
50
|
+
"eslint": "^9.0.0",
|
|
51
|
+
"typescript": ">=4.8.4"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@typescript-eslint/eslint-plugin": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@typescript-eslint/parser": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"typescript": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
47
63
|
},
|
|
48
64
|
"devDependencies": {
|
|
49
65
|
"@changesets/cli": "^2.29.8",
|
|
50
|
-
"eslint": "^8.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
67
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
68
|
+
"eslint": "^9.0.0",
|
|
51
69
|
"glob": "^7.2.0",
|
|
52
70
|
"mocha": "^11.7.5",
|
|
53
71
|
"prettier": "^2.6.1",
|
|
72
|
+
"typescript": "^5.0.0",
|
|
54
73
|
"yarn-deduplicate": "^6.0.2"
|
|
55
74
|
}
|
|
56
75
|
}
|