Add no-hardcoded-urls rule

Related to gitlab-org/gitlab#560240 and gitlab-org/gitlab#579401

Background

We haven't had strict guidelines around constructing URLs in GitLab and as a result we have a number of instances where we construct URLs on the frontend. This can cause issues with the relative_url_root setting, Geo, and in the future organization scoped routes. In gitlab-org/gitlab!215122 (merged) I added some developer documentation that now lives at https://docs.gitlab.com/development/urls_in_gitlab/. This MR goes one more step and introduces a ESLint rule that prevents hardcoding or constructing URLs on the frontend. This should help us prep for organization scoped routes and reduce relative_url_root setting and Geo bugs.

Screenshots

Screenshot_2025-12-09_at_2.52.18_PM

How to test

  1. Install yalc - https://github.com/wclr/yalc
  2. Pull down this MR
  3. Run yalc publish
  4. In gitlab-org/gitlab project run yalc add @gitlab/eslint-plugin
  5. Apply this patch (pbpaste | git apply)
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 7015081371df..b4d788535908 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -252,6 +252,41 @@ export default [
       '@gitlab/vue-tailwind-no-interpolation': 'error',
       '@gitlab/tailwind-no-max-width-media-queries': 'error',
       '@gitlab/vue-tailwind-no-max-width-media-queries': 'error',
+      '@gitlab/no-hardcoded-urls': [
+        'error',
+        {
+          allowedKeys: ['path', 'redirect'],
+          allowedFunctions: ['helpPagePath'],
+          allowedInterpolationVariables: [
+            'FORUM_URL',
+            'DOCS_URL',
+            'PROMO_URL',
+            'CONTRIBUTE_URL',
+            'DOCS_URL_IN_EE_DIR',
+            'CUSTOMERS_PORTAL_URL',
+          ],
+          allowedPatterns: ['\\/api\\/:version'],
+          disallowedObjectProperties: ['relative_url_root'],
+        },
+      ],
+      '@gitlab/vue-no-hardcoded-urls': [
+        'error',
+        {
+          allowedVueComponents: ['help-page-link'],
+          allowedKeys: ['path', 'redirect'],
+          allowedFunctions: ['helpPagePath'],
+          allowedInterpolationVariables: [
+            'FORUM_URL',
+            'DOCS_URL',
+            'PROMO_URL',
+            'CONTRIBUTE_URL',
+            'DOCS_URL_IN_EE_DIR',
+            'CUSTOMERS_PORTAL_URL',
+          ],
+          allowedPatterns: ['\\/api\\/:version'],
+          disallowedObjectProperties: ['relative_url_root'],
+        },
+      ],
 
       'no-param-reassign': [
         'error',
@@ -491,6 +526,8 @@ export default [
       'require-await': 'error',
       'import/no-dynamic-require': 'off',
       'no-import-assign': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
 
       'no-restricted-syntax': [
         'error',
@@ -626,6 +663,8 @@ export default [
       'import/no-nodejs-modules': 'off',
       'filenames/match-regex': 'off',
       'no-console': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
     },
   },
   {
@@ -634,6 +673,8 @@ export default [
     rules: {
       'filenames/match-regex': 'off',
       '@gitlab/require-i18n-strings': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
       'import/no-unresolved': [
         'error',
         // The test fixtures are dynamically generated in CI during
@@ -725,6 +766,8 @@ export default [
       'filenames/match-regex': 'off',
       'no-console': 'off',
       'import/no-unresolved': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
     },
   },
 
@@ -785,6 +828,8 @@ export default [
     rules: {
       '@gitlab/require-i18n-strings': 'off',
       'no-restricted-imports': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
     },
   },
 
@@ -818,6 +863,15 @@ export default [
       'func-names': 'off',
       // k6 globals are defined above
       'no-undef': 'off',
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
+    },
+  },
+  {
+    files: ['app/assets/javascripts/**/mock_data.js'],
+    rules: {
+      '@gitlab/no-hardcoded-urls': 'off',
+      '@gitlab/vue-no-hardcoded-urls': 'off',
     },
   },
   ...jhConfigs,
  1. Run ./scripts/dump_graphql_schema. You may have to download job artifacts from the latest generate-apollo-graphql-schema job and manually add the gitlab_schema_apollo.graphql file to tmp/tests/graphql
  2. Run yarn lint:eslint --no-cache

Next steps after this is merged

  1. Update eslint.config.mjs with above patch
  2. Run node scripts/frontend/generate_eslint_todo_list.mjs @gitlab/no-hardcoded-urls and node scripts/frontend/generate_eslint_todo_list.mjs @gitlab/vue-no-hardcoded-urls to generate todo lists for fixing these rules.
  3. Group files by GitLab team first using the .gitlab/CODEOWNERS and then manually. Create an issue for each team with list of files that need to be fixed.
  4. Work with teams to fix the violations.
Edited by Peter Hegman

Merge request reports

Loading