@itrocks/property-type
Version:
Runtime type reflection from TypeScript declaration files for properties
218 lines • 8.23 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnknownType = exports.UnionType = exports.TypeType = exports.LiteralType = exports.IntersectionType = exports.RecordType = exports.CompositeType = exports.CollectionType = exports.CanonicalType = exports.PropertyType = void 0;
exports.isCanonical = isCanonical;
exports.isLiteral = isLiteral;
exports.isType = isType;
exports.propertyTypesFromFile = propertyTypesFromFile;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const typescript_1 = __importDefault(require("typescript"));
class PropertyType {
type;
optional;
constructor(type, optional = false) {
this.type = type;
this.optional = optional;
}
get lead() { return this.type; }
}
exports.PropertyType = PropertyType;
class CanonicalType extends PropertyType {
constructor(type) { super(type); }
}
exports.CanonicalType = CanonicalType;
class CollectionType extends PropertyType {
elementType;
constructor(type, elementType) {
super(type);
this.elementType = elementType;
}
}
exports.CollectionType = CollectionType;
class CompositeType extends PropertyType {
types;
constructor(types) {
super(types[0].type);
this.types = types;
}
get lead() { return this.types[0].lead; }
}
exports.CompositeType = CompositeType;
class RecordType extends PropertyType {
keyType;
elementType;
constructor(keyType, elementType) {
super(Object);
this.keyType = keyType;
this.elementType = elementType;
}
}
exports.RecordType = RecordType;
class IntersectionType extends CompositeType {
}
exports.IntersectionType = IntersectionType;
class LiteralType extends PropertyType {
value;
constructor(value) {
super(literalValueType(value));
this.value = value;
}
}
exports.LiteralType = LiteralType;
class TypeType extends PropertyType {
args;
constructor(type, args) {
super(type);
this.args = args;
}
}
exports.TypeType = TypeType;
class UnionType extends CompositeType {
}
exports.UnionType = UnionType;
class UnknownType extends PropertyType {
raw;
constructor(raw) {
super(undefined);
this.raw = raw;
}
}
exports.UnknownType = UnknownType;
function isCanonical(propertyType, type) {
return (propertyType instanceof CanonicalType) && ((arguments.length === 1) || (propertyType.type === type));
}
function isLiteral(propertyType, literal) {
return (propertyType instanceof LiteralType) && ((arguments.length === 1) || (propertyType.value === literal));
}
function isType(propertyType, type) {
return (propertyType instanceof TypeType) && ((arguments.length === 1) || (propertyType.type === type));
}
function literalValueType(literal) {
switch (typeof literal) {
case 'bigint': return BigInt;
case 'boolean': return Boolean;
case 'number': return Number;
case 'string': return String;
case 'symbol': return Symbol;
}
}
function nodeToCanonicalType(node) {
const kind = node.kind;
const kinds = typescript_1.default.SyntaxKind;
switch (kind) {
case kinds.BigIntKeyword: return new CanonicalType(BigInt);
case kinds.BooleanKeyword: return new CanonicalType(Boolean);
case kinds.NumberKeyword: return new CanonicalType(Number);
case kinds.ObjectKeyword: return new CanonicalType(Object);
case kinds.StringKeyword: return new CanonicalType(String);
case kinds.SymbolKeyword: return new CanonicalType(Symbol);
}
}
function nodeToLiteralType(node) {
if (!typescript_1.default.isLiteralTypeNode(node))
return;
const kinds = typescript_1.default.SyntaxKind;
const literal = node.literal;
switch (literal.kind) {
case kinds.FalseKeyword: return new LiteralType(false);
case kinds.NullKeyword: return new LiteralType(null);
case kinds.TrueKeyword: return new LiteralType(true);
case kinds.UndefinedKeyword: return new LiteralType(undefined);
}
if (typescript_1.default.isNumericLiteral(literal)) {
return new LiteralType(+literal.text);
}
if (typescript_1.default.isStringLiteral(literal)) {
return new LiteralType(literal.text);
}
}
function nodeToType(node, typeImports) {
if (typescript_1.default.isArrayTypeNode(node)) {
return new CollectionType(Array, nodeToType(node.elementType, typeImports));
}
if (typescript_1.default.isIntersectionTypeNode(node)) {
return new IntersectionType(node.types.map(node => nodeToType(node, typeImports)));
}
if (typescript_1.default.isUnionTypeNode(node)) {
return new UnionType(node.types.map(node => nodeToType(node, typeImports)));
}
return nodeToCanonicalType(node)
?? nodeToLiteralType(node)
?? nodeToTypeType(node, typeImports)
?? new UnknownType(node.getText());
}
function nodeToTypeType(node, typeImports) {
if (!typescript_1.default.isTypeReferenceNode(node))
return;
const name = node.typeName.getText();
const args = node.typeArguments?.map(node => nodeToType(node, typeImports));
return ((name === 'Record') && (args?.length === 2))
? new RecordType(args[0], args[1])
: new TypeType(strToType(name, typeImports), args);
}
function propertyTypesFromFile(file) {
const content = readFile(file);
const filePath = (0, node_path_1.dirname)(file);
const sourceFile = typescript_1.default.createSourceFile(file, content, typescript_1.default.ScriptTarget.Latest, true);
const propertyTypes = {};
const typeImports = {};
function parseNode(node) {
if (typescript_1.default.isImportDeclaration(node) && node.importClause) {
let importPath = node.moduleSpecifier.text;
if ((importPath[0] === '.') && !importPath.endsWith('.js')) {
importPath += '.js';
}
const importFile = (importPath[0] === '.')
? (0, node_path_1.normalize)(filePath + '/' + importPath)
: importPath;
if (node.importClause.name) {
typeImports[node.importClause.name.getText()] = { import: importFile, name: 'default' };
}
const namedBindings = node.importClause.namedBindings;
if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
for (const importSpecifier of namedBindings.elements) {
const name = importSpecifier.name.getText();
const alias = importSpecifier.propertyName?.getText() ?? name;
typeImports[alias] = { import: importFile, name };
}
}
}
if (typescript_1.default.isClassDeclaration(node)
&& node.name
&& node.modifiers?.some(modifier => modifier.kind === typescript_1.default.SyntaxKind.ExportKeyword)) {
const className = node.name.getText();
typeImports[className] = { import: file, name: className };
for (const member of node.members) {
if (typescript_1.default.isPropertyDeclaration(member) && member.type) {
const type = nodeToType(member.type, typeImports);
type.optional = !!member.questionToken;
propertyTypes[member.name.text] = type;
}
}
return;
}
typescript_1.default.forEachChild(node, parseNode);
}
parseNode(sourceFile);
return propertyTypes;
}
function readFile(file) {
try {
return (0, node_fs_1.readFileSync)(file.substring(0, file.lastIndexOf('.')) + '.d.ts', 'utf8');
}
catch (exception) {
console.error('file', file);
throw exception;
}
}
function strToType(type, typeImports) {
const typeImport = typeImports[type];
return typeImport
? require(typeImport.import)[typeImport.name]
: globalThis[type];
}
//# sourceMappingURL=property-type.js.map