diff --git a/.github/workflows/dotnet-ci.yml b/.github/workflows/dotnet-ci.yml new file mode 100644 index 000000000..c206e5b19 --- /dev/null +++ b/.github/workflows/dotnet-ci.yml @@ -0,0 +1,75 @@ +# +# This workflow will build, run tests, and pack for the Microsoft.Bot.Builder.M365 .NET SDK. +# + +name: dotnet-ci + +on: + workflow_dispatch: + push: + branches: + - DOTNET + paths: + - 'dotnet/packages/**' + +jobs: + dotnet-build-test-pack: + strategy: + matrix: + os: [windows-latest] + dotnet-version: ['6.0', '7.0'] + configuration: [Release] + fail-fast: false + runs-on: ${{ matrix.os }} + env: + SOLUTION_DIR: dotnet/packages/Microsoft.Bot.Builder.M365/ + steps: + - name: Checkout source + uses: actions/checkout@v3 + with: + clean: true + + - name: Setup .NET SDK ${{ matrix.dotnet-version }} + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ matrix.dotnet-version }} + + - name: Restore + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet restore Microsoft.Bot.Builder.M365.sln + + - name: Build + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet build Microsoft.Bot.Builder.M365.sln --no-restore --configuration ${{ matrix.configuration }} + + - name: Test + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet test Microsoft.Bot.Builder.M365.Tests/Microsoft.Bot.Builder.M365.Tests.csproj --no-restore --verbosity normal --logger trx --results-directory ./TestResults --collect:"XPlat Code Coverage" --configuration ${{ matrix.configuration }} + + - name: Pack + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet pack --no-build --output . --configuration ${{ matrix.configuration }} Microsoft.Bot.Builder.M365/Microsoft.Bot.Builder.M365.csproj + + - name: Upload package + uses: actions/upload-artifact@v3 + with: + name: nupkg-dotnet-${{ matrix.dotnet-version }} + path: ${{ env.SOLUTION_DIR }}*.nupkg + + - name: Generate coverage report + uses: danielpalme/ReportGenerator-GitHub-Action@5.1.22 + with: + reports: ${{ env.SOLUTION_DIR }}TestResults/*/coverage.cobertura.xml + targetdir: ${{ env.SOLUTION_DIR }}TestResults/coverage + reporttypes: HtmlInline + toolpath: ${{ env.SOLUTION_DIR }}report-generator-tool + + - name: Upload test results + uses: actions/upload-artifact@v3 + with: + name: testresults-dotnet-${{ matrix.dotnet-version }} + path: ${{ env.SOLUTION_DIR }}TestResults diff --git a/.github/workflows/dotnet-pr.yml b/.github/workflows/dotnet-pr.yml new file mode 100644 index 000000000..99808af6c --- /dev/null +++ b/.github/workflows/dotnet-pr.yml @@ -0,0 +1,61 @@ +# +# This workflow will build and run tests for the Microsoft.Bot.Builder.M365 .NET SDK. +# + +name: dotnet-pr + +on: + pull_request: + branches: + - DOTNET + paths: + - 'dotnet/packages/**' + +jobs: + dotnet-build-test: + strategy: + matrix: + os: [windows-latest] + dotnet-version: ['6.0', '7.0'] + configuration: [Release] + fail-fast: false + runs-on: ${{ matrix.os }} + env: + SOLUTION_DIR: dotnet/packages/Microsoft.Bot.Builder.M365/ + steps: + - name: Checkout source + uses: actions/checkout@v3 + with: + clean: true + + - name: Setup .NET SDK ${{ matrix.dotnet-version }} + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ matrix.dotnet-version }} + + - name: Restore + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet restore Microsoft.Bot.Builder.M365.sln + + - name: Build + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet build Microsoft.Bot.Builder.M365.sln --no-restore --configuration ${{ matrix.configuration }} + + - name: Test + shell: bash + working-directory: ${{ env.SOLUTION_DIR }} + run: dotnet test Microsoft.Bot.Builder.M365.Tests/Microsoft.Bot.Builder.M365.Tests.csproj --no-restore --verbosity normal --logger trx --results-directory ./TestResults --collect:"XPlat Code Coverage" --configuration ${{ matrix.configuration }} + + - name: Generate coverage report + uses: danielpalme/ReportGenerator-GitHub-Action@5.1.22 + with: + reports: ${{ env.SOLUTION_DIR }}TestResults/*/coverage.cobertura.xml + targetdir: ${{ env.SOLUTION_DIR }}TestResults/coverage + reporttypes: TextSummary + toolpath: ${{ env.SOLUTION_DIR }}report-generator-tool + + - name: Show coverage + shell: bash + run: cat ${{ env.SOLUTION_DIR }}TestResults/coverage/*.txt diff --git a/dotnet/packages/.editorconfig b/dotnet/packages/.editorconfig new file mode 100644 index 000000000..8b23de72a --- /dev/null +++ b/dotnet/packages/.editorconfig @@ -0,0 +1,334 @@ +############################### +# Core EditorConfig Options # +############################### +root = true +# All files +[*] +indent_style = space +end_of_line = lf + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +tab_width = 2 +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# JSON config files +[*.json] +tab_width = 2 +indent_size = 2 +insert_final_newline = false +trim_trailing_whitespace = true + +# Code files +[*.{cs,csx,vb,vbx}] +tab_width = 4 +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8-bom + +# TODO: fix source format and change warning to error +############################### +# .NET Coding Conventions # +############################### +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true:warning +# this. preferences +dotnet_style_qualification_for_field = true:warning +dotnet_style_qualification_for_property = true:warning +dotnet_style_qualification_for_method = true:warning +dotnet_style_qualification_for_event = true:warning +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +dotnet_style_readonly_field = true:warning +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_coalesce_expression = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_return = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_compound_assignment = true:warning +# Code quality rules +dotnet_code_quality_unused_parameters = all:warning + +[*.cs] +# Explicitly define rules and ignore others +dotnet_analyzer_diagnostic.severity = none + +# Elevated diagnostics +dotnet_diagnostic.RCS1021.severity = warning # Use expression-bodied lambda. +dotnet_diagnostic.RCS1032.severity = warning # Remove redundant parentheses. +dotnet_diagnostic.RCS1036.severity = warning # Remove unnecessary blank line. +dotnet_diagnostic.RCS1037.severity = warning # Remove trailing white-space. +dotnet_diagnostic.RCS1061.severity = warning # Merge 'if' with nested 'if'. +dotnet_diagnostic.RCS1069.severity = warning # Remove unnecessary case label. +dotnet_diagnostic.RCS1074.severity = warning # Remove redundant constructor. +dotnet_diagnostic.RCS1077.severity = warning # Optimize LINQ method call. +dotnet_diagnostic.RCS1085.severity = warning # Use auto-implemented property. +dotnet_diagnostic.RCS1097.severity = warning # Remove redundant 'ToString' call. +dotnet_diagnostic.RCS1118.severity = warning # Mark local variable as const. +dotnet_diagnostic.RCS1124.severity = warning # Inline local variable. +dotnet_diagnostic.RCS1129.severity = warning # Remove redundant field initialization. +dotnet_diagnostic.RCS1138.severity = warning # Add summary to documentation comment. +dotnet_diagnostic.RCS1139.severity = warning # Add summary element to documentation comment. +dotnet_diagnostic.RCS1140.severity = warning # Add exception to documentation comment. +dotnet_diagnostic.RCS1141.severity = warning # Add 'param' element to documentation comment. +dotnet_diagnostic.RCS1142.severity = warning # Add 'typeparam' element to documentation comment. +dotnet_diagnostic.RCS1146.severity = warning # Use conditional access. +dotnet_diagnostic.RCS1151.severity = warning # Remove redundant cast. +dotnet_diagnostic.RCS1158.severity = warning # Static member in generic type should use a type parameter. +dotnet_diagnostic.RCS1161.severity = warning # Enum should declare explicit value +dotnet_diagnostic.RCS1163.severity = warning # Unused parameter 'foo'. +dotnet_diagnostic.RCS1168.severity = warning # Parameter name 'foo' differs from base name 'bar'. +dotnet_diagnostic.RCS1170.severity = warning # Use read-only auto-implemented property. +dotnet_diagnostic.RCS1173.severity = warning # Use coalesce expression instead of 'if'. +dotnet_diagnostic.RCS1175.severity = warning # Unused 'this' parameter 'operation'. +dotnet_diagnostic.RCS1181.severity = warning # Convert comment to documentation comment. +dotnet_diagnostic.RCS1186.severity = warning # Use Regex instance instead of static method. +dotnet_diagnostic.RCS1188.severity = warning # Remove redundant auto-property initialization. +dotnet_diagnostic.RCS1189.severity = warning # Add region name to #endregion. +dotnet_diagnostic.RCS1192.severity = warning # Unnecessary usage of verbatim string literal. +dotnet_diagnostic.RCS1194.severity = warning # Implement exception constructors. +dotnet_diagnostic.RCS1197.severity = warning # Optimize StringBuilder.AppendLine call. +dotnet_diagnostic.RCS1201.severity = warning # Use method chaining. +dotnet_diagnostic.RCS1205.severity = warning # Order named arguments according to the order of parameters. +dotnet_diagnostic.RCS1211.severity = warning # Remove unnecessary else clause. +dotnet_diagnostic.RCS1212.severity = warning # Remove redundant assignment. +dotnet_diagnostic.RCS1214.severity = warning # Unnecessary interpolated string. +dotnet_diagnostic.RCS1217.severity = warning # Convert interpolated string to concatenation. +dotnet_diagnostic.RCS1222.severity = warning # Merge preprocessor directives. +dotnet_diagnostic.RCS1225.severity = warning # Make class sealed. +dotnet_diagnostic.RCS1226.severity = warning # Add paragraph to documentation comment. +dotnet_diagnostic.RCS1228.severity = warning # Unused element in documentation comment. +dotnet_diagnostic.RCS1229.severity = warning # Use async/await when necessary. +dotnet_diagnostic.RCS1232.severity = warning # Order elements in documentation comment. +dotnet_diagnostic.RCS1234.severity = warning # Enum duplicate value +dotnet_diagnostic.RCS1238.severity = warning # Avoid nested ?: operators. +dotnet_diagnostic.RCS1241.severity = warning # Implement IComparable when implementing IComparable. + +dotnet_diagnostic.CA1000.severity = warning # Do not declare static members on generic types +dotnet_diagnostic.CA1002.severity = warning # Change 'List' in '...' to use 'Collection' ... +dotnet_diagnostic.CA1031.severity = warning # Do not catch general exception types +dotnet_diagnostic.CA1032.severity = warning # We're using RCS1194 which seems to cover more ctors +dotnet_diagnostic.CA1034.severity = warning # Do not nest type. Alternatively, change its accessibility so that it is not externally visible +dotnet_diagnostic.CA1050.severity = warning # Declare types in namespaces +dotnet_diagnostic.CA1062.severity = warning # Validate arguments of public methods +dotnet_diagnostic.CA1063.severity = warning # Implement IDisposable correctly +dotnet_diagnostic.CA1064.severity = warning # Exceptions should be public +dotnet_diagnostic.CA1303.severity = warning # Do not pass literals as localized parameters +dotnet_diagnostic.CA1416.severity = warning # Validate platform compatibility +dotnet_diagnostic.CA1508.severity = warning # Avoid dead conditional code +dotnet_diagnostic.CA1805.severity = warning # Member is explicitly initialized to its default value +dotnet_diagnostic.CA1822.severity = warning # Member does not access instance data and can be marked as static +dotnet_diagnostic.CA1848.severity = warning # For improved performance, use the LoggerMessage delegates +dotnet_diagnostic.CA1852.severity = warning # Sealed classes +dotnet_diagnostic.CA1859.severity = warning # Use concrete types when possible for improved performance +dotnet_diagnostic.CA1860.severity = warning # Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA2000.severity = warning # Call System.IDisposable.Dispose on object before all references to it are out of scope +dotnet_diagnostic.CA2007.severity = warning # Do not directly await a Task +dotnet_diagnostic.CA2201.severity = warning # Exception type System.Exception is not sufficiently specific +dotnet_diagnostic.CA2225.severity = warning # Operator overloads have named alternates +dotnet_diagnostic.CA2227.severity = warning # Change to be read-only by removing the property setter +dotnet_diagnostic.CA2253.severity = warning # Named placeholders in the logging message template should not be comprised of only numeric characters + +dotnet_diagnostic.CS1591.severity = warning # Missing XML comment for publicly visible type or member + +dotnet_diagnostic.IDE0001.severity = warning # Simplify name +dotnet_diagnostic.IDE0002.severity = warning # Simplify member access +dotnet_diagnostic.IDE0004.severity = warning # Remove unnecessary cast +dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary using directives +dotnet_diagnostic.IDE0008.severity = warning # Use explicit type instead of var +dotnet_diagnostic.IDE0009.severity = warning # Add this or Me qualification +dotnet_diagnostic.IDE0011.severity = warning # Add braces +dotnet_diagnostic.IDE0018.severity = warning # Inline variable declaration +dotnet_diagnostic.IDE0032.severity = warning # Use auto-implemented property +dotnet_diagnostic.IDE0034.severity = warning # Simplify 'default' expression +dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code +dotnet_diagnostic.IDE0036.severity = warning # Order modifiers +dotnet_diagnostic.IDE0040.severity = warning # Add accessibility modifiers +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references +dotnet_diagnostic.IDE0050.severity = warning # Convert anonymous type to tuple +dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member +dotnet_diagnostic.IDE0052.severity = warning # Remove unread private member +dotnet_diagnostic.IDE0055.severity = warning # Formatting rule +dotnet_diagnostic.IDE0058.severity = warning # Remove unused expression value +dotnet_diagnostic.IDE0059.severity = warning # Unnecessary assignment of a value +dotnet_diagnostic.IDE0060.severity = warning # Remove unused parameter +dotnet_diagnostic.IDE0070.severity = warning # Use 'System.HashCode.Combine' +dotnet_diagnostic.IDE0071.severity = warning # Simplify interpolation +dotnet_diagnostic.IDE0073.severity = warning # Require file header +dotnet_diagnostic.IDE0080.severity = warning # Remove unnecessary suppression operator +dotnet_diagnostic.IDE0082.severity = warning # Convert typeof to nameof +dotnet_diagnostic.IDE0090.severity = warning # Simplify new expression +dotnet_diagnostic.IDE0100.severity = warning # Remove unnecessary equality operator +dotnet_diagnostic.IDE0110.severity = warning # Remove unnecessary discards +dotnet_diagnostic.IDE0130.severity = warning # Namespace does not match folder structure +dotnet_diagnostic.IDE0160.severity = warning # Use block-scoped namespace + +dotnet_diagnostic.VSTHRD111.severity = warning # Use .ConfigureAwait(bool) + +# Suppressed diagnostics +# Suppress a diagnostic in a file with a 'dotnet_diagnostic..severity = none' entry + +############################### +# Naming Conventions # +############################### + +# Styles +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.static_underscored.capitalization = camel_case +dotnet_naming_style.static_underscored.required_prefix = s_ + +dotnet_naming_style.underscored.capitalization = camel_case +dotnet_naming_style.underscored.required_prefix = _ + +dotnet_naming_style.uppercase_with_underscore_separator.capitalization = all_upper +dotnet_naming_style.uppercase_with_underscore_separator.word_separator = _ + +dotnet_naming_style.end_in_async.required_prefix = +dotnet_naming_style.end_in_async.required_suffix = Async +dotnet_naming_style.end_in_async.capitalization = pascal_case +dotnet_naming_style.end_in_async.word_separator = + +# Symbols +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_constant.applicable_kinds = local +dotnet_naming_symbols.local_constant.applicable_accessibilities = * +dotnet_naming_symbols.local_constant.required_modifiers = const + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_symbols.any_async_methods.applicable_kinds = method +dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * +dotnet_naming_symbols.any_async_methods.required_modifiers = async + +# Rules +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning + +dotnet_naming_rule.local_constant_should_be_pascal_case.symbols = local_constant +dotnet_naming_rule.local_constant_should_be_pascal_case.style = pascal_case_style +dotnet_naming_rule.local_constant_should_be_pascal_case.severity = warning + +dotnet_naming_rule.private_static_fields_underscored.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_underscored.style = static_underscored +dotnet_naming_rule.private_static_fields_underscored.severity = warning + +dotnet_naming_rule.private_fields_underscored.symbols = private_fields +dotnet_naming_rule.private_fields_underscored.style = underscored +dotnet_naming_rule.private_fields_underscored.severity = warning + +dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods +dotnet_naming_rule.async_methods_end_in_async.style = end_in_async +dotnet_naming_rule.async_methods_end_in_async.severity = warning + +############################### +# C# Coding Conventions # +############################### + +# var preferences +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false +csharp_style_var_elsewhere = false +# Expression-bodied members +csharp_style_expression_bodied_methods = false:warning +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = false:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +# Null-checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +# Expression-level preferences +csharp_prefer_braces = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_prefer_local_over_anonymous_function = true:warning +csharp_style_inlined_variable_declaration = true:warning + +############################### +# C# Formatting Rules # +############################### + +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation preferences +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left +# Space preferences +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true +csharp_using_directive_placement = outside_namespace:warning +csharp_prefer_simple_using_statement = true:warning +csharp_style_namespace_declarations = block_scoped:warning +csharp_style_prefer_method_group_conversion = true:warning +csharp_style_prefer_top_level_statements = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = false:warning \ No newline at end of file diff --git a/dotnet/packages/Microsoft.Bot.Builder.M365/Microsoft.Bot.Builder.M365.Tests/.editorconfig b/dotnet/packages/Microsoft.Bot.Builder.M365/Microsoft.Bot.Builder.M365.Tests/.editorconfig new file mode 100644 index 000000000..5df2f8f49 --- /dev/null +++ b/dotnet/packages/Microsoft.Bot.Builder.M365/Microsoft.Bot.Builder.M365.Tests/.editorconfig @@ -0,0 +1,7 @@ +# Suppress errors for Test +[*.cs] +dotnet_diagnostic.CA2000.severity = none # Call System.IDisposable.Dispose on object before all references to it are out of scope +dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task +dotnet_diagnostic.IDE0008.severity = none # Use explicit type instead of var +dotnet_diagnostic.IDE0058.severity = none # Remove unnecessary expression value +dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) \ No newline at end of file