Skip to content

Fix Node creation crash when using plain object as options#1424

Merged
minggangw merged 2 commits intoRobotWebTools:developfrom
minggangw:fix-1423
Mar 9, 2026
Merged

Fix Node creation crash when using plain object as options#1424
minggangw merged 2 commits intoRobotWebTools:developfrom
minggangw:fix-1423

Conversation

@minggangw
Copy link
Member

@minggangw minggangw commented Mar 9, 2026

When a plain object (e.g., { enableRosout: false }) is passed as options instead of a NodeOptions instance, options.parameterOverrides is undefined, causing TypeError: Cannot read properties of undefined (reading 'length').

Added Node._normalizeOptions() to merge plain objects with NodeOptions.defaultOptions defaults using nullish coalescing (??), ensuring all documented defaults are preserved for unspecified fields while respecting explicitly set values (including false).

Files:

  • lib/node.js — added static _normalizeOptions(), called at the top of _init()
  • test/test-node.js — added tests for node creation with plain object options and empty object options

Fix: #1423

Copilot AI review requested due to automatic review settings March 9, 2026 03:30
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a crash in Node._init() when callers pass a plain object (or partial options object) instead of a NodeOptions instance, by ensuring parameterOverrides is treated as an empty array when missing.

Changes:

  • Default options.parameterOverrides to [] before accessing .length in Node._init().
  • Add regression tests covering new Node(..., { enableRosout: false }) and new Node(..., {}).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/node.js Prevents TypeError by guarding access to options.parameterOverrides.length.
test/test-node.js Adds tests to ensure node construction does not throw with plain/partial option objects.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +148 to +167
assert.doesNotThrow(() => {
const node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
});

it('Create a node with options missing parameterOverrides', function () {
assert.doesNotThrow(() => {
const node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();
});
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapping the entire test body (including assertions) in assert.doesNotThrow() can obscure assertion failures as “unexpected exception”. Prefer letting Mocha fail naturally, or only wrapping the new rclnodejs.Node(...) call if the intent is specifically to assert construction doesn’t throw.

Suggested change
assert.doesNotThrow(() => {
const node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
});
it('Create a node with options missing parameterOverrides', function () {
assert.doesNotThrow(() => {
const node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();
});
const node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
it('Create a node with options missing parameterOverrides', function () {
const node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();

Copilot uses AI. Check for mistakes.
Comment on lines +148 to +167
assert.doesNotThrow(() => {
const node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
});

it('Create a node with options missing parameterOverrides', function () {
assert.doesNotThrow(() => {
const node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();
});
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: assert.doesNotThrow() around the whole callback will also treat any later assertion as an “unexpected exception”, which makes failures harder to diagnose. Consider removing it or limiting it to the constructor call.

Suggested change
assert.doesNotThrow(() => {
const node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
});
it('Create a node with options missing parameterOverrides', function () {
assert.doesNotThrow(() => {
const node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();
});
let node;
assert.doesNotThrow(() => {
node = new rclnodejs.Node('plain_opts_node', '/ns', undefined, {
enableRosout: false,
});
});
assert.strictEqual(node.name(), 'plain_opts_node');
node.destroy();
});
it('Create a node with options missing parameterOverrides', function () {
let node;
assert.doesNotThrow(() => {
node = new rclnodejs.Node(
'partial_opts_node',
'/ns',
undefined,
{}
);
});
assert.strictEqual(node.name(), 'partial_opts_node');
node.destroy();

Copilot uses AI. Check for mistakes.
Comment on lines 151 to 155
// override cli parameterOverrides with those specified in options
if (options.parameterOverrides.length > 0) {
for (const parameter of options.parameterOverrides) {
const parameterOverrides = options.parameterOverrides || [];
if (parameterOverrides.length > 0) {
for (const parameter of parameterOverrides) {
if (!(parameter instanceof Parameter)) {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this code path is now explicitly supporting “plain object” / partial options, only defaulting parameterOverrides still leaves other NodeOptions fields as undefined (e.g. enableRosout, startParameterServices, startTypeDescriptionService), which changes behavior vs NodeOptions.defaultOptions even when callers pass {}. Consider normalizing options at the start of _init() (e.g., if it’s not a NodeOptions instance, create one with defaults and then apply provided fields via setters) so documented defaults and validation are preserved for all options, not just parameterOverrides.

Copilot uses AI. Check for mistakes.
@coveralls
Copy link

coveralls commented Mar 9, 2026

Coverage Status

coverage: 85.881% (+0.05%) from 85.827%
when pulling 47116ce on minggangw:fix-1423
into 035ce9f on RobotWebTools:develop.

@minggangw minggangw merged commit 2c0ac64 into RobotWebTools:develop Mar 9, 2026
23 of 24 checks passed
minggangw added a commit that referenced this pull request Mar 9, 2026
When a plain object (e.g., `{ enableRosout: false }`) is passed as options instead of a `NodeOptions` instance, `options.parameterOverrides` is `undefined`, causing `TypeError: Cannot read properties of undefined (reading 'length')`.

Added `Node._normalizeOptions()` to merge plain objects with `NodeOptions.defaultOptions` defaults using nullish coalescing (`??`), ensuring all documented defaults are preserved for unspecified fields while respecting explicitly set values (including `false`).

**Files:**
- `lib/node.js` — added `static _normalizeOptions()`, called at the top of `_init()`
- `test/test-node.js` — added tests for node creation with plain object options and empty object options

Fix: #1423
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node creation, parameter parameterOverrides do not have a default value

3 participants