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:
parent
d34cabacb8
commit
9f7bd38b61
|
@ -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
|
|
@ -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/",
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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/**/*"
|
||||
],
|
||||
}
|
Loading…
Reference in New Issue