Skip to content

Commit 7f7d155

Browse files
authored
src-transpiler/expandType.js: structurize IndexSignature (#160)
* src-transpiler/expandType.js: serialize IndexSignature * Add unit tests for structure itself * expandType: Only expose properties if we have some
1 parent 589cf75 commit 7f7d155

File tree

6 files changed

+195
-6
lines changed

6 files changed

+195
-6
lines changed

src-transpiler/expandType.js

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const requiredTypeofs = {};
6161
* This function handles various TypeScript AST node types and converts them into a string
6262
* or an object representing the type.
6363
*
64-
* @param {ts.TypeNode} node - The TypeScript AST node to convert.
64+
* @param {ts.TypeNode|ts.Identifier} node - The TypeScript AST node to convert.
6565
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
6666
* or an object with type information based on the node, or `undefined` if the node kind is not handled.
6767
*/
@@ -103,6 +103,7 @@ function toSourceTS(node) {
103103
BigIntLiteral, // parseType("123n" ).literal.kind === ts.SyntaxKind.BigIntLiteral
104104
ConditionalType, // parseType("1 extends number ? true : false").kind === ts.SyntaxKind.ConditionalType
105105
IndexedAccessType, // parseType('Test[123]' ).kind === ts.SyntaxKind.IndexedAccessType
106+
IndexSignature, // parseType('{[n: number]: string}' ).members[0].kind === ts.SyntaxKind.IndexSignature
106107
RestType, // parseType("[...number]" ).elements[0].kind === ts.SyntaxKind.RestType
107108
TypeQuery, // parseType('typeof Number' ).kind === ts.SyntaxKind.TypeQuery
108109
TypeOperator, // parseType('keyof typeof obj' ).kind === ts.SyntaxKind.TypeOperator
@@ -274,23 +275,52 @@ function toSourceTS(node) {
274275
}
275276
const members = node.types.map(toSourceTS);
276277
return {type: 'union', members};
277-
case TypeLiteral:
278+
case TypeLiteral: {
278279
if (!ts.isTypeLiteralNode(node)) {
279280
throw Error("Impossible");
280281
}
281282
const properties = {};
283+
/** @type {object[]} */
284+
let indexSignatures;
282285
node.members.forEach(member => {
283-
const name = toSourceTS(member.name);
284-
const type = toSourceTS(member.type);
285-
properties[name] = type;
286+
if (member.kind === IndexSignature) {
287+
indexSignatures = indexSignatures ?? [];
288+
indexSignatures.push(toSourceTS(member));
289+
} else if (member.kind === PropertySignature) {
290+
if (!ts.isPropertySignature(member)) {
291+
throw Error("Impossible");
292+
}
293+
const name = toSourceTS(member.name);
294+
const type = toSourceTS(member.type);
295+
properties[name] = type;
296+
} else {
297+
console.warn('TypeLiteral: unhandled member', member);
298+
}
286299
});
287-
return {type: 'object', properties};
300+
const ret = {type: 'object'};
301+
if (Object.keys(properties).length) {
302+
ret.properties = properties;
303+
}
304+
if (indexSignatures) {
305+
ret.indexSignatures = indexSignatures;
306+
}
307+
return ret;
308+
}
288309
case PropertySignature:
289310
if (!ts.isPropertySignature(node)) {
290311
throw Error("Impossible");
291312
}
292313
console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
293314
return `${toSourceTS(node.name)}: ${toSourceTS(node.type)}`;
315+
case IndexSignature:
316+
if (!ts.isIndexSignatureDeclaration(node)) {
317+
throw Error("Impossible");
318+
}
319+
// Only possible modifier I know of, but we don't need it:
320+
// {readonly [n: number]: string, length: number}
321+
const indexType = toSourceTS(node.type);
322+
const indexParameters = node.parameters.map(toSourceTS);
323+
return {type: 'indexSignature', indexType, indexParameters};
294324
case Identifier:
295325
if (!ts.isIdentifier(node)) {
296326
throw Error("Impossible");

test/typechecking.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323
"input": "./test/typechecking/ExportNamespaceSpecifier-input.mjs",
2424
"output": "./test/typechecking/ExportNamespaceSpecifier-output.mjs"
2525
},
26+
{
27+
"input": "./test/typechecking/IndexSignature-1-input.mjs",
28+
"output": "./test/typechecking/IndexSignature-1-output.mjs"
29+
},
30+
{
31+
"input": "./test/typechecking/IndexSignature-2-input.mjs",
32+
"output": "./test/typechecking/IndexSignature-2-output.mjs"
33+
},
2634
{
2735
"input": "./test/typechecking/JSDoc-FunctionExpression-ExpressionStatement-input.mjs",
2836
"output": "./test/typechecking/JSDoc-FunctionExpression-ExpressionStatement-output.mjs"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @param {{[n: number]: string, length: number}} words
3+
*/
4+
function countWords(words) {
5+
return words.length;
6+
}
7+
countWords({
8+
0: 'Hello',
9+
1: ' ',
10+
2: 'World',
11+
3: '!',
12+
length: 4
13+
});
14+
countWords(['Hello', ' ', 'World', '!']);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @param {{[n: number]: string, length: number}} words
3+
*/
4+
function countWords(words) {
5+
if (!inspectType(words, {
6+
"type": "object",
7+
"properties": {
8+
"length": "number"
9+
},
10+
"indexSignatures": [
11+
{
12+
"type": "indexSignature",
13+
"indexType": "string",
14+
"indexParameters": [
15+
{
16+
"type": "number",
17+
"name": "n"
18+
}
19+
]
20+
}
21+
],
22+
"optional": false
23+
}, 'countWords', 'words')) {
24+
youCanAddABreakpointHere();
25+
}
26+
return words.length;
27+
}
28+
countWords({
29+
0: 'Hello',
30+
1: ' ',
31+
2: 'World',
32+
3: '!',
33+
length: 4
34+
});
35+
countWords(['Hello', ' ', 'World', '!']);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @typedef {{[n: number]: boolean}} Arrayish
3+
* @typedef {{[k: string]: boolean}} Mapish
4+
*/
5+
/**
6+
* @param {Arrayish} arrayish - Arrayish.
7+
*/
8+
function takeArrayish(arrayish) {
9+
// Nothing yet.
10+
}
11+
/**
12+
* @param {Mapish} mapish - Mapish.
13+
*/
14+
function takeMapish(mapish) {
15+
// Nothing yet.
16+
}
17+
takeArrayish({
18+
0: true,
19+
1: false,
20+
});
21+
takeArrayish({
22+
0: "1st error"
23+
});
24+
takeArrayish({
25+
"0": "2nd error",
26+
});
27+
takeArrayish({
28+
"0": "3rd and 4th error",
29+
});
30+
takeMapish({
31+
'a': true,
32+
'b': false,
33+
'c': "trigger",
34+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
registerTypedef('Arrayish', {
2+
"type": "object",
3+
"indexSignatures": [
4+
{
5+
"type": "indexSignature",
6+
"indexType": "boolean",
7+
"indexParameters": [
8+
{
9+
"type": "number",
10+
"name": "n"
11+
}
12+
]
13+
}
14+
]
15+
});
16+
registerTypedef('Mapish', {
17+
"type": "object",
18+
"indexSignatures": [
19+
{
20+
"type": "indexSignature",
21+
"indexType": "boolean",
22+
"indexParameters": [
23+
{
24+
"type": "string",
25+
"name": "k"
26+
}
27+
]
28+
}
29+
]
30+
});
31+
/**
32+
* @typedef {{[n: number]: boolean}} Arrayish
33+
* @typedef {{[k: string]: boolean}} Mapish
34+
*/
35+
/**
36+
* @param {Arrayish} arrayish - Arrayish.
37+
*/
38+
function takeArrayish(arrayish) {
39+
if (!inspectType(arrayish, "Arrayish", 'takeArrayish', 'arrayish')) {
40+
youCanAddABreakpointHere();
41+
}
42+
}
43+
/**
44+
* @param {Mapish} mapish - Mapish.
45+
*/
46+
function takeMapish(mapish) {
47+
if (!inspectType(mapish, "Mapish", 'takeMapish', 'mapish')) {
48+
youCanAddABreakpointHere();
49+
}
50+
}
51+
takeArrayish({
52+
0: true,
53+
1: false,
54+
});
55+
takeArrayish({
56+
0: "1st error"
57+
});
58+
takeArrayish({
59+
"0": "2nd error",
60+
});
61+
takeArrayish({
62+
"0": "3rd and 4th error",
63+
});
64+
takeMapish({
65+
'a': true,
66+
'b': false,
67+
'c': "trigger",
68+
});

0 commit comments

Comments
 (0)