@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +299 -0
  2. package/README.md +16 -11
  3. package/docs/development.md +2 -1
  4. package/docs/usage.md +158 -47
  5. package/eslint9/index.js +6 -7
  6. package/lib/configs/base/best-practices.js +0 -4
  7. package/lib/configs/base/es6.js +1 -13
  8. package/lib/configs/base/imports.js +86 -106
  9. package/lib/configs/base/node.js +0 -4
  10. package/lib/confusing-browser-globals.js +68 -0
  11. package/lib/flat-configs/base.js +122 -0
  12. package/lib/flat-configs/default.js +13 -0
  13. package/lib/flat-configs/i18n.js +14 -0
  14. package/lib/flat-configs/jest.js +20 -0
  15. package/lib/flat-configs/tailwind.js +19 -0
  16. package/lib/flat-configs/typescript.js +136 -0
  17. package/lib/flat-configs/vue.js +16 -0
  18. package/lib/index.js +30 -39
  19. package/lib/plugin.js +32 -0
  20. package/lib/prettier-rules.js +10 -0
  21. package/lib/rules/no-runtime-template-compiler.js +6 -3
  22. package/lib/rules/require-i18n-strings.js +4 -4
  23. package/lib/rules/vtu-no-explicit-wrapper-destroy.js +1 -1
  24. package/lib/rules/vue-no-new-non-primitive-in-template.js +1 -1
  25. package/lib/rules/vue-no-undef-apollo-properties.js +1 -1
  26. package/lib/rules/vue-prefer-dollar-scopedslots.js +1 -1
  27. package/lib/rules/vue-require-required-key.js +1 -1
  28. package/lib/rules/vue-slot-name-casing.js +2 -3
  29. package/lib/utils/hardcoded-urls.js +1 -1
  30. package/lib/utils/string-utils.js +6 -8
  31. package/lib/vue-fragments.js +111 -0
  32. package/package.json +31 -12
  33. package/scripts/benchmarking/checkAllStrings.js +1 -1
  34. package/scripts/helpers/getConfigs.js +15 -6
  35. package/scripts/publish_npm_package.mjs +0 -2
  36. package/scripts/updateFiles.js +30 -6
  37. package/eslint9/configs/base.js +0 -140
  38. package/eslint9/configs/typescript.js +0 -134
  39. package/lib/configs/base.js +0 -115
  40. package/lib/configs/default.js +0 -17
  41. package/lib/configs/i18n.js +0 -10
  42. package/lib/configs/jest.js +0 -21
  43. package/lib/configs/tailwind.js +0 -10
  44. package/lib/configs/typescript.js +0 -95
  45. 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
- rules: {
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: require('./configs/base.js'),
31
- 'best-practices': require('./configs/base/best-practices.js'),
32
- errors: require('./configs/base/errors.js'),
33
- es6: require('./configs/base/es6.js'),
34
- imports: require('./configs/base/imports.js'),
35
- node: require('./configs/base/node.js'),
36
- strict: require('./configs/base/strict.js'),
37
- style: require('./configs/base/style.js'),
38
- variables: require('./configs/base/variables.js'),
39
- default: require('./configs/default.js'),
40
- i18n: require('./configs/i18n.js'),
41
- jest: require('./configs/jest.js'),
42
- tailwind: require('./configs/tailwind.js'),
43
- typescript: require('./configs/typescript.js'),
44
- vue: require('./configs/vue.js'),
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/lib/utils');
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 (type === 'definition' && utils.getVueComponentDefinitionType(node) === 'mixin') {
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'(node) {
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(/(\%{[\w-]+})/g, '') : '';
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 (!!expression.property.quasis) {
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 (!!node.callee.property) {
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 (!!node.callee.property) {
174
+ if (node.callee.property) {
175
175
  return htmlCalls.has(node.callee.property.name);
176
176
  }
177
177
  return htmlCalls.has(node.callee.name);
@@ -13,7 +13,7 @@ class Rule {
13
13
  #context;
14
14
 
15
15
  constructor(context, options) {
16
- this.#vmWrapperNameParts = options.wrapperNames || commonVmWrapperNameParts;
16
+ this.#vmWrapperNameParts = options.wrapperNames;
17
17
  this.#context = context;
18
18
  }
19
19
 
@@ -1,7 +1,7 @@
1
1
  // ------------------------------------------------------------------------------
2
2
  // Requirements
3
3
  // ------------------------------------------------------------------------------
4
- const utils = require('eslint-plugin-vue/lib/utils');
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/lib/utils');
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/lib/utils');
7
+ const utils = require('eslint-plugin-vue/dist/utils').default;
8
8
 
9
9
  // ------------------------------------------------------------------------------
10
10
  // Helpers
@@ -13,7 +13,7 @@ const NATIVE_TYPES = new Set([
13
13
  'Symbol',
14
14
  ]);
15
15
 
16
- const utils = require('eslint-plugin-vue/lib/utils');
16
+ const utils = require('eslint-plugin-vue/dist/utils').default;
17
17
 
18
18
  module.exports = {
19
19
  meta: {
@@ -4,8 +4,8 @@
4
4
  // Requirements
5
5
  // ------------------------------------------------------------------------------
6
6
 
7
- const utils = require('eslint-plugin-vue/lib/utils');
8
- const casing = require('eslint-plugin-vue/lib/utils/casing');
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 = /^\/(?![#?])[\/\.a-zA-Z0-9_-]+/;
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})[\/\~]+[\w-_\/]*/.test(value);
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?[\.\#\*\@]\w+.*/.test(value) ||
43
- /^[a-zA-Z]+[a-zA-Z-]*[\.\#\*]+[a-zA-Z]+[a-zA-Z-]*/.test(value) ||
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-]*[\#\*]$/.test(value) ||
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 /^[\:\%\_\&\#\?\[\$].*[^.]/.test(value) || /.*[\*\+]+$/.test(value);
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": "21.5.0",
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": ">=14"
31
+ "node": "^20.12.0 || ^22.0.0 || >=24.0.0"
31
32
  },
32
33
  "dependencies": {
33
- "@typescript-eslint/eslint-plugin": "^7.14.1",
34
- "confusing-browser-globals": "^1.0.11",
35
- "eslint-config-prettier": "^9.1.0",
36
- "eslint-plugin-import": "^2.29.1",
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": "^9.33.0",
41
+ "eslint-plugin-vue": "^10.0.0",
42
+ "globals": "^17.0.0",
42
43
  "lodash": "^4.18.1",
43
- "vue-eslint-parser": "^9.4.3"
44
+ "semver": "^7.0.0",
45
+ "vue-eslint-parser": "^10.3.0"
44
46
  },
45
47
  "peerDependencies": {
46
- "eslint": "^8.57.0 || ^9.0.0"
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.57.0",
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
  }
@@ -37,7 +37,7 @@ for (let i = 0, len = allStrings.length; i < len; i++) {
37
37
 
38
38
  try {
39
39
  result = testFn(allStrings[i]) ? 'TRUE' : 'FALSE';
40
- } catch (e) {
40
+ } catch {
41
41
  result = 'TIMEOUT';
42
42
  }
43
43