From 224b7881d9fe348f01d97601a8fc09c06d36023f Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 27 Jan 2026 20:59:51 +0100 Subject: [PATCH] Forbid localStorage access in eslint (#36461) Followup to https://github.com/go-gitea/gitea/commit/59f812bc1cc52f15d66d1b233f11e43339c09cec, enforce using our localStorage wrapper in eslint. Also did a few tweaks in the eslint config, like removing the incomplete list of globals, this is a non-issue with typescript. --------- Signed-off-by: silverwind --- eslint.config.ts | 26 ++++++++++++++++---------- web_src/js/modules/fetch.ts | 2 +- web_src/js/modules/user-settings.ts | 1 + web_src/js/standalone/swagger.ts | 3 ++- web_src/js/utils/image.test.ts | 2 +- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 97533890c4..2f710bd936 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -18,7 +18,18 @@ import {defineConfig, globalIgnores} from 'eslint/config'; const jsExts = ['js', 'mjs', 'cjs'] as const; const tsExts = ['ts', 'mts', 'cts'] as const; -const restrictedSyntax = ['WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression']; + +const restrictedGlobals = [ + {name: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'}, + {name: 'fetch', message: 'Use `modules/fetch.ts` instead.'}, +]; + +const restrictedProperties = [ + {object: 'window', property: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'}, + {object: 'globalThis', property: 'localStorage', message: 'Use `modules/user-settings.ts` instead.'}, + {object: 'window', property: 'fetch', message: 'Use `modules/fetch.ts` instead.'}, + {object: 'globalThis', property: 'fetch', message: 'Use `modules/fetch.ts` instead.'}, +]; export default defineConfig([ globalIgnores([ @@ -65,7 +76,7 @@ export default defineConfig([ 'import-x/resolver': {'typescript': true}, }, rules: { - '@eslint-community/eslint-comments/disable-enable-pair': [2], + '@eslint-community/eslint-comments/disable-enable-pair': [0], '@eslint-community/eslint-comments/no-aggregating-enable': [2], '@eslint-community/eslint-comments/no-duplicate-disable': [2], '@eslint-community/eslint-comments/no-restricted-disable': [0], @@ -555,9 +566,10 @@ export default defineConfig([ 'no-redeclare': [0], // must be disabled for typescript overloads 'no-regex-spaces': [2], 'no-restricted-exports': [0], - 'no-restricted-globals': [2, 'addEventListener', 'blur', 'close', 'closed', 'confirm', 'defaultStatus', 'defaultstatus', 'error', 'event', 'external', 'find', 'focus', 'frameElement', 'frames', 'history', 'innerHeight', 'innerWidth', 'isFinite', 'isNaN', 'length', 'locationbar', 'menubar', 'moveBy', 'moveTo', 'name', 'onblur', 'onerror', 'onfocus', 'onload', 'onresize', 'onunload', 'open', 'opener', 'opera', 'outerHeight', 'outerWidth', 'pageXOffset', 'pageYOffset', 'parent', 'print', 'removeEventListener', 'resizeBy', 'resizeTo', 'screen', 'screenLeft', 'screenTop', 'screenX', 'screenY', 'scroll', 'scrollbars', 'scrollBy', 'scrollTo', 'scrollX', 'scrollY', 'status', 'statusbar', 'stop', 'toolbar', 'top'], + 'no-restricted-globals': [2, ...restrictedGlobals], + 'no-restricted-properties': [2, ...restrictedProperties], 'no-restricted-imports': [0], - 'no-restricted-syntax': [2, ...restrictedSyntax, {selector: 'CallExpression[callee.name="fetch"]', message: 'use modules/fetch.ts instead'}], + 'no-restricted-syntax': [2, 'WithStatement', 'ForInStatement', 'LabeledStatement', 'SequenceExpression'], 'no-return-assign': [0], 'no-script-url': [2], 'no-self-assign': [2, {props: true}], @@ -923,12 +935,6 @@ export default defineConfig([ 'vue/require-typed-ref': [2], }, }, - { - files: ['web_src/js/modules/fetch.ts', 'web_src/js/standalone/**/*'], - rules: { - 'no-restricted-syntax': [2, ...restrictedSyntax], - }, - }, { files: ['**/*.test.ts', 'web_src/js/test/setup.ts'], plugins: {vitest}, diff --git a/web_src/js/modules/fetch.ts b/web_src/js/modules/fetch.ts index 16b2b58bae..1e4d1180f7 100644 --- a/web_src/js/modules/fetch.ts +++ b/web_src/js/modules/fetch.ts @@ -22,7 +22,7 @@ export function request(url: string, {method = 'GET', data, headers = {}, ...oth headersMerged.set(name, value); } - return fetch(url, { + return fetch(url, { // eslint-disable-line no-restricted-globals method, headers: headersMerged, ...other, diff --git a/web_src/js/modules/user-settings.ts b/web_src/js/modules/user-settings.ts index dbf69f384d..7c96ad8373 100644 --- a/web_src/js/modules/user-settings.ts +++ b/web_src/js/modules/user-settings.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-globals */ // Some people deploy Gitea under a subpath, so it needs prefix to avoid local storage key conflicts. // And these keys are for user settings only, it also needs a specific prefix, // in case in the future there are other uses of local storage, and/or we need to clear some keys when the quota is exceeded. diff --git a/web_src/js/standalone/swagger.ts b/web_src/js/standalone/swagger.ts index 2b90dcfd41..81759a2d02 100644 --- a/web_src/js/standalone/swagger.ts +++ b/web_src/js/standalone/swagger.ts @@ -1,13 +1,14 @@ import SwaggerUI from 'swagger-ui-dist/swagger-ui-es-bundle.js'; import 'swagger-ui-dist/swagger-ui.css'; import {load as loadYaml} from 'js-yaml'; +import {GET} from '../modules/fetch.ts'; window.addEventListener('load', async () => { const elSwaggerUi = document.querySelector('#swagger-ui')!; const url = elSwaggerUi.getAttribute('data-source')!; let spec: any; if (url) { - const res = await fetch(url); + const res = await GET(url); spec = await res.json(); } else { const elSpecContent = elSwaggerUi.querySelector('.swagger-spec-content')!; diff --git a/web_src/js/utils/image.test.ts b/web_src/js/utils/image.test.ts index 49856c891c..3e95ba5d78 100644 --- a/web_src/js/utils/image.test.ts +++ b/web_src/js/utils/image.test.ts @@ -5,7 +5,7 @@ const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91 const pngEmpty = 'data:image/png;base64,'; async function dataUriToBlob(datauri: string) { - return await (await globalThis.fetch(datauri)).blob(); + return await (await globalThis.fetch(datauri)).blob(); // eslint-disable-line no-restricted-properties } test('pngChunks', async () => {