@gitlab/eslint-plugin 21.2.0 → 21.3.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/README.md CHANGED
@@ -25,6 +25,19 @@ upstream later.
25
25
 
26
26
  The TS portion of this plugin has been migrated to eslint v9 and can be used as `const gitlabPlugin = require('@gitlab/eslint-plugin/eslint9');`. You still add the `@gitlab/eslint-plugin` as a dependency.
27
27
 
28
+ Add these overrides to your project's package.json:
29
+
30
+ ```json
31
+ "overrides": {
32
+ "@gitlab/eslint-plugin": {
33
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
34
+ "@typescript-eslint/parser": "^8.38.0"
35
+ }
36
+ }
37
+ ```
38
+
39
+ (replace the versions with what your main project uses)
40
+
28
41
  ## Contribution guidelines
29
42
 
30
43
  Please refer to
@@ -0,0 +1,27 @@
1
+ # @gitlab/no-hardcoded-urls
2
+
3
+ We should not hardcode or construct URLs on the frontend. See https://docs.gitlab.com/development/urls_in_gitlab/ for more information.
4
+
5
+ ## Options
6
+
7
+ ```json
8
+ {
9
+ "@gitlab/vue-no-hardcoded-urls": [
10
+ "error",
11
+ {
12
+ "allowedKeys": ["path", "redirect"],
13
+ "allowedFunctions": ["helpPagePath"],
14
+ "allowedInterpolationVariables": [
15
+ "FORUM_URL",
16
+ "DOCS_URL",
17
+ "PROMO_URL",
18
+ "CONTRIBUTE_URL",
19
+ "DOCS_URL_IN_EE_DIR",
20
+ "CUSTOMERS_PORTAL_URL"
21
+ ],
22
+ "allowedPatterns": ["\\/api\\/:version"],
23
+ "disallowedObjectProperties": ["relative_url_root"]
24
+ }
25
+ ]
26
+ }
27
+ ```
@@ -0,0 +1,30 @@
1
+ # @gitlab/vue-no-hardcoded-urls
2
+
3
+ We should not hardcode or construct URLs on the frontend. See https://docs.gitlab.com/development/urls_in_gitlab/ for more information.
4
+
5
+ ## Options
6
+
7
+ Nothing
8
+
9
+ ```json
10
+ {
11
+ "@gitlab/vue-no-hardcoded-urls": [
12
+ "error",
13
+ {
14
+ "allowedVueComponents": ["help-page-link"],
15
+ "allowedKeys": ["path", "redirect"],
16
+ "allowedFunctions": ["helpPagePath"],
17
+ "allowedInterpolationVariables": [
18
+ "FORUM_URL",
19
+ "DOCS_URL",
20
+ "PROMO_URL",
21
+ "CONTRIBUTE_URL",
22
+ "DOCS_URL_IN_EE_DIR",
23
+ "CUSTOMERS_PORTAL_URL"
24
+ ],
25
+ "allowedPatterns": ["\\/api\\/:version"],
26
+ "disallowedObjectProperties": ["relative_url_root"]
27
+ }
28
+ ]
29
+ }
30
+ ```
package/docs/rules.md CHANGED
@@ -5,6 +5,7 @@
5
5
  Available rules:
6
6
 
7
7
  - [no-global-event-off](./rules/no-global-event-off.md): Throw an error when globally deregistering all event listeners.
8
+ - [no-hardcoded-urls](./rules/no-hardcoded-urls.md): Disallow hardcoded URLs
8
9
  - [no-runtime-template-compiler](./rules/no-runtime-template-compiler.md): Disallow components which rely on a runtime template compiler.
9
10
  - [require-i18n-strings](./rules/require-i18n-strings.md): Detect a string which has been hard coded and requires externalization.
10
11
  - [require-valid-i18n-helpers](./rules/require-valid-i18n-helpers.md): Enforces valid usage of translation helpers in JavaScript.
@@ -13,6 +14,7 @@ Available rules:
13
14
  - [vtu-no-explicit-wrapper-destroy](./rules/vtu-no-explicit-wrapper-destroy.md): Prevents redundant destroy calls and null asignment
14
15
  - [vtu-no-wrapper-vm](./rules/vtu-no-wrapper-vm.md): Prevent direct access to `vm` internals for `@vue/test-utils` wrappers.
15
16
  - [vue-no-data-toggle](./rules/vue-no-data-toggle.md): Restrict the use of `data-toggle` bootstrap behaviors within Vue templates
17
+ - [vue-no-hardcoded-urls](./rules/vue-no-hardcoded-urls.md): Disallow hardcoded URLs in Vue templates
16
18
  - [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
17
19
  - [vue-no-undef-apollo-properties](./rules/vue-no-undef-apollo-properties.md): Require Apollo query properties to be initialized in component data.
18
20
  - [vue-prefer-dollar-scopedslots](./rules/vue-prefer-dollar-scopedslots.md): Prefer $scopedSlots over $slots for Vue 2.x.
@@ -5,14 +5,14 @@ const promisePlugin = require('eslint-plugin-promise');
5
5
  const importPlugin = require('eslint-plugin-import');
6
6
 
7
7
  // Import all the base configurations
8
- const bestPracticesConfig = require('./base/best-practices');
9
- const errorsConfig = require('./base/errors');
10
- const nodeConfig = require('./base/node');
11
- const styleConfig = require('./base/style');
12
- const variablesConfig = require('./base/variables');
13
- const es6Config = require('./base/es6');
14
- const importsConfig = require('./base/imports');
15
- const strictConfig = require('./base/strict');
8
+ const bestPracticesConfig = require('../../lib/configs/base/best-practices');
9
+ const errorsConfig = require('../../lib/configs/base/errors');
10
+ const nodeConfig = require('../../lib/configs/base/node');
11
+ const styleConfig = require('../../lib/configs/base/style');
12
+ const variablesConfig = require('../../lib/configs/base/variables');
13
+ const es6Config = require('../../lib/configs/base/es6');
14
+ const importsConfig = require('../../lib/configs/base/imports');
15
+ const strictConfig = require('../../lib/configs/base/strict');
16
16
 
17
17
  /*
18
18
  This plugin contains rules related to GitLab JavaScript style guide
package/lefthook.yml CHANGED
@@ -1,7 +1,9 @@
1
1
  pre-commit:
2
2
  piped: true
3
+ fail_on_changes: always
3
4
  commands:
4
5
  update:
5
6
  run: yarn run update
6
7
  prettier:
7
- run: yarn run pretty-quick --staged
8
+ glob: '*.{js,mjs,json,yml,md}'
9
+ run: yarn prettier --write {staged_files}
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  module.exports = {
5
5
  rules: {
6
6
  'no-global-event-off': require('./rules/no-global-event-off.js'),
7
+ 'no-hardcoded-urls': require('./rules/no-hardcoded-urls.js'),
7
8
  'no-runtime-template-compiler': require('./rules/no-runtime-template-compiler.js'),
8
9
  'require-i18n-strings': require('./rules/require-i18n-strings.js'),
9
10
  'require-valid-i18n-helpers': require('./rules/require-valid-i18n-helpers.js'),
@@ -12,6 +13,7 @@ module.exports = {
12
13
  'vtu-no-explicit-wrapper-destroy': require('./rules/vtu-no-explicit-wrapper-destroy.js'),
13
14
  'vtu-no-wrapper-vm': require('./rules/vtu-no-wrapper-vm.js'),
14
15
  'vue-no-data-toggle': require('./rules/vue-no-data-toggle.js'),
16
+ 'vue-no-hardcoded-urls': require('./rules/vue-no-hardcoded-urls.js'),
15
17
  'vue-no-new-non-primitive-in-template': require('./rules/vue-no-new-non-primitive-in-template.js'),
16
18
  'vue-no-undef-apollo-properties': require('./rules/vue-no-undef-apollo-properties.js'),
17
19
  'vue-prefer-dollar-scopedslots': require('./rules/vue-prefer-dollar-scopedslots.js'),
@@ -25,6 +27,14 @@ module.exports = {
25
27
  },
26
28
  configs: {
27
29
  base: require('./configs/base.js'),
30
+ 'best-practices': require('./configs/base/best-practices.js'),
31
+ errors: require('./configs/base/errors.js'),
32
+ es6: require('./configs/base/es6.js'),
33
+ imports: require('./configs/base/imports.js'),
34
+ node: require('./configs/base/node.js'),
35
+ strict: require('./configs/base/strict.js'),
36
+ style: require('./configs/base/style.js'),
37
+ variables: require('./configs/base/variables.js'),
28
38
  default: require('./configs/default.js'),
29
39
  i18n: require('./configs/i18n.js'),
30
40
  jest: require('./configs/jest.js'),
@@ -0,0 +1,71 @@
1
+ // ------------------------------------------------------------------------------
2
+ // Requirements
3
+ // ------------------------------------------------------------------------------
4
+ const { DOCS_BASE_URL } = require('../constants');
5
+ const {
6
+ literalValidator,
7
+ templateLiteralValidator,
8
+ memberExpressionValidator,
9
+ } = require('../utils/hardcoded-urls');
10
+
11
+ // ------------------------------------------------------------------------------
12
+ // Rule Definition
13
+ // ------------------------------------------------------------------------------
14
+
15
+ module.exports = {
16
+ meta: {
17
+ type: 'error',
18
+ docs: {
19
+ description: 'Disallow hardcoded URLs',
20
+ category: 'maintainability',
21
+ url: DOCS_BASE_URL + '/no-hardcoded-urls.md',
22
+ },
23
+ schema: [
24
+ {
25
+ type: 'object',
26
+ properties: {
27
+ allowedKeys: {
28
+ type: 'array',
29
+ items: { type: 'string' },
30
+ default: [],
31
+ },
32
+ allowedFunctions: {
33
+ type: 'array',
34
+ items: { type: 'string' },
35
+ default: [],
36
+ },
37
+ allowedInterpolationVariables: {
38
+ type: 'array',
39
+ items: { type: 'string' },
40
+ default: [],
41
+ },
42
+ allowedPatterns: {
43
+ type: 'array',
44
+ items: { type: 'string' },
45
+ default: [],
46
+ },
47
+ disallowedObjectProperties: {
48
+ type: 'array',
49
+ items: { type: 'string' },
50
+ default: [],
51
+ },
52
+ },
53
+ additionalProperties: false,
54
+ },
55
+ ],
56
+ },
57
+
58
+ create(context) {
59
+ return {
60
+ Literal(node) {
61
+ literalValidator(context, node);
62
+ },
63
+ TemplateLiteral(node) {
64
+ templateLiteralValidator(context, node);
65
+ },
66
+ MemberExpression(node) {
67
+ memberExpressionValidator(context, node);
68
+ },
69
+ };
70
+ },
71
+ };
@@ -0,0 +1,77 @@
1
+ // ------------------------------------------------------------------------------
2
+ // Requirements
3
+ // ------------------------------------------------------------------------------
4
+ const { DOCS_BASE_URL } = require('../constants');
5
+ const { defineTemplateBodyVisitor } = require('../utils/index');
6
+ const {
7
+ literalValidator,
8
+ templateLiteralValidator,
9
+ memberExpressionValidator,
10
+ } = require('../utils/hardcoded-urls');
11
+
12
+ // ------------------------------------------------------------------------------
13
+ // Rule Definition
14
+ // ------------------------------------------------------------------------------
15
+
16
+ module.exports = {
17
+ meta: {
18
+ type: 'error',
19
+ docs: {
20
+ description: 'Disallow hardcoded URLs in Vue templates',
21
+ category: 'maintainability',
22
+ url: DOCS_BASE_URL + '/vue-no-hardcoded-urls.md',
23
+ },
24
+ schema: [
25
+ {
26
+ type: 'object',
27
+ properties: {
28
+ allowedVueComponents: {
29
+ type: 'array',
30
+ items: { type: 'string' },
31
+ default: [],
32
+ },
33
+ allowedKeys: {
34
+ type: 'array',
35
+ items: { type: 'string' },
36
+ default: [],
37
+ },
38
+ allowedFunctions: {
39
+ type: 'array',
40
+ items: { type: 'string' },
41
+ default: [],
42
+ },
43
+ allowedInterpolationVariables: {
44
+ type: 'array',
45
+ items: { type: 'string' },
46
+ default: [],
47
+ },
48
+ allowedPatterns: {
49
+ type: 'array',
50
+ items: { type: 'string' },
51
+ default: [],
52
+ },
53
+ disallowedObjectProperties: {
54
+ type: 'array',
55
+ items: { type: 'string' },
56
+ default: [],
57
+ },
58
+ },
59
+ additionalProperties: false,
60
+ },
61
+ ],
62
+ },
63
+
64
+ create(context) {
65
+ return defineTemplateBodyVisitor(context, {
66
+ VLiteral(node) {
67
+ literalValidator(context, node);
68
+ },
69
+ TemplateLiteral(node) {
70
+ templateLiteralValidator(context, node);
71
+ },
72
+ MemberExpression(node) {
73
+ memberExpressionValidator(context, node);
74
+ },
75
+ });
76
+ },
77
+ };
@@ -0,0 +1,127 @@
1
+ const ERROR_MESSAGE =
2
+ 'Avoid hardcoded URLs. See https://docs.gitlab.com/development/urls_in_gitlab/.';
3
+
4
+ function traverse(node, type) {
5
+ if (node.type === type) {
6
+ return node;
7
+ }
8
+
9
+ if (!node.parent) {
10
+ return node;
11
+ }
12
+
13
+ return traverse(node.parent, type);
14
+ }
15
+
16
+ function literalValidator(context, node) {
17
+ if (isAllowedNode(context, node)) {
18
+ return;
19
+ }
20
+
21
+ if (isHardCodedUrl(context, node.value)) {
22
+ context.report({
23
+ node,
24
+ message: ERROR_MESSAGE,
25
+ });
26
+ }
27
+ }
28
+
29
+ function templateLiteralValidator(context, node) {
30
+ if (isAllowedNode(context, node)) {
31
+ return;
32
+ }
33
+
34
+ if (isConstructedUrl(context, node)) {
35
+ context.report({
36
+ node,
37
+ message: ERROR_MESSAGE,
38
+ });
39
+ }
40
+ }
41
+
42
+ function memberExpressionValidator(context, node) {
43
+ const { disallowedObjectProperties = [] } = context.options[0] || {};
44
+
45
+ if (disallowedObjectProperties.includes(node.property?.name)) {
46
+ context.report({
47
+ node,
48
+ message: ERROR_MESSAGE,
49
+ });
50
+ }
51
+ }
52
+
53
+ function isAllowedNode(context, node) {
54
+ const { parent } = node;
55
+ const {
56
+ allowedKeys = [],
57
+ allowedFunctions = [],
58
+ allowedVueComponents = [],
59
+ } = context.options[0] || {};
60
+
61
+ if (
62
+ parent?.type === 'Property' &&
63
+ parent?.key?.type === 'Identifier' &&
64
+ allowedKeys.includes(parent?.key?.name)
65
+ ) {
66
+ return true;
67
+ }
68
+
69
+ if (
70
+ parent?.type === 'CallExpression' &&
71
+ parent?.callee?.type === 'Identifier' &&
72
+ allowedFunctions.includes(parent?.callee?.name)
73
+ ) {
74
+ return true;
75
+ }
76
+
77
+ const vElement = traverse(node, 'VElement');
78
+ if (vElement && allowedVueComponents.includes(vElement.name)) {
79
+ return true;
80
+ }
81
+
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Check if a string looks like a hardcoded URL path
87
+ */
88
+ function isHardCodedUrl(context, value) {
89
+ if (typeof value !== 'string') return false;
90
+
91
+ const { allowedPatterns = [] } = context.options[0] || {};
92
+
93
+ if (allowedPatterns.some((pattern) => new RegExp(pattern).test(value))) {
94
+ return false;
95
+ }
96
+
97
+ // Check for path-like patterns:
98
+ // Starts with / followed by word characters
99
+ const urlPathPattern = /^\/(?![#?])[\/\.a-zA-Z0-9_-]+/;
100
+
101
+ return urlPathPattern.test(value);
102
+ }
103
+
104
+ /**
105
+ * Check if template literal looks like URL construction
106
+ */
107
+ function isConstructedUrl(context, node) {
108
+ const { allowedInterpolationVariables = [] } = context.options[0] || {};
109
+
110
+ if (
111
+ node.expressions.some((expression) => allowedInterpolationVariables.includes(expression.name))
112
+ ) {
113
+ return false;
114
+ }
115
+
116
+ // Check if any quasi (string part) looks like a hard coded URL
117
+ const hasHardCodedParts = node.quasis.some((quasi) => isHardCodedUrl(context, quasi.value.raw));
118
+
119
+ return hasHardCodedParts;
120
+ }
121
+
122
+ module.exports = {
123
+ literalValidator,
124
+ templateLiteralValidator,
125
+ memberExpressionValidator,
126
+ ERROR_MESSAGE,
127
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/eslint-plugin",
3
- "version": "21.2.0",
3
+ "version": "21.3.0",
4
4
  "description": "GitLab package for our custom eslint rules",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -38,40 +38,18 @@
38
38
  "eslint-plugin-promise": "^7.0.0",
39
39
  "eslint-plugin-unicorn": "^55.0.0",
40
40
  "eslint-plugin-vue": "^9.27.0",
41
- "vue-eslint-parser": "^9.4.3",
42
- "lodash": "^4.17.21"
41
+ "lodash": "^4.17.21",
42
+ "vue-eslint-parser": "^9.4.3"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "eslint": "^8.57.0 || ^9.0.0"
46
46
  },
47
47
  "devDependencies": {
48
+ "@changesets/cli": "^2.29.8",
48
49
  "eslint": "^8.57.0",
49
50
  "glob": "^7.2.0",
50
51
  "jest": "^27.5.1",
51
52
  "prettier": "^2.6.1",
52
- "pretty-quick": "^3.1.3",
53
53
  "yarn-deduplicate": "^6.0.2"
54
- },
55
- "release": {
56
- "branches": [
57
- "main"
58
- ],
59
- "verifyConditions": [
60
- "@semantic-release/changelog",
61
- "@semantic-release/npm",
62
- "@semantic-release/git",
63
- "@semantic-release/gitlab"
64
- ],
65
- "prepare": [
66
- "@semantic-release/changelog",
67
- "@semantic-release/npm",
68
- "@semantic-release/git"
69
- ],
70
- "publish": [
71
- "@semantic-release/npm",
72
- "@semantic-release/gitlab"
73
- ],
74
- "success": false,
75
- "fail": false
76
54
  }
77
55
  }
@@ -0,0 +1,19 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ check_yarn_install_is_idempotent() {
5
+ yarn install
6
+ if ! git diff --exit-code yarn.lock; then
7
+ echo "Lock file is not stable. See above for diff." >&2
8
+ echo "To fix this, run the following and commit the result:" >&2
9
+ echo "yarn && yarn yarn-deduplicate --strategy fewer && yarn" >&2
10
+ exit 1
11
+ fi
12
+ }
13
+
14
+ # We need to sandwich yarn-deduplicate with yarn install calls.
15
+ # This is how Renovate does it:
16
+ # https://gitlab.com/gitlab-org/frontend/renovate-fork/-/blob/f7745bc34724f01e2d953cfb047c706d0e3f7fc4/lib/modules/manager/npm/post-update/yarn.ts#L251-254
17
+ check_yarn_install_is_idempotent
18
+ yarn run yarn-deduplicate --strategy fewer --list --fail
19
+ check_yarn_install_is_idempotent
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ yarn install --frozen-lockfile
5
+ yarn run update
6
+ if ! git diff --exit-code; then
7
+ echo "Files not up to date after editing rules."
8
+ echo "To fix this, run the following and commit the result:"
9
+ echo "yarn run update"
10
+ exit 1
11
+ fi