Skip to content

Commit

Permalink
Remove TypePanel instantiation to make it mandatory to instantiate …
Browse files Browse the repository at this point in the history
…`TypePanel` manually (#198)

* Remove TypePanel instantiation

* Refactor some functions from `options.js` into `TypePanel` for proper multi-context support in same page

* Fix test_runtime.js by adding `clearObject` back

* Fix ESLint

* Nicely stack multi-contexts via `TypePanel.divAll`

* Fix example in repl/test-divide.worker.js for worker testing

* repl/index.js: Instantiate TypePanel for default REPL content
  • Loading branch information
kungfooman authored Jul 22, 2024
1 parent 71d61e2 commit 44d965a
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 51 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ module.exports = {
"jsdoc/require-property-description": "error",
"jsdoc/require-property-name": "error",
"jsdoc/require-property-type": "error",
"jsdoc/require-returns": "error",
// https://github.com/gajus/eslint-plugin-jsdoc/issues/503
"jsdoc/require-returns": ["error", {checkGetters: false}],
"jsdoc/require-returns-check": "error",
"jsdoc/require-returns-description": "error",
"jsdoc/require-returns-type": "error",
Expand Down
3 changes: 3 additions & 0 deletions repl/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ const aceEditorLeft = setupAce(
// write result to right editor
// Tip: open F12/DevTools to see errors and warnings
// Press Shift-Enter in right editor to eval result.
import {TypePanel} from '@runtime-type-inspector/runtime';
/**
* @param {number} a
* @param {number} b
Expand All @@ -451,6 +452,8 @@ const arr = [10_20];
const [a, b] = arr;
const ret = add(a, b);
console.log("ret", ret);
const typePanel = new TypePanel();
Object.assign(window, {add, typePanel});
`,
//editor => insertTypes(),
editor => runAction(),
Expand Down
3 changes: 2 additions & 1 deletion repl/test-divide.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ f();
setInterval(f, 2000); // Test spam mode

/*
import {typePanel} from '@runtime-type-inspector/runtime';
import {TypePanel} from '@runtime-type-inspector/runtime';
import {WorkerWithImportMapViaBedfordsShim} from 'worker-with-import-map';
// console.log("WorkerWithImportMapViaBedfordsShim", WorkerWithImportMapViaBedfordsShim);
// const url = './test-add.worker.js';
Expand All @@ -32,6 +32,7 @@ const worker = new WorkerWithImportMapViaBedfordsShim(url, {
importMap: 'inherit'
});
console.log("worker", worker);
const typePanel = new TypePanel();
worker.addEventListener('message', (e) => {
// console.log('addEventListener message', e.data);
if (e.data.type !== 'rti') {
Expand Down
58 changes: 32 additions & 26 deletions src-runtime/TypePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ function isEnabled() {
return tmp === null || tmp === 'true';
}
class TypePanel {
/** @type {HTMLDivElement | null} */
static divAll = null;
div = document.createElement('div');
inputEnable = document.createElement('input');
spanErrors = document.createElement('span');
Expand All @@ -60,15 +62,19 @@ class TypePanel {
buttonSaveState = document.createElement('button');
buttonClear = document.createElement('button');
warnedTable = createTable();
/** @type {Record<string, import('./Warning.js').Warning>} */
warnings = {};
constructor() {
const {
div, inputEnable, spanErrors, span, select, option_spam, option_once, option_never,
buttonHide, buttonLoadState, buttonSaveState, buttonClear, warnedTable,
} = this;
div.style.position = "absolute";
div.style.bottom = "0px";
div.style.right = "0px";
div.style.zIndex = "10";
TypePanel.divAll ??= document.createElement('div');
const {divAll} = TypePanel;
divAll.style.position = "absolute";
divAll.style.bottom = "0px";
divAll.style.right = "0px";
divAll.style.zIndex = "10";
niceDiv(div);
inputEnable.checked = isEnabled();
inputEnable.type = "checkbox";
Expand Down Expand Up @@ -111,7 +117,8 @@ class TypePanel {
div.append(inputEnable, spanErrors, span, select, buttonHide, buttonLoadState, buttonSaveState, buttonClear, warnedTable);
div.style.maxHeight = '200px';
div.style.overflow = 'scroll';
const finalFunc = () => document.body.append(div);
divAll.append(div);
const finalFunc = () => document.body.append(divAll);
// Add our <div> to <body> when possible
if (document.readyState === "complete") {
finalFunc();
Expand Down Expand Up @@ -150,6 +157,9 @@ class TypePanel {
localStorage.setItem('rti-enabled', 'true');
this.sendEnabledDisabledStateToWorker();
}
report() {
console.table(this.warnings);
}
lastKnownCountWithStatus = '0-true';
sendEnabledDisabledStateToWorker() {
// Problem: First time the worker may not even have started and `this.eventSources.size === 0`
Expand All @@ -171,11 +181,11 @@ class TypePanel {
});
}
clear() {
const {warned} = options;
for (const key in warned) {
const warning = warned[key];
const {warnings} = this;
for (const key in warnings) {
const warning = warnings[key];
warning.tr.remove();
delete warned[key];
delete warnings[key];
}
}
get state() {
Expand All @@ -184,8 +194,8 @@ class TypePanel {
/**
* @todo I would rather save loc/name because it's less likely to change in future... to keep state URL's alive
*/
for (const key in options.warned) {
const e = options.warned[key];
for (const key in this.warnings) {
const e = this.warnings[key];
const {state} = e;
if (state) {
const {loc, name} = e;
Expand All @@ -210,12 +220,13 @@ class TypePanel {
if (!json) {
return false;
}
const {warnings} = this;
for (const e of json) {
const {loc, name, state} = e;
/** @type {Warning|undefined} */
let foundWarning;
for (const key in options.warned) {
const warning = options.warned[key];
for (const key in warnings) {
const warning = warnings[key];
if (warning.loc === loc && warning.name === name) {
foundWarning = warning;
break;
Expand All @@ -225,7 +236,7 @@ class TypePanel {
if (!foundWarning) {
foundWarning = new Warning('msg', 'value', 'expect', loc, name);
this.warnedTable?.append(foundWarning.tr);
options.warned[`${loc}-${name}`] = foundWarning;
warnings[`${loc}-${name}`] = foundWarning;
}
foundWarning.state = state;
}
Expand All @@ -244,8 +255,9 @@ class TypePanel {
get eventSources() {
/** @type {Set<EventTarget | MessageEventSource>} */
const eventSources = new Set();
for (const key in options.warned) {
const warning = options.warned[key];
const {warnings} = this;
for (const key in warnings) {
const warning = warnings[key];
if (warning.eventSource) {
eventSources.add(warning.eventSource);
}
Expand All @@ -259,11 +271,11 @@ class TypePanel {
const {value, expect, loc, name, valueToString, strings, extras = [], key} = event.data;
const msg = `${loc}> The '${name}' argument has an invalid type. ${strings.join(' ')}`.trim();
this.updateErrorCount();
let warnObj = options.warned[key];
let warnObj = this.warnings[key];
if (!warnObj) {
warnObj = new Warning(msg, value, expect, loc, name);
this.warnedTable?.append(warnObj.tr);
options.warned[key] = warnObj;
this.warnings[key] = warnObj;
}
warnObj.event = event;
warnObj.hits++;
Expand All @@ -278,7 +290,7 @@ class TypePanel {
*/
deleteBreakpoint(event) {
const {key} = event.data;
const warnObj = options.warned[key];
const warnObj = this.warnings[key];
if (!warnObj) {
console.warn("warnObj doesn't exist", {key});
return;
Expand All @@ -301,10 +313,4 @@ class TypePanel {
this.sendEnabledDisabledStateToWorker();
}
}
/** @type {TypePanel | undefined} */
let typePanel;
// @todo create UI explicitly programmatically inside e.g. src/index.rti.js of the projects using it.
if (typeof importScripts === 'undefined') {
typePanel = new TypePanel();
}
export {niceDiv, TypePanel, typePanel};
export {niceDiv, TypePanel};
9 changes: 9 additions & 0 deletions src-runtime/Warning.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class Warning {
key: `${this.loc}-${this.name}`
});
}
/**
* Trigger `debugger;` next time this error is hit.
*/
get dbg() {
return this._dbg;
}
Expand Down Expand Up @@ -100,13 +103,19 @@ class Warning {
// key: `${this.loc}-${this.name}`
// });
}
/**
* Prevent F12/DevTools spamming for errors that occur often, even in "spam" mode.
*/
get hidden() {
return this._hidden;
}
set hits(_) {
this._hits = _;
this.td_count.textContent = _ + '';
}
/**
* How often this error occured.
*/
get hits() {
return this._hits;
}
Expand Down
24 changes: 2 additions & 22 deletions src-runtime/options.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
/**
* @todo move into new Warning class, should be unused everywhere
* @typedef {object} TypeError
* @property {number} hits - How often this error occured.
* @property {HTMLTableRowElement} tr - The <tr>.
* @property {boolean} dbg - Trigger `debugger;` next time this error is hit.
* @property {boolean} hide - Prevent F12/DevTools spamming for errors that occur often,
* even in "spam" mode.
* @todo Move everything into TypePanel, since every panel/worker should have its own controls.
*/
const options = {
enabled: true,
Expand All @@ -16,21 +10,7 @@ const options = {
* Spam-mode basically retains the order, which mentally helps to figure out the actual issues.
*/
mode: 'spam',
/** @type {Record<string, import('./Warning.js').Warning>} */
warned: {},
logSuperfluousProperty: false,
count: 0,
};
function report() {
console.table(options.warned);
}
/**
* @param {Object<string, any>} obj - The object to clear.
*/
function clearObject(obj) {
Object.keys(obj).forEach(_ => delete obj[_]);
}
function reset() {
clearObject(options.warned);
}
export {report, clearObject, reset, options};
export {options};
7 changes: 6 additions & 1 deletion test_runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import {expandType } from './src-transpiler/expandType.js';
import {validateType } from './src-runtime/validateType.js';
import {validateUnion } from './src-runtime/validateUnion.js';
import {validateTuple } from './src-runtime/validateTuple.js';
import {clearObject } from './src-runtime/options.js';
import {typedefs } from './src-runtime/registerTypedef.js';
/**
* @param {Object<string, any>} obj - The object to clear.
*/
function clearObject(obj) {
Object.keys(obj).forEach(_ => delete obj[_]);
}
const warn = () => undefined;
// We expect all functions to return true.
const tests = [
Expand Down

0 comments on commit 44d965a

Please sign in to comment.