Name conflict resolution with reserved Dart Keywords (#34)

Closes #21.

- Added reserved keyword conflict resolution to `UniqueNamer`.
- Added tests (`collision_tests/reserved_word_collision_test.dart`)
- Bug fix: UniqueNamers used by declarations are now reset to initial state before generating code. This ensures multiple calls to Library.generate will produce the same results
diff --git a/lib/src/code_generator/dart_keywords.dart b/lib/src/code_generator/dart_keywords.dart
new file mode 100644
index 0000000..2f3d4d6
--- /dev/null
+++ b/lib/src/code_generator/dart_keywords.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Dart reserved keywords, used for resolving conflict with a name.
+///
+/// Source: https://dart.dev/guides/language/language-tour#keywords.
+const keywords = {
+  'abstract',
+  'else',
+  'import',
+  'super',
+  'as',
+  'enum',
+  'in',
+  'switch',
+  'assert',
+  'export',
+  'interface',
+  'sync',
+  'async',
+  'extends',
+  'is',
+  'this',
+  'await',
+  'extension',
+  'library',
+  'throw',
+  'break',
+  'external',
+  'mixin',
+  'true',
+  'case',
+  'factory',
+  'new',
+  'try',
+  'catch',
+  'false',
+  'null',
+  'typedef',
+  'class',
+  'final',
+  'on',
+  'var',
+  'const',
+  'finally',
+  'operator',
+  'void',
+  'continue',
+  'for',
+  'part',
+  'while',
+  'covariant',
+  'Function',
+  'rethrow',
+  'with',
+  'default',
+  'get',
+  'return',
+  'yield',
+  'deferred',
+  'hide',
+  'set',
+  'do',
+  'if',
+  'show',
+  'dynamic',
+  'implements',
+  'static'
+};
diff --git a/lib/src/code_generator/library.dart b/lib/src/code_generator/library.dart
index 6c292fd..10b73a9 100644
--- a/lib/src/code_generator/library.dart
+++ b/lib/src/code_generator/library.dart
@@ -31,7 +31,6 @@
     final lookUpBindings = bindings.whereType<LookUpBinding>().toList();
     final noLookUpBindings = bindings.whereType<NoLookUpBinding>().toList();
 
-    //TODO(21): Resolve dart keyword as identifiers.
     /// Handle any declaration-declaration name conflict in [lookUpBindings].
     final lookUpDeclConflictHandler = UniqueNamer({});
     for (final b in lookUpBindings) {
diff --git a/lib/src/code_generator/utils.dart b/lib/src/code_generator/utils.dart
index 800b7f8..6bc9f2c 100644
--- a/lib/src/code_generator/utils.dart
+++ b/lib/src/code_generator/utils.dart
@@ -1,6 +1,15 @@
+import 'dart_keywords.dart';
+
 class UniqueNamer {
   final Set<String> _usedUpNames;
-  UniqueNamer(this._usedUpNames);
+
+  /// Creates a UniqueNamer with given [usedUpNames] and Dart reserved keywords.
+  UniqueNamer(Set<String> usedUpNames)
+      : assert(keywords.intersection(usedUpNames).isEmpty),
+        _usedUpNames = {...keywords, ...usedUpNames};
+
+  /// Creates a UniqueNamer with given [usedUpNames] only.
+  UniqueNamer._raw(this._usedUpNames);
 
   /// Returns a unique name by appending `_<int>` to it if necessary.
   ///
@@ -34,6 +43,8 @@
   bool isUnique(String name) {
     return !_usedUpNames.contains(name);
   }
+
+  UniqueNamer clone() => UniqueNamer._raw({..._usedUpNames});
 }
 
 /// Converts [text] to a dart doc comment.
diff --git a/lib/src/code_generator/writer.dart b/lib/src/code_generator/writer.dart
index 55a451a..b46c4f9 100644
--- a/lib/src/code_generator/writer.dart
+++ b/lib/src/code_generator/writer.dart
@@ -27,6 +27,11 @@
   String _dylibIdentifier;
   String get dylibIdentifier => _dylibIdentifier;
 
+  /// Initial namers set after running constructor. Namers are reset to this
+  /// initial state everytime [generate] is called.
+  UniqueNamer _initialTopLevelUniqueNamer, _initialWrapperLevelUniqueNamer;
+
+  /// Used by [Binding]s for generating required code.
   UniqueNamer _topLevelUniqueNamer, _wrapperLevelUniqueNamer;
   UniqueNamer get topLevelUniqueNamer => _topLevelUniqueNamer;
   UniqueNamer get wrapperLevelUniqueNamer => _wrapperLevelUniqueNamer;
@@ -51,20 +56,20 @@
       ..addAll(globalLevelNameSet)
       ..addAll(wrapperLevelNameSet);
 
-    _topLevelUniqueNamer = UniqueNamer(globalLevelNameSet);
-    _wrapperLevelUniqueNamer = UniqueNamer(wrapperLevelNameSet);
+    _initialTopLevelUniqueNamer = UniqueNamer(globalLevelNameSet);
+    _initialWrapperLevelUniqueNamer = UniqueNamer(wrapperLevelNameSet);
     final allLevelsUniqueNamer = UniqueNamer(allNameSet);
 
     /// Wrapper class name must be unique among all names.
     _className = allLevelsUniqueNamer.makeUnique(className);
-    wrapperLevelUniqueNamer.markUsed(_className);
-    topLevelUniqueNamer.markUsed(_className);
+    _initialWrapperLevelUniqueNamer.markUsed(_className);
+    _initialTopLevelUniqueNamer.markUsed(_className);
 
     /// [_ffiLibraryPrefix] should be unique in top level.
-    _ffiLibraryPrefix = topLevelUniqueNamer.makeUnique('ffi');
+    _ffiLibraryPrefix = _initialTopLevelUniqueNamer.makeUnique('ffi');
 
     /// [_dylibIdentifier] should be unique in top level.
-    _dylibIdentifier = wrapperLevelUniqueNamer.makeUnique('_dylib');
+    _dylibIdentifier = _initialTopLevelUniqueNamer.makeUnique('_dylib');
 
     /// Finding a unique prefix for Array Helper Classes and store into
     /// [_arrayHelperClassPrefix].
@@ -79,12 +84,23 @@
         _arrayHelperClassPrefix = '${base}${suffixInt}';
       }
     }
+
+    _resetUniqueNamersNamers();
+  }
+
+  /// Resets the namers to initial state. Namers are reset before generating.
+  void _resetUniqueNamersNamers() {
+    _topLevelUniqueNamer = _initialTopLevelUniqueNamer.clone();
+    _wrapperLevelUniqueNamer = _initialWrapperLevelUniqueNamer.clone();
   }
 
   /// Writes all bindings to a String.
   String generate() {
     final s = StringBuffer();
 
+    // Reset unique namers to initial state.
+    _resetUniqueNamersNamers();
+
     // Write file header (if any).
     if (header != null) {
       s.write(header);
diff --git a/test/collision_tests/decl_decl_collision_test.dart b/test/collision_tests/decl_decl_collision_test.dart
index 3b09841..24bd079 100644
--- a/test/collision_tests/decl_decl_collision_test.dart
+++ b/test/collision_tests/decl_decl_collision_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:ffigen/src/code_generator.dart';
+import 'package:logging/logging.dart';
 import 'package:test/test.dart';
 
 import '../test_utils.dart';
@@ -10,7 +11,7 @@
 void main() {
   group('decl_decl_collision_test', () {
     setUpAll(() {
-      logWarnings();
+      logWarnings(Level.SEVERE);
     });
     test('declaration conflict', () {
       final l1 = Library(name: 'Bindings', bindings: [
diff --git a/test/collision_tests/reserved_keyword_collision_test.dart b/test/collision_tests/reserved_keyword_collision_test.dart
new file mode 100644
index 0000000..a41e939
--- /dev/null
+++ b/test/collision_tests/reserved_keyword_collision_test.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:ffigen/src/code_generator.dart';
+import 'package:logging/logging.dart';
+import 'package:test/test.dart';
+
+import '../test_utils.dart';
+
+void main() {
+  group('reserved_keyword_collision_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+    });
+    test('reserved keyword collision', () {
+      final l1 = Library(name: 'Bindings', bindings: [
+        Struc(name: 'abstract'),
+        Struc(name: 'if'),
+        EnumClass(name: 'return'),
+        EnumClass(name: 'export'),
+        Func(
+            name: 'show',
+            returnType: Type.nativeType(SupportedNativeType.Void)),
+        Func(
+            name: 'implements',
+            returnType: Type.nativeType(SupportedNativeType.Void)),
+      ]);
+      final l2 = Library(name: 'Bindings', bindings: [
+        Struc(name: 'abstract_1'),
+        Struc(name: 'if_1'),
+        EnumClass(name: 'return_1'),
+        EnumClass(name: 'export_1'),
+        Func(
+            name: 'show_1',
+            originalName: 'show',
+            returnType: Type.nativeType(SupportedNativeType.Void)),
+        Func(
+            name: 'implements_1',
+            originalName: 'implements',
+            returnType: Type.nativeType(SupportedNativeType.Void)),
+      ]);
+
+      expect(l1.generate(), l2.generate());
+    });
+  });
+}
diff --git a/test/header_parser_tests/function_n_struct_.dart b/test/header_parser_tests/function_n_struct_test.dart
similarity index 95%
rename from test/header_parser_tests/function_n_struct_.dart
rename to test/header_parser_tests/function_n_struct_test.dart
index e99faf0..f3c7fe1 100644
--- a/test/header_parser_tests/function_n_struct_.dart
+++ b/test/header_parser_tests/function_n_struct_test.dart
@@ -5,6 +5,7 @@
 import 'package:ffigen/src/code_generator.dart';
 import 'package:ffigen/src/header_parser.dart' as parser;
 import 'package:ffigen/src/config_provider.dart';
+import 'package:logging/logging.dart';
 import 'package:test/test.dart';
 import 'package:yaml/yaml.dart' as yaml;
 import 'package:ffigen/src/strings.dart' as strings;
@@ -16,7 +17,7 @@
 void main() {
   group('function_n_struct_test', () {
     setUpAll(() {
-      logWarnings();
+      logWarnings(Level.SEVERE);
       expected = expectedLibrary();
       actual = parser.parse(
         Config.fromYaml(yaml.loadYaml('''
diff --git a/test/large_integration_tests/large_test.dart b/test/large_integration_tests/large_test.dart
index 87d9f2e..a78d903 100644
--- a/test/large_integration_tests/large_test.dart
+++ b/test/large_integration_tests/large_test.dart
@@ -5,14 +5,20 @@
 import 'dart:io';
 
 import 'package:ffigen/src/header_parser.dart';
+import 'package:logging/logging.dart';
 import 'package:yaml/yaml.dart';
 import 'package:ffigen/src/config_provider/config.dart';
 import 'package:test/test.dart';
 import 'package:ffigen/src/strings.dart' as strings;
 import 'package:path/path.dart' as path;
 
+import '../test_utils.dart';
+
 void main() {
   group('large_test', () {
+    setUpAll(() {
+      logWarnings(Level.SEVERE);
+    });
     test('Libclang test', () {
       final config = Config.fromYaml(loadYaml('''
 ${strings.name}: LibClang
diff --git a/test/native_test/native_test.dart b/test/native_test/native_test.dart
index eab5dfe..c829a59 100644
--- a/test/native_test/native_test.dart
+++ b/test/native_test/native_test.dart
@@ -7,11 +7,13 @@
 import 'dart:math';
 
 import 'package:test/test.dart';
+import '../test_utils.dart';
 import 'native_test_bindings.dart' as bindings;
 
 void main() {
   group('native_test', () {
     setUpAll(() {
+      logWarnings();
       var dylibName = 'test/native_test/native_test.so';
       if (Platform.isMacOS) {
         dylibName = 'test/native_test/native_test.dylib';
diff --git a/test/prefix_tests/prefix_test.dart b/test/prefix_tests/prefix_test.dart
index 0031c3b..dea22fc 100644
--- a/test/prefix_tests/prefix_test.dart
+++ b/test/prefix_tests/prefix_test.dart
@@ -23,6 +23,7 @@
 void main() {
   group('prefix_test', () {
     setUpAll(() {
+      logWarnings();
       expected = expectedLibrary();
       actual = parser.parse(Config.fromYaml(yaml.loadYaml('''
 ${strings.name}: 'NativeLibrary'
diff --git a/test/test_utils.dart b/test/test_utils.dart
index 4b9d9eb..8d49c18 100644
--- a/test/test_utils.dart
+++ b/test/test_utils.dart
@@ -41,8 +41,8 @@
   }
 }
 
-void logWarnings() {
-  Logger.root.level = Level.WARNING;
+void logWarnings([Level level = Level.WARNING]) {
+  Logger.root.level = level;
   Logger.root.onRecord.listen((record) {
     print('${record.level.name.padRight(8)}: ${record.message}');
   });