Skip to content

Commit

Permalink
test/static-code: add support for typescript
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
allisonkarlitskaya committed Apr 10, 2024
1 parent d34caba commit 9f7bd38
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 5 deletions.
13 changes: 13 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion node_modules
Submodule node_modules updated 3244 files
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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/",
Expand Down
200 changes: 200 additions & 0 deletions pkg/lib/cockpit.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
15 changes: 12 additions & 3 deletions test/static-code
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
Expand Down
28 changes: 28 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -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/**/*"
],
}

0 comments on commit 9f7bd38

Please sign in to comment.