@runtime-type-inspector/repl 1.0.0
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.
- package/ace-diff@2.3.0.css +73 -0
- package/babel-helper-plugin-utils.mjs +7 -0
- package/babel-parser.mjs +8 -0
- package/babel-plugin-syntax-typescript.mjs +58 -0
- package/importmap.js +28 -0
- package/index.html +33 -0
- package/index.mjs +473 -0
- package/package.json +14 -0
- package/serve.cjs +24 -0
- package/test.css +4 -0
- package/test.html +43 -0
- package/test.js +75 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*! Ace-diff | github.com/ace-diff/ace-diff */
|
|
2
|
+
.acediff__wrap{
|
|
3
|
+
display:-webkit-box;
|
|
4
|
+
display:-ms-flexbox;
|
|
5
|
+
display:flex;
|
|
6
|
+
-webkit-box-orient:horizontal;
|
|
7
|
+
-webkit-box-direction:normal;
|
|
8
|
+
-ms-flex-direction:row;
|
|
9
|
+
flex-direction:row;
|
|
10
|
+
position:absolute;
|
|
11
|
+
bottom:0;
|
|
12
|
+
top:0;
|
|
13
|
+
left:0;
|
|
14
|
+
height:100%;
|
|
15
|
+
width:100%;
|
|
16
|
+
overflow:auto
|
|
17
|
+
}
|
|
18
|
+
.acediff__gutter{
|
|
19
|
+
-webkit-box-flex:0;
|
|
20
|
+
-ms-flex:0 0 60px;
|
|
21
|
+
flex:0 0 60px;
|
|
22
|
+
border-left:1px solid #bcbcbc;
|
|
23
|
+
border-right:1px solid #bcbcbc;
|
|
24
|
+
overflow:hidden
|
|
25
|
+
}
|
|
26
|
+
.acediff__gutter,.acediff__gutter svg{
|
|
27
|
+
background-color:#efefef
|
|
28
|
+
}
|
|
29
|
+
.acediff__left,.acediff__right{
|
|
30
|
+
height:100%;
|
|
31
|
+
-webkit-box-flex:1;
|
|
32
|
+
-ms-flex:1;
|
|
33
|
+
flex:1
|
|
34
|
+
}
|
|
35
|
+
.acediff__diffLine{
|
|
36
|
+
background-color:#d8f2ff;
|
|
37
|
+
border-top:1px solid #a2d7f2;
|
|
38
|
+
border-bottom:1px solid #a2d7f2;
|
|
39
|
+
position:absolute;
|
|
40
|
+
z-index:4
|
|
41
|
+
}
|
|
42
|
+
.acediff__diffLine.targetOnly{
|
|
43
|
+
height:0!important;
|
|
44
|
+
border-top:1px solid #a2d7f2;
|
|
45
|
+
border-bottom:0;
|
|
46
|
+
position:absolute
|
|
47
|
+
}
|
|
48
|
+
.acediff__connector{
|
|
49
|
+
fill:#d8f2ff;
|
|
50
|
+
stroke:#a2d7f2
|
|
51
|
+
}
|
|
52
|
+
.acediff__copy--left,.acediff__copy--right{
|
|
53
|
+
position:relative
|
|
54
|
+
}
|
|
55
|
+
.acediff__copy--left div,.acediff__copy--right div{
|
|
56
|
+
color:#000;
|
|
57
|
+
text-shadow:1px 1px hsla(0,0%,100%,.7);
|
|
58
|
+
position:absolute;
|
|
59
|
+
margin:2px 3px;
|
|
60
|
+
cursor:pointer
|
|
61
|
+
}
|
|
62
|
+
.acediff__copy--right div:hover{
|
|
63
|
+
color:#004ea0
|
|
64
|
+
}
|
|
65
|
+
.acediff__copy--left{
|
|
66
|
+
float:right
|
|
67
|
+
}
|
|
68
|
+
.acediff__copy--left div{
|
|
69
|
+
right:0
|
|
70
|
+
}
|
|
71
|
+
.acediff__copy--left div:hover{
|
|
72
|
+
color:#c98100
|
|
73
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Test: await import("@babel/helper-plugin-utils")
|
|
2
|
+
/** @type {Record<string, any>} */
|
|
3
|
+
const exports = {};
|
|
4
|
+
globalThis.exports = exports;
|
|
5
|
+
await import("../node_modules/@babel/helper-plugin-utils/lib/index.js");
|
|
6
|
+
export const {declare, declarePreset} = exports;
|
|
7
|
+
export default exports;
|
package/babel-parser.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Note: this is still ugly, but Babel 8 should be "nice" ESM (when it's out)
|
|
2
|
+
// Test: const {parse} = await import("@babel/parser");
|
|
3
|
+
/** @type {Record<string, any>} */
|
|
4
|
+
const exports = {};
|
|
5
|
+
globalThis.exports = exports;
|
|
6
|
+
await import("./node_modules/@babel/parser/lib/index.js");
|
|
7
|
+
export const {parse, parseExpression, tokTypes} = exports;
|
|
8
|
+
export default exports;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
// Test: const {babelPluginSyntaxTypeScript} = await import("@babel/plugin-syntax-typescript");
|
|
3
|
+
/*
|
|
4
|
+
const {parse} = await import("@babel/parser");
|
|
5
|
+
parse('type tmp = 123;', {
|
|
6
|
+
plugins: [babelPluginSyntaxTypeScript?]
|
|
7
|
+
})
|
|
8
|
+
*/
|
|
9
|
+
import {declare} from '@babel/helper-plugin-utils';
|
|
10
|
+
/**
|
|
11
|
+
* @param {any[]} plugins
|
|
12
|
+
* @param {string} name
|
|
13
|
+
*/
|
|
14
|
+
function removePlugin(plugins, name) {
|
|
15
|
+
const indices = [];
|
|
16
|
+
plugins.forEach((plugin, i) => {
|
|
17
|
+
const n = Array.isArray(plugin) ? plugin[0] : plugin;
|
|
18
|
+
if (n === name) {
|
|
19
|
+
indices.unshift(i);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
for (const i of indices) {
|
|
23
|
+
plugins.splice(i, 1);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const babelPluginSyntaxTypeScript = declare((api, opts) => {
|
|
27
|
+
api.assertVersion(7);
|
|
28
|
+
const {
|
|
29
|
+
disallowAmbiguousJSXLike,
|
|
30
|
+
dts
|
|
31
|
+
} = opts;
|
|
32
|
+
{
|
|
33
|
+
var {
|
|
34
|
+
isTSX
|
|
35
|
+
} = opts;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
name: "syntax-typescript",
|
|
39
|
+
manipulateOptions(opts, parserOpts) {
|
|
40
|
+
{
|
|
41
|
+
const {
|
|
42
|
+
plugins
|
|
43
|
+
} = parserOpts;
|
|
44
|
+
removePlugin(plugins, "flow");
|
|
45
|
+
removePlugin(plugins, "jsx");
|
|
46
|
+
plugins.push("objectRestSpread", "classProperties");
|
|
47
|
+
if (isTSX) {
|
|
48
|
+
plugins.push("jsx");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
parserOpts.plugins.push(["typescript", {
|
|
52
|
+
disallowAmbiguousJSXLike,
|
|
53
|
+
dts
|
|
54
|
+
}]);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
export {babelPluginSyntaxTypeScript};
|
package/importmap.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} content - The content.
|
|
3
|
+
* @returns {string} data:text string
|
|
4
|
+
*/
|
|
5
|
+
function importFile(content) {
|
|
6
|
+
return "data:text/javascript;base64," + btoa(content);
|
|
7
|
+
}
|
|
8
|
+
const imports = {
|
|
9
|
+
"@runtime-type-inspector/runtime" : '../src-runtime/index.mjs',
|
|
10
|
+
"@runtime-type-inspector/transpiler": '../src-transpiler/index.mjs',
|
|
11
|
+
"@babel/parser": "./babel-parser.mjs",
|
|
12
|
+
//"@babel/helper-plugin-utils": "./babel-helper-plugin-utils.mjs",
|
|
13
|
+
//"@babel/plugin-syntax-typescript": "./babel-plugin-syntax-typescript.mjs",
|
|
14
|
+
"fs": importFile("export default {};"),
|
|
15
|
+
"typescript": importFile("export default ts;"), // UMD import
|
|
16
|
+
};
|
|
17
|
+
if (location.host === 'runtimetypeinspector.org' || location.port === '7000') {
|
|
18
|
+
imports['@runtime-type-inspector/runtime' ] = './node_modules/@runtime-type-inspector/runtime/index.mjs';
|
|
19
|
+
imports['@runtime-type-inspector/transpiler'] = './node_modules/@runtime-type-inspector/transpiler/index.mjs';
|
|
20
|
+
}
|
|
21
|
+
const importmap = document.createElement("script");
|
|
22
|
+
importmap.type = "importmap";
|
|
23
|
+
importmap.textContent = JSON.stringify({imports});
|
|
24
|
+
const dom = document.body || document.head;
|
|
25
|
+
if (!dom) {
|
|
26
|
+
throw new Error("neither <body> nor <head> available to append importmap");
|
|
27
|
+
}
|
|
28
|
+
dom.append(importmap);
|
package/index.html
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<script src="importmap.js"></script>
|
|
4
|
+
<script src="node_modules/ace-builds/src/ace.js"></script>
|
|
5
|
+
<script src="node_modules/typescript/lib/typescript.js"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<select id="action">
|
|
9
|
+
<option value="typechecking">Add full typechecking</option>
|
|
10
|
+
<option value="code2ast2code">code2ast2code (testing Stringifier class)</option>
|
|
11
|
+
<option value="ast">Show AST from JS</option>
|
|
12
|
+
<option value="ast-ts">Show AST from TS</option>
|
|
13
|
+
<option value="ast-babel-ts">Show AST from Babel-TS</option>
|
|
14
|
+
<option value="jsdoc">Show parsed JSDoc</option>
|
|
15
|
+
<option value="expand-type">expandType</option>
|
|
16
|
+
<option value="eval">Eval</option>
|
|
17
|
+
</select>
|
|
18
|
+
<button onclick="runAction()">Run action</button>
|
|
19
|
+
<select id="preferred-expand-type">
|
|
20
|
+
<option value="expandTypeTS">expandTypeTS</option>
|
|
21
|
+
<option value="expandTypeDepFree">expandTypeDepFree</option>
|
|
22
|
+
</select>
|
|
23
|
+
<button id="repl">REPL</button>
|
|
24
|
+
<button id="button-compare-ast" onclick="compareAST()">Compare AST</button>
|
|
25
|
+
<button id="button-set-hash" onclick="setHash()">Set hash</button>
|
|
26
|
+
Left: <input id="left" type="checkbox" checked></input>
|
|
27
|
+
Right: <input id="right" type="checkbox" checked></input>
|
|
28
|
+
<script type="module">
|
|
29
|
+
import * as Index from "./index.mjs";
|
|
30
|
+
Object.assign(window, {...Index});
|
|
31
|
+
</script>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
package/index.mjs
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import {parse} from '@babel/parser';
|
|
2
|
+
import {
|
|
3
|
+
parseJSDoc,
|
|
4
|
+
addTypeChecks,
|
|
5
|
+
expandType,
|
|
6
|
+
expandTypeBabelTS,
|
|
7
|
+
expandTypeDepFree,
|
|
8
|
+
code2ast2code,
|
|
9
|
+
ast2jsonForComparison
|
|
10
|
+
} from '@runtime-type-inspector/transpiler';
|
|
11
|
+
import * as ti from '@runtime-type-inspector/transpiler';
|
|
12
|
+
import * as rti from '@runtime-type-inspector/runtime';
|
|
13
|
+
const hashvars = new Map(location.hash.slice(1).split('&').map(_ => _.split('=')));
|
|
14
|
+
if (!hashvars.get('action')) {
|
|
15
|
+
hashvars.set('action', 'typechecking');
|
|
16
|
+
}
|
|
17
|
+
const currentAction = hashvars.get('action');
|
|
18
|
+
const selectAction = document.getElementById("action");
|
|
19
|
+
if (!(selectAction instanceof HTMLSelectElement)) {
|
|
20
|
+
throw new Error('This module requires a <select id="action" ...');
|
|
21
|
+
}
|
|
22
|
+
const selectPreferredExpandType = document.getElementById('preferred-expand-type');
|
|
23
|
+
if (!(selectPreferredExpandType instanceof HTMLSelectElement)) {
|
|
24
|
+
throw new Error('This module requires a <select id="action" ...');
|
|
25
|
+
}
|
|
26
|
+
function setHash() {
|
|
27
|
+
const left = btoa(getLeft());
|
|
28
|
+
const right = btoa(getRight());
|
|
29
|
+
location.hash = `action=${selectAction.value}&left=${left}&right=${right}`;
|
|
30
|
+
}
|
|
31
|
+
// selectPreferredExpandType.value;
|
|
32
|
+
selectAction.value = currentAction;
|
|
33
|
+
const buttonREPL = document.getElementById('repl');
|
|
34
|
+
if (!(buttonREPL instanceof HTMLButtonElement)) {
|
|
35
|
+
throw new Error('This module requires a <button id="repl" ...');
|
|
36
|
+
}
|
|
37
|
+
const inputLeft = document.getElementById('left');
|
|
38
|
+
if (!(inputLeft instanceof HTMLInputElement)) {
|
|
39
|
+
throw 'This module requires a <input id="left" ...';
|
|
40
|
+
}
|
|
41
|
+
const inputRight = document.getElementById('right');
|
|
42
|
+
if (!(inputRight instanceof HTMLInputElement)) {
|
|
43
|
+
throw 'This module requires a <input id="right" ...';
|
|
44
|
+
}
|
|
45
|
+
inputLeft.onchange = (event) => {
|
|
46
|
+
const {checked} = event.target;
|
|
47
|
+
if (checked) {
|
|
48
|
+
// Unhide right editor and resize right one to half size again
|
|
49
|
+
aceDivLeft.style.display = "";
|
|
50
|
+
setHalfSizeLeft();
|
|
51
|
+
setHalfSizeRight();
|
|
52
|
+
} else {
|
|
53
|
+
// Hide left editor and resize right one to full size
|
|
54
|
+
aceDivLeft.style.display = "none";
|
|
55
|
+
Object.assign(aceDivRight.style, {
|
|
56
|
+
position: "absolute",
|
|
57
|
+
left: "0vw",
|
|
58
|
+
top: "40px",
|
|
59
|
+
width: "100vw",
|
|
60
|
+
height: "calc(100vh - 40px)",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
inputRight.onchange = (event) => {
|
|
65
|
+
const {checked} = event.target;
|
|
66
|
+
if (checked) {
|
|
67
|
+
// Unhide left editor and resize right one to half size again
|
|
68
|
+
aceDivRight.style.display = "";
|
|
69
|
+
setHalfSizeLeft();
|
|
70
|
+
setHalfSizeRight();
|
|
71
|
+
} else {
|
|
72
|
+
// Hide left editor and resize right one to full size
|
|
73
|
+
aceDivRight.style.display = "none";
|
|
74
|
+
Object.assign(aceDivLeft.style, {
|
|
75
|
+
position: "absolute",
|
|
76
|
+
left: "0vw",
|
|
77
|
+
top: "40px",
|
|
78
|
+
width: "100vw",
|
|
79
|
+
height: "calc(100vh - 40px)",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function getPreferredExpandType() {
|
|
84
|
+
const {value} = selectPreferredExpandType;
|
|
85
|
+
if (value == 'expandTypeTS') {
|
|
86
|
+
return expandType;
|
|
87
|
+
}
|
|
88
|
+
return expandTypeDepFree;
|
|
89
|
+
}
|
|
90
|
+
function getCodeForAction() {
|
|
91
|
+
switch (getAction()) {
|
|
92
|
+
case 'typechecking':
|
|
93
|
+
return "const ret = addTypeChecks(jsdoc, {expandType: getPreferredExpandType()});\nsetRight(ret);";
|
|
94
|
+
case 'jsdoc':
|
|
95
|
+
return "const ret = parseJSDoc(jsdoc);\nsetRight(JSON.stringify(ret, null, 2));";
|
|
96
|
+
case 'code2ast2code':
|
|
97
|
+
return "const ret = code2ast2code(jsdoc);\nsetRight(ret);";
|
|
98
|
+
case 'expand-type':
|
|
99
|
+
return 'setRight(expandTypeAll(jsdoc));';
|
|
100
|
+
}
|
|
101
|
+
return '// This action has no special code';
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} name - The name.
|
|
105
|
+
* @param {any} data - Function or object.
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
function data2code(name, data) {
|
|
109
|
+
if (data instanceof Function) {
|
|
110
|
+
return data.toString();
|
|
111
|
+
} else if (data instanceof Object && data !== null) {
|
|
112
|
+
return `let ${name} = ${JSON.stringify(data, null, 2)};`;
|
|
113
|
+
}
|
|
114
|
+
return `// data2code> unhandled type: name=${name}, type of data: ${typeof data}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Since Asserter extends Stringifier, it needs to appear afterwards.
|
|
118
|
+
* @param {string} a
|
|
119
|
+
* @param {string} b
|
|
120
|
+
* @returns {-1|0|1}
|
|
121
|
+
*/
|
|
122
|
+
function compareBaseClasses(a, b) {
|
|
123
|
+
if (a.startsWith("class Asserter") && b.startsWith("class Stringifier")) {
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
if (b.startsWith("class Asserter") && a.startsWith("class Stringifier")) {
|
|
127
|
+
return -1;
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
function activateREPL() {
|
|
132
|
+
const code = Object.entries(ti)
|
|
133
|
+
.map(([key, val]) => data2code(key, val))
|
|
134
|
+
.sort(compareBaseClasses)
|
|
135
|
+
.join('\n');
|
|
136
|
+
const codeLocal = [getPreferredExpandType, expandTypeAll].join('\n');
|
|
137
|
+
const leftContent = aceEditorLeft.getValue().replaceAll('`', '\\`');
|
|
138
|
+
const leftContentAsCode = `const jsdoc = \`${leftContent}\`;`;
|
|
139
|
+
const out = [leftContentAsCode, code, codeLocal, getCodeForAction()].join('\n');
|
|
140
|
+
setLeft(out);
|
|
141
|
+
// @ts-ignore
|
|
142
|
+
selectAction.value = "eval";
|
|
143
|
+
}
|
|
144
|
+
buttonREPL.onclick = activateREPL;
|
|
145
|
+
Object.assign(window, {
|
|
146
|
+
parse,
|
|
147
|
+
ti, ...ti,
|
|
148
|
+
rti, ...rti,
|
|
149
|
+
});
|
|
150
|
+
let lastStats = {};
|
|
151
|
+
function statsPrint() {
|
|
152
|
+
console.table(lastStats);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* @param {string} value
|
|
156
|
+
*/
|
|
157
|
+
function setLeft(value) {
|
|
158
|
+
if (typeof value !== 'string') {
|
|
159
|
+
aceEditorRight.setValue(`Cannot set type ${typeof value}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
aceEditorLeft.setValue(value);
|
|
163
|
+
aceEditorLeft.clearSelection(); // setValue() selects everything, so unselect it now
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} value
|
|
167
|
+
*/
|
|
168
|
+
function setRight(value) {
|
|
169
|
+
if (typeof value !== 'string') {
|
|
170
|
+
aceEditorRight.setValue(`Cannot set type ${typeof value}`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
aceEditorRight.setValue(value);
|
|
174
|
+
aceEditorRight.clearSelection(); // setValue() selects everything, so unselect it now
|
|
175
|
+
}
|
|
176
|
+
function getLeft() {
|
|
177
|
+
return aceEditorLeft.getValue();
|
|
178
|
+
}
|
|
179
|
+
function getRight() {
|
|
180
|
+
return aceEditorRight.getValue();
|
|
181
|
+
}
|
|
182
|
+
function actionAST() {
|
|
183
|
+
const ast = parse(getLeft(), {sourceType: 'module'});
|
|
184
|
+
const out = JSON.stringify(ast, function (name, val) {
|
|
185
|
+
if (name === "loc" || name === "start" || name === "end") {
|
|
186
|
+
return undefined; // remove
|
|
187
|
+
}
|
|
188
|
+
return val; // keep
|
|
189
|
+
}, 2);
|
|
190
|
+
setRight(out);
|
|
191
|
+
}
|
|
192
|
+
function actionAST_TS() {
|
|
193
|
+
const str = getLeft();
|
|
194
|
+
const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, false /*setParentNodes*/);
|
|
195
|
+
const out = JSON.stringify(ast, function(name, val) {
|
|
196
|
+
if (name === "parent") {
|
|
197
|
+
return undefined; // remove
|
|
198
|
+
}
|
|
199
|
+
return val; // keep
|
|
200
|
+
}, 2);
|
|
201
|
+
setRight(out);
|
|
202
|
+
}
|
|
203
|
+
function actionAST_BabelTS() {
|
|
204
|
+
const str = getLeft();
|
|
205
|
+
const ast = parse(str, {plugins: ['typescript'], sourceType: "module"});
|
|
206
|
+
const out = JSON.stringify(ast, null, 2);
|
|
207
|
+
setRight(out);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} type - The type like `...string`.
|
|
211
|
+
* @returns {string} Structured output of all expandType functions.
|
|
212
|
+
*/
|
|
213
|
+
function expandTypeAll(type) {
|
|
214
|
+
let out = '';
|
|
215
|
+
out += '// expandTypeTS:\n';
|
|
216
|
+
try {
|
|
217
|
+
const ts = expandType(type);
|
|
218
|
+
out += JSON.stringify(ts, null, 2) + '\n';
|
|
219
|
+
} catch (e) {
|
|
220
|
+
out += e + '\n';
|
|
221
|
+
}
|
|
222
|
+
out += '// expandTypeBabelTS:\n';
|
|
223
|
+
try {
|
|
224
|
+
const babelts = expandTypeBabelTS(type);
|
|
225
|
+
out += JSON.stringify(babelts, null, 2) + '\n';
|
|
226
|
+
} catch (e) {
|
|
227
|
+
out += e + '\n';
|
|
228
|
+
}
|
|
229
|
+
out += '// expandTypeDepFree:\n';
|
|
230
|
+
try {
|
|
231
|
+
const depFree = expandTypeDepFree(type);
|
|
232
|
+
out += JSON.stringify(depFree, null, 2) + '\n';
|
|
233
|
+
} catch (e) {
|
|
234
|
+
out += e + '\n';
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
function actionExpandType() {
|
|
239
|
+
setRight(expandTypeAll(getLeft()));
|
|
240
|
+
}
|
|
241
|
+
function actionJSDoc() {
|
|
242
|
+
const ret = parseJSDoc(getLeft());
|
|
243
|
+
/** @type {string} */
|
|
244
|
+
let out;
|
|
245
|
+
if (ret === undefined) {
|
|
246
|
+
out = '// parseJSDoc returned undefined';
|
|
247
|
+
} else {
|
|
248
|
+
out = '// parseJSDoc output:\n' + JSON.stringify(ret, null, 2);
|
|
249
|
+
}
|
|
250
|
+
setRight(out);
|
|
251
|
+
}
|
|
252
|
+
function actionTypeChecking() {
|
|
253
|
+
setRight(addTypeChecks(getLeft(), {expandType: getPreferredExpandType()}));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* @todo use Monaco Diff Editor
|
|
257
|
+
* @param {string} left - Left source code.
|
|
258
|
+
* @param {string} right - Right source code.
|
|
259
|
+
*/
|
|
260
|
+
function compareAST(left = getLeft(), right = getRight()) {
|
|
261
|
+
const l = parse(left , {sourceType: 'module'});
|
|
262
|
+
const r = parse(right, {sourceType: 'module'});
|
|
263
|
+
const ljson = ast2jsonForComparison(l);
|
|
264
|
+
const rjson = ast2jsonForComparison(r);
|
|
265
|
+
const test = ljson === rjson;
|
|
266
|
+
const _ = test ? ' ' : ' NOT ';
|
|
267
|
+
console.log(`AST is ${_} equal`);
|
|
268
|
+
// todo add conversion buttons
|
|
269
|
+
//console.log('ljson', ljson);
|
|
270
|
+
//console.log('rjson', rjson);
|
|
271
|
+
setLeft(ljson);
|
|
272
|
+
setRight(rjson);
|
|
273
|
+
}
|
|
274
|
+
function actionCode2Ast2Code() {
|
|
275
|
+
const content = getLeft();
|
|
276
|
+
const out = code2ast2code(content);
|
|
277
|
+
if (!out) {
|
|
278
|
+
setRight("// code2ast2code failed");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
setRight(out);
|
|
282
|
+
// compareAST(content, out);
|
|
283
|
+
}
|
|
284
|
+
async function runAction() {
|
|
285
|
+
const action = getAction();
|
|
286
|
+
switch (action) {
|
|
287
|
+
case 'ast':
|
|
288
|
+
await actionAST();
|
|
289
|
+
break;
|
|
290
|
+
case 'ast-ts':
|
|
291
|
+
await actionAST_TS();
|
|
292
|
+
break;
|
|
293
|
+
case 'ast-babel-ts':
|
|
294
|
+
await actionAST_BabelTS();
|
|
295
|
+
break;
|
|
296
|
+
case 'jsdoc':
|
|
297
|
+
await actionJSDoc();
|
|
298
|
+
break;
|
|
299
|
+
case 'typechecking':
|
|
300
|
+
await actionTypeChecking();
|
|
301
|
+
break;
|
|
302
|
+
case 'code2ast2code':
|
|
303
|
+
await actionCode2Ast2Code();
|
|
304
|
+
break;
|
|
305
|
+
case 'eval':
|
|
306
|
+
eval(aceEditorLeft.getValue());
|
|
307
|
+
break;
|
|
308
|
+
case 'expand-type':
|
|
309
|
+
await actionExpandType();
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
setRight(`Action ${action} not implemented`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function insertTypes() {
|
|
316
|
+
const ret = await postData('insertTypes', {
|
|
317
|
+
file: aceEditorLeft.getValue()
|
|
318
|
+
});
|
|
319
|
+
ret.logs?.map(_ => console.log(..._));
|
|
320
|
+
ret.warns?.map(_ => console.warn(..._));
|
|
321
|
+
console.log(ret);
|
|
322
|
+
if (ret.success) {
|
|
323
|
+
lastStats = ret.stats;
|
|
324
|
+
statsPrint();
|
|
325
|
+
aceEditorRight.setValue(ret.result);
|
|
326
|
+
if (ret.insertTypesAndEval) {
|
|
327
|
+
eval(ret.result.replaceAll('export ', ''));
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
aceEditorRight.setValue(JSON.stringify(ret, null, 2));
|
|
331
|
+
}
|
|
332
|
+
aceEditorRight.clearSelection(); // setValue() selects everything, so unselect it now
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* @typedef {'typechecking'|'code2ast2code'|'ast'|'ast-ts'|'jsdoc'|'eval'|'expand-type'} Action
|
|
336
|
+
*/
|
|
337
|
+
/** @returns {Action} */
|
|
338
|
+
const getAction = () => selectAction.value;
|
|
339
|
+
/**
|
|
340
|
+
* @param {string} id - The ID.
|
|
341
|
+
* @returns {HTMLDivElement} - The DIV element.
|
|
342
|
+
*/
|
|
343
|
+
function createDivWithId(id) {
|
|
344
|
+
let div = document.getElementById(id);
|
|
345
|
+
if (!div) {
|
|
346
|
+
div = document.createElement('div');
|
|
347
|
+
div.id = id;
|
|
348
|
+
}
|
|
349
|
+
if (!(div instanceof HTMLDivElement)) {
|
|
350
|
+
throw "createDivWithId> div creation failed";
|
|
351
|
+
}
|
|
352
|
+
return div;
|
|
353
|
+
}
|
|
354
|
+
const aceDivLeft = createDivWithId("aceDivLeft");
|
|
355
|
+
const aceDivRight = createDivWithId("aceDivRight");
|
|
356
|
+
document.body.append(aceDivLeft, aceDivRight);
|
|
357
|
+
function setHalfSizeLeft() {
|
|
358
|
+
Object.assign(aceDivLeft.style, {
|
|
359
|
+
position: "absolute",
|
|
360
|
+
left: "0px",
|
|
361
|
+
top: "40px",
|
|
362
|
+
width: "50vw",
|
|
363
|
+
height: "calc(100vh - 40px)",
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function setHalfSizeRight() {
|
|
367
|
+
Object.assign(aceDivRight.style, {
|
|
368
|
+
position: "absolute",
|
|
369
|
+
left: "50vw",
|
|
370
|
+
top: "40px",
|
|
371
|
+
width: "50vw",
|
|
372
|
+
height: "calc(100vh - 40px)",
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
setHalfSizeLeft();
|
|
376
|
+
setHalfSizeRight();
|
|
377
|
+
/**
|
|
378
|
+
* @param {string} id - ID of editor.
|
|
379
|
+
* @param {string} txt - Initial text of editor.
|
|
380
|
+
* @param {Function} execShiftEnter - Function for Shift+Enter
|
|
381
|
+
* @param {Function} execAltEnter - Function for Alt+Enter
|
|
382
|
+
* @returns {ACE.Editor} The ACE editor.
|
|
383
|
+
*/
|
|
384
|
+
function setupAce(id, txt, execShiftEnter, execAltEnter) {
|
|
385
|
+
const aceEditor = ace.edit(id);
|
|
386
|
+
aceEditor.setFontSize(20);
|
|
387
|
+
aceEditor.setTheme('ace/theme/chrome');
|
|
388
|
+
aceEditor.session.setMode('ace/mode/javascript');
|
|
389
|
+
aceEditor.session.setUseWorker(false);
|
|
390
|
+
aceEditor.session.setOptions({
|
|
391
|
+
tabSize: 2,
|
|
392
|
+
useSoftTabs: true
|
|
393
|
+
});
|
|
394
|
+
aceEditor.setValue(txt);
|
|
395
|
+
aceEditor.clearSelection(); // setValue() selects everything, so unselect it now
|
|
396
|
+
aceEditor.commands.addCommand({
|
|
397
|
+
name: 'Insert types',
|
|
398
|
+
bindKey: {win: 'Shift-Enter', mac: 'Shift-Enter'},
|
|
399
|
+
exec: execShiftEnter
|
|
400
|
+
});
|
|
401
|
+
aceEditor.commands.addCommand({
|
|
402
|
+
name: 'Show AST',
|
|
403
|
+
bindKey: {win: 'Alt-Enter', mac: 'Alt-Enter'},
|
|
404
|
+
exec: execAltEnter
|
|
405
|
+
});
|
|
406
|
+
return aceEditor;
|
|
407
|
+
}
|
|
408
|
+
const aceEditorLeft = setupAce(
|
|
409
|
+
'aceDivLeft',
|
|
410
|
+
`// Shift-Enter: convert content of left editor,
|
|
411
|
+
// write result to right editor
|
|
412
|
+
// Tip: open F12/DevTools to see errors and warnings
|
|
413
|
+
// Press Shift-Enter in right editor to eval result.
|
|
414
|
+
/**
|
|
415
|
+
* @param {number} a
|
|
416
|
+
* @param {number} b
|
|
417
|
+
*/
|
|
418
|
+
function add(a, b) {
|
|
419
|
+
return a + b;
|
|
420
|
+
}
|
|
421
|
+
/** @type {number[]} */
|
|
422
|
+
const arr = [10_20];
|
|
423
|
+
const [a, b] = arr;
|
|
424
|
+
const ret = add(a, b);
|
|
425
|
+
console.log("ret", ret);
|
|
426
|
+
`,
|
|
427
|
+
//editor => insertTypes(),
|
|
428
|
+
editor => runAction(),
|
|
429
|
+
editor => actionAST()
|
|
430
|
+
);
|
|
431
|
+
const aceEditorRight = setupAce(
|
|
432
|
+
'aceDivRight',
|
|
433
|
+
'// Result editor',
|
|
434
|
+
editor => runRightEditor(),
|
|
435
|
+
editor => console.log("right alt")
|
|
436
|
+
);
|
|
437
|
+
function runRightEditor() {
|
|
438
|
+
const src = getRight();
|
|
439
|
+
if (src.includes('import') || src.includes('export')) {
|
|
440
|
+
const script = document.createElement("script");
|
|
441
|
+
script.type = "module";
|
|
442
|
+
script.textContent = src;
|
|
443
|
+
document.head.append(script);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
eval(src);
|
|
447
|
+
}
|
|
448
|
+
if (hashvars.get('left')) {
|
|
449
|
+
setLeft(atob(hashvars.get('left')));
|
|
450
|
+
}
|
|
451
|
+
if (hashvars.get('right')) {
|
|
452
|
+
setRight(atob(hashvars.get('right')));
|
|
453
|
+
}
|
|
454
|
+
export {
|
|
455
|
+
setHash,
|
|
456
|
+
hashvars,
|
|
457
|
+
selectAction,
|
|
458
|
+
selectPreferredExpandType,
|
|
459
|
+
buttonREPL,
|
|
460
|
+
aceDivLeft,
|
|
461
|
+
aceDivRight,
|
|
462
|
+
aceEditorLeft,
|
|
463
|
+
aceEditorRight,
|
|
464
|
+
setLeft,
|
|
465
|
+
setRight,
|
|
466
|
+
getLeft,
|
|
467
|
+
getRight,
|
|
468
|
+
compareAST,
|
|
469
|
+
getPreferredExpandType,
|
|
470
|
+
expandTypeAll,
|
|
471
|
+
runAction,
|
|
472
|
+
data2code,
|
|
473
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@runtime-type-inspector/repl",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "REPL of RuntimeTypeInspector.js",
|
|
5
|
+
"main": "index.html",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@runtime-type-inspector/transpiler": "^3.0.3",
|
|
8
|
+
"ace-builds": "^1.28.0",
|
|
9
|
+
"express": "^4.18.2"
|
|
10
|
+
},
|
|
11
|
+
"author": "Hermann Rolfes (kungfooman)",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"publishConfig": {"access": "public"}
|
|
14
|
+
}
|
package/serve.cjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var express = require('express');
|
|
2
|
+
|
|
3
|
+
express.static.mime.define({'text/javascript': ['mjs']});
|
|
4
|
+
var app = express();
|
|
5
|
+
|
|
6
|
+
app.use(express.static('.'));
|
|
7
|
+
|
|
8
|
+
//Serves all the request which includes /images in the url from Images folder
|
|
9
|
+
app.use('/images', express.static(__dirname + '/node_modules'));
|
|
10
|
+
app.use('/', express.static(__dirname));
|
|
11
|
+
|
|
12
|
+
var server = app.listen(7000);
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
const express = require('express');
|
|
16
|
+
const app = express();
|
|
17
|
+
const port = 6000;
|
|
18
|
+
app.get('/', (req, res) => {
|
|
19
|
+
res.send('Hello World!');
|
|
20
|
+
});
|
|
21
|
+
app.listen(port, () => {
|
|
22
|
+
console.log(`Example app listening on port ${port}`);
|
|
23
|
+
});
|
|
24
|
+
*/
|
package/test.css
ADDED
package/test.html
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>RTI Test</title>
|
|
5
|
+
<script src="./importmap.js"></script>
|
|
6
|
+
<script src="../node_modules/typescript/lib/typescript.js"></script>
|
|
7
|
+
<!-- Include Ace editor first -->
|
|
8
|
+
<!--
|
|
9
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"></script>
|
|
10
|
+
-->
|
|
11
|
+
<script src="../node_modules/ace-builds/src/ace.js"></script>
|
|
12
|
+
<!-- Include Ace-diff JS and CSS (using unpkg.com CDN in this case) -->
|
|
13
|
+
<script src="https://unpkg.com/ace-diff@^2"></script>
|
|
14
|
+
<link href="./ace-diff@2.3.0.css" rel="stylesheet">
|
|
15
|
+
<link href="./test.css" rel="stylesheet">
|
|
16
|
+
<!-- Bootstrap for additional styling of the demo -->
|
|
17
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.1/css/bootstrap.min.css" />
|
|
18
|
+
<style>
|
|
19
|
+
#buttons {
|
|
20
|
+
overflow: scroll;
|
|
21
|
+
height: 80vh;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<div class="container">
|
|
27
|
+
<h1>RTI Test</h1>
|
|
28
|
+
<div class="row">
|
|
29
|
+
<div class="col-sm-4">
|
|
30
|
+
<div id="buttons"></div>
|
|
31
|
+
<p>A red test requires more work, while a green test passes.</p>
|
|
32
|
+
<hr>
|
|
33
|
+
<h2>Troubleshooting</h2>
|
|
34
|
+
<p>If you run into a problem please open an issue on GitHub.</p>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="col-sm-8" id="here">
|
|
37
|
+
<div class="custom"></div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<script type="module" src="test.js"></script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
package/test.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {addTypeChecks, expandType} from "@runtime-type-inspector/transpiler";
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} a - Left text.
|
|
4
|
+
* @param {string} b - Right text.
|
|
5
|
+
* @returns {object} AceDiff editor instance.
|
|
6
|
+
*/
|
|
7
|
+
function addDiff(a, b) {
|
|
8
|
+
const element = document.createElement('div');
|
|
9
|
+
document.getElementById("here").innerHTML = '';
|
|
10
|
+
document.getElementById("here").append(element);
|
|
11
|
+
return new AceDiff({
|
|
12
|
+
element,
|
|
13
|
+
left: {
|
|
14
|
+
content: a,
|
|
15
|
+
},
|
|
16
|
+
right: {
|
|
17
|
+
content: b,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @param {*} input - Source code to normalize.
|
|
23
|
+
* @returns {string} Normalized output.
|
|
24
|
+
*/
|
|
25
|
+
function normalize(input) {
|
|
26
|
+
// Remove multiple newlines into one
|
|
27
|
+
// I would rather not do it, but Stringifier needs a bit more love in other areas:
|
|
28
|
+
// - multiline array output when elements surpass a max-col option
|
|
29
|
+
let output = input.replace(/\n+/g, '\n').trim();
|
|
30
|
+
/** @todo Remove bunch of whitespaces from test outputs */
|
|
31
|
+
output = output
|
|
32
|
+
.split('\n')
|
|
33
|
+
.filter(_ => _.trim().length)
|
|
34
|
+
.map(_ => _.trim())
|
|
35
|
+
.join('\n');
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
async function main() {
|
|
39
|
+
const resp = await fetch("../test/typechecking.json");
|
|
40
|
+
const json = await resp.json();
|
|
41
|
+
const buttonsDiv = document.getElementById('buttons');
|
|
42
|
+
if (!(buttonsDiv instanceof HTMLDivElement)) {
|
|
43
|
+
throw new Error("buttons isn't a <div>");
|
|
44
|
+
}
|
|
45
|
+
const aceDiffer = addDiff('1\n2\n3\n4\n', '1\n2\n33\n4\n');
|
|
46
|
+
/** @type {HTMLButtonElement[]} */
|
|
47
|
+
const buttons = [];
|
|
48
|
+
for (const {input, output} of json) {
|
|
49
|
+
const button = document.createElement('button');
|
|
50
|
+
buttonsDiv.append(button);
|
|
51
|
+
button.innerText = input;
|
|
52
|
+
const res = await fetch('../' + input);
|
|
53
|
+
const txtInput = await res.text();
|
|
54
|
+
const res2 = await fetch('../' + output);
|
|
55
|
+
const txtOutput = await res2.text();
|
|
56
|
+
const actualResult = addTypeChecks(txtInput, {expandType, addHeader: false});
|
|
57
|
+
const success = normalize(actualResult) === normalize(txtOutput);
|
|
58
|
+
button.style.backgroundColor = success ? 'lime' : 'red';
|
|
59
|
+
button.onclick = async () => {
|
|
60
|
+
aceDiffer.editors.left.ace.setValue(txtInput);
|
|
61
|
+
aceDiffer.editors.left.ace.clearSelection();
|
|
62
|
+
let rightText = txtOutput;
|
|
63
|
+
if (!success) {
|
|
64
|
+
rightText += `\n\n// Actual result:\n${actualResult}`;
|
|
65
|
+
}
|
|
66
|
+
aceDiffer.editors.right.ace.setValue(rightText);
|
|
67
|
+
aceDiffer.editors.right.ace.clearSelection();
|
|
68
|
+
//console.log({input, output});
|
|
69
|
+
};
|
|
70
|
+
buttons.push(button);
|
|
71
|
+
}
|
|
72
|
+
buttons[1].onclick();
|
|
73
|
+
Object.assign(window, {aceDiffer, json});
|
|
74
|
+
}
|
|
75
|
+
main();
|