runtime-type-inspector 1.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.eslintrc.js +480 -0
  2. package/README.md +12 -4
  3. package/jsconfig.json +10 -0
  4. package/package.json +32 -4
  5. package/plugin-parcel/index.mjs +4 -0
  6. package/plugin-rollup/index.mjs +78 -0
  7. package/plugin-webpack/index.mjs +4 -0
  8. package/rollup.config.mjs +183 -0
  9. package/src-runtime/assertType.mjs +54 -0
  10. package/src-runtime/createDiv.mjs +69 -0
  11. package/src-runtime/index.mjs +21 -0
  12. package/src-runtime/makeJSDoc.mjs +40 -0
  13. package/src-runtime/registerClass.mjs +11 -0
  14. package/src-runtime/registerTypedef.mjs +12 -0
  15. package/src-runtime/typecheckDiv.mjs +3 -0
  16. package/src-runtime/typecheckEvery.mjs +21 -0
  17. package/src-runtime/typecheckOptions.mjs +60 -0
  18. package/src-runtime/typecheckTable.mjs +14 -0
  19. package/src-runtime/typecheckWarn.mjs +23 -0
  20. package/src-runtime/validateArray.mjs +26 -0
  21. package/src-runtime/validateDivision.mjs +31 -0
  22. package/src-runtime/validateMap.mjs +36 -0
  23. package/src-runtime/validateNumber.mjs +29 -0
  24. package/src-runtime/validateObject.mjs +43 -0
  25. package/src-runtime/validateRecord.mjs +31 -0
  26. package/src-runtime/validateSet.mjs +26 -0
  27. package/src-runtime/validateTuple.mjs +28 -0
  28. package/src-runtime/validateType.mjs +214 -0
  29. package/src-runtime/validateTypedef.mjs +18 -0
  30. package/src-runtime/validateUnion.mjs +19 -0
  31. package/src-runtime/youCanAddABreakpointHere.mjs +11 -0
  32. package/src-transpiler/Stringifier.mjs +1268 -0
  33. package/src-transpiler/StringifierWithTypeAssertions.mjs +478 -0
  34. package/src-transpiler/addTypeChecks.mjs +20 -0
  35. package/src-transpiler/ast2json.mjs +13 -0
  36. package/src-transpiler/ast2jsonForComparison.mjs +28 -0
  37. package/src-transpiler/code2ast2code.mjs +16 -0
  38. package/src-transpiler/expandType.mjs +187 -0
  39. package/src-transpiler/expandTypeDepFree.mjs +122 -0
  40. package/src-transpiler/extractNameAndOptionality.mjs +23 -0
  41. package/src-transpiler/index.mjs +13 -0
  42. package/src-transpiler/parseJSDoc.mjs +69 -0
  43. package/src-transpiler/parseJSDocSetter.mjs +16 -0
  44. package/src-transpiler/parseJSDocTypedef.mjs +69 -0
  45. package/src-transpiler/simplifyType.mjs +31 -0
  46. package/src-transpiler/stat.mjs +13 -0
  47. package/ui/babel-core.mjs +5 -0
  48. package/ui/babel-helper-plugin-utils.mjs +7 -0
  49. package/ui/babel-parser.mjs +8 -0
  50. package/ui/babel-plugin-syntax-typescript.mjs +58 -0
  51. package/ui/importmap.js +38 -0
  52. package/ui/index.html +30 -0
  53. package/ui/index.mjs +379 -0
  54. package/src/index.mjs +0 -1
@@ -0,0 +1,183 @@
1
+ import { execSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ // 1st party Rollup plugins
5
+ import { createFilter } from '@rollup/pluginutils';
6
+ import { babel } from '@rollup/plugin-babel';
7
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
8
+ import terser from '@rollup/plugin-terser';
9
+ // 3rd party Rollup plugins
10
+ import dts from 'rollup-plugin-dts';
11
+ import { visualizer } from 'rollup-plugin-visualizer';
12
+ import json from '@rollup/plugin-json';
13
+ //import { addTypeChecks } from "./src-transpiler/typeInserter.mjs";
14
+ //const { addTypeChecks } = await import("runtime-type-inspector/src-transpiler/typeInserter.mjs");
15
+ import commonjs from '@rollup/plugin-commonjs';
16
+ import { runtimeTypeInspector } from './plugin-rollup/index.mjs';
17
+ /** @typedef {import('rollup').RollupOptions} RollupOptions */
18
+ /** @typedef {import('rollup').Plugin} Plugin */
19
+ /** @typedef {import('rollup').OutputOptions} OutputOptions */
20
+ /** @typedef {import('@rollup/plugin-babel').RollupBabelInputPluginOptions} RollupBabelInputPluginOptions */
21
+ /**
22
+ * The ES5 options for babel(...) plugin.
23
+ *
24
+ * @param {string} buildType - Only 'debug' requires special handling so far.
25
+ * @returns {RollupBabelInputPluginOptions} The babel options.
26
+ */
27
+ const es5Options = buildType => ({
28
+ babelHelpers: 'bundled',
29
+ babelrc: false,
30
+ comments: buildType === 'debug' || buildType === 'rti',
31
+ compact: false,
32
+ minified: false,
33
+ presets: [
34
+ [
35
+ '@babel/preset-env', {
36
+ loose: !true,
37
+ modules: false,
38
+ targets: {
39
+ ie: '11'
40
+ }
41
+ }
42
+ ]
43
+ ]
44
+ });
45
+ /**
46
+ * The ES6 options for babel(...) plugin.
47
+ *
48
+ * @param {string} buildType - Only 'debug' requires special handling so far.
49
+ * @returns {RollupBabelInputPluginOptions} The babel options.
50
+ */
51
+ const moduleOptions = buildType => ({
52
+ babelHelpers: 'bundled',
53
+ babelrc: false,
54
+ comments: buildType === 'debug',
55
+ compact: false,
56
+ minified: false,
57
+ presets: [
58
+ [
59
+ '@babel/preset-env', {
60
+ bugfixes: true,
61
+ loose: true,
62
+ modules: false,
63
+ targets: {
64
+ esmodules: true
65
+ }
66
+ }
67
+ ]
68
+ ]
69
+ });
70
+ /**
71
+ * Build a target that rollup is supposed to build.
72
+ *
73
+ * @param {'debug'|'release'|'profiler'|'min'} buildType - The build type.
74
+ * @param {'es5'|'es6'} moduleFormat - The module format.
75
+ * @returns {RollupOptions} One rollup target.
76
+ */
77
+ function buildTarget(buildType, moduleFormat) {
78
+ const banner = {
79
+ debug: ' (DEBUG)',
80
+ release: ' (RELEASE)',
81
+ profiler: ' (PROFILE)',
82
+ min: ' (RELEASE)'
83
+ };
84
+ const outputPlugins = {
85
+ release: [],
86
+ min: [
87
+ terser()
88
+ ]
89
+ };
90
+ if (process.env.treemap) {
91
+ outputPlugins.min.push(visualizer({
92
+ filename: 'treemap.html',
93
+ brotliSize: true,
94
+ gzipSize: true
95
+ }));
96
+ }
97
+ if (process.env.treenet) {
98
+ outputPlugins.min.push(visualizer({
99
+ filename: 'treenet.html',
100
+ template: 'network'
101
+ }));
102
+ }
103
+ if (process.env.treesun) {
104
+ outputPlugins.min.push(visualizer({
105
+ filename: 'treesun.html',
106
+ template: 'sunburst'
107
+ }));
108
+ }
109
+ const name = 'rti-transpiler';
110
+ const outputFile = {
111
+ release: `build/${name}`,
112
+ debug: `build/${name}.dbg`,
113
+ profiler: `build/${name}.prf`,
114
+ min: `build/${name}.min`,
115
+ rti: `build/${name}.rti`,
116
+ };
117
+ const outputExtension = {
118
+ es5: '.js',
119
+ es6: '.mjs'
120
+ };
121
+ /** @type {OutputOptions} */
122
+ const outputOptions = {
123
+ plugins: outputPlugins[buildType || outputPlugins.release],
124
+ format: 'es',
125
+ indent: '\t',
126
+ sourcemap: false,
127
+ name: 'pc',
128
+ preserveModules: false,
129
+ file: `${outputFile[buildType]}${outputExtension[moduleFormat]}`
130
+ };
131
+ const babelOptions = {
132
+ es5: es5Options(buildType),
133
+ es6: moduleOptions(buildType)
134
+ };
135
+ const rootFile = 'src-transpiler/index.mjs';
136
+ console.log("buildType", buildType);
137
+ return {
138
+ input: rootFile,
139
+ output: outputOptions,
140
+ plugins: [
141
+ //runtimeTypeInspector(buildType === 'debug'),
142
+ babel(babelOptions[moduleFormat]),
143
+ //nodeResolve({
144
+ // browser: true
145
+ //}),
146
+ //commonjs(),
147
+ //json(),
148
+ ],
149
+ ///external: ['fs', 'path', 'url', 'sharp', 'onnxruntime-node', 'onnxruntime-web', 'stream/web'],
150
+ };
151
+ }
152
+ /** @type {RollupOptions} */
153
+ const target_types = {
154
+ input: 'types/index.d.ts',
155
+ output: [{
156
+ file: 'build/runtime-type-inspector.d.ts',
157
+ footer: 'export as namespace pc;',
158
+ format: 'es'
159
+ }],
160
+ plugins: [
161
+ dts()
162
+ ]
163
+ };
164
+ export default (args) => {
165
+ /** @type {RollupOptions[]} */
166
+ let targets = [];
167
+ const envTarget = process.env.target ? process.env.target.toLowerCase() : null;
168
+ if (envTarget === 'types') {
169
+ targets.push(target_types);
170
+ } else if (envTarget === 'extras') {
171
+ targets = targets.concat(target_extras);
172
+ } else {
173
+ ['release', 'debug', 'profiler', 'min', 'rti'].forEach((t) => {
174
+ ['es5', 'es6'].forEach((m) => {
175
+ console.log("envTarget", envTarget);
176
+ if (envTarget === null || envTarget === t || envTarget === m || envTarget === `${t}_${m}`) {
177
+ targets.push(buildTarget(t, m));
178
+ }
179
+ });
180
+ });
181
+ }
182
+ return targets;
183
+ };
@@ -0,0 +1,54 @@
1
+ import {typecheckOptions } from "./typecheckOptions.mjs";
2
+ import {typecheckWarnedTable} from "./typecheckTable.mjs";
3
+ import {typecheckWarn } from "./typecheckWarn.mjs";
4
+ import {validateType } from "./validateType.mjs";
5
+ /**
6
+ * @param {*} value - The actual value that we need to validate.
7
+ * @param {*} expect - The supposed type information of said value.
8
+ * @param {string} loc - String like `BoundingBox#compute`
9
+ * @param {string} name - Name of the argument
10
+ * @param {boolean} critical - Only `false` for unions.
11
+ * @returns {boolean} - Boolean indicating if a type is correct.
12
+ */
13
+ export function assertType(value, expect, loc, name, critical = true) {
14
+ if (!expect) {
15
+ typecheckWarn("assertType> 'expect' always should be set");
16
+ return false;
17
+ }
18
+ const ret = validateType(value, expect, loc, name, critical);
19
+ if (!ret && critical) {
20
+ typecheckOptions.count++;
21
+ let expectStr = ', expected: ' + JSON.stringify(expect);
22
+ if (expectStr.length >= 40) {
23
+ expectStr = ', expected: ';
24
+ }
25
+ expectStr = '';
26
+ const msg = `${loc}> type of '${name}' is invalid${expectStr}`;
27
+ typecheckWarn(msg, { expect, value });
28
+ const warnObj = typecheckOptions.warned[msg];
29
+ if (!warnObj.tr) {
30
+ const tr = document.createElement('tr');
31
+ const dbg = document.createElement('td');
32
+ const dbgInput = document.createElement("input");
33
+ dbgInput.type = "checkbox";
34
+ dbgInput.onchange = () => {
35
+ warnObj.dbg = dbgInput.checked;
36
+ }
37
+ const count = document.createElement('td');
38
+ const desc = document.createElement('td');
39
+ desc.innerText = msg;
40
+ tr.append(dbg, count, desc);
41
+ dbg.append(dbgInput);
42
+ typecheckWarnedTable.append(tr);
43
+ warnObj.tr = tr;
44
+ }
45
+ const {tr, dbg} = warnObj;
46
+ if (dbg) {
47
+ debugger;
48
+ warnObj.dbg = false; // trigger only once to quickly get app running again
49
+ tr.children[0].children[0].checked = false; // update ui state
50
+ }
51
+ tr.children[1].textContent = warnObj.hits;
52
+ }
53
+ return ret;
54
+ }
@@ -0,0 +1,69 @@
1
+ import {typecheckOptions } from "./typecheckOptions.mjs";
2
+ import {typecheckWarnedTable} from "./typecheckTable.mjs";
3
+ /**
4
+ * @param {HTMLDivElement} div - The <div>.
5
+ */
6
+ function niceDiv(div) {
7
+ div.style.border = "1px solid #C1C1C1";
8
+ div.style.margin = "10px";
9
+ div.style.padding = "10px";
10
+ div.style.textAlign = "left";
11
+ div.style.lineHeight = "25px";
12
+ div.style.backgroundColor = "#F3F3F3";
13
+ div.style.borderRadius = "4px";
14
+ }
15
+ /**
16
+ * @todo
17
+ * Show number of validations too? Might be too noisy.
18
+ * @returns {HTMLDivElement}
19
+ */
20
+ function createDiv() {
21
+ const div = document.createElement("div");
22
+ div.style.position = "absolute";
23
+ div.style.bottom = "0px";
24
+ div.style.right = "0px";
25
+ div.style.zIndex = "1";
26
+ niceDiv(div);
27
+ const spanErrors = document.createElement("span");
28
+ const span = document.createElement("span");
29
+ span.innerText = " Spam type reports:";
30
+ const input = document.createElement("input");
31
+ input.type = "checkbox";
32
+ const spamTypeReports = localStorage.getItem('rti-spam-type-reports');
33
+ input.checked = true;
34
+ if (spamTypeReports !== null) {
35
+ input.checked = spamTypeReports === 'spam' ? true : false;
36
+ }
37
+ const onchange = () => {
38
+ localStorage.setItem('rti-spam-type-reports', input.checked ? 'spam' : 'once');
39
+ if (input.checked) {
40
+ typecheckOptions.mode = "spam";
41
+ } else {
42
+ typecheckOptions.mode = "once";
43
+ }
44
+ };
45
+ input.onchange = onchange;
46
+ onchange(); // update mode from localStorage
47
+ input.checked = true;
48
+ const buttonHide = document.createElement("button");
49
+ buttonHide.textContent = 'Hide';
50
+ buttonHide.onclick = () => {
51
+ div.style.display = 'none';
52
+ };
53
+ div.append(spanErrors, span, input, buttonHide, typecheckWarnedTable);
54
+ div.style.maxHeight = '200px';
55
+ div.style.overflow = 'scroll';
56
+ const finalFunc = () => document.body.append(div);
57
+ // Add our <div> to <body> when possible
58
+ if (document.readyState === "complete") {
59
+ finalFunc();
60
+ } else {
61
+ // add when page is loaded
62
+ document.addEventListener("DOMContentLoaded", finalFunc);
63
+ }
64
+ setInterval(() => {
65
+ spanErrors.innerText = `Type validation errors: ${typecheckOptions.count}`;
66
+ }, 100);
67
+ return div;
68
+ }
69
+ export {createDiv};
@@ -0,0 +1,21 @@
1
+ export * from './assertType.mjs';
2
+ export * from './createDiv.mjs';
3
+ export * from './makeJSDoc.mjs';
4
+ export * from './registerClass.mjs';
5
+ export * from './registerTypedef.mjs';
6
+ export * from './typecheckDiv.mjs';
7
+ export * from './typecheckEvery.mjs';
8
+ export * from './typecheckOptions.mjs';
9
+ export * from './typecheckTable.mjs';
10
+ export * from './typecheckWarn.mjs';
11
+ export * from './validateArray.mjs';
12
+ export * from './validateDivision.mjs';
13
+ export * from './validateMap.mjs';
14
+ export * from './validateNumber.mjs';
15
+ export * from './validateObject.mjs';
16
+ export * from './validateRecord.mjs';
17
+ export * from './validateTuple.mjs';
18
+ export * from './validateType.mjs';
19
+ export * from './validateTypedef.mjs';
20
+ export * from './validateUnion.mjs';
21
+ export * from './youCanAddABreakpointHere.mjs';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @type {Record<string, string>}
3
+ */
4
+ const jsdocCache = {};
5
+ /**
6
+ * @todo
7
+ * update jsdoc cache if it changes or keep all possible version to generate maybe multiple function signatures
8
+ * @example
9
+ * pc.makeJSDoc('VignetteEffect#render', ["inputTarget", "outputTarget", "rect"], [inputTarget, outputTarget, rect])
10
+ * @param {string} loc - The location.
11
+ * @param {string[]} names - The names
12
+ * @param {any[]} values - The values
13
+ */
14
+ function makeJSDoc(loc, names, values) {
15
+ const n = names.length;
16
+ let jsdoc = ' /**\n';
17
+ for (var i = 0; i < n; i++) {
18
+ const name = names[i];
19
+ const value = values[i];
20
+ /** @type {string} */
21
+ let type = typeof value;
22
+ if (value === null) {
23
+ type = 'null';
24
+ } else if (value instanceof Object) {
25
+ type = value.constructor.name;
26
+ }
27
+ if (type !== 'string' && pc[type]) {
28
+ type = 'pc.' + type;
29
+ }
30
+ //debugger;
31
+ jsdoc += ` * @param {${type}} ${name} - todo/jsdoc\n`
32
+ }
33
+ jsdoc += ' */';
34
+ if (!jsdocCache[loc]) {
35
+ jsdocCache[loc] = jsdoc;
36
+ console.log(loc);
37
+ console.log(jsdoc);
38
+ }
39
+ }
40
+ export {jsdocCache, makeJSDoc};
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @type {Record<string, Function>}
3
+ */
4
+ const classes = {};
5
+ /**
6
+ * @param {Function} theClass - The class.
7
+ */
8
+ function registerClass(theClass) {
9
+ classes[theClass.name] = theClass;
10
+ }
11
+ export {classes, registerClass};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @type {Record<string, object>}
3
+ */
4
+ const typedefs = {};
5
+ /**
6
+ * @param {string} name - Name of typedef.
7
+ * @param {Record<string, object>} typedef - The parsed typedef from source.
8
+ */
9
+ function registerTypedef(name, typedef) {
10
+ typedefs[name] = typedef;
11
+ }
12
+ export {typedefs, registerTypedef};
@@ -0,0 +1,3 @@
1
+ import {createDiv} from "./createDiv.mjs";
2
+ const typecheckDiv = createDiv();
3
+ export {typecheckDiv};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * This also works for an empty array, unlike `Array#every`.
3
+ * @example
4
+ * const a = [];
5
+ * a.length = 10;
6
+ * typecheckEvery(a, (e, i) => { console.log(e); return true; }
7
+ * @param {*[]} arr - The array to test.
8
+ * @param {Function} fn - The callback function to test each element with.
9
+ * @returns {boolean} True if every callback call is true aswell.
10
+ */
11
+ function typecheckEvery(arr, fn) {
12
+ const n = arr.length;
13
+ for (let i = 0; i < n; i++) {
14
+ const ret = fn(arr[i], i, arr);
15
+ if (!ret) {
16
+ return false;
17
+ }
18
+ }
19
+ return true;
20
+ }
21
+ export {typecheckEvery};
@@ -0,0 +1,60 @@
1
+ function typecheckReport() {
2
+ console.table(typecheckOptions.warned);
3
+ }
4
+ /**
5
+ * @param {Object<string, any>} obj - The object to clear.
6
+ */
7
+ function clearObject(obj) {
8
+ Object.keys(obj).forEach(_ => delete obj[_]);
9
+ }
10
+ function typecheckReset() {
11
+ clearObject(typecheckOptions.warned);
12
+ }
13
+ const typecheckOptions = {
14
+ /**
15
+ * @type {'spam'|'once'} - In spam-mode every message is printed. In once-mode a cache is
16
+ * looked up to check if it was printed already. Spam can be too noisy if there are too
17
+ * many type errors, so the best way to keep it quiet is to fix the noisiest type issues first.
18
+ * Spam-mode basically retains the order, which mentally helps to figure out the actual issues.
19
+ */
20
+ mode: 'spam',
21
+ /**
22
+ * @type {Record<string, {hits: number, tr: HTMLTableRowElement, dbg: boolean}>}
23
+ */
24
+ warned: {},
25
+ logSuperfluousProperty: false,
26
+ /**
27
+ * @todo integrate PC/Transformers Examples browser + custom config for each project
28
+ */
29
+ customTypes: {
30
+ AnimSetter(value) {
31
+ //console.log("@todo Is AnimSetter?", value);
32
+ return true;
33
+ },
34
+ AnimBinder(value) {
35
+ //console.log("@todo Is AnimBinder?", value);
36
+ return true;
37
+ },
38
+ AnimCurvePath(value) {
39
+ //console.log("@todo Is AnimCurvePath?", value);
40
+ return true;
41
+ },
42
+ ComponentData(value) {
43
+ //console.log("@todo Is ComponentData?", value);
44
+ return true;
45
+ },
46
+ Renderer(value) {
47
+ // E.g. instance of `ForwardRenderer`
48
+ //debugger;
49
+ return value?.constructor?.name?.endsWith("Renderer");
50
+ },
51
+ IArguments(value) {
52
+ //console.log("@todo Is IArguments?", value);
53
+ return true;
54
+ },
55
+ // @todo validate textures having width != 0 and height != 0
56
+ // @todo validate integers
57
+ },
58
+ count: 0,
59
+ };
60
+ export {typecheckReport, clearObject, typecheckReset, typecheckOptions};
@@ -0,0 +1,14 @@
1
+ const typecheckWarnedTable = document.createElement('table');
2
+ const tr = document.createElement('tr');
3
+ const th_0 = document.createElement('th');
4
+ const th_1 = document.createElement('th');
5
+ const th_2 = document.createElement('th');
6
+ th_0.innerText = 'dbg;';
7
+ th_1.innerText = 'Hits';
8
+ th_2.innerText = 'Message';
9
+ tr.append(th_0, th_1, th_2);
10
+ typecheckWarnedTable.append(tr);
11
+ //typecheckWarnedTable.style.maxHeight = '200px';
12
+ //typecheckWarnedTable.style.overflow = 'scroll';
13
+ //typecheckWarnedTable.style.textWrap = 'nowrap';
14
+ export {typecheckWarnedTable};
@@ -0,0 +1,23 @@
1
+ import {typecheckOptions} from "./typecheckOptions.mjs";
2
+ /**
3
+ * @param {string} msg - The main message.
4
+ * @param {...any} extra - Extra strings or objects etc.
5
+ */
6
+ function typecheckWarn(msg, ...extra) {
7
+ const { mode, warned } = typecheckOptions;
8
+ warned[msg] = warned[msg] || { hits: 0 };
9
+ warned[msg].hits++;
10
+ switch (mode) {
11
+ case 'spam':
12
+ console.error(msg, ...extra);
13
+ break;
14
+ case 'once':
15
+ if (warned[msg].hits === 1) {
16
+ console.error(msg, ...extra);
17
+ }
18
+ break;
19
+ default:
20
+ console.error("typecheckWarn> unsupported mode:", mode);
21
+ }
22
+ }
23
+ export {typecheckWarn};
@@ -0,0 +1,26 @@
1
+ import {assertType } from "./assertType.mjs";
2
+ import {typecheckEvery} from "./typecheckEvery.mjs";
3
+ /**
4
+ * @param {*} value
5
+ * @param {*} expect
6
+ * @param {*} loc
7
+ * @param {*} name
8
+ * @param {*} critical
9
+ * @returns {boolean}
10
+ */
11
+ function validateArray(value, expect, loc, name, critical) {
12
+ if (value && value instanceof Array) {
13
+ const { elementType } = expect;
14
+ // some that not validate -> type error
15
+ return typecheckEvery(value, (_, i) => assertType(
16
+ _,
17
+ elementType,
18
+ loc,
19
+ `${name}[${i}]`,
20
+ critical
21
+ ));
22
+ }
23
+ // if it's not even an array -> type error
24
+ return false;
25
+ }
26
+ export {validateArray};
@@ -0,0 +1,31 @@
1
+ import {typecheckWarn} from "./typecheckWarn.mjs";
2
+ /**
3
+ * @param {number} lhs - The left hand side.
4
+ * @param {number} rhs - The right hand side.
5
+ * @example
6
+ * validateDivision( 1 , 2 ); // Outputs: 0.5
7
+ * validateDivision( 1 , "2"); // Warns: validateDivision> incompatible type pair
8
+ * validateDivision(10n, 2n); // Outputs: 5n
9
+ * @returns {number}
10
+ */
11
+ function validateDivision(lhs, rhs) {
12
+ const ret = lhs / rhs;
13
+ const twoNumbers = typeof lhs === 'number' && typeof rhs === 'number';
14
+ const twoBigInts = typeof lhs === 'bigint' && typeof rhs === 'bigint';
15
+ const valid = twoNumbers || twoBigInts;
16
+ if (!valid) {
17
+ typecheckWarn(`validateDivision> incompatible type pair`, { lhs, rhs, twoNumbers, twoBigInts });
18
+ }
19
+ // If we got two bigint's, we are done, as isNaN and isFinite is only for "normal" numbers.
20
+ if (twoBigInts) {
21
+ return ret;
22
+ }
23
+ if (isNaN(ret)) {
24
+ typecheckWarn(`validateDivision> NaN`, { lhs, rhs });
25
+ }
26
+ if (!isFinite(ret)) {
27
+ typecheckWarn(`validateDivision> +-Infinity`, { lhs, rhs });
28
+ }
29
+ return ret;
30
+ }
31
+ export {validateDivision};
@@ -0,0 +1,36 @@
1
+ import {assertType } from "./assertType.mjs";
2
+ import {typecheckWarn} from "./typecheckWarn.mjs";
3
+ /**
4
+ * @param {*} value
5
+ * @param {*} expect
6
+ * @param {*} loc
7
+ * @param {*} name
8
+ * @param {*} critical
9
+ * @returns {boolean}
10
+ */
11
+ function validateMap(value, expect, loc, name, critical) {
12
+ const { key, val } = expect;
13
+ if (key !== 'string') {
14
+ typecheckWarn(`${loc}> validateType> map> unhandled key '${key}'`);
15
+ return false;
16
+ }
17
+ if (val !== 'any') {
18
+ typecheckWarn(`${loc}> validateType> map> expected any, not '${value}'`);
19
+ return false;
20
+ }
21
+ let ret = true;
22
+ for (const [k, v] of value) {
23
+ const good = assertType(
24
+ v,
25
+ val,
26
+ loc,
27
+ `${name}.get('${key}')`,
28
+ critical
29
+ );
30
+ if (!good) {
31
+ ret = false;
32
+ }
33
+ }
34
+ return ret;
35
+ }
36
+ export {validateMap};
@@ -0,0 +1,29 @@
1
+ import {typecheckWarn} from "./typecheckWarn.mjs";
2
+ /**
3
+ * Validates if a given property is neither NaN nor +-Infinity
4
+ * @param {Object<string|number, number>} obj - The object to test a property of.
5
+ * @param {string|number} prop - Name or index or property.
6
+ * @returns {boolean} True if property is a "proper" number (neither Nan nor Infinity)
7
+ */
8
+ function validateNumber(obj, prop) {
9
+ const val = obj[prop];
10
+ const type = typeof obj;
11
+ if (val === null) {
12
+ typecheckWarn(`${type}#${prop} null`, { obj });
13
+ return false;
14
+ }
15
+ if (val === undefined) {
16
+ typecheckWarn(`${type}#${prop} undefined`, { obj });
17
+ return false;
18
+ }
19
+ if (isNaN(val)) {
20
+ typecheckWarn(`${type}#${prop} NaN`, { obj });
21
+ return false;
22
+ }
23
+ if (!isFinite(val)) {
24
+ typecheckWarn(`${type}#${prop} +-Infinity`, { obj });
25
+ return false;
26
+ }
27
+ return true;
28
+ }
29
+ export {validateNumber};