Skip to content

Commit 7207d93

Browse files
authored
Fix a number of Typescript issues (#31877)
Typescript error count is reduced from 633 to 540 with this. No runtime changes except in test code.
1 parent 39d2fde commit 7207d93

File tree

16 files changed

+140
-78
lines changed

16 files changed

+140
-78
lines changed

types.d.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,52 @@ declare module '*.css' {
1010

1111
declare let __webpack_public_path__: string;
1212

13+
declare module 'htmx.org/dist/htmx.esm.js' {
14+
const value = await import('htmx.org');
15+
export default value;
16+
}
17+
18+
declare module 'uint8-to-base64' {
19+
export function encode(arrayBuffer: ArrayBuffer): string;
20+
export function decode(base64str: string): ArrayBuffer;
21+
}
22+
23+
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
24+
const value = await import('swagger-ui-dist');
25+
export default value.SwaggerUIBundle;
26+
}
27+
28+
interface JQuery {
29+
api: any, // fomantic
30+
areYouSure: any, // jquery.are-you-sure
31+
dimmer: any, // fomantic
32+
dropdown: any; // fomantic
33+
modal: any; // fomantic
34+
tab: any; // fomantic
35+
transition: any, // fomantic
36+
}
37+
38+
interface JQueryStatic {
39+
api: any, // fomantic
40+
}
41+
42+
interface Element {
43+
_tippy: import('tippy.js').Instance;
44+
}
45+
46+
type Writable<T> = { -readonly [K in keyof T]: T[K] };
47+
1348
interface Window {
1449
config: import('./web_src/js/types.ts').Config;
1550
$: typeof import('@types/jquery'),
1651
jQuery: typeof import('@types/jquery'),
17-
htmx: typeof import('htmx.org'),
52+
htmx: Omit<typeof import('htmx.org/dist/htmx.esm.js').default, 'config'> & {
53+
config?: Writable<typeof import('htmx.org').default.config>,
54+
},
55+
ui?: any,
1856
_globalHandlerErrors: Array<ErrorEvent & PromiseRejectionEvent> & {
1957
_inited: boolean,
2058
push: (e: ErrorEvent & PromiseRejectionEvent) => void | number,
2159
},
22-
}
23-
24-
declare module 'htmx.org/dist/htmx.esm.js' {
25-
const value = await import('htmx.org');
26-
export default value;
27-
}
28-
29-
interface Element {
30-
_tippy: import('tippy.js').Instance;
60+
__webpack_public_path__: string;
3161
}

web_src/js/htmx.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import {showErrorToast} from './modules/toast.ts';
2+
import 'idiomorph/dist/idiomorph-ext.js'; // https://github.com/bigskysoftware/idiomorph#htmx
3+
import type {HtmxResponseInfo} from 'htmx.org';
24

3-
// https://github.com/bigskysoftware/idiomorph#htmx
4-
import 'idiomorph/dist/idiomorph-ext.js';
5+
type HtmxEvent = Event & {detail: HtmxResponseInfo};
56

67
// https://htmx.org/reference/#config
78
window.htmx.config.requestClass = 'is-loading';
89
window.htmx.config.scrollIntoViewOnBoost = false;
910

1011
// https://htmx.org/events/#htmx:sendError
11-
document.body.addEventListener('htmx:sendError', (event) => {
12+
document.body.addEventListener('htmx:sendError', (event: HtmxEvent) => {
1213
// TODO: add translations
1314
showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`);
1415
});
1516

1617
// https://htmx.org/events/#htmx:responseError
17-
document.body.addEventListener('htmx:responseError', (event) => {
18+
document.body.addEventListener('htmx:responseError', (event: HtmxEvent) => {
1819
// TODO: add translations
1920
showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`);
2021
});

web_src/js/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ initGiteaFomantic();
9898
initDirAuto();
9999
initSubmitEventPolyfill();
100100

101-
function callInitFunctions(functions) {
101+
function callInitFunctions(functions: (() => any)[]) {
102102
// Start performance trace by accessing a URL by "https://localhost/?_ui_performance_trace=1" or "https://localhost/?key=value&_ui_performance_trace=1"
103103
// It is a quick check, no side effect so no need to do slow URL parsing.
104104
const initStart = performance.now();
105105
if (window.location.search.includes('_ui_performance_trace=1')) {
106-
let results = [];
106+
let results: {name: string, dur: number}[] = [];
107107
for (const func of functions) {
108108
const start = performance.now();
109109
func();

web_src/js/render/ansi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {AnsiUp} from 'ansi_up';
22

3-
const replacements = [
3+
const replacements: Array<[RegExp, string]> = [
44
[/\x1b\[\d+[A-H]/g, ''], // Move cursor, treat them as no-op
55
[/\x1b\[\d?[JK]/g, '\r'], // Erase display/line, treat them as a Carriage Return
66
];
77

88
// render ANSI to HTML
9-
export function renderAnsi(line) {
9+
export function renderAnsi(line: string): string {
1010
// create a fresh ansi_up instance because otherwise previous renders can influence
1111
// the output of future renders, because ansi_up is stateful and remembers things like
1212
// unclosed opening tags for colors.

web_src/js/standalone/swagger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ window.addEventListener('load', async () => {
88

99
// Make the page's protocol be at the top of the schemes list
1010
const proto = window.location.protocol.slice(0, -1);
11-
spec.schemes.sort((a, b) => {
11+
spec.schemes.sort((a: string, b: string) => {
1212
if (a === proto) return -1;
1313
if (b === proto) return 1;
1414
return 0;

web_src/js/svg.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('svgParseOuterInner', () => {
1717
test('SvgIcon', () => {
1818
const root = document.createElement('div');
1919
createApp({render: () => h(SvgIcon, {name: 'octicon-link', size: 24, class: 'base', className: 'extra'})}).mount(root);
20-
const node = root.firstChild;
20+
const node = root.firstChild as Element;
2121
expect(node.nodeName).toEqual('svg');
2222
expect(node.getAttribute('width')).toEqual('24');
2323
expect(node.getAttribute('height')).toEqual('24');

web_src/js/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ export type RequestData = string | FormData | URLSearchParams;
2929
export type RequestOpts = {
3030
data?: RequestData,
3131
} & RequestInit;
32+
33+
export type IssueData = {
34+
owner: string,
35+
repo: string,
36+
type: string,
37+
index: string,
38+
}

web_src/js/utils.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,23 +95,20 @@ test('toAbsoluteUrl', () => {
9595
});
9696

9797
test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => {
98-
// TextEncoder is Node.js API while Uint8Array is jsdom API and their outputs are not
99-
// structurally comparable, so we convert to array to compare. The conversion can be
100-
// removed once https://github.com/jsdom/jsdom/issues/2524 is resolved.
10198
const encoder = new TextEncoder();
10299
const uint8array = encoder.encode.bind(encoder);
103100

104101
expect(encodeURLEncodedBase64(uint8array('AA?'))).toEqual('QUE_'); // standard base64: "QUE/"
105102
expect(encodeURLEncodedBase64(uint8array('AA~'))).toEqual('QUF-'); // standard base64: "QUF+"
106103

107-
expect(Array.from(decodeURLEncodedBase64('QUE/'))).toEqual(Array.from(uint8array('AA?')));
108-
expect(Array.from(decodeURLEncodedBase64('QUF+'))).toEqual(Array.from(uint8array('AA~')));
109-
expect(Array.from(decodeURLEncodedBase64('QUE_'))).toEqual(Array.from(uint8array('AA?')));
110-
expect(Array.from(decodeURLEncodedBase64('QUF-'))).toEqual(Array.from(uint8array('AA~')));
104+
expect(new Uint8Array(decodeURLEncodedBase64('QUE/'))).toEqual(uint8array('AA?'));
105+
expect(new Uint8Array(decodeURLEncodedBase64('QUF+'))).toEqual(uint8array('AA~'));
106+
expect(new Uint8Array(decodeURLEncodedBase64('QUE_'))).toEqual(uint8array('AA?'));
107+
expect(new Uint8Array(decodeURLEncodedBase64('QUF-'))).toEqual(uint8array('AA~'));
111108

112109
expect(encodeURLEncodedBase64(uint8array('a'))).toEqual('YQ'); // standard base64: "YQ=="
113-
expect(Array.from(decodeURLEncodedBase64('YQ'))).toEqual(Array.from(uint8array('a')));
114-
expect(Array.from(decodeURLEncodedBase64('YQ=='))).toEqual(Array.from(uint8array('a')));
110+
expect(new Uint8Array(decodeURLEncodedBase64('YQ'))).toEqual(uint8array('a'));
111+
expect(new Uint8Array(decodeURLEncodedBase64('YQ=='))).toEqual(uint8array('a'));
115112
});
116113

117114
test('file detection', () => {

web_src/js/utils.ts

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,69 @@
11
import {encode, decode} from 'uint8-to-base64';
2+
import type {IssueData} from './types.ts';
23

34
// transform /path/to/file.ext to file.ext
4-
export function basename(path) {
5+
export function basename(path: string): string {
56
const lastSlashIndex = path.lastIndexOf('/');
67
return lastSlashIndex < 0 ? path : path.substring(lastSlashIndex + 1);
78
}
89

910
// transform /path/to/file.ext to .ext
10-
export function extname(path) {
11+
export function extname(path: string): string {
1112
const lastSlashIndex = path.lastIndexOf('/');
1213
const lastPointIndex = path.lastIndexOf('.');
1314
if (lastSlashIndex > lastPointIndex) return '';
1415
return lastPointIndex < 0 ? '' : path.substring(lastPointIndex);
1516
}
1617

1718
// test whether a variable is an object
18-
export function isObject(obj) {
19+
export function isObject(obj: any): boolean {
1920
return Object.prototype.toString.call(obj) === '[object Object]';
2021
}
2122

2223
// returns whether a dark theme is enabled
23-
export function isDarkTheme() {
24+
export function isDarkTheme(): boolean {
2425
const style = window.getComputedStyle(document.documentElement);
2526
return style.getPropertyValue('--is-dark-theme').trim().toLowerCase() === 'true';
2627
}
2728

2829
// strip <tags> from a string
29-
export function stripTags(text) {
30+
export function stripTags(text: string): string {
3031
return text.replace(/<[^>]*>?/g, '');
3132
}
3233

33-
export function parseIssueHref(href) {
34+
export function parseIssueHref(href: string): IssueData {
3435
const path = (href || '').replace(/[#?].*$/, '');
3536
const [_, owner, repo, type, index] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || [];
3637
return {owner, repo, type, index};
3738
}
3839

3940
// parse a URL, either relative '/path' or absolute 'https://localhost/path'
40-
export function parseUrl(str) {
41+
export function parseUrl(str: string): URL {
4142
return new URL(str, str.startsWith('http') ? undefined : window.location.origin);
4243
}
4344

4445
// return current locale chosen by user
45-
export function getCurrentLocale() {
46+
export function getCurrentLocale(): string {
4647
return document.documentElement.lang;
4748
}
4849

4950
// given a month (0-11), returns it in the documents language
50-
export function translateMonth(month) {
51+
export function translateMonth(month: number) {
5152
return new Date(Date.UTC(2022, month, 12)).toLocaleString(getCurrentLocale(), {month: 'short', timeZone: 'UTC'});
5253
}
5354

5455
// given a weekday (0-6, Sunday to Saturday), returns it in the documents language
55-
export function translateDay(day) {
56+
export function translateDay(day: number) {
5657
return new Date(Date.UTC(2022, 7, day)).toLocaleString(getCurrentLocale(), {weekday: 'short', timeZone: 'UTC'});
5758
}
5859

5960
// convert a Blob to a DataURI
60-
export function blobToDataURI(blob) {
61+
export function blobToDataURI(blob: Blob): Promise<string> {
6162
return new Promise((resolve, reject) => {
6263
try {
6364
const reader = new FileReader();
6465
reader.addEventListener('load', (e) => {
65-
resolve(e.target.result);
66+
resolve(e.target.result as string);
6667
});
6768
reader.addEventListener('error', () => {
6869
reject(new Error('FileReader failed'));
@@ -75,7 +76,7 @@ export function blobToDataURI(blob) {
7576
}
7677

7778
// convert image Blob to another mime-type format.
78-
export function convertImage(blob, mime) {
79+
export function convertImage(blob: Blob, mime: string): Promise<Blob> {
7980
return new Promise(async (resolve, reject) => {
8081
try {
8182
const img = new Image();
@@ -104,7 +105,7 @@ export function convertImage(blob, mime) {
104105
});
105106
}
106107

107-
export function toAbsoluteUrl(url) {
108+
export function toAbsoluteUrl(url: string): string {
108109
if (url.startsWith('http://') || url.startsWith('https://')) {
109110
return url;
110111
}
@@ -118,15 +119,15 @@ export function toAbsoluteUrl(url) {
118119
}
119120

120121
// Encode an ArrayBuffer into a URLEncoded base64 string.
121-
export function encodeURLEncodedBase64(arrayBuffer) {
122+
export function encodeURLEncodedBase64(arrayBuffer: ArrayBuffer): string {
122123
return encode(arrayBuffer)
123124
.replace(/\+/g, '-')
124125
.replace(/\//g, '_')
125126
.replace(/=/g, '');
126127
}
127128

128-
// Decode a URLEncoded base64 to an ArrayBuffer string.
129-
export function decodeURLEncodedBase64(base64url) {
129+
// Decode a URLEncoded base64 to an ArrayBuffer.
130+
export function decodeURLEncodedBase64(base64url: string): ArrayBuffer {
130131
return decode(base64url
131132
.replace(/_/g, '/')
132133
.replace(/-/g, '+'));
@@ -135,20 +136,22 @@ export function decodeURLEncodedBase64(base64url) {
135136
const domParser = new DOMParser();
136137
const xmlSerializer = new XMLSerializer();
137138

138-
export function parseDom(text, contentType) {
139+
export function parseDom(text: string, contentType: DOMParserSupportedType): Document {
139140
return domParser.parseFromString(text, contentType);
140141
}
141142

142-
export function serializeXml(node) {
143+
export function serializeXml(node: Element | Node): string {
143144
return xmlSerializer.serializeToString(node);
144145
}
145146

146-
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
147+
export function sleep(ms: number): Promise<void> {
148+
return new Promise((resolve) => setTimeout(resolve, ms));
149+
}
147150

148-
export function isImageFile({name, type}) {
151+
export function isImageFile({name, type}: {name: string, type?: string}): boolean {
149152
return /\.(jpe?g|png|gif|webp|svg|heic)$/i.test(name || '') || type?.startsWith('image/');
150153
}
151154

152-
export function isVideoFile({name, type}) {
155+
export function isVideoFile({name, type}: {name: string, type?: string}): boolean {
153156
return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/');
154157
}

web_src/js/utils/color.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@ import type {ColorInput} from 'tinycolor2';
33

44
// Returns relative luminance for a SRGB color - https://en.wikipedia.org/wiki/Relative_luminance
55
// Keep this in sync with modules/util/color.go
6-
function getRelativeLuminance(color: ColorInput) {
6+
function getRelativeLuminance(color: ColorInput): number {
77
const {r, g, b} = tinycolor(color).toRgb();
88
return (0.2126729 * r + 0.7151522 * g + 0.072175 * b) / 255;
99
}
1010

11-
function useLightText(backgroundColor: ColorInput) {
11+
function useLightText(backgroundColor: ColorInput): boolean {
1212
return getRelativeLuminance(backgroundColor) < 0.453;
1313
}
1414

1515
// Given a background color, returns a black or white foreground color that the highest
1616
// contrast ratio. In the future, the APCA contrast function, or CSS `contrast-color` will be better.
1717
// https://github.com/color-js/color.js/blob/eb7b53f7a13bb716ec8b28c7a56f052cd599acd9/src/contrast/APCA.js#L42
18-
export function contrastColor(backgroundColor: ColorInput) {
18+
export function contrastColor(backgroundColor: ColorInput): string {
1919
return useLightText(backgroundColor) ? '#fff' : '#000';
2020
}
2121

22-
function resolveColors(obj: Record<string, string>) {
22+
function resolveColors(obj: Record<string, string>): Record<string, string> {
2323
const styles = window.getComputedStyle(document.documentElement);
2424
const getColor = (name: string) => styles.getPropertyValue(name).trim();
2525
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, getColor(value)]));

web_src/js/utils/dom.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,8 @@ export function initSubmitEventPolyfill() {
266266
/**
267267
* Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
268268
* Note: This function doesn't account for all possible visibility scenarios.
269-
* @param {HTMLElement} element The element to check.
270-
* @returns {boolean} True if the element is visible.
271269
*/
272-
export function isElemVisible(element: HTMLElement) {
270+
export function isElemVisible(element: HTMLElement): boolean {
273271
if (!element) return false;
274272

275273
return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);

web_src/js/utils/image.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
export async function pngChunks(blob) {
1+
type PngChunk = {
2+
name: string,
3+
data: Uint8Array,
4+
}
5+
6+
export async function pngChunks(blob: Blob): Promise<PngChunk[]> {
27
const uint8arr = new Uint8Array(await blob.arrayBuffer());
3-
const chunks = [];
8+
const chunks: PngChunk[] = [];
49
if (uint8arr.length < 12) return chunks;
510
const view = new DataView(uint8arr.buffer);
611
if (view.getBigUint64(0) !== 9894494448401390090n) return chunks;
@@ -19,9 +24,14 @@ export async function pngChunks(blob) {
1924
return chunks;
2025
}
2126

27+
type ImageInfo = {
28+
width?: number,
29+
dppx?: number,
30+
}
31+
2232
// decode a image and try to obtain width and dppx. It will never throw but instead
2333
// return default values.
24-
export async function imageInfo(blob) {
34+
export async function imageInfo(blob: Blob): Promise<ImageInfo> {
2535
let width = 0, dppx = 1; // dppx: 1 dot per pixel for non-HiDPI screens
2636

2737
if (blob.type === 'image/png') { // only png is supported currently

0 commit comments

Comments
 (0)