test/static-code: add support for typescript

Add the typescript compiler to our devel dependencies and run it from
`test/static-code`.  We add an initial cockpit.d.ts and a tsconfig.json
which will get `tsc` to report errors against any `.ts` or `.tsx` files.
This commit is contained in:
Allison Karlitskaya 2024-04-10 10:52:19 +02:00
parent d34cabacb8
commit 9f7bd38b61
6 changed files with 260 additions and 5 deletions

View File

@ -56,6 +56,19 @@
"require": false,
"module": false
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"plugins": [
"@typescript-eslint"
],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"]
}
}
],
"settings": {
"react": {
"version": "detect"

@ -1 +1 @@
Subproject commit 227402e77a575750d30a621ef937160aee1a955b
Subproject commit caaed7f1413e1da064ceca94bd9ed418b33f6bed

View File

@ -25,6 +25,10 @@
"xterm-addon-canvas": "0.5.0"
},
"devDependencies": {
"@types/deep-equal": "1.0.4",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"@typescript-eslint/eslint-plugin": "7.4.0",
"argparse": "2.0.1",
"chrome-remote-interface": "0.33.0",
"esbuild": "0.20.2",
@ -53,7 +57,8 @@
"stylelint-config-standard": "36.0.0",
"stylelint-config-standard-scss": "13.1.0",
"stylelint-formatter-pretty": "4.0.0",
"stylelint-use-logical-spec": "5.0.1"
"stylelint-use-logical-spec": "5.0.1",
"typescript": "^5.3.3"
},
"scripts": {
"eslint": "eslint --ext .js --ext .jsx pkg/ test/common/",

200
pkg/lib/cockpit.d.ts vendored Normal file
View File

@ -0,0 +1,200 @@
/* This file is part of Cockpit.
*
* Copyright (C) 2024 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare module 'cockpit' {
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
type JsonObject = Record<string, JsonValue>;
class BasicError {
problem: string;
message: string;
toString(): string;
}
/* === Events mix-in ========================= */
interface EventMap {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[_: string]: (...args: any[]) => void;
}
type EventListener<E extends (...args: unknown[]) => void> =
(event: CustomEvent<Parameters<E>>, ...args: Parameters<E>) => void;
interface EventSource<EM extends EventMap> {
addEventListener<E extends keyof EM>(event: E, listener: EventListener<EM[E]>): void;
removeEventListener<E extends keyof EM>(event: E, listener: EventListener<EM[E]>): void;
dispatchEvent<E extends keyof EM>(event: E, ...args: Parameters<EM[E]>): void;
}
interface CockpitEvents extends EventMap {
locationchanged(): void;
visibilitychange(): void;
}
function addEventListener<E extends keyof CockpitEvents>(
event: E, listener: EventListener<CockpitEvents[E]>
): void;
function removeEventListener<E extends keyof CockpitEvents>(
event: E, listener: EventListener<CockpitEvents[E]>
): void;
interface ChangedEvents {
changed(): void;
}
/* === Channel =============================== */
interface ControlMessage extends JsonObject {
command: string;
}
interface ChannelEvents<T> extends EventMap {
control(options: JsonObject): void;
ready(options: JsonObject): void;
close(options: JsonObject): void;
message(data: T): void;
}
interface Channel<T> extends EventSource<ChannelEvents<T>> {
id: string | null;
binary: boolean;
options: JsonObject;
valid: boolean;
send(data: T): void;
control(options: ControlMessage): void;
wait(): Promise<void>;
close(options?: JsonObject): void;
}
interface ChannelOptions {
payload: string;
superuser?: string;
[_: string]: JsonValue | undefined;
}
function channel(options: ChannelOptions & { binary?: false; }): Channel<string>;
function channel(options: ChannelOptions & { binary: true; }): Channel<Uint8Array>;
/* === cockpit.location ========================== */
interface Location {
url_root: string;
options: { [name: string]: string | Array<string> };
path: Array<string>;
href: string;
go(path: Location | string, options?: { [key: string]: string }): void;
replace(path: Location | string, options?: { [key: string]: string }): void;
}
export const location: Location;
/* === cockpit.dbus ========================== */
interface DBusProxyEvents extends EventMap {
changed(changes: { [property: string]: unknown }): void;
}
interface DBusProxy extends EventSource<DBusProxyEvents> {
valid: boolean;
[property: string]: unknown;
}
interface DBusOptions {
bus?: string;
address?: string;
superuser?: "require" | "try";
track?: boolean;
}
interface DBusClient {
readonly unique_name: string;
readonly options: DBusOptions;
proxy(interface: string, path: string, options?: { watch?: boolean }): DBusProxy;
close(): void;
}
function dbus(name: string | null, options?: DBusOptions): DBusClient;
/* === cockpit.file ========================== */
interface FileSyntaxObject<T, B> {
parse(content: B): T;
stringify(content: T): B;
}
type FileTag = string;
type FileWatchCallback<T> = (data: T | null, tag: FileTag | null, error: BasicError | null) => void;
interface FileWatchHandle {
remove(): void;
}
interface FileHandle<T> {
read(): Promise<T>;
replace(content: T): Promise<FileTag>;
watch(callback: FileWatchCallback<T>, options?: { read?: boolean }): FileWatchHandle;
modify(callback: (data: T) => T): Promise<[T, FileTag]>;
close(): void;
path: string;
}
type FileOpenOptions = {
max_read_size?: number;
superuser?: string;
};
function file(
path: string,
options?: FileOpenOptions & { binary?: false; syntax?: undefined; }
): FileHandle<string>;
function file(
path: string,
options: FileOpenOptions & { binary: true; syntax?: undefined; }
): FileHandle<Uint8Array>;
function file<T>(
path: string,
options: FileOpenOptions & { binary?: false; syntax: FileSyntaxObject<T, string>; }
): FileHandle<T>;
function file<T>(
path: string,
options: FileOpenOptions & { binary: true; syntax: FileSyntaxObject<T, Uint8Array>; }
): FileHandle<T>;
/* === cockpit.user ========================== */
type UserInfo = {
id: number;
name: string;
full_name: string;
groups: Array<string>;
home: string;
shell: string;
};
export function user(): Promise<UserInfo>;
/* === String helpers ======================== */
function gettext(message: string): string;
function gettext(context: string, message?: string): string;
function ngettext(message1: string, messageN: string, n: number): string;
function ngettext(context: string, message1: string, messageN: string, n: number): string;
function format_bytes(n: number): string;
function format(format_string: string, ...args: unknown[]): string;
}

View File

@ -81,7 +81,16 @@ test_js_translatable_strings() {
if [ "${WITH_PARTIAL_TREE:-0}" = 0 ]; then
test_eslint() {
test -x node_modules/.bin/eslint -a -x /usr/bin/node || skip 'no eslint'
find_scripts 'node' '*.js' '*.jsx' | xargs -0 node_modules/.bin/eslint
find_scripts 'node' '*.[jt]s' '*.[jt]sx' | xargs -0 node_modules/.bin/eslint
}
test_typescript() {
test -x node_modules/.bin/tsc -a -x /usr/bin/node || skip 'no tsc'
# https://github.com/microsoft/TypeScript/issues/30511
# We can't tell tsc to ignore the .d.ts in node_modules/ and check our
# own cockpit.d.ts, so we do two separate invocations as a workaround:
node_modules/.bin/tsc --typeRoots /dev/null --strict pkg/lib/cockpit.d.ts
node_modules/.bin/tsc --checkJs false --skipLibCheck
}
fi
@ -108,9 +117,9 @@ test_unsafe_security_policy() {
}
test_json_verify() {
# Check all JSON files for validity
# Check (almost) all JSON files for validity
git ls-files -z '*.json' | while read -d '' filename; do
git ls-files -z '*.json' | grep -zv ^tsconfig | while read -d '' filename; do
python3 -m json.tool "${filename}" /dev/null 2>&1 | sed "s@^@${filename}: @"
done
}

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
/*
* Config for `tsc` and `tsserver`, currently only used for typechecking.
* Our main build is via `esbuild` which can handle `.ts`, but doesn't do any
* checking on its own. To typecheck, run `tsc`.
*
* Extended JSON comments and trailing commas are good here.
*/
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"baseUrl": "./pkg/lib",
"esModuleInterop": true,
"jsx": "react",
"lib": [
"dom",
"es2020"
],
"noEmit": true, // we only use `tsc` for type checking
"skipLibCheck": true, // don't check node_modules
"strict": true,
},
"include": [
"pkg/**/*"
],
}