Skip to content

Commit

Permalink
Use native TS types for src-transpiler/expandType.js
Browse files Browse the repository at this point in the history
  • Loading branch information
kungfooman committed Jun 9, 2024
1 parent 95bf8f2 commit 23d934f
Showing 1 changed file with 108 additions and 31 deletions.
139 changes: 108 additions & 31 deletions src-transpiler/expandType.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,19 @@ import ts from 'typescript';
* expandType('typeof Number '); // Outputs:
* @param {string} type - The type string to be expanded into a structured representation.
* @todo Share type with expandTypeBabelTS and expandTypeDepFree
* @returns {string | {type: string, [key: string]: any} | undefined} The structured type
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The structured type
* representation obtained from parsing and converting the provided type string.
*/
function expandType(type) {
const ast = parseType(type);
if (!ast) {
return 'never';
}
return toSourceTS(ast);
}
/**
* @todo I want to use for example: import('typescript').Node
* But the TS types make no sense to me so far ... need to investigate more.
* @typedef TypeScriptType
* @property {object[]|undefined} typeArguments - The type arguments.
* @property {import('typescript').Node} typeName - The type name.
* @property {number} kind - The kind for `ts.SyntaxKind[kind]`.
*/
/**
* @param {string} str - The type string.
* @returns {TypeScriptType} - The node containing all the information about the input type string.
* @returns {ts.TypeNode|undefined} - The node containing all the information about the input type string.
*/
function parseType(str) {
// TS doesn't like ... notation in this context
Expand All @@ -51,7 +46,12 @@ function parseType(str) {
// type tmp = (...string) => 123; to have a function context
str = `type tmp = ${str};`;
const ast = ts.createSourceFile('repl.ts', str, ts.ScriptTarget.Latest, true /*setParentNodes*/);
return ast.statements[0].type;
const firstStatement = ast.statements[0];
if (!ts.isTypeAliasDeclaration(firstStatement)) {
console.warn('parseType> Expected type alias declaration, got', firstStatement, 'instead.');
return;
}
return firstStatement.type;
}
/** @type {Record<string, 'missing'|'found'>} */
export const requiredTypeofs = {};
Expand All @@ -61,7 +61,7 @@ export const requiredTypeofs = {};
* This function handles various TypeScript AST node types and converts them into a string
* or an object representing the type.
*
* @param {TypeScriptType} node - The TypeScript AST node to convert.
* @param {ts.TypeNode} node - The TypeScript AST node to convert.
* @returns {string | number | boolean | {type: string, [key: string]: any} | undefined} The source string/number,
* or an object with type information based on the node, or `undefined` if the node kind is not handled.
*/
Expand All @@ -72,9 +72,22 @@ function toSourceTS(node) {
AnyKeyword, ArrayType, BooleanKeyword, FunctionType, Identifier, IntersectionType,
JSDocAllType, LastTypeNode, LiteralType, NullKeyword, NumberKeyword, NumericLiteral,
ObjectKeyword, Parameter, ParenthesizedType, PropertySignature, StringKeyword,
StringLiteral, ThisType, TupleType, TypeLiteral, TypeReference, UndefinedKeyword,
UnionType, JSDocNullableType, TrueKeyword, FalseKeyword, VoidKeyword, UnknownKeyword,
NeverKeyword, BigIntKeyword, BigIntLiteral, ConditionalType, IndexedAccessType, RestType,
StringLiteral, ThisType, TupleType,
TypeLiteral, // parseType todo
TypeReference,
UndefinedKeyword,
UnionType,
JSDocNullableType,
TrueKeyword,
FalseKeyword,
VoidKeyword,
UnknownKeyword,
NeverKeyword,
BigIntKeyword,
BigIntLiteral,
ConditionalType,
IndexedAccessType,
RestType,
TypeQuery, // parseType('typeof Number')
TypeOperator, // parseType('keyof typeof obj')
KeyOfKeyword, // "operator" key in TypeOperator node
Expand All @@ -88,36 +101,60 @@ function toSourceTS(node) {
case BigIntKeyword:
return {type: 'bigint'};
case BigIntLiteral:
if (!ts.isBigIntLiteral(node)) {
throw Error("Impossible");
}
const literal = node.text.slice(0, -1); // Remove the "n"
return {type: 'bigint', literal};
case ConditionalType:
if (!ts.isConditionalTypeNode(node)) {
throw Error("Impossible");
}
// Keys on node:
// ['pos', 'end', 'flags', 'modifierFlagsCache', 'transformFlags', 'parent', 'kind', 'checkType',
// 'extendsType', 'trueType', 'falseType', 'locals', 'nextContainer']
const checkType = toSourceTS(node.checkType);
const checkType = toSourceTS(node.checkType );
const extendsType = toSourceTS(node.extendsType);
const trueType = toSourceTS(node.trueType);
const falseType = toSourceTS(node.falseType);
const trueType = toSourceTS(node.trueType );
const falseType = toSourceTS(node.falseType );
return {type: 'condition', checkType, extendsType, trueType, falseType};
case ConstructorType: {
if (!ts.isConstructorTypeNode(node)) {
throw Error("Impossible");
}
const parameters = node.parameters.map(toSourceTS);
const ret = toSourceTS(node.type);
return {type: 'new', parameters, ret};
}
case FunctionType:
if (!ts.isFunctionTypeNode(node)) {
throw Error("Impossible");
}
const parameters = node.parameters.map(toSourceTS);
return {type: 'function', parameters};
case IndexedAccessType:
const index = toSourceTS(node.indexType);
if (!ts.isIndexedAccessTypeNode(node)) {
throw Error("Impossible");
}
const index = toSourceTS(node.indexType);
const object = toSourceTS(node.objectType);
return {type: 'indexedAccess', index, object};
case RestType:
if (!ts.isRestTypeNode(node)) {
throw Error("Impossible");
}
const annotation = toSourceTS(node.type);
return {type: 'rest', annotation};
case JSDocNullableType:
if (!ts.isJSDocNullableType(node)) {
throw Error("Impossible");
}
const t = toSourceTS(node.type);
return {type: 'union', members: [t, 'null']};
case MappedType: {
if (!ts.isMappedTypeNode(node)) {
throw Error("Impossible");
}
const result = toSourceTS(node.type);
const parameter = node.typeParameter;
if (parameter.kind === TypeParameter) {
Expand All @@ -132,6 +169,9 @@ function toSourceTS(node) {
// todo work out more: const jsdoc = `(...a: ...number) => 123
// TS even thinks it's two parameters... just go for array/[]
case Parameter:
if (!ts.isParameter(node)) {
throw Error("Impossible");
}
const type = node.type ? toSourceTS(node.type) : 'any';
const name = toSourceTS(node.name);
const ret = {type, name};
Expand All @@ -140,19 +180,28 @@ function toSourceTS(node) {
}
return ret;
case TypeQuery:
if (!ts.isTypeQueryNode(node)) {
throw Error("Impossible");
}
const argument = toSourceTS(node.exprName);
// Notify Asserter class that we have to register variables with this name
if (!requiredTypeofs[argument]) {
requiredTypeofs[argument] = 'missing';
}
return {type: 'typeof', argument};
case TypeOperator:
if (!ts.isTypeOperatorNode(node)) {
throw Error("Impossible");
}
if (node.operator === KeyOfKeyword) {
const argument = toSourceTS(node.type);
return {type: 'keyof', argument};
}
console.warn("unimplemented TypeOperator", node);
case TypeReference: {
if (!ts.isTypeReferenceNode(node)) {
throw Error("Impossible");
}
if ((typeName.text === 'Object' || typeName.text === 'Record') && typeArguments?.length === 2) {
return {
type: 'record',
Expand Down Expand Up @@ -185,23 +234,34 @@ function toSourceTS(node) {
const args = typeArguments.map(toSourceTS);
return {type: 'reference', name, args};
}
case StringKeyword:
return node.getText();
case NumberKeyword:
return node.getText();
case NamedTupleMember:
if (!ts.isNamedTupleMember(node)) {
throw Error("Impossible");
}
return toSourceTS(node.type);
case IntersectionType: {
if (!ts.isIntersectionTypeNode(node)) {
throw Error("Impossible");
}
const members = node.types.map(toSourceTS);
return {type: 'intersection', members};
}
case TupleType:
if (!ts.isTupleTypeNode(node)) {
throw Error("Impossible");
}
const elements = node.elements.map(toSourceTS);
return {type: 'tuple', elements};
case UnionType:
if (!ts.isUnionTypeNode(node)) {
throw Error("Impossible");
}
const members = node.types.map(toSourceTS);
return {type: 'union', members};
case TypeLiteral:
if (!ts.isTypeLiteralNode(node)) {
throw Error("Impossible");
}
const properties = {};
node.members.forEach(member => {
const name = toSourceTS(member.name);
Expand All @@ -210,27 +270,41 @@ function toSourceTS(node) {
});
return {type: 'object', properties};
case PropertySignature:
if (!ts.isPropertySignature(node)) {
throw Error("Impossible");
}
console.warn('toSourceTS> should not happen, handled by TypeLiteral directly');
return `${toSourceTS(node.name)}: ${toSourceTS(node.type)}`;
case Identifier:
if (!ts.isIdentifier(node)) {
throw Error("Impossible");
}
return node.text;
case ArrayType: {
if (!ts.isArrayTypeNode(node)) {
throw Error("Impossible");
}
const elementType = toSourceTS(node.elementType);
return {type: 'array', elementType};
}
case LiteralType:
if (!ts.isLiteralTypeNode(node)) {
throw Error("Impossible");
}
return toSourceTS(node.literal);
case AnyKeyword:
case BooleanKeyword:
case AnyKeyword:
case BooleanKeyword:
case StringKeyword:
case NeverKeyword:
case NullKeyword:
case NumberKeyword:
case UndefinedKeyword:
case UnknownKeyword:
case VoidKeyword:
// ts.SyntaxKind[parseType("*").kind] === 'JSDocAllType'
case JSDocAllType:
case NullKeyword:
case ThisType:
case StringLiteral:
case ThisType:
case UndefinedKeyword:
case VoidKeyword:
case UnknownKeyword:
case NeverKeyword:
return node.getText();
case TrueKeyword:
return true;
Expand All @@ -244,6 +318,9 @@ function toSourceTS(node) {
properties: {}
};
case ParenthesizedType:
if (!ts.isParenthesizedTypeNode(node)) {
throw Error("Impossible");
}
// fall-through for parentheses
return toSourceTS(node.type);
case LastTypeNode:
Expand Down

0 comments on commit 23d934f

Please sign in to comment.