Repository: intel/rohd (dart)

Issue:
Port can be initialized with empty name
### Describe the bug

Port of the Module can be initialized with empty name.

### To Reproduce

```dart
// system verilog generate: input logic ,
final signal1 = addInput('', unamedSignal); 

```

### Expected behavior

throw exception on empty string port initialization

### Actual behavior

The initialization passed and the generated system verilog contains errors.

### Additional: Dart SDK info

_No response_

### Additional: pubspec.yaml

_No response_

### Additional: Context

_No response_

Pull request description:
<!-- Please make sure you check out the contribution guidelines before submitting a pull request! -->

## Description & Motivation

Port of the Module can be initialized with an empty name.

## Related Issue(s)

Fix #281 

## Testing

Added Logic name and definition name test

## Backwards-compatibility

> Is this a breaking change that will not be backwards-compatible? If yes, how so?

No

## Documentation

> Does the change require any updates to documentation? If so, where? Are they included?

No

Patch:
diff --git a/lib/src/exceptions/name/empty_reserved_name_exception.dart b/lib/src/exceptions/name/empty_reserved_name_exception.dart
new file mode 100644
index 000000000..650e264fe
--- /dev/null
+++ b/lib/src/exceptions/name/empty_reserved_name_exception.dart
@@ -0,0 +1,21 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// empty_reserved_name_exception.dart
+/// An exception that thrown when a reserved name is empty string.
+///
+/// 2023 March 14
+/// Author: Yao Jing Quek <yao.jing.quek@intel.com>
+///
+
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that thrown when a reserved name is `null`.
+class EmptyReservedNameException extends RohdException {
+  /// Display error [message] on empty reserved name string.
+  ///
+  /// Creates a [EmptyReservedNameException] with an optional error [message].
+  EmptyReservedNameException(
+      [super.message = 'Reserved Name cannot be empty string '
+          'if reserved name set to true']);
+}
diff --git a/lib/src/exceptions/name/invalid_portname_exceptions.dart b/lib/src/exceptions/name/invalid_portname_exceptions.dart
new file mode 100644
index 000000000..c48516305
--- /dev/null
+++ b/lib/src/exceptions/name/invalid_portname_exceptions.dart
@@ -0,0 +1,21 @@
+/// Copyright (C) 2023 Intel Corporation
+/// SPDX-License-Identifier: BSD-3-Clause
+///
+/// invalid_portname_exceptions.dart
+/// An exception that thrown when a port or interface name is invalid.
+///
+/// 2023 April 10
+/// Author: Yao Jing Quek <yao.jing.quek@intel.com>
+
+import 'package:rohd/src/exceptions/rohd_exception.dart';
+
+/// An exception that thrown when a port or interface name is invalid.
+class InvalidPortNameException extends RohdException {
+  /// Display error [message] on invalid reserved name.
+  ///
+  /// Creates a [InvalidPortNameException] with an optional error [message].
+  InvalidPortNameException(String name)
+      : super(
+            'Invalid name "$name", must be legal SystemVerilog and not collide'
+            ' with any keywords.');
+}
diff --git a/lib/src/exceptions/name/name_exceptions.dart b/lib/src/exceptions/name/name_exceptions.dart
index 09ffeb24f..969507957 100644
--- a/lib/src/exceptions/name/name_exceptions.dart
+++ b/lib/src/exceptions/name/name_exceptions.dart
@@ -1,5 +1,7 @@
 /// Copyright (C) 2022 Intel Corporation
 /// SPDX-License-Identifier: BSD-3-Clause
 
+export 'empty_reserved_name_exception.dart';
+export 'invalid_portname_exceptions.dart';
 export 'invalid_reserved_name_exception.dart';
 export 'null_reserved_name_exception.dart';
diff --git a/lib/src/interface.dart b/lib/src/interface.dart
index 8191274c6..2433d5c50 100644
--- a/lib/src/interface.dart
+++ b/lib/src/interface.dart
@@ -10,7 +10,9 @@
 
 import 'package:collection/collection.dart';
 import 'package:meta/meta.dart';
+
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/name/name_exceptions.dart';
 import 'package:rohd/src/utilities/sanitizer.dart';
 
 /// An extension of [Logic] useful for [Interface] definitions.
@@ -18,9 +20,7 @@ class Port extends Logic {
   /// Constructs a [Logic] intended to be used for ports in an [Interface].
   Port(String name, [int width = 1]) : super(name: name, width: width) {
     if (!Sanitizer.isSanitary(name)) {
-      throw Exception(
-          'Invalid name "$name", must be legal SystemVerilog and not collide'
-          ' with any keywords.');
+      throw InvalidPortNameException(name);
     }
   }
 }
diff --git a/lib/src/logic.dart b/lib/src/logic.dart
index d6e800d96..c10cde618 100644
--- a/lib/src/logic.dart
+++ b/lib/src/logic.dart
@@ -432,7 +432,9 @@ class Logic {
   /// The default value for [width] is 1.  The [name] should be synthesizable
   /// to the desired output (e.g. SystemVerilog).
   Logic({String? name, int width = 1})
-      : name = name == null ? 's${_signalIdx++}' : Sanitizer.sanitizeSV(name),
+      : name = (name == null || name.isEmpty)
+            ? 's${_signalIdx++}'
+            : Sanitizer.sanitizeSV(name),
         _wire = _Wire(width: width) {
     if (width < 0) {
       throw Exception('Logic width must be greater than or equal to 0.');
diff --git a/lib/src/module.dart b/lib/src/module.dart
index 8daf6edaa..2da8498d5 100644
--- a/lib/src/module.dart
+++ b/lib/src/module.dart
@@ -133,17 +133,17 @@ abstract class Module {
   /// Return string type definition name if validation passed
   /// else throw exception.
   ///
-  /// This validation method ensure that [definitionName] is valid if
-  /// [reserveDefinitionName] set to `true`.
-  static String? _nameValidation(
-      String? definitionName, bool reserveDefinitionName) {
-    if (reserveDefinitionName && definitionName == null) {
+  /// This validation method ensure that [name] is valid if
+  /// [reserveName] set to `true`.
+  static String? _nameValidation(String? name, bool reserveName) {
+    if (reserveName && name == null) {
       throw NullReservedNameException();
-    } else if (reserveDefinitionName &&
-        !Sanitizer.isSanitary(definitionName!)) {
+    } else if (reserveName && name!.isEmpty) {
+      throw EmptyReservedNameException();
+    } else if (reserveName && !Sanitizer.isSanitary(name!)) {
       throw InvalidReservedNameException();
     } else {
-      return definitionName;
+      return name;
     }
   }
 
@@ -181,7 +181,7 @@ abstract class Module {
       this.reserveName = false,
       String? definitionName,
       this.reserveDefinitionName = false})
-      : _uniqueInstanceName = name,
+      : _uniqueInstanceName = _nameValidation(name, reserveName) ?? name,
         _definitionName =
             _nameValidation(definitionName, reserveDefinitionName);
 
@@ -555,9 +555,7 @@ abstract class Module {
   /// Checks whether a port name is safe to add (e.g. no duplicates).
   void _checkForSafePortName(String name) {
     if (!Sanitizer.isSanitary(name)) {
-      throw Exception(
-          'Invalid name "$name", must be legal SystemVerilog and not collide'
-          ' with any keywords.');
+      throw InvalidPortNameException(name);
     }
     if (outputs.containsKey(name) || inputs.containsKey(name)) {
       throw Exception('Already defined a port with name "$name".');
diff --git a/lib/src/utilities/sanitizer.dart b/lib/src/utilities/sanitizer.dart
index 13902f6a2..df97aa2e1 100644
--- a/lib/src/utilities/sanitizer.dart
+++ b/lib/src/utilities/sanitizer.dart
@@ -33,6 +33,11 @@ abstract class Sanitizer {
       newName = 's$newName';
     }
 
+    // name can't be empty string
+    if (newName.isEmpty) {
+      return 'empty_';
+    }
+
     // add `_` to the end if the name is a SystemVerilog keyword
     while (_reservedSVKeywords.contains(newName)) {
       // ignore: use_string_buffers


Test patch:
diff --git a/test/definition_name_test.dart b/test/definition_name_test.dart
index ddd269f0b..99798efd4 100644
--- a/test/definition_name_test.dart
+++ b/test/definition_name_test.dart
@@ -22,10 +22,9 @@ class DefinitionName {
 }
 
 class ValidDefNameModule extends Module {
-  ValidDefNameModule(Logic a, DefinitionName defName)
+  ValidDefNameModule(Logic a, DefinitionName defName,
+      {super.name = 'specialNameInstance', super.reserveName = false})
       : super(
-          name: 'specialNameInstance',
-          reserveName: false,
           definitionName: defName.name,
           reserveDefinitionName: defName.isReserved,
         ) {
@@ -64,6 +63,14 @@ void main() {
         ValidDefNameModule(Logic(), defName);
       }, throwsA((dynamic e) => e is NullReservedNameException));
     });
+    test(
+        'WHEN definition name is empty string, THEN expect to throw exception.',
+        () async {
+      final defName = DefinitionName(name: '', isReserved: true);
+      expect(() async {
+        ValidDefNameModule(Logic(), defName);
+      }, throwsA((dynamic e) => e is EmptyReservedNameException));
+    });
   });
 
   group('GIVEN that Reserved Definition Name is FALSE,', () {
@@ -96,4 +103,13 @@ void main() {
       });
     });
   });
+
+  test('should return Exception when super.name is empty string', () async {
+    final defName =
+        DefinitionName(name: DefinitionName.getValidName(), isReserved: true);
+
+    expect(() async {
+      ValidDefNameModule(Logic(), defName, name: '', reserveName: true);
+    }, throwsA((dynamic e) => e is EmptyReservedNameException));
+  });
 }
diff --git a/test/logic_name_test.dart b/test/logic_name_test.dart
index e33749558..670b7fc35 100644
--- a/test/logic_name_test.dart
+++ b/test/logic_name_test.dart
@@ -9,12 +9,13 @@
 ///
 
 import 'package:rohd/rohd.dart';
+import 'package:rohd/src/exceptions/name/name_exceptions.dart';
 import 'package:rohd/src/utilities/sanitizer.dart';
 import 'package:test/test.dart';
 
 class LogicTestModule extends Module {
-  LogicTestModule(Logic a) {
-    addInput(a.name, a, width: a.width);
+  LogicTestModule(String logicName) {
+    addInput(logicName, Logic());
   }
 }
 
@@ -42,4 +43,20 @@ void main() {
     final bus = Logic();
     expect(bus.name, equals('s0'));
   });
+
+  test(
+      'GIVEN logic name is empty string THEN expected to see autogeneration '
+      'of name', () async {
+    final bus = Logic(name: '');
+    expect(bus.name, isNot(equals('')));
+  });
+
+  group('port name:', () {
+    test('GIVEN port name is empty string THEN expected to see exception',
+        () async {
+      expect(() async {
+        LogicTestModule('');
+      }, throwsA((dynamic e) => e is InvalidPortNameException));
+    });
+  });
 }