Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

better typescript typings #551

Merged
merged 17 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 102 additions & 51 deletions rostsd_gen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function generateAll() {
// load pkg and interface info (msgs and srvs)
const generatedPath = path.join(__dirname, '../generated/');
const pkgInfos = getPkgInfos(generatedPath);

// write message.d.ts file
const messagesFilePath = path.join(__dirname, '../types/interfaces.d.ts');
const fd = fs.openSync(messagesFilePath, 'w');
Expand All @@ -56,17 +56,19 @@ function getPkgInfos(generatedRoot) {

const pkgInfo = {
name: pkg,
messages: []
messages: [],
services: []
};

const pkgPath = path.join(rootDir, pkg);
const files = fs.readdirSync(pkgPath);
const files = fs.readdirSync(pkgPath).filter(fn => fn.endsWith('.js'));

for (let filename of files) {
const typeClass = fileName2Typeclass(filename);

if (typeClass.type === 'srv') { // skip __srv__<action>
if (!typeClass.name.endsWith('Request') && !typeClass.name.endsWith('Response')) {
pkgInfo.services.push(typeClass);
continue;
}
}
Expand All @@ -93,10 +95,12 @@ function getPkgInfos(generatedRoot) {


function savePkgInfoAsTSD(pkgInfos, fd) {

let fullMessageNames = ['string'];
let messagesMap = {
string: 'string',
};

fs.writeSync(fd, '/* eslint-disable camelcase */\n');
fs.writeSync(fd, '/* eslint-disable max-len */\n');
fs.writeSync(fd, '// DO NOT EDIT\n');
fs.writeSync(fd, '// This file is generated by the rostsd_gen script\n\n');

Expand All @@ -115,7 +119,7 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
for (const msgInfo of pkgInfo.messages) {

if (msgInfo.typeClass.type != curNS) {
if (curNS) { // close current ns
if (curNS) { // close current ns
fs.writeSync(fd, ' }\n');
}

Expand All @@ -129,10 +133,11 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
}

saveMsgInfoAsTSD(msgInfo, fd);
saveMsgWrapperAsTSD(msgInfo, fd);

// full path to this msg
const fullMessageName = `${pkgInfo.name}.${msgInfo.typeClass.type}.${msgInfo.typeClass.name}`;
fullMessageNames.push(fullMessageName);
const fullMessageName = `${pkgInfo.name}/${msgInfo.typeClass.type}/${msgInfo.typeClass.name}`;
messagesMap[fullMessageName] = `${pkgInfo.name}.${msgInfo.typeClass.type}.${msgInfo.typeClass.name}`;
}

if (curNS) {
Expand All @@ -144,66 +149,112 @@ function savePkgInfoAsTSD(pkgInfos, fd) {
fs.writeSync(fd, ' }\n\n');
}

// write type alias for Message
// e.g. type Message =
// string |
// std_msgs.msg.Bool |
// std_msgs.msg.Byte |
// ...
fs.writeSync(fd, ' type Message = \n');
for (let i=0; i < fullMessageNames.length; i++) {
fs.writeSync(fd, ' ' + fullMessageNames[i]);
if (i != fullMessageNames.length-1) {
fs.writeSync(fd, ' |\n');
}
// write messages type mappings
fs.writeSync(fd, ' type MessagesMap = {\n');
for (const key in messagesMap) {
fs.writeSync(fd, ` '${key}': ${messagesMap[key]},\n`);
}
fs.writeSync(fd, ' };\n');
fs.writeSync(fd, ' type MessageTypeClassName = keyof MessagesMap;\n');
fs.writeSync(fd, ' type Message = MessagesMap[MessageTypeClassName];\n');
fs.writeSync(fd, ' type MessageType<T> = T extends MessageTypeClassName ? MessagesMap[T] : object;\n\n');

// write message wrappers mappings
fs.writeSync(fd, ' type MessageTypeClassWrappersMap = {\n');
for (const key in messagesMap) {
if (key === 'string') {
fs.writeSync(fd, " 'string': never,\n");
continue;
}
fs.writeSync(fd, ` '${key}': ${messagesMap[key]}_WrapperType,\n`);
}
fs.writeSync(fd, ' };\n');
fs.writeSync(fd, ' type MessageWrapperType<T> = T extends MessageTypeClassName ? MessageTypeClassWrappersMap[T] : object;\n\n');

// write service type class string
const services = [];
for (const pkg of pkgInfos) {
services.push(...pkg.services);
}
if (!services.length) {
fs.writeSync(fd, ' type ServiceTypeClassName = never;\n\n');
} else {
fs.writeSync(fd, ' type ServiceTypeClassName = \n');
for (let i = 0; i < services.length; i++) {
const srv = services[i];
const srvTypeClassStr = `${srv.package}/${srv.type}/${srv.name}`;
fs.writeSync(fd, ` '${srvTypeClassStr}'`);

if (i !== services.length - 1) {
fs.writeSync(fd, ' |\n');
}
}
fs.writeSync(fd, ';\n\n');
}
fs.writeSync(fd, ';\n');

fs.writeSync(fd, ' type TypeClassName = MessageTypeClassName | ServiceTypeClassName;\n');

// close module declare
fs.writeSync(fd, '}\n');

fs.closeSync(fd);
}


function saveMsgInfoAsTSD(msgInfo, fd) {

// write type = xxxx {
const typeTemplate =
` export type ${msgInfo.typeClass.name} = {\n`;

fs.writeSync(fd, typeTemplate);

// write constant definitions
for (let i = 0; i < msgInfo.def.constants.length; i++) {
const constant = msgInfo.def.constants[i];
function saveMsgWrapperAsTSD(msgInfo, fd) {
const msgName = msgInfo.typeClass.name;
fs.writeSync(fd, ` export type ${msgName}_WrapperType = {\n`);
for (const constant of msgInfo.def.constants) {
const constantType = primitiveType2JSName(constant.type);
const tmpl = (constantType == 'string') ?
` ${constant.name}: '${constant.value}'` :
` ${constant.name}: ${constant.value}`;
fs.writeSync(fd, tmpl);

if (i != msgInfo.def.constants.length - 1) {
fs.writeSync(fd, ',\n');
} else if (msgInfo.def.fields.length > 0) {
fs.writeSync(fd, ',\n');
}
fs.writeSync(fd, ` readonly ${constant.name}: ${constantType},\n`);
}
fs.writeSync(fd, ` new(other?: ${msgName}): ${msgName},\n`);
fs.writeSync(fd, ' }\n');
}

// write field definitions

/**
* Writes the message fields as typescript definitions.
*
* @param {*} msgInfo ros message info
* @param {*} fd file descriptor
* @param {string} indent The amount of indent, in spaces
* @param {string} lineEnd The character to put at the end of each line, usually ','
* or ';'
* @param {string} typePrefix The prefix to put before the type name for
* non-primitive types
* @returns {undefined}
*/
function saveMsgFieldsAsTSD(msgInfo, fd, indent=0, lineEnd=',', typePrefix='') {
const indentStr = ' '.repeat(indent);
for (let i = 0; i < msgInfo.def.fields.length; i++) {
const field = msgInfo.def.fields[i];
const fieldType = fieldType2JSName(field);
const tmpl = ` ${field.name}: ${fieldType}`;
let fieldType = fieldType2JSName(field);
let tp = field.type.isPrimitiveType ? '' : typePrefix;
if (typePrefix === 'rclnodejs.') {
fieldType = 'any';
tp = '';
}
const tmpl = `${indentStr}${field.name}: ${tp}${fieldType}`;
fs.writeSync(fd, tmpl);
if (field.type.isArray) {
fs.writeSync(fd, '[]');
}
if (i != msgInfo.def.fields.length - 1) {
fs.writeSync(fd, ',');
}
fs.writeSync(fd, lineEnd);
fs.writeSync(fd, '\n');
}
}


function saveMsgInfoAsTSD(msgInfo, fd) {
// write type = xxxx {
const typeTemplate =
` export type ${msgInfo.typeClass.name} = {\n`;

fs.writeSync(fd, typeTemplate);

// write field definitions
saveMsgFieldsAsTSD(msgInfo, fd, 8);

// end of def
fs.writeSync(fd, ' };\n');
Expand All @@ -223,7 +274,7 @@ function primitiveType2JSName(type) {
switch (type) {
case 'char':
case 'byte':
case 'uin8':
case 'uint8':
case 'int8':
case 'int16':
case 'uint16':
Expand Down Expand Up @@ -256,7 +307,7 @@ function fileName2Typeclass(filename) {
const array = filename.split(regex).filter(Boolean);

if (!array || array.length != 3) {
// todo: throw error
// todo: throw error
console.log('ERRORRROOROR', array);
return;
}
Expand Down
34 changes: 17 additions & 17 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@

// eslint-disable-next-line spaced-comment
/// <reference path="base.d.ts" />
/// <reference path="base.d.ts" />

declare module 'rclnodejs' {

/**
* Create a node.
*
*
* @remarks
* See {@link Node}
*
*
* @param nodeName - The name used to register in ROS.
* @param namespace - The namespace used in ROS, default is an empty string.
* @param context - The context, default is Context.defaultContext().
Expand All @@ -19,68 +18,69 @@ declare module 'rclnodejs' {

/**
* Init the module.
*
*
* @param context - The context, default is Context.defaultContext().
* @returns A Promise.
*/
function init(context?: Context): Promise<void>;

/**
* Spin up the node event loop to check for incoming events.
*
*
* @param node - The node to be spun.
* @param timeout - ms to wait, block forever if negative, return immediately when 0, default is 10.
*/
function spin(node: Node, timeout?: number): void;

/**
* Stop all activity, destroy all nodes and node components.
*
*
* @param context - The context, default is Context.defaultContext()
*/
function shutdown(context?: Context): void;

/**
* Test if the module is shutdown.
*
*
* @returns True if the module is shut down, otherwise return false.
*/
function isShutdown(): boolean;

/**
* Get the interface package, which is used by publisher/subscription or client/service.
*
*
* @param name - The name of interface to be required.
* @returns The object of the required package/interface.
*/
function require<T extends MessageTypeClassName>(name: T): MessageWrapperType<T>;
function require(name: string): object;

/**
* Generate JavaScript structs files from the IDL of
* messages(.msg) and services(.srv).
* messages(.msg) and services(.srv).
* Search packages which locate under path $AMENT_PREFIX_PATH
* and output JS files into the 'generated' folder.
* and output JS files into the 'generated' folder.
* Any existing files under the generated folder will
* be overwritten.
*
*
* @returns A Promise.
*/
function regenerateAll(): Promise<void>;

/**
* Judge if the topic or service is hidden,
*
* Judge if the topic or service is hidden,
*
* @remarks
* See {@link http://design.ros2.org/articles/topic_and_service_names.html#hidden-topic-or-service-names}
*
*
* @param name - Name of topic or service.
* @returns True if a given topic or service name is hidden, otherwise False.
*/
function isTopicOrServiceHidden(name: string): boolean;

/**
* Expand a given topic name using given node name and namespace.
*
*
* @param topicName - Topic name to be expanded.
* @param nodeName - Name of the node that this topic is associated with.
* @param nodeNamespace - Namespace that the topic is within.
Expand All @@ -91,7 +91,7 @@ declare module 'rclnodejs' {

/**
* Create a plain JavaScript message object.
*
*
* @param type - type identifier, acceptable formats could be 'std_msgs/std/String'
* or {package: 'std_msgs', type: 'msg', name: 'String'}
* @returns A Message object or undefined if type is not recognized.
Expand Down
Loading