1
0
mirror of https://github.com/go-gitea/gitea synced 2025-01-05 14:25:56 +01:00

Add initial typescript config and use it for eslint,vitest,playwright ()

This enables eslint to use the typescript parser and resolver which
brings some benefits that eslint rules now have type information
available and a tsconfig.json is required for the upcoming typescript
migration as well. Notable changes done:

- Add typescript parser and resolver
- Move the vue-specific config into the root file
- Enable `vue-scoped-css/enforce-style-type` rule, there was only one
violation and I added a inline disable there.
- Fix new lint errors that were detected because of the parser change
- Update `i/no-unresolved` to remove now-unnecessary workaround for the
resolver
- Disable `i/no-named-as-default` as it seems to raise bogus issues in
the webpack config
- Change vitest config to typescript
- Change playwright config to typescript
- Add `eslint-plugin-playwright` and fix issues
- Add `tsc` linting to `make lint-js`
This commit is contained in:
silverwind 2024-06-28 18:15:51 +02:00 committed by GitHub
parent df805d6ed0
commit 08579d6cbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 249 additions and 66 deletions

View File

@ -6,9 +6,20 @@ ignorePatterns:
- /web_src/fomantic
- /public/assets/js
parser: "@typescript-eslint/parser"
parserOptions:
sourceType: module
ecmaVersion: latest
project: true
extraFileExtensions: [".vue"]
settings:
import/extensions: [".js", ".ts"]
import/parsers:
"@typescript-eslint/parser": [".js", ".ts"]
import/resolver:
typescript: true
plugins:
- "@eslint-community/eslint-plugin-eslint-comments"
@ -103,6 +114,22 @@ overrides:
- files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"]
rules:
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
- files: ["**/*.vue"]
plugins:
- eslint-plugin-vue
- eslint-plugin-vue-scoped-css
extends:
- plugin:vue/vue3-recommended
- plugin:vue-scoped-css/vue3-recommended
rules:
vue/attributes-order: [0]
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
vue/max-attributes-per-line: [0]
vue/singleline-html-element-content-newline: [0]
- files: ["tests/e2e/**"]
plugins:
- eslint-plugin-playwright
extends: plugin:playwright/recommended
rules:
"@eslint-community/eslint-comments/disable-enable-pair": [2]
@ -264,7 +291,7 @@ rules:
i/no-internal-modules: [0]
i/no-mutable-exports: [0]
i/no-named-as-default-member: [0]
i/no-named-as-default: [2]
i/no-named-as-default: [0]
i/no-named-default: [0]
i/no-named-export: [0]
i/no-namespace: [0]
@ -274,7 +301,7 @@ rules:
i/no-restricted-paths: [0]
i/no-self-import: [2]
i/no-unassigned-import: [0]
i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$", ^vitest/]}]
i/no-unresolved: [2, {commonjs: true, ignore: ["\\?.+$"]}]
i/no-unused-modules: [2, {unusedExports: true}]
i/no-useless-path-segments: [2, {commonjs: true}]
i/no-webpack-loader-syntax: [2]

View File

@ -375,11 +375,13 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig
.PHONY: lint-js
lint-js: node_modules
npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES)
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES)
npx tsc
.PHONY: lint-js-fix
lint-js-fix: node_modules
npx eslint --color --max-warnings=0 --ext js,vue $(ESLINT_FILES) --fix
npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix
npx tsc
.PHONY: lint-css
lint-css: node_modules

169
package-lock.json generated
View File

@ -52,6 +52,7 @@
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.5.2",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.4.29",
@ -68,13 +69,16 @@
"@stoplight/spectral-cli": "6.11.1",
"@stylistic/eslint-plugin-js": "2.2.1",
"@stylistic/stylelint-plugin": "2.1.2",
"@typescript-eslint/parser": "7.14.1",
"@vitejs/plugin-vue": "5.0.5",
"eslint": "8.57.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "5.0.1",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-no-jquery": "3.0.1",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "1.6.2",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.3",
"eslint-plugin-unicorn": "54.0.0",
@ -2399,15 +2403,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz",
"integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz",
"integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "7.13.1",
"@typescript-eslint/types": "7.13.1",
"@typescript-eslint/typescript-estree": "7.13.1",
"@typescript-eslint/visitor-keys": "7.13.1",
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/typescript-estree": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4"
},
"engines": {
@ -2426,6 +2431,98 @@
}
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz",
"integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz",
"integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz",
"integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz",
"integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.14.1",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.13.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz",
@ -5353,6 +5450,31 @@
"ms": "^2.1.1"
}
},
"node_modules/eslint-import-resolver-typescript": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz",
"integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"enhanced-resolve": "^5.12.0",
"eslint-module-utils": "^2.7.4",
"fast-glob": "^3.3.1",
"get-tsconfig": "^4.5.0",
"is-core-module": "^2.11.0",
"is-glob": "^4.0.3"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts"
},
"peerDependencies": {
"eslint": "*",
"eslint-plugin-import": "*"
}
},
"node_modules/eslint-module-utils": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz",
@ -5701,6 +5823,30 @@
"node": ">=6.0.0"
}
},
"node_modules/eslint-plugin-playwright": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.6.2.tgz",
"integrity": "sha512-mraN4Em3b5jLt01q7qWPyLg0Q5v3KAWfJSlEWwldyUXoa7DSPrBR4k6B6LROLqipsG8ndkwWMdjl1Ffdh15tag==",
"dev": true,
"workspaces": [
"examples"
],
"dependencies": {
"globals": "^13.23.0"
},
"engines": {
"node": ">=16.6.0"
},
"peerDependencies": {
"eslint": ">=8.40.0",
"eslint-plugin-jest": ">=25"
},
"peerDependenciesMeta": {
"eslint-plugin-jest": {
"optional": true
}
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
@ -11867,11 +12013,10 @@
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"devOptional": true,
"peer": true,
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@ -51,6 +51,7 @@
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.5.2",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.4.29",
@ -67,13 +68,16 @@
"@stoplight/spectral-cli": "6.11.1",
"@stylistic/eslint-plugin-js": "2.2.1",
"@stylistic/stylelint-plugin": "2.1.2",
"@typescript-eslint/parser": "7.14.1",
"@vitejs/plugin-vue": "5.0.5",
"eslint": "8.57.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-array-func": "4.0.0",
"eslint-plugin-github": "5.0.1",
"eslint-plugin-i": "2.29.1",
"eslint-plugin-no-jquery": "3.0.1",
"eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "1.6.2",
"eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "1.0.3",
"eslint-plugin-unicorn": "54.0.0",

View File

@ -1,15 +1,12 @@
// @ts-check
import {devices} from '@playwright/test';
import {env} from 'node:process';
import type {PlaywrightTestConfig} from '@playwright/test';
const BASE_URL = process.env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000';
const BASE_URL = env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000';
/**
* @see https://playwright.dev/docs/test-configuration
* @type {import('@playwright/test').PlaywrightTestConfig}
*/
export default {
testDir: './tests/e2e/',
testMatch: /.*\.test\.e2e\.js/, // Match any .test.e2e.js files
testMatch: /.*\.test\.e2e\.ts/, // Match any .test.e2e.ts files
/* Maximum time one test can run for. */
timeout: 30 * 1000,
@ -24,13 +21,13 @@ export default {
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: Boolean(process.env.CI),
forbidOnly: Boolean(env.CI),
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: env.CI ? 2 : 0,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? 'list' : [['list'], ['html', {outputFolder: 'tests/e2e/reports/', open: 'never'}]],
reporter: env.CI ? 'list' : [['list'], ['html', {outputFolder: 'tests/e2e/reports/', open: 'never'}]],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
@ -98,4 +95,4 @@ export default {
outputDir: 'tests/e2e/test-artifacts/',
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
snapshotDir: 'tests/e2e/test-snapshots/',
};
} satisfies PlaywrightTestConfig;

View File

@ -65,7 +65,7 @@ TEST_MSSQL_HOST=localhost:1433 TEST_MSSQL_DBNAME=gitea_test TEST_MSSQL_USERNAME=
## Running individual tests
Example command to run `example.test.e2e.js` test file:
Example command to run `example.test.e2e.ts` test file:
_Note: unlike integration tests, this filtering is at the file level, not function_

View File

@ -73,10 +73,10 @@ func TestMain(m *testing.M) {
os.Exit(exitVal)
}
// TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.js" files in this directory and build a test for each.
// TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.ts" files in this directory and build a test for each.
func TestE2e(t *testing.T) {
// Find the paths of all e2e test files in test directory.
searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js")
searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.ts")
paths, err := filepath.Glob(searchGlob)
if err != nil {
t.Fatal(err)

View File

@ -1,19 +1,18 @@
// @ts-check
import {test, expect} from '@playwright/test';
import {login_user, save_visual, load_logged_in_context} from './utils_e2e.js';
import {login_user, save_visual, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2');
});
test('Load Homepage', async ({page}) => {
test('homepage', async ({page}) => {
const response = await page.goto('/');
await expect(response?.status()).toBe(200); // Status OK
await expect(page).toHaveTitle(/^Gitea: Git with a cup of tea\s*$/);
await expect(page.locator('.logo')).toHaveAttribute('src', '/assets/img/logo.svg');
});
test('Test Register Form', async ({page}, workerInfo) => {
test('register', async ({page}, workerInfo) => {
const response = await page.goto('/user/sign_up');
await expect(response?.status()).toBe(200); // Status OK
await page.type('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`);
@ -29,7 +28,7 @@ test('Test Register Form', async ({page}, workerInfo) => {
save_visual(page);
});
test('Test Login Form', async ({page}, workerInfo) => {
test('login', async ({page}, workerInfo) => {
const response = await page.goto('/user/login');
await expect(response?.status()).toBe(200); // Status OK
@ -37,14 +36,14 @@ test('Test Login Form', async ({page}, workerInfo) => {
await page.type('input[name=password]', `password`);
await page.click('form button.ui.primary.button:visible');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
await expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`);
save_visual(page);
});
test('Test Logged In User', async ({browser}, workerInfo) => {
test('logged in user', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();

View File

@ -1,4 +1,5 @@
import {expect} from '@playwright/test';
import {env} from 'node:process';
const ARTIFACTS_PATH = `tests/e2e/test-artifacts`;
const LOGIN_PASSWORD = 'password';
@ -20,7 +21,7 @@ export async function login_user(browser, workerInfo, user) {
await page.type('input[name=password]', LOGIN_PASSWORD);
await page.click('form button.ui.primary.button:visible');
await page.waitForLoadState('networkidle');
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
await expect(page.url(), {message: `Failed to login user ${user}`}).toBe(`${workerInfo.project.use.baseURL}/`);
@ -44,8 +45,8 @@ export async function load_logged_in_context(browser, workerInfo, user) {
export async function save_visual(page) {
// Optionally include visual testing
if (process.env.VISUAL_TEST) {
await page.waitForLoadState('networkidle');
if (env.VISUAL_TEST) {
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
// Mock page/version string
await page.locator('footer div.ui.left').evaluate((node) => node.innerHTML = 'MOCK');
await expect(page).toHaveScreenshot({

30
tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"include": [
"*",
"tests/e2e/**/*",
"tools/**/*",
"web_src/js/**/*",
],
"compilerOptions": {
"target": "es2020",
"module": "node16",
"moduleResolution": "node16",
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"],
"allowImportingTsExtensions": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"alwaysStrict": true,
"esModuleInterop": true,
"isolatedModules": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"stripInternal": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": false,
"exactOptionalPropertyTypes": false,
}
}

View File

@ -1,22 +0,0 @@
plugins:
- eslint-plugin-vue
- eslint-plugin-vue-scoped-css
extends:
- ../../../.eslintrc.yaml
- plugin:vue/vue3-recommended
- plugin:vue-scoped-css/vue3-recommended
parserOptions:
sourceType: module
ecmaVersion: latest
env:
browser: true
rules:
vue/attributes-order: [0]
vue/html-closing-bracket-spacing: [2, {startTag: never, endTag: never, selfClosingTag: never}]
vue/max-attributes-per-line: [0]
vue/singleline-html-element-content-newline: [0]
vue-scoped-css/enforce-style-type: [0]

View File

@ -797,7 +797,7 @@ export function initRepositoryActionView() {
}
</style>
<style>
<style> /* eslint-disable-line vue-scoped-css/enforce-style-type */
/* some elements are not managed by vue, so we need to use global style */
.job-status-rotate {
animation: job-status-rotate-keyframes 1s linear infinite;

View File

@ -153,7 +153,7 @@ export function initRepoCodeView() {
});
$(window).on('hashchange', () => {
let m = window.location.hash.match(rangeAnchorRegex);
let m = rangeAnchorRegex.exec(window.location.hash.match);
const $linesEls = $(getLineEls());
let $first;
if (m) {
@ -170,7 +170,7 @@ export function initRepoCodeView() {
return;
}
}
m = window.location.hash.match(singleAnchorRegex);
m = singleAnchorRegex.exec(window.location.hash.match);
if (m) {
$first = $linesEls.filter(`[rel=L${m[2]}]`);
if ($first.length) {

View File

@ -4,7 +4,7 @@ import {isDocumentFragmentOrElementNode} from '../utils/dom.js';
import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg';
window.customElements.define('overflow-menu', class extends HTMLElement {
updateItems = throttle(100, () => {
updateItems = throttle(100, () => { // eslint-disable-line unicorn/consistent-function-scoping -- https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2088
if (!this.tippyContent) {
const div = document.createElement('div');
div.classList.add('tippy-target');