Skip to content

Commit

Permalink
Merge branch 'dev' into config-schema
Browse files Browse the repository at this point in the history
  • Loading branch information
arildm committed Jan 29, 2025
2 parents 2977997 + bfa3d5e commit 1908a11
Show file tree
Hide file tree
Showing 22 changed files with 214 additions and 86 deletions.
16 changes: 0 additions & 16 deletions .eslintrc.cjs

This file was deleted.

36 changes: 36 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pluginVue from "eslint-plugin-vue";
import {
defineConfigWithVueTs,
vueTsConfigs,
} from "@vue/eslint-config-typescript";
import skipFormatting from "@vue/eslint-config-prettier/skip-formatting";
import importPlugin from "eslint-plugin-import";

export default defineConfigWithVueTs(
{
ignores: ["node_modules", "dist", ".vscode"],
},

// https://github.com/vuejs/eslint-config-typescript?tab=readme-ov-file#minimal-setup
pluginVue.configs["flat/essential"],
vueTsConfigs.recommended,

// https://github.com/import-js/eslint-plugin-import

{
...importPlugin.flatConfigs.recommended,

settings: {
// Tell eslint-plugin-import to use eslint-import-resolver-typescript
"import/resolver": "typescript",
},
rules: {
// https://github.com/import-js/eslint-plugin-import
"import/order": ["warn"],
},
},

// Assume that a separate command is used for formatting
// https://github.com/vuejs/eslint-config-prettier?tab=readme-ov-file#use-separate-commands-for-linting-and-formatting
skipFormatting,
);
13 changes: 12 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
interface Window {
// Matomo queue
_paq?: (string | number | undefined)[][];

// Stuff exposed in dev
api?: import("@/api/api");
resourceStore?: import("pinia").Store;
util?: import("@/util");
}
declare const window: Window;

declare module "vue-matomo" {
// See https://github.com/AmazingDreams/vue-matomo/blob/master/src/utils.js

Expand Down Expand Up @@ -46,6 +57,6 @@ declare module "@cloudflare/json-schema-walker" {
schema: import("json-schema").JSONSchema7,
preFunc?: Visitor,
postFunc?: Visitor,
vocabulary?: any,
vocabulary?: unknown,
): void;
}
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "yarn typecheck && vite build",
"serve": "vite preview",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore --fix src",
"lint": "eslint",
"lintfix": "yarn lint --fix",
"format": "prettier . --write --ignore-path .gitignore",
"test": "vitest"
},
Expand Down Expand Up @@ -58,8 +59,9 @@
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.10.10",
"@types/react": "^19.0.8",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.3.0",
"eslint-config-prettier": "^10.0.1",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-vue": "^9.32.0",
"happy-dom": "^16.7.3",
Expand Down
6 changes: 3 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ const isHome = computed(() => route.path == "/");
refreshJwt();
if (import.meta.env.DEV) {
(window as any).api = api;
(window as any).resourceStore = resourceStore;
(window as any).util = util;
window.api = api;
window.resourceStore = resourceStore;
window.util = util;
}
</script>

Expand Down
4 changes: 2 additions & 2 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ function filesFormData(...files: File[]): FormData {
}

/** Handle an exception from an API call that may be encoded as Blob */
async function rethrowBlobError(error: any): Promise<never> {
if (error.response?.data instanceof Blob) {
async function rethrowBlobError(error: unknown): Promise<never> {
if (Axios.isAxiosError(error) && error.response?.data instanceof Blob) {
// Parse JSON and replace the blob
const text = await error.response.data.text();
error.response.data = JSON.parse(text) as MinkResponse;
Expand Down
4 changes: 3 additions & 1 deletion src/api/api.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { AxiosProgressEvent } from "axios";
import type { ByLang, SweEng } from "@/util.types";

/** Properties common to most backend responses */
export type MinkResponse<T extends { [k: string]: any } = {}> = T & {
export type MinkResponse<
T extends Record<string, unknown> = Record<string, unknown>,
> = T & {
status: "success" | "error";
return_code: string;
message: string;
Expand Down
21 changes: 13 additions & 8 deletions src/api/corpusConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,15 +226,15 @@ export function emptyConfig(): ConfigOptions {
* May throw all kinds of errors, the sky is the limit (:
*/
export function parseConfig(configYaml: string): ConfigOptions {
const config = Yaml.load(configYaml) as any;
const config = Yaml.load(configYaml) as unknown as Partial<SparvConfig>;

if (!config)
throw new TypeError(`Parsing config failed, returned "${config}"`);

// Throw specific errors if required parts are missing.
if (!config.import?.importer) throw new TypeError(`Importer setting missing`);
const format = (Object.keys(FORMATS) as FileFormat[]).find(
(ext) => FORMATS[ext as FileFormat] == config.import.importer,
(ext) => FORMATS[ext as FileFormat] == config.import?.importer,
);
if (!format)
throw new TypeError(`Unrecognized importer: "${config.import.importer}"`);
Expand All @@ -251,19 +251,24 @@ export function parseConfig(configYaml: string): ConfigOptions {
...emptyConfig(),
format,
name,
description: config.metadata.description,
description: config.metadata?.description,
textAnnotation: config.import.text_annotation,
sentenceSegmenter: config.segment?.sentence_segmenter,
};

// Identify annotations
const datetimeFrom = config.custom_annotations?.find(
(a: any) => a.params.out == "<text>:misc.datefrom",
)?.params.value;
(a) => a.params?.out == "<text>:misc.datefrom",
)?.params?.value;
const datetimeTo = config.custom_annotations?.find(
(a: any) => a.params.out == "<text>:misc.dateto",
)?.params.value;
if (datetimeFrom && datetimeTo)
(a) => a.params?.out == "<text>:misc.dateto",
)?.params?.value;
if (
datetimeFrom &&
typeof datetimeFrom == "string" &&
datetimeTo &&
typeof datetimeTo == "string"
)
options.annotations.datetime = { from: datetimeFrom, to: datetimeTo };

options.annotations.lexicalClasses = config.export?.annotations?.includes(
Expand Down
10 changes: 3 additions & 7 deletions src/api/sparvConfig.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type SparvConfig = {
segment: ConfigSegment;
export: ConfigExport;
dateformat?: ConfigDateformat;
custom_annotations?: ConfigCustomAnnotations[];
custom_annotations?: ConfigCustomAnnotation[];
korp?: ConfigKorp;
sbx_strix?: ConfigStrix;
};
Expand Down Expand Up @@ -40,13 +40,9 @@ type ConfigDateformat = {
datetime_informat?: string;
};

type ConfigCustomAnnotations = {
type ConfigCustomAnnotation = {
annotator: string;
params: {
out: string;
chunk: string;
value: string;
};
params?: Record<string, unknown>;
};

type ConfigKorp = {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/auth.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function useAuth() {
api.setJwt(jwtValue);

// Schedule next request shortly before expiration time.
refreshTimer && clearTimeout(refreshTimer);
if (refreshTimer) clearTimeout(refreshTimer);
if (payload.value?.exp) {
const timeoutMs = (payload.value.exp - 10) * 1000 - Date.now();
refreshTimer = setTimeout(refreshJwt, timeoutMs);
Expand Down
17 changes: 11 additions & 6 deletions src/auth/sbAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const JWT_URL: string = import.meta.env.VITE_JWT_URL || AUTH_URL + "jwt";
export type stringJwt = `${string}.${string}.${string}`;

export type JwtSb = {
header: any;
header: unknown;
payload: JwtSbPayload;
};

Expand Down Expand Up @@ -72,13 +72,18 @@ export function decodeJwt(jwt: stringJwt): JwtSb {
};
}

function assertValidPayload(payload: any): payload is JwtSbPayload {
function assertValidPayload(payload: unknown): payload is JwtSbPayload {
const isValid =
payload instanceof Object &&
"scope" in payload &&
payload?.scope &&
payload.levels &&
payload.levels.ADMIN &&
payload.levels.WRITE &&
payload.levels.READ;
"levels" in payload &&
payload.levels instanceof Object &&
["READ", "WRITE", "ADMIN"].every(
(level) =>
level in (payload.levels as object) &&
(payload.levels as Record<string, unknown>)[level],
);

if (!isValid)
throw new TypeError("Malformed jwt payload: " + JSON.stringify(payload));
Expand Down
2 changes: 1 addition & 1 deletion src/corpus/config/CorpusConfigCustom.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { AxiosError } from "axios";
import { PhPencilSimple, PhWarning } from "@phosphor-icons/vue";
import CorpusConfigCustomHelp from "./CorpusConfigCustomHelp.vue";
import useCorpusIdParam from "@/corpus/corpusIdParam.composable";
import useConfig from "@/corpus/config/config.composable";
import { useAuth } from "@/auth/auth.composable";
Expand All @@ -12,7 +13,6 @@ import useMessenger from "@/message/messenger.composable";
import SyntaxHighlight from "@/components/SyntaxHighlight.vue";
import PendingContent from "@/spin/PendingContent.vue";
import RouteButton from "@/components/RouteButton.vue";
import CorpusConfigCustomHelp from "./CorpusConfigCustomHelp.vue";
const corpusId = useCorpusIdParam();
const { config, uploadConfigRaw } = useConfig(corpusId);
Expand Down
12 changes: 6 additions & 6 deletions src/corpus/config/CorpusConfigCustomEdit.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<script setup lang="ts">
import Yaml from "js-yaml";
import { useAuth } from "@/auth/auth.composable";
import useConfig from "./config.composable";
import useCorpusIdParam from "../corpusIdParam.composable";
import { ref, watchEffect } from "vue";
import useMessenger from "@/message/messenger.composable";
import type { AxiosError } from "axios";
import { PhFileX, PhWarning } from "@phosphor-icons/vue";
import useCorpusIdParam from "../corpusIdParam.composable";
import useConfig from "./config.composable";
import CorpusConfigCustomHelp from "./CorpusConfigCustomHelp.vue";
import { useAuth } from "@/auth/auth.composable";
import useMessenger from "@/message/messenger.composable";
import type { MinkResponse } from "@/api/api.types";
import LayoutBox from "@/components/LayoutBox.vue";
import HelpBox from "@/components/HelpBox.vue";
import { PhFileX, PhWarning } from "@phosphor-icons/vue";
import PendingContent from "@/spin/PendingContent.vue";
import ActionButton from "@/components/ActionButton.vue";
import { getException } from "@/util";
import CorpusConfigCustomHelp from "./CorpusConfigCustomHelp.vue";
import RouteButton from "@/components/RouteButton.vue";
const corpusId = useCorpusIdParam();
Expand Down
2 changes: 1 addition & 1 deletion src/formkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function addAsteriskPlugin(node: FormKitNode) {

/** Validation rule to require that all or none of a group of fields are set. */
function onlyif(node: FormKitNode, othersComma: string) {
const parent = node.at("$parent") as FormKitNode<Record<string, any>>;
const parent = node.at("$parent") as FormKitNode<Record<string, unknown>>;
if (!parent?.value) {
console.error("onlyif rule missing parent");
return true;
Expand Down
4 changes: 2 additions & 2 deletions src/matomo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function useMatomo(): Matomo | undefined {
*/
export const MatomoProxy = new Proxy<Matomo>({} as Matomo, {
get:
<F extends keyof Matomo>(target: {}, prop: F) =>
<F extends keyof Matomo>(target: object, prop: F) =>
(...args: Parameters<Matomo[F]>) =>
(window as any)._paq?.push([prop, ...args]),
window._paq?.push([prop, ...args]),
});
6 changes: 3 additions & 3 deletions src/schema-form/JsonSchemaForm.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
export type JsonSchemaFormProps<D extends {}> = {
export type JsonSchemaFormProps<D extends object> = {
schema: StrictRJSFSchema;
data?: D;
uiSchema?: UiSchema;
onChange?: (event: { formData: D }, fieldId: string) => {};
onSubmit?: (event: { formData: D }) => {};
onChange?: (event: { formData: D }, fieldId: string) => void;
onSubmit?: (event: { formData: D }) => void;
};
</script>

Expand Down
2 changes: 1 addition & 1 deletion src/schema-form/theme/form-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import MoveDownButtonVue from "./MoveDownButton.vue";
import SubmitButtonVue from "./SubmitButton.vue";

// Rename and retype the Veaury converter to allow specifying return type better.
const toReact = <P = {}>(component: Component) =>
const toReact = <P>(component: Component) =>
applyPureVueInReact(component) as React.ComponentType<P>;

// https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields
Expand Down
2 changes: 1 addition & 1 deletion src/spin/spin.composable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { enarray } from "@/util";

const unsettled = ref<SpinItem[]>([]);

type SpinItem<T = any> = {
type SpinItem<T = unknown> = {
promise: Promise<T>;
token?: string;
};
Expand Down
4 changes: 3 additions & 1 deletion src/store/resource.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ export const useResourceStore = defineStore("resource", () => {
// forget the old state. The actual number doesn't really matter, as long as
// it's a new one.
const resourcesRef = useStorage("[email protected]", {});
const resources: Record<string, {} | Resource> = reactive(resourcesRef.value);
const resources: Record<string, object | Resource> = reactive(
resourcesRef.value,
);

const corpora = computed<Record<string, Partial<Corpus>>>(() =>
filterResources("corpus"),
Expand Down
18 changes: 9 additions & 9 deletions src/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ describe("getException", () => {
throw new EvalError("Leverpastej");
};
const exception = getException(f);
expect(exception.name).toBe("EvalError");
expect(exception.message).toBe("Leverpastej");
expect(exception).toBeInstanceOf(EvalError);
expect((exception as EvalError).name).toBe("EvalError");
expect((exception as EvalError).message).toBe("Leverpastej");
});
});

Expand Down Expand Up @@ -161,11 +162,10 @@ describe("unarray", () => {
test("empty list", () => {
expect(unarray([])).toBe(undefined);
});
test("empty arg"),
() => {
expect(unarray(null)).toBe(null);
expect(unarray(undefined)).toBe(undefined);
};
test("empty arg", () => {
expect(unarray(null)).toBe(null);
expect(unarray(undefined)).toBe(undefined);
});
});

describe("objsToDict", () => {
Expand All @@ -186,12 +186,12 @@ describe("objsToDict", () => {
expect(objsToDict(objs, "k", "v")).toEqual(dict);
});
test("missing key", () => {
// @ts-expect-error
// @ts-expect-error The key "k" is missing in the object
const dict = objsToDict([{ v: "A" }], "k", "v");
expect(dict).toEqual({ undefined: "A" });
});
test("missing value", () => {
// @ts-expect-error
// @ts-expect-error The key "v" is missing in the object
const dict = objsToDict([{ k: "a" }], "k", "v");
expect(dict).toEqual({ a: undefined });
});
Expand Down
Loading

0 comments on commit 1908a11

Please sign in to comment.