diff --git a/Directory.Packages.props b/Directory.Packages.props index 1351bbb22..a70281288 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -49,8 +49,14 @@ - + + + + + + + diff --git a/docfx/CommandLine/api/index.md b/docfx/CommandLine/api/index.md deleted file mode 100644 index 6de856d79..000000000 --- a/docfx/CommandLine/api/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# About -`Ubiquity.NET.CommandLine` contains general extensions for .NET. to support command line -parsing using `System.CommandLine` diff --git a/docfx/CommandLine/index.md b/docfx/CommandLine/index.md deleted file mode 100644 index e35c04e85..000000000 --- a/docfx/CommandLine/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# About -Ubiquity.NET.CommandLine contains general extensions for .NET. to support command line -parsing using `System.CommandLine` diff --git a/docfx/CommandLine/toc.yml b/docfx/CommandLine/toc.yml deleted file mode 100644 index 1963d8b3d..000000000 --- a/docfx/CommandLine/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for the 'CommandLine' folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/SrcGeneration/api/index.md b/docfx/SrcGeneration/api/index.md deleted file mode 100644 index 832948997..000000000 --- a/docfx/SrcGeneration/api/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Ubiquity.NET.SrcGeneration -This library contains support functionality to aid in building a source generator. -This library is multi-targetting to allow consumption from a Roslyn source generator -or VSIX project. Other uses should leverage the modern runtimes but those cases -***MUST*** target only `.NET Standard 2.0` - diff --git a/docfx/SrcGeneration/index.md b/docfx/SrcGeneration/index.md deleted file mode 100644 index 832948997..000000000 --- a/docfx/SrcGeneration/index.md +++ /dev/null @@ -1,6 +0,0 @@ -# Ubiquity.NET.SrcGeneration -This library contains support functionality to aid in building a source generator. -This library is multi-targetting to allow consumption from a Roslyn source generator -or VSIX project. Other uses should leverage the modern runtimes but those cases -***MUST*** target only `.NET Standard 2.0` - diff --git a/docfx/SrcGeneration/toc.yml b/docfx/SrcGeneration/toc.yml deleted file mode 100644 index c6a728a53..000000000 --- a/docfx/SrcGeneration/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for SrcGeneration folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/antlr-utils/api/.gitignore b/docfx/antlr-utils/api/.gitignore deleted file mode 100644 index e8079a3be..000000000 --- a/docfx/antlr-utils/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -############### -# temp file # -############### -*.yml -.manifest diff --git a/docfx/antlr-utils/api/index.md b/docfx/antlr-utils/api/index.md deleted file mode 100644 index ff7b78337..000000000 --- a/docfx/antlr-utils/api/index.md +++ /dev/null @@ -1,2 +0,0 @@ -# Ubiquity.NET.ANTLR.Utils -This namespace supports all of extensions to support use of ANTLR as a parsing engine. diff --git a/docfx/antlr-utils/index.md b/docfx/antlr-utils/index.md deleted file mode 100644 index 45b94f0a8..000000000 --- a/docfx/antlr-utils/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# About -This library provides general extensions to ANTLR including adapter bindings -for the Ubiquity.NET.Runtime library. - -# Key usage -* Get a SourceLocation from various ANTLR types (rule,tokens,terminals) - - This provides an adaptation to the abstract SourceLocation -* Debug trace listener - - Provides debug TRACE support for any parser by listening for every rule and using - Debug.Trace() to generate a string representation of that rule. This is VERY useful - when developing or debugging a grammar. -* Adapter for parse error listeners to a unified and abstract - `Ubiquity.NET.Runtime.IParseErrorListener`. - - This allows building consumers that deal with errors and remain independent of the - parsing technology. -* Extension functions that provides commonly used support for ANTLR - - Get a character interval from a ParserRuleContext with support for the standard EOF - rule. - - Get the source stream from an IRecognizer - - Get the source text from a rule context and recognizer that produced it. - - Get source text from a rule context and stream that it was parsed from. - - Get a unique ID for a parse tree - * Useful for building graphs of the result of parsing as many graphing representations - require a unique node id for every node in the graph. diff --git a/docfx/antlr-utils/toc.yml b/docfx/antlr-utils/toc.yml deleted file mode 100644 index d5dad6106..000000000 --- a/docfx/antlr-utils/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for antly-utils folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/docfx.json b/docfx/docfx.json index d0cdf9eb2..32bd670e1 100644 --- a/docfx/docfx.json +++ b/docfx/docfx.json @@ -11,64 +11,9 @@ // warnings and returns a non-success value which stops the build. // Each project is listed in the order they appear in the VS solution explorer so they are easier // to find and check etc... - { - // Interop helpers library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.InteropHelpers", - "files": [ "**.csproj" ] - } - ], - "dest": "interop-helpers/api" - }, + // // NOTE: Ubiquity.NET.Llvm.Interop is intentionally NOT documented (It's considered an implementation detail) // NOTE: Sample projects are not generating docs, they are... samples 8^) - { - // ANTLR Utilities library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.ANTLR.Utils", - "files": [ "**.csproj" ] - } - ], - "dest": "antlr-utils/api" - }, - { - // ANTLR Utilities library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.CommandLine", - "files": [ "**.csproj" ] - } - ], - "dest": "CommandLine/api" - }, - { - // Extensions library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.Extensions", - "files": [ "**.csproj" ] - } - ], - "dest": "extensions/api", - "properties": { - // use .NET 8.0 for the TFM as it is multi-targeting - // Sadly, DocFX can't find the dependent project builds if this is .NET 9.0 - // and then generates warnings as a result. (DocFX metadata generation is - // pretty well borked and needs replacement as there are a LOT of workarounds - // in this project let alone all the ones found on-line.) - "TargetFramework": "net8.0" - } - }, { // LLVM OO Wrappers library "memberLayout": "separatePages", @@ -80,38 +25,6 @@ } ], "dest": "llvm/api" - }, - { - // Runtime utilities library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.Runtime.Utils", - "files": [ "**.csproj" ] - } - ], - "dest": "runtime-utils/api", - }, - { - // SrcGeneration library - "memberLayout": "separatePages", - "namespaceLayout": "nested", - "src": [ - { - "src": "../src/Ubiquity.NET.SrcGeneration", - "files": [ "**.csproj" ] - } - ], - "dest": "SrcGeneration/api", - "properties": { - // use .NET 8.0 for the TFM as it is multi-targeting - // Sadly, DocFX can't find the dependent project builds if this is .NET 9.0 - // and then generates warnings as a result. (DocFX metadata generation is - // pretty well borked and needs replacement as there are a LOT of workarounds - // in this project let alone all the ones found on-line.) - "TargetFramework": "net8.0" - } } ], "build": { @@ -129,17 +42,6 @@ "toc.yml" ] }, - { - // InteropHelpers project additional content, Includes the generated metadata API folder - "files": [ - "interop-helpers/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] - }, { // Pull in the LLVM samples as content. The LLVM core project is more complex in the docfx.json as it // includes the samples but they are NOT participating in metadata generation. They only contribute @@ -159,39 +61,6 @@ "**/ReadMe.md" ] }, - { - // ANTLR Utils project additional content, Includes the generated metadata API folder - "files": [ - "antlr-utils/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] - }, - { - // CommandLine Utils project additional content, Includes the generated metadata API folder - "files": [ - "CommandLine/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] - }, - { - // extensions project additional content, Includes the generated metadata API folder - "files": [ - "extensions/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] - }, { // LLVM project additional content, Includes the generated metadata API folder "files": [ @@ -202,30 +71,6 @@ "**/namespaces/**.md", "**/*-xref.yml" ] - }, - { - // Runtime Utils project additional content, Includes the generated metadata API folder - // NOTE: File paths are relative to the location of this file - "files": [ - "runtime-utils/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] - }, - { - // SrcGenerations project additional content, Includes the generated metadata API folder - // NOTE: File paths are relative to the location of this file - "files": [ - "SrcGeneration/**.{md,yml}" - ], - // Exclude the namespace overwrites and XREF maps as they are listed explicitly elsewhere - "exclude": [ - "**/namespaces/**.md", - "**/*-xref.yml" - ] } ], "resource": [ diff --git a/docfx/documentation.msbuildproj b/docfx/documentation.msbuildproj index b187f8d38..ff7692251 100644 --- a/docfx/documentation.msbuildproj +++ b/docfx/documentation.msbuildproj @@ -33,25 +33,21 @@ - - - - @@ -62,16 +58,12 @@ - - - - diff --git a/docfx/extensions/api/.gitignore b/docfx/extensions/api/.gitignore deleted file mode 100644 index e8079a3be..000000000 --- a/docfx/extensions/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -############### -# temp file # -############### -*.yml -.manifest diff --git a/docfx/extensions/api/index.md b/docfx/extensions/api/index.md deleted file mode 100644 index 7e6c50857..000000000 --- a/docfx/extensions/api/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# About -Ubiquity.NET.Extensions contains general extensions for .NET. This is -a bit of a [grab bag](https://www.merriam-webster.com/dictionary/grab%20bag) of -functionality used by but not actually part of multiple other Ubiquity.NET projects. - -## Key support -* Computing a hash code for a ReadOnlySpan of bytes using - [System.IO.System.IO.Hashing.XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) -* DisposableAction for building actions that must occur on Dispose - - This is useful for implementing the RAII pattern in .NET. -* MustUseReturnValueAttribute that is compatible with the [MustUseRetVal](https://github.com/mykolav/must-use-ret-val-fs) - package. -* StringNormalizer extensions to support converting line endings of strings for - interoperability. -* Fluent style parameter value validation extensions. - - These are useful when passing parameters to a function that produces a result that is - fed to the base constructor. These are also useful in body expressions to validate - input parameters. -* DictionaryBuilder to enable dictionary initializer style initialization of - `ImmutableDictionary` with significantly reduced overhead. - - This leverages an `ImmutableDictionary.Builder` under the hood to build - the dictionary. When the `ToImmutable()` method is called the builder is converted to - the immutable state without any overhead of a copy or re-construction of hash tables - etc... -* KvpArrayBuilder to enable array initializer style initialization of - `ImmutableArray>` with significantly reduced overhead. - - This leverages an `ImmutableArray.Builder` under the hood to build the array - directly. When the `ToImmutable()` method is called the builder is converted to the - immutable state without any overhead of a copy. - - Since this is an array and not a dictionary there is no overhead for allocating, - initializing or copying any hash mapping for the keys. - diff --git a/docfx/extensions/index.md b/docfx/extensions/index.md deleted file mode 100644 index 7e6c50857..000000000 --- a/docfx/extensions/index.md +++ /dev/null @@ -1,32 +0,0 @@ -# About -Ubiquity.NET.Extensions contains general extensions for .NET. This is -a bit of a [grab bag](https://www.merriam-webster.com/dictionary/grab%20bag) of -functionality used by but not actually part of multiple other Ubiquity.NET projects. - -## Key support -* Computing a hash code for a ReadOnlySpan of bytes using - [System.IO.System.IO.Hashing.XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) -* DisposableAction for building actions that must occur on Dispose - - This is useful for implementing the RAII pattern in .NET. -* MustUseReturnValueAttribute that is compatible with the [MustUseRetVal](https://github.com/mykolav/must-use-ret-val-fs) - package. -* StringNormalizer extensions to support converting line endings of strings for - interoperability. -* Fluent style parameter value validation extensions. - - These are useful when passing parameters to a function that produces a result that is - fed to the base constructor. These are also useful in body expressions to validate - input parameters. -* DictionaryBuilder to enable dictionary initializer style initialization of - `ImmutableDictionary` with significantly reduced overhead. - - This leverages an `ImmutableDictionary.Builder` under the hood to build - the dictionary. When the `ToImmutable()` method is called the builder is converted to - the immutable state without any overhead of a copy or re-construction of hash tables - etc... -* KvpArrayBuilder to enable array initializer style initialization of - `ImmutableArray>` with significantly reduced overhead. - - This leverages an `ImmutableArray.Builder` under the hood to build the array - directly. When the `ToImmutable()` method is called the builder is converted to the - immutable state without any overhead of a copy. - - Since this is an array and not a dictionary there is no overhead for allocating, - initializing or copying any hash mapping for the keys. - diff --git a/docfx/extensions/toc.yml b/docfx/extensions/toc.yml deleted file mode 100644 index 262c8b726..000000000 --- a/docfx/extensions/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for the 'extensions' folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/index.md b/docfx/index.md index 1883d63d1..9b8d79b74 100644 --- a/docfx/index.md +++ b/docfx/index.md @@ -5,15 +5,14 @@ are rather close. In the mean time this set of libraries provides the building b for creating a Domain Specific Language (DSL) implementation or custom language compiler, including JIT execution. Several useful generalized libraries are also included. -## The Libraries in this repository -(At least the ones generating docs at this point anyway! :grin:) +## The Libraries[1](#footnote_1) in this repository | Library | Description | |---------|-------------| -| [Ubiquity.NET.Antlr.Utils](antlr-utils/index.md) | This library contains extensions and helpers for using ANTLR with .NET | -| [Ubiquity.NET.CommandLine](CommandLine/index.md) | This library contains extensions and helpers for command line parsing via `System.CommandLine` | -| [Ubiquity.NET.Extensions](extensions/index.md) | This library contains general extensions and helpers for many scenarios using .NET | | [Ubiquity.NET.Llvm](llvm/index.md) | This library contains The core of the LLVM projection to .NET | -| [Ubiquity.NET.Runtime.Utils](runtime-utils/index.md) | This library contains common support for DSL runtime and language implementors | -| [Ubiquity.NET.InteropHelpers](interop-helpers/index.md) | This library contains extensions and helpers for implementing interop support for native libraries | -| [Ubiquity.NET.SrcGeneration](SrcGeneration/index.md) | This library contains extensions and helpers for implementing source generators | + +--- +1 The Ubiquity.NET.Llvm.Interop is intentionally NOT documented. It is an internal +implementation detail subject to change in the future. There are plans to merge it with the +OO wrapper library. Therefore, applications should NOT depend on it as it is likely to cease +existing in the future. diff --git a/docfx/interop-helpers/api/.gitignore b/docfx/interop-helpers/api/.gitignore deleted file mode 100644 index e8079a3be..000000000 --- a/docfx/interop-helpers/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -############### -# temp file # -############### -*.yml -.manifest diff --git a/docfx/interop-helpers/api/index.md b/docfx/interop-helpers/api/index.md deleted file mode 100644 index dbfea6c2f..000000000 --- a/docfx/interop-helpers/api/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# About -`Ubiquity.NET.InteropHelpers` helper support common to low level interop libraries. While -this library is intended to support the Ubiquity.NET.Llvm interop requirements there isn't -anything bound to that library in the support here. That is it is independent and a useful -library for any code base providing interop support. - -# Key Features -* String handling - * A lot of interop deals with strings in some form or another and handling them is a - major amount of effort for most interop libraries. The support provided here enables - lazy evaluation/marshalling and encoding of native strings and managed strings. These - allow a simple `byte[]` to store a native string and ONLY marshals to a UTF16 managed - string once when needed. This allows storing and passing strings in their native form - for FAST retrieval from a native call and then providing that same string as an `in` - parameter in another call. All without the need to marshal from native and then back - again just for the call. This is a MAJOR performance enhancement for APIs that deal in - strings. -* Delegates and NativeCallbacks as Function pointers - * Function pointers are a new feature of C# that makes for very high performance interop - scenarios. However, sometimes the callback for a function pointer actually needs - additional data not part of the parameters of the function to work properly. This - library provides support for such scenarios where a delegate is used to "capture" the - data while still supporting AOT scenarios. (NOTE: `Marshal.GetFunctionPointerForDelegate()` - must dynamically emit a thunk that contains the proper signature and the captured - "this" pointer so is NOT AOT friendly) The support offered in this library, though a - bit more tedious, is AOT friendly. - diff --git a/docfx/interop-helpers/index.md b/docfx/interop-helpers/index.md deleted file mode 100644 index dbfea6c2f..000000000 --- a/docfx/interop-helpers/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# About -`Ubiquity.NET.InteropHelpers` helper support common to low level interop libraries. While -this library is intended to support the Ubiquity.NET.Llvm interop requirements there isn't -anything bound to that library in the support here. That is it is independent and a useful -library for any code base providing interop support. - -# Key Features -* String handling - * A lot of interop deals with strings in some form or another and handling them is a - major amount of effort for most interop libraries. The support provided here enables - lazy evaluation/marshalling and encoding of native strings and managed strings. These - allow a simple `byte[]` to store a native string and ONLY marshals to a UTF16 managed - string once when needed. This allows storing and passing strings in their native form - for FAST retrieval from a native call and then providing that same string as an `in` - parameter in another call. All without the need to marshal from native and then back - again just for the call. This is a MAJOR performance enhancement for APIs that deal in - strings. -* Delegates and NativeCallbacks as Function pointers - * Function pointers are a new feature of C# that makes for very high performance interop - scenarios. However, sometimes the callback for a function pointer actually needs - additional data not part of the parameters of the function to work properly. This - library provides support for such scenarios where a delegate is used to "capture" the - data while still supporting AOT scenarios. (NOTE: `Marshal.GetFunctionPointerForDelegate()` - must dynamically emit a thunk that contains the proper signature and the captured - "this" pointer so is NOT AOT friendly) The support offered in this library, though a - bit more tedious, is AOT friendly. - diff --git a/docfx/interop-helpers/toc.yml b/docfx/interop-helpers/toc.yml deleted file mode 100644 index abeb0d1f0..000000000 --- a/docfx/interop-helpers/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for interop-helpers folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/runtime-utils/api/.gitignore b/docfx/runtime-utils/api/.gitignore deleted file mode 100644 index e8079a3be..000000000 --- a/docfx/runtime-utils/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -############### -# temp file # -############### -*.yml -.manifest diff --git a/docfx/runtime-utils/api/index.md b/docfx/runtime-utils/api/index.md deleted file mode 100644 index c3858c81f..000000000 --- a/docfx/runtime-utils/api/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ubiquity.NET.Runtime.Utils -This library contains support functionality to aid in building a language -parser or runtime. Generally this is used in conjunction with custom types -and the Ubiquity.NET.Llvm library to provide custom DSL JIT support. - diff --git a/docfx/runtime-utils/index.md b/docfx/runtime-utils/index.md deleted file mode 100644 index c3858c81f..000000000 --- a/docfx/runtime-utils/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ubiquity.NET.Runtime.Utils -This library contains support functionality to aid in building a language -parser or runtime. Generally this is used in conjunction with custom types -and the Ubiquity.NET.Llvm library to provide custom DSL JIT support. - diff --git a/docfx/runtime-utils/toc.yml b/docfx/runtime-utils/toc.yml deleted file mode 100644 index d76070c1b..000000000 --- a/docfx/runtime-utils/toc.yml +++ /dev/null @@ -1,5 +0,0 @@ -# TOC (Left nav) for runtime-utils folder -- name: Overview - href: index.md -- name: Namespaces - href: api/toc.yml diff --git a/docfx/toc.yml b/docfx/toc.yml index f555478e9..9d7578d04 100644 --- a/docfx/toc.yml +++ b/docfx/toc.yml @@ -11,15 +11,3 @@ items: - name: Llvm href: llvm/index.md - - name: Runtime Utils - href: runtime-utils/index.md - - name: .NET Extensions - href: extensions/index.md - - name: ANTLR Utilities - href: antlr-utils/index.md - - name: Interop Helpers - href: interop-helpers/index.md - - name: CommandLine Parsing - href: CommandLine/index.md - - name: Source Generation - href: SrcGeneration/index.md diff --git a/src/Interop/InteropTests/Ubiquity.NET.Llvm.Interop.UT.csproj b/src/Interop/InteropTests/Ubiquity.NET.Llvm.Interop.UT.csproj index ecebd6c97..cc7a5fe9a 100644 --- a/src/Interop/InteropTests/Ubiquity.NET.Llvm.Interop.UT.csproj +++ b/src/Interop/InteropTests/Ubiquity.NET.Llvm.Interop.UT.csproj @@ -8,7 +8,6 @@ True - Ubiquity.NET.Llvm.Interop true @@ -20,5 +19,6 @@ + diff --git a/src/Interop/LlvmBindingsGenerator/LlvmBindingsGenerator.csproj b/src/Interop/LlvmBindingsGenerator/LlvmBindingsGenerator.csproj index 5c48600b6..4479e05dd 100644 --- a/src/Interop/LlvmBindingsGenerator/LlvmBindingsGenerator.csproj +++ b/src/Interop/LlvmBindingsGenerator/LlvmBindingsGenerator.csproj @@ -19,7 +19,7 @@ - + @@ -73,7 +69,7 @@ <__RspContent Include="-e $(PkgUbiquity_NET_LibLLVM)\Content\native" /> <__RspContent Include="-o $(__GeneratedSourcePath)" /> - + diff --git a/src/Interop/Ubiquity.NET.Llvm.Interop/Ubiquity.NET.Llvm.Interop.csproj b/src/Interop/Ubiquity.NET.Llvm.Interop/Ubiquity.NET.Llvm.Interop.csproj index 294d2662b..1e3436f88 100644 --- a/src/Interop/Ubiquity.NET.Llvm.Interop/Ubiquity.NET.Llvm.Interop.csproj +++ b/src/Interop/Ubiquity.NET.Llvm.Interop/Ubiquity.NET.Llvm.Interop.csproj @@ -81,11 +81,8 @@ + + - - - - - diff --git a/src/Samples/Kaleidoscope/Chapter8/Chapter8.csproj b/src/Samples/Kaleidoscope/Chapter8/Chapter8.csproj index f24a94f7e..49401afed 100644 --- a/src/Samples/Kaleidoscope/Chapter8/Chapter8.csproj +++ b/src/Samples/Kaleidoscope/Chapter8/Chapter8.csproj @@ -16,7 +16,10 @@ - + + + + diff --git a/src/Samples/Kaleidoscope/Chapter9/Chapter9.csproj b/src/Samples/Kaleidoscope/Chapter9/Chapter9.csproj index 64ca0c8c5..9e812d113 100644 --- a/src/Samples/Kaleidoscope/Chapter9/Chapter9.csproj +++ b/src/Samples/Kaleidoscope/Chapter9/Chapter9.csproj @@ -16,7 +16,10 @@ - + + + + diff --git a/src/Samples/Kaleidoscope/Kaleidoscope.Grammar/Kaleidoscope.Grammar.csproj b/src/Samples/Kaleidoscope/Kaleidoscope.Grammar/Kaleidoscope.Grammar.csproj index 98e12bd14..e036924ab 100644 --- a/src/Samples/Kaleidoscope/Kaleidoscope.Grammar/Kaleidoscope.Grammar.csproj +++ b/src/Samples/Kaleidoscope/Kaleidoscope.Grammar/Kaleidoscope.Grammar.csproj @@ -20,11 +20,8 @@ - - - - - - + + + diff --git a/src/Samples/OrcV2VeryLazy/OrcV2VeryLazy.csproj b/src/Samples/OrcV2VeryLazy/OrcV2VeryLazy.csproj index 2c2d1339d..278bbf9cf 100644 --- a/src/Samples/OrcV2VeryLazy/OrcV2VeryLazy.csproj +++ b/src/Samples/OrcV2VeryLazy/OrcV2VeryLazy.csproj @@ -10,9 +10,12 @@ True + + + + - diff --git a/src/Ubiquity.NET.ANTLR.Utils/AntlrParseErrorListenerAdapter.cs b/src/Ubiquity.NET.ANTLR.Utils/AntlrParseErrorListenerAdapter.cs deleted file mode 100644 index a9c63ae0f..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/AntlrParseErrorListenerAdapter.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.ANTLR.Utils -{ - /// Adapter to translate ANTLR error listeners to an - /// - /// This intentionally ignores the provided by ANTLR and uses the - /// provided in the constructor. This allows a much greater level of flexibility in reporting of diagnostics from - /// a parser. Especially in abstracting the underlying parse technology from the diagnostic reporting - /// - /// The is used to allow for future adaptation of the parser to map errors from a - /// recognizer state, which is not stable if the grammar changes. This ensures that the ID values remain unique - /// even if the underlying grammar changes. The default is to use a 1:1 mapping where the ID values are used - /// directly. Any value not in the map is used directly. - /// - /// - public class AntlrParseErrorListenerAdapter - : IAntlrErrorListener - , IAntlrErrorListener - { - /// Initializes a new instance of the class - /// Inner listener to route all notifications to - /// Map of ids to translate values to an ID - public AntlrParseErrorListenerAdapter( - IParseErrorListener innerListener, - ImmutableDictionary? identifierMap = default - ) - { - InnerListener = innerListener; - IdentifierMap = identifierMap; - } - - /// Gets the mapping for identifiers. If this is then no mapping is used. - public ImmutableDictionary? IdentifierMap { get; } - - /// - public void SyntaxError( TextWriter output // ignored - , [NotNull] IRecognizer recognizer - , [Nullable] int offendingSymbol - , int line - , int charPositionInLine - , [NotNull] string msg - , [Nullable] RecognitionException e - ) - { - var err = new SyntaxError( ParseErrorSource.Lexer - , recognizer.InputStream.SourceName - , GetMappedId(recognizer.State) - , string.Empty - , new SourcePosition(line, charPositionInLine, recognizer.InputStream.Index) - , msg - , e - ); - - InnerListener.SyntaxError( err ); - } - - /// - public void SyntaxError( TextWriter output // ignored - , [NotNull] IRecognizer recognizer - , [Nullable] IToken offendingSymbol - , int line - , int charPositionInLine - , [NotNull] string msg - , [Nullable] RecognitionException e - ) - { - var err = new SyntaxError( ParseErrorSource.Parser - , recognizer.InputStream.SourceName - , GetMappedId(recognizer.State) - , offendingSymbol.Text - , new SourcePosition(line, charPositionInLine, recognizer.InputStream.Index) - , msg - , e - ); - - InnerListener.SyntaxError( err ); - } - - private int GetMappedId(int state) - { - int mappedId = state; // assume 1:1 mapping. - - if(IdentifierMap is not null && IdentifierMap.IsEmpty) - { - if(IdentifierMap.TryGetValue(mappedId, out int mappedValue)) - { - mappedId = mappedValue; - } - } - - return mappedId; - } - - private readonly IParseErrorListener InnerListener; - } -} diff --git a/src/Ubiquity.NET.ANTLR.Utils/AntlrUtilities.cs b/src/Ubiquity.NET.ANTLR.Utils/AntlrUtilities.cs deleted file mode 100644 index 7187c5c89..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/AntlrUtilities.cs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.ANTLR.Utils -{ - /// Utility functions for extending ANTLR types - public static class AntlrUtilities - { - /// Gets a character based interval from a - /// context to get the interval from - /// Character based interval covered by the context - /// This handles the standard EOF rule and translates that to an invalid interval - public static Interval GetCharInterval( this ParserRuleContext ruleContext ) - { - ArgumentNullException.ThrowIfNull( ruleContext ); - - // if this is an EOF return an invalid interval - if(ruleContext.Start.Type == Recognizer.Eof) - { - return Interval.Invalid; - } - - int startChar = ruleContext.Start.StartIndex; - int endChar = ruleContext.Stop.StopIndex - 1; - return Interval.Of( Math.Min( startChar, endChar ), Math.Max( startChar, endChar ) ); - } - - /// Gets the source from a recognizer if it is available - /// Recognizer to get the stream from - /// The character stream or null if not available. - public static ICharStream? GetSourceStream( this IRecognizer recognizer ) - { - ArgumentNullException.ThrowIfNull( recognizer ); - - return recognizer.InputStream != null && recognizer.InputStream is ITokenStream tokenStream - ? tokenStream.TokenSource.InputStream - : null; - } - - /// Gets the source input text for a produced by an - /// Rule context to get the source text from - /// Recognizer that produced - /// Source contents for the rule or an empty string if the source is not available - public static string GetSourceText( this ParserRuleContext ruleContext, IRecognizer recognizer ) - { - return ruleContext.GetSourceText( recognizer.GetSourceStream() ); - } - - /// Gets the source input text for a parsed from a stream - /// Rule context to get the source text from - /// The stream the rule was parsed from - /// Source contents for the rule or an empty string if the source is not available - public static string GetSourceText( this ParserRuleContext ruleContext, ICharStream? charStream ) - { - if(charStream == null) - { - return string.Empty; - } - - var span = ruleContext.GetCharInterval( ); - return span.a < 0 ? string.Empty : charStream.GetText( span ); - } - - /// Generates a Unique ID for a parse tree node - /// parse tree to generate the id for - /// ID for the node - /// - /// This is useful when generating various graph visualization file formats as - /// they normally need unique IDs for each node to maintain proper references - /// between the nodes. - /// - public static string GetUniqueNodeId( this IParseTree tree ) - { - ArgumentNullException.ThrowIfNull( tree ); - - var bldr = new StringBuilder( tree.GetHashCode( ).ToString( CultureInfo.InvariantCulture ) ); - if(tree.Parent != null) - { - bldr.Append( tree.Parent.GetChildIndex( tree ) ); - bldr.Append( tree.Parent.GetUniqueNodeId() ); - } - - return bldr.ToString(); - } - - /// Determines the index of a child item in the parent - /// Parent tree to find the item in - /// Item to determine the index of - /// Zero based index in the parent or -1 if the item is not a child of - public static int GetChildIndex( this IParseTree tree, IParseTree item ) - { - ArgumentNullException.ThrowIfNull( tree ); - - for(int i = 0; i < tree.ChildCount; ++i) - { - if(item == tree.GetChild( i )) - { - return i; - } - } - - return -1; - } - - /// Enables enumeration of characters from a - /// Builder to enumerate - /// Enumerable for all the characters in the builder - public static IEnumerable AsEnumerable( this StringBuilder bldr ) - { - ArgumentNullException.ThrowIfNull( bldr ); - - for(int i = 0; i < bldr.Length; ++i) - { - yield return bldr[ i ]; - } - } - } -} diff --git a/src/Ubiquity.NET.ANTLR.Utils/DebugTraceListener.cs b/src/Ubiquity.NET.ANTLR.Utils/DebugTraceListener.cs deleted file mode 100644 index 6757e34eb..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/DebugTraceListener.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.ANTLR.Utils -{ - /// Provides debug notification of all rule processing while parsing - public class DebugTraceListener - : IParseTreeListener - { - /// Initializes a new instance of the class. - /// Parser to use to resolve names when generating messages - public DebugTraceListener( Antlr4.Runtime.Parser parser ) - { - Parser = parser; - } - - /// - public virtual void EnterEveryRule( ParserRuleContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - - Trace.TraceInformation( $"enter[{ctx.SourceInterval}] {Parser.RuleNames[ ctx.RuleIndex ]} [{ctx.GetType().Name}] Lt(1)='{((ITokenStream)Parser.InputStream).LT( 1 ).Text}'" ); - } - - /// - public virtual void ExitEveryRule( ParserRuleContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - - Trace.TraceInformation( $"exit[{ctx.SourceInterval}] {Parser.RuleNames[ ctx.RuleIndex ]} [{ctx.GetType().Name}] Lt(1)='{((ITokenStream)Parser.InputStream).LT( 1 ).Text}'" ); - } - - /// - public virtual void VisitErrorNode( IErrorNode node ) - { - ArgumentNullException.ThrowIfNull( node ); - - Trace.TraceInformation( "Error: '{0}'", node.ToStringTree() ); - } - - /// - public virtual void VisitTerminal( ITerminalNode node ) - { - ArgumentNullException.ThrowIfNull( node ); - - var parserRuleContext = ( ParserRuleContext )node.Parent.RuleContext; - IToken symbol = node.Symbol; - Trace.TraceInformation( "Terminal: '{0}' rule {1}", symbol, Parser.RuleNames[ parserRuleContext.RuleIndex ] ); - } - - private readonly Antlr4.Runtime.Parser Parser; - } -} diff --git a/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs b/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs deleted file mode 100644 index 2a699c844..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.Diagnostics; -global using System.Globalization; -global using System.IO; -global using System.Text; - -global using Antlr4.Runtime; -global using Antlr4.Runtime.Misc; -global using Antlr4.Runtime.Tree; - -global using Ubiquity.NET.Extensions; -global using Ubiquity.NET.Runtime.Utils; diff --git a/src/Ubiquity.NET.ANTLR.Utils/LocationExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/LocationExtensions.cs deleted file mode 100644 index de1ca1cff..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/LocationExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.ANTLR.Utils -{ - /// Utility class to provide extensions for translation of ANTLR location data into a - public static class LocationExtensions - { - /// Gets the for a given - /// Parser rule context to get the location from - /// for the context - public static SourceRange GetSourceRange( this ParserRuleContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - - return new SourceRange( new(ctx.Start.Line, ctx.Start.Column, ctx.Start.StartIndex) - , new(ctx.Stop.Line, ctx.Stop.Column, ctx.Stop.StopIndex) - ); - } - - /// Attempts to retrieve the from a - /// Context to get the span from - /// for this input context or a default constructed one - /// - /// Not all derived types will support line+col location information. This - /// only tests for a and retrieves the location from that. The base - /// RuleContext and other derived types simply get a default constructed location as the location is - /// not known. (They only store location as an integral interval without the line+col information) - /// - public static SourceRange GetSourceRange( this RuleContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - - if(ctx is ParserRuleContext ruleCtx) - { - ruleCtx.GetSourceRange(); - } - - // NOTE other RuleContext types may track position but a RuleContext itself - // only tracks the integral position as an Interval (no line+col info!) - return default; - } - - /// Gets the source location information for a token an represents - /// Terminal node - /// Source span for the terminal's token - public static SourceRange GetSourceRange( this ITerminalNode node ) - { - ArgumentNullException.ThrowIfNull( node ); - - return node.Symbol.GetSourceRange(); - } - - /// Gets the from an - /// Token to get the location information for - /// SourceLocation - public static SourceRange GetSourceRange( this IToken token ) - { - ArgumentNullException.ThrowIfNull( token ); - - // TODO: Q: Should this account for a newline in the token? - // A: Probably not, as a token can't span a newline. - return new SourceRange( - new(token.Line, token.Column, token.StartIndex), - new(token.Line, token.Column + token.Text.Length, token.StopIndex) - ); - } - } -} diff --git a/src/Ubiquity.NET.ANTLR.Utils/ReadMe.md b/src/Ubiquity.NET.ANTLR.Utils/ReadMe.md deleted file mode 100644 index 7abac0ebf..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/ReadMe.md +++ /dev/null @@ -1,25 +0,0 @@ -# About -This library provides general extensions to ANTLR including adapter bindings for the -`Ubiquity.NET.Runtime` library. - -# Key usage -* Get a SourceLocation from various ANTLR types (rule, tokens, terminals) - - This provides an adaptation to the abstract source location -* Debug trace listener - - Provides debug TRACE support for any parser by listening for every rule and using - Debug.Trace() to generate a string representation of that rule. This is VERY useful - when developing or debugging a grammar. -* Adapter for parse error listeners to a unified and abstract - `Ubiquity.NET.Runtime.IParseErrorListener`. - - This allows building consumers that deal with errors and remain independent of the - parsing technology. -* Extension functions that provides commonly used support for ANTLR - - Get a character interval from a ParserRuleContext with support for the standard EOF - rule. - - Gets the source stream from an IRecognizer - - Gets the source text from a rule context and recognizer that produced it. - - Gets source text from a rule context and stream that it was parsed from. - - Gets a unique ID for a parse tree - * Useful for building graphs of the result of parsing as many graphing - representations require a unique node id for every node in the graph. - diff --git a/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj b/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj deleted file mode 100644 index 00a812d8b..000000000 --- a/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - net8.0 - 13 - enable - True - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - General use .NET extensions and utilities - Extensions,.NET,Ubiquity.NET - ReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - - - - - - - - - - - - - all - false - Analyzer - - - - - diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs deleted file mode 100644 index 0fc338d3d..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/AnalyzerConfigOptionsProviderExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// Mostly from https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/AnalyzerConfigOptionsProviderExtensions.cs -// Reformatted and made to conform to repo guides - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Extension methods for the type. - public static class AnalyzerConfigOptionsProviderExtensions - { - /// Checks whether the input property has a valid value. - /// The input instance. - /// The Build property name. - /// The resulting property value, if invalid. - /// Whether the target property is a valid value. - public static bool IsValidBoolBuildProperty( - this AnalyzerConfigOptionsProvider options, - string propertyName, - [NotNullWhen( false )] out string? propertyValue - ) - { - return !options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out propertyValue ) - || string.IsNullOrEmpty( propertyValue ) - || string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase ) - || string.Equals( propertyValue, bool.FalseString, StringComparison.OrdinalIgnoreCase ); - } - - /// Gets the value of a build property. - /// The input instance. - /// The build property name. - /// The value of the specified build property. - /// - /// The return value is equivalent to a (case insensitive) '$(PropertyName)' == 'true' check. - /// That is, any other value, including empty/not present, is considered . - /// - public static bool GetBoolBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName ) - { - return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue ) - && string.Equals( propertyValue, bool.TrueString, StringComparison.OrdinalIgnoreCase ); - } - - /// Gets the value of a Build property representing a semicolon-separated list of strings. - /// The input instance. - /// The build property name. - /// The value of the specified build property. - public static ImmutableArray GetStringArrayBuildProperty( this AnalyzerConfigOptionsProvider options, string propertyName ) - { - return options.GlobalOptions.TryGetValue( $"{BuildProperty}.{propertyName}", out string? propertyValue ) - ? [ .. propertyValue.Split( ',', ';' ) ] - : []; - } - - // MSBuild properties that are visible to the compiler are available with the "build_property." prefix - // See: https://andrewlock.net/creating-a-source-generator-part-13-providing-and-accessing-msbuild-settings-in-source-generators/ - private const string BuildProperty = "build_property"; - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs deleted file mode 100644 index 6ca2942b5..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/BaseTypeDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility type to provide extensions for - public static class BaseTypeDeclarationSyntaxExtensions - { - // The following 2 extension methods are based on: - // https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/ - - /// Gets the declared namespace for a - /// Syntax to get the namespace for - /// Namespace of - public static string GetDeclaredNamespace(this BaseTypeDeclarationSyntax syntax) - { - // If we don't have a namespace at all we'll return an empty string - // This accounts for the "default namespace" case - string nameSpace = string.Empty; - - // Get the containing syntax node for the type declaration - // (could be a nested type, for example) - SyntaxNode? potentialNamespaceParent = syntax.Parent; - - // Keep moving "out" of nested classes etc until we get to a namespace - // or until we run out of parents - while (potentialNamespaceParent != null - && potentialNamespaceParent is not NamespaceDeclarationSyntax - && potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) - { - potentialNamespaceParent = potentialNamespaceParent.Parent; - } - - // Build up the final namespace by looping until we no longer have a namespace declaration - if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) - { - // We have a namespace. Use that as the type - nameSpace = namespaceParent.Name.ToString(); - - // Keep moving "out" of the namespace declarations until there - // are no more nested namespace declarations. - while (true) - { - if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) - { - break; - } - - // Add the outer namespace as a prefix to the final namespace - nameSpace = $"{namespaceParent.Name}.{nameSpace}"; - namespaceParent = parent; - } - } - - // return the final namespace - return nameSpace; - } - - /// Gets the nested class name for a - /// Syntax to get the name for - /// Flag to indicate if the type itself is included in the name [Default: - /// of the syntax or - public static NestedClassName? GetNestedClassName( this BaseTypeDeclarationSyntax syntax, bool includeSelf = false) - { - // Try and get the parent syntax. If it isn't a type like class/struct, this will be null - TypeDeclarationSyntax? parentSyntax = includeSelf ? syntax as TypeDeclarationSyntax : syntax.Parent as TypeDeclarationSyntax; - NestedClassName? parentClassInfo = null; - - // We can only be nested in class/struct/record - - // Keep looping while we're in a supported nested type - while (parentSyntax is not null) - { - // NOTE: due to bug https://github.com/dotnet/roslyn/issues/78042 this - // is not using a local static function to evaluate this in the condition - // of the while loop [Workaround: go back to "old" extension syntax...] - var rawKind = parentSyntax.Kind(); - bool isAllowedKind - = rawKind == SyntaxKind.ClassDeclaration - || rawKind == SyntaxKind.StructDeclaration - || rawKind == SyntaxKind.RecordDeclaration; - - if (!isAllowedKind) - { - break; - } - - // Record the parent type keyword (class/struct etc), name, and constraints - parentClassInfo = new NestedClassName( - keyword: parentSyntax.Keyword.ValueText, - name: parentSyntax.Identifier.ToString() + parentSyntax.TypeParameterList, - constraints: parentSyntax.ConstraintClauses.ToString(), - children: parentClassInfo is null ? [] : [parentClassInfo]); // set the child link (null initially) - - // Move to the next outer type - parentSyntax = parentSyntax.Parent as TypeDeclarationSyntax; - } - - // return a link to the outermost parent type - return parentClassInfo; - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs deleted file mode 100644 index eb631224e..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/CompilationExtensions.cs +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// Mostly from: https://github.com/Sergio0694/PolySharp/blob/main/src/PolySharp.SourceGenerators/Extensions/CompilationExtensions.cs -// Reformated and adapted to support repo guidelines - -using Microsoft.CodeAnalysis.VisualBasic; - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Structure for a runtime version - /// Name of the runtime - /// Version of the runtime - [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Simple record" )] - public readonly record struct RuntimeVersion( string RuntimeName, Version Version ); - - /// Extension methods for the type. - public static class CompilationExtensions - { - /// Checks whether a given compilation (assumed to be for C#) is using at least a given language version. - /// The to consider for analysis. - /// The minimum language version to check. - /// Whether is using at least the specified language version. - public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.CSharp.LanguageVersion languageVersion ) - { - return compilation is not CSharpCompilation csharpCompilation - ? throw new ArgumentNullException( nameof( compilation ) ) - : csharpCompilation.LanguageVersion >= languageVersion; - } - - /// Checks whether a given VB compilation is using at least a given language version. - /// The to consider for analysis. - /// The minimum language version to check. - /// Whether is using at least the specified language version. - [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "not Hungarian" )] - public static bool HasLanguageVersionAtLeastEqualTo( this Compilation compilation, Microsoft.CodeAnalysis.VisualBasic.LanguageVersion languageVersion ) - { - return compilation is not VisualBasicCompilation vbCompilation - ? throw new ArgumentNullException( nameof( compilation ) ) - : vbCompilation.LanguageVersion >= languageVersion; - } - - /// Gets the runtime version by extracting the version from the assembly implementing - /// Compilation to get the version information from - /// Version of the runtime the compilation is targetting - public static RuntimeVersion GetRuntimeVersion(this Compilation self) - { - var objectType = self.GetSpecialType(SpecialType.System_Object); - var runtimeAssembly = objectType.ContainingAssembly; - return new(runtimeAssembly.Identity.Name, runtimeAssembly.Identity.Version); - } - - /// Gets a value indicating wheter the compilation has a minium version of the runtime - /// Compilation to test - /// Minimum version accepted - /// if the runtime version targetted by the compilation is at least ; otherwise - public static bool HasRuntimeVersionAtLeast(this Compilation self, RuntimeVersion minVersion) - { - var runtimeVersion = GetRuntimeVersion(self); - return runtimeVersion.RuntimeName == minVersion.RuntimeName && runtimeVersion.Version >= minVersion.Version; - } - - /// Checks whether or not a type with a specified metadata name is accessible from a given instance. - /// The to consider for analysis. - /// The fully-qualified metadata type name to find. - /// Whether a type with the specified metadata name can be accessed from the given compilation. - /// - /// This method enumerates candidate type symbols to find a match in the following order: - /// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.
- /// 2) If the current defines the symbol, check its accessibility.
- /// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.
- ///
- public static bool HasAccessibleTypeWithMetadataName( this Compilation compilation, string fullyQualifiedMetadataName ) - { - if(compilation is null) - { - throw new ArgumentNullException( nameof( compilation ) ); - } - - if(string.IsNullOrWhiteSpace( fullyQualifiedMetadataName )) - { - throw new ArgumentException( $"'{nameof( fullyQualifiedMetadataName )}' cannot be null or whitespace.", nameof( fullyQualifiedMetadataName ) ); - } - - // If there is only a single matching symbol, check its accessibility - if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol) - { - return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly ); - } - - // Otherwise, check all available types - foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName )) - { - if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly )) - { - return true; - } - } - - return false; - } - - /// Checks whether or not a type with a specified metadata name is accessible from a given instance. - /// The to consider for analysis. - /// The fully-qualified metadata type name to find. - /// Name of the member - /// Whether a type with the specified metadata name can be accessed from the given compilation. - /// - /// This method enumerates candidate type symbols to find a match in the following order: - /// 1) If only one type with the given name is found within the compilation and its referenced assemblies, check its accessibility.
- /// 2) If the current defines the symbol, check its accessibility.
- /// 3) Otherwise, check whether the type exists and is accessible from any of the referenced assemblies.
- ///
- public static bool HasAccessibleMember( this Compilation compilation, string fullyQualifiedMetadataName, string memberName ) - { - // If there is only a single matching symbol, check its accessibility - if(compilation.GetTypeByMetadataName( fullyQualifiedMetadataName ) is INamedTypeSymbol typeSymbol) - { - return compilation.IsSymbolAccessibleWithin( typeSymbol, compilation.Assembly ) - && compilation.HasAccessibleMemberWithin( typeSymbol, memberName, compilation.Assembly); - } - - // Otherwise, check all available types - foreach(INamedTypeSymbol currentTypeSymbol in compilation.GetTypesByMetadataName( fullyQualifiedMetadataName )) - { - if(compilation.IsSymbolAccessibleWithin( currentTypeSymbol, compilation.Assembly ) - && compilation.HasAccessibleMemberWithin( currentTypeSymbol, memberName, compilation.Assembly) - ) - { - return true; - } - } - - return false; - } - - /// Tests if a has a type with an accessible member of a given name - /// to test - /// Type symbol for the type to test - /// Name of the member to test for - /// Symbol to test if the member is accessible within - /// Symbol to use for "protected access" [default: null] - /// if the member is accesible and - public static bool HasAccessibleMemberWithin( - this Compilation self, - ITypeSymbol typeSymbol, - string memberName, - ISymbol within, - ITypeSymbol? throughType = null - ) - { - if(self is null) - { - throw new ArgumentNullException( nameof( self ) ); - } - - if(typeSymbol is null) - { - throw new ArgumentNullException( nameof( typeSymbol ) ); - } - - if(string.IsNullOrEmpty( memberName )) - { - throw new ArgumentException( $"'{nameof( memberName )}' cannot be null or empty.", nameof( memberName ) ); - } - - if(within is null) - { - throw new ArgumentNullException( nameof( within ) ); - } - - var memberSymbol = typeSymbol.GetMembers().Where(s=>s.Name == memberName).FirstOrDefault(); - return memberSymbol is not null - && self.IsSymbolAccessibleWithin(memberSymbol, within, throughType); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs deleted file mode 100644 index fe67ebd7a..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// Modified from idea in blog post: https://andrewlock.net/creating-a-source-generator-part-9-avoiding-performance-pitfalls-in-incremental-generators/ - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Contains diagnostic information collected for reporting to the host - /// - /// This is an equatable type and therefore is legit for use in generators/analyzers where - /// that is needed for caching. A is not, so this record bundles - /// the parameters needed for creation of one and defers the construction until needed. - /// - public sealed record DiagnosticInfo - { - /// Initializes a new instance of the class. - /// Descriptor for the diagnostic - /// Location in the source file that triggered this diagnostic - /// Args for the message - public DiagnosticInfo(DiagnosticDescriptor descriptor, Location? location, params IEnumerable msgArgs) - { - Descriptor = descriptor; - Location = location; - Params = msgArgs.ToImmutableArray(); - } - - /// Gets the parameters for this diagnostic - public EquatableArray Params { get; } - - /// Gets the descriptor for this diagnostic - public DiagnosticDescriptor Descriptor { get; } - - // Location is an abstract type but all derived types implement IEquatable where T is Location - // Thus a location is equatable even though the base abstract type doesn't implement that interface. - - /// Gets the location of the source of this diagnostic - public Location? Location { get; } - - /// Factory to create a from the information contained in this holder - /// that represents this information - public Diagnostic CreateDiagnostic() - { - return Diagnostic.Create(Descriptor, Location, Params.ToArray()); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs deleted file mode 100644 index 29a5c8e3f..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableArray.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#pragma warning disable SA1642 // Constructor summary documentation should begin with standard text -#pragma warning disable SA1615 // Element return value should be documented -#pragma warning disable SA1604 // Element documentation should have summary -#pragma warning disable SA1611 // Element parameters should be documented -#pragma warning disable CA1000 // Do not declare static members on generic types -#pragma warning disable CA2225 // Operator overloads have named alternates - -// ORIGINALLY FROM: https://github.com/CommunityToolkit/dotnet/blob/7b53ae23dfc6a7fb12d0fc058b89b6e948f48448/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Extensions for . - public static class EquatableArray - { - /// Creates an instance from a given . - /// The type of items in the input array. - /// The input instance. - /// An instance from a given . - public static EquatableArray AsEquatableArray(this ImmutableArray array) - where T : IEquatable - { - return array.IsDefault ? throw new ArgumentNullException(nameof(array)) - : new(array); - } - } - - /// - /// An immutable, equatable array. This is equivalent to but with value equality of members support. - /// - /// The type of values in the array. - public readonly struct EquatableArray - : IEquatable> - , IEnumerable - where T : IEquatable - { - /// - /// The underlying array. - /// - private readonly T[]? array; - - /// - /// Creates a new instance. - /// - /// The input to wrap. - public EquatableArray(ImmutableArray array) - { - this.array = Unsafe.As, T[]?>(ref array); - } - - /// - /// Gets a reference to an item at a specified position within the array. - /// - /// The index of the item to retrieve a reference to. - /// A reference to an item at a specified position within the array. - public ref readonly T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref AsImmutableArray().ItemRef(index); - } - - /// - /// Gets a value indicating whether the current array is empty. - /// - public bool IsEmpty - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => AsImmutableArray().IsEmpty; - } - - /// Gets the length of the array - public int Length => array?.Length ?? 0; - - /// - public bool Equals(EquatableArray array) - { - return AsSpan().SequenceEqual(array.AsSpan()); - } - - /// - public override bool Equals([NotNullWhen(true)] object? obj) - { - return obj is EquatableArray array && Equals(this, array); - } - - /// - public override int GetHashCode() - { - if (this.array is not T[] array) - { - return 0; - } - - HashCode hashCode = default; - - foreach (T item in array) - { - hashCode.Add(item); - } - - return hashCode.ToHashCode(); - } - - /// - /// Gets an instance from the current . - /// - /// The from the current . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImmutableArray AsImmutableArray() - { - return Unsafe.As>(ref Unsafe.AsRef(in array)); - } - - /// - /// Creates an instance from a given . - /// - /// The input instance. - /// An instance from a given . - public static EquatableArray FromImmutableArray(ImmutableArray array) - { - return new(array); - } - - /// - /// Returns a wrapping the current items. - /// - /// A wrapping the current items. - public ReadOnlySpan AsSpan() - { - return AsImmutableArray().AsSpan(); - } - - /// - /// Copies the contents of this instance to a mutable array. - /// - /// The newly instantiated array. - public T[] ToArray() - { - return [.. AsImmutableArray()]; - } - - /// - /// Gets an value to traverse items in the current array. - /// - /// An value to traverse items in the current array. - public ImmutableArray.Enumerator GetEnumerator() - { - return AsImmutableArray().GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)AsImmutableArray()).GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)AsImmutableArray()).GetEnumerator(); - } - - /// - /// Implicitly converts an to . - /// - /// An instance from a given . - public static implicit operator EquatableArray(ImmutableArray array) - { - return FromImmutableArray(array); - } - - /// - /// Implicitly converts an to . - /// - /// An instance from a given . - public static implicit operator ImmutableArray(EquatableArray array) - { - return array.AsImmutableArray(); - } - - /// - /// Checks whether two values are the same. - /// - /// The first value. - /// The second value. - /// Whether and are equal. - public static bool operator ==(EquatableArray left, EquatableArray right) - { - return left.Equals(right); - } - - /// - /// Checks whether two values are not the same. - /// - /// The first value. - /// The second value. - /// Whether and are not equal. - public static bool operator !=(EquatableArray left, EquatableArray right) - { - return !left.Equals(right); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs deleted file mode 100644 index ffd720fe7..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalNamespaceImports.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -// BUG: False positive from IDE0005 - Using directive is unnecessary -// Attempts to remove/sort are at least able to figure it out and do the right thing. -// Bug seems to be related to multi-targetting. -#pragma warning disable IDE0005 - -global using System; -global using System.CodeDom.Compiler; -global using System.Collections; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.Diagnostics.CodeAnalysis; -global using System.IO; -global using System.Linq; -global using System.Reflection; -global using System.Runtime.CompilerServices; -global using System.Text; - -global using Microsoft.CodeAnalysis; -global using Microsoft.CodeAnalysis.CSharp; -global using Microsoft.CodeAnalysis.CSharp.Syntax; -global using Microsoft.CodeAnalysis.Diagnostics; -global using Microsoft.CodeAnalysis.Text; diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs deleted file mode 100644 index 2b0ab7675..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/GlobalSuppressions.cs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs deleted file mode 100644 index 2177bf75c..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/MemberDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class to provide extensions for - public static class MemberDeclarationSyntaxExtensions - { - /// Determines if a contains a specified attribute or not - /// to test - /// name of the attribute - /// if the attribute is found or if not - /// is - public static bool HasAttribute(this MemberDeclarationSyntax self, string attributeName) - { - return self is not null - ? self.TryGetAttribute(attributeName, out _) - : throw new ArgumentNullException(nameof(self)); - } - - /// Tries to get an from a - /// The to get the attribute from - /// name of the attribute - /// resulting or if not found - /// if the attribute is found or if not - public static bool TryGetAttribute( - this MemberDeclarationSyntax self, - string attributeName, - [NotNullWhen(true)] out AttributeSyntax? value - ) - { - value = null; - string shortName = attributeName; - if (attributeName.EndsWith("Attribute")) - { - shortName = shortName[..^9]; - } - else - { - attributeName += "Attribute"; - } - - var q = from attributeList in self.AttributeLists - from attribute in attributeList.Attributes - let name = attribute.GetIdentifierName() - where name == attributeName || name == shortName - select attribute; - - value = q.FirstOrDefault(); - return value != null; - } - - /// Determines if a declartion has an extern modifier - /// to test - /// if has the modifier or not - /// is null - public static bool IsExtern(this MemberDeclarationSyntax self) - { - return self is not null - ? self.Modifiers.HasExtern() - : throw new ArgumentNullException(nameof(self)); - } - - /// Determines if a declartion has a partial modifier - /// - public static bool IsPartial(this MemberDeclarationSyntax self) - { - return self is not null - ? self.Modifiers.HasPartialKeyword() - : throw new ArgumentNullException(nameof(self)); - } - - /// Determines if a declartion has a static modifier - /// - public static bool IsStatic(this MemberDeclarationSyntax self) - { - return self is not null - ? self.Modifiers.HasStatic() - : throw new ArgumentNullException(nameof(self)); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs deleted file mode 100644 index 2f4198212..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/MethodDeclarationSyntaxExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class to provide extensions for - public static class MethodDeclarationSyntaxExtensions - { - // LibraryImportAttribute - intentionally NOT reported as a P/Invoke - // It uses different qualifiers and, technically, is NOT a P/Invoke - // signature (It's a generated marshaling function with a nested private - // P/Invoke using NO marshaling) - - /// Determines if a method declaration is a P/Invoke - /// The to test - /// if is a P/Invoke declaration or if not - /// - /// LibraryImportAttribute is intentionally NOT reported as a P/Invoke. It uses different qualifiers and, - /// technically, is NOT a P/Invoke signature (It's a marker for a Roslyn source generator. The generated function - /// contains the marshaling with a nested private P/Invoke using NO marshaling) - /// - public static bool IsPInvoke(this MethodDeclarationSyntax self) - { - return self.IsStatic() - && self.IsExtern() - && self.HasAttribute("System.Runtime.InteropServices.DllImportAttribute"); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs deleted file mode 100644 index 7aea1b6ef..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/NestedClassName.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// Originally FROM: https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/ -// Modified to support IEquatable for caching -// Additional functionality as needed to generalize it. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Cacheable storage of nested class names for use in source generation - public sealed class NestedClassName - : IEquatable - { - /// Initializes a new instance of the class. - /// Keyword for this declaration - /// Name of the type - /// Constraints for this type - /// Names of any nested child types to form hiearachies - /// - /// is normally one of ("class", "struct", "interface", "record [class|struct]?"). - /// - public NestedClassName(string keyword, string name, string constraints, params IEnumerable children) - { - Keyword = keyword; - Name = name; - Constraints = constraints; - Children = children.ToImmutableArray().AsEquatableArray(); - } - - /// Gets child nested types - public EquatableArray Children { get; } - - /// Gets the keyword for this type - /// - /// This is normally one of ("class", "struct", "interface", "record [class|struct]?" - /// - public string Keyword { get; } - - /// Gets the name of the nested type - public string Name { get; } - - /// Gets the constraints for a nested type - public string Constraints { get; } - - /// Gets a value indicating whether this name contains constraints - public bool HasConstraints => !string.IsNullOrWhiteSpace(Constraints); - - /// Compares this instance with another - /// Value to compare this instance with - /// if the is equal to this instance - /// - /// This is, at worst, a recursive O(n) operation! However, since it is used for nested types - /// the actual depth is statistically rather small and nearly always 0 (Children is empty). - /// Deeply nested type declarations is a VERY rare anti-pattern so not a real world problem. - /// - public bool Equals(NestedClassName other) - { - if (other == null) - { - return false; - } - - if (ReferenceEquals(this, other)) - { - return true; - } - - // NOTE: This is a recursive O(n) operation! - return Equals(Children, other.Children) - && Name.Equals( other.Name, StringComparison.Ordinal ) - && Constraints.Equals( other.Constraints, StringComparison.Ordinal ); - } - - /// - public override bool Equals(object obj) - { - return obj is NestedClassName parentClass && Equals(parentClass); - } - - /// - public override int GetHashCode() - { - return HashCode.Combine(Children, Keyword, Name, Constraints); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs deleted file mode 100644 index 68af2179f..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/Result.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Container for a result or an error descriptor () - /// Value contained in the result [Constrained to ] - /// - /// - /// It is debatable if an incremental generator should produce diagnostics. The official - /// cookbook - /// recommends against it [Section: "Issue diagnostics"]. Instead it recommends use of an analyzer. While it - /// doesn't say what a generator is supposed to do in the event of an input error explicitly, it is implied - /// that it should silently ignore them. - /// - /// This type allows generating diagnostics in the final source production stage of the pipeline by plumbing - /// through all of the available data AND diagnostics via this generic record. This acts as a sort of - /// discriminated union of results or diagnostics (or possibly a combination of both). All while maintaining - /// support for caching with . - /// - /// - public readonly record struct Result - where T : IEquatable - { - /// Initializes a new instance of the struct from a value [No diagnostics] - /// Value of the result - public Result(T value) - : this(value, []) - { - } - - /// Initializes a new instance of the struct from diagnostics - /// Information describing the diagnostics for this result - public Result(params IEnumerable diagnostics) - : this(default, [.. diagnostics]) - { - } - - /// Initializes a new instance of the struct from a nullable value and set of potentially empty diagnostics - /// Value of the result (may be null to indicate no results) - /// Array of to describe any diagnostics/warnings encountered while producing - /// - /// This is the most generalized from of constructor. It supports BOTH a value and diagnostics as it is possible that - /// a value is producible, but there are warnings or other informative diagnostics to include with it. Attempts to construct - /// a result with no value and no diagnostics throws an exception. - /// - /// Both and are or empty - public Result(T? value, ImmutableArray diagnostics) - { - if (value is null && diagnostics.IsDefaultOrEmpty) - { - throw new ArgumentException($"Either {nameof(Value)} or {nameof(diagnostics)} must contain a value"); - } - - Value = value; - Diagnostics = diagnostics; - } - - /// Gets the value produced for this result or if no value produced - public T? Value { get; init; } = default; - - /// Gets a value indicating whether a value was produced for this result - public bool HasValue => Value is not null; - - /// Gets the diagnostics produced for this result (if any) - /// This may provide an empty array but is never - public EquatableArray Diagnostics { get; init; } = ImmutableArray.Empty; - - /// Gets a value indicating whether this result contains any diagnostics - /// This is a shorthand for testing the length of the property - public bool HasDiagnostics => !Diagnostics.IsEmpty; - - /// Report all diagnostics to the provided - /// to report the diagnostics to - /// - /// This supports the deferral of reporting with a collection of cahceable . This allows - /// for a generatr to report critical internal problems. - /// - public void ReportDiagnostics(SourceProductionContext ctx) - { - foreach (var di in Diagnostics) - { - ctx.ReportDiagnostic(di.CreateDiagnostic()); - } - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs deleted file mode 100644 index ba45e641a..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/RoslynExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class for general Roslyn extensions - /// - /// This is a place-holder for extensions that don't fit anywhere else and don't really warrant their own type/file. - /// - public static class RoslynExtensions - { - /// Gets an identifier name or () if the is not - /// to get the identifier from - /// Identifier name - /// is - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Nested conditionals are NOT simpler" )] - public static string GetIdentifierName(this AttributeSyntax self) - { - if(self is null) - { - throw new ArgumentNullException(nameof(self)); - } - - return self.Name is not IdentifierNameSyntax identifier - ? string.Empty - : identifier.Identifier.ValueText; - } - - /// Adds a source file from a manifest resource - /// The to add the source to - /// Assembly hosting the resource - /// Name of the resource - /// Hint name for the generated file - /// Encoding for the source [Default: ] - public static void AddSourceFromResource( - this IncrementalGeneratorPostInitializationContext self, - Assembly resourceAssembly, - string resourceName, - string hintName, - Encoding? encoding = null - ) - { - encoding ??= Encoding.UTF8; - using var reader = new StreamReader(resourceAssembly.GetManifestResourceStream(resourceName), encoding); - self.AddSource(hintName, SourceText.From(reader, checked((int)reader.BaseStream.Length), encoding)); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs deleted file mode 100644 index d48f94280..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/SourceProductionContextExtensions.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class to provide extensions for a - public static class SourceProductionContextExtensions - { - /// Reports deferred diagnostics for a - /// Type of the result values - /// The context to report any diagnostics to - /// The result - /// - /// if has no diagnostics associated with it this is a NOP. - /// - public static void ReportDiagnostic(this SourceProductionContext self, Result result) - where T : IEquatable - { - if(result.HasDiagnostics) - { - for(int i = 0; i < result.Diagnostics.Length; ++i) - { - self.ReportDiagnostic(result.Diagnostics[i]); - } - } - } - - /// Reports a diagnostic to - /// The context to report the diagnostic to - /// Cached info to report - public static void ReportDiagnostic( this SourceProductionContext self, DiagnosticInfo info) - { - self.ReportDiagnostic(info.CreateDiagnostic()); - } - - /// Reports a diagnostic to - /// The context to report the diagnostic to - /// Descriptor of the diagnostic - /// Message arguments - public static void ReportDiagnostic( this SourceProductionContext self, DiagnosticDescriptor descriptor, params object[] messageArgs) - { - self.ReportDiagnostic(Diagnostic.Create(descriptor, null, messageArgs)); - } - - /// Reports a diagnostic to - /// The context to report the diagnostic to - /// Location of the source of this diagnostic - /// Descriptor for the diagnostic - /// Argumnets, if any, for the diagnostic message - public static void ReportDiagnostic( - this SourceProductionContext self, - Location location, - DiagnosticDescriptor descriptor, - params object[] messageArgs - ) - { - self.ReportDiagnostic(Diagnostic.Create(descriptor, location, messageArgs)); - } - - /// Reports a diagnostic to - /// The context to report the diagnostic to - /// Node as the source of the diagnostic - /// Descriptor for the diagnostic - /// Argumnets, if any, for the diagnostic message - public static void ReportDiagnostic( - this SourceProductionContext self, - CSharpSyntaxNode node, - DiagnosticDescriptor descriptor, - params object[] messageArgs - ) - { - self.ReportDiagnostic(node.GetLocation(), descriptor, messageArgs); - } - - /// Report diagnostics for results to - /// Type of the result - /// The context to report the diagnostic to - /// Array of for the results - public static void ReportDiagnostics( this SourceProductionContext self, ImmutableArray> results) - where T : IEquatable - { - for(int i = 0; i < results.Length; ++i) - { - self.ReportDiagnostic(results[i]); - } - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs deleted file mode 100644 index 628ea851a..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/StringBuilderText.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// A implementation over a - /// - /// This provides a implementation over a - /// it provides access to the underlying builder to allow construction of an - /// to manage indentation in the output. Manual generation of the output with indentation tracking is the - /// recommended approach to generating source output. - /// - /// - public class StringBuilderText - : SourceText - { - /// Initializes a new instance of the class - /// Builder to use for building strings - /// Encoding to use for the strings - /// Hash algorithm to use for debug symbols in the source - public StringBuilderText(StringBuilder builder, Encoding encoding, SourceHashAlgorithm algorithm = SourceHashAlgorithm.Sha1) - : base(checksumAlgorithm: algorithm) - { - Builder = builder; - InternalEncoding = encoding; - } - - /// Initializes a new instance of the class - /// Encoding to use for the strings - /// Hash algorithm to use for debug symbols in the source - /// - /// This constructor overload will create a new as the underlying - /// store for the strings. This is likely the most common case. - /// - public StringBuilderText(Encoding encoding, SourceHashAlgorithm algorithm = SourceHashAlgorithm.Sha1) - : this(new StringBuilder(), encoding, algorithm) - { - } - - /// Creates a new over the internal - /// The newly created - /// - /// The created, does NOT dispose of or invalidate the underlying - /// . This allows things like - /// to work even after is called. - /// The created writer is commonly wrapped in an instance of - /// for generating source output in a source generator. - /// - public StringWriter CreateWriter() - { - return new StringWriter(Builder); - } - - /// Gets the internal builder - public StringBuilder Builder { get; init; } - - /// - public override char this[int position] => Builder[position]; - - /// - public override Encoding Encoding => InternalEncoding; - - /// - public override int Length => Builder.Length; - - /// - public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count) - { - Builder.CopyTo(sourceIndex, destination, destinationIndex, count); - } - - /// Converts the specified span in the underlying builder into a string - /// Span to convert - /// Text from the builder as a string - public override string ToString(TextSpan span) - { - return Builder.ToString(span.Start, span.Length); - } - - private readonly Encoding InternalEncoding; - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs deleted file mode 100644 index bf0287dd9..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/SyntaxTokenListExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class for extensions to - public static class SyntaxTokenListExtensions - { - /// Gets a value indicating whether has the "extern" keyword - /// to test - /// if the keyword is found if not - public static bool HasExtern(this SyntaxTokenList self) - { - return self.Any(SyntaxKind.ExternKeyword); - } - - /// Gets a value indicating whether has the "partial" keyword - /// - public static bool HasPartialKeyword(this SyntaxTokenList self) - { - return self.Any(SyntaxKind.PartialKeyword); - } - - /// Gets a value indicating whether has the "static" keyword - /// - public static bool HasStatic(this SyntaxTokenList self) - { - return self.Any(SyntaxKind.StaticKeyword); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs deleted file mode 100644 index 4088b132c..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/TypeSyntaxExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CodeAnalysis.Utils -{ - /// Utility class to provide extensions for - public static class TypeSyntaxExtensions - { - /// Tests if is a string - /// to test - /// if is a string and if not - public static bool IsString(this TypeSyntax? self) - { - return (self is PredefinedTypeSyntax pts && pts.Keyword.IsKind(SyntaxKind.StringKeyword)) - || (self is QualifiedNameSyntax qns && qns.Left.ToString() == "System" && qns.Right.ToString() == "String"); - } - - /// Tests if is a void - /// to test - /// if is a void and if not - public static bool IsVoid(this TypeSyntax? self) - { - return self is PredefinedTypeSyntax pts - && pts.Keyword.IsKind(SyntaxKind.VoidKeyword); - } - } -} diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj b/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj deleted file mode 100644 index 0d7dabb50..000000000 --- a/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - netstandard2.0 - enable - - - preview - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs b/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs deleted file mode 100644 index 9e94e6a57..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Text; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Ubiquity.NET.CommandLine; - -namespace Ubiquity.NET.Commandline.UT -{ - [TestClass] - public class ArgumentExceptionReporterTests - { - private const string ParamExpressionName = "expression"; - private const string VerboseMessage = "This is a verbose"; - private const string InformationMessage = "This is an Information level message"; - private const string WarningMessage = "This is a warning"; - private const string ErrorMessage = "This is an error"; - - [TestMethod] - public void ArgumentExceptionReporterTest( ) - { - var reporter = new ArgumentExceptionReporter(ParamExpressionName); - Assert.IsNotNull( reporter ); - Assert.AreEqual( ParamExpressionName, reporter.ArgumentExpression ); - Assert.AreEqual( MsgLevel.Error, reporter.Level ); - Assert.AreEqual( Encoding.Unicode, reporter.Encoding ); - } - - [TestMethod] - public void ReportTest( ) - { - var reporter = new ArgumentExceptionReporter(ParamExpressionName); - - // should not throw for a Verbose level message - var verboseMsg = new DiagnosticMessage() - { - Level = MsgLevel.Verbose, - Text = VerboseMessage, - }; - - reporter.Report( verboseMsg ); - - // should not throw for an Information level message - var informationMsg = new DiagnosticMessage() - { - Level = MsgLevel.Information, - Text = InformationMessage, - }; - - reporter.Report( verboseMsg ); - - // should not throw for a warning - var warningMsg = new DiagnosticMessage() - { - Level = MsgLevel.Warning, - Text = WarningMessage, - }; - - reporter.Report( warningMsg ); - - // should only throw for an error - var errorMsg = new DiagnosticMessage() - { - Level = MsgLevel.Error, - Text = ErrorMessage, - }; - - var ex = Assert.ThrowsExactly(()=>reporter.Report(errorMsg)); - Assert.AreSame( ParamExpressionName, ex.ParamName ); - Assert.StartsWith( ErrorMessage, ex.Message ); - } - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/AssemblyInfo.cs b/src/Ubiquity.NET.CommandLine.UT/AssemblyInfo.cs deleted file mode 100644 index f3266f005..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customize this process see: https://aka.ms/assembly-info-properties - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. -[assembly: Guid( "994905c9-5cea-480a-b9fa-1458c5fc04d5" )] - -[assembly: CLSCompliant( false )] - -[assembly: Parallelize( Scope = ExecutionScope.ClassLevel )] -[assembly: ExcludeFromCodeCoverage] diff --git a/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs b/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs deleted file mode 100644 index 229c375f8..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.CommandLine; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.CommandLine.UT -{ - [TestClass] - public class CommandLineTests - { - [TestMethod] - public void CommandLine_parse_with_version_option_only_succeeds( ) - { - var settings = CreateTestSettings(); - var result = ArgsParsing.Parse(["--version"], settings); - - Assert.HasCount( 0, result.Errors, "Version alone should not procue errors" ); - - var versionOption = result.GetVersionOption(); - Assert.IsNotNull(versionOption); - Assert.AreEqual( versionOption.Action, result.Action); - } - - [TestMethod] - public void CommandLine_with_help_option_only_succeeds( ) - { - var settings = CreateTestSettings(); - - var result = ArgsParsing.Parse(["--help"], settings); - Assert.HasCount( 0, result.Errors ); - } - - [TestMethod] - public void CommandLine_with_unknown_option_has_errors( ) - { - var settings = CreateTestSettings(); - ParseResult result = ArgsParsing.Parse(["--FooBar"], settings ); - Assert.HasCount( 2, result.Errors, "Errors should include missing Required, and invalid param" ); - } - - [TestMethod] - public void CommandLine_with_known_option_and_version_has_errors( ) - { - var settings = CreateTestSettings(); - ParseResult result = ArgsParsing.Parse(["--version", "--option1"], settings ); - - Assert.HasCount( 1, result.Errors, "Should be one error (--version must be set alone) [Other errors ignored for --version]" ); - } - - [TestMethod] - [Ignore("https://github.com/dotnet/command-line-api/issues/2664")] - public void CommandLine_with_known_option_requiring_arg_and_version_has_errors( ) - { - var settings = CreateTestSettings(); - ParseResult result = ArgsParsing.Parse(["--option1", "--version"], settings ); - - // until https://github.com/dotnet/command-line-api/issues/2664 is resolved this will fail - Assert.HasCount( 2, result.Errors, "Should be one error (--version must be set alone, missing arg for --option1)" ); - } - - internal static CmdLineSettings CreateTestSettings( DefaultOption defaultOptions = DefaultOption.Help | DefaultOption.Version ) - { - return new CmdLineSettings() - { - DefaultOptions = defaultOptions, - }; - } - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/GlobalSuppressions.cs b/src/Ubiquity.NET.CommandLine.UT/GlobalSuppressions.cs deleted file mode 100644 index a79c00f1d..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/GlobalSuppressions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Unit Tests" )] -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1652:Enable XML documentation output", Justification = "Unit Tests" )] diff --git a/src/Ubiquity.NET.CommandLine.UT/ModuleFixtures.cs b/src/Ubiquity.NET.CommandLine.UT/ModuleFixtures.cs deleted file mode 100644 index d54a29323..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/ModuleFixtures.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#if USE_MODULE_FIXTURES -using System; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.CommandLine.UT -{ - // Provides common location for one time initialization for all tests in this assembly - [TestClass] - public static class ModuleFixtures - { - [AssemblyInitialize] - public static void AssemblyInitialize( TestContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - } - - [AssemblyCleanup] - public static void AssemblyCleanup( ) - { - } - } -} -#endif diff --git a/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs b/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs deleted file mode 100644 index 888b32dd8..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/RawApiTests.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.CommandLine; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.CommandLine.UT -{ - /// This is mostly for validation.understanding of the underlying RAW API as well as a good place to put "samples" for bug reports - [TestClass] - public class RawApiTests - { - [TestMethod] - [Ignore( "https://github.com/dotnet/command-line-api/issues/2664" )] - public void RawApi_Version_Error_tests( ) - { - var rootCommand = new RootCommand("Test Root") - { - new Option("--option1") - { - Description = "Test option `", - Required = true, - }, - }; - - var result = rootCommand.Parse(["--FooBar", "--version"]); - Assert.HasCount( 3, result.Errors, "Errors should account for, bogus arg (`--FooBar`), missing required arg (`--option1`), AND that `--version` should be solo" ); - } - - [TestMethod] - [Ignore( "https://github.com/dotnet/command-line-api/issues/2664" )] - public void RawApi_Help_Error_tests( ) - { - var rootCommand = new RootCommand("Test Root") - { - new Option("--option1") - { - Description = "Test option", - Required = true, - }, - }; - - var result = rootCommand.Parse(["--FooBar", "--help"]); - var helpOption = result.GetHelpOption(); - Assert.IsNotNull(helpOption); - Assert.AreEqual(helpOption.Action, result.Action); - Assert.HasCount( 3, result.Errors, "Errors should account for bogus arg (`--FooBar`), missing required arg (`--option1`), AND that `--version` should be solo" ); - } - - [TestMethod] - public void RawApi_Version_Only_with_required_has_no_errors( ) - { - var rootCommand = new RootCommand("Test Root") - { - new Option("--option1") - { - Description = "Test option `", - Required = true, - }, - }; - - var result = rootCommand.Parse(["--version"]); - Assert.HasCount( 0, result.Errors, "Should not be any errors" ); - } - - [TestMethod] - [Ignore("https://github.com/dotnet/command-line-api/issues/2664")] - public void RawApi_Version_with_required_option_has_errors( ) - { - var rootCommand = new RootCommand("Test Root") - { - new Option("--option1") - { - Description = "Test option `", - Required = true, - }, - }; - - ParseResult result = rootCommand.Parse(["--version", "--option1"]); - - // Known bug in runtime lib. This assert will fail - result.Errors.Count == 1! - // result.Errors: - // [0]{--version option cannot be combined with other arguments.} - // [1]{Required argument missing for option: '--option1'.} - Assert.HasCount( 2, result.Errors, "Should be two errors (version not used solo, missing arg)" ); - - // try with arguments in reversed order (--version is later) - result = rootCommand.Parse(["--option1", "--version"]); - - // result.Action == null! [BUG] - // result.Errors.Count == 0! [BUG] - Assert.HasCount( 2, result.Errors, "Should be two errors (version not used solo, missing arg)" ); - result = rootCommand.Parse("--option1 --version"); - - // Known bug in runtime lib. This assert will fail - result.Errors.Count == 0! - // result.Action == null! [BUG] - // result.Errors.Count == 0! [BUG] - Assert.HasCount( 2, result.Errors, "Should be two errors (version not used solo, missing arg)" ); - } - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/SettingsTestExtensions.cs b/src/Ubiquity.NET.CommandLine.UT/SettingsTestExtensions.cs deleted file mode 100644 index 5b17167ab..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/SettingsTestExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Collections.Immutable; -using System.IO; - -namespace Ubiquity.NET.CommandLine.UT -{ - internal static class SettingsTestExtensions - { - public static ImmutableArray GetOutput( this StringWriter self ) - { - ArgumentNullException.ThrowIfNull( self ); - string underlyingString = self.ToString(); - return string.IsNullOrWhiteSpace( underlyingString ) - ? [] - : [ .. underlyingString.Split( self.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries ) ]; - } - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/TestOptions.cs b/src/Ubiquity.NET.CommandLine.UT/TestOptions.cs deleted file mode 100644 index b87f1c62b..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/TestOptions.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.UT -{ - internal partial class TestOptions - { - public string Option1 { get; init; } = string.Empty; - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/TestOptions.g.cs b/src/Ubiquity.NET.CommandLine.UT/TestOptions.g.cs deleted file mode 100644 index 531e436fb..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/TestOptions.g.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Ubiquity.NET.CommandLine.UT -{ - // FUTURE: Generate this with source generator from attributes in other partial declaration - internal partial class TestOptions - : ICommandLineOptions - { - public static TestOptions Bind( ParseResult parseResult ) - { - return new() - { - Option1 = parseResult.GetValue(Descriptors.Option1), - }; - } - - public static AppControlledDefaultsRootCommand BuildRootCommand( CmdLineSettings settings ) - { - return new( settings, "Test option root command") - { - Descriptors.Option1, - }; - } - - internal static class Descriptors - { - internal static Option Option1 - = new("--option1") - { - Description = "Test Option", - Required = true, // Should have no impact on Help/Version - }; - } - } -} diff --git a/src/Ubiquity.NET.CommandLine.UT/Ubiquity.NET.CommandLine.UT.csproj b/src/Ubiquity.NET.CommandLine.UT/Ubiquity.NET.CommandLine.UT.csproj deleted file mode 100644 index dcafb15ac..000000000 --- a/src/Ubiquity.NET.CommandLine.UT/Ubiquity.NET.CommandLine.UT.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net8.0 - false - True - - - - - - - - - - - - diff --git a/src/Ubiquity.NET.CommandLine/AppControlledDefaultsRootCommand.cs b/src/Ubiquity.NET.CommandLine/AppControlledDefaultsRootCommand.cs deleted file mode 100644 index a78573455..000000000 --- a/src/Ubiquity.NET.CommandLine/AppControlledDefaultsRootCommand.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Extension of that allows app control of defaults that are otherwise forced - /// - /// This type is derived from and offers no additional behavior beyond the construction. - /// The constructor will adapt the command based on the provided. This moves the - /// hard coded defaults into an app controlled domain. The default constructed settings matches the behavior of - /// so there's no distinction. This allows an application to explicitly decide the behavior - /// and support of various defaults that could otherwise surprise the author/user. This is especially important when - /// replacing the internal command line handling of a published app or otherwise creating a "drop-in" replacement. In - /// such cases, strict adherence to back-compat is of paramount importance and the addition of default behavior is - /// potentially a breaking change. - /// - [SuppressMessage( "Design", "CA1010:Generic interface should also be implemented", Justification = "Collection initialization" )] - public class AppControlledDefaultsRootCommand - : RootCommand - { - /// Initializes a new instance of the class. - /// Description of this root command - /// Settings to apply for the command parsing - public AppControlledDefaultsRootCommand( CmdLineSettings settings, string description = "" ) - : base( description ) - { - ArgumentNullException.ThrowIfNull(settings); - - // RootCommand constructor already adds HelpOption and VersionOption so remove them - // unless specified by caller. - var removeableOptions = from o in Options - where (o is HelpOption && !settings.DefaultOptions.HasFlag(DefaultOption.Help)) - || (o is VersionOption && !settings.DefaultOptions.HasFlag(DefaultOption.Version)) - select o; - - // .ToArray forces duplication of the enumeration to prevent exception from modifying - // the underlying list while enumerating. - foreach(var o in removeableOptions.ToArray()) - { - Options.Remove( o ); - } - - // RootCommand constructor adds the "SuggestDirective" directive. - if(!settings.DefaultDirectives.HasFlag( DefaultDirective.Suggest )) - { - // Remove default added and start clean. - Directives.Clear(); - } - - // Add additional directives based on app controlled settings - if(settings.DefaultDirectives.HasFlag( DefaultDirective.Diagram )) - { - Add( new DiagramDirective() ); - } - - if(settings.DefaultDirectives.HasFlag( DefaultDirective.EnvironmentVariables )) - { - Add( new EnvironmentVariablesDirective() ); - } - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/ArgsParsing.cs b/src/Ubiquity.NET.CommandLine/ArgsParsing.cs deleted file mode 100644 index af5b31595..000000000 --- a/src/Ubiquity.NET.CommandLine/ArgsParsing.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Results of invoking the default handlers for or - /// Indicates whether the caller should exit (either it was handled successfully or an error was reported) - /// Exit code from the invocation (only valid if is - [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Simple record used by this type" )] - public readonly record struct DefaultHandlerInvocationResult( bool ShouldExit, int ExitCode ); - - /// Static utility class to provide parsing of command lines - public static class ArgsParsing - { - /// Parses the command line - /// Type of value to bind the results to [Must implement ] - /// args array for the command line - /// Settings for the parse - /// Results of the parse - /// - /// Additional steps might include:
- /// - /// 1) App specific Validation/semantic analysis
- /// 2) Binding of results to an app specific type
- /// 3) Act on the results as proper for the application
- /// a. This might include actions parsed but generally isolating the various stages is an easier to understand/maintain model
- /// b. Usually this is just app specific code that uses the bound results to adapt behavior
- /// - /// - /// This isolation of stages fosters clean implementation AND allows for variances not considered or accounted for in the - /// parsing library. (For instance mutual exclusion of options etc...) validation is an APP specific thing. There - /// may well be common validations available for re-use. But ultimately the relationships of all options etc... are dependent - /// on the app and sometimes the runtime environment. (i.e., Should an app maintain strict adherence to a command line options - /// even when such options/patterns are NOT the norm on that environment/OS?) - /// The system already confuses the help and version "options" as they are conceptually - /// "commands" in terms of that library. To be fair, the POSIX description it is based on confuses the point as well. This - /// ambiguity continues by attaching actions and validation to many symbols. While that might seem like it's a good thing, - /// almost every app needs to customize the behavior. Some apps simply can't do so using the current models. Thus, this - /// implementation simply removes the actions and validation to leave both stages to the calling application as it keeps - /// things clearer when stages are unique - ///
- /// - public static ParseResult Parse( string[] args, CmdLineSettings? settings = null ) - where T : ICommandLineOptions - { - settings ??= new CmdLineSettings(); - RootCommand rootCommand = T.BuildRootCommand(settings); - return rootCommand.Parse( args, settings ); - } - - /// Invokes default options ( or ) - /// Result of parse - /// settings to determine if enabled (optimizes implementation to a skip if none set) - /// Reporter to use to report any diagnostics (Including the "output" of the option) - /// Results of the invocation (see remarks for details). - /// - /// The results of invoking defaults is a tuple of a flag indicating if the app should exit - default command handled, - /// and the exit code for the application. The exit code is undefined if the flag indicates the app should not exit (e.g., not - /// handled). If it is defined, then that is what the app should return. It may be 0 if the command had no errors. But if there - /// was an error with the execution of the default option - /// - public static DefaultHandlerInvocationResult InvokeDefaultOptions( - this ParseResult parseResult, - CmdLineSettings settings, - IDiagnosticReporter diagnosticReporter - ) - { - // OPTIMIZATION: skip this if not selected by settings - if(!settings.DefaultOptions.HasFlag( DefaultOption.Help ) && !settings.DefaultOptions.HasFlag( DefaultOption.Version )) - { - return new( false, 0 ); - } - - // Find the options and only invoke the results action if it is one of the option's. - // Sadly, there is no other way to provide the invocation configuration besides the - // Invoke method on the results type. - var helpOption = parseResult.GetHelpOption(); - var versionOption = parseResult.GetVersionOption(); - if((helpOption?.Action != null && parseResult.Action == helpOption.Action) - || (versionOption?.Action != null && parseResult.Action == versionOption.Action) - ) - { - int exitCode = parseResult.Invoke(diagnosticReporter.CreateConfig()); - return new( true, exitCode ); - } - - // action doesn't match; "no-app-exit" - // NOTE: Action won't match if it is for parse errors... - return new( false, 0 ); - } - - /// Reports any error found during parsing - /// Results of the parse - /// Reporter to report parse errors to - /// if errors found and if not - public static bool ReportErrors( this ParseResult parseResult, IDiagnosticReporter diagnosticReporter ) - { - if(parseResult.HasErrors()) - { - foreach(var err in parseResult.Errors) - { - // CONSIDER: extract location from error? - diagnosticReporter.Error( CultureInfo.CurrentCulture, err.Message ); - } - - return true; - } - - return false; - } - - /// Tries to parse a command line, and binds the results to a value - /// Type of the value to bind the results to - /// Arguments to parse - /// settings to use for the parse - /// Reporter to use for diagnostic reporting - /// Resulting value if parsed successfully - /// Exit code for the process (only valid when return is (see remarks) - /// if the app should continue and if not - /// - /// Since this wraps several common operations, some of which may require exiting the app, the return value - /// has the semantics of "App should continue". In the event of parse errors or failures in invocation of - /// the default options the result is a with an set to - /// a proper exit code value. (non-zero for errors and zero for success even though the app should still - /// exit) - /// - /// This wraps the common pattern of parsing a command line, invoking default options, and binding the results of a parse - /// using a standard .NET "try pattern". The invocation of default options may return with - /// set to 0. This indicates the parse was successful AND that the default option was - /// run successfully and the app should exit. - /// - /// In short, this wraps the following sequence of common operations and exiting on completion of - /// any operation with errors or successful invocation of default options:
- /// 1)
- /// 2)
- /// 3)
- /// 4)
- /// - /// The is set to the exit code for the app on failures. This code indicates the - /// parse errors and is the result of invoking which, as of the current release, - /// is always 1, though this behavior is not documented and therefore subject to change. Thus, calling applications - /// should ***NOT*** rely on this value and instead use their own value to indicate a parse error that is documented - /// and stable. - ///
- public static bool TryParse( - string[] args, - CmdLineSettings? settings, - IDiagnosticReporter diagnosticReporter, - [MaybeNullWhen( false )] out T boundValue, - out int exitCode - ) - where T : ICommandLineOptions - { - settings ??= new CmdLineSettings(); - boundValue = default; - RootCommand rootCommand = T.BuildRootCommand(settings); - ParseResult parseResult = rootCommand.Parse( args, settings ); - - // Special case the default options (Help/Version) before checking for reported errors - // as errors about missing required params when the defaults are used is actually "normal" - // (Ridiculously stupid, but considered normal by owners of System.CommandLine [sigh...]) - var invokeResults = parseResult.InvokeDefaultOptions(settings, diagnosticReporter); - if(invokeResults.ShouldExit) - { - exitCode = invokeResults.ExitCode; - return false; - } - - if(parseResult.Action is ParseErrorAction pea) - { - pea.ShowHelp = settings.ShowHelpOnErrors; - pea.ShowTypoCorrections = settings.ShowTypoCorrections; - exitCode = pea.Invoke(parseResult); - return false; - } - - boundValue = T.Bind( parseResult ); - exitCode = 0; - return true; - } - - /// - public static bool TryParse( string[] args, CmdLineSettings settings, [MaybeNullWhen( false )] out T boundValue, out int exitCode ) - where T : ICommandLineOptions - { - return TryParse( args, settings, new ConsoleReporter( MsgLevel.Information ), out boundValue, out exitCode ); - } - - /// - public static bool TryParse( string[] args, [MaybeNullWhen( false )] out T boundValue, out int exitCode ) - where T : ICommandLineOptions - { - return TryParse( args, settings: null, new ConsoleReporter( MsgLevel.Information ), out boundValue, out exitCode ); - } - - /// - public static bool TryParse( string[] args, IDiagnosticReporter diagnosticReporter, [MaybeNullWhen( false )] out T boundValue, out int exitCode ) - where T : ICommandLineOptions - { - return TryParse( args, settings: null, diagnosticReporter, out boundValue, out exitCode ); - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs b/src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs deleted file mode 100644 index b46f70b4b..000000000 --- a/src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Implementation of that throws for any errors reported. - public class ArgumentExceptionReporter - : IDiagnosticReporter - { - /// Initializes a new instance of the class. - /// Expression for the argument to throw exceptions for - public ArgumentExceptionReporter( string exp ) - { - ArgumentExpression = exp; - } - - /// Gets the expression for the argument to throw exceptions for - public string ArgumentExpression { get; } - - /// - public MsgLevel Level => MsgLevel.Error; - - /// - public Encoding Encoding => Encoding.Unicode; - - /// Any diagnostics with will throw an argument exception - /// - public void Report( DiagnosticMessage diagnostic ) - { - if(this.IsEnabled(diagnostic.Level)) - { - throw new ArgumentException(diagnostic.ToString(), ArgumentExpression); - } - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/CmdLineSettings.cs b/src/Ubiquity.NET.CommandLine/CmdLineSettings.cs deleted file mode 100644 index 5eb446679..000000000 --- a/src/Ubiquity.NET.CommandLine/CmdLineSettings.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Flags to determine the default Options for an - [Flags] - public enum DefaultOption - { - /// No default options used - None = 0, - - /// Include the default help option - Help, - - /// Include the default version option - Version, - } - - /// Flags to determine the default directives supported for an - [Flags] - public enum DefaultDirective - { - /// No default directives included - None = 0, - - /// Include support for - Suggest, - - /// Include support for - Diagram, - - /// Include support for - EnvironmentVariables, - } - - /// Contains settings for parsing a command line - /// - /// This is effectively an extension to adding control - /// of the default options and directives. This is used with - /// to adapt the defaults otherwise forced by . Of particular interest is the - /// which, if not set uses the default from - /// which is not publicly available. If the value - /// is set to then no replacer is used or supported. If it is set to a non-null value - /// then that replacer is used. - /// The default values follows the default behaviors of the underlying library. This ensures the - /// principle of least surprise while allowing for explicit overrides - /// - public class CmdLineSettings - { - /// Gets a value indicating whether errors reported should also show the help message [Default: ] - /// This has a default, which is the opposite of the default from - public bool ShowHelpOnErrors { get; init; } = true; - - /// Gets a value indicating whether errors reported should also show Typo corrections [Default: ] - /// This has a default, which is the opposite of the default from - public bool ShowTypoCorrections { get; init; } = false; - - /// - public bool EnablePosixBundling { get; init; } = true; - - /// Gets a value that indicates the default options to include - /// - /// Default handling includes and . - /// This allows overriding that to specify behavior as needed. - /// - public DefaultOption DefaultOptions { get; init; } = DefaultOption.Help | DefaultOption.Version; - - /// Gets a value that indicates the default Directives to include - /// - /// Default handling includes . - /// This allows overriding that to specify behavior as needed. - /// - public DefaultDirective DefaultDirectives { get; init; } = DefaultDirective.Suggest; - - /// Gets a response file token replacer. [Default: internal common token replacement] - /// - /// Unless explicitly set the default behavior, which is not otherwise accessible, is used. - /// - /// Any option preceded by a '@' will trigger a call to this delegate if specified. The `tokenToReplace` - /// parameter to the delegate is the characters following, but not including, the leading '@'. The default - /// assumes that is a file name containing the command in a `Response` file. The default is normally what - /// is used but, since it isn't directly accessible, this class helps in expressing the behavior. - /// - /// - public TryReplaceToken? ResponseFileTokenReplacer - { - get; - init - { - field = value; - HasCustomeResponseFileBehavior = true; - } - } - - /// Constructs a new based on this instance - /// new from this instance - public ParserConfiguration ToParserConfiguration( ) - { - ParserConfiguration retVal = new() - { - EnablePosixBundling = EnablePosixBundling, - }; - - // Don't set the behavior unless explicitly specified as the default - // is not publicly accessible (and therefore cannot be expressed as - // a value for this type). - if(HasCustomeResponseFileBehavior) - { - retVal.ResponseFileTokenReplacer = ResponseFileTokenReplacer; - } - - return retVal; - } - - private bool HasCustomeResponseFileBehavior = false; - - /// Implicitly constructs a new based on an instance of - /// The settings to build the configuration from - public static implicit operator ParserConfiguration( CmdLineSettings self ) - { - return self.ToParserConfiguration(); - } - - /// Gets a settings with defaults for options and directives removed - /// - /// The "default" support for help and version is rather inflexible and an "all or nothing" - /// approach. (with late bug fix hacks [https://github.com/dotnet/command-line-api/issues/2659] - /// to resolve issues). Thus, this simply removes them and leaves it to the calling app to - /// specify them explicitly as a custom option. Then validation is customized to handle - /// behavior as desired by the app. - /// - public static CmdLineSettings NoDefaults { get; } - = new() - { - DefaultDirectives = DefaultDirective.None, - DefaultOptions = DefaultOption.None, - ResponseFileTokenReplacer = null, - EnablePosixBundling = false, - }; - } -} diff --git a/src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs b/src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs deleted file mode 100644 index ec2c10061..000000000 --- a/src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Implementation of that uses colorized console output - public sealed class ColoredConsoleReporter - : ConsoleReporter - { - /// Initializes a new instance of the class - /// Level of messages to enable for this reporter [Default: - /// Provides the color mapping for the message levels this reporter will use (see remarks) - /// - /// (or a default if ) is set as the property. - /// - /// The default colors, if not provided via , are: - /// - /// LevelDescription - /// - /// - /// - /// - /// - /// - /// Any level not in is reported using . - /// - /// - [SetsRequiredMembers] - public ColoredConsoleReporter( MsgLevel level = MsgLevel.Information, ImmutableDictionary? colorMapping = null) - : base(level) - { - ColorMap = colorMapping ?? ImmutableDictionary.Empty; - } - - /// Gets the to colorMapping used for coloring - public ImmutableDictionary ColorMap - { - get => field.IsEmpty ? DefaultColorMap : field; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// Gets the default color map for this type - public static ImmutableDictionary DefaultColorMap { get; } - = new DictionaryBuilder - { - [MsgLevel.Verbose] = Color.LtBlue, - [MsgLevel.Information] = Color.Default, - [MsgLevel.Warning] = Color.LtYellow, - [MsgLevel.Error] = Color.LtRed, - }.ToImmutable(); - - /// - /// This implementation will apply ANSI color code sequences to each message based on . - /// - /// - protected override void ReportMessage( MsgLevel level, string msg ) - { - if(!ColorMap.TryGetValue(level, out AnsiCode? color)) - { - color = Color.Default; - } - - // use base to write to the correct stream - base.ReportMessage( level, $"{color}{msg}{Reset.All}" ); - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/ConsoleReporter.cs b/src/Ubiquity.NET.CommandLine/ConsoleReporter.cs deleted file mode 100644 index bfea22793..000000000 --- a/src/Ubiquity.NET.CommandLine/ConsoleReporter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Implementation of that reports messages to a - /// - /// Messages with a of are reported to the console's - /// writer, while other levels, if enabled, are reported to the console's writer. - /// - public class ConsoleReporter - : TextWriterReporter - { - /// Initializes a new instance of the class. - /// Level of messages to enable for this reporter [Default: - /// - /// Any message reported with a level that is greater than or equal to - /// is enabled, and thus reported. - /// - /// The stream is used for . Any other message level - /// goes to . - /// - /// - [SetsRequiredMembers] - public ConsoleReporter( MsgLevel level = MsgLevel.Information) - : base(level, Console.Error, Console.Out, Console.Out, Console.Out) - { - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs b/src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs deleted file mode 100644 index bf3c6eb51..000000000 --- a/src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Tool Message category - public enum MsgLevel - { - /// All channels off - None = 0, - - /// Verbose messages (or higher) are enabled - Verbose = 100, - - /// Informational messages (or higher) are enabled. - Information = 200, - - /// Warning messages (or higher) are enabled. [This is the default value] - Warning = 300, // Default level is warning & error only - - /// Error messages (or higher) are enabled. - Error = 400, - } - - /// Diagnostic message for reporting diagnostics as part of end-user facing experience (UX) - /// - /// must NOT be localized. It is required to universally identify a - /// particular message. should be localized if the source tool supports - /// localization. - /// Diagnostic codes () are of the form <prefix><number> - /// (example: FOO01234). This is a unique identifier for the message that allows a user to reference - /// it for support or other diagnostic analysis. The <prefix> portion of the code indicates - /// the application source of the message. - /// - public readonly record struct DiagnosticMessage - : IFormattable - { - /// Gets the origin of the message - public Uri? Origin { get; init; } - - /// Gets the location in source for the origin of this message - public SourceRange? Location { get; init; } - - /// Gets the subcategory of the message - public string? Subcategory { get; init; } - - /// Gets the Level/Category of the message - public MsgLevel Level { get; init; } - - /// Gets the code for the message (No spaces) - public string? Code - { - get; - init - { - if(value is not null && value.Any( ( c ) => char.IsWhiteSpace( c ) )) - { - throw new ArgumentException( "If provided, code must not contain whitespace", nameof( value ) ); - } - - field = value; - } - } - - /// Gets the text of the message - public string Text - { - get; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// Formats this instance using the general runtime specific format - /// Formatted string for the message - public override string ToString( ) - { - // use runtime default formatting - return ToString("G", CultureInfo.CurrentCulture); - } - - /// - /// - /// Accepted format strings are: - /// "M" for MSBuild format (used for Windows/MSBUILD build tools). - /// "G" for runtime specific (For Windows, this is the same as the MSBuild format['M']) - /// [Format strings for other runtimes TBD (L:Linux, A:Apple ... ????)] - /// - public string ToString( string? format, IFormatProvider? formatProvider ) - { - formatProvider ??= CultureInfo.CurrentCulture; - return format switch - { - "M" => FormatMsBuild( formatProvider ), - "G" => FormatRuntime( formatProvider ), - _ => throw new FormatException($"{format} is not a valid format specifier for {nameof(DiagnosticMessage)}") - }; - } - - private string FormatMsBuild(IFormatProvider formatProvider) - { - if(Origin is null || string.IsNullOrWhiteSpace(Origin.AbsoluteUri)) - { - return Text; - } - - string locString = string.Empty; - if(Location is not null) - { - locString = Location.Value.ToString( "M", formatProvider ); - } - - // account for optional values with leading space. - string subCat = Subcategory is not null ? $" {Subcategory}" : string.Empty; - string code = Code is not null ? $" {Code}" : string.Empty; - string origin = Origin.IsFile ? Origin.LocalPath : Origin.ToString(); - return $"{origin}{locString} :{subCat} {Level}{code} : {Text}"; - } - - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Place holder for future work" )] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1108:BlockStatementsMustNotContainEmbeddedComments", Justification = "Reviewed.")] - private string FormatRuntime(IFormatProvider formatProvider) - { - if(OperatingSystem.IsWindows()) - { - return FormatMsBuild(formatProvider); - } - else // TODO: Adjust this to format based on styles of additional runtimes - { - // for now - always use MSBUILD format - return FormatMsBuild(formatProvider); - } - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs b/src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs deleted file mode 100644 index 263048320..000000000 --- a/src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Reporter that collects diagnostic information without reporting it in any UI/UX - public class DiagnosticMessageCollection - : IDiagnosticReporter - , IReadOnlyCollection - { - /// Initializes a new instance of the class. - /// Minimal reporting level for this collector [Default is to collect all messaged - public DiagnosticMessageCollection( MsgLevel level = MsgLevel.Error ) - { - Level = level; - } - - /// - public MsgLevel Level { get; } - - /// - public Encoding Encoding => Encoding.Unicode; - - /// - public int Count => Messages.Count; - - /// - public void Report( DiagnosticMessage diagnostic ) - { - if(this.IsEnabled( Level )) - { - Messages = Messages.Add( diagnostic ); - } - } - - /// - public IEnumerator GetEnumerator( ) - { - return ((IEnumerable)Messages).GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator( ) - { - return ((IEnumerable)Messages).GetEnumerator(); - } - - /// Gets the current list of messages received by this collector - public ImmutableList Messages { get; private set; } = []; - } -} diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs b/src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs deleted file mode 100644 index 3ae5eb52c..000000000 --- a/src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs +++ /dev/null @@ -1,725 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Extensions to for specific message levels - public static class DiagnosticReporterExtensions - { - /// Gets a value indicating whether the specified level (and higher) messages are enabled - /// Reporter to test - /// Level to test if enabled - /// if the level or higher messages are enabled or if not - public static bool IsEnabled( this IDiagnosticReporter self, MsgLevel level ) - { - return self.Level <= level; - } - - #region Report - - /// Reports a message - /// Reporter to use in reporting the message - /// Message level - /// Message text - public static void Report( this IDiagnosticReporter self, MsgLevel level, string msg ) - { - ArgumentNullException.ThrowIfNull( self ); - - var diagnostic = new DiagnosticMessage() - { - Origin = null, - Location = default, - Subcategory = default, - Level = level, - Code = default, - Text = msg - }; - - self.Report( diagnostic ); - } - - /// Reports a message using an interpolated string - /// Reporter to use in reporting the message - /// Message level - /// Origin for the source of this diagnostic - /// Location in source relating to the diagnostic - /// Interpolated string for the message (processed via ) - /// - /// The will optimize (short circuit) the interpolation based on the value of - /// . If a level is not enabled, then the handler will short circuit the rest of the - /// interpolation. Thus, any methods with side effects may not be called and callers should not depend on them happening - /// in all cases. - /// - public static void Report( - this IDiagnosticReporter self, - MsgLevel level, - Uri? origin, - SourceRange location, - [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Origin = origin, - Location = location, - Subcategory = default, - Level = level, - Code = default, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// Reports a message using an interpolated string - /// Reporter to use in reporting the message - /// Message level - /// Location in source relating to the diagnostic - /// Interpolated string for the message (processed via ) - /// - /// The will optimize (short circuit) the interpolation based on the value of - /// . If a level is not enabled, then the handler will short circuit the rest of the - /// interpolation. Thus, any methods with side effects may not be called and callers should not depend on them happening - /// in all cases. - /// - public static void Report( - this IDiagnosticReporter self, - MsgLevel level, - SourceRange location, - [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler - ) - { - Report(self, level, null, location, handler); - } - - /// Reports a message using an interpolated string - /// Reporter to use in reporting the message - /// Message level - /// Interpolated string for the message (processed via ) - /// - /// The will optimize (short circuit) the interpolation based on the value of - /// . If a level is not enabled, then the handler will short circuit the rest of the - /// interpolation. Thus, any methods with side effects may not be called and callers should not depend on them happening - /// in all cases. - /// - public static void Report( - this IDiagnosticReporter self, - MsgLevel level, - [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Origin = null, - Location = default, - Subcategory = default, - Level = level, - Code = default, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// Reports a message using classic string formatting - /// Reporter to use in reporting the message - /// Message level - /// Origin for the source of this diagnostic - /// Location in source relating to the diagnostic - /// Format string for the message - /// Arguments for the message - public static void Report( - this IDiagnosticReporter self, - MsgLevel level, - Uri? origin, - SourceRange location, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - ArgumentNullException.ThrowIfNull( self ); - - var diagnostic = new DiagnosticMessage() - { - Origin = origin, - Location = location, - Subcategory = default, - Level = level, - Code = default, - Text = args.Length == 0 ? fmt : string.Format( CultureInfo.CurrentCulture, fmt, args ) - }; - - self.Report( diagnostic ); - } - - /// Reports a message using classic string formatting - /// Reporter to use in reporting the message - /// Message level - /// Location in source relating to the diagnostic - /// Format string for the message - /// Arguments for the message - public static void Report( - this IDiagnosticReporter self, - MsgLevel level, - SourceRange location, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Report(self, level, null, location, fmt, args); - } - - /// Reports multiple diagnostics to the reporter - /// Reporter to report the diagnostics to - /// DIagnostics to report. This is a 'params' value so it is variadic in languages that support such a thing - public static void Report( this IDiagnosticReporter self, params IEnumerable diagnostics ) - { - foreach(var dm in diagnostics) - { - self.Report( dm ); - } - } - #endregion - - #region MsgLevel.Error - - // maintainer's note: Doc comments are inherited from the "Error" implementation (except the summary) - // and therefore the verbiage of the full comments should remain neutral. - - /// Reports a level message to - /// Reporter to report message to - /// Identifier code for this message - /// Location in the origin that this message refers to - /// Origin of the diagnostic (Usually the origin is a File, but may be anything or nothing) - /// Subcategory for this message - /// Format provider to use when formatting the string - /// Format string for the message - /// Arguments for the message - /// - /// The reporter () may filter out any messages reported for this level. The - /// is NOT applied to unless the level for the message is - /// enabled. This helps reduce the overhead of producing the final formatted string if it is ignored anyway. - /// - public static void Error( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(self.IsEnabled(MsgLevel.Error)) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Error, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = string.Format(formatProvider, fmt, args), - }; - - self.Report( diagnostic ); - } - } - - /// Reports a a level message to - /// Reporter to report message to - /// Identifier code for this message - /// Location in the origin that this message refers to - /// Origin of the diagnostic (Usually the origin is a File, but may be anything or nothing) - /// Subcategory for this message - /// Interpolated string for the message (processed via ) - /// - /// The reporter () may filter out any messages reported for this level. The - /// is NOT applied to format the final message unless the level for the message is - /// enabled. This helps reduce the overhead of producing the final formatted string if it is ignored anyway. - /// - public static void Error( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - [InterpolatedStringHandlerArgument( "self" )] ErrorReportingInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Error, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// Reports a level message to - /// Reporter to report message to - /// Exception to report - /// Identifier code for this message - /// Location in the origin that this message refers to - /// Origin of the diagnostic (Usually the origin is a File, but may be anything or nothing) - /// Subcategory for this message - /// - /// The reporter () may filter out any messages reported for level. - /// - public static void Error( - this IDiagnosticReporter self, - Exception ex, - string? code = null, - SourceRange location = default, - Uri? origin = null, - string? subCategory = null - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(self.IsEnabled(MsgLevel.Error)) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Error, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = ex.Message, - }; - - self.Report( diagnostic ); - } - } - - /// - public static void Error( - this IDiagnosticReporter self, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Error(self, code: null, location: default, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Error( - this IDiagnosticReporter self, - SourceRange location, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Error(self, code: null, location, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Error( - this IDiagnosticReporter self, - [InterpolatedStringHandlerArgument( "self" )] ErrorReportingInterpolatedStringHandler handler - ) - { - Error(self, code: null, location: default, origin: null, subCategory: null, handler); - } - - /// - public static void Error( - this IDiagnosticReporter self, - SourceRange location, - [InterpolatedStringHandlerArgument( "self" )] ErrorReportingInterpolatedStringHandler handler - ) - { - Error(self, code: null, location, origin: null, subCategory: null, handler); - } - - // doc comments don't inherit param correctly, see: https://github.com/dotnet/roslyn/issues/67326#issuecomment-3452843800 - - /// - /// Reporter to report message to - /// Message to report - /// Location in the origin that this message refers to - /// - public static void Error(this IDiagnosticReporter self, string msg, SourceRange location = default) - { - Error(self, code: null, location, origin: null, subCategory: null, formatProvider: null, msg /*, args...*/); - } - #endregion - - #region MsgLevel.Warning - - /// Reports a level message to - /// - public static void Warning( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(self.IsEnabled(MsgLevel.Warning)) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Warning, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = string.Format(formatProvider, fmt, args), - }; - - self.Report( diagnostic ); - } - } - - /// Reports a level message to - /// - public static void Warning( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - [InterpolatedStringHandlerArgument( "self" )] WarningReportingInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Warning, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// - public static void Warning( - this IDiagnosticReporter self, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Warning(self, code: null, location: default, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Warning( - this IDiagnosticReporter self, - SourceRange location, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Warning(self, code: null, location, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Warning( - this IDiagnosticReporter self, - [InterpolatedStringHandlerArgument( "self" )] WarningReportingInterpolatedStringHandler handler - ) - { - Warning(self, code: null, location: default, origin: null, subCategory: null, handler); - } - - /// - public static void Warning( - this IDiagnosticReporter self, - SourceRange location, - [InterpolatedStringHandlerArgument( "self" )] WarningReportingInterpolatedStringHandler handler - ) - { - Warning(self, code: null, location, origin: null, subCategory: null, handler); - } - - // doc comments don't inherit param correctly, see: https://github.com/dotnet/roslyn/issues/67326#issuecomment-3452843800 - - /// - /// Reporter to report message to - /// Message to report - /// Location in the origin that this message refers to - /// - public static void Warning( this IDiagnosticReporter self, string msg, SourceRange location = default ) - { - Warning( self, code: null, location, origin: null, subCategory: null, formatProvider: null, msg /*, args...*/); - } - #endregion - - #region MsgLevel.Information - - /// Reports a level message to - /// - public static void Information( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(self.IsEnabled(MsgLevel.Information)) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Information, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = string.Format(formatProvider, fmt, args), - }; - - self.Report( diagnostic ); - } - } - - /// Reports a level message to - /// - public static void Information( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - [InterpolatedStringHandlerArgument( "self" )] InformationReportingInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Information, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// - public static void Information( - this IDiagnosticReporter self, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Information(self, code: null, location: default, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Information( - this IDiagnosticReporter self, - SourceRange location, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Information(self, code: null, location, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Information( - this IDiagnosticReporter self, - [InterpolatedStringHandlerArgument( "self" )] InformationReportingInterpolatedStringHandler handler - ) - { - Information(self, code: null, location: default, origin: null, subCategory: null, handler); - } - - /// - public static void Information( - this IDiagnosticReporter self, - SourceRange location, - [InterpolatedStringHandlerArgument( "self" )] InformationReportingInterpolatedStringHandler handler - ) - { - Information(self, code: null, location, origin: null, subCategory: null, handler); - } - - // doc comments don't inherit param correctly, see: https://github.com/dotnet/roslyn/issues/67326#issuecomment-3452843800 - - /// - /// Reporter to report message to - /// Message to report - /// Location in the origin that this message refers to - /// - public static void Information( this IDiagnosticReporter self, string msg, SourceRange location = default ) - { - Information( self, code: null, location, origin: null, subCategory: null, formatProvider: null, msg /*, args...*/); - } - - #endregion - - #region MsgLevel.Verbose - - /// Reports a level message to - /// - public static void Verbose( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(self.IsEnabled(MsgLevel.Verbose)) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Verbose, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = string.Format(formatProvider, fmt, args), - }; - - self.Report( diagnostic ); - } - } - - /// Reports a level message to - /// - public static void Verbose( - this IDiagnosticReporter self, - string? code, - SourceRange location, - Uri? origin, - string? subCategory, - [InterpolatedStringHandlerArgument( "self" )] VerboseReportingInterpolatedStringHandler handler - ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(handler.IsEnabled) - { - var diagnostic = new DiagnosticMessage() - { - Code = code, - Level = MsgLevel.Verbose, - Location = location, - Origin = origin, - Subcategory = subCategory, - Text = handler.GetFormattedText() - }; - - self.Report( diagnostic ); - } - } - - /// - public static void Verbose( - this IDiagnosticReporter self, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Verbose(self, code: null, location: default, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Verbose( - this IDiagnosticReporter self, - SourceRange location, - IFormatProvider? formatProvider, - [StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt, - params object[] args - ) - { - Verbose(self, code: null, location, origin: null, subCategory: null, formatProvider, fmt, args); - } - - /// - public static void Verbose( - this IDiagnosticReporter self, - [InterpolatedStringHandlerArgument( "self" )] VerboseReportingInterpolatedStringHandler handler - ) - { - Verbose(self, code: null, location: default, origin: null, subCategory: null, handler); - } - - /// - public static void Verbose( - this IDiagnosticReporter self, - SourceRange location, - [InterpolatedStringHandlerArgument( "self" )] VerboseReportingInterpolatedStringHandler handler - ) - { - Verbose(self, code: null, location, origin: null, subCategory: null, handler); - } - - // doc comments don't inherit param correctly, see: https://github.com/dotnet/roslyn/issues/67326#issuecomment-3452843800 - - /// - /// Reporter to report message to - /// Message to report - /// Location in the origin that this message refers to - /// - public static void Verbose( this IDiagnosticReporter self, string msg, SourceRange location = default ) - { - Verbose( self, code: null, location, origin: null, subCategory: null, formatProvider: null, msg /*, args...*/); - } - #endregion - } -} diff --git a/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs b/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs deleted file mode 100644 index 6e1097b80..000000000 --- a/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.Collections; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.CommandLine; -global using System.CommandLine.Completions; -global using System.CommandLine.Help; -global using System.CommandLine.Invocation; -global using System.CommandLine.Parsing; -global using System.ComponentModel; -global using System.Diagnostics.CodeAnalysis; -global using System.Globalization; -global using System.IO; -global using System.Linq; -global using System.Runtime.CompilerServices; -global using System.Text; - -global using AnsiCodes; - -global using Ubiquity.NET.CommandLine.InterpolatedStringHandlers; -global using Ubiquity.NET.Extensions; diff --git a/src/Ubiquity.NET.CommandLine/ICommandLineOptions.cs b/src/Ubiquity.NET.CommandLine/ICommandLineOptions.cs deleted file mode 100644 index 97b290c46..000000000 --- a/src/Ubiquity.NET.CommandLine/ICommandLineOptions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Interface for a root command in command line parsing - /// Type of the command (CRTP) - /// - /// This only contains a static interface and thus requires the static abstract feature of the runtime. - /// (.NET 7 or later) It is used to constrain methods to those that have static methods that are specifically - /// designed for command line parsing. - /// - public interface ICommandLineOptions - where T : ICommandLineOptions - { - /// Binds the results of the parse to a new instance of - /// Results of the parse to bind - /// Newly constructed instance of with properties bound from - /// - /// The implementation of this method allows pure source code AOT (Zero-Runtime-Reflection) binding. - /// Normally this is generated by a source generator to leverage compile time reflection. But for simple - /// applications hand generation is often used. (Until a generator is implemented that's the only - /// option actually). - /// - /// Thrown when required argument or option was not parsed or has no default value configured. - public static abstract T Bind( ParseResult parseResult ); - - /// Builds a new for parsing the command line - /// Settings to use for parsing - /// New instance for - /// - /// - /// The settings determine the default Options and Directives, etc... to add or remove from the command. - /// contains hard coded defaults without any construction - /// configuration. takes care of adding or removing things - /// based on the to make the defaults app controlled. - /// - /// Normally, this just creates an instance of and initializes - /// it with all of the s for a given command line - /// The implementation of this method allows pure source code AOT (Zero-Runtime-Reflection) description. - /// Normally this is generated by a source generator to leverage compile time reflection. But for simple - /// applications hand generation is often used. (Until a generator is implemented that's the only - /// option actually). - /// - public static abstract AppControlledDefaultsRootCommand BuildRootCommand( CmdLineSettings settings ); - } -} diff --git a/src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs b/src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs deleted file mode 100644 index c73dc56b7..000000000 --- a/src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Interface for UX message reporting - /// - /// Unlike logging, which is meant for developers/administrator diagnostics, - /// this interface is focused solely on text based (typically command line) - /// end user facing experiences. The idea is that UX is distinct - /// from any logging and should not be mixed or confused as they have very different - /// uses/intents. - /// - public interface IDiagnosticReporter - { - /// Gets the current reporting level for this reporter - MsgLevel Level { get; } - - /// Gets the encoding used for this reporter - Encoding Encoding { get; } - - /// Report a message as defined by the implementation - /// Message to report - void Report( DiagnosticMessage diagnostic ); - } -} diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs deleted file mode 100644 index 1789550b4..000000000 --- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.InterpolatedStringHandlers -{ - /// Interpolated string handler for an - /// - /// This handler will use the state of the to filter messages - /// as interpolated strings. If the channel for the message is not enabled, then the handler filters - /// the entire message and can skip even constructing the parameterized elements (unless it is the first - /// one [Limit of how Handlers work in .NET]). The limitation of the first is further restricted to the - /// first "thing" interpolated including a string literal. Thus, the parameterized elements are not generated - /// if the channel isn't enabled unless the element is the first thing in the interpolated string. In that - /// case only, the first entry is evaluated. - /// - /// Apps should NOT depend on the subtleties of interpolated parameter evaluation to guarantee invocation - /// (or not) of side effects. This is a "best-effort" optimization. In particular, apps should assume that - /// a parameterized value may or may not be executed (i.e., non-deterministic). Therefore, it cannot assume - /// (one way or another) that the side-effects of evaluation have, or have not, occurred. - /// - /// Despite lots of samples (for preview variants) that use a 'ref struct', this is NOT - /// a by ref like type. This is to allow use interpolating async parameters. - /// - [InterpolatedStringHandler] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not relevant for an interpolated string handler" )] - public readonly struct DiagnosticReporterInterpolatedStringHandler - { - /// Initializes a new instance of the struct. - /// Length of the literal - /// Sadly .NET doesn't document this, or much else in relation to interpolated string handlers - /// "this" reference for the reporter. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// Reporting level parameter to report for. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// Format provider - /// - /// The may not have the level enabled. This is used to ONLY process the interpolated string - /// if the reporter has the level enabled. Thus, it may produce NO message at all if not enabled. - /// - public DiagnosticReporterInterpolatedStringHandler( - int literalLength, - int formattedCount, - IDiagnosticReporter reporter, - MsgLevel level, - IFormatProvider? formatProvider = null - ) - { - FormatProvider = formatProvider ?? CultureInfo.CurrentCulture; - Builder = reporter.IsEnabled( level ) ? new( literalLength ) : null; - } - - /// Gets a value indicating whether this handler is enabled - public bool IsEnabled => Builder is not null; - - /// Gets the format provider used by this interpolated string handler - public IFormatProvider FormatProvider { get; } - - /// Appends a literal value to the results of interpolating a string - /// literal value to append - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// reporting level is enabled for a given reporter. - /// - public bool AppendLiteral( string s ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( s ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// reporting level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString() ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// format string for formatting the value - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// reporting level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t, string format ) - where T : IFormattable - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString( format, FormatProvider ) ); - return true; - } - - /// Gets the full results of interpolation - /// Results of the interpolation (thus far) - public string GetFormattedText( ) - { - return Builder?.ToString() ?? string.Empty; - } - - private readonly StringBuilder? Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs deleted file mode 100644 index 52ba40aa8..000000000 --- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.InterpolatedStringHandlers -{ - /// Interpolated string handler for an using a fixed - /// - /// This handler will use the state of the to filter messages - /// as interpolated strings. If the channel for the message is not enabled, then the handler filters - /// the entire message and can skip even constructing the parameterized elements (unless it is the first - /// one [Limit of how Handlers work in .NET]). The limitation of the first is further restricted to the - /// first "thing" interpolated including a string literal. Thus, the parameterized elements are not generated - /// if the channel isn't enabled unless the element is the first thing in the interpolated string. In that - /// case only the first entry is evaluated. - /// - /// Apps should NOT depend on the subtleties of interpolated parameter evaluation to guarantee invocation - /// (or not) of side effects. This is a "best-effort" optimization. In particular, apps should assume that - /// a parameterized value may or may not be executed (i.e., non-deterministic). Therefore, it cannot assume - /// (one way or another) that the side-effects of evaluation have, or have not, occurred. - /// - /// Despite lots of samples (for preview variants) that use a 'ref struct', this is NOT - /// a by ref like type. This is to allow use interpolating async parameters. - /// - [InterpolatedStringHandler] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not relevant for an interpolated string handler" )] - public readonly struct ErrorReportingInterpolatedStringHandler - { - /// Initializes a new instance of the struct. - /// Length of the literal - /// Sadly .NET doesn't document this, or much else in relation to interpolated string handlers - /// "this" reference for the reporter. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// - /// The may not have the level enabled. This is used to ONLY process the interpolated string - /// if the reporter has the level enabled. Thus, it may produce NO message at all if not enabled. - /// - public ErrorReportingInterpolatedStringHandler( int literalLength, int formattedCount, IDiagnosticReporter reporter ) - { - Builder = reporter.IsEnabled( MsgLevel.Error ) ? new( literalLength ) : null; - } - - /// Gets a value indicating whether this handler is enabled - public bool IsEnabled => Builder is not null; - - /// Appends a literal value to the results of interpolating a string - /// literal value to append - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public bool AppendLiteral( string s ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( s ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString() ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// format string for formatting the value - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t, string format ) - where T : IFormattable - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString( format, null ) ); - return true; - } - - /// Gets the full results of interpolation - /// Results of the interpolation (thus far) - public string GetFormattedText( ) - { - return Builder?.ToString() ?? string.Empty; - } - - private readonly StringBuilder? Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs deleted file mode 100644 index 23ad5009c..000000000 --- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.InterpolatedStringHandlers -{ - /// Interpolated string handler for an using a fixed - /// - /// This handler will use the state of the to filter messages - /// as interpolated strings. If the channel for the message is not enabled, then the handler filters - /// the entire message and can skip even constructing the parameterized elements (unless it is the first - /// one [Limit of how Handlers work in .NET]). The limitation of the first is further restricted to the - /// first "thing" interpolated including a string literal. Thus, the parameterized elements are not generated - /// if the channel isn't enabled unless the element is the first thing in the interpolated string. In that - /// case only the first entry is evaluated. - /// - /// Apps should NOT depend on the subtleties of interpolated parameter evaluation to guarantee invocation - /// (or not) of side effects. This is a "best-effort" optimization. In particular, apps should assume that - /// a parameterized value may or may not be executed (i.e., non-deterministic). Therefore, it cannot assume - /// (one way or another) that the side-effects of evaluation have, or have not, occurred. - /// - /// Despite lots of samples (for preview variants) that use a 'ref struct', this is NOT - /// a by ref like type. This is to allow use interpolating async parameters. - /// - [InterpolatedStringHandler] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not relevant for an interpolated string handler" )] - public readonly struct InformationReportingInterpolatedStringHandler - { - /// Initializes a new instance of the struct. - /// Length of the literal - /// Sadly .NET doesn't document this, or much else in relation to interpolated string handlers - /// "this" reference for the reporter. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// - /// The may not have the level enabled. This is used to ONLY process the interpolated string - /// if the reporter has the level enabled. Thus, it may produce NO message at all if not enabled. - /// - public InformationReportingInterpolatedStringHandler( int literalLength, int formattedCount, IDiagnosticReporter reporter ) - { - Builder = reporter.IsEnabled( MsgLevel.Information ) ? new( literalLength ) : null; - } - - /// Gets a value indicating whether this handler is enabled - public bool IsEnabled => Builder is not null; - - /// Appends a literal value to the results of interpolating a string - /// literal value to append - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public bool AppendLiteral( string s ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( s ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString() ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// format string for formatting the value - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t, string format ) - where T : IFormattable - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString( format, null ) ); - return true; - } - - /// Gets the full results of interpolation - /// Results of the interpolation (thus far) - public string GetFormattedText( ) - { - return Builder?.ToString() ?? string.Empty; - } - - private readonly StringBuilder? Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs deleted file mode 100644 index 0e5e06900..000000000 --- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.InterpolatedStringHandlers -{ - /// Interpolated string handler for an using a fixed - /// - /// This handler will use the state of the to filter messages - /// as interpolated strings. If the channel for the message is not enabled, then the handler filters - /// the entire message and can skip even constructing the parameterized elements (unless it is the first - /// one [Limit of how Handlers work in .NET]). The limitation of the first is further restricted to the - /// first "thing" interpolated including a string literal. Thus, the parameterized elements are not generated - /// if the channel isn't enabled unless the element is the first thing in the interpolated string. In that - /// case only the first entry is evaluated. - /// - /// Apps should NOT depend on the subtleties of interpolated parameter evaluation to guarantee invocation - /// (or not) of side effects. This is a "best-effort" optimization. In particular, apps should assume that - /// a parameterized value may or may not be executed (i.e., non-deterministic). Therefore, it cannot assume - /// (one way or another) that the side-effects of evaluation have, or have not, occurred. - /// - /// Despite lots of samples (for preview variants) that use a 'ref struct', this is NOT - /// a by ref like type. This is to allow use interpolating async parameters. - /// - [InterpolatedStringHandler] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not relevant for an interpolated string handler" )] - public readonly struct VerboseReportingInterpolatedStringHandler - { - /// Initializes a new instance of the struct. - /// Length of the literal - /// Sadly .NET doesn't document this, or much else in relation to interpolated string handlers - /// "this" reference for the reporter. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// - /// The may not have the level enabled. This is used to ONLY process the interpolated string - /// if the reporter has the level enabled. Thus, it may produce NO message at all if not enabled. - /// - public VerboseReportingInterpolatedStringHandler( int literalLength, int formattedCount, IDiagnosticReporter reporter ) - { - Builder = reporter.IsEnabled( MsgLevel.Verbose ) ? new( literalLength ) : null; - } - - /// Gets a value indicating whether this handler is enabled - public bool IsEnabled => Builder is not null; - - /// Appends a literal value to the results of interpolating a string - /// literal value to append - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public bool AppendLiteral( string s ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( s ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString() ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// format string for formatting the value - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t, string format ) - where T : IFormattable - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString( format, null ) ); - return true; - } - - /// Gets the full results of interpolation - /// Results of the interpolation (thus far) - public string GetFormattedText( ) - { - return Builder?.ToString() ?? string.Empty; - } - - private readonly StringBuilder? Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs deleted file mode 100644 index 6bfc64371..000000000 --- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine.InterpolatedStringHandlers -{ - /// Interpolated string handler for an using a fixed - /// - /// This handler will use the state of the to filter messages - /// as interpolated strings. If the channel for the message is not enabled, then the handler filters - /// the entire message and can skip even constructing the parameterized elements (unless it is the first - /// one [Limit of how Handlers work in .NET]). The limitation of the first is further restricted to the - /// first "thing" interpolated including a string literal. Thus, the parameterized elements are not generated - /// if the channel isn't enabled unless the element is the first thing in the interpolated string. In that - /// case only the first entry is evaluated. - /// - /// Apps should NOT depend on the subtleties of interpolated parameter evaluation to guarantee invocation - /// (or not) of side effects. This is a "best-effort" optimization. In particular, apps should assume that - /// a parameterized value may or may not be executed (i.e., non-deterministic). Therefore, it cannot assume - /// (one way or another) that the side-effects of evaluation have, or have not, occurred. - /// - /// Despite lots of samples (for preview variants) that use a 'ref struct', this is NOT - /// a by ref like type. This is to allow use interpolating async parameters. - /// - [InterpolatedStringHandler] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Not relevant for an interpolated string handler" )] - public readonly struct WarningReportingInterpolatedStringHandler - { - /// Initializes a new instance of the struct. - /// Length of the literal - /// Sadly .NET doesn't document this, or much else in relation to interpolated string handlers - /// "this" reference for the reporter. (Mapped via InterpolatedStringHandlerArgument applied to method) - /// - /// The may not have the level enabled. This is used to ONLY process the interpolated string - /// if the reporter has the level enabled. Thus, it may produce NO message at all if not enabled. - /// - public WarningReportingInterpolatedStringHandler( int literalLength, int formattedCount, IDiagnosticReporter reporter ) - { - Builder = reporter.IsEnabled( MsgLevel.Warning ) ? new( literalLength ) : null; - } - - /// Gets a value indicating whether this handler is enabled - public bool IsEnabled => Builder is not null; - - /// Appends a literal value to the results of interpolating a string - /// literal value to append - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public bool AppendLiteral( string s ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( s ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t ) - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString() ); - return true; - } - - /// Appends an interpolated value to the result of interpolation - /// Type of the interpolated value - /// Value to format - /// format string for formatting the value - /// if the interpolation should continue with other conversions or if not. - /// - /// The return is used to short circuit all other calls to interpolation, thus, this implementation returns if the - /// level is enabled for a given reporter. - /// - public readonly bool AppendFormatted( T t, string format ) - where T : IFormattable - { - if(!IsEnabled) - { - return false; - } - - Builder?.Append( t?.ToString( format, null ) ); - return true; - } - - /// Gets the full results of interpolation - /// Results of the interpolation (thus far) - public string GetFormattedText( ) - { - return Builder?.ToString() ?? string.Empty; - } - - private readonly StringBuilder? Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/PackageReadMe.md b/src/Ubiquity.NET.CommandLine/PackageReadMe.md deleted file mode 100644 index 6f41b0910..000000000 --- a/src/Ubiquity.NET.CommandLine/PackageReadMe.md +++ /dev/null @@ -1,45 +0,0 @@ -# Ubiquity.NET.CommandLine -Common Text based (console) UX support. This provides a number of support classes for -Text based UI/UXm including command line parsing extensions. This is generally only relevant -for console based apps. - -Example Command line parsing: -``` C# -var reporter = new ColoredConsoleReporter(MsgLevel.Information); -if(!ArgsParsing.TryParse( args, reporter, out Options? options, out int exitCode )) -{ - return exitCode; -} - -// ... - -// Options is a class that has properties for all parsed commands, arguments and options -// Allowing for validation of them all in context (including each other) -// App can then dispatch behavior based on the commands/options etc... as needed. -// NO ASSUMPTION IS MADE ABOUT THE USE OF COMMANDS NOR THE BEHAVIOR OF THEM. The app -// is entirely in control of how they are used. -``` - -## Supported Functionality -`IDiagnosticReporter` interface is at the core of the UX. It is similar in many ways to many -of the logging interfaces available. The primary distinction is with the ***intention*** of -use. `IDiagnosticReporter` specifically assumes the use for UI/UX rather than a -debugging/diagnostic log. These have VERY distinct use cases and purposes and generally show -very different information. (Not to mention the overlly complex requirements of -the anti-pattern DI container assumed in `Microsoft.Extensions.Logging`) - -### Messages -All messages for the UX use a simple immutable structure to store the details of a message -represented as `DiagnosticMessage`. - -### Pre-Built Reporters -There are a few pre-built implementation of the `IDiagnosticReporter` interface. -* `TextWriterReporter` - * Base class for writing UX to a `TextWriter` -* `ConsoleReporter` - * Reporter that reports errors to `Console.Error` and all other nessages to - `Console.Out` -* `ColoredConsoleReporter` - * `ConsoleReporter` that colorizes output using ANSI color codes - * Colors are customizable, but contains a common default - diff --git a/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs b/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs deleted file mode 100644 index bcac7428c..000000000 --- a/src/Ubiquity.NET.CommandLine/ParseResultExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - // This does NOT use the new C# 14 extension syntax due to several reasons - // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end] - // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx]) - // 3) Many tools (like docfx don't support the new syntax yet) - // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]). - // - // Bottom line it's a good idea with an incomplete implementation lacking support - // in the overall ecosystem. Don't use it unless you absolutely have to until all - // of that is sorted out. - - /// Utility extension methods for command line parsing - public static class ParseResultExtensions - { - /// Gets a value indicating whether has any errors - /// Result to test for errors - /// value indicating whether has any errors - public static bool HasErrors(this ParseResult self) => self.Errors.Count > 0; - - /// Gets the optional from the result of a parse - /// Result of parse to get the option from - /// if found or - public static HelpOption? GetHelpOption(this ParseResult self) - { - var helpOptions = from r in self.CommandResult.RecurseWhileNotNull(r => r.Parent as CommandResult) - from o in r.Command.Options.OfType() - select o; - - return helpOptions.FirstOrDefault(); - } - - /// Gets the optional from the result of a parse - /// Result of parse to get the option from - /// if found or - public static VersionOption? GetVersionOption(this ParseResult self) - { - var versionOptions = from r in self.CommandResult.RecurseWhileNotNull(r => r.Parent as CommandResult) - from o in r.Command.Options.OfType() - select o; - - return versionOptions.FirstOrDefault(); - } - - // shamelessly "borrowed" from: https://github.com/dotnet/dotnet/blob/8c7b3dcd2bd657c11b12973f1214e7c3c616b174/src/command-line-api/src/System.CommandLine/Help/HelpBuilderExtensions.cs#L42 - internal static IEnumerable RecurseWhileNotNull( this T? source, Func next ) - where T : class - { - while(source is not null) - { - yield return source; - - source = next( source ); - } - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/ReportingSettings.cs b/src/Ubiquity.NET.CommandLine/ReportingSettings.cs deleted file mode 100644 index b6cc26e5f..000000000 --- a/src/Ubiquity.NET.CommandLine/ReportingSettings.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - // internal extension for IDiagnosticReporter - internal static class ReportingSettings - { - // Create an InvocationConiguration that wraps an IDiagnosticReporter - public static InvocationConfiguration CreateConfig( this IDiagnosticReporter self ) - { - ArgumentNullException.ThrowIfNull( self ); - - return new() - { - EnableDefaultExceptionHandler = false, - Error = new ReportingTextWriter( self, MsgLevel.Error ), - Output = new ReportingTextWriter( self, MsgLevel.Information ), - }; - } - } - - // TextWriter that wraps an IDiagnosticReporter for a given level - // This is an implementation of the GoF "Adapter Pattern" - [SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "File scoped..." )] - file class ReportingTextWriter - : TextWriter - { - public ReportingTextWriter( IDiagnosticReporter diagnosticReporter, MsgLevel level ) - { - Reporter = diagnosticReporter; - MsgLevel = level; - Builder = new(); - } - - public override Encoding Encoding => Reporter.Encoding; - - public MsgLevel MsgLevel { get; } - - public override void Write(string? value) - { - if (value == Environment.NewLine) - { - WriteLine(); - } - else - { - base.Write(value); - } - } - - public override void WriteLine( ) - { - Reporter.Report( MsgLevel, Builder.ToString() ); - Builder.Clear(); - } - - public override void Write( char value ) - { - Builder.Append( value ); - } - - private readonly IDiagnosticReporter Reporter; - private readonly StringBuilder Builder; - } -} diff --git a/src/Ubiquity.NET.CommandLine/SymbolValidationExtensions.cs b/src/Ubiquity.NET.CommandLine/SymbolValidationExtensions.cs deleted file mode 100644 index 8154283b5..000000000 --- a/src/Ubiquity.NET.CommandLine/SymbolValidationExtensions.cs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Utility class to provide extensions for validation of options and arguments - public static class SymbolValidationExtensions - { - /// Fluent function to add a validator to an - /// Type of Option to add the validator to - /// The option to add the validator to - /// validator to add - /// for fluent use - public static T AddValidator( this T self, Action validator ) - where T : Option - { - ArgumentNullException.ThrowIfNull( self ); - self.Validators.Add( validator ); - return self; - } - - /// Fluent function to add a validator to an - /// Type of Option to add the validator to - /// The option to add the validator to - /// validator to add - /// for fluent use - public static T AddValidator( this T self, Action validator ) - where T : Argument - { - ArgumentNullException.ThrowIfNull( self ); - self.Validators.Add( validator ); - return self; - } - - /// Add validation for an existing file only - /// Option to add the validation to - /// for fluent use - /// - /// This is similar to - /// but it is constrained to specifically files AND enhances the error message to - /// provide more details to aid user in correcting the problem. - /// - public static Option AcceptExistingFileOnly( this Option self ) - { - self.AddValidator( ValidateFileExists ); - return self; - } - - /// - public static Argument AcceptExistingFileOnly( this Argument self ) - { - self.AddValidator( ValidateFileExists ); - return self; - } - - /// Add validation for an existing file only - /// Type of value for the argument - /// Option to add the validation to - /// for fluent use - /// - /// This is similar to - /// but it is constrained to specifically files AND enhances the error message to - /// provide more details to aid user in correcting the problem. - /// - public static Argument AcceptExistingFileOnly( this Argument self ) - where T : IEnumerable - { - self.AddValidator( ValidateFileExists ); - return self; - } - - /// Option validator for an Option's tokens that ensures each represents an existing file - /// result to validate - /// - /// This is simply a parse validation sanity check for common usage errors. It is explicitly NOT - /// a security check/test!. Apps MUST NOT assume anything about the file beyond the fact that it - /// existed AT THE TIME this validation ran. It might not exist when needed, might have been replaced - /// since, or may exist as or been replaced by a link to something else. None of those scenarios is - /// verified or prevented by this. - /// - public static void ValidateFileExists( OptionResult result ) - { - string optionName = result.Option.HelpName ?? result.Option.Name; - foreach(var token in result.Tokens) - { - if(!File.Exists( token.Value )) - { - result.AddError( $"File '{token.Value}' specified for '{optionName}' does not exist" ); - return; - } - } - } - - /// Argument validator for an Arguments's tokens that ensures each represents an existing file - /// result to validate - /// - /// This is simply a parse validation sanity check for common usage errors. It is explicitly NOT - /// a security check/test!. Apps MUST NOT assume anything about the file beyond the fact that it - /// existed AT THE TIME this validation ran. It might not exist when needed, might have been replaced - /// since, or may exist as or been replaced by a link to something else. None of those scenarios is - /// verified or prevented by this. - /// - public static void ValidateFileExists( ArgumentResult result ) - { - string optionName = result.Argument.HelpName ?? result.Argument.Name; - foreach(var token in result.Tokens) - { - if(!File.Exists( token.Value )) - { - result.AddError( $"File '{token.Value}' specified for '{optionName}' does not exist" ); - return; - } - } - } - - /// Extension method to add the validator - /// Option to add the validator for - /// for fluent use - public static Option AcceptExistingFolderOnly( this Option self ) - { - self.AddValidator( ValidateFolderExists ); - return self; - } - - /// Option validator for an Option's tokens that ensures each represents an existing Folder - /// result to validate - /// - /// This is simply a parse validation sanity check for common usage errors. It is explicitly NOT - /// a security check/test!. Apps MUST NOT assume anything about the folder beyond the fact that it - /// existed AT THE TIME this validation ran. It might not exist when needed, might have been replaced - /// since, or may exist as or been replaced by a link to something else later. None of those scenarios - /// is verified or prevented by this. - /// - public static void ValidateFolderExists( OptionResult result ) - { - string optionName = result.Option.HelpName ?? result.Option.Name; - foreach(var token in result.Tokens) - { - if(!Directory.Exists( token.Value )) - { - result.AddError( $"Folder '{token.Value}' specified for '{optionName}' does not exist" ); - return; - } - } - } - - /// Extension method to add the validator - /// Option to add the validator for - /// for fluent use - public static Option EnsureFolder( this Option self ) - { - self.AddValidator(EnsureFolderExists); - return self; - } - - /// Option validator for an Option's tokens that ensures each Folder exists (creates it if not present) - /// result to validate - /// - /// This is a parse validation that will create a directory that does not exist. It is explicitly NOT a security - /// check/test!. Apps MUST NOT assume anything about the folder beyond the fact that it existed AT THE TIME this - /// validation ran. It might not exist when needed, might have been replaced since, or may exist as or been - /// replaced by a link to something else later or even deleted later. None of those scenarios is verified or - /// prevented by this. - /// - public static void EnsureFolderExists( OptionResult result ) - { - string value = result.Option.HelpName ?? result.Option.Name; - foreach (Token token in result.Tokens) - { - if (!Directory.Exists(token.Value)) - { - try - { - Directory.CreateDirectory(token.Value); - } - catch(Exception ex) when (IsDirectoryCreationException(ex)) - { - result.AddError($"Attempt to create directory '{token.Value}' resulted in {ex.Message}"); - } - - break; - } - } - } - - // returns true if the exception is one of the documented types for Directory.CreateDirectory(). - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsDirectoryCreationException(Exception ex) - { - return ex is IOException or - UnauthorizedAccessException or - ArgumentException or - PathTooLongException or - DirectoryNotFoundException or - NotSupportedException; - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/TextWriterReporter.cs b/src/Ubiquity.NET.CommandLine/TextWriterReporter.cs deleted file mode 100644 index faed113c3..000000000 --- a/src/Ubiquity.NET.CommandLine/TextWriterReporter.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.CommandLine -{ - /// Base class for reporting diagnostics via an instance of - public class TextWriterReporter - : IDiagnosticReporter - { - /// Initializes a new instance of the class. - /// Reporting level for this reporter (any message with an equal or greater level is reported) - /// Error writer - /// Warning writer - /// Information writer - /// Verbose writer - [SetsRequiredMembers] - public TextWriterReporter( - MsgLevel level, - TextWriter? error = null, - TextWriter? warning = null, - TextWriter? information = null, - TextWriter? verbose = null - ) - { - ArgumentNullException.ThrowIfNull(error); - - Level = level; - Error = error ?? TextWriter.Null; - Warning = warning ?? TextWriter.Null; - Information = information ?? TextWriter.Null; - Verbose = verbose ?? TextWriter.Null; - } - - /// Gets the Error writer for this reporter - public required TextWriter Error - { - get; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// Gets the warning writer for this reporter - public TextWriter Warning - { - get; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// Gets the information writer for this reporter - public TextWriter Information - { - get; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// Gets the verbose writer for this reporter - public TextWriter Verbose - { - get; - init - { - ArgumentNullException.ThrowIfNull(value); - field = value; - } - } - - /// - public MsgLevel Level { get; init; } - - /// - public Encoding Encoding => Error.Encoding; - - /// - /// - /// This implementation will test if the of the - /// message is enabled. If so, then a call is made to the virtual - /// with the results of as the message text. - /// - public void Report( DiagnosticMessage diagnostic ) - { - if(!this.IsEnabled( diagnostic.Level )) - { - return; - } - - ReportMessage(diagnostic.Level, diagnostic.ToString()); - } - - /// Virtual method to report a message formatted as a string - /// Level of the message - /// Message formatted as a string - /// - /// The default base implementation will simply redirect messages based on to - /// the writer for . It is possible that such a writer is - /// and the messages go nowhere. - /// - /// Invalid/Unknown level - should never hit this, internal or derived type error if it does. - protected virtual void ReportMessage(MsgLevel level, string msg) - { - var writer = level switch - { - MsgLevel.Error => Error, - MsgLevel.Warning => Warning, - MsgLevel.Information => Information, - MsgLevel.Verbose => Verbose, - MsgLevel.None => null, - _ => throw new InvalidEnumArgumentException(nameof(level), (int)level, typeof(MsgLevel)) - }; - - // A message level of None is always ignored, this will not occur normally as that level is - // NEVER enabled. But, if a derived type ever calls this directly it might not check for enabled. - // Additionally, not all streams are guaranteed non-null, so this will ignore any that are. - writer?.WriteLine(msg); - } - } -} diff --git a/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj b/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj deleted file mode 100644 index 2268b0246..000000000 --- a/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - - net8.0 - enable - - preview - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - General use Support for Command line parsing based on System.CommandLine - Extensions,.NET,Ubiquity.NET, Console - PackageReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - - - - - - - - - - - - - - - all - false - Analyzer - - - - diff --git a/src/Ubiquity.NET.Extensions.UT/AssemblyExtensionsTests.cs b/src/Ubiquity.NET.Extensions.UT/AssemblyExtensionsTests.cs deleted file mode 100644 index 2281d08df..000000000 --- a/src/Ubiquity.NET.Extensions.UT/AssemblyExtensionsTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Reflection; - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class AssemblyExtensionsTests - { - [TestMethod] -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - public void GetInformationalVersion_with_null_throws( ) - { - var ex = Assert.ThrowsExactly( ()=> _ = AssemblyExtensions.GetInformationalVersion( null ) ); - Assert.AreEqual("null", ex.ParamName, "CallerExpressionArgumentAttribute should provide the expression used"); - - ex = Assert.ThrowsExactly( ()=> _ = AssemblyExtensions.GetInformationalVersion( null, "self" ) ); - Assert.AreEqual( "self", ex.ParamName, "explicit value for CallerExpressionArgumentAttribute should override it" ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void GetInformationalVersion_succeeds( ) - { - var thisAsm = typeof( AssemblyExtensionsTests ).Assembly; - - var assemblyVersionAttribute = thisAsm.GetCustomAttribute(); - - string expected = assemblyVersionAttribute is not null - ? assemblyVersionAttribute.InformationalVersion - : thisAsm.GetName().Version?.ToString() ?? string.Empty; - - string actual = thisAsm.GetInformationalVersion(); - Assert.AreEqual( expected, actual ); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/AssemblyInfo.cs b/src/Ubiquity.NET.Extensions.UT/AssemblyInfo.cs deleted file mode 100644 index a4d0b1392..000000000 --- a/src/Ubiquity.NET.Extensions.UT/AssemblyInfo.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customise this process see: https://aka.ms/assembly-info-properties - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. - -[assembly: Guid("312c3354-ea8c-4819-8823-ac78064c645c")] - -// Tests are so trivial they perform better when not individually parallelized. -// Unfortunately this is an assembly wide choice and not class or method level -// see: https://github.com/microsoft/testfx/issues/5555#issuecomment-3448956323 -[assembly: Parallelize( Scope = ExecutionScope.ClassLevel )] - -// can't use this at assembly level as it isn't supported there for downlevel... [Sigh...] -//[assembly: ExcludeFromCodeCoverage] - -// NOTE: use of this and `internal` test classes results in a flurry of -// error CA1812: '' is an internal class that is apparently never instantiated. If so, remove the code from the assembly. -// If this class is intended to contain only static members, make it 'static' (Module in Visual Basic). -// (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1812) -// In other words, not worth the bother... -// [assembly: DiscoverInternals] diff --git a/src/Ubiquity.NET.Extensions.UT/DictionaryBuilderTests.cs b/src/Ubiquity.NET.Extensions.UT/DictionaryBuilderTests.cs deleted file mode 100644 index 730e9f8e5..000000000 --- a/src/Ubiquity.NET.Extensions.UT/DictionaryBuilderTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class DictionaryBuilderTests - { - [TestMethod] - public void Inintialization_of_KvpArray_is_successfull( ) - { - ImmutableDictionary testDictionary = new DictionaryBuilder() - { - ["one"] = 1, - ["two"] = 2, - }.ToImmutable(); - - Assert.AreEqual( 1, testDictionary[ "one" ] ); - Assert.AreEqual( 2, testDictionary[ "two" ] ); - } - - [TestMethod] - public void Getting_enumerator_from_KvpArrayBuilder_throws( ) - { - var testBuilder = new DictionaryBuilder() - { - ["one"] = 1, - ["two"] = 2, - }; - - Assert.ThrowsExactly( ( ) => - { -#pragma warning disable IDISP004 // Don't ignore created IDisposable - // NOT disposable, no idea where the analyzer gets this from but System.Collections.IEnumerator - // does not implement IDisposable. [Methods is supposed to throw anyway] - _ = testBuilder.GetEnumerator(); -#pragma warning restore IDISP004 // Don't ignore created IDisposable - } - , "Syntactic sugar only for initialization" ); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/DisposableActionTests.cs b/src/Ubiquity.NET.Extensions.UT/DisposableActionTests.cs deleted file mode 100644 index b993dae93..000000000 --- a/src/Ubiquity.NET.Extensions.UT/DisposableActionTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class DisposableActionTests - { - [TestMethod] - public void DisposableAction_CreateNOP_succeeds( ) - { - using var disp = DisposableAction.CreateNOP(); - Assert.IsNotNull(disp); - } - - [TestMethod] - public void DisposableAction_with_null_Action_throws( ) - { - var ex = Assert.ThrowsExactly(static ()=> - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // Testing explicit case of null param. - using var x = new DisposableAction(null); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } ); - Assert.IsNotNull( ex ); - Assert.AreEqual("null", ex.ParamName); - } - - [TestMethod] - [System.Diagnostics.CodeAnalysis.SuppressMessage( "IDisposableAnalyzers.Correctness", "IDISP017:Prefer using", Justification = "Explicit testing" )] - public void DisposableAction_called_correctly() - { - bool actionCalled = false; - - var raiiAction = new DisposableAction( ()=> actionCalled = true ); - raiiAction.Dispose(); // Should trigger call to action - - Assert.IsTrue(actionCalled); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs b/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs deleted file mode 100644 index ff0021d38..000000000 --- a/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using Ubiquity.NET.Extensions.FluentValidation; - -namespace Ubiquity.NET.Extensions.UT.FluentValidation -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class ExceptionValidationExtensionsTests - { - [TestMethod] - public void ThrowIfNull_throws_expected_exception_when_null( ) - { - var ex = Assert.ThrowsExactly(()=> - { - ExceptionValidationExtensions.ThrowIfNull( null ); - } ); - Assert.AreEqual("null", ex.ParamName, "parameter name should match input expression"); - } - - [TestMethod] - public void ThrowIfNull_does_not_throw_on_non_null_input() - { - const string input = "This is a test"; - - Assert.AreSame(input, ExceptionValidationExtensions.ThrowIfNull(input), "Fluent API should return input value on success" ); - } - - [TestMethod] - public void ThrowIfNull_reports_exception_whith_provided_expression( ) - { - const string exp = "My-Expression"; - var ex = Assert.ThrowsExactly(()=> - { - ExceptionValidationExtensions.ThrowIfNull( null, exp ); - } ); - Assert.AreEqual( exp, ex.ParamName, "parameter name should match input expression" ); - } - - [TestMethod] - public void ThrowIfNotDefined_does_not_throw_for_defined_value() - { - Assert.AreEqual(TestEnum.Max, ExceptionValidationExtensions.ThrowIfNotDefined(TestEnum.Max), "Fluent API should return input value on success" ); - } - - [TestMethod] - public void ThrowIfOutOfRange_does_not_throw_for_inrange_values( ) - { - double value = 1.0; - double min = 0.0; - double max = 2.0; - - Assert.AreEqual(value, ExceptionValidationExtensions.ThrowIfOutOfRange(value, min, max), "Fluent API should return input value on success"); - } - - [TestMethod] - public void ThrowIfOutOfRange_throws_for_out_of_range_values( ) - { - double value = 2.0; - double min = 1.0; - double max = 1.5; - - var ex = Assert.ThrowsExactly(()=> - { - _ = ExceptionValidationExtensions.ThrowIfOutOfRange( value, min, max ); - } ); - Assert.AreEqual(value, ex.ActualValue); - Assert.AreEqual(nameof(value), ex.ParamName); - } - - [TestMethod] - public void ThrowIfOutOfRange_throws_with_custom_expression_for_out_of_range_values( ) - { - double value = 2.0; - double min = 1.0; - double max = 1.5; - - const string exp = "My Expression"; - var ex = Assert.ThrowsExactly(()=> - { - _ = ExceptionValidationExtensions.ThrowIfOutOfRange( value, min, max, exp ); - } ); - Assert.AreEqual( value, ex.ActualValue ); - Assert.AreEqual( exp, ex.ParamName ); - } - - [TestMethod] - public void ThrowIfNotDefined_throws_for_undefined_values( ) - { - var temp = (TestEnum)4; - var ex = Assert.ThrowsExactly( ( ) => - { - ExceptionValidationExtensions.ThrowIfNotDefined(temp); - } ); - Assert.AreEqual(nameof(temp), ex.ParamName, "parameter name should match input expression" ); - - var temp2 = (TestByteEnum)4; - var ex2 = Assert.ThrowsExactly( ( ) => - { - ExceptionValidationExtensions.ThrowIfNotDefined(temp2); - } ); - Assert.AreEqual( nameof( temp2 ), ex2.ParamName, "parameter name should match input expression" ); - - // This still fits an int so the normal constructor that sets paramName is available - var temp3 = (TestU64Enum)int.MaxValue; - var ex3 = Assert.ThrowsExactly( ( ) => - { - ExceptionValidationExtensions.ThrowIfNotDefined(temp3); - } ); - Assert.AreEqual( nameof( temp3 ), ex3.ParamName, "parameter name should match input expression" ); - - // This can't fit into an int so, the exception constructor that does not provide paramName is - // the only option :( [But at least this scenario is VERY rare in the real world] - var temp4 = (TestU64Enum)(UInt64.MaxValue - 1); - var ex4 = Assert.ThrowsExactly( ( ) => - { - ExceptionValidationExtensions.ThrowIfNotDefined(temp4); - } ); - Assert.IsNull( ex4.ParamName, "parameter name not available for non-int formattable enums" ); - } - - private enum TestEnum // default underling type is Int32 - { - Zero, - One, - Two, - Max = int.MaxValue - } - - private enum TestByteEnum - : byte - { - Zero, - One, - Two, - Max = byte.MaxValue - } - - private enum TestU64Enum - : UInt64 - { - Zero, - One, - Two, - Max = UInt64.MaxValue - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Extensions.UT/GlobalNamespaceImports.cs deleted file mode 100644 index d21b98d8c..000000000 --- a/src/Ubiquity.NET.Extensions.UT/GlobalNamespaceImports.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -// BUG: False positive from IDE0005 - Using directive is unnecessary -// Attempts to remove/sort are at least able to figure it out and do the right thing. -// Bug seems to be related to multi-targetting. -#pragma warning disable IDE0005 - -global using System; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.ComponentModel; -global using System.Diagnostics.CodeAnalysis; -global using System.Runtime.InteropServices; - -global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/Ubiquity.NET.Extensions.UT/GlobalSuppressions.cs b/src/Ubiquity.NET.Extensions.UT/GlobalSuppressions.cs deleted file mode 100644 index caa5d65f9..000000000 --- a/src/Ubiquity.NET.Extensions.UT/GlobalSuppressions.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Test module" )] diff --git a/src/Ubiquity.NET.Extensions.UT/KvpArrayBuilderTests.cs b/src/Ubiquity.NET.Extensions.UT/KvpArrayBuilderTests.cs deleted file mode 100644 index ebbf1a5d9..000000000 --- a/src/Ubiquity.NET.Extensions.UT/KvpArrayBuilderTests.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class KvpArrayBuilderTests - { - [TestMethod] - public void Inintialization_of_KvpArray_is_successfull( ) - { - ImmutableArray> testArray = new KvpArrayBuilder() - { - ["one"] = 1, - ["two"] = 2, - }.ToImmutable(); - - Assert.AreEqual( "one", testArray[ 0 ].Key); - Assert.AreEqual( 1, testArray[ 0 ].Value ); - - Assert.AreEqual( "two", testArray[ 1 ].Key ); - Assert.AreEqual( 2, testArray[ 1 ].Value ); - } - - [TestMethod] - public void Getting_enumerator_from_KvpArrayBuilder_throws( ) - { - var testBuilder = new KvpArrayBuilder() - { - ["one"] = 1, - ["two"] = 2, - }; - - Assert.ThrowsExactly(()=> - { -#pragma warning disable IDISP004 // Don't ignore created IDisposable - // NOT disposable, no idea where the analyzer gets this from but System.Collections.IEnumerator - // does not implement IDisposable. [Methods is supposed to throw anyway] - _ = testBuilder.GetEnumerator(); -#pragma warning restore IDISP004 // Don't ignore created IDisposable - } - , "Syntactic sugar only for initialization"); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs b/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs deleted file mode 100644 index fc3dda87f..000000000 --- a/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// .NET 7 added the various exception static methods for parameter validation -// This will back fill them for earlier versions. -// -// NOTE: C #14 extension keyword support is required to make this work. -#if !NET7_0_OR_GREATER - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class PolyFillExceptionValidatorsTests - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - [TestMethod] - public void ThrowIfNullOrWhiteSpace_throws_if_null_or_whitespace( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( null )); - Assert.AreEqual( "null", ex.ParamName, "Compiler should provide expression as name" ); - - ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( null, "self" ) ); - Assert.AreEqual( "self", ex.ParamName, "explicit name should override compiler" ); - - var argEx = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(" \t ")); - Assert.AreEqual( "\" \\t \"", argEx.ParamName, "Compiler should provide expression as name" ); - - argEx = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( " \t ", "self" ) ); - Assert.AreEqual( "self", argEx.ParamName, "explicit name should override compiler" ); - - argEx = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( string.Empty ) ); - Assert.AreEqual( "string.Empty", argEx.ParamName, "Compiler should provide expression as name" ); - - argEx = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( string.Empty, "self" ) ); - Assert.AreEqual( "self", argEx.ParamName, "explicit name should override compiler" ); - } - - [TestMethod] - public void ThrowIfNull_throws_if_null( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNull( null )); - Assert.AreEqual( "null", ex.ParamName, "Compiler should provide expression as name" ); - - ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNull( null, "self" ) ); - Assert.AreEqual( "self", ex.ParamName, "explicit name should override compiler" ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void ThrowIf_throws_as_expected( ) - { - object instance = new(); - - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIf(true, instance)); - Assert.AreEqual( "System.Object", ex.ObjectName ); - - // should not throw - PolyFillExceptionValidators.ThrowIf( false, instance ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfEqual_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfEqual(1, 1)); - Assert.AreEqual( "1", ex.ParamName ); - Assert.AreEqual( 1, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfEqual( 1, 0 ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfNotEqual_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfNotEqual(1, 0)); - Assert.AreEqual( "1", ex.ParamName ); - Assert.AreEqual( 1, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfNotEqual( 1, 1 ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfGreaterThan_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfGreaterThan(1, 0)); - Assert.AreEqual( "1", ex.ParamName ); - Assert.AreEqual( 1, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfGreaterThan( 0, 1 ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfGreaterThanOrEqual_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfGreaterThanOrEqual(1, 1)); - Assert.AreEqual( "1", ex.ParamName ); - Assert.AreEqual( 1, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfGreaterThanOrEqual( 0, 1 ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfLessThan_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfLessThan(0, 1)); - Assert.AreEqual( "0", ex.ParamName ); - Assert.AreEqual( 0, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfLessThan( 1, 0 ); - } - - [TestMethod] - public void ArgumentOutOfRangeExcpetion_ThrowIfLessThanOrEqual_operates_as_expected( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillExceptionValidators.ThrowIfLessThanOrEqual(1, 1)); - Assert.AreEqual( "1", ex.ParamName ); - Assert.AreEqual( 1, ex.ActualValue ); - - // should not throw - PolyFillExceptionValidators.ThrowIfLessThanOrEqual( 1, 0 ); - } - } -} -#endif diff --git a/src/Ubiquity.NET.Extensions.UT/PolyFillOperatingSystemTests.cs b/src/Ubiquity.NET.Extensions.UT/PolyFillOperatingSystemTests.cs deleted file mode 100644 index 7092894f4..000000000 --- a/src/Ubiquity.NET.Extensions.UT/PolyFillOperatingSystemTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - public sealed class PolyFillOperatingSystemTests - { - [TestMethod] - public void IsWindows_reports_correct_value( ) - { - bool isWindows = Environment.OSVersion.Platform switch - { - PlatformID.Win32S or - PlatformID.Win32Windows or - PlatformID.Win32NT or - PlatformID.WinCE => true, - _ => false, - }; - - Assert.AreEqual(isWindows, OperatingSystem.IsWindows()); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/PolyFillStringExtensionsTests.cs b/src/Ubiquity.NET.Extensions.UT/PolyFillStringExtensionsTests.cs deleted file mode 100644 index 9d9d4e56a..000000000 --- a/src/Ubiquity.NET.Extensions.UT/PolyFillStringExtensionsTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#if !NET6_0_OR_GREATER -// need to import the namespace to allow implicit access to extensions -using System.Text; -#endif - -namespace Ubiquity.NET.Extensions.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class PolyFillStringExtensionsTests - { - // since these are ONLY an extension with runtime's prior to .NET 6 - // test the handling. For .NET6 or later, that's up to .NET itself to test -#if !NET6_0_OR_GREATER -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - [TestMethod] - public void Methods_throw_on_invalid_input( ) - { - var ex = Assert.ThrowsExactly( ( ) => PolyFillStringExtensions.ReplaceLineEndings( null )); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => PolyFillStringExtensions.ReplaceLineEndings( null, "replacement" )); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => PolyFillStringExtensions.ReplaceLineEndings( "source text", null ) ); - Assert.AreEqual( "replacementText", ex.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. -#endif - - // Technically this tests both poly filled and official implementations, but validates the - // assumptions that exist betweeen them. If both pass then the poly fill is replicating the - // tested behavior of the official runtime implementation. (If, perhaps, less performant...) - - [TestMethod] - public void ReplaceLineEndings_uses_Environment_newlines( ) - { - const string inputMixedLines = "line0\r\nline1\rline2\nline3\fline4\u0085line5\u2028line6\u2029"; - string expected = "line0" + Environment.NewLine - + "line1" + Environment.NewLine - + "line2" + Environment.NewLine - + "line3" + Environment.NewLine - + "line4" + Environment.NewLine - + "line5" + Environment.NewLine - + "line6" + Environment.NewLine; - - string actual = inputMixedLines.ReplaceLineEndings(); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void ReplaceLineEndings_uses_provided_string( ) - { - const string inputMixedLines = "line0\r\nline1\rline2\nline3\fline4\u0085line5\u2028line6\u2029"; - const string expected = "line0line1line2line3line4line5line6"; - - string actual = inputMixedLines.ReplaceLineEndings(""); - Assert.AreEqual( expected, actual ); - } - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/StringNormalizerTests.cs b/src/Ubiquity.NET.Extensions.UT/StringNormalizerTests.cs deleted file mode 100644 index e310727fd..000000000 --- a/src/Ubiquity.NET.Extensions.UT/StringNormalizerTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.UT -{ - // NOTE: Test output will include special characters for CR (U+240D) and LF (U+240A) characters that makes it easy to visualize - // line ending differences. - // - // Example: - // Assert.AreEqual failed. Expected string length 51 but was 52. 'expected' expression: 'expectedOutput', 'actual' expression: 'systemNormalizedInput'. - // Expected: "This is a line␊This is anotherline␊And..." - // But was: "This is a line␍␊This is anotherline␍An..." - // -------------------------^ - - // NOTE: In C# the string "line1\nLine2" has exactly what was put in the string - // That is, it contains a SINGLE LF '\n' character. NOT an environment - // specific "newline". Thus, if a string needs to represent a platform specific - // newline sequence it can use `Environment.NewLine` or `string.ReplaceLineEndings()`. - // The docs on `ReplaceLineEndings()` are silent on the point of input forms - // replaced. However, spelunking the code indicates it follows Unicode standard §5.8, - // Recommendation R4 and Table 5-2 (CR, LF, CRLF, NEL, LS, FF, PS). Explicitly excluded - // is VT. Thus, that will normalize ANY newline sequence to the form expected by the - // environment. - // Unfortunately, ReplaceLineEndings() is NOT available in downlevel runtimes... - - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class StringNormalizerTests - { - [TestMethod] - public void System_line_ending_detected_correctly( ) - { - Assert.AreEqual( OsDefaultLineEnding, StringNormalizer.SystemLineEndings); - } - - [TestMethod] - public void Normalize_with_default_endings_does_nothing( ) - { - string testInput = "This is a line" + Environment.NewLine + "And so is this"; // Platform sepecific - string normalizedOutput = testInput.NormalizeLineEndings(StringNormalizer.SystemLineEndings); - Assert.AreSame(testInput, normalizedOutput, "Should return same instance (zero copy)"); - } - - [TestMethod] - public void Normalize_with_alternate_endings_produces_new_string( ) - { - string testInput = "This is a line"+ Environment.NewLine + "And so is this"; // Platform sepecific - const string expectedOutput = "This is a line\rAnd so is this"; - - // CR Only is not the default for any currently supported runtinme for .NET so this - // remains a platform neutral test - verify that assumption! - // See also: System_line_ending_detected_correctly() - Assert.AreNotEqual(LineEndingKind.CarriageReturn, StringNormalizer.SystemLineEndings, "TEST ERROR: CR is default line ending for this runtime!"); - - string normalizedOutput = testInput.NormalizeLineEndings(LineEndingKind.CarriageReturn); - Assert.AreEqual(expectedOutput, normalizedOutput); - } - - [TestMethod] - public void Normalize_with_mixed_input_succeeds() - { - const string mixedInput = "This is a line\r\nThis is anotherline\rAnd aonther line"; - string expectedOutput = "This is a line"+ Environment.NewLine + "This is anotherline"+ Environment.NewLine + "And aonther line"; // Platform sepecific - string systemNormalizedInput = mixedInput.NormalizeLineEndings(LineEndingKind.MixedOrUnknownEndings, StringNormalizer.SystemLineEndings); - Assert.AreEqual(expectedOutput, systemNormalizedInput); - } - - // Technincally Mac OS prior to OS X (Lion) use CR, but .NET does not - // support those older versions. Thus, this only treats Windows as the - // "odd man out", everything else uses LF. - private static LineEndingKind OsDefaultLineEnding - => OperatingSystem.IsWindows() - ? LineEndingKind.CarriageReturnLineFeed - : LineEndingKind.LineFeed; - } -} diff --git a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj b/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj deleted file mode 100644 index d3d73d298..000000000 --- a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net8.0;net481 - - preview - false - True - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - diff --git a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs b/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs deleted file mode 100644 index 552ed2092..000000000 --- a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Utility class to provide extensions for consumers - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Extension" )] - public static class AssemblyExtensions - { - // VS2026 builds of this are OK, however VS2019, command line/PR/CI builds will generate an error. - // The VS builds use the VS provided MSBuild, while the command line uses the .NET Core build. - // This is just another example of why the `extension` keyword is "not yet ready for prime time". - // Too many things don't support it properly yet so use needs justification as the ONLY option and - // HEAVY testing to ensure all the issues are accounted for. - // [Hopefully, that works itself out in short order as it's a mostly useless feature unless fully supported] -#if COMPILER_SUPPORTS_CALLER_ATTRIBUES_ON_EXTENSION - extension(Assembly self) - { - /// Gets the informational version from an assembly - /// Expresssion for the assembly to retrieve the attribute data from; normally provided by compiler - /// String contents from the in the assembly or - [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "Instance extension" )] - public string GetInformationalVersion( [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - { - ArgumentNullException.ThrowIfNull( self, exp ); - - var assemblyVersionAttribute = self.GetCustomAttribute(); - - return assemblyVersionAttribute is not null - ? assemblyVersionAttribute.InformationalVersion - : self.GetName().Version?.ToString() ?? string.Empty; - } - } -#else - /// Gets the informational version from an assembly - /// Assembly to extract the version from - /// Expresssion for the assembly to retrieve the attribute data from; normally provided by compiler - /// String contents from the in the assembly or - public static string GetInformationalVersion(this Assembly self, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - { - ArgumentNullException.ThrowIfNull( self, exp ); - - var assemblyVersionAttribute = self.GetCustomAttribute(); - - return assemblyVersionAttribute is not null - ? assemblyVersionAttribute.InformationalVersion - : self.GetName().Version?.ToString() ?? string.Empty; - } -#endif - - } -} diff --git a/src/Ubiquity.NET.Extensions/DictionaryBuilder.cs b/src/Ubiquity.NET.Extensions/DictionaryBuilder.cs deleted file mode 100644 index 9ade8101b..000000000 --- a/src/Ubiquity.NET.Extensions/DictionaryBuilder.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Simple type to support direct creation of an immutable array via - /// Type of the key - /// Type of the value - /// - /// - /// For unknown reasons does not have an equivalent - /// to (Perhaps because there is none for a dictionary?) - /// the normal behavior of initializing a mutable dictionary and then converting it to immutable - /// has sub-par performance as it needs to allocate the mutable form, add the members, then allocate - /// the immutable form and COPY all the members. This includes the overhead of building and maintaining - /// the hash codes for all the keys - just to copy it and destroy it... MUCH better performance is achieved - /// by building the dictionary directly then converting it into the final immutable form. - /// - /// This implementation is based on a post on StackOverflow by Ian Griffiths (updated for latest language - /// and runtime version functionality). It provides collection initialization support for - /// . - /// - /// - [SuppressMessage( "Design", "CA1010:Generic interface should also be implemented", Justification = "IEnumerable is unused but must exist to support collection initializers" )] - [SuppressMessage( "Naming", "CA1710:Identifiers should have correct suffix", Justification = "It is the correct suffix" )] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Equality not relevant for a builder" )] - public readonly struct DictionaryBuilder( ) - : IEnumerable - where TKey : notnull - { - /// Add a key+value pair into the dictionary - /// Key for the entry - /// Value for the entry - public void Add( TKey key, TValue value ) - { - Builder.Add( key, value ); - } - - /// Indexer to set the value of an entry - /// Key value to set - /// Value to set for the - /// Value type [Ignored for set only support] - [SuppressMessage( "Design", "CA1044:Properties should not be write only", Justification = "This type is ONLY for building immutable dictionaries" )] - public TValue this[ TKey key ] - { - set => Builder[ key ] = value; - } - - /// - [MustUseReturnValue] - public ImmutableDictionary ToImmutable( ) - { - return Builder.ToImmutable(); - } - - /// - /// Always; Do not use this method. It exists only to allow compile time initializer syntax. - public IEnumerator GetEnumerator( ) - { - // Only implementing IEnumerable because collection initializer - // syntax is unavailable if you don't. - throw new NotImplementedException(); - } - - private readonly ImmutableDictionary.Builder Builder = ImmutableDictionary.CreateBuilder(); - } -} diff --git a/src/Ubiquity.NET.Extensions/DisposableAction.cs b/src/Ubiquity.NET.Extensions/DisposableAction.cs deleted file mode 100644 index a214fed35..000000000 --- a/src/Ubiquity.NET.Extensions/DisposableAction.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Disposable type that runs a specified action on dispose - /// - /// This is used in RAII pattern style code instead of try/finally. It is most - /// valuable when the scope extends beyond a single function (as a return of - /// ) where a try/finally simply won't work. - /// - public sealed class DisposableAction - : IDisposable - { - /// Initializes a new instance of the class. - /// Action to run when is called. - /// Expression for any exceptions; default normally provided by compiler as expression for - public DisposableAction( Action onDispose, [CallerArgumentExpression(nameof(onDispose))] string? exp = null ) - { - OnDispose = onDispose ?? throw new ArgumentNullException( exp ); - } - - /// Runs the action provided in the constructor () - public void Dispose( ) - { - var disposeOp = Interlocked.Exchange(ref OnDispose, null); - disposeOp!(); - } - - /// Creates an implementation of that does nothing for the "Null Object" pattern - /// The that does nothing on - /// - /// The instance returned is allocated from the managed heap to ensure that is - /// called only once (any additional call results in an ). - /// - public static IDisposable CreateNOP() - { - return new DisposableAction(()=> { }); - } - - private Action? OnDispose; - } -} diff --git a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs b/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs deleted file mode 100644 index ec3908330..000000000 --- a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.FluentValidation -{ - // This does NOT use the new C# 14 extension syntax due to several reasons - // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly, marked as "not planned" - e.g., dead-end] - // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx]) - // 3) Many external tools don't support the new syntax yet and it isn't clear if they will in the future. - // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]). - // - // Bottom line it's a good idea with an incomplete implementation lacking support - // in the overall ecosystem. Don't use it unless you absolutely have to until all - // of that is sorted out. - - /// Extension class to provide Fluent validation of arguments - /// - /// These are similar to many of the built-in support checks except that - /// they use a `Fluent' style to allow validation of parameters used as inputs - /// to other functions that ultimately produce parameters for a base constructor. - /// They also serve to provide validation when using body expressions for property - /// method implementations etc... Though the C# 14 field keyword makes that - /// use mostly a moot point. - /// - /// In .NET Standard 2.0 builds this can create ambiguities with the static extensions - /// in `PolyFillExceptionValidators`. This is becuase they are "Poly Filled" - /// in downstream versions and the resolution rules for extensions in the C# language. - /// Instance methods are resolved before the static extensions and therefore the extensions - /// here are resolved even if there is a direct static extensions. This seems broken, but - /// is how the language is resolving things. Therefore carefull use of namespace usings - /// and global usings as well as explicit use of this type is needed to resolve this. It - /// is NOT recommended to use explict references to the static method in `PolyFillExceptionValidators` - /// as the methods don't exist if the BCL type contains the method already in a given - /// runtime. Thus, in compilation units, needing both namespaces only this one is - /// explicitly referenced. - /// - /// - public static class ExceptionValidationExtensions - { - /// Throws an exception if is - /// Type of reference parameter to test for - /// Instance to test - /// Name or expression of the value in [Default: provided by compiler] - /// - /// is - [DebuggerStepThrough] - public static T ThrowIfNull( [NotNull] this T? self, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - { - return self is null - ? throw new ArgumentNullException(exp) - : self; - } - - /// Throws an exception if an argument is outside of a given (Inclusive) range - /// Type of value to test - /// Value to test - /// Minimum value allowed for - /// Maximum value allowed for - /// Name or expression of the value in [Default: provided by compiler] - /// - [DebuggerStepThrough] - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Not simpler, more readable this way" )] - public static T ThrowIfOutOfRange( this T self, T min, T max, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - where T : struct, IComparable - { - ArgumentOutOfRangeException.ThrowIfLessThan(self, min, exp); - ArgumentOutOfRangeException.ThrowIfGreaterThan(self, max, exp); - return self; - } - - /// Tests if an enum is defined or not - /// Type of value to test - /// Value to test - /// Name or expression of the value in [Default: provided by compiler] - /// - /// The enumerated value is not defined - /// - /// This is useful to prevent callers from playing tricks with casts, etc... to land with a value - /// that is otherwise undefined. Note: This is mostly useless on an enumeration marked with - /// as a legit value that is a combination of flags does not have - /// a defined value (Only single bit values do) - /// - [DebuggerStepThrough] - public static T ThrowIfNotDefined( this T self, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - where T : struct, Enum - { - exp ??= string.Empty; - - if(Enum.IsDefined( typeof(T), self )) - { - return self; - } - - try - { - int underlyingValue = (int)Convert.ChangeType(self, typeof(int), CultureInfo.InvariantCulture); - throw new InvalidEnumArgumentException( exp, underlyingValue, typeof( T ) ); - } - catch(Exception ex) when (ex is InvalidCastException or FormatException or OverflowException) - { - // InvalidEnumArgumentException constructors ONLY provide paramater name value set for values - // that are representable as an int. Thus, anything else requires a custom message that at - // least includes the original value in question. (Normally an enum does fit an int, but for - // interop might not) the resulting exception will have "ParamName" as the default of "null"! - // - // This matches the overloaded constructor version but allows for reporting enums with non-int underlying type. - throw new InvalidEnumArgumentException( SR.Format( nameof( Resources.InvalidEnumArgument_NonInt ), exp, self, typeof( T ) ) ); - } - } - } -} diff --git a/src/Ubiquity.NET.Extensions/FluentValidation/ReadMe.md b/src/Ubiquity.NET.Extensions/FluentValidation/ReadMe.md deleted file mode 100644 index da2006705..000000000 --- a/src/Ubiquity.NET.Extensions/FluentValidation/ReadMe.md +++ /dev/null @@ -1,11 +0,0 @@ -# About -This folder contains fluent validation extensions. It is a distinct namespace to aid in -diasambiguation when using downlevel polyfills for static validation extensions. When both -instance extensions and static extensions are available with the same name there is an -ambiguity and the compiler resolves to the instance extension. Thus, -`.` is resolved as -`.` when both are available. Thus, when supporting -downlevel runtimes (like for a Roslyn Source generator/analyzer/fixer or VSIX extension) -then both namespaces are not implicitly "used". Instead only one is. If both namespaces are -needed in a compilation unit, then the poly fill is "used" and the fluent form is explicitly -referenced. Thus, there is no implicit ambiguity/confusion. diff --git a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs deleted file mode 100644 index 8137e3280..000000000 --- a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -// BUG: False positive from IDE0005 - Using directive is unnecessary -// Attempts to remove/sort are at least able to figure it out and do the right thing. -// Bug seems to be related to multi-targetting. -#pragma warning disable IDE0005 - -global using System; -global using System.Collections; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.ComponentModel; -global using System.Diagnostics; -global using System.Diagnostics.CodeAnalysis; -global using System.Globalization; -global using System.IO; -global using System.Linq; -global using System.Reflection; -global using System.Runtime.CompilerServices; -global using System.Text; -global using System.Threading; - -global using Ubiquity.NET.Extensions.Properties; diff --git a/src/Ubiquity.NET.Extensions/KvpArrayBuilder.cs b/src/Ubiquity.NET.Extensions/KvpArrayBuilder.cs deleted file mode 100644 index b533adb04..000000000 --- a/src/Ubiquity.NET.Extensions/KvpArrayBuilder.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Simple type to support dictionary style creation of an immutable array of KeyValue pairs via - /// Type of the key - /// Type of the value - /// - /// - /// This allows creation of an array of pairs without incurring the overhead of allocating and initializing - /// the hash codes for the keys, if they are not used. (As may be the case if the list is provided to - /// native interop). The normal behavior of building an would - /// at a minimum incur the cost of allocation and computation of the hash code for each key. - /// MUCH better performance is achieved by building the array via a builder then converting it into the - /// final immutable form. - /// - /// This implementation is based on a post on StackOverflow by Ian Griffiths (updated for latest language - /// and runtime version functionality and to produce an ). It provides collection - /// initialization support for a sequence of . - /// - /// - [SuppressMessage( "Design", "CA1010:Generic interface should also be implemented", Justification = "IEnumerable is unused but must exist to support collection initializers" )] - [SuppressMessage( "Naming", "CA1710:Identifiers should have correct suffix", Justification = "It is the correct suffix; This is NOT a collection it's a builder" )] - [SuppressMessage( "Performance", "CA1815:Override equals and operator equals on value types", Justification = "Equality not relevant for a builder" )] - public readonly struct KvpArrayBuilder( ) - : IEnumerable - where TKey : notnull - { - /// Add a key+value pair into the dictionary - /// Key for the entry - /// Value for the entry - public void Add( TKey key, TValue value ) - { - Builder.Add( new( key, value ) ); - } - - /// Indexer to set the value of an entry - /// Key value to set - /// Value to set for the - /// Value type [Ignored for set only support] - /// - /// - /// Since this builder does NOT store or compute hash codes the indexer is a syntactical convenience - /// wrapper around the method. It does NOT prevent duplicate entries. If you use - /// the same with a different value, then you get TWO entries with that key. - /// (In some scenarios this may be desirable, but in most it is not.) The important point on this is - /// that it is ***NOT*** checked here. - /// - /// - [SuppressMessage( "Design", "CA1044:Properties should not be write only", Justification = "This type is ONLY for building immutable arrays" )] - public TValue this[ TKey key ] - { - set => Add( key, value ); - } - - /// - [MustUseReturnValue] - public ImmutableArray> ToImmutable( ) - { - return Builder.ToImmutable(); - } - - /// - /// Always; Do not use this method. It exists only to allow compile time initializer syntax. - [DoesNotReturn] - public IEnumerator GetEnumerator( ) - { - // Only implementing IEnumerable because collection initializer - // syntax is unavailable if you don't. - throw new NotImplementedException(); - } - - private readonly ImmutableArray>.Builder Builder = ImmutableArray.CreateBuilder>(); - } -} diff --git a/src/Ubiquity.NET.Extensions/MustUseReturnValueAttribute.cs b/src/Ubiquity.NET.Extensions/MustUseReturnValueAttribute.cs deleted file mode 100644 index 43447d89f..000000000 --- a/src/Ubiquity.NET.Extensions/MustUseReturnValueAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// `MustUseRetVal` analyzer compatible attribute - /// - /// - [AttributeUsage( AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct )] - public sealed class MustUseReturnValueAttribute - : Attribute - { - } -} diff --git a/src/Ubiquity.NET.Extensions/ProcessInfo.cs b/src/Ubiquity.NET.Extensions/ProcessInfo.cs deleted file mode 100644 index 8e51ea05d..000000000 --- a/src/Ubiquity.NET.Extensions/ProcessInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Process related extensions/support - public static class ProcessInfo - { - /// Gets the active assembly as of the first use of this property - /// - /// The active assembly is the entry assembly which may be null if called from native - /// code as no such assembly exists for that scenario. - /// - public static Assembly? ActiveAssembly => field ??= Assembly.GetEntryAssembly(); - - /// Gets the executable path for this instance of an application - /// This is a short hand for [ 0 ] - public static string ExecutablePath => Environment.GetCommandLineArgs()[ 0 ]; - - /// Gets the name of the executable for this instance of an application - /// - /// This is a short hand for using - /// as the path. - /// - public static string ExecutableName => Path.GetFileNameWithoutExtension( ExecutablePath ); - } -} diff --git a/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs b/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs deleted file mode 100644 index 2fe41e37d..000000000 --- a/src/Ubiquity.NET.Extensions/Properties/ResourceNotFoundException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.Properties -{ - /// Exception thornw if a resource is missing - /// - /// This is ALWAYS a bug in the application and should not be caught or suppressed in any way. - /// It indicates that a named resource does not exist, either add the resource or correct the - /// spelling of the name - NEVER dismiss this. - /// - [Serializable] - public class ResourceNotFoundException - : Exception - { - /// Initializes a new instance of the class. - public ResourceNotFoundException( ) - { - } - - /// - public ResourceNotFoundException( string resourceName ) - : base( string.Format( Resources.Culture, Resources.Missing_Resource_Exception_Message_fmt, resourceName ) ) - { - ResourceName = resourceName; - } - - /// - public ResourceNotFoundException( string message, Exception inner ) - : base( message, inner ) - { - } - - /// Gets the name of the resource missing - public string ResourceName { get; } = string.Empty; - } -} diff --git a/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs b/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs deleted file mode 100644 index 71cb072c4..000000000 --- a/src/Ubiquity.NET.Extensions/Properties/Resources.Designer.cs +++ /dev/null @@ -1,171 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Ubiquity.NET.Extensions.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ubiquity.NET.Extensions.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to The value cannot be an empty string or composed entirely of whitespace.. - /// - internal static string Argument_EmptyOrWhiteSpaceString { - get { - return ResourceManager.GetString("Argument_EmptyOrWhiteSpaceString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be equal to '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeEqual { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeEqual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be greater than '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeGreater { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeGreater", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be greater than or equal to '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeGreaterOrEqual { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeGreaterOrEqual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be less than '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeLess { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeLess", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be less than or equal to '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeLessOrEqual { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeLessOrEqual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be a non-negative value.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeNonNegative { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeNonNegative", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be a non-negative and non-zero value.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must be a non-zero value.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeNonZero { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeNonZero", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} ('{1}') must not be equal to '{2}'.. - /// - internal static string ArgumentOutOfRange_Generic_MustBeNotEqual { - get { - return ResourceManager.GetString("ArgumentOutOfRange_Generic_MustBeNotEqual", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The value of argument '{0}' ({1}) is invalid for Enum of type '{2}'. - /// - internal static string InvalidEnumArgument_NonInt { - get { - return ResourceManager.GetString("InvalidEnumArgument_NonInt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Missing resource. Name='{0}'. - /// - internal static string Missing_Resource_Exception_Message_fmt { - get { - return ResourceManager.GetString("Missing_Resource_Exception_Message_fmt", resourceCulture); - } - } - } -} diff --git a/src/Ubiquity.NET.Extensions/Properties/Resources.resx b/src/Ubiquity.NET.Extensions/Properties/Resources.resx deleted file mode 100644 index 62c8ccfc7..000000000 --- a/src/Ubiquity.NET.Extensions/Properties/Resources.resx +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - {0} ('{1}') must be a non-zero value. - - - {0} ('{1}') must be a non-negative value. - - - {0} ('{1}') must be a non-negative and non-zero value. - - - {0} ('{1}') must be less than or equal to '{2}'. - - - {0} ('{1}') must be less than '{2}'. - - - {0} ('{1}') must be greater than or equal to '{2}'. - - - {0} ('{1}') must be greater than '{2}'. - - - {0} ('{1}') must be equal to '{2}'. - - - {0} ('{1}') must not be equal to '{2}'. - - - The value cannot be an empty string or composed entirely of whitespace. - - - The value of argument '{0}' ({1}) is invalid for Enum of type '{2}' - Message used for enums with an underlying type that is not able to fit into an int. {0} - expression that generated the exception; {1} value of the expression that is invalid; {2} is the type of the enumeration. - - - Missing resource. Name='{0}' - - \ No newline at end of file diff --git a/src/Ubiquity.NET.Extensions/Properties/SR.cs b/src/Ubiquity.NET.Extensions/Properties/SR.cs deleted file mode 100644 index d59355747..000000000 --- a/src/Ubiquity.NET.Extensions/Properties/SR.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions.Properties -{ - // RESX file generator does not use `partial` and VS2022 doesn't correctly support - // C# 14 `extension` so derivation is the only option at present... - // CONSIDER: Generator to create this from an attribute that includes the type of the Resources - // generator/Analyzer should validate that the type has a ResourceManager member. - // (Effectively a C++ concept since the type is generated by a SingleFileGenerator - // it doesn't have an interface or base type to use as a constraint) - internal static class SR - { - internal static string Format( [NotNull] string resourceName, TArg0 arg0 ) - { - ArgumentNullException.ThrowIfNull(resourceName); - - string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName); - return string.Format(Resources.Culture, fmt, arg0); - } - - internal static string Format( [NotNull] string resourceName, TArg0 arg0, TArg1 arg1 ) - { - ArgumentNullException.ThrowIfNull(resourceName); - - string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName); - return string.Format(Resources.Culture, fmt, arg0, arg1); - } - - internal static string Format( [NotNull] string resourceName, TArg0 arg0, TArg1 arg1, TArg2 arg2 ) - { - ArgumentNullException.ThrowIfNull(resourceName); - - string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName); - return string.Format(Resources.Culture, fmt, arg0, arg1, arg2); - } - - internal static string Format( [NotNull] string resourceName, params IEnumerable args ) - { - ArgumentNullException.ThrowIfNull(resourceName); - - string? fmt = Resources.ResourceManager.GetString(resourceName, Resources.Culture) ?? throw new ResourceNotFoundException(resourceName); - return string.Format(Resources.Culture, fmt, args); - } - } -} diff --git a/src/Ubiquity.NET.Extensions/Readme.md b/src/Ubiquity.NET.Extensions/Readme.md deleted file mode 100644 index 85e55ee69..000000000 --- a/src/Ubiquity.NET.Extensions/Readme.md +++ /dev/null @@ -1,56 +0,0 @@ -# About -Ubiquity.NET.Extensions contains general extensions for .NET. This is a bit of a "grab bag" -of functionality used by but not actually part of multiple other Ubiquity.NET projects. A -core principal is that this library has NO dependencies beyond the runtime itself. That is, -this library should remain at the bottom of any dependency chain. - -## Key support -* Computing a hash code for a ReadOnlySpan of bytes using - [System.IO.System.IO.Hashing.XxHash3](https://learn.microsoft.com/en-us/dotnet/api/system.io.hashing.xxhash3) -* DisposableAction for building actions that must occur on Dispose - - This is useful for implementing the RAII pattern in .NET. -* MustUseReturnValueAttribute that is compatible with the [MustUseRetVal](https://github.com/mykolav/must-use-ret-val-fs) - package. -* StringNormalizer extensions to support converting line endings of strings - for interoperability across OS platforms and compatibility with "on disk" representations. -* A custom ValidatedNotNullAttribute to allow compiler to assume a parameter - value is validated as not null. -* DictionaryBuilder to enable dictionary initializer style initialization of - `ImmutableDictionary` with significantly reduced overhead. - - This leverages an `ImmutableDictionary.Builder` under the hood to build - the dictionary. When the `ToImmutable()` method is called the builder is converted to - the immutable state without any overhead of a copy or re-construction of hash tables - etc... -* KvpArrayBuilder to enable initializer style initialization of - `ImmutableArray>` with significantly reduced overhead. - - This leverages an `ImmutableArray.Builder` under the hood to build the array - directly. When the `ToImmutable()` method is called the builder is ***converted*** to - the immutable state without any overhead of a copy. - - Since this is an array and not a dictionary there is no overhead for allocating, - initializing or copying any hash mapping for the keys. - -## Fluent Validation -The library includes extensions that support fluent validation to allow use in property -accessors and constructors that forward values to a base or another constructor. This is -normally used when the value itself isn't passed on but some transformed value is. - -|Method | Description | -|-------|-------------| -|`ThrowIfNull()`| Thows an exception if the argument is null or returns it as-is | -|`ThrowIfOutOfRange()` | Thorws an exception if a value is out of the specified range | -|`ThrowIfNotDefined()` | Throws an exception if an enum value is undefined | - -## Why not PolyFill? -While it isn't impossible to make polyfill for this to work on .NET standard 2.0 it -***is*** a HUGE amount of effort to do so and there's no compelling reason to do so. Roslyn -extensions/VSIX extensions are the most likely candidates left. VS extensions are shifting -to external processes for extensions for this reason, leaving Roslyn generators. The bottom -line is that too much of MODERN .NET and C# is incompatible with .NET Standard 2.0 that a -distinct PolyFill library can only cover some of it. (Even the PolySharp generator covers -only some of the missing functionality) - -## Runtime Dependencies -None - -There are dependencies on various compile time analyzers but no runtime dependencies are -allowed. diff --git a/src/Ubiquity.NET.Extensions/SourcePosition.cs b/src/Ubiquity.NET.Extensions/SourcePosition.cs deleted file mode 100644 index d71460980..000000000 --- a/src/Ubiquity.NET.Extensions/SourcePosition.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Represents a single point position in a source input - /// - /// Positions use a Line numbering similar to most editors that is 1 based.([1..n][0 = uninitialized/unknown]). - /// Column position, also like most editors, use a 0 based value ([0..n-1]). - /// The index, if available, indicates the 0 based index into a theoretical vector of characters - /// for the position. Not all location information includes such a thing so it may not have a value. - /// - [DebuggerDisplay("{DebuggerToString(),nq}")] - public readonly record struct SourcePosition - { - /// Initializes a new instance of the struct. - /// 1 based line number of this position ([1..n][0 = uninitialized/unknown]) - /// 0 based column position of this position ([0..n-1]) - /// 0 based index of this position (Optional) - [SetsRequiredMembers] - public SourcePosition(int line, int column, int? index = default) - { - Line = line; - Column = column; - Index = index; - } - - /// Gets the 0 based index from the start of the source to this position if available - public int? Index { get; init; } - - /// Gets the 0 based column value of this position - public required int Column - { - get; - init - { -#if NET7_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfLessThan(value, 0); -#else - if(value.CompareTo(0) < 0) - { - throw new ArgumentOutOfRangeException(SR.Format(nameof(Resources.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual), nameof(value), value, 0)); - } -#endif - field = value; - } - } - - /// Gets the one based line position of the location - public required int Line - { - get; - init - { -#if NET7_0_OR_GREATER - ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0); -#else - if(value.CompareTo(0) <= 0) - { - throw new ArgumentOutOfRangeException(SR.Format(nameof(Resources.ArgumentOutOfRange_Generic_MustBeGreater), nameof(value), value, 0)); - } -#endif - field = value; - } - } - - /// Produces a string form of this position - /// string form of the position - public override string ToString( ) - { - return $"({Line},{Column})"; - } - - [EditorBrowsable(EditorBrowsableState.Never)] - internal string DebuggerToString() - { - return $"({Line},{Column}); [{Index}]"; - } - } -} diff --git a/src/Ubiquity.NET.Extensions/SourceRange.cs b/src/Ubiquity.NET.Extensions/SourceRange.cs deleted file mode 100644 index 5a5544a54..000000000 --- a/src/Ubiquity.NET.Extensions/SourceRange.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Abstraction to hold a source location range as a pair of values - /// - /// It is possible that some sources do not provide dual points to make a proper range. This supports such - /// "ranges" as a single point that is not sliceable. This allows callers to deal with sources that only - /// contain the start point (Looking at you !). - /// - public readonly record struct SourceRange - : IFormattable - { - /// Initializes a new instance of the struct. - /// Position for the start of the range - /// Position for the end of the line - /// - /// If is then this creates a simple range - /// that cannot slice ( is ). - /// - [SetsRequiredMembers] - public SourceRange( SourcePosition start, SourcePosition end = default ) - { - Start = start; - End = end; - } - - /// Gets the start position of the range - public required SourcePosition Start { get; init; } - - /// Gets the end position of the range - public required SourcePosition End { get; init; } - - /// Gets a value indicating whether this instance can slice an input - /// Attempts to slice an input when this is result in an empty string. - public bool CanSlice => Start.Index.HasValue && End.Index.HasValue; - - /// Gets the length of the range in characters if available - /// This only has a value if is - public int? Length => !CanSlice ? null : End.Index - Start.Index - 1; - - /// - public override string ToString( ) - { - // use runtime default formatting - return ToString("G", CultureInfo.CurrentCulture); - } - - /// - /// - /// Accepted format strings are: - /// "B" for MSBuild format used for Windows build tools. - /// "G" for runtime specific (For Windows, this is the MSBuild format) - /// [Format strings for other runtimes TBD] - /// - public string ToString( string? format, IFormatProvider? formatProvider ) - { - formatProvider ??= CultureInfo.CurrentCulture; - return format switch - { - "M" => FormatMsBuild( formatProvider ), - "G" => FormatRuntime( formatProvider ), - _ => ToString() - }; - } - - /// Forms a slice out of the given read-only span based on this range - /// source text to slice - /// slice representing the characters of this range - /// Thrown when the start or end of the range is not in range of the input source (<0 or >=Length) - /// - /// If is then this returns an empty span. (i.e., does not throw). - /// Slicing is only possible if BOTH the and have an value as - /// it is the index that is used to mark the start and end of the slice. Since various text sources track line positions uniquely - /// they are not reliable for slicing the source. - /// - public ReadOnlySpan Slice(ReadOnlySpan source) - { - return !CanSlice ? [] : source.Slice(Start.Index!.Value, Length!.Value); - } - - /// Implicitly Converts a position to a single point range - /// Position to convert - [SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Implicit operator for public constructor" )] - public static implicit operator SourceRange( SourcePosition p ) => new( p ); - - // SEE: https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-diagnostic-format-for-tasks?view=vs-2022 - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Result is NOT simpler" )] - private string FormatMsBuild( IFormatProvider formatProvider ) - { - if(Start.Line == 0) - { - return string.Empty; - } - -#if NET6_0_OR_GREATER - if(End.Line == 0) - { - return Start.Column == 0 - ? string.Create(formatProvider, $"({Start.Line}") - : string.Create(formatProvider, $"({Start.Line}, {Start.Column})"); - } - else if(End.Line == Start.Line) - { - return string.Create(formatProvider, $"({Start.Line}, {Start.Column}-{End.Column})"); - } - else - { - return Start.Column == 0 && End.Column == 0 - ? string.Create(formatProvider, $"({Start.Line}-{End.Line})") - : string.Create(formatProvider, $"({Start.Line}, {Start.Column}, {End.Line}, {End.Column})"); - } -#else - if(End.Line == 0) - { - return Start.Column == 0 - ? string.Format( formatProvider, "({0})", Start.Line) - : string.Format( formatProvider, "({0}, {1})", Start.Line, Start.Column ); - } - else if(End.Line == Start.Line) - { - return string.Format( formatProvider, "({0}, {1}-{2})", Start.Line, Start.Column, End.Column ); - } - else - { - return Start.Column == 0 && End.Column == 0 - ? string.Format( formatProvider, "({0}-{1})", Start.Line, End.Line ) - : string.Format( formatProvider, "({0}, {1}, {2}, {3})", Start.Line, Start.Column, End.Line, End.Column ); - } -#endif - } - - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Place holder for future work" )] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1108:BlockStatementsMustNotContainEmbeddedComments", Justification = "Reviewed.")] - private string FormatRuntime(IFormatProvider formatProvider) - { - if(OperatingSystem.IsWindows()) - { - return FormatMsBuild(formatProvider); - } - else // TODO: Adjust this to format based on styles of additional runtimes - { - // for now - always use MSBUILD format - return FormatMsBuild(formatProvider); - } - } - } -} diff --git a/src/Ubiquity.NET.Extensions/StringNormalizer.cs b/src/Ubiquity.NET.Extensions/StringNormalizer.cs deleted file mode 100644 index ac2d5d975..000000000 --- a/src/Ubiquity.NET.Extensions/StringNormalizer.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// The kind of line endings for a string of characters - public enum LineEndingKind - { - /// Source line endings are mixed or otherwise unknown. - /// - /// This kind is only allowed as a source description since it does NOT define any - /// particular output format. Only an ambiguous input. - /// - MixedOrUnknownEndings, - - /// Line endings consist of the single line feed character '\n' - /// This is the typical form for *nix systems, and Mac OS X and later - LineFeed, - - /// Line endings consist of a pair of carriage return '\r' AND line feed '\n' - /// This is the canonical form used in Windows environments - CarriageReturnLineFeed, - - /// Line endings consist of the single carriage return character '\r' - /// This is the typical form for older Mac systems - CarriageReturn, - } - -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved - /// Utility class for converting line endings to expected forms - /// This is similar to and - /// except that it allows explicit - /// control of the input AND output forms of the string via . - /// Ultimately all forms of normalization resolves to a call to - /// . - /// -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved - public static partial class StringNormalizer - { - /// Gets a string form of the line ending - /// Kind of line ending to get the string form of - /// String form of the specified line ending - /// Unknown value for - public static string LineEnding( this LineEndingKind kind ) - { - return kind switch - { - LineEndingKind.LineFeed => "\n", - LineEndingKind.CarriageReturnLineFeed => "\r\n", - LineEndingKind.CarriageReturn => "\r", - _ => throw new InvalidEnumArgumentException( nameof( kind ), (int)kind, typeof( LineEndingKind ) ), - }; - } - - /// Gets the system () line ending kind for the current environment - public static LineEndingKind SystemLineEndings => LazySystemKind.Value; - - /// Normalize a managed string with system defined line endings to a specified kind - /// input string to convert - /// destination kind of string to convert - /// Normalized string; If dstKind matches the current system environment then this returns un-modified - /// - /// This is equivalent to a call to with the - /// source kind set to . Thus, it sets all forms of line endings to - /// the kind specified in . - /// - [return: NotNullIfNotNull(nameof(txt))] - public static string? NormalizeLineEndings( this string? txt, LineEndingKind dstKind ) - { - return txt.NormalizeLineEndings( LineEndingKind.MixedOrUnknownEndings, dstKind ); - } - -#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved - /// Converts a string into a string with managed environment line endings - /// string to convert - /// Line ending kind for the source () - /// Line ending kind for the destination (return string) - /// Normalized string; If the is the same as this is returns un-modified - /// - /// Unlike the .NET 6+ runtime provided this does NOT replace - /// ALL forms of line endings unless is . In - /// all other cases it ONLY replaces exact matches for the line endings specified in . - /// -#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved - [return: NotNullIfNotNull(nameof(txt))] - [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Nested conditional are NOT simpler" )] - public static string? NormalizeLineEndings( this string? txt, LineEndingKind srcKind, LineEndingKind dstKind ) - { - if(dstKind == LineEndingKind.MixedOrUnknownEndings) - { - throw new ArgumentException("Mixed line endings is invalid for the destination.", nameof(dstKind)); - } - - // short-circuit for null, empty, or same kind. - if(srcKind == dstKind || string.IsNullOrEmpty( txt )) - { - return txt; - } - -#if NETSTANDARD2_0 - return srcKind == LineEndingKind.MixedOrUnknownEndings - ? txt!.ReplaceLineEndings( dstKind.LineEnding() ) - : txt!.Replace( srcKind.LineEnding(), dstKind.LineEnding() ); -#else - return srcKind == LineEndingKind.MixedOrUnknownEndings - ? txt.ReplaceLineEndings( dstKind.LineEnding() ) - : txt.Replace( srcKind.LineEnding(), dstKind.LineEnding(), StringComparison.Ordinal ); -#endif - } - - // simplifies consistency of exception in face of unknown environment configuration - private static InvalidOperationException UnknownLineEndingsException => new( "Unknown environment line ending kind" ); - - private static readonly Lazy LazySystemKind = new(ComputeSystemLineEndings); - - [SuppressMessage( "Style", "IDE0066:Convert switch statement to expression", Justification = "Far more readable this way" )] - private static LineEndingKind ComputeSystemLineEndings( ) - { - string newLine = Environment.NewLine; - switch(newLine.Length) - { - case 1: - return newLine[ 0 ] switch - { - CR => LineEndingKind.CarriageReturn, - LF => LineEndingKind.LineFeed, - _ => throw UnknownLineEndingsException - }; - - case 2: - return newLine[ 0 ] == CR && newLine[ 1 ] == LF - ? LineEndingKind.CarriageReturnLineFeed - : throw UnknownLineEndingsException; - - default: - throw UnknownLineEndingsException; - } - } - - private const char LF = '\n'; - private const char CR = '\r'; - } -} diff --git a/src/Ubiquity.NET.Extensions/StringSplitOptions2.cs b/src/Ubiquity.NET.Extensions/StringSplitOptions2.cs deleted file mode 100644 index db06a21f4..000000000 --- a/src/Ubiquity.NET.Extensions/StringSplitOptions2.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Extensions -{ - /// Poly fill extensions for the enumeration - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Extension - Broken analyzer" )] - [Flags] - public enum StringSplitOptions2 - { - /// Use the default options when splitting strings. - None = StringSplitOptions.None, - - /// Omit array elements that contain an empty string from the result. - /// - /// If and are specified together, - /// then substrings that consist only of white-space characters are also removed from the result. - /// - RemoveEmptyEntries = StringSplitOptions.RemoveEmptyEntries, - -#if NET5_0_OR_GREATER - /// Trim white-space characters from each substring in the result. - /// - /// If and are specified together, - /// then substrings that consist only of white-space characters are also removed from the result. - /// - TrimEntries = StringSplitOptions.TrimEntries -#else - /// Trim white-space characters from each substring in the result. - /// - /// - /// The official value of this field is available in .NET 5 and later versions only. Unless a method - /// using is explicitily re-written to support , - /// then the functionality for this is not available. - /// - /// - /// If and are specified together, - /// then substrings that consist only of white-space characters are also removed from the result. - /// - /// - TrimEntries = 2, -#endif - } -} diff --git a/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj b/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj deleted file mode 100644 index 752d42abc..000000000 --- a/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - net8.0;netstandard2.0 - - preview - enable - True - True - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - General use .NET extensions and utilities - Extensions,.NET,Ubiquity.NET - ReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - en-US - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - false - Analyzer - - - - - - Resources.resx - True - True - - - - - - Resources.Designer.cs - ResXFileCodeGenerator - - - - - - diff --git a/src/Ubiquity.NET.InteropHelpers.UT/AssemblyInfo.cs b/src/Ubiquity.NET.InteropHelpers.UT/AssemblyInfo.cs deleted file mode 100644 index 4e002590c..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customize this process see: https://aka.ms/assembly-info-properties - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. -[assembly: Guid( "8f035f8b-26ae-462f-ae45-1f0672873747" )] - -[assembly: CLSCompliant( false )] - -[assembly: Parallelize( Scope = ExecutionScope.ClassLevel )] -[assembly: ExcludeFromCodeCoverage] diff --git a/src/Ubiquity.NET.InteropHelpers.UT/CStringHandleTests.cs b/src/Ubiquity.NET.InteropHelpers.UT/CStringHandleTests.cs deleted file mode 100644 index a16d113bc..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/CStringHandleTests.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Threading; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.InteropHelpers.UT -{ - [TestClass] - public class CStringHandleTests - { - [TestMethod] - public void EqualityTests( ) - { - unsafe - { - // need to use a real string as the length is computed for it (Lazily) - var testMsg = "testing1,2,3"u8; - var testMsg2 = "testing1,2,3"u8; - fixed(byte* pMsg = &MemoryMarshal.GetReference( testMsg )) - { - var handle1 = new TestStringHandle(); - var handle2 = new TestStringHandle(); - var handle3 = new TestStringHandle(pMsg); - Assert.IsFalse( handle1.IsClosed ); - Assert.IsFalse( handle2.IsClosed ); - Assert.IsFalse( handle3.IsClosed ); - - Assert.IsTrue( handle1.IsInvalid ); - Assert.IsTrue( handle2.IsInvalid ); - Assert.IsFalse( handle3.IsInvalid ); - - using(handle1) - using(handle2) - using(handle3) - { - Assert.AreEqual( handle1, handle2, "Different instances but NULL value should report value equal" ); - Assert.AreNotEqual( handle1, handle3, "Different instances with different value should NOT report as equal" ); - Assert.IsTrue( handle3.Equals( testMsg2 ), "Should compare equal with same content" ); - } - - // Verify dispose handling is correct. - // Actual pointer is not supposed to change, but it should change state - // and should have called the release method once (for any valid handle) - Assert.IsTrue( handle1.IsClosed ); - Assert.AreEqual( 0ul, handle1.ReleaseCount ); - Assert.AreEqual( nint.Zero, handle1.DangerousGetHandle() ); - - Assert.IsTrue( handle2.IsClosed ); - Assert.AreEqual( 0ul, handle2.ReleaseCount ); - Assert.AreEqual( nint.Zero, handle2.DangerousGetHandle() ); - - Assert.IsTrue( handle3.IsClosed ); - Assert.AreEqual( 1ul, handle3.ReleaseCount ); - Assert.AreEqual( (nint)pMsg, handle3.DangerousGetHandle() ); - } - } - } - - [TestMethod] - public void ReadOnlySpanPropertyTests( ) - { - // need to use a real string as the length is computed for it (Lazily) - var testMsg = "testing1,2,3"u8; - var testMsg2 = "testing1,2,3"u8; - unsafe - { - fixed(byte* pMsg = &MemoryMarshal.GetReference( testMsg )) - { - using var testHandle = new TestStringHandle(pMsg); - Assert.IsTrue( testHandle.ReadOnlySpan.SequenceEqual( testMsg2 ), "span should evaluate as equal to identical content span" ); - } - } - } - - [TestMethod] - public void ToStringTests( ) - { - using var handle1 = new TestStringHandle(); - Assert.IsNull( handle1.ToString(), "invalid handle should produce a null string" ); - - var testMsg = "testing1,2,3"u8; - string testMsgManaged = "testing1,2,3"; - unsafe - { - fixed(byte* pMsg = &MemoryMarshal.GetReference( testMsg )) - { - using var testHandle = new TestStringHandle(pMsg); - Assert.AreEqual( testMsgManaged, testHandle.ToString() ); - } - } - } - - [TestMethod] - [System.Diagnostics.CodeAnalysis.SuppressMessage( "Globalization", "CA1307:Specify StringComparison for clarity", Justification = "Testing API" )] - public void GetHashCodeTests( ) - { - using var invalidHandle = new TestStringHandle(); - Assert.AreEqual( 0, invalidHandle.GetHashCode() ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.CurrentCulture ) ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.CurrentCultureIgnoreCase ) ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.InvariantCulture ) ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.InvariantCultureIgnoreCase ) ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.Ordinal ) ); - Assert.AreEqual( 0, invalidHandle.GetHashCode( StringComparison.OrdinalIgnoreCase ) ); - - var testMsg = "Testing1,2,3"u8; - string testMsgManaged = "Testing1,2,3"; - unsafe - { - fixed(byte* pMsg = &MemoryMarshal.GetReference( testMsg )) - { - using var testHandle = new TestStringHandle(pMsg); - Assert.AreEqual( testMsgManaged.GetHashCode(), testHandle.GetHashCode() ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.CurrentCulture ), testHandle.GetHashCode( StringComparison.CurrentCulture ) ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.CurrentCultureIgnoreCase ), testHandle.GetHashCode( StringComparison.CurrentCultureIgnoreCase ) ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.InvariantCulture ), testHandle.GetHashCode( StringComparison.InvariantCulture ) ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.InvariantCultureIgnoreCase ), testHandle.GetHashCode( StringComparison.InvariantCultureIgnoreCase ) ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.Ordinal ), testHandle.GetHashCode( StringComparison.Ordinal ) ); - Assert.AreEqual( testMsgManaged.GetHashCode( StringComparison.OrdinalIgnoreCase ), testHandle.GetHashCode( StringComparison.OrdinalIgnoreCase ) ); - } - } - } - } - - // Internal type to test abstract base type - [SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "It's file scoped - https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803" )] - file class TestStringHandle - : CStringHandle - { - public TestStringHandle( ) - : base() - { - } - - public unsafe TestStringHandle( byte* p ) - : base( p ) - { - } - - public TestStringHandle( nint p ) - : base( p ) - { - } - - public UInt64 ReleaseCount => ReleaseCountField; - - protected override bool ReleaseHandle( ) - { - Interlocked.Increment( ref ReleaseCountField ); - return true; - } - - private UInt64 ReleaseCountField; - } -} diff --git a/src/Ubiquity.NET.InteropHelpers.UT/EncodingExtensionsTests.cs b/src/Ubiquity.NET.InteropHelpers.UT/EncodingExtensionsTests.cs deleted file mode 100644 index 15a42e3dc..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/EncodingExtensionsTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Runtime.InteropServices; -using System.Text; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.InteropHelpers.UT -{ - [TestClass] - public class EncodingExtensionsTests - { - [TestMethod] - public void MarshalStringTest( ) - { - string? emptyString = EncodingExtensions.MarshalString(Encoding.UTF8, []); - Assert.IsNotNull( emptyString ); - Assert.AreEqual( string.Empty, emptyString ); - - // NOTE: u8 places a null terminator in the allocated space for the string! - // [See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/utf8-string-literals] - var testMsg="Testing 1,2,3"u8; - string expectedManagedString = "Testing 1,2,3"; - unsafe - { - string? nullString = Encoding.UTF8.MarshalString((byte*)null); - Assert.IsNull( nullString ); - - fixed(byte* pMsg = &MemoryMarshal.GetReference( testMsg )) - { - string? managedString = EncodingExtensions.MarshalString(Encoding.UTF8, pMsg); - Assert.IsNotNull( managedString ); - Assert.AreEqual( expectedManagedString, managedString ); - } - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers.UT/GlobalSuppressions.cs b/src/Ubiquity.NET.InteropHelpers.UT/GlobalSuppressions.cs deleted file mode 100644 index 45e810e11..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/GlobalSuppressions.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA0001:XML comment analysis is disabled due to project configuration", Justification = "Sample Application" )] -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1652:Enable XML documentation output", Justification = "Unit Tests" )] -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Unit Tests" )] diff --git a/src/Ubiquity.NET.InteropHelpers.UT/LazyEncodedStringTests.cs b/src/Ubiquity.NET.InteropHelpers.UT/LazyEncodedStringTests.cs deleted file mode 100644 index 5c1976fcc..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/LazyEncodedStringTests.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.InteropHelpers.UT -{ - [TestClass] - public class LazyEncodedStringTests - { - [TestMethod] - public void LazyEncodedStringTest( ) - { - var lazyString = new LazyEncodedString("managed"); - Assert.IsNotNull( lazyString ); - Assert.IsFalse( lazyString.IsEmpty ); - Assert.AreEqual( Encoding.UTF8, lazyString.Encoding ); - } - - [TestMethod] - public void LazyEncodedStringTest1( ) - { - var lazyString = new LazyEncodedString("utf8 Text"u8); - Assert.IsNotNull( lazyString ); - Assert.IsFalse( lazyString.IsEmpty ); - Assert.AreEqual( Encoding.UTF8, lazyString.Encoding ); - } - - [TestMethod] - public void ToStringTest( ) - { - var lazyString = new LazyEncodedString("utf8 Text"u8); - string managed = lazyString.ToString(); - Assert.IsNotNull( managed ); - Assert.AreEqual( "utf8 Text", managed ); - - const string managedText = "managed Text"; - var lazyString2 = new LazyEncodedString(managedText); - string managed2 = lazyString2.ToString(); - Assert.IsNotNull( managed2 ); - Assert.AreEqual( managedText, managed2 ); - } - - [TestMethod] - public void ToReadOnlySpanTest( ) - { - const string managedText = "Some Text"; - var utf8Span = "Some Text"u8; - - var lazyString = new LazyEncodedString(utf8Span); - var containedSpan = lazyString.ToReadOnlySpan(); - Assert.IsTrue( utf8Span.SequenceEqual( containedSpan ) ); - - var lazyString2 = new LazyEncodedString(managedText); - containedSpan = lazyString2.ToReadOnlySpan(); - Assert.IsTrue( utf8Span.SequenceEqual( containedSpan ) ); - } - - [TestMethod] - public void PinTest( ) - { - const string managedText = "Some Text"; - var utf8Span = "Some Text"u8; - - var lazyString = new LazyEncodedString(utf8Span); - using(var memHandle = lazyString.Pin()) - { - unsafe - { - var span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memHandle.Pointer); - Assert.IsTrue( utf8Span.SequenceEqual( span ) ); - } - } - - lazyString = new LazyEncodedString( managedText ); - using(var memHandle = lazyString.Pin()) - { - unsafe - { - var span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memHandle.Pointer); - Assert.IsTrue( utf8Span.SequenceEqual( span ) ); - } - } - } - - /* - Test matrix for this: - # | lhs | rhs | Result - --|---------|---------|----------- - 1 | null | null | Always equal - 2 | null | managed | Never equal - 3 | null | native | Never equal - 4 | managed | null | Never equal - 5 | managed | managed | Equal if contents of string are equal (ordinal compare) - 6 | managed | native | Equal if contents of string are equal (ordinal compare) - 7 | native | null | Never equal - 8 | native | managed | Equal if contents of string are equal (ordinal compare) - 9 | native | native | Equal if contents of native array are equal - */ - [TestMethod] - public void EqualsTest( ) - { - LazyEncodedString? nullString1 = null; - LazyEncodedString? nullString2 = null; - LazyEncodedString managed1 = "tEst one"; - LazyEncodedString native1 = "tEst one"u8; - LazyEncodedString managed1Rhs = "tEst one"; - LazyEncodedString native1Rhs = "tEst one"u8; - - LazyEncodedString managed2 = "tEst two"; - LazyEncodedString native2 = "tEst two"u8; - LazyEncodedString managed2Rhs = "tEst two"; - LazyEncodedString native2Rhs = "tEst two"u8; - - // case 1: - Assert.IsTrue( nullString1 == nullString2 ); - Assert.IsTrue( Equals( nullString1, nullString1 ) ); -#pragma warning disable SA1131 // Use readable conditions - // Order is intentional to test specific operator call - Assert.IsTrue( null == nullString1 ); -#pragma warning restore SA1131 // Use readable conditions - Assert.IsTrue( Equals( null, nullString1 ) ); - Assert.IsTrue( nullString1 == null ); - Assert.IsTrue( Equals( nullString1, null ) ); - - // case 2: - Assert.IsFalse( nullString1 == managed1 ); - Assert.IsFalse( Equals( nullString1, managed1 ) ); - - // case 3: - Assert.IsFalse( nullString1 == managed1 ); - Assert.IsFalse( Equals( nullString1, native1 ) ); - - // case 4: - Assert.IsFalse( managed1 == nullString1 ); - Assert.IsFalse( Equals( managed1, nullString1 ) ); - - // case 5: - Assert.IsFalse( managed1 == managed2 ); - Assert.IsFalse( managed1.Equals( managed2 ) ); - Assert.IsTrue( managed1 == managed1Rhs ); - Assert.IsTrue( managed1.Equals( managed1Rhs ) ); - - // case 6: - Assert.IsFalse( managed1 == native2 ); - Assert.IsFalse( managed1.Equals( native2 ) ); - Assert.IsTrue( managed1 == native1 ); - Assert.IsTrue( managed1.Equals( native1 ) ); - - // case 7: - Assert.IsFalse( native1 == nullString1 ); - Assert.IsFalse( Equals( native1, nullString1 ) ); - - // case 8: - Assert.IsFalse( native2 == managed1 ); - Assert.IsFalse( native2.Equals( managed1 ) ); - Assert.IsTrue( native1 == managed1 ); - Assert.IsTrue( native1.Equals( managed1 ) ); - - // case 9: - Assert.IsFalse( native1 == native2 ); - Assert.IsFalse( native1.Equals( native2 ) ); - Assert.IsTrue( native1 == native1Rhs ); - Assert.IsTrue( native1.Equals( native1Rhs ) ); - } - - [TestMethod] - public void GetHashCodeTest( ) - { - LazyEncodedString managed1 = "tEst one"; - LazyEncodedString native1 = "tEst one"u8; - Assert.AreNotEqual( 0, managed1.GetHashCode() ); - Assert.AreNotEqual( 0, native1.GetHashCode() ); - Assert.AreEqual( managed1.GetHashCode(), native1.GetHashCode() ); - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers.UT/ModuleFixtures.cs b/src/Ubiquity.NET.InteropHelpers.UT/ModuleFixtures.cs deleted file mode 100644 index f04b29722..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/ModuleFixtures.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.InteropHelpers.UT -{ - // Provides common location for one time initialization for all tests in this assembly - [TestClass] - public static class ModuleFixtures - { - [AssemblyInitialize] - public static void AssemblyInitialize( TestContext ctx ) - { - ArgumentNullException.ThrowIfNull( ctx ); - } - - [AssemblyCleanup] - public static void AssemblyCleanup( ) - { - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers.UT/StringMarshallingTests.cs b/src/Ubiquity.NET.InteropHelpers.UT/StringMarshallingTests.cs deleted file mode 100644 index 58351172b..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/StringMarshallingTests.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.InteropHelpers.UT -{ - // TODO: Test encoding handling - [TestClass] - public class StringMarshallingTests - { - [TestMethod] - [SuppressMessage( "Usage", "MSTEST0037:Use proper 'Assert' methods", Justification = "Broken analyzer, suggested API does NOT handle unsafe pointer types" )] - [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "It's a pointer - chill" )] - public void TestConvertToUnmanagedDefault( ) - { - unsafe - { - byte* pNativeString = null; - try - { - pNativeString = ExecutionEncodingStringMarshaller.ConvertToUnmanaged( "123" ); - Assert.IsTrue( pNativeString is not null ); - Assert.AreEqual( (byte)0x31, pNativeString[ 0 ] ); - Assert.AreEqual( (byte)0x32, pNativeString[ 1 ] ); - Assert.AreEqual( (byte)0x33, pNativeString[ 2 ] ); - Assert.AreEqual( 0, pNativeString[ 3 ] ); - } - finally - { - if(pNativeString is not null) - { - ExecutionEncodingStringMarshaller.Free( pNativeString ); - } - } - } - } - - [TestMethod] - [SkipLocalsInit] - public void TestConvertToManagedDefault( ) - { - unsafe - { - byte* nativeBytes = stackalloc byte[4]; - nativeBytes[ 0 ] = 0x31; - nativeBytes[ 1 ] = 0x32; - nativeBytes[ 2 ] = 0x33; - nativeBytes[ 3 ] = 0; - - string? managed = ExecutionEncodingStringMarshaller.ConvertToManaged(nativeBytes); - Assert.IsNotNull( managed ); - Assert.AreEqual( 3, managed.Length ); - Assert.AreEqual( "123", managed ); - } - } - - [TestMethod] - [SuppressMessage( "Usage", "MSTEST0037:Use proper 'Assert' methods", Justification = "Broken analyzer, suggested API does NOT handle unsafe pointer types" )] - [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "It's a pointer - chill" )] - [SkipLocalsInitAttribute] - public void TestConVersionToManagedWithInSemantics( ) - { - // simulate the general flow of code used by the LibraryImportAttribute source generator - unsafe - { - ExecutionEncodingStringMarshaller.ManagedToUnmanagedIn marshaller = default; - try - { -#pragma warning disable CS9081 // A result of a stackalloc expression of this type in this context may be exposed outside of the containing method - // It isn't exposed and this is EXACTLY what the source generator does! - marshaller.FromManaged( "123", stackalloc byte[ ExecutionEncodingStringMarshaller.ManagedToUnmanagedIn.BufferSize ] ); -#pragma warning restore CS9081 // A result of a stackalloc expression of this type in this context may be exposed outside of the containing method - - byte* pNativeString = marshaller.ToUnmanaged(); - Assert.IsTrue( pNativeString is not null ); - Assert.AreEqual( (byte)0x31, pNativeString[ 0 ] ); - Assert.AreEqual( (byte)0x32, pNativeString[ 1 ] ); - Assert.AreEqual( (byte)0x33, pNativeString[ 2 ] ); - Assert.AreEqual( 0, pNativeString[ 3 ] ); - } - finally - { - marshaller.Free(); - } - } - } - - [TestMethod] - [SuppressMessage( "Usage", "MSTEST0037:Use proper 'Assert' methods", Justification = "Broken analyzer, suggested API does NOT handle unsafe pointer types" )] - [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "It's a pointer - chill" )] - [SkipLocalsInitAttribute] - public void TestConVersionToManagedWithInSemanticsAllocated( ) - { - // simulate the general flow of code used by the LibraryImportAttribute source generator - unsafe - { - ExecutionEncodingStringMarshaller.ManagedToUnmanagedIn marshaller = default; - try - { - // use a default span as the input so the size is 0, which forces the marshaller to perform internal allocation - // and free is relevant (and really required here) - marshaller.FromManaged( "123", default ); - - byte* pNativeString = marshaller.ToUnmanaged(); - Assert.IsTrue( pNativeString is not null ); - Assert.AreEqual( (byte)0x31, pNativeString[ 0 ] ); - Assert.AreEqual( (byte)0x32, pNativeString[ 1 ] ); - Assert.AreEqual( (byte)0x33, pNativeString[ 2 ] ); - Assert.AreEqual( 0, pNativeString[ 3 ] ); - } - finally - { - marshaller.Free(); - } - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers.UT/Ubiquity.NET.InteropHelpers.UT.csproj b/src/Ubiquity.NET.InteropHelpers.UT/Ubiquity.NET.InteropHelpers.UT.csproj deleted file mode 100644 index 8df5c22ab..000000000 --- a/src/Ubiquity.NET.InteropHelpers.UT/Ubiquity.NET.InteropHelpers.UT.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - net8.0 - - preview - enable - - false - True - - - - - - - - - - diff --git a/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs b/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs deleted file mode 100644 index 3966f9b41..000000000 --- a/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - /// Abstract base class for types that represents a native string - /// - /// This base class provides most of the functionality for a string pointer except - /// the disposal/release of the string (if Any). That is left to derived types to - /// provide the specific operation to release the pointer. In particular this provides - /// a simple copy by value marshalling and there is no copy made nor any marshalling to - /// managed code overhead until is called. In particular, - /// will NOT make a managed copy. The returned span is to the - /// original unmanaged memory. - /// - public abstract class CStringHandle - : SafeHandle - , IEquatable - { - /// - public override bool IsInvalid => handle == IntPtr.Zero; - - /// Gets a readonly span for the data in this string - /// Span of the ANSI characters in this string (as byte) - /// - /// This does NOT make a managed copy of the underlying string memory. Instead - /// the returned span refers directly to the unmanaged memory of the string. - /// - public ReadOnlySpan ReadOnlySpan - { - get - { - ObjectDisposedException.ThrowIf( IsClosed, this ); - unsafe - { - return new( (void*)handle, LazyStrLen.Value ); - } - } - } - - /// Converts the underlying string pointer into a managed string - /// Managed string for the provided pointer - /// - /// The return is a managed string that is equivalent to the string of this pointer. - /// It's lifetime is controlled by the runtime GC. - /// - public override string? ToString( ) - { - ObjectDisposedException.ThrowIf( IsClosed, this ); - return ManagedString.Value; - } - - /// - public override bool Equals( object? obj ) - { - return obj is CStringHandle other - && ((IEquatable)this).Equals( other ); - } - - /// - [SuppressMessage( "Globalization", "CA1307:Specify StringComparison for clarity", Justification = "Matches string API" )] - public override int GetHashCode( ) - { - ObjectDisposedException.ThrowIf( IsClosed, this ); - return ToString()?.GetHashCode() ?? 0; - } - -#if !NETSTANDARD2_0 - /// Returns the hash code for this string using the specified rules. - /// One of the enumeration values that specifies the rules to use in the comparison. - /// A 32-bit signed integer hash code. - public int GetHashCode( StringComparison comparisonType ) - { - ObjectDisposedException.ThrowIf( IsClosed, this ); - return ToString()?.GetHashCode( comparisonType ) ?? 0; - } -#endif - - /// - public bool Equals( CStringHandle? other ) - { - // perf optimization to skip longer scan if possible (null input or exact same handle value) - return other is not null - && ((handle == other.handle) || Equals( other.ReadOnlySpan )); - } - - /// Tests if the span of characters for this string is identical to the provided span - /// Span of bytes to compare this string to - /// if the spans contain the same data or if not - public bool Equals( ReadOnlySpan otherSpan ) - { - return ReadOnlySpan.SequenceEqual( otherSpan ); - } - - /// Initializes a new instance of the class. - protected CStringHandle( ) - : base( IntPtr.Zero, ownsHandle: true ) - { - unsafe - { - ManagedString = new( ( ) => ExecutionEncodingStringMarshaller.ConvertToManaged( (byte*)handle ), LazyThreadSafetyMode.ExecutionAndPublication ); - LazyStrLen = new( ( ) => MemoryMarshal.CreateReadOnlySpanFromNullTerminated( (byte*)handle ).Length, LazyThreadSafetyMode.ExecutionAndPublication ); - } - } - - /// Initializes a new instance of the class. - /// Native unwrapped handle - protected CStringHandle( nint p ) - : this() - { - SetHandle( p ); - } - - /// Initializes a new instance of the class. - /// native string pointer - protected unsafe CStringHandle( byte* p ) - : this( (nint)p ) - { - } - - private readonly Lazy ManagedString; - private readonly Lazy LazyStrLen; // count of bytes in the native string (Not including null terminator) - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs b/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs deleted file mode 100644 index 76c114540..000000000 --- a/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - /// Utility extensions for the class - /// - /// These differ from the APIs already present on with - /// respect to handling of and empty spans. Specifically, - /// these implementation don't throw an exception in such a case. They just - /// return when given that as the input. That is, if given - /// these will return so that the - /// resulting value mirrors the native value. This is especially important in scenarios - /// where a value has a distinct meaning from an empty string. - /// - public static class EncodingExtensions - { -#if NETSTANDARD2_0 - /// Encodes into a span of bytes a set of characters from the specified read-only span. - /// Encoding to extend - /// The span containing the set of characters to encode. - /// The byte span to hold the encoded bytes. - /// The number of encoded bytes. - [SuppressMessage( "StyleCop.CSharp.LayoutRules", "SA1519:Braces should not be omitted from multi-line child statement", Justification = "multiple fixed statements" )] - internal static int GetBytes( this Encoding self, ReadOnlySpan chars, Span bytes ) - { - unsafe - { - fixed(Char* pChar = chars) - fixed(byte* pBytes = bytes) - { - return self.GetBytes(pChar, chars.Length, pBytes, bytes.Length); - } - } - } -#endif - - /// Provides conversion of a span of bytes to managed code - /// The encoding to use for conversion - /// Input span to convert with or without a null terminator. - /// string containing the decoded characters from the input - /// - /// If the input is empty, then this returns . - /// - /// is - public static string? MarshalString( this Encoding self, ReadOnlySpan span ) - { - ArgumentNullException.ThrowIfNull( self ); - - return span.IsEmpty - ? string.Empty // optimization for empty spans - : self.GetString( span[ ^1 ] == 0 ? span[ ..^1 ] : span ); // drop the null terminator if there is one. - } - - /// Provides conversion of the native bytes to managed code - /// The encoding to use for conversion - /// pointer to the native code string to convert - /// string containing the decoded characters from the input - /// - /// If the input is , then this returns . - /// - /// is - public static unsafe string? MarshalString( this Encoding self, byte* nativeStringPtr ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(nativeStringPtr is null) - { - return null; - } - - var span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated( nativeStringPtr ); - return MarshalString( self, span ); - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/ExecutionEncodingStringMarshaller.cs b/src/Ubiquity.NET.InteropHelpers/ExecutionEncodingStringMarshaller.cs deleted file mode 100644 index ea439eb0b..000000000 --- a/src/Ubiquity.NET.InteropHelpers/ExecutionEncodingStringMarshaller.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. -namespace Ubiquity.NET.InteropHelpers -{ - /// Represents a marshaller for native strings using . - /// - /// This will handle marshalling of s to/from native code. This is very similar - /// to the default string marshalling support except that it does all conversion to/from native code via - /// a static property, [defaults to UTF8] so that applications can have control of that. - /// -#if !NETSTANDARD2_0 - [CustomMarshaller( typeof( string ), MarshalMode.Default, typeof( ExecutionEncodingStringMarshaller ) )] - [CustomMarshaller( typeof( string ), MarshalMode.ManagedToUnmanagedIn, typeof( ManagedToUnmanagedIn ) )] -#endif - public static unsafe class ExecutionEncodingStringMarshaller - { - /// Gets or sets the ExecutionEncoding for the native code - /// - /// On Windows and MSVC (.NET Framework) the default encoding is that of the OS/Platform runtime. On .NET Core/Linux - /// UTF8 has reached a level of common standard that you can assume it is the default unless documented otherwise. - /// Not so much on Windows. [To be fair. there's a LOT more legacy client code targeting Windows to contend with.] - /// This often doesn't matter for const strings as they tend to fall in the ASCII/ANSI (Latin1) encoding ranges. - /// However, that isn't guaranteed, which makes for all sorts of "interesting" latent bugs. - /// - /// Even as well documented and well thought out as LLVM is, it remains silent on this point. - /// Spelunking the build system generated for LLVM itself by CMake there is NOTHING to set either the source or - /// execution encoding for Windows, MSVC or any other tool-set that I can see so they seem to be left at defaults. - /// (LLVM is just an example as it was where this library began, but it is not limited to that.) - /// - /// - /// - /// For .NET Core, which includes .NET 5+, is the same as - /// even on Windows. So that is the assumed encoding used here. - /// - /// - public static Encoding Encoding { get; set; } = Encoding.UTF8; - -#if !NETSTANDARD2_0 - /// Creates a ReadOnlySpan from a null terminated native string - /// The null terminated string - /// ReadOnlySpan for the string - /// - /// This does NOT perform any encoding or decoding, just gets the span of the - /// underlying native bytes. - /// - public static ReadOnlySpan ReadOnlySpanFromNullTerminated( byte* nativePtr ) - { - return MemoryMarshal.CreateReadOnlySpanFromNullTerminated( nativePtr ); - } - - /// Converts a string to an unmanaged version. - /// The managed string to convert. - /// An unmanaged string. - public static byte* ConvertToUnmanaged( string? managed ) - { - if(managed is null) - { - return null; - } - - int exactByteCount = Encoding.GetByteCount(managed) + 1; // Includes null terminator - byte* mem = (byte*)NativeMemory.Alloc((nuint)exactByteCount); - Span buffer = new (mem, exactByteCount); - - int numBytes = Encoding.GetBytes(managed, buffer); // Does NOT include null terminator - buffer[ numBytes ] = 0; // now it has the null terminator! 8^) - Debug.Assert( exactByteCount == numBytes + 1, "Mismatched lengths, likely result in bogus native string!" ); - return mem; - } - - /// Frees the memory for the unmanaged string representation allocated by - /// The memory allocated for the unmanaged string. - public static void Free( byte* unmanaged ) - { - NativeMemory.Free( unmanaged ); - } -#endif - - /// Converts an unmanaged string to a managed version. - /// The unmanaged string to convert. - /// A managed string. - public static string? ConvertToManaged( byte* unmanaged ) - { - return Encoding.MarshalString( unmanaged ); - } - -#if !NETSTANDARD2_0 - /// Custom marshaller to marshal a managed string as an unmanaged string using the property for encoding the native string. - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Standard pattern for custom marshalers" )] - public ref struct ManagedToUnmanagedIn - { - /// Gets the default buffer size for optimized marshalling. - public static int BufferSize => 0x100; - - /// Initializes the marshaller with a managed string and requested buffer. - /// The managed string to initialize the marshaller with. - /// A request buffer of at least size . - /// - /// If the buffer is not large enough to handle a native representation of - /// then a new buffer is allocated and is not released until is called to release - /// it. - /// - public void FromManaged( string? managed, Span buffer ) - { - AllocatedByThisMarshaller = false; - - if(managed is null) - { - NativePointer = null; - return; - } - - // >= for null terminator - if(Encoding.GetMaxByteCount( managed.Length ) >= buffer.Length) - { - // Calculate accurate byte count when the provided stack-allocated buffer is not sufficient - int exactByteCount = Encoding.GetByteCount(managed) + 1; // + 1 to include null terminator - if(exactByteCount > buffer.Length) - { - buffer = new Span( (byte*)NativeMemory.Alloc( (nuint)exactByteCount ), exactByteCount ); - AllocatedByThisMarshaller = true; - } - } - - NativePointer = (byte*)Unsafe.AsPointer( ref MemoryMarshal.GetReference( buffer ) ); - - int numBytes = Encoding.GetBytes(managed, buffer); - buffer[ numBytes ] = 0; // Enforce termination assumption in native code - } - - /// Converts the current managed string to an unmanaged string. - /// The converted unmanaged string. - public readonly byte* ToUnmanaged( ) => NativePointer; - - /// Frees any allocated unmanaged string memory. - public readonly void Free( ) - { - if(AllocatedByThisMarshaller) - { - NativeMemory.Free( NativePointer ); - } - } - - private byte* NativePointer; - private bool AllocatedByThisMarshaller; - } -#endif - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/GlobalNamespaceImports.cs b/src/Ubiquity.NET.InteropHelpers/GlobalNamespaceImports.cs deleted file mode 100644 index 6d1bd8f69..000000000 --- a/src/Ubiquity.NET.InteropHelpers/GlobalNamespaceImports.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.Buffers; -global using System.Collections.Generic; -global using System.Diagnostics; -global using System.Diagnostics.CodeAnalysis; -global using System.Numerics; -global using System.Reflection; -global using System.Resources; -global using System.Runtime.CompilerServices; -global using System.Runtime.InteropServices; - -#if !NETSTANDARD2_0 -global using System.Runtime.InteropServices.Marshalling; -#endif -global using System.Text; -global using System.Threading; - -global using Ubiquity.NET.Extensions; diff --git a/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs b/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs deleted file mode 100644 index 6b98f0c24..000000000 --- a/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - /// Lazily encoded string with implicit casting to a read only span of bytes or a normal managed string - /// - /// This class handles capturing a managed string or a span of bytes for a native one. It supports a lazily - /// evaluated representation of the string as a sequence of native bytes or a managed string. The encoding ONLY happens - /// once, and ONLY when needed the first time. This reduces the overhead to a onetime hit for any strings that "sometimes" - /// get passed to native code or native strings that "sometimes" get used in managed code as a string. - /// This is essentially a pair of members to handle conversions in one direction. - /// Constructors exist for an existing of bytes and another for a string. Each constructor - /// will pre-initialize one of the lazy values and set up the evaluation function for the other. Thus the string - /// is encoded/decoded ONLY if needed and then, only once. - /// This class handles all the subtle complexity regarding terminators as most of the encoding APIs in .NET will - /// drop/ignore a string terminator but native code usually, but not always, requires it. Thus, this ensures the presence - /// of a terminator even if the span provided to the constructor doesn't include one. (It has to copy the string anyway - /// so why not be nice and robust at the cost of one byte of allocated space) - /// -#if NET7_0_OR_GREATER - [NativeMarshalling( typeof( LazyEncodedStringMarshaller ) )] -#endif - public sealed class LazyEncodedString - : IEquatable - , IEquatable -#if NET7_0_OR_GREATER - , IEqualityOperators -#endif - { - /// Initializes a new instance of the class from an existing managed string - /// string to lazy encode for native code use - /// Encoding to use for the string [Optional: Defaults to if not provided] - public LazyEncodedString( string managed, Encoding? encoding = null ) - { - EncodingCodePage = (encoding ?? Encoding.UTF8).CodePage; - - // Pre-Initialize with the provided string -#if NETSTANDARD2_0 // pre-initialized Lazy supported in .NET Standard 2.1 and all runtimes after that. - ManagedString = new Lazy( ( ) => managed ); -#else - ManagedString = new( managed ); -#endif - NativeBytes = new( GetNativeArrayWithTerminator ); - - unsafe byte[] GetNativeArrayWithTerminator( ) - { - int nativeByteLen = Encoding.GetByteCount(managed) + 1; // +1 for terminator - byte[] retVal = new byte[nativeByteLen]; - - int numBytes = Encoding.GetBytes(ManagedString.Value, retVal); - Debug.Assert( numBytes == nativeByteLen - 1, "Invalid terminator length assumptions!" ); // -1 as numBytes does not account for terminator - retVal[ numBytes ] = 0; // force null termination so it is viable with native code - return retVal; - } - } - - /// Initializes a new instance of the class from an existing span of native bytes - /// span of native bytes - /// Encoding to use for the string [Optional: Defaults to if not provided] - /// This has some performance overhead as it MUST make a copy of the contents of the span. The lifetime - /// of is not guaranteed beyond this call and is not allowed as a field of this type. - /// - public LazyEncodedString( ReadOnlySpan span, Encoding? encoding = null ) - { - EncodingCodePage = (encoding ?? Encoding.UTF8).CodePage; -#if NETSTANDARD2_0 // pre-initialized Lazy only supported in .NET Standard 2.1 and all runtimes after that. - // can't capture span for a lambda, so make array locally. - byte[] tmp = GetNativeArrayWithTerminator( span ); - NativeBytes = new( ( ) => tmp ); -#else - NativeBytes = new( GetNativeArrayWithTerminator( span ) ); -#endif - ManagedString = new( ConvertString, LazyThreadSafetyMode.ExecutionAndPublication ); - - // drop the terminator for conversion to managed so it won't appear in the string - string ConvertString( ) - { - byte[] nativeArray = NativeBytes.Value; - return nativeArray.Length > 0 - ? Encoding.GetString( nativeArray, 0, nativeArray.Length - 1 ) - : string.Empty; - } - - // This incurs the cost of a copy but the lifetime of the span is not known or - // guaranteed beyond this call so it has to make a copy. - static byte[] GetNativeArrayWithTerminator( ReadOnlySpan span ) - { - // If it already has a terminator just use it - if(span.IsEmpty || span[ ^1 ] == 0) - { - return span.ToArray(); - } - - // need to account for terminator so manually allocate and copy the span - byte[] retVal = new byte[span.Length + 1]; - span.CopyTo( retVal ); - retVal[ ^1 ] = 0; // force terminator - return retVal; - } - } - - /// Gets the encoding used for this instance - public Encoding Encoding => Encoding.GetEncoding( EncodingCodePage ); - - /// Gets a value indicating whether this instance represents an empty string - /// Never incurs the cost of conversion - public bool IsEmpty => ManagedString.IsValueCreated - ? ManagedString.Value.Length == 0 - : !NativeBytes.IsValueCreated || NativeBytes.Value.Length == 0; - - /// - /// - /// This will perform conversion if - /// was used to construct this instance and conversion has not yet occurred. Otherwise it will provide the - /// string it was constructed with or a previously converted one. - /// - public override string ToString( ) => ManagedString.Value; - - /// Gets a of bytes for the native encoding of the string - /// Indicates whether the span includes the terminator character [default: false] - /// Span for the encoded bytes of the string - /// - /// This will perform conversion if the - /// was used to construct this instance and conversion has not yet occurred. Otherwise it will provide - /// the span it was constructed with or a previously converted one. - /// - /// When passing a string view (pointer+length) then the terminator is not normally included in the length. - /// When passing a string as a null terminated sequence of characters (pointer) then the null terminator is - /// also not normally included in the length. Thus the default behavior is to not include the terminator. - /// If a span that include any allocated space for a terminator is needed then a value - /// for will result in a span that includes the terminator. - /// - /// - public ReadOnlySpan ToReadOnlySpan( bool includeTerminator = false ) - => new( NativeBytes.Value, 0, checked((int)(includeTerminator ? NativeLength : NativeStrLen)) ); - - /// Pins the native representation of this memory for use in native APIs - /// MemoryHandle that owns the pinned data - public MemoryHandle Pin( ) - { - return NativeBytes.Value.AsMemory().Pin(); - } - - /// - /// - /// May incur the cost of conversion to native if both strings don't have the same - /// form. If both have, a managed representation already that is used, otherwise the - /// native data is used for the comparison. This only performs an ordinal comparison - /// of the values. - /// - public bool Equals( LazyEncodedString? other ) - { - if(other is null) - { - return false; - } - - if(ReferenceEquals( this, other )) - { - return true; - } - - // both have Managed version of string so compare that... - if(ManagedString.IsValueCreated && other.ManagedString.IsValueCreated) - { - return Equals( other.ManagedString.Value ); - } - - // Otherwise at least one has the native form, so use that for both - // this might incur the cost of conversion for one of them - return ToReadOnlySpan().SequenceEqual( other.ToReadOnlySpan() ); - } - - /// - public bool Equals( string? other ) - { - return ManagedString.Value.Equals( other, StringComparison.Ordinal ); - } - - /// - public override bool Equals( object? obj ) - { - return (obj is LazyEncodedString les && Equals( les )) - || (obj is string s && Equals( s )); - } - - /// Gets the native size (in bytes, including the terminator) of the memory for this string - public nuint NativeLength => checked((nuint)NativeBytes.Value.LongLength); - - /// Gets the native length (in bytes, NOT including the terminator) of the native form of the string - public nuint NativeStrLen => NativeLength - 1; - - /// - /// - /// For consistency of the computed hash code value, this will use the managed form of the - /// string. This might incur a one time perf hit to encode it, but that is generally assumed - /// needed when this is called anyway, so not a major hit. - /// - public override int GetHashCode( ) - { - return ManagedString.Value.GetHashCode( StringComparison.Ordinal ); - } - - /// Tests if the given is or Empty - /// string to test - /// if is or Empty - /// - /// This test does NOT have the side effect of performing any conversions. Testing for null or empty - /// is viable on either form directly as-is. - /// - public static bool IsNullOrEmpty( [NotNullWhen( false )] LazyEncodedString? self ) - { - if(self is null) - { - return true; - } - - if(self.ManagedString.IsValueCreated) - { - return string.IsNullOrEmpty( self.ManagedString.Value ); - } - else if(self.NativeBytes.IsValueCreated) - { - return self.NativeStrLen == 0; - } - - return false; - } - - /// Gets a value indicating if the provided instance is or all whitespace - /// instance to check - /// if is or all whitespace - /// - /// This might have the performance overhead of converting the native representation to managed in order to perform - /// the test. - /// - public static bool IsNullOrWhiteSpace( [NotNullWhen( false )] LazyEncodedString? self ) - { - // easy check first as secondary level might require conversion to a managed string - // for detection of whitespace. - return self is null - || string.IsNullOrWhiteSpace( self.ManagedString.Value ); - } - - /// Creates a nullable from an unmanaged view - /// pointer to first character of view - /// length of the view - /// - /// - /// To preserve the potential meaning distinction between null and empty strings, this will treat a - /// for as a return value. Empty, - /// strings are a . - /// - /// This method handles safely converting (down casting) the length to an as required - /// by .NET runtime types. [ is used as managed equivalent of size_t, though the formal C/C++ - /// definition is compiler/platform specific. Most use an unsigned 64 bit value for a 64 bit platform and an - /// unsigned 32 bit value for 32 bit platforms.] - /// - /// - public static unsafe LazyEncodedString? FromUnmanaged( byte* p, nuint len ) - { - if(p is null) - { - return null; - } - - // attempt to convert all empty strings to same instance to reduce - // pressure on GC Heap. - var span = new ReadOnlySpan(p, checked((int)len)); - return span.IsEmpty ? Empty : new( span ); - } - - /// Creates a nullable from an unmanaged string pointer (terminated!) - /// pointer to first character of string - /// - /// - /// To preserve the potential meaning distinction between and empty strings, this will treat a - /// for as a return value. Empty, - /// strings are always . - /// - [return: NotNullIfNotNull( nameof( p ) )] - public static unsafe LazyEncodedString? FromUnmanaged( byte* p ) - { - if(p == null) - { - // until: https://github.com/dotnet/roslyn/issues/78550 is fixed, have to do the ugly suppression -#pragma warning disable CS8825 // Return value must be non-null because parameter is non-null. - return null; -#pragma warning restore CS8825 // Return value must be non-null because parameter is non-null. - } - - // attempt to convert all empty strings to same instance to reduce - // pressure on GC Heap. - var span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(p); - return span.IsEmpty ? Empty : new( span ); - } - -#if NETSTANDARD2_0 - /// - /// with the joined result - /// - /// This will join the managed form of any LazyEncodedString (Technically the results - /// of calling on each value provided) to produce a final - /// joined string. The result will only have the managed form created but will lazily - /// provide the managed form if/when needed (conversion still only happens once). - /// -#else - /// - /// with the joined result - /// - /// This will join the managed form of any LazyEncodedString (Technically the results - /// of calling on each value provided) to produce a final - /// joined string. The result will only have the managed form created but will lazily - /// provide the managed form if/when needed (conversion still only happens once). - /// -#endif - public static LazyEncodedString Join( char separator, params IEnumerable values ) - { - return new( string.Join( separator, values ) ); - } - - /// Specialized join that optimizes for values - /// Separator character - /// Values to join together - /// Result of the joined set of values - public static LazyEncodedString Join( char separator, params IEnumerable values ) - { - // TODO: Optimize this to deal with only the native UTF8 form. - // Though "optimize" is a bit of a toss up. The normal case is that ALL of the values - // are in the same form. If not, things get murky about any actual benefits of optimizations. - // If all are already in native form then this could convert the separator and use that character - // to join the contents of the native arrays. - // for now just do the unoptimized variant. - return new( string.Join( separator, values ) ); - } - - /// Gets a representation of an empty string - public static LazyEncodedString Empty { get; } = new( string.Empty ); - - /// Converts a managed string into a - /// Input string to convert - /// wrapping - /// - /// If the input is then this will return - /// a to maintain intent and semantics that - /// may not have the same meaning as an empty string. - /// - [return: NotNullIfNotNull( nameof( managed ) )] - public static LazyEncodedString? From( string? managed ) - { - return managed is null ? null : new( managed ); - } - - /// Implicit cast to a string via - /// instance to cast - [return: NotNullIfNotNull( nameof( self ) )] - public static implicit operator string?( LazyEncodedString? self ) - { - return self?.ToString(); - } - - /// Implicit cast to a span via - /// instance to cast - /// - /// The resulting span is a view of the characters that does NOT include the terminating 0 - /// - public static implicit operator ReadOnlySpan( LazyEncodedString self ) - { - ArgumentNullException.ThrowIfNull( self ); - - return self.ToReadOnlySpan(); - } - - /// Convenient implicit conversion of a managed string into a Lazily encoded string - /// managed string to wrap with lazy encoding support - [SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "It has one, just not the dumb name analyzer wants" )] - [return: NotNullIfNotNull( nameof( managed ) )] - public static implicit operator LazyEncodedString?( string? managed ) => From( managed ); - - /// Convenient implicit conversion of a managed string into a Lazily encoded string - /// Span of UTF8 characters to wrap with lazy encoding support - [SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "It's a convenience wrapper around an existing constructor" )] - public static implicit operator LazyEncodedString( ReadOnlySpan utf8Data ) => new( utf8Data ); - -#if NETSTANDARD2_0 - /// Compares two values to determine equality. - /// The value to compare with . - /// The value to compare with . - /// if left is equal to right; otherwise, . -#else - /// -#endif - public static bool operator ==( LazyEncodedString? left, LazyEncodedString? right ) - { - return ReferenceEquals( left, right ) - || (left is not null && left.Equals( right )); - } - -#if NETSTANDARD2_0 - /// Compares two values to determine equality. - /// The value to compare with . - /// The value to compare with . - /// if left is not equal to right; otherwise, . -#else - /// -#endif - public static bool operator !=( LazyEncodedString? left, LazyEncodedString? right ) - { - return !ReferenceEquals( left, right ) - && (left is null || !left.Equals( right )); - } - - private readonly int EncodingCodePage; - private readonly Lazy ManagedString; - - // The native array MUST include the terminator so it is usable as a fixed pointer in native code - private readonly Lazy NativeBytes; - } - - /// Utility extensions to validate a - /// - /// These are extension methods to allow use of the - /// on the instance. Otherwise there is no way to get the name/expression - /// that is tested. - /// - [SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Tightly coupled extensions" )] - public static class LazyEncodedStringValidators - { - /// Throws an exception if the string is null or empty - /// String to test - /// Argument expression that is calling this test [Normally supplied by compiler] - /// The provided string is empty - /// The provided string is null - public static void ThrowIfNullOrEmpty( this LazyEncodedString? self, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - { - if(LazyEncodedString.IsNullOrEmpty( self )) - { - throw new ArgumentException( "String is null or empty", exp ); - } - } - - /// Throws an exception if the string is null or empty - /// String to test - /// Argument expression that is calling this test [Normally supplied by compiler] - /// The provided string is empty - /// The provided string is null - public static void ThrowIfNullOrWhiteSpace( this LazyEncodedString? self, [CallerArgumentExpression( nameof( self ) )] string? exp = null ) - { - if(LazyEncodedString.IsNullOrWhiteSpace( self )) - { - throw new ArgumentException( "String is null or white space", exp ); - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/LazyEncodedStringMarshaller.cs b/src/Ubiquity.NET.InteropHelpers/LazyEncodedStringMarshaller.cs deleted file mode 100644 index 07d7f1be8..000000000 --- a/src/Ubiquity.NET.InteropHelpers/LazyEncodedStringMarshaller.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. -#if NET7_0_OR_GREATER -namespace Ubiquity.NET.InteropHelpers -{ - /// Represents a marshaller for . - /// - /// This will handle marshalling of s to/from native code. This will - /// use any pre-existing native representation in the string so that there is as little overhead - /// as possible. If conversion has not yet occurred it is done once here. - /// - [CustomMarshaller( typeof( LazyEncodedString ), MarshalMode.ManagedToUnmanagedIn, typeof( ManagedToUnmanagedIn ) )] - [CustomMarshaller( typeof( LazyEncodedString ), MarshalMode.Default, typeof( LazyEncodedStringMarshaller ) )] - public static unsafe class LazyEncodedStringMarshaller - { - /// Converts a native string to a using the default (UTF8) encoding - /// Unmanaged string (in UTF8) - /// Wrapped string with lazy conversion to managed form - public static LazyEncodedString? ConvertToManaged( byte* unmanaged ) - { - return LazyEncodedString.FromUnmanaged( unmanaged ); - } - - // Generally, can't support the [In] direction of [Out,In] - // The initialized "in" array might be marshaled to the native where it is "updated" and then needs marshaling - // again on the return. This is NOT a supported scenario as conversion would need a stateful form to hold - // a pinned element for the call. Thus the generated marshalling would need to hold an array of the pinned - // managed values and unpin them after the call..., which is not an option for array elements. - - /// Converts a to a managed form - /// String to convert or retrieve the native form of - /// managed pointer - /// is not - /// - /// Without state, this version can only convert default values to a - /// return. Any other value results in an exception. - /// - public static unsafe byte* ConvertToUnmanaged( LazyEncodedString? managed ) - { - // NOTE: Care is needed here as the semantics are unknown and it is NOT legit to assume null means empty. - // It might not have that meaning to the caller and this implementation cannot know either way. - // - // Null is convertible without any state so go-ahead with that, anything else requires state. - return managed is null - ? (byte*)null - : throw new NotSupportedException( "non-null element for [Out] scenarios is not supported" ); - } - - /// Stateful custom marshaller to marshal LazyEncodedString as an unmanaged string - /// A stateful marshaller is needed to hold the pinned state of the managed string - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "Standard pattern for custom marshalers" )] - public ref struct ManagedToUnmanagedIn - { - /// Initializes the marshaller with a managed string and requested buffer. - /// The managed string to initialize the marshaller with. - public void FromManaged( LazyEncodedString? managed ) - { - Managed = managed; - } - - /// Converts the current managed string to an unmanaged string. - /// The converted unmanaged string. - public byte* ToUnmanaged( ) - { - if(Managed is null) - { - return null; - } - - Handle.Dispose(); - Handle = Managed.Pin(); - return (byte*)Handle.Pointer; - } - - /// Frees any allocated unmanaged string memory. - public readonly void Free( ) - { - Handle.Dispose(); - } - - [SuppressMessage( "IDisposableAnalyzers.Correctness", "IDISP006:Implement IDisposable", Justification = "Covered by Free() method in pattern" )] - private MemoryHandle Handle; - private LazyEncodedString? Managed; - } - } -} -#endif diff --git a/src/Ubiquity.NET.InteropHelpers/NativeContext.cs b/src/Ubiquity.NET.InteropHelpers/NativeContext.cs deleted file mode 100644 index 3a42e6a5f..000000000 --- a/src/Ubiquity.NET.InteropHelpers/NativeContext.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - // This does NOT use the new C# 14 extension syntax due to several reasons - // 1) Code lens does not work https://github.com/dotnet/roslyn/issues/79006 [Sadly marked as "not planned" - e.g., dead-end] - // 2) MANY analyzers get things wrong and need to be supressed (CA1000, CA1034, and many others [SAxxxx]) - // 3) Many tools (like docfx don't support the new syntax yet) - // 4) No clear support for Caller* attributes ([CallerArgumentExpression(...)]). - // - // Bottom line it's a good idea with an incomplete implementation lacking support - // in the overall ecosystem. Don't use it unless you absolutely have to until all - // of that is sorted out. - - /// Operations and extensions for a Native ABI context - /// - /// To interop with native ABI callbacks it is important to ensure that any - /// context pointer provided is valid when the callback is called by native - /// layers. This type provides support, as extension methods, for doing so using a - /// . The handle is allocated in - /// and then released in . The actual target is - /// obtainable via which is normally used in - /// the callback to get back the original managed object the handle refers to. - /// - /// Since the handle is allocated the GC will keep the object it refers to alive - /// until freed. Thus, the callback can use the object it gets. Normally, - /// is called from a final callback method but it is also possible that the API guarantees - /// only a single call to a provided method where it would release the native context - /// after materializing it to a managed form. (After materialization, normal GC rules - /// apply to the managed instance such that while it is in scope it is not released by - /// the GC) - /// - /// - public static class NativeContext - { - /// Extension method to get a native consumable context pointer for a managed object - /// Type of the managed object - /// Managed object to get the context for - /// Native API consumable "pointer" for a GC handle - /// - /// This creates a from the instance and gets a native ABI - /// pointer from that. The returned value is a native pointer that is convertible - /// back to a handle, which can then allow access to the original . - /// - /// Normally the materialization of a managed target is done via - /// in a callback implementation. Ultimately the allocated handle is freed in a call to . - /// - /// - [MustUseReturnValue] - public static unsafe void* AsNativeContext(this T self) - { - var handle = GCHandle.Alloc( self ); - try - { - return GCHandle.ToIntPtr(handle).ToPointer(); - } - catch - { - handle.Free(); - throw; - } - } - - /// Materializes an instance from a native ABI context as a void* - /// Type of the managed object to materialize - /// Native context - /// Materialized value or if not - /// if the value is succesfully materialized and if not - [MustUseReturnValue] - public static unsafe bool TryFrom(void* ctx, [MaybeNullWhen(false)] out T value) - { - if(ctx is not null && GCHandle.FromIntPtr( (nint)ctx ).Target is T managedInst) - { - value = managedInst; - return true; - } - - value = default; - return false; - } - - /// Releases a native context handle - /// Reference to the context to release - /// - /// This releases the native context AND sets to - /// so that no subsequent releases are not possible (or get an exception). - /// - [SuppressMessage( "Design", "CA1045:Do not pass types by reference", Justification = "Allows controlled reset to null (invalid)" )] - public static unsafe void Release(ref void* ctx) - { - Release(ctx); - ctx = null; - } - - /// Releases the context without resetting it to - /// Context to release - /// - /// This does NOT re-assign the context as it is intended for use from within a call back - /// that performs the release. In such a case any field or property of the containing type - /// should be set to to prevent a double free problem. Use of this - /// method is discouraged as it requires more care, but is sometimes required. - /// - public static unsafe void Release(void* ctx) - { - Debug.Assert(ctx is not null, "Attempting to release a NULL context - something is likely wrong in the caller!"); - - // SAFETY - NOP if null, don't trigger a crash in native code. - // Debugger assert above will trigger in debug builds. - if(ctx is not null) - { - GCHandle.FromIntPtr((nint)ctx) - .Free(); - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/NativeLibraryHandle.cs b/src/Ubiquity.NET.InteropHelpers/NativeLibraryHandle.cs deleted file mode 100644 index 198fd2915..000000000 --- a/src/Ubiquity.NET.InteropHelpers/NativeLibraryHandle.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#if NET8_0_OR_GREATER // NativeLibrary only exists in later runtimes (.NET 8 is latest under support) -namespace Ubiquity.NET.InteropHelpers -{ - /// Safe handle for a - public sealed class NativeLibraryHandle - : SafeHandle - { - /// Initializes a new instance of the class. - public NativeLibraryHandle( ) - : base( 0, true ) - { - } - - /// - public override bool IsInvalid => handle == 0; - - /// - public static NativeLibraryHandle Load( string libraryName, Assembly assembly, DllImportSearchPath? searchPath ) - { - return new( NativeLibrary.Load( libraryName, assembly, searchPath ) ); - } - - /// Native library handle. - /// -#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) - public static bool TryLoad( string libraryPath, [MaybeNullWhen( false )] out NativeLibraryHandle lib ) - { - if(NativeLibrary.TryLoad( libraryPath, out nint handle )) - { - lib = new NativeLibraryHandle( handle ); - return true; - } - - lib = null; - return false; - } - - /// - /// Native library Handle - public static bool TryLoad( string libraryName, Assembly assembly, DllImportSearchPath? searchPath, [MaybeNullWhen( false )] out NativeLibraryHandle lib ) - { - if(NativeLibrary.TryLoad( libraryName, assembly, searchPath, out nint handle )) - { - lib = new NativeLibraryHandle( handle ); - return true; - } - - lib = null; - return false; - } -#pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) - - /// - protected override bool ReleaseHandle( ) - { - NativeLibrary.Free( handle ); - return true; - } - - private NativeLibraryHandle( nint osHandle ) - : this() - { - SetHandle( osHandle ); - } - } -} -#endif diff --git a/src/Ubiquity.NET.InteropHelpers/Properties/AssemblyInfo.cs b/src/Ubiquity.NET.InteropHelpers/Properties/AssemblyInfo.cs deleted file mode 100644 index eb5aba066..000000000 --- a/src/Ubiquity.NET.InteropHelpers/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customize this process see: https://aka.ms/assembly-info-properties - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. -[assembly: ComVisible( false )] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. -[assembly: Guid( "e5a59f35-9f8d-4d43-b766-74117c1f99ed" )] - -[assembly: CLSCompliant( false )] - -[assembly: NeutralResourcesLanguage( "en" )] diff --git a/src/Ubiquity.NET.InteropHelpers/Properties/Resources.Designer.cs b/src/Ubiquity.NET.InteropHelpers/Properties/Resources.Designer.cs deleted file mode 100644 index d43bde6f7..000000000 --- a/src/Ubiquity.NET.InteropHelpers/Properties/Resources.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Ubiquity.NET.InteropHelpers.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ubiquity.NET.InteropHelpers.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/Properties/Resources.resx b/src/Ubiquity.NET.InteropHelpers/Properties/Resources.resx deleted file mode 100644 index 1af7de150..000000000 --- a/src/Ubiquity.NET.InteropHelpers/Properties/Resources.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/src/Ubiquity.NET.InteropHelpers/Readme.md b/src/Ubiquity.NET.InteropHelpers/Readme.md deleted file mode 100644 index 71cf94c5e..000000000 --- a/src/Ubiquity.NET.InteropHelpers/Readme.md +++ /dev/null @@ -1,27 +0,0 @@ -# About -Ubiquity.NET.InteropHelpers helper support common to low level interop libraries. While -this library is intended to support the Ubiquity.NET.Llvm interop requirements there isn't -anything bound to that library in the support here. That is it is independent and a useful -library for any code base providing interop support. - -# Key Features -* String handling - * A lot of interop deals with strings in some form or another and handling them - is a major amount of effort for most interop libraries. The support provided here - enables lazy evaluation/marshalling and encoding of native strings and managed - strings. These allow a simple `byte[]` to store a native string and ONLY marshals to a - UTF16 managed string once when needed. This allows storing and passing strings in - their native form for FAST retrieval from a native call and then providing that same - string as an `in` parameter in another call. All without the need to marshal from - native and then back again just for the call. This is a MAJOR performance enhancement - for APIs that deal in strings. -* Delegates and NativeCallbacks as Function pointers - * Function pointers are a new feature of C# that makes for very high performance interop - scenarios. However, sometimes the callback for a function pointer actually needs - additional data not part of the parameters of the function to work properly. This - library provides support for such scenarios where a delegate is used to "capture" the - data while still supporting AOT scenarios. (NOTE: - `Marshal.GetFunctionPointerForDelegate()` must dynamically emit a thunk that contains - the proper signature and the captured "this" pointer so is NOT AOT friendly) The - support offered in this library, though a bit more tedious, is AOT friendly. - diff --git a/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs b/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs deleted file mode 100644 index 904c42465..000000000 --- a/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - /// Performs custom marshalling of handle arrays as in parameters - /// - /// Sadly, the built-in support for safe handles doesn't include arrays of the elements - /// as `in` parameters while still retaining ownership (That is, `ref` semantics). Worse, - /// the documentation for source generator custom marshallers (especially for arrays) - /// is so poor that it wasn't plausible to implement this support as a custom marshaller. - /// Instead these APIs are declared to simplify and control the marshalling as safely - /// as possible. Callers must use either - /// or - /// to allocate, build, call an operation delegate, and then release the native array - /// [optionally returning a result]. That is, the hard and tedious work of allocating, - /// copying the managed array and pinning the array for native consumption is ALL handled - /// in the methods provided by this class. - /// - public static class RefHandleMarshaller - { - // TODO: Support APIs that need to accept more than one such array... - - /// Delegate for an operation with marshaled memory - /// Return value of the operation - /// Pointer to a pinned array of raw handles - /// Number of handles in the array - /// Result of the operation - public unsafe delegate TRetVal ReturningOp( nint* nativeArrayPtr, int size ); - - /// Delegate for an operation with marshaled memory - /// Pointer to a pinned array of raw handles - /// Number of handles in the array - public unsafe delegate void VoidOp( nint* nativeArrayPtr, int size ); - - /// Marshals an array SafeHandle to a native pointer (as an array of nint) - /// type to marshal - /// Return type of the operation - /// Managed array of handles to marshal (by reference) - /// Operation to perform with the native array. - /// Value returned from - /// - /// A native nint holding array is allocated, then the managed array's handle values are - /// copied to it (Without any AddRefs etc... the managed array OWNS the handles) before - /// pinning the memory for the native handles and calling - /// - public static TRetVal WithNativePointer( this THandle[] managedArray, ReturningOp op ) - where THandle : SafeHandle - { - ArgumentNullException.ThrowIfNull( managedArray ); - ArgumentNullException.ThrowIfNull( op ); - - unsafe - { - using var nativeArray = AllocateNativeSpace(managedArray.Length); - FillNative( nativeArray.Memory, managedArray ); - - using var pinnedHandle = nativeArray.Memory.Pin(); - return op( (nint*)pinnedHandle.Pointer, managedArray.Length ); - } - } - - /// Marshals an array SafeHandle to a native pointer (as an array of nint) - /// type to marshal - /// Managed array of handles to marshal (by reference) - /// Operation to perform with the native array. - /// - public static void WithNativePointer( this THandle[] managedArray, VoidOp op ) - where THandle : SafeHandle - { - ArgumentNullException.ThrowIfNull( managedArray ); - ArgumentNullException.ThrowIfNull( op ); - - unsafe - { - using var nativeArray = AllocateNativeSpace(managedArray.Length); - FillNative( nativeArray.Memory, managedArray ); - - using var pinnedHandle = nativeArray.Memory.Pin(); - op( (nint*)pinnedHandle.Pointer, managedArray.Length ); - } - } - - private static IMemoryOwner AllocateNativeSpace( int len ) - { - return MemoryPool.Shared.Rent( Unsafe.SizeOf() * len ); - } - - private static void FillNative( Memory nativeSpace, T[] managedArray ) - where T : SafeHandle - { - ArgumentNullException.ThrowIfNull( managedArray ); - ArgumentOutOfRangeException.ThrowIfNotEqual( nativeSpace.Length, managedArray.Length ); - - for(int i = 0; i < managedArray.Length; ++i) - { - nativeSpace.Span[ i ] = managedArray[ i ].DangerousGetHandle(); - } - } - } -} diff --git a/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj b/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj deleted file mode 100644 index 63a90ff97..000000000 --- a/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj +++ /dev/null @@ -1,66 +0,0 @@ - - - net8.0;netstandard2.0 - - preview - enable - - True - False - True - True - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - .NET Low Level Interop support library to aid in native code interop scenarios - Interop,Ubiquity.NET - ReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - diff --git a/src/Ubiquity.NET.InteropHelpers/UnexpectedNullHandleException.cs b/src/Ubiquity.NET.InteropHelpers/UnexpectedNullHandleException.cs deleted file mode 100644 index 71a3518a2..000000000 --- a/src/Ubiquity.NET.InteropHelpers/UnexpectedNullHandleException.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.InteropHelpers -{ - /// Exception thrown when an underlying Native API `handle` is unexpectedly - /// - /// This is generally a non-recoverable error state where the underlying API library is in an inconsistent - /// or otherwise unexpected state. - /// - public sealed class UnexpectedNullHandleException - : InvalidOperationException - { - /// Initializes a new instance of the class. - /// Exception error message - public UnexpectedNullHandleException( string message ) - : base( message ) - { - } - - /// Initializes a new instance of the class. - /// Exception error message - /// Inner Exception - public UnexpectedNullHandleException( string message, Exception innerException ) - : base( message, innerException ) - { - } - - /// Initializes a new instance of the class. - public UnexpectedNullHandleException( ) - { - } - } -} diff --git a/src/Ubiquity.NET.Llvm.JIT.Tests/Ubiquity.NET.Llvm.JIT.Tests.csproj b/src/Ubiquity.NET.Llvm.JIT.Tests/Ubiquity.NET.Llvm.JIT.Tests.csproj index 193f1f759..c9792b8c4 100644 --- a/src/Ubiquity.NET.Llvm.JIT.Tests/Ubiquity.NET.Llvm.JIT.Tests.csproj +++ b/src/Ubiquity.NET.Llvm.JIT.Tests/Ubiquity.NET.Llvm.JIT.Tests.csproj @@ -10,9 +10,9 @@ + - diff --git a/src/Ubiquity.NET.Llvm.Tests/Ubiquity.NET.Llvm.UT.csproj b/src/Ubiquity.NET.Llvm.Tests/Ubiquity.NET.Llvm.UT.csproj index 84f65aad1..9c7d83242 100644 --- a/src/Ubiquity.NET.Llvm.Tests/Ubiquity.NET.Llvm.UT.csproj +++ b/src/Ubiquity.NET.Llvm.Tests/Ubiquity.NET.Llvm.UT.csproj @@ -9,9 +9,9 @@ + - diff --git a/src/Ubiquity.NET.Llvm.slnx b/src/Ubiquity.NET.Llvm.slnx index ed4dcfdfd..2215e2448 100644 --- a/src/Ubiquity.NET.Llvm.slnx +++ b/src/Ubiquity.NET.Llvm.slnx @@ -7,7 +7,6 @@ - @@ -73,25 +72,12 @@ - - - - - - - - - - - - - diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.cs deleted file mode 100644 index 0f3acb4db..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using global::System; -using global::System.Text; - -namespace System.Text -{ - internal static class PolyFillEncodingExtensions - { - public static unsafe string GetString(this Encoding self, ReadOnlySpan bytes) - { - fixed (byte* bytesPtr = bytes) - { - return self.GetString(bytesPtr, bytes.Length); - } - } - } -} diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs deleted file mode 100644 index 476003986..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs +++ /dev/null @@ -1,334 +0,0 @@ -// -#nullable enable - -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// .NET 7 added the various exception static methods for parameter validation -// This will back fill them for earlier versions. -// -// NOTE: C #14 extension keyword support is required to make this work. - -#pragma warning disable IDE0130 // Namespace does not match folder structure - -namespace System -{ - file enum ResourceId - { - None = 0, - Argument_EmptyOrWhiteSpaceString, - ArgumentOutOfRange_Generic_MustBeEqual, - ArgumentOutOfRange_Generic_MustBeGreater, - ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, - ArgumentOutOfRange_Generic_MustBeLess, - ArgumentOutOfRange_Generic_MustBeLessOrEqual, - ArgumentOutOfRange_Generic_MustBeNonNegative, - ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero, - ArgumentOutOfRange_Generic_MustBeNonZero, - ArgumentOutOfRange_Generic_MustBeNotEqual - } - - // Sadly, these are NOT localized messages as the official forms are. - // There is no way, at least no-known way (easy or not) to inject resources - // that would participate in loclization. (If the consumer even does that...) - // The actual strings used are the same as the values in the official runtime - // support so are at least compatible for "en-us". This fakes it to make it - // more readable AND make it easier to shift if a means of injecting resources - // is found. - file static class ResourceIdExtensions - { - internal static string GetResourceString(this ResourceId id) - { - return id switch - { - ResourceId.Argument_EmptyOrWhiteSpaceString => "The value cannot be an empty string or composed entirely of whitespace.", - ResourceId.ArgumentOutOfRange_Generic_MustBeEqual => "{0} ('{1}') must be equal to '{2}'.", - ResourceId.ArgumentOutOfRange_Generic_MustBeGreater => "{0} ('{1}') must be greater than '{2}'.", - ResourceId.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual => "{0} ('{1}') must be greater than or equal to '{2}'.", - ResourceId.ArgumentOutOfRange_Generic_MustBeLess => "{0} ('{1}') must be less than '{2}'.", - ResourceId.ArgumentOutOfRange_Generic_MustBeLessOrEqual => "{0} ('{1}') must be less than or equal to '{2}'.", - ResourceId.ArgumentOutOfRange_Generic_MustBeNonNegative => "{0} ('{1}') must be a non-negative value.", - ResourceId.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero => "{0} ('{1}') must be a non-negative and non-zero value.", - ResourceId.ArgumentOutOfRange_Generic_MustBeNonZero => "{0} ('{1}') must be a non-zero value.", - ResourceId.ArgumentOutOfRange_Generic_MustBeNotEqual => "{0} ('{1}') must not be equal to '{2}'.", - - _ => throw new global::System.ComponentModel.InvalidEnumArgumentException(nameof(id), (int)id, typeof(ResourceId)) - }; - } - } - - /// poly fill extensions for static methods added in .NET 7 - /// - /// This requires support of the C#14 keyword `extension` to work properly. There is - /// no other way to add static methods to non-partial types for source compatibility. - /// Otherwise code cannot use the modern .NET runtime implementations and instead - /// must always use some extension methods, or litter around a LOT of #if/#else/#endif - /// based on the framework version... - /// - internal static class PolyFillExceptionValidators - { - /// Poly fill Extensions for - extension( global::System.ArgumentException ) - { - /// Throw an if a string is m empty, or all whitepsace. - /// input string to test - /// expression or name of the string to test; normally provided by compiler - /// string is m empty, or all whitepsace - public static void ThrowIfNullOrWhiteSpace( - [global::System.Diagnostics.CodeAnalysis.NotNullAttribute] string? argument, - [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = null - ) - { - global::System.ArgumentNullException.ThrowIfNull( argument, paramName ); - - // argument is non-null verified by this, sadly older frameworks don't have - // attributes to declare that. - if(string.IsNullOrWhiteSpace( argument )) - { - throw new global::System.ArgumentException( "The value cannot be an empty string or composed entirely of whitespace.", paramName ); - } - } - } - - /// Poly fill Extensions for - extension( global::System.ArgumentNullException ) - { - /// Throws an aexception if the tested argument is - /// value to test - /// expression for the name of the value; normally provided by compiler - /// is - public static void ThrowIfNull( - [global::System.Diagnostics.CodeAnalysis.NotNullAttribute] object? argument, - [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = default - ) - { - if(argument is null) - { - throw new global::System.ArgumentNullException( paramName ); - } - } - } - - /// Poly fill Extensions for - extension( global::System.ObjectDisposedException ) - { - /// Throws an if is . - /// Condition to determine if the instance is disposed - /// instance that is tested; Used to get type name for exception - /// is - public static void ThrowIf( - [global::System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute( true )] bool condition, - object instance - ) - { - if(condition) - { - throw new global::System.ObjectDisposedException( instance?.GetType().FullName ); - } - } - } - - /// Poly fill Extensions for - extension( global::System.ArgumentOutOfRangeException ) - { - /// Throws an if is equal to . - /// The argument to validate as not equal to . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfEqual( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : IEquatable? - { - if(global::System.Collections.Generic.EqualityComparer.Default.Equals( value, other )) - { - ThrowEqual( value, other, paramName ); - } - } - - /// Throws an if is not equal to . - /// The argument to validate as equal to . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfNotEqual( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : global::System.IEquatable? - { - if(!global::System.Collections.Generic.EqualityComparer.Default.Equals( value, other )) - { - ThrowNotEqual( value, other, paramName ); - } - } - - /// Throws an if is greater than . - /// The argument to validate as less or equal than . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfGreaterThan( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : global::System.IComparable - { - if(value.CompareTo( other ) > 0) - { - ThrowGreater( value, other, paramName ); - } - } - - /// Throws an if is greater than or equal . - /// The argument to validate as less than . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfGreaterThanOrEqual( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : global::System.IComparable - { - if(value.CompareTo( other ) >= 0) - { - ThrowGreaterEqual( value, other, paramName ); - } - } - - /// Throws an if is less than . - /// The argument to validate as greatar than or equal than . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfLessThan( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : global::System.IComparable - { - if(value.CompareTo( other ) < 0) - { - ThrowLess( value, other, paramName ); - } - } - - /// Throws an if is less than or equal . - /// The argument to validate as greatar than than . - /// The value to compare with . - /// The name of the parameter with which corresponds. - public static void ThrowIfLessThanOrEqual( T value, T other, [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null ) - where T : global::System.IComparable - { - if(value.CompareTo( other ) <= 0) - { - ThrowLessEqual( value, other, paramName ); - } - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowZero( T value, string? paramName ) - { - string msg = string.Format( global::System.Globalization.CultureInfo.CurrentCulture - , ResourceId.ArgumentOutOfRange_Generic_MustBeNonNegative.GetResourceString() - , paramName - , value - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowNegative( T value, string? paramName ) - { - string msg = string.Format( global::System.Globalization.CultureInfo.CurrentCulture - , ResourceId.ArgumentOutOfRange_Generic_MustBeNonZero.GetResourceString() - , paramName - , value - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowNegativeOrZero( T value, string? paramName ) - { - string msg = string.Format( global::System.Globalization.CultureInfo.CurrentCulture - , ResourceId.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero.GetResourceString() - , paramName - , value - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowGreater( T value, T other, string? paramName ) - { - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeLessOrEqual.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowGreaterEqual( T value, T other, string? paramName ) - { - - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeLess.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowLess( T value, T other, string? paramName ) - { - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowLessEqual( T value, T other, string? paramName ) - { - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeGreater.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowEqual( T value, T other, string? paramName ) - { - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeNotEqual.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg); - } - - [global::System.Diagnostics.CodeAnalysis.DoesNotReturn] - private static void ThrowNotEqual( T value, T other, string? paramName ) - { - var msg = string.Format( - global::System.Globalization.CultureInfo.CurrentCulture, - ResourceId.ArgumentOutOfRange_Generic_MustBeEqual.GetResourceString(), - paramName, - value, - other - ); - - throw new global::System.ArgumentOutOfRangeException( paramName, value, msg ); - } - } - } -} diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs deleted file mode 100644 index 59818985a..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -#nullable enable - -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#pragma warning disable IDE0130 // Namespace does not match folder structure -#pragma warning disable CS3021 // Type or member does not need a CLSCompliant attribute because the assembly does not have a CLSCompliant attribute - -namespace System.Runtime.InteropServices -{ - /// Extensions for to allow downlevel compatibility - internal static class PolyFillMemoryMarshal - { - extension(global::System.Runtime.InteropServices.MemoryMarshal) - { - /// Creates a new read-only span for a null-terminated UTF-8 string. - /// The pointer to the null-terminated string of bytes. - /// A read-only span representing the specified null-terminated string, or an empty span if the pointer is null. - /// The returned span does not include the null terminator, nor does it validate the well-formedness of the UTF-8 data. - /// The string is longer than . - [global::System.CLSCompliant(false)] - public static unsafe global::System.ReadOnlySpan CreateReadOnlySpanFromNullTerminated(byte* value) - { - return value != null - ? new global::System.ReadOnlySpan(value, StrLen(value)) - : default; - } - } - - private static unsafe int StrLen(byte* p) - { - // Crude but functional - definately NOT perf optimized. - int indexOfTerminator = 0; - for(; *p != 0; ++p, ++indexOfTerminator) - { - } - - return indexOfTerminator; - } - } -} diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs deleted file mode 100644 index c7b1a834f..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -#nullable enable - -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#pragma warning disable IDE0130 // Namespace does not match folder structure - -namespace System -{ - /// Poly fill extensions for - /// - [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034: Nested types should not be visible", Justification = "extension, broken analyzer")] - internal static class PolyFillOperatingSystem - { - /// Poly fill Extensions for - extension(global::System.OperatingSystem) - { - /// Indicates whether the current application is running on Windows. - /// if the current application is running on Windows; otherwise. - public static bool IsWindows() - { - return global::System.Environment.OSVersion.Platform switch - { - PlatformID.Win32S or - PlatformID.Win32Windows or - PlatformID.Win32NT or - PlatformID.WinCE => true, - _ => false, - }; - } - - // other forms of Is* are more difficult to poly fill as Linux, macOS, iOS, and android, are all apparently reported as PlatformId.Unix - // So they need to rely on additional native interop APIs for unix AND type name searches - // see: https://github.com/ryancheung/PlatformUtil/blob/master/PlatformUtil/PlatformInfo.cs - } - } -} diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillRuntimeHelpersExtensions.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillRuntimeHelpersExtensions.cs deleted file mode 100644 index 6698c72d6..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillRuntimeHelpersExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -#nullable enable - -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -#if COMPILER_LOOKS_FOR_EXTENSIONS -// based on work from: https://github.com/Sergio0694/PolySharp/issues/104 - -#pragma warning disable IDE0130 // Namespace does not match folder structure - -namespace System.Runtime.CompilerServices -{ - /// Poly Fill Extensions to - [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Design","CA1034: Nested types should not be visible", Justification = "extension, broken analyzer")] - internal static class PolyFillRuntimeHelpersExtensions - { - /// Poly Fill Extensions to - extension( global::System.Runtime.CompilerServices.RuntimeHelpers ) - { - /// - /// Slices the specified array using the specified range. - /// Adapted from source: https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs. - /// Required for be able to use `array[10..20] ` syntax in .NET Standard 2.0 and net48. - /// - public static T[] GetSubArray( T[] array, global::System.Range range ) - { - global::System.ArgumentNullException.ThrowIfNull(array); - (int offset, int length) = range.GetOffsetAndLength( array.Length ); - - if(length == 0) - { - return []; - } - - T[] dest; - - if(typeof( T[] ) == array.GetType()) - { - // We know the type of the array to be exactly T[]. - dest = new T[ length ]; - } - else - { - // exception should never hit since array is T[], but this keeps compiler/analyzers happy - global::System.Type elementType = array.GetType().GetElementType() ?? throw new global::System.InvalidOperationException("element type is not known!"); - - // The array is actually a U[] where U:T. We'll make sure to create - // an array of the exact same backing type. The cast to T[] will - // never fail. - dest = (T[])global::System.Array.CreateInstance( elementType, length ); - } - - // In either case, the newly-allocated array is the exact same type as the - // original incoming array. It's safe for us to Array.Copy the contents - // from the source array to the destination array, otherwise the contents - // wouldn't have been valid for the source array in the first place. - global::System.Array.Copy( array, offset, dest, 0, length ); - return dest; - } - } - } -} -#endif diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs deleted file mode 100644 index 7c5a0e173..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs +++ /dev/null @@ -1,91 +0,0 @@ -// -#nullable enable - -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// from .NET sources -// see: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs - -#pragma warning disable IDE0130 // Namespace does not match folder structure - -using global::System.Diagnostics; - -namespace System -{ - /// Pollyfill extensions for support not present in older runtimes - /// - internal static class PolyFillStringExtensions - { - extension(string) - { - /// Concatenates the members of a collection, using the specified separator between each member. - /// The type of the members of values. - /// The character to use as a separator. separator is included in the returned string only if values has more than one element. - /// A collection that contains the objects to concatenate. - /// - /// A string that consists of the members of values delimited by the separator character. - /// -or- System.String.Empty if values has no elements. - /// - /// is - /// The length of the resulting string overflows the maximum allowed length (). - public static string Join( char separator, IEnumerable values ) - { - return string.Join(separator.ToString(), values); - } - } - - public static int GetHashCode( this string self, StringComparison comparisonType ) - { - if(comparisonType != StringComparison.Ordinal) - { - throw new global::System.ComponentModel.InvalidEnumArgumentException(nameof(comparisonType), (int)comparisonType, typeof(StringComparison)); - } - - return self.GetHashCode(); - } - - /// Replace line endings in the string with environment specific forms - /// string to change line endings for - /// string with environment specific line endings - public static string ReplaceLineEndings(this string self) - { - return ReplaceLineEndings(self, global::System.Environment.NewLine); - } - - // This is NOT the most performant implementation, it's going for simplistic pollyfill that has - // the correct behavior, even if not the most performant. If performance is critical, use a - // later version of the runtime! - - /// Replace line endings in the string with a given string - /// string to change line endings for - /// Text to replace all of the line endings in - /// string with line endings replaced by - [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public static string ReplaceLineEndings(this string self, string replacementText) - { - global::System.ArgumentNullException.ThrowIfNull(self); - global::System.ArgumentNullException.ThrowIfNull(replacementText); - - string retVal = UnicodeNewLinesRegEx.Replace(self, replacementText); - - // if the result of replacement is the same, just return the original - // This is wasted overhead, but at least matches the behavior - return self == retVal ? self : retVal; - } - - // The Unicode Standard, Sec. 5.8, Recommendation R4 and Table 5-2 state that the CR, LF, - // CRLF, NEL, LS, FF, and PS sequences are considered newline functions. That section - // also specifically excludes VT from the list of newline functions, so we do not include - // it in the regular expression match. - - // language=regex - private const string UnicodeNewLinesRegExPattern = @"(\r\n|\r|\n|\f|\u0085|\u2028|\u2029)"; - - // NOTE: can't use source generated RegEx here as there's no way to declare the depency on - // the output of one generator as the input for another. They all see the same input, therefore - // the partial implementation would never be filled in and produces a compilation error instead. - private static global::System.Text.RegularExpressions.Regex UnicodeNewLinesRegEx { get; } - = new global::System.Text.RegularExpressions.Regex(UnicodeNewLinesRegExPattern); - } -} diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/ReadMe.md b/src/Ubiquity.NET.PollyFill.SharedSources/ReadMe.md deleted file mode 100644 index 8ec41a65f..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/ReadMe.md +++ /dev/null @@ -1,7 +0,0 @@ -# About -This library contains extensions that are shared amongst multiple additional projects. This, -currently takes the place of a source generator that would inject these types. The problem -with a Roslyn source generator for this is that the "generated" sources have a dependency on -types that are poly filled by a diffent source generator. Source generators all see the same -input and therefore a source generator is untestable without solving the problem of -explicilty generating the sources for the poly filled types. diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.projitems b/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.projitems deleted file mode 100644 index de344bbfa..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.projitems +++ /dev/null @@ -1,19 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 9fb998e5-6d78-4129-8022-9bb39fea81f2 - - - Ubuiquity.NET.PollyFill.SharedSources - - - - - - - - - - \ No newline at end of file diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.shproj b/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.shproj deleted file mode 100644 index 3ce6efc70..000000000 --- a/src/Ubiquity.NET.PollyFill.SharedSources/Ubiquity.NET.PollyFill.SharedSources.shproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - 9fb998e5-6d78-4129-8022-9bb39fea81f2 - 14.0 - - - - - - - - - - - diff --git a/src/Ubiquity.NET.Runtime.Utils/AnonymousNameProvider.cs b/src/Ubiquity.NET.Runtime.Utils/AnonymousNameProvider.cs deleted file mode 100644 index fa9989e58..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/AnonymousNameProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Provides anonymous names as a sequence of strings with an enumerated integral suffix - /// - /// Each call to starts a new sequence. This is generally used to provide - /// a name for a compiler generated function with module level visibility. - /// Each resulting enumerator is conceptually infinite. However, since an unsigned - /// integer () is used as the value it will wrap around to the beginning again. Thus, - /// the values are not guaranteed unique even from a single instance. Though this is rarely an issue in - /// practical circumstances. - /// - /// - [SuppressMessage( "Naming", "CA1710:Identifiers should have correct suffix", Justification = "This isn't a collection, shouldn't be named as one" )] - public class AnonymousNameProvider - : IEnumerable - { - /// Initializes a new instance of the class. - /// Prefix to use for the name - public AnonymousNameProvider( string prefix ) - { - NamePrefix = prefix; - } - - /// - public IEnumerator GetEnumerator( ) - { - UInt64 anonymousIndex = 0; - while(true) - { - yield return $"{NamePrefix}{anonymousIndex++}"; - } - } - - /// - IEnumerator IEnumerable.GetEnumerator( ) - { - return GetEnumerator(); - } - - private readonly string NamePrefix; - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/AstNode.cs b/src/Ubiquity.NET.Runtime.Utils/AstNode.cs deleted file mode 100644 index 5775fe79b..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/AstNode.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Common abstract base implementation of - public abstract class AstNode - : IAstNode - { - /// - public SourceRange Location { get; } - - /// - public abstract IEnumerable Children { get; } - - // NOTE: Accept() dispatching is NOT implemented here to allow type specific handling - // dispatch to the correct Visit(...). Implementation of that method requires - // type specific knowledge of the thing being visited. So this is an abstract - // method that an implementation will need to provide, even though the implementation - // looks the same, it isn't, as it includes direct calls to the correct overload - // of the Visit() method. It is plausible that a source generator could create - // the implementation of such mundane and error prone code duplication though... - - /// - public abstract TResult? Accept( IAstVisitor visitor ); - - /// - public abstract TResult? Accept( IAstVisitor visitor, ref readonly TArg arg ) -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct; -#else - where TArg : struct; -#endif - - /// Initializes a new instance of the class - /// Location in the source this node represents - protected AstNode( SourceRange location ) - { - Location = location; - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/AstNodeExtensions.cs b/src/Ubiquity.NET.Runtime.Utils/AstNodeExtensions.cs deleted file mode 100644 index 692f9552c..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/AstNodeExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Extensions for IAstNode - /// - /// While default interface methods seems like a great idea, it's not yet complete enough to be useful. - /// In particular there's pretty much no debugger support for evaluating such things, leaving you - /// with no way to see what they produce when used as a property. Hopefully, that will be resolved in - /// the future - but for now it is more a hindrance than it is a help. - /// - public static class AstNodeExtensions - { - /// Gets the complete collection of errors for this node and children - /// Node to traverse for errors - /// Traverses the node hierarchy to find all error nodes at any depth - /// Collection of errors found - public static ImmutableArray CollectErrors( this IAstNode node ) - { - ArgumentNullException.ThrowIfNull( node ); - - var collector = new ErrorNodeCollector(); - return node.Accept( collector ); - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/AstVisitorBase.cs b/src/Ubiquity.NET.Runtime.Utils/AstVisitorBase.cs deleted file mode 100644 index e9b8637f0..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/AstVisitorBase.cs +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// - public class AstVisitorBase - : IAstVisitor - { - /// - /// - /// The implementation for this base class does nothing beyond visiting each child via - /// a call to - /// - public virtual TResult? Visit( IAstNode node ) - { - return VisitChildren( node ); - } - - /// - public virtual TResult? VisitChildren( IAstNode node ) - { - ArgumentNullException.ThrowIfNull( node ); - TResult? aggregate = DefaultResult; - foreach(var child in node.Children) - { - aggregate = AggregateResult( aggregate, child.Accept( this ) ); - } - - return aggregate; - } - - /// Initializes a new instance of the class - /// Default result to use for visitation - protected AstVisitorBase( TResult? defaultResult ) - { - DefaultResult = defaultResult; - } - - /// - protected virtual TResult? AggregateResult( TResult? aggregate, TResult? newResult ) - { - return newResult; - } - - /// - protected TResult? DefaultResult { get; } - } - - /// Common base implementation of visitor for - /// Result of the visit - /// Type of the argument to pass to visit methods - /// - /// DSL languages will typically define the language specific node types to test for a - /// language specific implementation of this interface in their - /// and implementations. - /// This allows them to re-route the visitation to the language specific node type directly. If they don't do this - /// then the default behavior is to visit ANY node via . The default implementation of that here - /// does nothing of it's own beyond visiting each child. While that can be made to work in a language specific form - /// it requires such a visitor to implement a "type" switch on the node and then call the correct Visit method for - /// that type. This is tedious, error prone and performance overhead that is avoided if each node type contains a - /// redirecting implementation of the Accept methods. - /// An alternative is that each language specific node already contains a "kind" property (perhaps to make binary - /// serialization more efficient.) Then no re-directing to a custom interface is needed as a switch on the "kind" can - /// provide all the information needed to adjust the visit behavior. A number of possibilities exist for customization, - /// though the typical implementation is to use a simple redirecting Accept. (As shown in the example). - /// - /// - /// - /// - /// { - /// // Declare Visit methods for other node types here... - /// - /// TResult? Visit( MyLangNode node ); - /// } - /// - /// public interface IMyAstVisitor - /// where TArg : struct, allows ref struct - /// { - /// TResult? Visit( MyLangNode node, ref readonly TArg arg ); - /// } - /// - /// // [...] - /// public class MyLangAstVisitor - /// : AstVisitorBase - /// { - /// // ... - /// - /// TResult? Visit( MyLangNode node ) - /// { - /// // Do something interesting with specific MyLangNode type for `node` - /// // and return the result... - /// return default; - /// } - /// } - /// - /// // [...] - /// - /// public class MyLangAstVisitor - /// : AstVisitorBase - /// where TArg : struct, allows ref struct - /// { - /// // ... - /// - /// TResult? Visit( MyLangNode node, ref readonly TArg arg ) - /// { - /// // Do something interesting with specific MyLangNode type for `node` - /// // and input arg (if passing it to another function use `in`). Then - /// // return the result... - /// return default; - /// } - /// } - /// - /// // [ ... ] - /// - /// public sealed class MyLangNode - /// : AstNode - /// { - /// // ... - /// - /// public override TResult? Accept( IAstVisitor visitor ) - /// where TResult : default - /// { - /// return visitor is IMyAstVisitor myVisitor - /// ? myVisitor.Visit(this) - /// : visitor.Visit(this); - /// } - /// - /// public override TResult? Accept( IAstVisitor visitor, ref readonly TArg arg ) - /// where TResult : default - /// { - /// return visitor is IMyAstVisitor myVisitor - /// ? myVisitor.Visit(this, in arg) - /// : visitor.Visit(this, in arg); - /// } - /// } - /// ]]> - /// - [SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Related Generic - split file names is just confusing" )] - public class AstVisitorBase - : IAstVisitor -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct -#else - where TArg : struct -#endif - { - /// Visit a node and all of it's children - /// The node to visit - /// Argument to pass to the visit for use in association with the node - /// Result of the visitation - /// - /// The implementation for this base class does nothing beyond visiting each child via - /// a call to - /// - public virtual TResult? Visit( IAstNode node, ref readonly TArg arg ) - { - return VisitChildren( node, in arg ); - } - - /// Visits each child and aggregates the results as the return value - /// Node to visit children of - /// Argument to pass to the visit for use in association with the node - /// Result of the visitation - /// - /// The protected virtual method is responsible for aggregation - /// of results. The default is to simply replace the results with that of the new visitation. - /// - public virtual TResult? VisitChildren( IAstNode node, ref readonly TArg arg ) - { - ArgumentNullException.ThrowIfNull( node ); - TResult? aggregate = DefaultResult; - foreach(var child in node.Children) - { - aggregate = AggregateResult( aggregate, child.Accept( this, in arg ) ); - } - - return aggregate; - } - - /// Initializes a new instance of the class - /// Default result to use for visitation - protected AstVisitorBase( TResult? defaultResult ) - { - DefaultResult = defaultResult; - } - - /// Performs result aggregation for - /// Current aggregate result - /// New result to aggregate into the results - /// Aggregated result - /// - /// The result type is deliberately ambiguous to allow for simple replacement to - /// capturing all results in a sequence of some sort as needed by a given implementation. - /// The default behavior is to simply replace the results but an override is allowed to do - /// pretty much whatever it wants as long as the types are consistent. - /// - protected virtual TResult? AggregateResult( TResult? aggregate, TResult? newResult ) - { - return newResult; - } - - /// Gets the default value for a result provided in the constructor - protected TResult? DefaultResult { get; } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs b/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs deleted file mode 100644 index 3ef654e6d..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Exception to represent an error in the code generation of a DSL - /// - /// This is used to indicate exceptions in the process of transforming an AST into - /// an intermediate representation or some other form of native code. Errors in parsing - /// are mostly handled as instances of the class or a parse - /// technology specific exception. - /// - [Serializable] - public class CodeGeneratorException - : Exception - { - /// - public CodeGeneratorException( ) - { - } - - /// - public CodeGeneratorException( string message ) - : base( message ) - { - } - - /// Initializes a new instance of the class - /// Message for the exception - /// Inner exception - public CodeGeneratorException( string message, Exception inner ) - : base( message, inner ) - { - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ErrorNode.cs b/src/Ubiquity.NET.Runtime.Utils/ErrorNode.cs deleted file mode 100644 index 112da586b..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ErrorNode.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Represents an IAstNode where an error occurred in the parse - /// - /// The error may represent a syntax or semantic error but is used to mark the node - /// where the error occurred. This allows for AST generation and consumers to "recover" - /// from the error, but still report it as well as report multiple errors that might - /// occur. - /// - public class ErrorNode - : IAstNode - { - /// Initializes a new instance of the class - /// Original location of the error in source - /// Identifier code for the error - /// Error message for the error - /// Message level [default: ] - public ErrorNode( SourceRange location, int code, string err, MsgLevel level = MsgLevel.Error ) - { - ArgumentNullException.ThrowIfNull( err ); - - Location = location; - Code = code; - Message = err; - Level = level; - } - - /// Gets the source location for this error - public SourceRange Location { get; } - - /// Gets the code for the error - public int Code { get; } - - /// Gets the message level of this node - public MsgLevel Level { get; } - - /// Gets the string message for this error - public string Message { get; } - - /// - public IEnumerable Children { get; } = []; - - /// - public TResult? Accept( IAstVisitor visitor ) - { - ArgumentNullException.ThrowIfNull( visitor ); - - return visitor.Visit( this ); - } - - /// - public virtual TResult? Accept( IAstVisitor visitor, ref readonly TArg arg ) -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct -#else - where TArg : struct -#endif - { - ArgumentNullException.ThrowIfNull( visitor ); - - return visitor.Visit( this, in arg ); - } - - /// - public override string ToString( ) => $"{Location}:{Message}"; - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ErrorNodeCollector.cs b/src/Ubiquity.NET.Runtime.Utils/ErrorNodeCollector.cs deleted file mode 100644 index 508cd9316..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ErrorNodeCollector.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// AST visitor that collects all errors for a given node - public class ErrorNodeCollector - : AstVisitorBase> - { - /// Initializes a new instance of the class - public ErrorNodeCollector( ) - : base( [] ) - { - } - - /// - /// - /// This implementation will aggregate the node to the results of errors if it is an - /// before visiting all children of the node, which will, in - /// turn, add any s to the collected results. Thus resulting - /// in a final array of errors. - /// - public override ImmutableArray Visit( IAstNode node ) - { - return node is ErrorNode errNode - ? AggregateResult( [ errNode ], base.Visit( node ) ) - : base.Visit( node ); - } - - /// - protected override ImmutableArray AggregateResult( - ImmutableArray aggregate, - ImmutableArray newResult - ) - { - return aggregate.AddRange( newResult ); - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Runtime.Utils/GlobalNamespaceImports.cs deleted file mode 100644 index b990188c1..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/GlobalNamespaceImports.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.CodeDom.Compiler; -global using System.Collections; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.Diagnostics.CodeAnalysis; -global using System.Globalization; -global using System.IO; -global using System.Linq; -global using System.Runtime.CompilerServices; -global using System.Text; -global using System.Threading; -global using System.Threading.Tasks; -global using System.Xml.Linq; - -global using Ubiquity.NET.CommandLine; -global using Ubiquity.NET.Extensions; -global using Ubiquity.NET.InteropHelpers; -global using Ubiquity.NET.SrcGeneration.CSharp; diff --git a/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs b/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs deleted file mode 100644 index d3c293365..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Root interface for nodes in the Abstract Syntax Tree - public interface IAstNode - { - /// Gets the source location covering the original source for the node - SourceRange Location { get; } - - /// Gets a collection of children for the node - IEnumerable Children { get; } - - /// Visitor pattern support for implementations to dispatch the concrete node type to a visitor - /// Result type for the visitor - /// Visitor to dispatch the concrete type to - /// Result of visiting this node - TResult? Accept( IAstVisitor visitor ); - - /// Visitor pattern support for implementations to dispatch the concrete node type to a visitor - /// Result type for the visitor - /// Type of the argument to pass on to the visitor - /// Visitor to dispatch the concrete type to - /// Argument to pass to the concrete type as a readonly ref - /// Result of visiting this node - TResult? Accept( IAstVisitor visitor, ref readonly TArg arg ) -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct; -#else - where TArg : struct; -#endif - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs b/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs deleted file mode 100644 index 4ffe4c6b6..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// - /// - /// This interface is sued for visiting an AST, typically for - /// generating code but may also be used to detect errors in the - /// AST etc.. - /// - public interface IAstVisitor - { - /// - TResult? Visit( IAstNode node ); - } - - /// Interface for implementing the Visitor pattern with - /// Result type of the visit - /// Argument type to pass to all visit methods - /// - /// This interface is used for visiting an AST, typically for generating code but may also - /// be used to detect errors in the AST etc.. - /// In frameworks that support it the is typically used for a byref-like - /// type where the type may NOT be stored on the heap and MUST be passed via ref readonly. (Such support - /// requires at least .NET 9 to support allows ref struct, which requires runtime support.) - /// - public interface IAstVisitor -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct -#else - where TArg : struct -#endif - { - /// Visits a given node to produce a result - /// Node to visit - /// Arg associated with this node and visit operation - /// Result of the visit - TResult? Visit( IAstNode node, ref readonly TArg arg ); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ICodeGenerator.cs b/src/Ubiquity.NET.Runtime.Utils/ICodeGenerator.cs deleted file mode 100644 index 2a84727d1..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ICodeGenerator.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Interface for a Kaleidoscope code generator - /// Result type of the generation - /// - /// For eager JIT and AOT compilation is normally - /// a "value" for the JIT or IR. Though any type is viable. - /// - public interface ICodeGenerator - : IDisposable - { - /// Generates output from the tree - /// Tree to generate - /// Generated result - /// - /// The behavior of this method depends on the implementation. The common case is to - /// actually generate a module for the JIT engine. Normally, any anonymous expressions - /// are JIT compiled and executed. The result of executing the expression is returned. - /// For Function definitions or declarations, the function is returned. - /// However, that's not required. In a simple syntax analyzer, the generate may do nothing - /// more than generate diagrams or other diagnostics from the input tree. - /// For a lazy compilation JIT the generator will defer the actual generation of code and instead - /// will create stubs for each function definition. When those functions are called, the stubs trigger a - /// callback to the application that will then generate the code for the function "on the fly". In this case, - /// only a top level expression is immediately generated/executed to produce a value. - /// - TResult? Generate( IAstNode ast ); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/IParseErrorListener.cs b/src/Ubiquity.NET.Runtime.Utils/IParseErrorListener.cs deleted file mode 100644 index 9cd1c0702..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IParseErrorListener.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Interface for a generic error listener - public interface IParseErrorListener - { - /// Process a single SyntaxError found during parse - /// Error found - /// - /// Implementation should not assume that calling this is "terminal" for the - /// parse. Many parsers are able to "recover" from syntax errors (and may - /// even do so correctly) but the original source still contains the erroneous - /// input. - /// - void SyntaxError( SyntaxError syntaxError ); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/IParseErrorReporter.cs b/src/Ubiquity.NET.Runtime.Utils/IParseErrorReporter.cs deleted file mode 100644 index 745114af7..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IParseErrorReporter.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Interface for a logger of parse errors - public interface IParseErrorReporter - { - /// Log errors for a given error node - /// Node containing error information to log - void ReportError( ErrorNode node ); - - /// Log an error message for the parse - /// Message to log for the error - /// - /// This is normally used only for internal errors where the message is provided - /// from an Exception. That is ONLY when the actual source context isn't known. - /// Ideally, the message contains information that can help identify the location - /// or cause of the error better. - /// - void ReportError( string msg ); - } - - /// Utility class to provide extension methods for - public static class ParseErrorReporterExtensions - { - /// Collects and reports all errors in an - /// Reporter to use for any errors found - /// Node to find errors from - /// if any errors were found; if not - public static bool CheckAndReportParseErrors( this IParseErrorReporter self, [NotNullWhen(false)] IAstNode? node ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(node is null) - { - return true; - } - - var errors = node.CollectErrors( ); - if(errors.Length == 0) - { - return false; - } - - foreach(var err in errors) - { - self.ReportError( err ); - } - - return true; - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/IParser.cs b/src/Ubiquity.NET.Runtime.Utils/IParser.cs deleted file mode 100644 index 7c413396b..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IParser.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// CONSIDER: Adaptation that accepts IParsesErrorReporter as a param argument to the parse APIs instead of relying on -// an implementation to "hold" one. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Core interface for a general parser that parses input text into an AST represented as a root - public interface IParser - { - /// Try parsing the given input text - /// Text to parse - /// Parse results as an - /// - /// If the parse fails then the result is . - /// Errors from the parse are reported through error listeners provided - /// to the parser. Normally, this is done via the constructor of a type - /// implementing . - /// - /// If the parse succeeds, but creation of the AST fails, then the result is - /// an AST tree with some nodes as - /// - /// - IAstNode? Parse( string txt ); - - /// Try parsing the given input text as full source, potentially containing multiple definitions - /// TextReader to parse - /// Parse results as an - /// - IAstNode? Parse( TextReader reader ); - } - - /// Extensions to - public static class ParserExtensions - { - /// Parses the contents of a file as text - /// Type implementing - /// instance of the parser to use for parsing - /// Path of the input file to parse - /// Parse results as an - /// - public static IAstNode? ParseFrom(this T self, string sourceFilePath) - where T : IParser - { - using var rdr = File.OpenText( sourceFilePath ); - return self.Parse(rdr); - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs b/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs deleted file mode 100644 index 4253116b6..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Interface to process the results of a parse for "visualization" - /// - /// Generally this is used before or in place of generating any actual code to aid - /// with diagnosing issues with the parsing itself. - /// - public interface IVisualizer - { - /// Gets the kind of visualizations supported (if any) - VisualizationKind VisualizationKind { get; } - - /// Processes the raw parse tree as XML if has the flag - /// XML representation of the full parse tree - /// - /// This is an XML form of the raw parse tree (Not reduced to the AST) - /// - public void VisualizeParseTree( XDocument parseTreeXml ); - - /// - /// processes the DGML representation of the Kaleidoscope AST if has the flag - /// - /// DGML representation of the Kaleidoscope AST - public void VisualizeAstDgml( XDocument astDgml ); - - /// - /// Called to process the BlockDiag representation of the Kaleidoscope AST if has the flag - /// - /// BlockDiag representation of the parse - void VisualizeBlockDiag( string blockDiag ); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/NullNode.cs b/src/Ubiquity.NET.Runtime.Utils/NullNode.cs deleted file mode 100644 index c4506448f..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/NullNode.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Null Object pattern implementation for AST Nodes - public class NullNode - : IAstNode - { - /// - public SourceRange Location { get; } = default; - - /// - public IEnumerable Children { get; } = []; - - /// - public TResult? Accept( IAstVisitor visitor ) - { - return default; - } - - /// - public virtual TResult? Accept( IAstVisitor visitor, ref readonly TArg arg ) -#if NET9_0_OR_GREATER - where TArg : struct, allows ref struct -#else - where TArg : struct -#endif - { - ArgumentNullException.ThrowIfNull( visitor ); - return default; - } - - /// Gets a singleton null node instance - public static NullNode Instance => LazyInstance.Value; - - private static readonly Lazy LazyInstance = new(LazyThreadSafetyMode.PublicationOnly); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs b/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs deleted file mode 100644 index f741a3edc..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Implementation of to collect any errors that occur during a parse - public class ParseErrorCollector - : IParseErrorListener - { - /// - /// - /// This will collect every reported during a parse - /// - public void SyntaxError( SyntaxError syntaxError ) - { - ArgumentNullException.ThrowIfNull( syntaxError ); - ErrorNodes = ErrorNodes.Add( new ErrorNode( syntaxError.Location, syntaxError.Id, syntaxError.ToString() ) ); - } - - /// Gets the error nodes found by this listener - public ImmutableArray ErrorNodes { get; private set; } = []; - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ParseErrorDiagnosticAdapter.cs b/src/Ubiquity.NET.Runtime.Utils/ParseErrorDiagnosticAdapter.cs deleted file mode 100644 index ea151b802..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ParseErrorDiagnosticAdapter.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Adapter to redirect calls to to a given - public class ParseErrorDiagnosticAdapter - : IParseErrorReporter - { - /// Initializes a new instance of the class. - /// reporter that this instance adapts errors to - /// Prefix used for all codes - /// Origin of the diagnostics reported by this adapter (default is an in memory string [string:memory]) - public ParseErrorDiagnosticAdapter( IDiagnosticReporter targetReporter, string diagnosticCodePrefix, Uri? origin = null ) - { - TargetReporter = targetReporter; - Origin = origin ?? new( "string:memory" ); - Prefix = diagnosticCodePrefix; - } - - /// Gets the origin reported by this adapter - public Uri Origin { get; } - - /// Gets the prefix used for each diagnostic - public string Prefix { get; } - - /// Gets the target reporter this adapter redirects to - public IDiagnosticReporter TargetReporter { get; } - - /// - public void ReportError( ErrorNode node ) - { - var diagnostic = new DiagnosticMessage() - { - Origin = Origin, - Code = $"{Prefix}{Convert.ToInt32(node.Code, CultureInfo.InvariantCulture)}", - Level = node.Level, - Location = node.Location, - Subcategory = default, - Text = node.Message - }; - TargetReporter.Report( diagnostic ); - } - - /// - public void ReportError( string msg ) - { - var diagnostic = new DiagnosticMessage() - { - Origin = Origin, - Code = default, - Level = MsgLevel.Error, - Location = default, - Subcategory = default, - Text = msg - }; - TargetReporter.Report( diagnostic ); - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs b/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs deleted file mode 100644 index f1ede8847..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Common core implementation of a Read, Evaluate, Print Loop (REPL) - /// Type of values produced by the evaluation stage - public abstract class REPLBase - { - /// Shows a prompt appropriate for the runtime and current state - /// Ready state for the REPL - public abstract void ShowPrompt( ReadyState state ); - - /// Show or otherwise process the results of an evaluation - /// Evaluated result value - public abstract void ProcessResults( T resultValue ); - - /// Gets the error logger to use for logging any parse errors - public IParseErrorReporter ErrorLogger { get; } - - /// Asynchronously runs the REPL loop on the input reader - /// Reader to process the input for - /// Parser to process the input for - /// Result generator to transform nodes parsed into the evaluated result - /// Cancellation token to allow cancellation of the REPL loop - /// Async task for the operation - public async Task Run( TextReader input, IParser parser, ICodeGenerator generator, CancellationToken cancelToken = default ) - { - ShowPrompt( ReadyState.StartExpression ); - - // Create sequence of parsed AST RootNodes to feed the REPL loop - var replSeq = from stmt in input.ToStatements( ShowPrompt, cancelToken: cancelToken ) - let node = parser.Parse( stmt ) - where !ErrorLogger.CheckAndReportParseErrors( node ) - select node; - - await foreach(IAstNode node in replSeq.WithCancellation( cancelToken )) - { - try - { - T? result = generator.Generate( node ); - if(result is not null) - { - ProcessResults( result ); - } - } - catch(CodeGeneratorException ex) - { - // This is an internal error that is not recoverable. - // Report the error and stop additional processing - ErrorLogger.ReportError( ex.ToString() ); - break; - } - } - } - - /// Initializes a new instance of the class - /// Logger to use for reporting any errors during parse - protected REPLBase( IParseErrorReporter logger ) - { - ErrorLogger = logger; - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ReadMe.md b/src/Ubiquity.NET.Runtime.Utils/ReadMe.md deleted file mode 100644 index 388e99770..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ReadMe.md +++ /dev/null @@ -1,6 +0,0 @@ -# Ubiquity.NET.Runtime.Utils -This library contains support functionality to aid in building a language parser or runtime, -including common implementation of a Read-Evaluate-Print Loop (REPL). Generally this is used -in conjunction with the Ubiquity.NET.Llvm library to provide custom DSL JIT support. - -See the Kaleidoscope tutorial for details on how to use this library diff --git a/src/Ubiquity.NET.Runtime.Utils/ReadyStateManager.cs b/src/Ubiquity.NET.Runtime.Utils/ReadyStateManager.cs deleted file mode 100644 index df3352965..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ReadyStateManager.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Defines state for the UI for REPL parsing to show a prompt to the user - public enum ReadyState - { - /// Start of an expression - /// - /// Usually this state provides a "Ready" or other similar prompt that indicates ready to accept - /// any valid language expression. - /// - StartExpression, - - /// In the middle of an expression continuation - /// - /// Usually this state provides a simpler short prompt to indicate the continuation - /// of an expression from the previous line. - /// - ContinueExpression - } - - /// Tracks the ready state for a REPL interpreter - /// - /// The ready state is used to handle partial input lines where the prompt - /// presented to the user indicates that input is a continuation of the previous. - /// - internal class ReadyStateManager - { - public ReadyStateManager( Action? prompt = null ) - { - PromptAction = prompt; - } - - public ReadyState State { get; private set; } - - public void Prompt( ) - { - PromptAction?.Invoke( State ); - } - - public void UpdateState( string txt, bool isPartial ) - { - /* - Current Ready | IsPartial | blank | new value for Ready - -------------------|-----------|-------|-------------------- - StartExpression | false | false | StartExpression - StartExpression | false | true | - StartExpression | true | false | ContinueExpression - StartExpression | true | true | StartExpression - ContinueExpression | false | x | StartExpression - ContinueExpression | true | x | ContinueExpression - */ - if(State == ReadyState.StartExpression) - { - bool isBlank = string.IsNullOrWhiteSpace( txt ); - - if(!isPartial && isBlank) - { - throw new InvalidOperationException(); - } - - State = isPartial == isBlank ? ReadyState.StartExpression : ReadyState.ContinueExpression; - } - else - { - State = isPartial ? ReadyState.ContinueExpression : ReadyState.StartExpression; - } - } - - private readonly Action? PromptAction; - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs b/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs deleted file mode 100644 index d77e133df..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Simple implementation of common Variable scoping - /// Type of the values to associate with the symbol name - /// - /// In essence, this is a stack of Dictionaries of symbol names to - /// values that is most commonly used in code generation. Most languages have some sort of notion - /// of symbol scopes and name lookups. This implements the common case of nested scopes where a - /// new 'local scope' may override some of the symbols in a parent scope. Any values in any parent - /// not overridden by the child are visible to the child scope. - /// - [SuppressMessage( "Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Semantically this type *is* a Stack" )] - public class ScopeStack - { - /// Initializes a new instance of the class. - /// - /// The stack is initialized with a "global" scope ready for use without additional - /// initialization. Subsequent scopes for the stack are generated by calling the - /// method. - /// - public ScopeStack( ) - { - Scopes.Push( new Dictionary() ); - } - - /// Gets the depth of the stack - /// The depth starts as 1 since the stack starts at the global scope. - public int Depth => Scopes.Count; - - /// Gets the dictionary at the top of the stack - /// Dictionary at top of stack - public IDictionary Peek() - { - return Scopes.Peek(); - } - - /// Gets or sets the value of a symbol in scope - /// name of the symbol - /// Value for the symbol - /// - /// Getting a symbol value searches all scopes, starting with the current scope. - /// Setting a symbol value will only set the value in the current scope. (e.g. - /// if the value does not exist in the current scope a new entry is made for it - /// in the current scope, even if some outer scope has the same name. - /// - /// If the wasn't found in the active or parent scopes - [SuppressMessage( "Design", "CA1043:Use Integral Or String Argument For Indexers", Justification = "It is a string, it just supports UTF8 as well as System.String" )] - public T this[ LazyEncodedString name ] - { - get => TryGetValue( name, out T? retVal ) ? retVal : throw new KeyNotFoundException( name ); - set => Scopes.Peek()[ name ] = value; - } - - /// Starts a new scope - /// to enable automatic restore of the scope in RAII style patterns - /// - /// A new scope is pushed on the stack and remains active until the method - /// of the return is called. Normally, code generation does this with a language provided means of ensuring the - /// Dispose method is called even when an exception occurs. (i.e. C# 'using' or try/finally) - /// - public IDisposable EnterScope( ) - { - Scopes.Push( new Dictionary() ); - return new DisposableAction( ( ) => Scopes.Pop() ); - } - - /// Generates a string representation of the full scope stack - /// String representation fo the scope stack - public override string ToString( ) - { - var bldr = new StringBuilder("Depth:") - .Append(Depth) - .AppendLine(); - - using var innerWriter = new StringWriter(bldr, CultureInfo.CurrentCulture); - - // NOTE: unless writer.Close is called the innerWriter is NOT disposed. - // Technically, Dispose of an IndentedTextWriter is a NOP, but keeps - // the compiler happy and meets the API requirements of the base `TextWriter` - // Sadly, this behavior is NOT documented nor clarified in any way. - using var writer = new IndentedTextWriter(innerWriter, " "); - - var disposableScopeStack = new Stack(); - foreach(IDictionary scope in Scopes) - { - // add each scope to a stack so they nest in the formatted string - disposableScopeStack.Push(WriteScope(writer, scope)); - } - - foreach(var disp in disposableScopeStack) - { - disp.Dispose(); - } - - return bldr.ToString(); - } - - /// Attempts to retrieve a value from the stack - /// Name of the value - /// Value - /// if the symbol was found from a search of the scopes - public bool TryGetValue( LazyEncodedString name, [MaybeNullWhen( false )] out T value ) - { - value = default; - foreach(var scope in Scopes) - { - if(scope.TryGetValue( name, out value )) - { - return true; - } - } - - return false; - } - - private static IDisposable WriteScope(IndentedTextWriter writer, IDictionary scope) - { - var txtScope = writer.Scope("Scope"); - try - { - foreach(var kvp in scope) - { - writer.Write(kvp.Key); - writer.Write(" = "); - writer.Write(kvp.Value); - } - } - catch - { - txtScope.Dispose(); - throw; - } - - return txtScope; - } - - private readonly Stack> Scopes = new(); - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/SyntaxError.cs b/src/Ubiquity.NET.Runtime.Utils/SyntaxError.cs deleted file mode 100644 index 79cac0eb6..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/SyntaxError.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Enumeration to indicate the source of an error during parse - public enum ParseErrorSource - { - /// Error is a simple lexical error from the lexer - Lexer, - - /// Error is from the parsing stage - Parser, - } - - /// Parse technology independent abstraction of a syntax error from the lexer or parser - public class SyntaxError - { - /// Initializes a new instance of the class - /// Source of the error - /// Source file this error was found in - /// ID of the error - /// symbol the error is from - /// Location in the error was found - /// message for the error - /// Any exception associated with the error - public SyntaxError( - ParseErrorSource source, - string sourceFile, - int id, - string symbol, - SourceRange location, - string message, - Exception? exception - ) - { - ArgumentNullException.ThrowIfNull( sourceFile ); - ArgumentNullException.ThrowIfNull( symbol ); - ArgumentNullException.ThrowIfNull( message ); - - Source = source; - SourceFile = sourceFile; - Id = id; - Symbol = symbol; - Location = location; - Message = message; - Exception = exception; - } - - /// Gets the production source of the error - public ParseErrorSource Source { get; } - - /// Gets the sourceFile containing the error - public string SourceFile { get; } - - /// Gets the symbol related to the error - public string Symbol { get; } - - /// Gets the ID of the error - public int Id { get; } - - /// Gets the source location of the error in - public SourceRange Location { get; } - - /// Gets the message for this error - public string Message { get; } - - /// Gets any exceptions associated with this error - public Exception? Exception { get; } - - /// - public override string ToString( ) - { - return $"{SourceFile}({Location}): error: {Source}{Id} {Message}"; - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/TextReaderExtensions.cs b/src/Ubiquity.NET.Runtime.Utils/TextReaderExtensions.cs deleted file mode 100644 index 6245b1a0c..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/TextReaderExtensions.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Utility class to provide extensions for REPL Loop and other scenarios requiring Async processing of text input - public static class TextReaderExtensions - { - /// Provides an async sequence of lines from a reader - /// reader to retrieve lines from - /// Cancellation token to cancel production of lines - /// Async sequence of lines from - public static async IAsyncEnumerable ToLinesAsync( this TextReader input, [EnumeratorCancellation] CancellationToken cancelToken = default ) - { - ArgumentNullException.ThrowIfNull( input ); - - string? line; - do - { - line = await input.ReadLineAsync( cancelToken ); - if(line != null) - { - yield return line; - } - } while(line != null); - } - - /// - /// - /// This uses a common statement completion character of ';' if that is not desired then the - /// overload - /// takes a parameter for the completion character. - /// - public static IAsyncEnumerable ToStatements( - this TextReader reader, - Action? prompt, - CancellationToken cancelToken = default - ) - { - return ToStatements( reader, prompt, ';', cancelToken ); - } - - /// Async operator to encapsulate conversion of text from a into an observable sequence of Kaleidoscope statements - /// Input reader - /// Action to provide prompts when the transform requires new data from the reader - /// Character to mark termination of the statement - /// Cancellation token for the async operation - /// Async sequence of complete statements ready for parsing - public static async IAsyncEnumerable ToStatements( - this TextReader reader, - Action? prompt, - char terminationChar, - [EnumeratorCancellation] CancellationToken cancelToken = default - ) - { - var stateManager = new ReadyStateManager( prompt ); - var bldr = new StringBuilder( ); - await foreach(string line in reader.ToLinesAsync( cancelToken )) - { - if(cancelToken.IsCancellationRequested) - { - break; - } - - var partials = SplitLines(bldr, line, terminationChar, cancelToken); - foreach(var (txt, isPartial) in partials) - { - stateManager.UpdateState( txt, isPartial ); - if(!isPartial) - { - yield return txt; - } - } - - stateManager.Prompt(); - } - } - - private static IEnumerable<(string Txt, bool IsPartial)> SplitLines( - StringBuilder buffer, - string line, - char terminationChar, - CancellationToken cancelToken = default - ) - { - string[ ] statements = line.Split( terminationChar ); - - // if the last line in the group was terminated with a terminator character - // the last entry is an empty string, but a single blank line as input isn't - // considered completed. - int completeStatements = statements.Length - 1; - bool wasLastTerminated = string.IsNullOrEmpty( statements[ ^1 ] ) && statements.Length > 1; - if(wasLastTerminated && completeStatements > 1) - { - ++completeStatements; - } - - for(int i = 0; i < completeStatements; ++i) - { - if(cancelToken.IsCancellationRequested) - { - yield break; - } - - string statement = statements[ i ]; - buffer.Append( statement ); - buffer.Append( terminationChar ); - buffer.AppendLine(); - - yield return (buffer.ToString(), false); - - if(buffer.Length > statement.Length + 1) - { - buffer.Clear(); - } - } - - if(!wasLastTerminated) - { - string partial = statements[ ^1 ]; - buffer.AppendLine( partial ); - yield return (partial, true); - } - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/Ubiquity.NET.Runtime.Utils.csproj b/src/Ubiquity.NET.Runtime.Utils/Ubiquity.NET.Runtime.Utils.csproj deleted file mode 100644 index 7d41df232..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/Ubiquity.NET.Runtime.Utils.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - net8.0 - enable - - preview - True - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - General use Support for interpreters/runtimes - Extensions,.NET,Ubiquity.NET - ReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - - - - - - - - - - - - - - - - - - all - false - Analyzer - - - - diff --git a/src/Ubiquity.NET.Runtime.Utils/Utilities.cs b/src/Ubiquity.NET.Runtime.Utils/Utilities.cs deleted file mode 100644 index 68247d582..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/Utilities.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Utility class to provide general functionality that would be "free standing" functions if allowed in the C# language - public static class Utilities - { - /// replaces any characters in a name that are invalid for a file name - /// name to convert - /// character to replace invalid characters with [Default: '_'] - /// with invalid characters replaced with - /// - /// Determination of invalid characters for a file name comes from the runtime via the - /// method. Thus, this adapts to the runtime environment automatically. - /// - public static string GetSafeFileName( string name, char replacementChar = '_' ) - { - ArgumentException.ThrowIfNullOrWhiteSpace( name ); - - var bldr = new StringBuilder( name.Length ); - char[ ] invalidChars = Path.GetInvalidFileNameChars( ); - foreach(char c in name) - { - bldr.Append( invalidChars.Contains( c ) ? replacementChar : c ); - } - - return bldr.ToString(); - } - } -} diff --git a/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs b/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs deleted file mode 100644 index 3a0bee138..000000000 --- a/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.Runtime.Utils -{ - /// Enumeration to define the kinds of diagnostic intermediate data to generate from a runtime/language AST - [Flags] - public enum VisualizationKind - { - /// No Visualizations - None, - - /// Generate an XML representation of the parse tree - Xml, - - /// Generate a DGML representation of the parse tree - Dgml, - - /// Generates a BlockDiag representation of the parse tree - BlockDiag, - - /// Emits debug tracing during the parse to an attached debugger - /// This is a NOP if no debugger is attached - DebugTraceParser, - - /// Emit all visualizations - All = Xml | Dgml | BlockDiag | DebugTraceParser - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/CSharp/SourceGeneratorTest.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/CSharp/SourceGeneratorTest.cs deleted file mode 100644 index 9a385f150..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/CSharp/SourceGeneratorTest.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils.CSharp -{ - /// Source generator tests for C# that allows specification of the language - /// Source generator type - /// Verifier type - public class SourceGeneratorTest - : Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest - where TSourceGenerator : new() - where TVerifier : IVerifier, new() - { - /// Initializes a new instance of the class. - /// Version of the language for this test - public SourceGeneratorTest(LanguageVersion ver) - { - LanguageVersion = ver; - } - - /// - /// Creates parse options - protected override ParseOptions CreateParseOptions( ) - { - // TODO: until C# 14 is formally released, this is "preview" - return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion); - } - - private readonly LanguageVersion LanguageVersion; - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/EnumerableObjectComparer.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/EnumerableObjectComparer.cs deleted file mode 100644 index e0fe6aa52..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/EnumerableObjectComparer.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - /// Comparer to test an array (element by element) for equality - public class EnumerableObjectComparer - : IEqualityComparer> - { - /// - [SuppressMessage( "StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "xValues and yValues are not hungarioan names" )] - public bool Equals(IEnumerable? x, IEnumerable? y) - { - ArgumentNullException.ThrowIfNull(x); - ArgumentNullException.ThrowIfNull(y); - var xValues = x.ToImmutableArray(); - var yValues = x.ToImmutableArray(); - return xValues.Length == yValues.Length - && xValues.Zip(yValues, (a, b) => a.Equals(b)).All(x => x); - } - - /// - public int GetHashCode([DisallowNull] IEnumerable obj) - { - return obj.GetHashCode(); - } - - /// Default constructed comparer. - public static readonly EnumerableObjectComparer Default = new(); - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverExtensions.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverExtensions.cs deleted file mode 100644 index 13b4f0c83..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverExtensions.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Collections.Immutable; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - /// Utility class for to add extensions for testing - public static class GeneratorDriverExtensions - { - /// Runs a source generator twice and validates the results - /// Driver to use for the run - /// Compilation to use for the run - /// Array of names to filter all of the internal tracking names - /// Results of first run - /// - /// This will run the generator twice. The value of results are tested for - /// any banned types and are additionally tested to ensure the expected - /// tracking names are found and only use cached results on the second run. - /// - public static GeneratorDriverRunResult RunGeneratorAndAssertResults( - this GeneratorDriver driver, - CSharpCompilation compilation, - ImmutableArray trackingNames - ) - { - ArgumentOutOfRangeException.ThrowIfLessThan(trackingNames.Length, 1, nameof(trackingNames)); - - var compilationClone = compilation.Clone(); - - // save the resulting immutable driver for use in second run. - driver = driver.RunGenerators(compilation); - GeneratorDriverRunResult runResult1 = driver.GetRunResult(); - GeneratorDriverRunResult runResult2 = driver.RunGenerators(compilationClone) - .GetRunResult(); - Assert.That.AreEqual(runResult1, runResult2, trackingNames); - Assert.That.Cached(runResult2); - return runResult1; - } - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverRunResultExtensions.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverRunResultExtensions.cs deleted file mode 100644 index 8f3476888..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/GeneratorDriverRunResultExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// based on blog code (but heavily modified): -// https://andrewlock.net/creating-a-source-generator-part-10-testing-your-incremental-generator-pipeline-outputs-are-cacheable/ - -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; - -using Microsoft.CodeAnalysis; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - /// Static class with extensions/helpers for testing incremental source generators - public static class GeneratorDriverRunResultExtensions - { - /// Extension method for a to Get all of the tracked steps matching a name contained in the input - /// Results to get steps from - /// Set of names to get from - /// Dictionary to map the name to any run steps for that name - [SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Significantly less readable if applied")] - public static ImmutableDictionary> GetTrackedSteps( - this GeneratorDriverRunResult runResult, - ImmutableArray trackingNames - ) - { - if (trackingNames.Length == 0) - { - return ImmutableDictionary>.Empty; - } - - return runResult.Results[0] - .TrackedSteps - .Where(step => trackingNames.Contains(step.Key)) - .ToImmutableDictionary(v => v.Key, v => v.Value); - } - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/MsTestAssertExtensions.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/MsTestAssertExtensions.cs deleted file mode 100644 index a16becd9b..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/MsTestAssertExtensions.cs +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; -using System.Reflection; - -using Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - // NOTE: due to bug https://github.com/dotnet/roslyn/issues/78042 this is not convertible to - // the new 'extension' keyword. The problem is in the compiler generated lambda for the LINQ - // expressions. Fortunately, the "old" syntax still works fine... - - /// Utility class to implement extensions for - public static class MsTestAssertExtensions - { - /// Extension method for use with to validate two are equivalent - /// Unused, provides extension support - /// Results of first run - /// Results of second run - /// Names of custom tracking steps to validate - public static void AreEqual( - this Assert _, - GeneratorDriverRunResult r1, - GeneratorDriverRunResult r2, - ImmutableArray trackingNames - ) - { - var trackedSteps1 = r1.GetTrackedSteps(trackingNames); - var trackedSteps2 = r2.GetTrackedSteps(trackingNames); - - // Assert the static requirements - Assert.AreNotEqual(0, trackedSteps1.Count, "Should not be an empty set of steps matching tracked names"); - Assert.HasCount( trackedSteps1.Count, trackedSteps2, "Both runs should have same number of tracked steps"); - bool hasSameKeys = trackedSteps1.Zip(trackedSteps2, (s1, s2) => trackedSteps2.ContainsKey(s1.Key) && trackedSteps1.ContainsKey(s2.Key)) - .All(x => x); - Assert.IsTrue(hasSameKeys, "Both sets of runs should have the same keys"); - - // loop through all KVPs of name to step in result set 1 - // assert that the second run steps for the same tracking name are equal. - foreach (var (trackingName, runSteps1) in trackedSteps1) - { - var runSteps2 = trackedSteps2[trackingName]; - Assert.That.AreEqual(runSteps1, runSteps2, trackingName); - } - } - - /// - /// Extension method for use with to validate each member of a pair of - /// are equivalent. - /// - /// Unused, provides extension support - /// Array of steps to test against - /// Array of steps to assert are equal to the elements of - /// Tracking name of the step for use in diagnostic messages - /// - /// This uses the built-in extensibility point to perform asserts on - /// each member of the input arrays. Each is tested for equality and this only passes - /// if ALL members are equal. - /// - public static void AreEqual( - this Assert _, - ImmutableArray steps1, - ImmutableArray steps2, - string stepTrackingName - ) - { - Assert.HasCount( steps1.Length, steps2, "Step lengths should be equal"); - for (int i = 0; i < steps1.Length; ++i) - { - var runStep1 = steps1[i]; - var runStep2 = steps2[i]; - - IEnumerable outputs1 = runStep1.Outputs.Select(x => x.Value); - IEnumerable outputs2 = runStep2.Outputs.Select(x => x.Value); - - Assert.AreEqual(outputs1, outputs2, EnumerableObjectComparer.Default, $"{stepTrackingName} should produce cacheable outputs"); - Assert.That.OutputsCachedOrUnchanged(runStep2, stepTrackingName); - Assert.That.ObjectGraphContainsValidSymbols(runStep1, stepTrackingName); - } - } - - /// Extension method for use with to assert all of the tracked output steps are cached - /// Unused, provides extension support - /// Run results to test for cached outputs - public static void Cached(this Assert _, GeneratorDriverRunResult driverRunResult) - { - // verify the second run only generated cached source outputs - var uncachedSteps = from generatorRunResult in driverRunResult.Results - from trackedStepKvp in generatorRunResult.TrackedOutputSteps - from runStep in trackedStepKvp.Value // name is used in select if condition passes - from valueReasonTuple in runStep.Outputs // all outputs must have a cached reason. - where valueReasonTuple.Reason != IncrementalStepRunReason.Cached - select runStep.Name; - - foreach (string stepTrackingName in uncachedSteps) - { - Assert.Fail($"Step name {stepTrackingName ?? ""} contains uncached results for second run!"); - } - } - - /// Extension method for use with to validate that an object is not of a banned type - /// [ignored] syntactic sugar for extension method - /// object node to test - /// reason message for any failures - /// parameters for construction of any exceptions - public static void NotBannedType( - this Assert _, - object? node, - [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string message, - params string[] parameters - ) - { - // can't validate anything for the type of a null - if (node is not null) - { - string msg = string.Format(CultureInfo.CurrentCulture, message, parameters); - - // While this is not a comprehensive list. it covers the most common mistakes directly - Assert.IsNotInstanceOfType(node, msg); - Assert.IsNotInstanceOfType(node, msg); - Assert.IsNotInstanceOfType(node, msg); - } - } - - /// - /// Extension method for use with to validate that all - /// are either or - /// - /// Unused, provides extension support - /// Step of the run to test - /// Tracking name to use in assertion messages on failures - public static void OutputsCachedOrUnchanged( - this Assert _, - IncrementalGeneratorRunStep runStep, - string stepTrackingName - ) - { - Assert.IsFalse( - runStep.Outputs.Any(x => x.Reason != IncrementalStepRunReason.Cached && x.Reason != IncrementalStepRunReason.Unchanged), - $"{stepTrackingName} should have only cached or unchanged reasons!" - ); - } - - /// Extension method to validate that the output of a doesn't use any banned types. - /// [Ignored] - /// Run step to validate - /// Name of the step to aid in diagnostics - /// - /// - /// It is debatable if this should be used in a test or an analyzer. In a test it is easy - /// to omit from the tests (or not test at all in early development cycles). - /// An analyzer can operate as you type code in the editor or when you compile the code so - /// has a greater chance of catching erroneous use. Unfortunately no such analyzer exists - /// as of yet. [It's actually hard to define the rules an analyzer should follow]. So this - /// will do the best it can for now... - /// - public static void ObjectGraphContainsValidSymbols( - this Assert _, - IncrementalGeneratorRunStep runStep, - string stepTrackingName - ) - { - // Including the stepTrackingName in error messages to make it easier to isolate issues - const string because = "Step shouldn't contain banned symbols or non-equatable types. [{0}; {1}]"; - var visited = new HashSet(); - - // Check all of the outputs - probably overkill, but why not - foreach (var (obj, _) in runStep.Outputs) - { - Visit(obj, visited, because, stepTrackingName, runStep.Name ?? ""); - } - - // Private static function to recursively validate an object is cacheable - static void Visit( - object? node, - HashSet visitedNodes, - [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string message, - params string[] parameters - ) - { - // If we've already seen this object, or it's null, stop. - if (node is null || !visitedNodes.Add(node)) - { - return; - } - - Assert.That.NotBannedType(node, message, parameters); - - // Skip basic types and anything equatable, this includes - // any equatable collections such as EquatableArray as - // that implies all elements are equatable already. - // For now equatable type skipping is disabled as testing for - // that is complex... - Type type = node.GetType(); - if (type.IsBasicType() /*|| type.IsEquatable()*/) - { - return; - } - - // If the object is a collection, check each of the values - if (node is IEnumerable collection and not string) - { - foreach (object element in collection) - { - // recursively check each element in the collection - Visit(element, visitedNodes, message, parameters); - } - } - else - { - // Recursively check each field in the object - foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) - { - object? fieldValue = field.GetValue(node); - Visit(fieldValue, visitedNodes, message, parameters); - } - } - } - } - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/ReflectionTypeExtensions.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/ReflectionTypeExtensions.cs deleted file mode 100644 index 48a97d8ca..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/ReflectionTypeExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - /// Utility extensions for runtime reflection types - public static class ReflectionTypeExtensions - { - /// Simple type test to determine if a type is a basic type - /// Type to test - /// if the type is a basic type; if not - /// - /// A basic type for the purposes of this test is one that is a primitive, enum, or string. - /// - public static bool IsBasicType(this Type t) - { - return t.IsPrimitive - || t.IsEnum - || t == typeof(string); - } - -#if USE_ISEQUATABLE - /// Tests if a type is equatable - /// Type to tests - /// if the type is equatable; if not - /// - /// The definition of equatable is not fully understood, so at present this ALWAYS returns false. - /// However, this acts as a place holder for when it is determined how to accomplish this. Detection - /// of equatable is a test optimization so does not impact the correctness of a test - only the cost - /// to run it. - /// - public static bool IsEquatable(this Type _) - { - // TODO: Figure out how to validate that node implements IEquatable where T is - // some type in the object hierarchy of 'node'. In a test this would require - // full reflection of the type of node to get the full hierarchy and then - // test that an implementation of IEquatable exists for that type. Ideally - // this should go in deepest hierarchy first ordering as it is most likely - // implemented at the lowest layer for an immediate base type. (Though - // technically it could be at any level, that's the most likely case so, for - // efficiency, test it first) - // For now, just assume it isn't and skip the optimization... - return false; - } -#endif - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/ResourceSourceTextDictionary.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/ResourceSourceTextDictionary.cs deleted file mode 100644 index cdb729570..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/ResourceSourceTextDictionary.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Reflection; -using System.Resources; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; - -#pragma warning disable SA1316 // Tuple element names should use correct casing - -namespace Ubiquity.NET.SourceGenerator.Test.Utils -{ - /// Dictionary to map a resource name to information about expected generated files - /// Type of the generator - public class ResourceSourceTextDictionary - : IReadOnlyDictionary - where TGenerator : IIncrementalGenerator - { - /// Initializes a new instance of the class. - /// Assembly that contains the resources - /// Transform function to convert the resource name into a generated file name - /// - /// If is then the default is to use a C# transformation - /// that assumes the input resource name is a file name and extracts the file name (without extension) and adds - /// a '.g.cs' to the end. This is the most common, if a test requires something different then it can provide - /// a transformation function for - /// - public ResourceSourceTextDictionary( Func? nameTransform = null) - { - GeneratorAssembly = typeof(TGenerator).Assembly; - NameTransform = nameTransform ?? CSharpNameTransform; - } - - /// Gets the containing assembly for the resources - public Assembly GeneratorAssembly { get; } - - /// Adds a named resource to the map - /// name of the resource in the assembly - /// This instance for fluent use - /// resource is missing in the resources (programming error) - public ResourceSourceTextDictionary Add(string resourceName) - { - // {GeneratorAssemblyName}\{Generator FQN}\{GeneratorNamespace}.{HintName} - // Is this the assembly name or the namespace name? (In first use case they are the same) - // It makes sense that Roslyn generators use the assembly name. - string generatorAssemblyName = GeneratorAssembly.GetName().Name ?? throw new InvalidOperationException("Internal Error: Generator assembly has no name!"); - string generatorFQN = typeof(TGenerator).FullName ?? throw new InvalidOperationException("Internal Error: Generator type has no FQN!"); - string generatorNamespace = typeof(TGenerator).Namespace ?? throw new InvalidOperationException("Internal Error: Generator type has no namespace!"); - string fullResourceName = string.Join('.', generatorNamespace, resourceName); - string fullGeneratedSource = Path.Combine(generatorAssemblyName, generatorFQN,generatorNamespace, resourceName); - using Stream stream = GeneratorAssembly.GetManifestResourceStream(fullResourceName) - ?? throw new MissingManifestResourceException($"GENERATOR BUG: Missing resource '{fullResourceName}'"); - - string expectedGeneratedName = NameTransform(fullGeneratedSource); - var srcTxt = SourceText.From(stream, throwIfBinaryDetected: true, canBeEmbedded: true); - InnerDictionary.Add(resourceName, (expectedGeneratedName, srcTxt)); - return this; - } - - /// Adds a resource file name and information - /// Name of the manifest resource - /// Generated file information for the resource - public void Add(string resourceName, (string filename, SourceText content) generatedFile) - { - InnerDictionary.Add(resourceName, generatedFile); - } - - /// - public (string filename, SourceText content) this[ string key ] => InnerDictionary[ key ]; - - /// - public IEnumerable Keys => InnerDictionary.Keys; - - /// - public IEnumerable<(string filename, SourceText content)> Values => InnerDictionary.Values; - - /// - public int Count => InnerDictionary.Count; - - /// - public bool ContainsKey( string key ) - { - return InnerDictionary.ContainsKey( key ); - } - - /// - public IEnumerator> GetEnumerator( ) - { - return InnerDictionary.GetEnumerator(); - } - - /// - public bool TryGetValue( string key, [MaybeNullWhen( false )] out (string filename, SourceText content) value ) - { - return InnerDictionary.TryGetValue( key, out value ); - } - - /// - IEnumerator IEnumerable.GetEnumerator( ) - { - return InnerDictionary.GetEnumerator(); - } - - private readonly Func NameTransform; - - private readonly Dictionary InnerDictionary = []; - - private string CSharpNameTransform(string resourceName) - { - return $"{Path.GetDirectoryName(resourceName)}.{Path.GetFileNameWithoutExtension(resourceName)}.g.cs"; - } - } -} diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/Ubiquity.NET.SourceGenerator.Test.Utils.csproj b/src/Ubiquity.NET.SourceGenerator.Test.Utils/Ubiquity.NET.SourceGenerator.Test.Utils.csproj deleted file mode 100644 index 24020f75a..000000000 --- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/Ubiquity.NET.SourceGenerator.Test.Utils.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net9.0 - enable - - - - - - - - - - - - - - - - - - diff --git a/src/Ubiquity.NET.SrcGeneration.UT/AssemblyInfo.cs b/src/Ubiquity.NET.SrcGeneration.UT/AssemblyInfo.cs deleted file mode 100644 index 8b27dbe18..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Runtime.InteropServices; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customise this process see: https://aka.ms/assembly-info-properties - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. - -[assembly: Guid("f78531f5-3836-4e03-a1b1-59c9cac29d3d")] - -// Tests are so trivial they perform better when not individually parallelized. -// Unfortunately this is an assembly wide choice and not class or method level -// see: https://github.com/microsoft/testfx/issues/5555#issuecomment-3448956323 -[assembly: Parallelize( Scope = ExecutionScope.ClassLevel )] - -// can't use this at assembly level as it isn't supported there for downlevel... [Sigh...] -//[assembly: ExcludeFromCodeCoverage] - -// NOTE: use of this and `internal` test classes results in a flurry of -// error CA1812: '' is an internal class that is apparently never instantiated. If so, remove the code from the assembly. -// If this class is intended to contain only static members, make it 'static' (Module in Visual Basic). -// (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1812) -// In other words, not worth the bother... -// [assembly: DiscoverInternals] diff --git a/src/Ubiquity.NET.SrcGeneration.UT/CSharp/IndentedTestWriterExtensionTests.cs b/src/Ubiquity.NET.SrcGeneration.UT/CSharp/IndentedTestWriterExtensionTests.cs deleted file mode 100644 index 38265370f..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/CSharp/IndentedTestWriterExtensionTests.cs +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using Ubiquity.NET.SrcGeneration.CSharp; - -// simplification for self==null test cases -using CsExtensions = Ubiquity.NET.SrcGeneration.CSharp.IndentedTextWriterExtensions; - -namespace Ubiquity.NET.SrcGeneration.UT.CSharp -{ - [TestClass] - [ExcludeFromCodeCoverage] - public class IndentedTestWriterExtensionTests - { - #region Basic API Validation -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - [TestMethod] - public void Methods_report_exception_with_invalid_input( ) - { - var ex = Assert.ThrowsExactly(()=>CsExtensions.WriteAutoGeneratedComment(null, "tool", "version")); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.WriteAutoGeneratedCommentBlock(null, "tool", "version"); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.Namespace(null, "My.Namespace"); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.Struct(null, "access", "StructName"); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.UnsafeScope(null); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.Scope(null); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => CsExtensions.MultiLineComment( null, null ) ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = CsExtensions.Class(null, "access", "ClassName"); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - // additional param checks (not "self") - using var writer = new OwningIndentedStringWriter(); - ex = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( null, "version" ) ); - Assert.AreEqual( "toolName", ex.ParamName ); - - var argEx = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( string.Empty, "version" ) ); - Assert.AreEqual( "toolName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( " \t ", "version" ) ); - Assert.AreEqual( "toolName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( "tool", null ) ); - Assert.AreEqual( "toolVersion", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( "tool", string.Empty ) ); - Assert.AreEqual( "toolVersion", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => writer.WriteAutoGeneratedComment( "tool", " \t " ) ); - Assert.AreEqual( "toolVersion", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( null, "version"); - } ); - Assert.AreEqual( "toolName", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( string.Empty, "version"); - } ); - Assert.AreEqual( "toolName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( " \t ", "version"); - } ); - Assert.AreEqual( "toolName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( "tool", null); - } ); - Assert.AreEqual( "toolVersion", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( "tool", string.Empty); - } ); - Assert.AreEqual( "toolVersion", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.WriteAutoGeneratedCommentBlock( "tool", " \t "); - } ); - Assert.AreEqual( "toolVersion", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Namespace( null); - } ); - Assert.AreEqual( "namespaceName", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Namespace( string.Empty); - } ); - Assert.AreEqual( "namespaceName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Namespace( " \t "); - } ); - Assert.AreEqual( "namespaceName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Struct( null, null); - } ); - Assert.AreEqual( "structName", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Struct( null, string.Empty); - } ); - Assert.AreEqual( "structName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Struct( null, " \t "); - } ); - Assert.AreEqual( "structName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Class( null, null); - } ); - Assert.AreEqual( "className", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Class(null, string.Empty); - } ); - Assert.AreEqual( "className", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => - { - using var scope = writer.Class( null, " \t "); - } ); - Assert.AreEqual( "className", argEx.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - #endregion - - [TestMethod] - public void WriteAutoGeneratedComment_succeeds( ) - { - const string expected = """ - // ------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // MyTool [1.2.3.4] - // - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - // ------------------------------------------------------------------------------ - """; - - using var writer = new OwningIndentedStringWriter(); - writer.WriteAutoGeneratedComment("MyTool", "1.2.3.4"); - Assert.AreEqual(expected, writer.ToString()); - } - - [TestMethod] - public void WriteAutoGeneratedComment_with_source_succeeds( ) - { - const string expected = """ - // ------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // MyTool [1.2.3.4] - // From: C:\SomeDir\MyFile.cs - // - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - // ------------------------------------------------------------------------------ - """; - - using var writer = new OwningIndentedStringWriter(); - writer.WriteAutoGeneratedComment( "MyTool", "1.2.3.4", @"C:\SomeDir\MyFile.cs" ); - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void WriteAutoGeneratedCommentBlock_succeeds( ) - { - const string expected = """ - // ------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // MyTool [1.2.3.4] - // - // This is a test - // - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - // ------------------------------------------------------------------------------ - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.WriteAutoGeneratedCommentBlock( "MyTool", "1.2.3.4" )) - { - writer.WriteLine("//"); - writer.WriteLine("// This is a test"); - writer.WriteLine("//"); - } - - Assert.AreEqual(expected, writer.ToString()); - } - - [TestMethod] - public void Namespace_succeeds( ) - { - const string expected = """ - namespace My.Namespace - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.Namespace( "My.Namespace" )) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void Struct_succeeds( ) - { - const string expected = """ - public partial struct MyStruct - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.Struct("public partial", "MyStruct" )) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void UnsafeScope_succeeds( ) - { - const string expected = """ - unsafe - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.UnsafeScope()) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void Scope_with_leading_succeeds( ) - { - const string expected = """ - // C# scope - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.Scope( "// C# scope" )) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void Scope_without_leading_succeeds( ) - { - const string expected = """ - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.Scope( )) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void MultiLineComment_with_null_or_empty_text_is_nop( ) - { - using var writer = new OwningIndentedStringWriter(); - writer.MultiLineComment(null); - Assert.AreEqual(0, ((StringWriter)writer.InnerWriter).GetStringBuilder().Length); - - writer.MultiLineComment( string.Empty ); - Assert.AreEqual( 0, ((StringWriter)writer.InnerWriter).GetStringBuilder().Length ); - - writer.MultiLineComment( " \t " ); - Assert.AreEqual( 0, ((StringWriter)writer.InnerWriter).GetStringBuilder().Length ); - } - - [TestMethod] - public void MultiLineComment_succeeds( ) - { - const string expected = """ - /* - This is a test... - of a multi-line comment - */ - """; - - using var writer = new OwningIndentedStringWriter(); - writer.MultiLineComment( string.Join(Environment.NewLine, ["This is a test...", "of a multi-line comment"] )); - - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void Class_succeeds( ) - { - const string expected = """ - public partial class MyClass - { - // This is a test... - } - """; - - using var writer = new OwningIndentedStringWriter(); - using(writer.Class( "public partial", "MyClass" )) - { - writer.WriteLine( "// This is a test..." ); - } - - Assert.AreEqual( expected, writer.ToString() ); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/CSharp/TextWriterExtensionsTests.cs b/src/Ubiquity.NET.SrcGeneration.UT/CSharp/TextWriterExtensionsTests.cs deleted file mode 100644 index 06ffdcbad..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/CSharp/TextWriterExtensionsTests.cs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using Ubiquity.NET.SrcGeneration.CSharp; - -namespace Ubiquity.NET.SrcGeneration.UT.CSharp -{ - [TestClass] - [ExcludeFromCodeCoverage] - public class TextWriterExtensionsTests - { - #region Basic API Validation -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - /// Validates extension APIs handle nulls, empty strings, etc... - [TestMethod] - public void ExtensionMethods_throw_on_null_or_whitespace_args( ) - { - var ex = Assert.ThrowsExactly(()=>TextWriterExtensions.WriteAttribute( null, "fooAttrib" )); - Assert.AreEqual( "self", ex.ParamName ); - - Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteAttributeLine( null, "fooAttrib" ) ); - Assert.AreEqual( "self", ex.ParamName ); - - Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteRemarksComment( null, null ) ); - Assert.AreEqual( "self", ex.ParamName ); - - Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteSummaryAndRemarksComments( null, null ) ); - Assert.AreEqual( "self", ex.ParamName ); - - Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteSummaryComment( null, null ) ); - Assert.AreEqual( "self", ex.ParamName ); - - Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteUsingDirective( null, string.Empty ) ); - Assert.AreEqual( "self", ex.ParamName ); - - using var writer = new StringWriter(); - ex = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteSummaryAndRemarksComments( writer, "non-null remarks", null ) ); - Assert.AreEqual( "defaultSummary", ex.ParamName ); - - var argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteSummaryAndRemarksComments( writer, "non-null remarks", " \t " ) ); - Assert.AreEqual( "defaultSummary", argEx.ParamName ); - - ex = Assert.ThrowsExactly( () => TextWriterExtensions.WriteAttributeLine(writer, null)); - Assert.AreEqual("attributeName", ex.ParamName); - - argEx = Assert.ThrowsExactly( () => TextWriterExtensions.WriteAttributeLine(writer, string.Empty)); - Assert.AreEqual( "attributeName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteAttributeLine( writer, " " ) ); - Assert.AreEqual( "attributeName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( () => TextWriterExtensions.WriteAttribute(writer, null)); - Assert.AreEqual( "attributeName", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteAttribute( writer, string.Empty ) ); - Assert.AreEqual( "attributeName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteAttribute( writer, " " ) ); - Assert.AreEqual( "attributeName", argEx.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteUsingDirective( writer, null ) ); - Assert.AreEqual( "namespaceName", ex.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteUsingDirective( writer, string.Empty ) ); - Assert.AreEqual( "namespaceName", argEx.ParamName ); - - argEx = Assert.ThrowsExactly( ( ) => TextWriterExtensions.WriteUsingDirective( writer, " " ) ); - Assert.AreEqual( "namespaceName", argEx.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - #endregion - - [TestMethod] - public void WriteAttributeLine_with_no_args_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteAttributeLine("TestIt"); - Assert.AreEqual("[TestIt]" + Environment.NewLine, writer.ToString()); - - writer.GetStringBuilder().Clear(); - writer.WriteAttributeLine("AnotherAttribute"); - Assert.AreEqual( "[AnotherAttribute]" + Environment.NewLine, writer.ToString() ); - } - - [TestMethod] - public void WriteAttribute_with_no_args_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteAttribute( "TestIt" ); - Assert.AreEqual( "[TestIt]", writer.ToString() ); - - writer.GetStringBuilder().Clear(); - writer.WriteAttribute( "AnotherAttribute" ); - Assert.AreEqual( "[AnotherAttribute]", writer.ToString() ); - } - - [TestMethod] - public void WriteAttributeLine_with_args_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteAttributeLine( "TestIt", "baz", "foo=bar" ); - Assert.AreEqual( "[TestIt(baz, foo=bar)]" + Environment.NewLine, writer.ToString() ); - - writer.GetStringBuilder().Clear(); - writer.WriteAttributeLine( "AnotherAttribute", "baz:123", "foo=bar" ); - Assert.AreEqual( "[AnotherAttribute(baz:123, foo=bar)]" + Environment.NewLine, writer.ToString() ); - } - - [TestMethod] - public void WriteAttribute_with_args_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteAttribute( "TestIt", "baz", "foo=bar" ); - Assert.AreEqual( "[TestIt(baz, foo=bar)]", writer.ToString() ); - - writer.GetStringBuilder().Clear(); - writer.WriteAttribute( "AnotherAttribute", "baz:123", "foo=bar" ); - Assert.AreEqual( "[AnotherAttribute(baz:123, foo=bar)]", writer.ToString() ); - } - - [TestMethod] - public void WriteSummaryContent_with_null_or_empty_description_is_nop( ) - { - using var writer = new StringWriter(); - writer.WriteSummaryComment( null ); - Assert.AreEqual(0, writer.GetStringBuilder().Length); - - writer.WriteSummaryComment( string.Empty ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length ); - } - - [TestMethod] - public void WriteSummaryContent_with_description_succeeds( ) - { - // NOTE: Literal strings requires a blank line to indicate a line terminator. - // Otherwise, the compiler generates a string without a terminating new line! - const string expected = """ - /// description of this API - - """; - - using var writer = new StringWriter(); - writer.WriteSummaryComment( "description of this API" ); - Assert.AreEqual( expected, writer.ToString() ); - - // ensure trimming is applied correctly - writer.GetStringBuilder().Clear(); - writer.WriteSummaryComment( " \t description of this API " ); - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void WriteRemarksComment_with_null_or_empty_txt_is_nop( ) - { - using var writer = new StringWriter(); - writer.WriteRemarksComment( null ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length ); - - writer.WriteRemarksComment( string.Empty ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length ); - } - - [TestMethod] - public void WriteRemarksComment_with_txt_succeeds( ) - { - const string input = """ - This is a remarks line - - - This is another discrete line. The preceding duplicate blank is removed. - """; - - // NOTE: Literal strings requires a blank line to indicate a line terminator. - // Otherwise, the compiler generates a string without a terminating new line! - const string expected = """ - /// - /// This is a remarks line - /// - /// This is another discrete line. The preceding duplicate blank is removed. - /// - - """; - - using var writer = new StringWriter(); - writer.WriteRemarksComment( input ); - Assert.AreEqual( expected, writer.ToString() ); - } - - [TestMethod] - public void WriteSummaryAndRemarksComments_with_null_or_empty_txt_is_nop( ) - { - using var writer = new StringWriter(); - writer.WriteSummaryAndRemarksComments( null ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "null should be nop" ); - - writer.WriteSummaryAndRemarksComments( string.Empty ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "empty string should be NOP" ); - - writer.WriteSummaryAndRemarksComments( " \t " ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "All whitespace should be nop" ); - } - - [TestMethod] - public void WriteSummaryAndRemarksComments_with_valid_inputs_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteSummaryAndRemarksComments( null ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "null should be nop" ); - - writer.WriteSummaryAndRemarksComments( string.Empty ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "empty string should be NOP" ); - - writer.WriteSummaryAndRemarksComments( " \t " ); - Assert.AreEqual( 0, writer.GetStringBuilder().Length, "All whitespace should be nop" ); - - const string inputRemarks = """ - This is a remarks line - - - This is another discrete line. The preceding duplicate blank is removed. - """; - - const string defaultSummary = " \t description of this API "; - - writer.WriteSummaryAndRemarksComments(inputRemarks, defaultSummary); - - // NOTE: Literal strings requires a blank line to indicate a line terminator. - // Otherwise, the compiler generates a string without a terminating new line! - const string expected = """ - /// description of this API - /// - /// This is a remarks line - /// - /// This is another discrete line. The preceding duplicate blank is removed. - /// - - """; - Assert.AreEqual(expected, writer.ToString()); - } - - [TestMethod] - public void WriteUsingDirective_succeeds( ) - { - using var writer = new StringWriter(); - writer.WriteUsingDirective( "My.Namespace" ); - string expected = "using My.Namespace;" + Environment.NewLine; - Assert.AreEqual(expected, writer.ToString()); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/GlobalNamespaceImports.cs b/src/Ubiquity.NET.SrcGeneration.UT/GlobalNamespaceImports.cs deleted file mode 100644 index edc70b57e..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/GlobalNamespaceImports.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.CodeDom.Compiler; -global using System.Collections.Generic; -global using System.Diagnostics.CodeAnalysis; -global using System.Globalization; -global using System.IO; - -global using Microsoft.VisualStudio.TestTools.UnitTesting; - -global using Ubiquity.NET.Extensions; diff --git a/src/Ubiquity.NET.SrcGeneration.UT/GlobalSuppressions.cs b/src/Ubiquity.NET.SrcGeneration.UT/GlobalSuppressions.cs deleted file mode 100644 index 9871da584..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/GlobalSuppressions.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Test assembly" )] diff --git a/src/Ubiquity.NET.SrcGeneration.UT/IndentedTextWriterExtensionsTests.cs b/src/Ubiquity.NET.SrcGeneration.UT/IndentedTextWriterExtensionsTests.cs deleted file mode 100644 index 895b3570f..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/IndentedTextWriterExtensionsTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration.UT -{ - [TestClass] - [ExcludeFromCodeCoverage] - public sealed class IndentedTextWriterExtensionsTests - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // nullability checks is the point of this test... - [TestMethod] - public void WriteEmptyLine_throws_if_null( ) - { - var ex = Assert.ThrowsExactly(()=>IndentedTextWriterExtensions.WriteEmptyLine(null)); - Assert.AreEqual( "self", ex.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void WriteEmptyLine_writes_a_blank_line_without_indentation( ) - { - using var writer = new OwningIndentedStringWriter(); - writer.WriteLine("line1"); - IndentedTextWriterExtensions.WriteEmptyLine(writer); - writer.Write("line2 [no trailing newline]"); - Assert.AreEqual( TestEmptyLine, writer.ToString() ); - } - -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // nullability checks is the point of this test... - [TestMethod] - public void PushIndent_throws_if_null( ) - { - var ex = Assert.ThrowsExactly(()=> - { - using var x = IndentedTextWriterExtensions.PushIndent(null); - }); - Assert.AreEqual( "self", ex.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void PushIndent_correctly_pushes_indentation_level( ) - { - using var writer = new OwningIndentedStringWriter(); - Assert.AreEqual( 0, writer.Indent, "SANITY: Writer should start at level 0" ); - using(var scope1 = IndentedTextWriterExtensions.PushIndent( writer )) - { - Assert.AreEqual( 1, writer.Indent, "Indentation level should be +1 while indented" ); - using(var scope2 = IndentedTextWriterExtensions.PushIndent( writer )) - { - Assert.AreEqual( 2, writer.Indent, "Indentation level should be +1 while indented" ); - } - - Assert.AreEqual( 1, writer.Indent, "Indentation level should be -1 after indentation" ); - } - - Assert.AreEqual( 0, writer.Indent, "Disposal of scope should result in indentation level of 0" ); - } - -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // nullability checks is the point of this test... - [TestMethod] - public void Block_throws_if_null( ) - { - var ex = Assert.ThrowsExactly(()=> - { - using var x = IndentedTextWriterExtensions.Block(null, "{","}"); - }); - Assert.AreEqual( "self", ex.ParamName ); - - using var writer = new OwningIndentedStringWriter(); - ex = Assert.ThrowsExactly( ( ) => - { - using var x = IndentedTextWriterExtensions.Block(writer, null,"}"); - } ); - - Assert.AreEqual( "open", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - using var x = IndentedTextWriterExtensions.Block(writer, "{",null); - } ); - - Assert.AreEqual( "close", ex.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void Block_produces_correct_results_for_defaults( ) - { - using var writer = new OwningIndentedStringWriter(); - using(var scope = IndentedTextWriterExtensions.Block( writer, "begin", "end" )) - { - writer.WriteLine( "test line" ); - } - - Assert.AreEqual( 0, writer.Indent ); - Assert.AreEqual( TestBlock, writer.ToString()); - } - - [TestMethod] - public void Block_produces_correct_results_with_leading_line( ) - { - using var writer = new OwningIndentedStringWriter(); - using(var scope = IndentedTextWriterExtensions.Block( writer, "begin", "end", "" )) - { - writer.WriteLine( "test line" ); - } - - Assert.AreEqual( 0, writer.Indent ); - Assert.AreEqual( TestBlockWithLeadingLine, writer.ToString() ); - } - - [TestMethod] - public void Block_produces_correct_results_with_leading_line_no_indentation( ) - { - using var writer = new OwningIndentedStringWriter(); - using(var scope = IndentedTextWriterExtensions.Block( writer, "begin", "end", "", indented: false )) - { - writer.WriteLine( "test line" ); - } - - Assert.AreEqual( 0, writer.Indent ); - Assert.AreEqual( TestBlockWithLeadingLineNoIndentation, writer.ToString() ); - } - - [TestMethod] - public void Block_produces_correct_results_with_no_indentation( ) - { - using var writer = new OwningIndentedStringWriter(); - using(var scope = IndentedTextWriterExtensions.Block( writer, "begin", "end", indented: false )) - { - writer.WriteLine( "test line" ); - } - - Assert.AreEqual( 0, writer.Indent ); - Assert.AreEqual( TestBlockWithNoIndentation, writer.ToString() ); - } - - private const string TestBlock = """ - begin - test line - end - """; - - private const string TestBlockWithLeadingLine = """ - - begin - test line - end - """; - - private const string TestBlockWithLeadingLineNoIndentation = """ - - begin - test line - end - """; - - private const string TestBlockWithNoIndentation = """ - begin - test line - end - """; - - private const string TestEmptyLine = """ - line1 - - line2 [no trailing newline] - """; - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/OwningIndentedStringWriter.cs b/src/Ubiquity.NET.SrcGeneration.UT/OwningIndentedStringWriter.cs deleted file mode 100644 index d874130ea..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/OwningIndentedStringWriter.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration.UT -{ - // simple owning indented writer using a StringWriter, this is NOT generalized due to the potential - // confusion over the ownership when an exception occurs during construction of this type. (It isn't - // actually moved in such a case - but safe/correct handling of that in general is rather complicated.) - // In a test scenario, an exception in the constructor will crash the test host and treated as a test - // failure. This is desired behavior, and extremely unlikely to ever occur, so OK in this special case. - [SuppressMessage( "StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "DUH, it's file scoped" )] - [ExcludeFromCodeCoverage] - internal class OwningIndentedStringWriter - : IndentedTextWriter - { - [SuppressMessage( "Reliability", "CA2000:Dispose objects before losing scope", Justification = "Owned, move semantics, disposed of in Dispose" )] - public OwningIndentedStringWriter( string? tabString = null ) - : base( new StringWriter( CultureInfo.CurrentCulture ), tabString ?? DefaultTabString ) - { - } - - protected override void Dispose( bool disposing ) - { - if(disposing) - { - InnerWriter.Dispose(); - } - - base.Dispose( disposing ); - } - - public override string? ToString( ) - { - return InnerWriter.ToString(); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs b/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs deleted file mode 100644 index 8175fdaa8..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Collections.Immutable; - -namespace Ubiquity.NET.SrcGeneration.UT -{ - [SuppressMessage( "StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Record" )] - [ExcludeFromCodeCoverage] - internal readonly record struct LineData( string Input, ImmutableArray Expected ); - - [TestClass] - [ExcludeFromCodeCoverage] - public class StringExtensionTests - { - public TestContext TestContext { get; set; } - -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - // tests are intended to VALIDATE nullability checks in implementation - [TestMethod] - public void Extensions_throw_if_null_self( ) - { - var ex = Assert.ThrowsExactly(()=> - { - StringExtensions.GetCommentLines( null ); - } ); - Assert.AreEqual("self", ex.ParamName); - - ex = Assert.ThrowsExactly(()=> - { - StringExtensions.EscapeComment( null ); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - StringExtensions.SplitLines( null ); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - StringExtensions.MakeXmlSafe( null ); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - StringExtensions.EscapeForComment( null ); - } ); - Assert.AreEqual( "self", ex.ParamName ); - - ex = Assert.ThrowsExactly( ( ) => - { - var strings = StringExtensions.SkipDuplicates( null ); - } ); - - Assert.AreEqual( "self", ex.ParamName ); - } -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - - [TestMethod] - public void GetCommentLines_splits_string_correctly( ) - { - // default behavior is StringSplitOptions2.TrimEntries - verify that - var testData = GetCommentLinesTestData( StringSplitOptions2.TrimEntries ); - var actual = StringExtensions.GetCommentLines(testData.Input).ToImmutableArray(); - VerifyLineData( testData.Expected, actual ); - } - - [TestMethod] - [DataRow( StringSplitOptions2.None )] - [DataRow( StringSplitOptions2.RemoveEmptyEntries )] - [DataRow( StringSplitOptions2.TrimEntries )] - [DataRow( StringSplitOptions2.TrimEntries | StringSplitOptions2.RemoveEmptyEntries )] - public void GetCommentLines_with_options( StringSplitOptions2 options ) - { - var testData = GetCommentLinesTestData( options ); - var actual = StringExtensions.GetCommentLines(testData.Input, options).ToImmutableArray(); - VerifyLineData( testData.Expected, actual ); - } - - [TestMethod] - public void EscapeComment_handles_newline_escapes( ) - { - const string input = "line0\\nline1\\nline2"; - string expected = "line0" + Environment.NewLine + "line1" + Environment.NewLine + "line2"; - string actual = StringExtensions.EscapeComment(input); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void SplitLines_default_option_is_none( ) - { - var testData = GetSplitLinesTestData(StringSplitOptions2.None); - var actual = StringExtensions.SplitLines(testData.Input).ToImmutableArray(); - VerifyLineData(testData.Expected, actual); - } - - [TestMethod] - [DataRow( StringSplitOptions2.None )] - [DataRow( StringSplitOptions2.RemoveEmptyEntries )] - [DataRow( StringSplitOptions2.TrimEntries )] - [DataRow( StringSplitOptions2.TrimEntries | StringSplitOptions2.RemoveEmptyEntries )] - public void SplitLines_with_options( StringSplitOptions2 options ) - { - var testData = GetSplitLinesTestData( options ); - var actual = StringExtensions.SplitLines(testData.Input, options).ToImmutableArray(); - VerifyLineData( testData.Expected, actual ); - } - - [TestMethod] - public void MakeXmlSafe_handles_XML_Conversion( ) - { - Assert.AreEqual("&", StringExtensions.MakeXmlSafe("&")); - Assert.AreEqual( "<text2>", StringExtensions.MakeXmlSafe("")); - - // additional escapes are testable but it comes down to - // testing XText(...).ToString() at that point... - } - - [TestMethod] - public void SkipDuplicates_skips_any_duplicate_line( ) - { - ImmutableArray input = [ - "text", - "text2", - "text2", // duplicate of previous line - expect removed, - "text", // duplicate of first line, but not previous - expect remains - ]; - - ImmutableArray expected = [ - "text", - "text2", - /* "text2", // duplicate of previous line - expect removed, */ - "text", // duplicate of first line, but not previous - expect remains - ]; - - var actual = StringExtensions.SkipDuplicates(input).ToImmutableArray(); - VerifyLineData(expected, actual); - } - - [TestMethod] - public void EscapeForComment_escapes_each_string( ) - { - ImmutableArray input = [ - "text\\npart2", - "text2", - "text2", - "text\\n", - "\\ntext" - ]; - - ImmutableArray expected = [ - "text" + Environment.NewLine + "part2", - "text2", - "text2", - "text" + Environment.NewLine, - Environment.NewLine + "text", - ]; - - var actual = StringExtensions.EscapeForComment(input).ToImmutableArray(); - VerifyLineData( expected, actual ); - } - - [TestMethod] - public void EscapeForXML_escapes_each_string( ) - { - ImmutableArray input = [ - "&this or that", - "", - "this or &that", - ]; - - ImmutableArray expected = [ - "&this or that", - "<text2>", - "this or &that", - ]; - - var actual = StringExtensions.EscapeForXML(input).ToImmutableArray(); - VerifyLineData( expected, actual ); - } - - private void VerifyLineData( ImmutableArray expected, ImmutableArray actual ) - { - TestContext.Report( "expected", expected); - TestContext.Report( "actual", actual); - Assert.HasCount( expected.Length, actual ); - for(int i = 0; i < expected.Length; ++i) - { - Assert.AreEqual( expected[ i ], actual[ i ], $"Mismatch on element {i}" ); - } - } - - private static LineData GetCommentLinesTestData( StringSplitOptions2 options ) - { - // determine expected results based on options - string line0 = "Comment line0"; - string line1 = " s e "; - string line2 = " "; - string line3 = string.Empty; - string line4 = "Comment line4"; - - string testInput = string.Join(Environment.NewLine, line0, line1, line2, line3, line4); - - var bldr = ImmutableArray.CreateBuilder(); - - bldr.Add(line0); // never altered, always included. - - // if Trim entries is specified, then trim lines 1 & 2 - // These are the only input lines that can be trimmed. - if(options.HasFlag( StringSplitOptions2.TrimEntries )) - { - line1 = line1.Trim(); - line2 = line2.Trim(); - } - - // never empty, but might be trimmed - bldr.Add(line1); - - // line 2 & 3 might be empty strings and might be removed. - if(options.HasFlag( StringSplitOptions2.RemoveEmptyEntries )) - { - if(!string.IsNullOrEmpty( line2 )) - { - bldr.Add( line2 ); - } - - // IFF line3 is not empty AND not the same as previous line add it - // duplicates always removed. - if(!string.IsNullOrEmpty( line3 ) && (bldr[ ^1 ] != line3)) - { - bldr.Add( line3 ); - } - } - else - { - bldr.Add( line2 ); - - // IFF line3 is not the same as previous line add it; duplicates always removed. - if(bldr[ ^1 ] != line3) - { - bldr.Add( line3 ); - } - } - - bldr.Add( line4 ); - - return new(testInput, bldr.ToImmutable()); - } - - private static LineData GetSplitLinesTestData( StringSplitOptions2 options ) - { - // determine expected results based on options - string line0 = "Comment line0"; - string line1 = " s e "; - string line2 = " "; - string line3 = string.Empty; - string line4 = "Comment line4"; - - string testInput = string.Join(Environment.NewLine, line0, line1, line2, line3, line4); - - var bldr = ImmutableArray.CreateBuilder(); - - bldr.Add( line0 ); // never altered, always included. - - // if Trim entries is specified, then trim lines 1 & 2 - // These are the only input lines that can be trimmed. - if(options.HasFlag( StringSplitOptions2.TrimEntries )) - { - line1 = line1.Trim(); - line2 = line2.Trim(); - } - - // never empty, but might be trimmed - bldr.Add( line1 ); - - // line 2 & 3 might be empty strings and might be removed. - if(options.HasFlag( StringSplitOptions2.RemoveEmptyEntries )) - { - if(!string.IsNullOrEmpty( line2 )) - { - bldr.Add( line2 ); - } - - if(!string.IsNullOrEmpty( line3 )) - { - bldr.Add( line3 ); - } - } - else - { - bldr.Add( line2 ); - bldr.Add( line3 ); - } - - bldr.Add( line4 ); - - return new( testInput, bldr.ToImmutable() ); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs b/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs deleted file mode 100644 index 45a1e913f..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -using System.Collections.Immutable; - -namespace Ubiquity.NET.SrcGeneration.UT -{ - [ExcludeFromCodeCoverage] - internal static class TestContextExtensions - { - internal static void Report( this TestContext ctx, string title, ImmutableArray arrayVal ) - { - ctx.WriteLine( "{0}[{1}]", title, arrayVal.Length ); - for(int i = 0; i < arrayVal.Length; ++i) - { - ctx.WriteLine(" [{0}] = {1}", i, arrayVal[i]); - } - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration.UT/Ubiquity.NET.SrcGeneration.UT.csproj b/src/Ubiquity.NET.SrcGeneration.UT/Ubiquity.NET.SrcGeneration.UT.csproj deleted file mode 100644 index e31676d7d..000000000 --- a/src/Ubiquity.NET.SrcGeneration.UT/Ubiquity.NET.SrcGeneration.UT.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0;net481 - 12 - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs deleted file mode 100644 index d6116c7fb..000000000 --- a/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration.CSharp -{ - /// Support for generating source files in the C# language - public static class CSharpLanguage - { - /// Opening of a scope for C# - public const string ScopeOpen = "{"; - - /// Closing of a scope for C# - public const string ScopeClose = "}"; - - /// Gets the language keywords - /// - /// This is normally used from within - /// to escape keywords as identifiers. But is available for any use. - /// - public static ImmutableArray KeyWords { get; } - = [ // Source: Language spec. §6.4.4 Keywords - "abstract", - "as", - "base", - "bool", - "break", - "byte", - "case", - "catch", - "char", - "checked", - "class", - "const", - "continue", - "decimal", - "default", - "delegate", - "do", - "double", - "else", - "enum", - "event", - "explicit", - "extern", - "false", - "finally", - "fixed", - "float", - "for", - "foreach", - "goto", - "if", - "implicit", - "in", - "int", - "interface", - "internal", - "is", - "lock", - "long", - "namespace", - "new", - "null", - "object", - "operator", - "out", - "override", - "params", - "private", - "protected", - "public", - "readonly", - "ref", - "return", - "sbyte", - "sealed", - "short", - "sizeof", - "stackalloc", - "static", - "string", - "struct", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "uint", - "ulong", - "unchecked", - "unsafe", - "ushort", - "using", - "virtual", - "void", - "volatile", - "while" - ]; - - /// Makes an identifier (Escaping a language keyword) - /// identifier string to convert - /// Syntactically valid identifier - /// - /// Current implementation simplisticly performs keyword escaping AND - /// conversion of space to `'_'`. Specifically, it does NOT (yet anyway) - /// validate that the result satisfies the language definition of an - /// identifier (which limits the characters allowed and further restricts - /// the first such character) - /// - public static string MakeIdentifier( this string self ) - { - ArgumentNullException.ThrowIfNull( self ); - - // always replace invalid characters - // TODO: more sophisticated Regex that matches anything NOT a valid identifier char -#if NETSTANDARD2_0 - string retVal = self.Replace( " ", "_" ); -#else - string retVal = self.Replace( " ", "_", StringComparison.Ordinal ); -#endif - return KeyWords.Contains( self ) - ? $"@{retVal}" - : retVal; - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs deleted file mode 100644 index 122566395..000000000 --- a/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration.CSharp -{ - /// Utility extensions for an specific to the C# language - [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "extension" )] - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "extension" )] - public static class IndentedTextWriterExtensions - { - /// Writes a standard auto-generated comment block - /// Writer to apply extension method to - /// Name of the generating tool (included in comment) - /// Version of the tool (included in comment) - /// Source of the generated file [Default: null] - public static void WriteAutoGeneratedComment( this IndentedTextWriter self, string toolName, string toolVersion, string? source = null ) - { - ArgumentNullException.ThrowIfNull( self ); - - using var scope = self.WriteAutoGeneratedCommentBlock(toolName, toolVersion); - - if(!string.IsNullOrWhiteSpace( source )) - { - self.WriteLine( $"// From: {source}" ); - } - - self.WriteLine( "//" ); - } - - /// Writes a standard auto-generated comment block returning the scope for additional contents - /// Writer to apply extension method to - /// Name of the generating tool (included in comment) - /// Version of the tool (included in comment) - /// Disposable scope for the namespace (closes the scope on dispose) - /// - /// To remain syntactically valid all lines written to the writer within this block - /// MUST include a leading "//" (this does NOT alter the prefix requirement - /// for the writer). The resulting scope is NOT indented. - /// - public static IDisposable WriteAutoGeneratedCommentBlock( this IndentedTextWriter self, string toolName, string toolVersion ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentException.ThrowIfNullOrWhiteSpace( toolName ); - ArgumentException.ThrowIfNullOrWhiteSpace( toolVersion ); - - string open = $""" - // ------------------------------------------------------------------------------ - // - // This code was generated by a tool. - // {toolName} [{toolVersion}] - """; - - string close = """ - // Changes to this file may cause incorrect behavior and will be lost if - // the code is regenerated. - // - // ------------------------------------------------------------------------------ - """; - return self.Block( open, close, indented: false ); - } - - /// Writes a Namespace declaration scope - /// Writer to apply extension method to - /// Name of the namespace - /// Disposable scope for the namespace (closes the scope on dispose) - public static IDisposable Namespace( this IndentedTextWriter self, string namespaceName ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentException.ThrowIfNullOrWhiteSpace(namespaceName); - - return self.Scope( $"namespace {namespaceName}" ); - } - - /// Writes a C# struct declaration - /// Writer to apply extension method to - /// Access (prefix) for the struct declaration - /// name of the structure - /// Disposable scope that closes the struct declaration on - public static IDisposable Struct( this IndentedTextWriter self, string? access, string structName ) - { - ArgumentException.ThrowIfNullOrWhiteSpace( structName ); - - access ??= string.Empty; - return self.Scope( $"{access} struct {structName}" ); - } - - /// Write an "unsafe" region as a scope - /// Writer to apply extension method to - /// Disposable scope that closes the region declaration on - public static IDisposable UnsafeScope( this IndentedTextWriter self ) - { - return self.Scope( "unsafe" ); - } - - /// Write a scope as a region - /// Writer to apply extension method to - /// Leading line of the scope, if any. - /// Disposable scope that closes the struct declaration on - /// - /// is written on a line of it's own then - /// is written and the indentation is pushed and the returned disposer will outdent and write - /// . Thus any values written until the return is disposed is indented - /// automatically. - /// - public static IDisposable Scope( this IndentedTextWriter self, string? leadingLine = null ) - { - return self.Block( CSharpLanguage.ScopeOpen, CSharpLanguage.ScopeClose, leadingLine ); - } - - /// Writes as a multi-line comment - /// Writer to apply extension method to - /// Text of the comment - /// If is , empty, or all whitespace then this is a NOP - public static void MultiLineComment( this IndentedTextWriter self, string? txt ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(string.IsNullOrWhiteSpace(txt)) - { - return; - } - - string[] lines = [ .. txt!.GetCommentLines() ]; - if(lines.Length > 0) - { - using(self.Block( "/*", "*/" )) - { - foreach(string line in lines) - { - self.WriteLine( line ); - } - } - } - } - - /// Writes a C# class scope - /// Writer to apply extension method to - /// access modifier (prefix) for the declaration - /// Name of the class to generate - /// Disposable scope that closes the class declaration on - public static IDisposable Class( this IndentedTextWriter self, string? access, string className ) - { - ArgumentException.ThrowIfNullOrWhiteSpace( className ); - - access ??= string.Empty; - return self.Scope( $"{access} class {className}" ); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs deleted file mode 100644 index 1cb1f14f2..000000000 --- a/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration.CSharp -{ - /// Utility extensions for a specific to the C# language - [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "extension" )] - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "extension" )] - public static class TextWriterExtensions - { - /// Writes an attribute as a line - /// The writer to write to - /// Name of the attribute - /// arguments for the attribute - public static void WriteAttributeLine( this TextWriter self, string attributeName, params string[] attribArgs ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentException.ThrowIfNullOrWhiteSpace(attributeName); - - self.WriteAttribute( attributeName, attribArgs ); - self.WriteLine(); - } - - /// Writes an attribute to the specified writer - /// The writer to write to - /// Name of the attribute - /// arguments for the attribute - public static void WriteAttribute(this TextWriter self, string attributeName, params string[] attribArgs ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentException.ThrowIfNullOrWhiteSpace( attributeName ); - - self.Write( $"[{attributeName}" ); - if(attribArgs.Length > 0) - { - self.Write( $"({string.Join( ", ", attribArgs )})" ); - } - - self.Write( "]" ); - } - - /// Writes an XML Doc comment summary - /// The writer to write to - /// Text to include in the summary (Nothing is written if this is or all whitespace - public static void WriteSummaryComment(this TextWriter self, string? description ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(!string.IsNullOrWhiteSpace( description )) - { - self.WriteLine( $"/// {description!.Trim()}" ); - } - } - - /// Writes remarks comment (XML Doc comment) - /// The writer to write to - /// Text for the remarks - /// - /// If for is true - /// then nothing is generated. - /// - public static void WriteRemarksComment( this TextWriter self, string? txt ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(string.IsNullOrWhiteSpace( txt )) - { - return; - } - - string[] lines = [ .. txt!.GetCommentLines() ]; - if(lines.Length > 0) - { - self.WriteLine( "/// " ); - foreach(string line in lines) - { - self.WriteLine( $"/// {line}" ); - } - - self.WriteLine( "/// " ); - } - } - - /// Writes summary and remarks comment (XML Doc comment) - /// The writer to write to - /// Text for the remarks - /// Default summary text to use if does not contain any - /// - /// If for is true - /// then nothing is generated. If has no content then - /// is used as the summary. If is also empty or all Whitespace then nothing - /// is output. - /// - public static void WriteSummaryAndRemarksComments( this TextWriter self, string? txt, string? defaultSummary = null ) - { - ArgumentNullException.ThrowIfNull( self ); - - if(string.IsNullOrWhiteSpace( txt )) - { - if(!string.IsNullOrWhiteSpace( defaultSummary )) - { - self.WriteLine( $"/// {defaultSummary!.Trim()}" ); - } - - return; - } - - ArgumentException.ThrowIfNullOrWhiteSpace(defaultSummary); - self.WriteLine( $"/// {defaultSummary.Trim()}" ); - string[] lines = [ .. txt!.GetCommentLines() ]; - if(lines.Length > 0) - { - // summary + remarks. - self.WriteLine( "/// " ); - for(int i = 0; i < lines.Length; ++i) - { - self.Write( "/// " ); - self.WriteLine( lines[ i ] ); - } - - self.WriteLine( "/// " ); - } - } - - /// Writes a C# using directive (namespace reference) - /// The writer to write to - /// Namespace for the using directive - public static void WriteUsingDirective(this TextWriter self, string namespaceName ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentException.ThrowIfNullOrWhiteSpace(namespaceName); - - self.WriteLine( $"using {namespaceName};" ); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration/GlobalNamespaceImports.cs b/src/Ubiquity.NET.SrcGeneration/GlobalNamespaceImports.cs deleted file mode 100644 index d9e3ac1fa..000000000 --- a/src/Ubiquity.NET.SrcGeneration/GlobalNamespaceImports.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -/* -NOTE: -While the MsBuild `ImplicitUsings` property is banned from this repo, the C# language feature of global usings is NOT. -The build property will auto include an invisible and undiscoverable (without looking up obscure documentation) -set of namespaces that is NOT consistent or controlled by the developer. THAT is what is BAD/BROKEN about that feature. -By banning it's use and then providing a `GlobalNamespaceImports.cs` source file with ONLY global using statements ALL of -that is eliminated. Such use of the language feature restores FULL control and visibility of the namespaces to the developer, -where it belongs. For a good explanation of this problem see: https://rehansaeed.com/the-problem-with-csharp-10-implicit-usings/. -For an explanation of the benefits of the language feature see: https://www.hanselman.com/blog/implicit-usings-in-net-6 -*/ - -global using System; -global using System.CodeDom.Compiler; -global using System.Collections.Generic; -global using System.Collections.Immutable; -global using System.Diagnostics.CodeAnalysis; -global using System.IO; -global using System.Linq; -global using System.Xml.Linq; - -global using Ubiquity.NET.Extensions; diff --git a/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs deleted file mode 100644 index c2aea91b0..000000000 --- a/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration -{ - /// Extensions to to support generic source generation - [SuppressMessage( "Performance", "CA1822:Mark members as static", Justification = "extension" )] - [SuppressMessage( "Design", "CA1034:Nested types should not be visible", Justification = "extension" )] - public static class IndentedTextWriterExtensions - { - /// Writes an indented block to an - /// Writer to apply extension method to - /// Opening value of block (on it's own line) - /// Closing value of block (on it's own line) - /// Line of text preceding the block - /// Indicates if additional content written is indented or not [Default: ] - /// that will automatically emit and out dent the writer. - /// - /// - /// This does NOT end the line. This allows writing a comment or other output after the block is closed on the same line - /// as the . - /// - /// - public static IDisposable Block(this IndentedTextWriter self, string open, string close, string? leadingLine = null, bool indented = true ) - { - ArgumentNullException.ThrowIfNull( self ); - ArgumentNullException.ThrowIfNull( open ); - ArgumentNullException.ThrowIfNull( close ); - - if(leadingLine is not null) - { - self.WriteLine( leadingLine ); - } - - self.WriteLine( open ); - if(indented) - { - ++self.Indent; - } - - // presence of this when using `extension` keyword triggers bugs in C#14 preview 3 - // https://github.com/dotnet/roslyn/issues/78135 - // https://github.com/dotnet/roslyn/issues/78042 - return new Extensions.DisposableAction( ( ) => - { - if(indented) - { - --self.Indent; - } - - self.Write( close ); - } ); - } - - /// Pushes the indentation and returns an that will restore it on Dispose (RAII pattern) - /// Writer to apply extension method to - /// Disposable that when invoked, will reduce the indentation. - public static IDisposable PushIndent( this IndentedTextWriter self ) - { - ArgumentNullException.ThrowIfNull( self ); - - ++self.Indent; - - // presence of lambda when using `extension` keyword triggers bugs in C#14 preview 3 - // https://github.com/dotnet/roslyn/issues/78135 - // https://github.com/dotnet/roslyn/issues/78042 - return new Extensions.DisposableAction( ( ) => --self.Indent ); - } - - /// Writes a blank line (New line, with no additional characters or whitespace) - /// Writer to apply extension method to - public static void WriteEmptyLine( this IndentedTextWriter self ) - { - ArgumentNullException.ThrowIfNull( self ); - - self.WriteLineNoTabs( string.Empty ); - } - } -} diff --git a/src/Ubiquity.NET.SrcGeneration/ReadMe.md b/src/Ubiquity.NET.SrcGeneration/ReadMe.md deleted file mode 100644 index 91de33071..000000000 --- a/src/Ubiquity.NET.SrcGeneration/ReadMe.md +++ /dev/null @@ -1,76 +0,0 @@ -# Ubiquity.NET.SrcGeneration -This library provides support for source generation using -`System.CodeDom.Compiler.IndentedTextWriter`. -While .NET does have support for T4 to generate source that is used at runtime to generate -a final source this can easily get VERY terse and hard to use. (Let alone debug, especially -with respect to correct white space.) Thus, while it is useful for simpler templating when -things get complicated and there are lots of "decisions" to make based on the input it can -get downright unruly. - -## Support includes -* `StringExtensions` to support manipulations of strings commonly used by source generators - * Method to split a string into lines fit for use in XML doc comments - * Method to escape processing for a single string for comments - * Currently the only escape processed is `'\n'` which is transformed into an - environment specific newline. - * Method to split a string into lines where a newline is the delimiter - * Method to Escape a string for use in XML (Specifically XML doc comments but any XML - usage is valid) - * Escaping a sequence of strings for use in XML doc comments - * Sequence of string transforms to remove side by side duplicates -* `IndentedTextWriterExtensions` to provide extensions for an `IndentedTextWriter` - * Extensions to support auto out-denting via IDisposable in a RAII like pattern - * Extension to write an empty line (Without any indentation) - * While `IndentedTextWriter` has this via the `IndentedTextWriter.WriteLineNoTabs(string)`, - this extension makes it simpler and clearer what the intent was. - * Extension to generate a block of code that has subsequent content indented. - * Including an opening and closing text as well as an optional leading line - ``` - - - [additional lines of text] - - ``` -### C# target language specific support -While other languages are possible this is the only one currently "built-in". -* `CSharpLanguage` contains constants and statics for generating C# source - * Constants for the open/close of a scope ("{","}") - * Array of known keywords to allow escaping text that uses them - * Function to make an identifier out of a string - * Keywords in the array of known keywords are escaped with a '@' prefix. -* `IndentedTextWriterCsExtensions` To provide extensions to `IndentedTextWriter` that are - specific to the C# language. - * Write a `auto-generated` comment in a form recognized by the compiler as `generated` - for use in determining if analyzers apply or not. (Usually analyzers are set to ignore - generated code as it isn't something that is controlled by the developer). - * Write an auto generated comment as a scope to allow adding custom content in the comment - * Closing of the comment region doesn't occur until the return is Disposed (RAII - pattern) - * Write a namespace scope - * All subsequent content is indented for the scope - * Closing of the scope doesn't occur until the return is Disposed (RAII pattern). - * Write a struct scope - * All subsequent content is indented for the scope - * Closing of the scope doesn't occur until the return is Disposed (RAII pattern). - * Write an unsafe block scope - * All subsequent content is indented for the scope - * Closing of the scope doesn't occur until the return is Disposed (RAII pattern). - * Write a generic scope - * All subsequent content is indented for the scope - * Closing of the scope doesn't occur until the return is Disposed (RAII pattern). - * Write a multi-line comment - * Creates a scope using "\*" and "*/" as the beginning/ending of the block - * Input string is escaped and converted to a sequence of lines that is emitted - as the indented contents of the comment. - * Write a class scope - * All subsequent content is indented for the scope - * Closing of the scope doesn't occur until the return is Disposed (RAII pattern). -* `TextWriterCsExtension` to provide more extensions to a `TextWriter` that are specific to - the C# language. - * Method to write an attribute line - * Method to write (without new line) an attribute - * Method to write an XML Doc comment `summary` tag. - * Method to write an XML Doc comment `remarks` tag. - * Method to write an XML Doc comment `summary` and `remarks` tags with optional default - `summary` contents - * Method to write a `using` directive. diff --git a/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs b/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs deleted file mode 100644 index 162d4b3e4..000000000 --- a/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Ubiquity.NET Contributors. All rights reserved. -// Licensed under the Apache-2.0 WITH LLVM-exception license. See the LICENSE.md file in the project root for full license information. - -namespace Ubiquity.NET.SrcGeneration -{ - /// Utility class to host extensions for a . - public static class StringExtensions - { - /// Gets the lines of this instance as a contents for a comment - /// String to apply extension to - /// String split options to use when splitting lines - /// Enumerable sequence of comment strings without any language specific leading or trailing delimiters - /// - /// This is generally used by language specific extensions that will also emit the comment leading/trailiing text as needed. - /// Thus it is a general language neutral facility that is used to produce the final language specific comments. - /// It will perform the following on the input string: - /// 1) to ensure character escaping is applied to the whole string
- /// 2) to split the string into distinct lines
- /// 3) to ensure the lines are valid for XML doc comments
- /// 4) to fold duplicate entries into one (usually to reduce multiple blank lines to one)
- ///
- public static IEnumerable GetCommentLines( this string self, StringSplitOptions2 options = StringSplitOptions2.TrimEntries ) - { - ArgumentNullException.ThrowIfNull( self ); - - // For now, naive conversion - just splits on newlines - // more sophisticated implementation could split on word boundaries based on length... - return self.EscapeComment() - .SplitLines( options ) - .EscapeForXML() - .SkipDuplicates(); - } - - /// Escapes characters in a comment - /// String to apply extension to - /// Escaped comment - /// - /// Currently, the only character that is allowed for an escape is '\n' - /// which is converted to an . - /// - public static string EscapeComment( this string self ) - { - ArgumentNullException.ThrowIfNull( self ); - - // For now, the only escape is a newline '\n' -#if NETSTANDARD2_0 - return self.Replace( "\\n", Environment.NewLine ); -#else - return self.Replace( "\\n", Environment.NewLine, StringComparison.Ordinal ); -#endif - } - - /// Splits a string into a sequence of lines - /// String to apply extension to - /// String split options to use when splitting lines - /// Enumerable sequence of strings, one for each line in the original - /// - /// - /// For runtimes prior to .NET 5 the behavior of - /// is emulated here. The implementation of that emulation is not as performant as the - /// official form in later runtimes. This emulation was chosen for correctness of behavior - /// and simplicity of implementation over performance. If absolute best performance is - /// desired then use the latest runtime. - /// - /// - public static IEnumerable SplitLines( this string self, StringSplitOptions2 splitOptions = StringSplitOptions2.None ) - { - ArgumentNullException.ThrowIfNull( self ); - -#if !NET5_0_OR_GREATER - // StringSplitOptions.TrimeEntries member is not available, do it the hard/slow way - if(splitOptions.HasFlag(StringSplitOptions2.TrimEntries)) - { - var options = (StringSplitOptions)((int)splitOptions & ~(int)StringSplitOptions2.TrimEntries); - return from s in self.Split( MixedLineEndings, options) - let t = s.Trim() - where !splitOptions.HasFlag(StringSplitOptions2.RemoveEmptyEntries) || !string.IsNullOrEmpty(t) - select t; - } -#endif - return self.Split( MixedLineEndings, (StringSplitOptions)splitOptions); - } - - // TODO: WithLines(Action> op) - // finds all line endings and provides each line as a span to op - // This avoids the problem of IEnumerable> lifetime management - // though that is plausible with a custom implementation instead of a generated - // iterator... - - /// Escapes a string for use in XML - /// String to apply extension to - /// XML safe escaped string - /// This will perform escaping of characters for XML such as conversion of `&` into `&amp;` etc... - public static string MakeXmlSafe( this string self ) - { - ArgumentNullException.ThrowIfNull( self ); - - return new XText( self ).ToString(); - } - - /// Transforms a sequence of strings to a sequence of XML escaped strings - /// Sequence to apply this extension to - /// Sequence of XML escaped strings - public static IEnumerable EscapeForXML( this IEnumerable self ) - { - ArgumentNullException.ThrowIfNull( self ); - - return from s in self - select MakeXmlSafe( s ); - } - - /// Transforms a sequence of strings to a sequence of strings escaped for comments - /// Sequence to apply this extension to - /// Sequence of escaped strings - /// - public static IEnumerable EscapeForComment( this IEnumerable self ) - { - ArgumentNullException.ThrowIfNull( self ); - - return from s in self - select EscapeComment( s ); - } - - /// Transforms a sequence of strings removing duplicate strings - /// Sequence to apply this extension to - /// Sequence of strings that has no duplicate side by side entries - /// - /// For the purposes of this method `duplicate` means "identical to previous". Thus, - /// it is possible for multiple entries in to have the same value - /// in the result, just none where the previous entry is the same as the current one. This - /// is normally used when importing from a format that contains multiple new lines in a - /// row so they are converted to a single new line. - /// The returned value is a deferred iterator, the actual work of duplicate detection - /// and removal is deferred until needed by the caller and stopped whenever the caller ceases - /// to enumerate more items. - /// - public static IEnumerable SkipDuplicates( this IEnumerable self ) - { - ArgumentNullException.ThrowIfNull( self ); - - string? oldVal = null; - return self.Where((s)=> - { - bool retVal = s != oldVal; - oldVal = s; - return retVal; - } ); - } - -#pragma warning disable IDE0002 -// names can't be simplified further due to weird ambiguities with how extensions are resolved -// net stadard 2.0 does NOT contain the static methods for argument validation on exceptions. -// Thus in tat runtime they are polly fill extensions, but they have the same name as instance -// extensions - those win out and collide causing mass confusion. - private static readonly string [] MixedLineEndings = - [ - Extensions.StringNormalizer.LineEnding(Extensions.LineEndingKind.CarriageReturnLineFeed), - Extensions.StringNormalizer.LineEnding(Extensions.LineEndingKind.CarriageReturn), - Extensions.StringNormalizer.LineEnding(Extensions.LineEndingKind.LineFeed), - ]; -#pragma warning restore IDE0002 - } -} diff --git a/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj b/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj deleted file mode 100644 index 79ba81404..000000000 --- a/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - net8.0;netstandard2.0 - AnyCPU - - preview - True - True - True - - - true - 4.9.0 - .NET Foundation,Ubiquity.NET - false - General use Support for Source Generators - Extensions,.NET,Ubiquity.NET - ReadMe.md - https://github.com/UbiquityDotNET/Llvm.NET - https://github.com/UbiquityDotNET/Llvm.NET.git - git - Apache-2.0 WITH LLVM-exception - true - snupkg - - - - - - - - - - all - false - Analyzer - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/src/settings.VisualStudio.json b/src/settings.VisualStudio.json deleted file mode 100644 index c484cb618..000000000 --- a/src/settings.VisualStudio.json +++ /dev/null @@ -1,4 +0,0 @@ -/* Visual Studio Settings File */ -{ - "environment.terminal.profiles+defaultItemIndex": 0 -} \ No newline at end of file