Remove and forbid @ts-expect-error (#36513)

Removes `@ts-expect-error` in the code base and forbids it.

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: silverwind <115237+silverwind@users.noreply.github.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Copilot
2026-02-02 01:00:34 +08:00
committed by GitHub
parent c2dea22926
commit 7883f6dde9
22 changed files with 170 additions and 117 deletions
+4 -5
View File
@@ -15,6 +15,7 @@ import vue from 'eslint-plugin-vue';
import vueScopedCss from 'eslint-plugin-vue-scoped-css';
import wc from 'eslint-plugin-wc';
import {defineConfig, globalIgnores} from 'eslint/config';
import type {ESLint} from 'eslint';
const jsExts = ['js', 'mjs', 'cjs'] as const;
const tsExts = ['ts', 'mts', 'cts'] as const;
@@ -62,8 +63,7 @@ export default defineConfig([
'@stylistic': stylistic,
'@typescript-eslint': typescriptPlugin.plugin,
'array-func': arrayFunc,
// @ts-expect-error -- https://github.com/un-ts/eslint-plugin-import-x/issues/203
'import-x': importPlugin,
'import-x': importPlugin as unknown as ESLint.Plugin, // https://github.com/un-ts/eslint-plugin-import-x/issues/203
regexp,
sonarjs,
unicorn,
@@ -156,7 +156,7 @@ export default defineConfig([
'@typescript-eslint/adjacent-overload-signatures': [0],
'@typescript-eslint/array-type': [0],
'@typescript-eslint/await-thenable': [2],
'@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}],
'@typescript-eslint/ban-ts-comment': [2, {'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': false, 'ts-check': false}],
'@typescript-eslint/ban-tslint-comment': [0],
'@typescript-eslint/class-literal-property-style': [0],
'@typescript-eslint/class-methods-use-this': [0],
@@ -924,8 +924,7 @@ export default defineConfig([
},
extends: [
vue.configs['flat/recommended'],
// @ts-expect-error
vueScopedCss.configs['flat/recommended'],
vueScopedCss.configs['flat/recommended'] as any,
],
rules: {
'vue/attributes-order': [0],
+1 -1
View File
@@ -13,7 +13,7 @@
"target": "es2020",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"],
"lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext", "webworker"],
"allowImportingTsExtensions": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
Vendored
+87
View File
@@ -2,18 +2,105 @@ declare module '@techknowlogick/license-checker-webpack-plugin' {
const plugin: any;
export = plugin;
}
declare module 'eslint-plugin-no-use-extend-native' {
import type {Eslint} from 'eslint';
const plugin: Eslint.Plugin;
export = plugin;
}
declare module 'eslint-plugin-array-func' {
import type {Eslint} from 'eslint';
const plugin: Eslint.Plugin;
export = plugin;
}
declare module 'eslint-plugin-github' {
import type {Eslint} from 'eslint';
const plugin: Eslint.Plugin;
export = plugin;
}
declare module '*.svg' {
const value: string;
export default value;
}
declare module '*.css' {
const value: string;
export default value;
}
declare module '*.vue' {
import type {DefineComponent} from 'vue';
const component: DefineComponent<unknown, unknown, any>;
export default component;
// Here we declare all exports from vue files so `tsc` or `tsgo` can work for
// non-vue files. To lint .vue files, `vue-tsc` must be used.
export function initDashboardRepoList(): void;
export function initRepositoryActionView(): void;
}
declare module 'htmx.org/dist/htmx.esm.js' {
const value = await import('htmx.org');
export default value;
}
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
const value = await import('swagger-ui-dist');
export default value.SwaggerUIBundle;
}
declare module 'asciinema-player' {
interface AsciinemaPlayer {
create(src: string, element: HTMLElement, options?: Record<string, unknown>): void;
}
const exports: AsciinemaPlayer;
export = exports;
}
declare module '@citation-js/core' {
export class Cite {
constructor(data: string);
format(format: string, options?: Record<string, any>): string;
}
export const plugins: {
config: {
get(name: string): any;
};
};
}
declare module '@citation-js/plugin-software-formats' {}
declare module '@citation-js/plugin-bibtex' {}
declare module '@citation-js/plugin-csl' {}
declare module 'vue-bar-graph' {
import type {DefineComponent} from 'vue';
interface BarGraphPoint {
value: number;
label: string;
}
export const VueBarGraph: DefineComponent<{
points?: Array<BarGraphPoint>;
barColor?: string;
textColor?: string;
textAltColor?: string;
height?: number;
labelHeight?: number;
}>;
}
declare module '@mcaptcha/vanilla-glue' {
export let INPUT_NAME: string;
export default class Widget {
constructor(options: {
siteKey: {
instanceUrl: URL;
key: string;
};
});
}
}
+2 -3
View File
@@ -80,13 +80,12 @@ function initGlobalErrorHandler() {
// we added an event handler for window error at the very beginning of <script> of page head the
// handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before
// this init then in this init, we can collect all error events and show them.
for (const e of window._globalHandlerErrors || []) {
for (const e of (window._globalHandlerErrors as Iterable<ErrorEvent & PromiseRejectionEvent>) || []) {
processWindowErrorEvent(e);
}
// then, change _globalHandlerErrors to an object with push method, to process further error
// events directly
// @ts-expect-error -- this should be refactored to not use a fake array
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)};
window._globalHandlerErrors = {_inited: true, push: (e: ErrorEvent & PromiseRejectionEvent) => processWindowErrorEvent(e)} as any;
}
initGlobalErrorHandler();
+6 -5
View File
@@ -13,6 +13,8 @@ import {localUserSettings} from '../modules/user-settings.ts';
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
type StepContainerElement = HTMLElement & {_stepLogsActiveContainer?: HTMLElement}
type LogLine = {
index: number;
timestamp: number;
@@ -221,19 +223,18 @@ export default defineComponent({
},
// get the job step logs container ('.job-step-logs')
getJobStepLogsContainer(stepIndex: number): HTMLElement {
getJobStepLogsContainer(stepIndex: number): StepContainerElement {
return (this.$refs.logs as any)[stepIndex];
},
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
getActiveLogsContainer(stepIndex: number): HTMLElement {
getActiveLogsContainer(stepIndex: number): StepContainerElement {
const el = this.getJobStepLogsContainer(stepIndex);
// @ts-expect-error - _stepLogsActiveContainer is a custom property
return el._stepLogsActiveContainer ?? el;
},
// begin a log group
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
const el = (this.$refs.logs as any)[stepIndex];
const el = (this.$refs.logs as any)[stepIndex] as StepContainerElement;
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
this.createLogLine(stepIndex, startTime, {
index: line.index,
@@ -395,7 +396,7 @@ export default defineComponent({
}
// auto-scroll to the last log line of the last step
let autoScrollJobStepElement: HTMLElement | undefined;
let autoScrollJobStepElement: StepContainerElement | undefined;
for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) {
if (!autoScrollStepIndexes.get(stepIndex)) continue;
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
@@ -1,5 +1,4 @@
<script lang="ts" setup>
// @ts-expect-error - module exports no types
import {VueBarGraph} from 'vue-bar-graph';
import {computed, onMounted, shallowRef, useTemplateRef, type ShallowRef} from 'vue';
@@ -155,9 +155,8 @@ export default defineComponent({
return -1;
},
getActiveItem() {
const el = this.$refs[`listItem${this.activeItemIndex}`];
// @ts-expect-error - el is unknown type
return (el && el.length) ? el[0] : null;
const el = this.$refs[`listItem${this.activeItemIndex}`] as Array<HTMLDivElement>;
return el?.length ? el[0] : null;
},
keydown(e: KeyboardEvent) {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
@@ -174,7 +173,7 @@ export default defineComponent({
return;
}
this.activeItemIndex = nextIndex;
this.getActiveItem().scrollIntoView({block: 'nearest'});
this.getActiveItem()!.scrollIntoView({block: 'nearest'});
} else if (e.key === 'Enter') {
e.preventDefault();
this.getActiveItem()?.click();
+10 -2
View File
@@ -41,6 +41,15 @@ const customEventListener: Plugin = {
},
};
type LineOptions = ChartOptions<'line'> & {
plugins?: {
customEventListener?: {
chartType: string;
instance: unknown;
};
};
}
Chart.defaults.color = chartJsColors.text;
Chart.defaults.borderColor = chartJsColors.border;
@@ -251,7 +260,7 @@ export default defineComponent({
}
},
getOptions(type: string): ChartOptions<'line'> {
getOptions(type: string): LineOptions {
return {
responsive: true,
maintainAspectRatio: false,
@@ -264,7 +273,6 @@ export default defineComponent({
position: 'top',
align: 'center',
},
// @ts-expect-error: bug in chart.js types
customEventListener: {
chartType: type,
instance: this,
+2 -2
View File
@@ -8,6 +8,7 @@ import {
TimeScale,
type ChartOptions,
type ChartData,
type ChartDataset,
} from 'chart.js';
import {GET} from '../modules/fetch.ts';
import {Bar} from 'vue-chartjs';
@@ -83,13 +84,12 @@ function toGraphData(data: DayData[]): ChartData<'bar'> {
return {
datasets: [
{
// @ts-expect-error -- bar chart expects one-dimensional data, but apparently x/y still works
data: data.map((i) => ({x: i.week, y: i.commits})),
label: 'Commits',
backgroundColor: chartJsColors['commits'],
borderWidth: 0,
tension: 0.3,
},
} as unknown as ChartDataset<'bar'>,
],
};
}
-1
View File
@@ -41,7 +41,6 @@ export async function initCaptcha() {
// * the INPUT_NAME is a "const", it should not be changed.
// * the "mCaptcha.default" is actually the "Widget".
// @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property.
mCaptcha.INPUT_NAME = 'm-captcha-response';
const instanceURL = captchaEl.getAttribute('data-instance-url')!;
-4
View File
@@ -6,13 +6,9 @@ const {pageData} = window.config;
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
const [{Cite, plugins}] = await Promise.all([
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
]);
const {citationFileContent} = pageData;
@@ -72,10 +72,9 @@ class Source {
const sourcesByUrl = new Map<string, Source | null>();
const sourcesByPort = new Map<MessagePort, Source | null>();
// @ts-expect-error: typescript bug?
self.addEventListener('connect', (e: MessageEvent) => {
(self as unknown as SharedWorkerGlobalScope).addEventListener('connect', (e: MessageEvent) => {
for (const port of e.ports) {
port.addEventListener('message', (event) => {
port.addEventListener('message', (event: MessageEvent) => {
if (!self.EventSource) {
// some browsers (like PaleMoon, Firefox<53) don't support EventSource in SharedWorkerGlobalScope.
// this event handler needs EventSource when doing "new Source(url)", so just post a message back to the caller,
+1 -2
View File
@@ -56,8 +56,7 @@ function initRepoDiffConversationForm() {
const idx = newConversationHolder.getAttribute('data-idx');
form.closest('.conversation-holder')!.replaceWith(newConversationHolder);
// @ts-expect-error -- prevent further usage of the form because it should have been replaced
form = null;
(form as any) = null; // prevent further usage of the form because it should have been replaced
if (trLineType) {
// if there is a line-type for the "tr", it means the form is on the diff page
+1 -1
View File
@@ -201,7 +201,7 @@ async function pinMoveEnd(e: SortableEvent) {
}
async function initIssuePinSort() {
const pinDiv = document.querySelector('#issue-pins');
const pinDiv = document.querySelector<HTMLElement>('#issue-pins');
if (pinDiv === null) return;
+2 -2
View File
@@ -38,7 +38,7 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
async function initRepoProjectSortable(): Promise<void> {
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
const mainBoard = document.querySelector('#project-board')!;
const mainBoard = document.querySelector<HTMLElement>('#project-board')!;
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
createSortable(mainBoard, {
group: 'project-column',
@@ -67,7 +67,7 @@ async function initRepoProjectSortable(): Promise<void> {
});
for (const boardColumn of boardColumns) {
const boardCardList = boardColumn.querySelector('.cards')!;
const boardCardList = boardColumn.querySelector<HTMLElement>('.cards')!;
createSortable(boardCardList, {
group: 'shared',
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
@@ -56,12 +56,11 @@ describe('Repository Branch Settings', () => {
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
// Mock createSortable to capture and execute the onEnd callback
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions | undefined) => {
vi.mocked(createSortable).mockImplementation(async (_el: HTMLElement, options: SortableOptions | undefined) => {
if (options?.onEnd) {
options.onEnd(new Event('SortableEvent') as SortableEvent);
}
// @ts-expect-error: mock is incomplete
return {destroy: vi.fn()} as Sortable;
return {destroy: vi.fn()} as unknown as Sortable;
});
initRepoSettingsBranchesDrag();
@@ -4,7 +4,7 @@ import {showErrorToast} from '../modules/toast.ts';
import {queryElemChildren} from '../utils/dom.ts';
export function initRepoSettingsBranchesDrag() {
const protectedBranchesList = document.querySelector('#protected-branches-list');
const protectedBranchesList = document.querySelector<HTMLElement>('#protected-branches-list');
if (!protectedBranchesList) return;
createSortable(protectedBranchesList, {
+40 -39
View File
@@ -1,51 +1,52 @@
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
import {html, htmlRaw} from '../utils/html.ts';
type TributeItem = Record<string, any>;
import type {TributeCollection} from 'tributejs';
export async function attachTribute(element: HTMLElement) {
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
const collections = [
{ // emojis
trigger: ':',
requireLeadingSpace: true,
values: (query: string, cb: (matches: Array<string>) => void) => {
const matches = [];
for (const name of emojiKeys) {
if (name.includes(query)) {
matches.push(name);
if (matches.length > 5) break;
}
const emojiCollection: TributeCollection<string> = { // emojis
trigger: ':',
requireLeadingSpace: true,
values: (query: string, cb: (matches: Array<string>) => void) => {
const matches = [];
for (const name of emojiKeys) {
if (name.includes(query)) {
matches.push(name);
if (matches.length > 5) break;
}
cb(matches);
},
lookup: (item: TributeItem) => item,
selectTemplate: (item: TributeItem) => {
if (item === undefined) return null;
return emojiString(item.original);
},
menuItemTemplate: (item: TributeItem) => {
return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`;
},
}, { // mentions
values: window.config.mentionValues,
requireLeadingSpace: true,
menuItemTemplate: (item: TributeItem) => {
const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : '';
return html`
<div class="tribute-item">
<img alt src="${item.original.avatar}" width="21" height="21"/>
<span class="name">${item.original.name}</span>
${htmlRaw(fullNameHtml)}
</div>
`;
},
}
cb(matches);
},
];
lookup: (item) => item,
selectTemplate: (item) => {
if (item === undefined) return '';
return emojiString(item.original) ?? '';
},
menuItemTemplate: (item) => {
return html`<div class="tribute-item">${htmlRaw(emojiHTML(item.original))}<span>${item.original}</span></div>`;
},
};
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
const mentionCollection: TributeCollection<Record<string, any>> = {
values: window.config.mentionValues,
requireLeadingSpace: true,
menuItemTemplate: (item) => {
const fullNameHtml = item.original.fullname && item.original.fullname !== '' ? html`<span class="fullname">${item.original.fullname}</span>` : '';
return html`
<div class="tribute-item">
<img alt src="${item.original.avatar}" width="21" height="21"/>
<span class="name">${item.original.name}</span>
${htmlRaw(fullNameHtml)}
</div>
`;
},
};
const tribute = new Tribute({
collection: [emojiCollection as TributeCollection<any>, mentionCollection],
noMatchTemplate: () => '',
});
tribute.attach(element);
return tribute;
}
-30
View File
@@ -1,33 +1,3 @@
declare module '*.svg' {
const value: string;
export default value;
}
declare module '*.css' {
const value: string;
export default value;
}
declare module '*.vue' {
import type {DefineComponent} from 'vue';
const component: DefineComponent<unknown, unknown, any>;
export default component;
// Here we declare all exports from vue files so `tsc` or `tsgo` can work for
// non-vue files. To lint .vue files, `vue-tsc` must be used.
export function initDashboardRepoList(): void;
export function initRepositoryActionView(): void;
}
declare module 'htmx.org/dist/htmx.esm.js' {
const value = await import('htmx.org');
export default value;
}
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
const value = await import('swagger-ui-dist');
export default value.SwaggerUIBundle;
}
interface JQuery {
areYouSure: any, // jquery.are-you-sure
fomanticExt: any; // fomantic extension
+1 -2
View File
@@ -3,12 +3,11 @@ import {queryElems} from '../utils/dom.ts';
export async function initMarkupRenderAsciicast(elMarkup: HTMLElement): Promise<void> {
queryElems(elMarkup, '.asciinema-player-container', async (el) => {
const [player] = await Promise.all([
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
]);
player.create(el.getAttribute('data-asciinema-player-src'), el, {
player.create(el.getAttribute('data-asciinema-player-src')!, el, {
// poster (a preview frame) to display until the playback is started.
// Set it to 1 hour (also means the end if the video is shorter) to make the preview frame show more.
poster: 'npt:1:0:0',
+3 -3
View File
@@ -1,9 +1,9 @@
import type {SortableOptions, SortableEvent} from 'sortablejs';
import type SortableType from 'sortablejs';
export async function createSortable(el: Element, opts: {handle?: string} & SortableOptions = {}): Promise<SortableType> {
// @ts-expect-error: wrong type derived by typescript
const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs');
export async function createSortable(el: HTMLElement, opts: {handle?: string} & SortableOptions = {}): Promise<SortableType> {
// type reassigned because typescript derives the wrong type from this import
const {Sortable} = (await import(/* webpackChunkName: "sortablejs" */'sortablejs') as unknown as {Sortable: typeof SortableType});
return new Sortable(el, {
animation: 150,
+2 -3
View File
@@ -4,17 +4,16 @@ try {
new Intl.NumberFormat('en', {style: 'unit', unit: 'minute'}).format(1);
} catch {
const intlNumberFormat = Intl.NumberFormat;
// @ts-expect-error - polyfill is incomplete
Intl.NumberFormat = function(locales: string | string[], options: Intl.NumberFormatOptions) {
if (options.style === 'unit') {
return {
format(value: number | bigint | string) {
return ` ${value} ${options.unit}`;
},
};
} as Intl.NumberFormat;
}
return intlNumberFormat(locales, options);
};
} as unknown as typeof Intl.NumberFormat;
}
export function weakRefClass() {