diff --git a/pyscriptjs/package-lock.json b/pyscriptjs/package-lock.json index 1f81b3e6be1..bdb741ed81d 100644 --- a/pyscriptjs/package-lock.json +++ b/pyscriptjs/package-lock.json @@ -22,6 +22,7 @@ "@rollup/plugin-legacy": "2.2.0", "@rollup/plugin-node-resolve": "14.1.0", "@rollup/plugin-typescript": "8.5.0", + "@types/codemirror": "^5.60.5", "@types/jest": "29.1.2", "@types/node": "18.8.3", "@typescript-eslint/eslint-plugin": "5.39.0", @@ -1510,6 +1511,15 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/codemirror": { + "version": "5.60.5", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz", + "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -1628,6 +1638,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -7364,6 +7383,15 @@ "@babel/types": "^7.3.0" } }, + "@types/codemirror": { + "version": "5.60.5", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.5.tgz", + "integrity": "sha512-TiECZmm8St5YxjFUp64LK0c8WU5bxMDt9YaAek1UqUb9swrSCoJhh92fWu1p3mTEqlHjhB5sY7OFBhWroJXZVg==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -7482,6 +7510,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", diff --git a/pyscriptjs/package.json b/pyscriptjs/package.json index 22192398c99..b2f456339d3 100644 --- a/pyscriptjs/package.json +++ b/pyscriptjs/package.json @@ -20,6 +20,7 @@ "@rollup/plugin-legacy": "2.2.0", "@rollup/plugin-node-resolve": "14.1.0", "@rollup/plugin-typescript": "8.5.0", + "@types/codemirror": "^5.60.5", "@types/jest": "29.1.2", "@types/node": "18.8.3", "@typescript-eslint/eslint-plugin": "5.39.0", diff --git a/pyscriptjs/src/components/pyrepl.ts b/pyscriptjs/src/components/pyrepl.ts index bd969cc3177..2ef5036bbf8 100644 --- a/pyscriptjs/src/components/pyrepl.ts +++ b/pyscriptjs/src/components/pyrepl.ts @@ -1,7 +1,7 @@ import { basicSetup, EditorView } from 'codemirror'; import { python } from '@codemirror/lang-python'; import { indentUnit } from '@codemirror/language' -import { Compartment, StateCommand } from '@codemirror/state'; +import { Compartment } from '@codemirror/state'; import { keymap } from '@codemirror/view'; import { defaultKeymap } from '@codemirror/commands'; import { oneDarkTheme } from '@codemirror/theme-one-dark'; diff --git a/pyscriptjs/src/components/pytitle.ts b/pyscriptjs/src/components/pytitle.ts index c4b63723220..5cd21fe77da 100644 --- a/pyscriptjs/src/components/pytitle.ts +++ b/pyscriptjs/src/components/pytitle.ts @@ -1,4 +1,4 @@ -import { addClasses, htmlDecode, ensureUniqueId } from '../utils'; +import { addClasses, htmlDecode } from '../utils'; export class PyTitle extends HTMLElement { widths: string[]; diff --git a/pyscriptjs/src/components/pywidget.ts b/pyscriptjs/src/components/pywidget.ts index 716e6d5acb3..abe0e0b8098 100644 --- a/pyscriptjs/src/components/pywidget.ts +++ b/pyscriptjs/src/components/pywidget.ts @@ -1,4 +1,5 @@ import type { Runtime } from '../runtime'; +import type {PyProxy} from "pyodide" import { getLogger } from '../logger'; const logger = getLogger('py-register-widget'); @@ -12,7 +13,7 @@ function createWidget(runtime: Runtime, name: string, code: string, klass: strin name: string = name; klass: string = klass; code: string = code; - proxy: any; + proxy: PyProxy; proxyClass: any; constructor() { @@ -38,7 +39,9 @@ function createWidget(runtime: Runtime, name: string, code: string, klass: strin runtime.globals.set(this.id, this.proxy); } } + /* eslint-disable @typescript-eslint/no-unused-vars */ const xPyWidget = customElements.define(name, CustomWidget); + /* eslint-enable @typescript-eslint/no-unused-vars */ } export function make_PyWidget(runtime: Runtime) { diff --git a/pyscriptjs/src/logger.ts b/pyscriptjs/src/logger.ts index 5269685f4e4..63d945c3d5a 100644 --- a/pyscriptjs/src/logger.ts +++ b/pyscriptjs/src/logger.ts @@ -24,10 +24,10 @@ */ interface Logger { - debug(message: string, ...args: any[]): void; - info(message: string, ...args: any[]): void; - warn(message: string, ...args: any[]): void; - error(message: string, ...args: any[]): void; + debug(message: string, ...args: unknown[]): void; + info(message: string, ...args: unknown[]): void; + warn(message: string, ...args: unknown[]): void; + error(message: string, ...args: unknown[]): void; } const _cache = new Map(); @@ -46,7 +46,7 @@ function _makeLogger(prefix: string): Logger { function make(level: string) { const out_fn = console[level].bind(console); - function fn(fmt: string, ...args: any[]) { + function fn(fmt: string, ...args: unknown[]) { out_fn(prefix + fmt, ...args); } return fn diff --git a/pyscriptjs/src/main.ts b/pyscriptjs/src/main.ts index 97e453d3866..b3c74bfb0e4 100644 --- a/pyscriptjs/src/main.ts +++ b/pyscriptjs/src/main.ts @@ -10,6 +10,11 @@ import { getLogger } from './logger'; import { handleFetchError, showError, globalExport } from './utils' import { createCustomElements } from './components/elements'; +type ImportType = { [key: string]: unknown } +type ImportMapType = { + imports: ImportType | null +} + const logger = getLogger('pyscript/main'); @@ -69,7 +74,7 @@ class PyScriptApp { // and show a big error. PRs welcome :) logger.info('searching for '); const elements = document.getElementsByTagName('py-config'); - let el = null; + let el: Element | null = null; if (elements.length > 0) el = elements[0]; if (elements.length >= 2) { @@ -188,7 +193,7 @@ class PyScriptApp { // lifecycle (7) executeScripts(runtime: Runtime) { - this.register_importmap(runtime); + void this.register_importmap(runtime); this.PyScript = make_PyScript(runtime); customElements.define('py-script', this.PyScript); } @@ -207,9 +212,9 @@ class PyScriptApp { // inside py-script. It's also unclear whether we want to wait or not // (or maybe only wait only if we do an actual 'import'?) for (const node of document.querySelectorAll("script[type='importmap']")) { - const importmap = (() => { + const importmap: ImportMapType = (() => { try { - return JSON.parse(node.textContent); + return JSON.parse(node.textContent) as ImportMapType; } catch { return null; } @@ -224,7 +229,7 @@ class PyScriptApp { try { // XXX: pyodide doesn't like Module(), failing with // "can't read 'name' of undefined" at import time - exports = { ...(await import(url)) }; + exports = { ...(await import(url)) } as object; } catch { logger.warn(`failed to fetch '${url}' for '${name}'`); continue; diff --git a/pyscriptjs/src/pyconfig.ts b/pyscriptjs/src/pyconfig.ts index 9bc34e6db00..ed6522b4d58 100644 --- a/pyscriptjs/src/pyconfig.ts +++ b/pyscriptjs/src/pyconfig.ts @@ -56,8 +56,8 @@ export const defaultConfig: AppConfig = { export function loadConfigFromElement(el: Element): AppConfig { - let srcConfig; - let inlineConfig; + let srcConfig: AppConfig; + let inlineConfig: AppConfig; if (el === null) { srcConfig = {}; inlineConfig = {}; @@ -127,7 +127,7 @@ function mergeConfig(inlineConfig: AppConfig, externalConfig: AppConfig): AppCon for (const keyType in allKeys) { - const keys = allKeys[keyType]; + const keys: string[] = allKeys[keyType]; keys.forEach(function(item: string){ if (keyType === "boolean") { @@ -199,14 +199,14 @@ function validateConfig(configText: string, configType = "toml") { for (const keyType in allKeys) { - const keys = allKeys[keyType]; + const keys: string[] = allKeys[keyType]; keys.forEach(function(item: string){ if (validateParamInConfig(item, keyType, config)) { if (item === "runtimes") { finalConfig[item] = []; - const runtimes = config[item]; + const runtimes = config[item] as object[]; runtimes.forEach(function(eachRuntime: object){ const runtimeConfig: object = {}; for (const eachRuntimeParam in eachRuntime) diff --git a/pyscriptjs/src/pyexec.ts b/pyscriptjs/src/pyexec.ts index e497d88e6e3..a7613b5c775 100644 --- a/pyscriptjs/src/pyexec.ts +++ b/pyscriptjs/src/pyexec.ts @@ -1,5 +1,5 @@ import { getLogger } from './logger'; -import { ensureUniqueId, addClasses } from './utils'; +import { ensureUniqueId } from './utils'; import type { Runtime } from './runtime'; const logger = getLogger('pyexec'); diff --git a/pyscriptjs/src/pyodide.ts b/pyscriptjs/src/pyodide.ts index e11b101809f..4d87ac4e2c1 100644 --- a/pyscriptjs/src/pyodide.ts +++ b/pyscriptjs/src/pyodide.ts @@ -1,6 +1,6 @@ import { Runtime } from './runtime'; import { getLogger } from './logger'; -import type { loadPyodide as loadPyodideDeclaration, PyodideInterface } from 'pyodide'; +import type { loadPyodide as loadPyodideDeclaration, PyodideInterface, PyProxy } from 'pyodide'; // eslint-disable-next-line // @ts-ignore import pyscript from './python/pyscript.py'; @@ -10,12 +10,17 @@ declare const loadPyodide: typeof loadPyodideDeclaration; const logger = getLogger('pyscript/pyodide'); +interface Micropip { + install: (packageName: string | string[]) => Promise; + destroy: () => void; +} + export class PyodideRuntime extends Runtime { src: string; name?: string; lang?: string; interpreter: PyodideInterface; - globals: any; + globals: PyProxy; constructor( config: AppConfig, @@ -83,7 +88,7 @@ export class PyodideRuntime extends Runtime { async installPackage(package_name: string | string[]): Promise { if (package_name.length > 0) { logger.info(`micropip install ${package_name.toString()}`); - const micropip = this.globals.get('micropip'); + const micropip = this.globals.get('micropip') as Micropip; await micropip.install(package_name); micropip.destroy(); } diff --git a/pyscriptjs/src/runtime.ts b/pyscriptjs/src/runtime.ts index 37b560eeac5..448656f480c 100644 --- a/pyscriptjs/src/runtime.ts +++ b/pyscriptjs/src/runtime.ts @@ -1,5 +1,5 @@ import type { AppConfig } from './pyconfig'; -import type { PyodideInterface } from 'pyodide'; +import type { PyodideInterface, PyProxy } from 'pyodide'; import { getLogger } from './logger'; const logger = getLogger('pyscript/runtime'); @@ -35,7 +35,7 @@ export abstract class Runtime extends Object { /** * global symbols table for the underlying interpreter. * */ - abstract globals: any; + abstract globals: PyProxy; constructor(config: AppConfig) { super(); @@ -54,7 +54,7 @@ export abstract class Runtime extends Object { * (asynchronously) which can call its own API behind the scenes. * Python exceptions are turned into JS exceptions. * */ - abstract run(code: string): Promise; + abstract run(code: string): Promise; /** * Same as run, but Python exceptions are not propagated: instead, they @@ -63,9 +63,10 @@ export abstract class Runtime extends Object { * This is a bad API and should be killed/refactored/changed eventually, * but for now we have code which relies on it. * */ - async runButDontRaise(code: string): Promise { + async runButDontRaise(code: string): Promise { return this.run(code).catch(err => { - logger.error(err); + const error = err as Error + logger.error("Error:", error); }); } diff --git a/pyscriptjs/src/utils.ts b/pyscriptjs/src/utils.ts index 7458591b182..8f591ce3e20 100644 --- a/pyscriptjs/src/utils.ts +++ b/pyscriptjs/src/utils.ts @@ -37,7 +37,7 @@ export function ltrim(code: string): string { let _uniqueIdCounter = 0; export function ensureUniqueId(el: HTMLElement) { if (el.id === "") - el.id = "py-internal-" + _uniqueIdCounter++; + el.id = `py-internal-${_uniqueIdCounter++}`; } /* @@ -94,7 +94,7 @@ export function inJest(): boolean { return typeof process === 'object' && process.env.JEST_WORKER_ID !== undefined; } -export function globalExport(name: string, obj: any) { +export function globalExport(name: string, obj: object) { // attach the given object to the global object, so that it is globally // visible everywhere. Should be used very sparingly! diff --git a/pyscriptjs/tests/unit/utils.test.ts b/pyscriptjs/tests/unit/utils.test.ts new file mode 100644 index 00000000000..0776f331566 --- /dev/null +++ b/pyscriptjs/tests/unit/utils.test.ts @@ -0,0 +1,34 @@ +import { jest } from "@jest/globals" +import { ensureUniqueId } from "../../src/utils" + +describe("Utils", () => { + + let element: HTMLElement; + + beforeEach(() => { + element = document.createElement("div"); + }) + + it("ensureUniqueId sets unique id on element", async () => { + expect(element.id).toBe("") + + ensureUniqueId(element) + + expect(element.id).toBe("py-internal-0") + }) + + it("ensureUniqueId sets unique id with increasing counter", async () => { + const secondElement = document.createElement("div") + + expect(element.id).toBe("") + expect(secondElement.id).toBe("") + + ensureUniqueId(element) + ensureUniqueId(secondElement) + + // The counter will have been incremented on + // the previous test, make sure it keeps increasing + expect(element.id).toBe("py-internal-1") + expect(secondElement.id).toBe("py-internal-2") + }) +})