diff --git a/Directory.Packages.props b/Directory.Packages.props
index d5f6bd8..759a138 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -30,12 +30,7 @@
-
-
-
-
-
diff --git a/IgnoredWords.dic b/IgnoredWords.dic
index 957a873..088601b 100644
--- a/IgnoredWords.dic
+++ b/IgnoredWords.dic
@@ -103,6 +103,7 @@ invocable
jit
len
Lexer
+lhs
LibLLVM
Licensor
Llilum
@@ -158,6 +159,7 @@ referenceable
Relocations
repl
repo
+rhs
RMW
runtimes
RValue
diff --git a/docfx/ReadMe.md b/docfx/ReadMe.md
index 0e9518a..f3e030e 100644
--- a/docfx/ReadMe.md
+++ b/docfx/ReadMe.md
@@ -87,8 +87,15 @@ possible.
### Lists
The largest intrusion of the XML into the source is that of lists. The XML doc comments
-official support is to use the `` tag. However, that is VERY intrusive and doesn't
-easily support sub-lists. Consider:
+official support is to use the `` tag. For an example of how intrusive and ugly these
+can become see this [Blog Article](https://documentation.contiem.com/HelpAuthoringTools/UseListsAndTablesInXmlComments.html)
+>[!NOTE]
+> The output shown in that article is using a theoretcial rendering. It is ***NOT*** how the
+> IDE Will present the text. Usually the IDE is not as "pretty".
+
+#### Bulleted lists
+Unfortunately, the XML doc comments for a bulleted list (and any other form really) is VERY
+intrusive and doesn't easily support sub-lists. Consider:
``` C#
/// Additional steps might include:
@@ -122,7 +129,7 @@ Which one would ***YOU*** rather encounter in code? Which one is easier to under
reading the source? This repo chooses the latter. (If you favor the former, perhaps you
should reconsider... :grinning:)
-#### How to handle lists
+##### How to handle bulleted lists
There is little that can be done to alter the rendering of any editor support, at most an
editor might allow specification of a CSS file, but that is the lowest priority of doc
comments. Readability by maintainers of the docs AND the rendering for final docs used by
@@ -151,3 +158,24 @@ render properly in final docs.
1) Turning this off can greatly reduce the noise AND reduce the problems of
different rendering as lists are generally not used in the other elements.
+#### Tables
+There's sadly no simple rendering of these. Many user requests for a simpler markdown tag
+have gone unresolved. The docfx tool will render tables from the XML comment form though
+so a pattern of implementing them is possible
+
+#### Guidance for tables
+1) Avoid using them if at all possible.
+ - This is the simplest advice to give for these, they are ugly and intrusive so avoid
+ them whenever possible.
+2) If they are unavoidable or alternate representations leads to even uglier forms then use
+ the following guidance.
+
+``` C#
+///
+/// {Header 1}{Header 2}
+/// - {Row 1, Col 1}{Row 1, Col 2}
+/// - {Row 2, Col 2}{Row 2, Col 2}
+///
+```
+If a table needs more than 3 columns this gets ugly, fast, don't do that. Re-think the docs,
+use an additional markdown file that includes a simpler form of the table, anything else...
diff --git a/src/DemoCommandLineSrcGen/Program.cs b/src/DemoCommandLineSrcGen/Program.cs
index 25ec715..4aa0c9e 100644
--- a/src/DemoCommandLineSrcGen/Program.cs
+++ b/src/DemoCommandLineSrcGen/Program.cs
@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using Ubiquity.NET.CommandLine;
+using Ubiquity.NET.Extensions;
namespace TestNamespace
{
@@ -25,7 +26,7 @@ public static async Task Main( string[] args )
};
// start with information level for parsing; parsed options might specify different level
- var reporter = new ColoredConsoleReporter( MsgLevel.Information );
+ var reporter = new ColoredConsoleReporter( MessageLevel.Information );
return await TestOptions.BuildRootCommand( ( options, ct ) => AppMainAsync( options, reporter, ct ) )
.ParseAndInvokeResultAsync( reporter, cts.Token, args );
diff --git a/src/DemoCommandLineSrcGen/TestOptions.cs b/src/DemoCommandLineSrcGen/TestOptions.cs
index 833e8dc..f5dd2a7 100644
--- a/src/DemoCommandLineSrcGen/TestOptions.cs
+++ b/src/DemoCommandLineSrcGen/TestOptions.cs
@@ -6,15 +6,11 @@
using System.IO;
using System.Linq;
-using Ubiquity.NET.CommandLine;
using Ubiquity.NET.CommandLine.GeneratorAttributes;
+using Ubiquity.NET.Extensions;
namespace TestNamespace
{
- // It is important to understand how "required" is handled in the underlying command line handler
- // in System.CommandLine. The semantics are:
- // When the command is invoked, the value may not be null.
- // That is, it is NOT evaluated during parse, ONLY during invocation.
[RootCommand( Description = "Root command for tests" )]
internal partial class TestOptions
{
@@ -23,7 +19,7 @@ internal partial class TestOptions
public required DirectoryInfo SomePath { get; init; }
[Option( "-v", Description = "Verbosity Level" )]
- public MsgLevel Verbosity { get; init; } = MsgLevel.Information;
+ public MessageLevel Verbosity { get; init; } = MessageLevel.Information;
[Option( "-b", Description = "Test Some existing Path", Required = true )]
[FolderValidation( FolderValidation.ExistingOnly )]
diff --git a/src/Ubiquity.NET.ANTLR.Utils/AntlrErrorListenerAdapter.cs b/src/Ubiquity.NET.ANTLR.Utils/AntlrErrorListenerAdapter.cs
new file mode 100644
index 0000000..7eb8a94
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/AntlrErrorListenerAdapter.cs
@@ -0,0 +1,85 @@
+// 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 for messages reported by this type are always null. If
+ /// an application desires specific sub categories then it can use it's own implementation of this type
+ ///
+ public class AntlrErrorListenerAdapter
+ : IAntlrErrorListener
+ , IAntlrErrorListener
+ {
+ /// Initializes a new instance of the class
+ /// Source name for all errors
+ /// Severity map to use when transforming errors
+ /// Formatter to use to form a string version of the error code
+ /// Diagnostic reporter to adapt ANTL errors to
+ /// is null or whitespace
+ public AntlrErrorListenerAdapter(
+ string sourceName,
+ IMessageLevelMap severityMap,
+ IDiagnosticIdFormatter formatter,
+ IDiagnosticReporter diagnosticReporter
+ )
+ {
+ Requires.NotNullOrWhiteSpace( sourceName );
+
+ SeverityMap = severityMap;
+ IdFormatter = formatter;
+ Reporter = diagnosticReporter;
+ }
+
+ /// Gets the severity mapping used when forming a diagnostic
+ public IMessageLevelMap SeverityMap { get; init; }
+
+ /// Gets the error ID mapping used when forming a diagnostic
+ public IDiagnosticIdFormatter IdFormatter { get; init; }
+
+ /// Gets the reporter this instance adapts to
+ public IDiagnosticReporter Reporter { get; init; }
+
+ ///
+ public void SyntaxError( TextWriter output // ignored
+ , IRecognizer recognizer
+ , int offendingSymbol
+ , int line
+ , int charPositionInLine
+ , string msg
+ , RecognitionException? e
+ )
+ {
+ var loc = new SourceLocation(recognizer.InputStream.SourceName, new SourcePosition(line, charPositionInLine, recognizer.InputStream.Index));
+ var code = new ScopedDiagnosticId(ParseSource.Lexical, recognizer.State );
+ var diagnostic = code.AsDiagnostic(SeverityMap, IdFormatter, msg, loc, subcategory: null);
+ Reporter.Report( diagnostic );
+ }
+
+ ///
+ public void SyntaxError( TextWriter output // ignored
+ , IRecognizer recognizer
+ , IToken? offendingSymbol
+ , int line
+ , int charPositionInLine
+ , string msg
+ , RecognitionException? e
+ )
+ {
+ var loc = new SourceLocation(recognizer.InputStream.SourceName, new SourcePosition(line, charPositionInLine, recognizer.InputStream.Index));
+ int id = e is ParseException pe ? pe.ErrorId : recognizer.State;
+ var code = new ScopedDiagnosticId(ParseSource.Syntactic, id );
+ var diagnostic = code.AsDiagnostic(SeverityMap, IdFormatter, msg, loc, subcategory: null);
+ Reporter.Report( diagnostic );
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/AntlrParseErrorListenerAdapter.cs b/src/Ubiquity.NET.ANTLR.Utils/AntlrParseErrorListenerAdapter.cs
deleted file mode 100644
index a9c63ae..0000000
--- 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/CancellableParseTreeListener.cs b/src/Ubiquity.NET.ANTLR.Utils/CancellableParseTreeListener.cs
new file mode 100644
index 0000000..b013b91
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/CancellableParseTreeListener.cs
@@ -0,0 +1,49 @@
+// 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.Threading;
+
+namespace Ubiquity.NET.ANTLR.Utils
+{
+ /// IParseTreeListener that provides support for cancellation
+ ///
+ /// This class will throw an OperationCanceled from any method when the provided
+ /// has requested cancellation of the parse. Cancellable
+ ///
+ public class CancellableParseTreeListener
+ : IParseTreeListener
+ {
+ /// Initializes a new instance of the class.
+ /// Token to use for detection of cancellation
+ public CancellableParseTreeListener( CancellationToken ct )
+ {
+ CancelToken = ct;
+ }
+
+ ///
+ public void EnterEveryRule( ParserRuleContext ctx )
+ {
+ CancelToken.ThrowIfCancellationRequested();
+ }
+
+ ///
+ public void ExitEveryRule( ParserRuleContext ctx )
+ {
+ CancelToken.ThrowIfCancellationRequested();
+ }
+
+ ///
+ public void VisitErrorNode( IErrorNode node )
+ {
+ CancelToken.ThrowIfCancellationRequested();
+ }
+
+ ///
+ public void VisitTerminal( ITerminalNode node )
+ {
+ CancelToken.ThrowIfCancellationRequested();
+ }
+
+ private readonly CancellationToken CancelToken;
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/CustomErrorStrategy.cs b/src/Ubiquity.NET.ANTLR.Utils/CustomErrorStrategy.cs
new file mode 100644
index 0000000..cded51d
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/CustomErrorStrategy.cs
@@ -0,0 +1,90 @@
+// 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
+{
+ /// Class to implement custom error strategies.
+ ///
+ /// This is derived from the ANTLR provided and extends
+ /// it to support . To correctly leverage the error strategy
+ /// the lexer must implement so that lexer errors are
+ /// redirected to the strategy before notifying the listeners. This allows consistent central
+ /// adoption of custom error strings for error messages. It also allows for customized behavior
+ /// with regards to actual error handling/recovery of errors during a parse.
+ ///
+ /// There is no support in ANTLR for any custom recovery strategy support for a lexer. In fact,
+ /// there is no error customization at all in the default lexer support. This class only supports
+ /// customizing the lexer exception before notification if the lexer implements
+ /// , usually in a customizing partial class.
+ ///
+ ///
+ public class CustomErrorStrategy
+ : DefaultErrorStrategy
+ , ILexerErrorStrategy
+ {
+ /// Initializes a new instance of the class
+ /// Indicates if unknown types cause an immediate failure [Default: false]
+ ///
+ /// Unknown exceptions are always logged to the debugger, if attached. The parameter
+ /// determines if they are considered a failure point and stop additional processing.
+ ///
+ public CustomErrorStrategy( bool failOnUnknownException = false )
+ {
+ FailOnUnknownException = failOnUnknownException;
+ }
+
+ /// Gets a value indicating whether this instance will fail on unknown s or just log them
+ public bool FailOnUnknownException { get; }
+
+ /// Overrides to prevent use of
+ /// Recognizer that found the error
+ /// Exception for the original error
+ public override void ReportError( Parser recognizer, RecognitionException e )
+ {
+ if(!InErrorRecoveryMode( recognizer ))
+ {
+ BeginErrorCondition( recognizer );
+ if(e is NoViableAltException exception)
+ {
+ ReportNoViableAlternative( recognizer, exception );
+ return;
+ }
+
+ if(e is InputMismatchException inputMisMatchEx)
+ {
+ ReportInputMismatch( recognizer, inputMisMatchEx );
+ return;
+ }
+
+ if(e is FailedPredicateException failedPredEx)
+ {
+ ReportFailedPredicate( recognizer, failedPredEx );
+ return;
+ }
+
+ // always log to an attached debugger.
+ Debug.WriteLine( "ERROR: unknown recognition type: {0}", e.GetType().FullName );
+ if(FailOnUnknownException)
+ {
+ throw new NotSupportedException( $"Unknown recognition exception type: {e.GetType().FullName}" );
+ }
+ else
+ {
+ // directly notify any listeners of an unknown exception type
+ NotifyErrorListeners( recognizer, e.Message, e );
+ }
+ }
+ }
+
+ ///
+ public virtual void ReportLexerError( Lexer recognizer, LexerNoViableAltException e )
+ {
+ var lexerWithStrategy = (ISupportLexerErrorStrategy)recognizer;
+
+ string srcText = ((ITokenSource)recognizer).InputStream.GetText(Interval.Of(recognizer.TokenStartCharIndex, recognizer.InputStream.Index));
+ string utfEscapedSrcText = recognizer.GetErrorDisplay(srcText);
+ string msg = $"Syntax error: '{utfEscapedSrcText}' was unexpected here";
+ recognizer.ErrorListenerDispatch.SyntaxError( lexerWithStrategy.NotificationErrorOutput, recognizer, 0, recognizer.TokenStartLine, recognizer.TokenStartColumn, msg, e );
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs b/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs
index 2a699c8..93a7aff 100644
--- a/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs
+++ b/src/Ubiquity.NET.ANTLR.Utils/GlobalNamespaceImports.cs
@@ -16,8 +16,10 @@ set of namespaces that is NOT consistent or controlled by the developer. THAT is
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Diagnostics;
+global using System.Diagnostics.CodeAnalysis;
global using System.Globalization;
global using System.IO;
+global using System.Linq;
global using System.Text;
global using Antlr4.Runtime;
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ICombinedParseErrorListener.cs b/src/Ubiquity.NET.ANTLR.Utils/ICombinedParseErrorListener.cs
new file mode 100644
index 0000000..5bdb0f5
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ICombinedParseErrorListener.cs
@@ -0,0 +1,14 @@
+// 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
+{
+ /// Interface that specifies requirements for a type supporting FULL error listening (Lexer, and parser)
+ public interface ICombinedParseErrorListener
+ : IAntlrErrorListener // Lexers, provide discreet symbols as an integral value
+ , IAntlrErrorListener // Parsers, provide discreet symbols as a full token
+ {
+ // TODO: Consider additional Semantic Error methods to differentiate between
+ // actual syntactical errors, and post-parse semantic errors.
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ILexerErrorStrategy.cs b/src/Ubiquity.NET.ANTLR.Utils/ILexerErrorStrategy.cs
new file mode 100644
index 0000000..0ca3c72
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ILexerErrorStrategy.cs
@@ -0,0 +1,23 @@
+// 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
+{
+ /// Interface for a lexer error strategy
+ ///
+ /// To leverage this interface in a lexer partial class, it must
+ /// implement
+ ///
+ public interface ILexerErrorStrategy
+ {
+ /// Reports an error from a lexer
+ /// Lexer reporting the error
+ /// Error to report
+ ///
+ /// The provided MUST implement the
+ /// interface to allow access to the output text writer used for generating the output. (Sadly, ANTLR design
+ /// requires the writer as a parameter that a notifier must track, instead of being a property of the listener)
+ ///
+ void ReportLexerError( Lexer recognizer, LexerNoViableAltException e );
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/IParseErrorList.cs b/src/Ubiquity.NET.ANTLR.Utils/IParseErrorList.cs
new file mode 100644
index 0000000..defe873
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/IParseErrorList.cs
@@ -0,0 +1,12 @@
+// 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
+{
+ /// Interface that specifies requirements for a type collecting errors (Lexer, and parser)
+ public interface IParseErrorList
+ : ICombinedParseErrorListener
+ , IReadOnlyList // Collects and provides the full set of errors as DiagnosticMessages
+ {
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/IParseResult.cs b/src/Ubiquity.NET.ANTLR.Utils/IParseResult.cs
new file mode 100644
index 0000000..eb29035
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/IParseResult.cs
@@ -0,0 +1,19 @@
+// 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
+{
+ /// Interface for a parse result
+ /// Type of the parser that produced the results
+ /// Type of the resulting parse tree
+ public interface IParseResult
+ where TParser : Parser
+ where TTree : IParseTree
+ {
+ /// Gets the result of parsing as a tree
+ TTree Tree { get; }
+
+ /// Gets the parser that produced
+ TParser Parser { get; }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ISupportLexerErrorStrategy.cs b/src/Ubiquity.NET.ANTLR.Utils/ISupportLexerErrorStrategy.cs
new file mode 100644
index 0000000..1094d76
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ISupportLexerErrorStrategy.cs
@@ -0,0 +1,59 @@
+// 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
+{
+ /// Interface for a lexer that supports error strategies
+ ///
+ /// Sadly, a lexer doesn't have any sort of "Error strategy" as there is only the one
+ /// known exception. Thus, there is no official strategy to provide consistency. This
+ /// interface, in combination with allows for
+ /// consistent interception and establishment of error messages for both a lexer and
+ /// parser. This is normally applied to a partial class for the generated lexer as shown
+ /// in the example.
+ ///
+ ///
+ /// This example shows the basic implementation in a partial class
+ /// for a fictitious `My.g4` language grammar.
+ /// ErrorOutput;
+ ///
+ /// /// Notifies listeners of lexer errors via
+ /// /// Exception from the recognition process
+ /// public override void NotifyListeners(LexerNoViableAltException e)
+ /// {
+ /// if (ErrorStrategy != null)
+ /// {
+ /// ErrorStrategy.ReportLexerError(this, e);
+ /// }
+ /// else
+ /// {
+ /// // no strategy, so 'fall-back' to the default behavior
+ /// base.NotifyListeners(e);
+ /// }
+ /// }
+ /// }
+ /// ]]>
+ ///
+ public interface ISupportLexerErrorStrategy
+ {
+ /// Gets or sets the error strategy to use for this lexer (If any)
+ /// A null value will result in the default handling built into
+ ILexerErrorStrategy? ErrorStrategy { get; set; }
+
+ /// Gets the otherwise internal 'ErrorOutput' from a
+ ///
+ /// Sadly, the ANTLR design makes the output writer something the source must track instead of being a part of the
+ /// listener. (A lexer really shouldn't need to care about the output writer in any way, as different listeners might
+ /// use different output writers, or none at all.)
+ ///
+ TextWriter NotificationErrorOutput { get; }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/IntStreamExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/IntStreamExtensions.cs
new file mode 100644
index 0000000..84b19c7
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/IntStreamExtensions.cs
@@ -0,0 +1,33 @@
+// 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
+{
+ /// Extensions for an ANTLR int stream
+ public static class IntStreamExtensions
+ {
+ /// Gets a string for a potentially escaped character from a stream at a given index
+ /// Stream to get the text from (Must also implement ICharStream)
+ /// Index in the stream to get the character
+ /// String representing the character or Empty if out of bounds
+ public static string GetAt( this IIntStream strm, int index )
+ {
+ return ((ICharStream)strm).GetAt( index );
+ }
+
+ /// Gets a string for a potentially escaped character from a stream at a given index
+ /// Stream to get the text from
+ /// Index in the stream to get the character
+ /// String representing the character or Empty if out of bounds
+ public static string GetAt( this ICharStream strm, int index )
+ {
+ string retVal = string.Empty;
+ if(index >= 0 && index < strm.Size)
+ {
+ retVal = strm.GetText( Interval.Of( index, index ) );
+ retVal = Antlr4.Runtime.Misc.Utils.EscapeWhitespace( retVal, escapeSpaces: false );
+ }
+
+ return retVal;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/IntervalExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/IntervalExtensions.cs
new file mode 100644
index 0000000..4458903
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/IntervalExtensions.cs
@@ -0,0 +1,19 @@
+// 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 extend support for an
+ public static class IntervalExtensions
+ {
+ /// Support C# Deconstruction for an
+ /// to deconstruct
+ /// The lower bound value
+ /// The upper bound value
+ public static void Deconstruct( this Interval interval, out int lowerBound, out int upperBound )
+ {
+ lowerBound = interval.a;
+ upperBound = interval.b;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParseErrorList.cs b/src/Ubiquity.NET.ANTLR.Utils/ParseErrorList.cs
new file mode 100644
index 0000000..a3c8364
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParseErrorList.cs
@@ -0,0 +1,120 @@
+// 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;
+
+namespace Ubiquity.NET.ANTLR.Utils
+{
+ /// Common Implementation of specific to ANTLR parsers/lexers
+ public class ParseErrorList
+ : IReadOnlyList
+ , IParseErrorList
+ {
+ /// Initializes a new instance of the class
+ /// Source name for all errors
+ /// Severity map to use when transforming errors
+ /// Formatter to use to form a string version of the error code
+ /// is null or whitespace
+ public ParseErrorList( string sourceName, IMessageLevelMap severityMap, IDiagnosticIdFormatter formatter )
+ : this( new SourceLocation( sourceName ), // default location has position of (0,0) so all errors are effectively "absolute"
+ severityMap,
+ formatter
+ )
+ {
+ }
+
+ /// Initializes a new instance of the class
+ /// Base location for all errors
+ /// Severity map to use when transforming errors
+ /// Formatter to use to form a string version of the error code
+ ///
+ /// This constructor is generally used for post parse validation via a sub language parser
+ /// [or perhaps a specific rule of the same parser]. The base location is used to compute
+ /// a final location for any errors which are treated as relative to this location.
+ ///
+ public ParseErrorList(
+ SourceLocation baseLocation,
+ IMessageLevelMap severityMap,
+ IDiagnosticIdFormatter formatter
+ )
+ {
+ BaseLocation = baseLocation;
+ SeverityMap = severityMap;
+ IdFormatter = formatter;
+ }
+
+ /// Gets the base location for all errors
+ ///
+ /// This location is offset by any posted error positions. That is, the posted error positions
+ /// are relative to this location.
+ ///
+ public SourceLocation BaseLocation { get; init; }
+
+ /// Gets the severity mapping used when forming a diagnostic
+ public IMessageLevelMap SeverityMap { get; init; }
+
+ /// Gets the functor used to format an integral id to a string form
+ public IDiagnosticIdFormatter IdFormatter { get; init; }
+
+ ///
+ public int Count => InnerList.Count;
+
+ ///
+ public DiagnosticMessage this[ int index ] => InnerList.Messages[ index ];
+
+ ///
+ public IEnumerator GetEnumerator( )
+ {
+ return InnerList.GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator( )
+ {
+ return InnerList.GetEnumerator();
+ }
+
+ ///
+ public void SyntaxError( TextWriter output // ignored, errors are collected not formatted for any output
+ , IRecognizer recognizer
+ , int offendingSymbol // ignored, always 0 for lexer errors
+ , int line
+ , int charPositionInLine
+ , string msg
+ , RecognitionException e
+ )
+ {
+ ArgumentNullException.ThrowIfNull(recognizer);
+
+ // -1 for the index since, in ANTLR parsing, the current index is one past the source of the error!
+ int srcIndex = (recognizer?.InputStream?.Index ?? 1) - 1;
+
+ // +1 for column as SourceLocation uses a 1 based column, and ANTLR uses a 0 based
+ int srcCol = charPositionInLine + 1;
+
+ var loc = new SourceLocation(recognizer!.InputStream.SourceName, new SourcePosition(line, srcCol, srcIndex));
+ var scopedCode = new ScopedDiagnosticId(ParseSource.Lexical, recognizer.State);
+ var diagnostic = scopedCode.AsDiagnostic(SeverityMap, IdFormatter, msg, loc);
+ InnerList.Report( diagnostic );
+ }
+
+ ///
+ public void SyntaxError( TextWriter output // ignored, errors are collected not formatted for any output
+ , IRecognizer recognizer
+ , IToken offendingSymbol
+ , int line
+ , int charPositionInLine
+ , string msg
+ , RecognitionException e
+ )
+ {
+ var loc = new SourceLocation(recognizer.InputStream.SourceName, new SourcePosition(line, charPositionInLine, recognizer.InputStream.Index));
+ int id = e is ParseException pe ? pe.ErrorId : recognizer.State;
+ var code = new ScopedDiagnosticId(ParseSource.Syntactic, id );
+ var diagnostic = code.AsDiagnostic(SeverityMap, IdFormatter, msg, loc, subcategory: null);
+ InnerList.Report( diagnostic );
+ }
+
+ private readonly DiagnosticMessageCollection InnerList = [];
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParseException.cs b/src/Ubiquity.NET.ANTLR.Utils/ParseException.cs
new file mode 100644
index 0000000..b1ba2c1
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParseException.cs
@@ -0,0 +1,43 @@
+// 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
+{
+ /// Wraps a with an error id value
+ ///
+ /// This is used by a custom implementation of a lexer or parser error strategy.
+ /// (See: , , and )
+ /// This type facilitates communication of a custom error ID to an error listener as there
+ /// is no other good way to communicate between the two what the error ID is. The strategy is
+ /// the best place to determine such a thing.
+ ///
+ [Serializable]
+ [SuppressMessage( "Design", "CA1032:Implement standard exception constructors", Justification = "Base doesn't implement those and they'd be nonsensical" )]
+ public class ParseException
+ : RecognitionException
+ {
+ /// Initializes a new instance of the class
+ /// The inner exception this wraps
+ /// Error Id for the error
+ public ParseException( RecognitionException? sourceException, int errorId )
+ : base( sourceException?.Message ?? string.Empty, sourceException?.Recognizer, sourceException?.InputStream, sourceException?.Context as ParserRuleContext )
+ {
+ OffendingToken = sourceException?.OffendingToken;
+ SourceException = sourceException ?? throw new ArgumentNullException(nameof( sourceException ));
+ ErrorId = errorId;
+ }
+
+ /// Initializes a new instance of the class
+ /// Error Id for the error
+ public ParseException( int errorId )
+ : this( null, errorId )
+ {
+ }
+
+ /// Gets the original exception
+ public RecognitionException SourceException { get; }
+
+ /// Gets the language specific unique ID for the error
+ public int ErrorId { get; }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParseResultBase.cs b/src/Ubiquity.NET.ANTLR.Utils/ParseResultBase.cs
new file mode 100644
index 0000000..1273e36
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParseResultBase.cs
@@ -0,0 +1,29 @@
+// 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
+{
+ /// Generic base type for a parse result
+ /// Type of Parser the result comes from
+ /// Type of the resulting parse tree
+ public class ParseResultBase
+ : IParseResult
+ where TParser : Parser
+ where TTree : IParseTree
+ {
+ /// Initializes a new instance of the class
+ /// Result tree
+ /// Parser that produced the tree
+ public ParseResultBase( TTree parseTree, TParser parser )
+ {
+ Tree = parseTree;
+ Parser = parser;
+ }
+
+ ///
+ public TTree Tree { get; /*init;*/ }
+
+ ///
+ public TParser Parser { get; /*init;*/ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParseTreeExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/ParseTreeExtensions.cs
new file mode 100644
index 0000000..9ea8d4a
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParseTreeExtensions.cs
@@ -0,0 +1,74 @@
+// 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
+{
+ /// Extensions for an
+ public static class ParseTreeExtensions
+ {
+ /// Gets and enumerable collection of children for an
+ /// Tree to get children from
+ /// Enumerable of all of the children in the tree
+ public static IEnumerable GetChildren( this IParseTree tree )
+ {
+ for(int i = 0; i < tree.ChildCount; ++i)
+ {
+ yield return tree.GetChild( i );
+ }
+ }
+
+ /// Gets a SyntaxNode from a parse tree
+ /// Parse tree
+ /// for the input
+ /// Invalid
+ ///
+ /// The formal documentation for an is that the
+ /// property is either or . This depends on that
+ /// by testing the type of Payload and using the payload itself to produce the resulting
+ /// .
+ ///
+ [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Result is NOT simpler" )]
+ public static ISyntaxNode AsSyntaxNode( this IParseTree tree )
+ {
+ if(tree.Payload is IToken token)
+ {
+ return new TokenNode( token );
+ }
+ else if(tree.Payload is RuleContext rule)
+ {
+ return new RuleContextNode( rule );
+ }
+ else
+ {
+ throw new NotSupportedException( $"Unexpected payload type: {tree.Payload.GetType().Name}" );
+ }
+ }
+
+ /// Get the source location for an instance
+ /// Tree to get the location from
+ /// Location from the tree
+ /// for is invalid
+ ///
+ /// The formal documentation for an is that the
+ /// property is either or . This depends on that
+ /// by testing the type of Payload and using the payload itself to produce the resulting
+ /// location.
+ ///
+ [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Result is NOT simpler" )]
+ public static SourceLocation GetSourceLocation( this IParseTree tree )
+ {
+ if(tree.Payload is IToken token)
+ {
+ return token.GetSourceLocation();
+ }
+ else if(tree.Payload is RuleContext rule)
+ {
+ return rule.GetSourceLocation();
+ }
+ else
+ {
+ throw new NotSupportedException( $"Unexpected payload type: {tree.Payload.GetType().Name}" );
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParseTreeVisitorAdapter.cs b/src/Ubiquity.NET.ANTLR.Utils/ParseTreeVisitorAdapter.cs
new file mode 100644
index 0000000..2d89219
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParseTreeVisitorAdapter.cs
@@ -0,0 +1,24 @@
+// 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 DELETE_ME_LATER
+namespace Ubiquity.NET.ANTLR.Utils
+{
+ internal class ParseTreeVisitorAdapter
+ : ISyntaxTreeVisitor
+ {
+ public ParseTreeVisitorAdapter( IParseTreeVisitor parseTreeVisitor )
+ {
+ ParseTreeVisitor = parseTreeVisitor;
+ }
+
+ public TResult? Visit( ISyntaxNode node )
+ {
+ return node is IParseTree tree
+ ? ParseTreeVisitor.Visit(tree)
+ : default;
+ }
+
+ private readonly IParseTreeVisitor ParseTreeVisitor;
+ }
+}
+#endif
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParserExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/ParserExtensions.cs
new file mode 100644
index 0000000..c4753d1
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParserExtensions.cs
@@ -0,0 +1,89 @@
+// 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 extension methods for a
+ public static class ParserExtensions
+ {
+ /// Tries to get a Token stream from the parser
+ /// Type of stream to get (Must implement )
+ /// Parser to get the stream from
+ /// Stream from the parser
+ /// if the stream was retrieved
+ public static bool TryGetTokenStream( this Parser self, [MaybeNullWhen(false)] out T strm )
+ where T : ITokenStream
+ {
+ if(self.TokenStream is T t)
+ {
+ strm = t;
+ return true;
+ }
+
+ strm = default;
+ return false;
+ }
+
+ /// Tries to get a Token stream from the parser
+ /// Type of stream to get (Must implement )
+ /// Parser to get the stream from
+ /// Stream from the parser
+ /// Could not retrieve a stream of the specified type
+ public static T GetTokenStream( this Parser self )
+ where T : ITokenStream
+ {
+ return TryGetTokenStream( self, out T? retVal )
+ ? retVal
+ : throw new InvalidOperationException( $"Expected TokenStream of type {typeof( T )} but it is '{self.TokenStream.GetType()}'" );
+ }
+
+ /// Tries to get a Token Source from a
+ /// Type of the source (Must implement
+ /// to get the source from
+ /// out source if retrieved ( if not retrieved)
+ /// if the source was retrieved
+ ///
+ /// This is normally used to get the Lexer from a parser. In such cases
+ /// is the ANTLR generated lexer type for the grammar.
+ ///
+ public static bool TryGetTokenSource( this Parser self, [MaybeNullWhen(false)] out T tokenSrc )
+ where T : ITokenSource
+ {
+ if(self.TokenStream.TokenSource is T t)
+ {
+ tokenSrc = t;
+ return true;
+ }
+
+ tokenSrc = default;
+ return false;
+ }
+
+ /// Tries to get a Token Source from a
+ /// Type of the source (Must implement
+ /// to get the source from
+ /// Token source from the
+ ///
+ /// This is normally used to get the Lexer from a parser. In such cases
+ /// is the ANTLR generated lexer type for the grammar.
+ ///
+ /// Could not retrieve the source of the specified type
+ public static T GetTokenSource( this Parser self )
+ where T : ITokenSource
+ {
+ return TryGetTokenSource( self, out T? retVal )
+ ? retVal
+ : throw new InvalidOperationException( $"Expected TokenSource of type {typeof( T )} but it is '{self.TokenStream.TokenSource.GetType()}'" );
+ }
+
+ /// Gets the combined error listener, if any, for a
+ /// The parser to get the listener for
+ /// for the parser or if there is none or there is more than one
+ public static ICombinedParseErrorListener? GetCombinedErrorListener( this Parser p )
+ {
+ return p.ErrorListeners
+ .OfType()
+ .SingleOrDefault();
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/ParserRuleContextExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/ParserRuleContextExtensions.cs
new file mode 100644
index 0000000..4f2cb33
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/ParserRuleContextExtensions.cs
@@ -0,0 +1,112 @@
+// 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
+{
+ /// Extension methods for a
+ public static class ParserRuleContextExtensions
+ {
+ /// Gets the source name from a
+ /// Rule to get the source from
+ /// Source name from the stream that the rules start token came from
+ public static string GetSourceFileName( this ParserRuleContext ctx )
+ {
+ return ctx.Start.GetSourceFileName();
+ }
+
+ /// Gets the from a
+ /// Context to get the location from
+ /// Source Location of the context
+ public static SourceLocation GetSourceLocation( this ParserRuleContext ctx )
+ {
+ SourceLocation startLoc = ctx.Start.GetSourceLocation();
+ SourceLocation endLoc = ctx.Stop.GetSourceLocation();
+ return startLoc.Source != endLoc.Source
+ ? throw new ArgumentException( "Mismatched source for start and end tokens!", nameof( ctx ) )
+ : new SourceLocation( ctx.Start.InputStream.SourceName, startLoc.Range );
+ }
+
+ /// Determines if a spans multiple lines
+ /// Context to test
+ /// if spans multiple lines
+ public static bool IsMultiLine( this ParserRuleContext ctx )
+ {
+ return ctx.Start.Line < ctx.Stop.Line;
+ }
+
+ /// Gets an enumerable from the children of a
+ /// Context to get children from
+ /// A non-null, but possibly empty enumeration of children
+ public static IList GetChildren( this ParserRuleContext ctx )
+ {
+ return ctx.children == null || ctx.ChildCount == 0
+ ? []
+ : ctx.children;
+ }
+
+ /// Gets an enumerable of the children of a of the specified type
+ /// Type of children to provide an enumerable for
+ /// Context to get the children from
+ /// Non-null, but possibly empty enumeration of children matching the type of
+ public static IEnumerable GetChildren( this ParserRuleContext ctx )
+ {
+ return ctx.GetChildren().OfType();
+ }
+
+ /// Retrieves the index, in the source , of the first character of
+ /// Context to get the source index of
+ /// First character index
+ ///
+ ///
+ /// It is important to note that ANTLR does NOT guarantee the and
+ /// are ordered! That is the stop token may indicate a position in the input
+ /// that precedes that of the start token.
+ ///
+ /// This method provides guarantees on the ordering internally and provides the first character index in input stream index order.
+ ///
+ public static int FirstCharIndex( this ParserRuleContext ctx )
+ {
+ return Math.Min( ctx.Start.StartIndex, ctx.Stop.StopIndex );
+ }
+
+ /// Retrieves the index, in the source , of the last character of
+ /// Context to get the source index of
+ /// Last character index
+ ///
+ ///
+ /// It is important to note that ANTLR does NOT guarantee the and
+ /// are ordered! That is the stop token may indicate a position in the input
+ /// that precedes that of the start token.
+ ///
+ /// This method provides guarantees on the ordering internally and provides the last character index in input stream index order.
+ ///
+ public static int LastCharIndex( this ParserRuleContext ctx )
+ {
+ return Math.Max( ctx.Start.StartIndex, ctx.Stop.StopIndex );
+ }
+
+ /// Retrieves the length, in the source , of all characters of
+ /// Context to get the length of
+ /// Length of the input (character count) covered by the context
+ ///
+ ///
+ /// It is important to note that ANTLR does NOT guarantee the and
+ /// are ordered! That is the stop token may indicate a position in the input
+ /// that precedes that of the start token.
+ ///
+ /// This method provides guarantees on the ordering internally and provides the length of the input text (including skipped or "off channel" tokens).
+ ///
+ public static int CharLength( this ParserRuleContext ctx )
+ {
+ return ctx.LastCharIndex() - ctx.FirstCharIndex() + 1;
+ }
+
+ /// Gets the full source text for a
+ /// Context to get the source from
+ /// Full source text for the context (including any text hidden/skipped by the lexer)
+ public static string GetSourceText( this ParserRuleContext ctx )
+ {
+ return ctx.Start.InputStream.GetText( new Interval( ctx.FirstCharIndex(), ctx.LastCharIndex() ) );
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/PreBufferedTokenStream.cs b/src/Ubiquity.NET.ANTLR.Utils/PreBufferedTokenStream.cs
new file mode 100644
index 0000000..66f83aa
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/PreBufferedTokenStream.cs
@@ -0,0 +1,40 @@
+// 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
+{
+ /// A that pre-fetches **all** tokens in the constructor AND is an
+ ///
+ /// This class is used to cover scenarios where ALL tokens from lexical scanning are available. For example, in multi stage
+ /// editor classification a first pass of classification is based on only the results of scanning the input. Subsequent
+ /// passes will add/update classifications based on the results of a parse and yet again for semantic analysis of the
+ /// parse. This multi-layered approach allows for getting partial results while rapid edits are in process. (Canceling
+ /// a previous parse as it goes). The idea with these streams is to allow using the results of the lexical scan (all
+ /// channels) and then apply a filter for the default channel to pass to the parser. Without performing another,
+ /// redundant, lexical scan. [All the tokens were already produced - don't throw them away!]
+ ///
+ [SuppressMessage( "Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "It has the correct suffix that matches base" )]
+ public class PreBufferedTokenStream
+ : BufferedTokenStream
+ {
+ /// Initializes a new instance of the class
+ /// The token source to fetch tokens from
+ public PreBufferedTokenStream( ITokenSource src )
+ : base( src )
+ {
+ // fetch in chunks of 100, keep trying until EOF hit.
+ while(Fetch( 100 ) > 0)
+ {
+ }
+ }
+
+ /// Gets a for this stream
+ /// The tokens pre-fetched in this stream as a new
+ ///
+ /// This is normally used to pass the lexer results on to a parser after processing the results.
+ /// Most common use for this pattern is in scenarios where an input is fully scanned and the application
+ /// wants to deal with the tokens before parsing begins. (Typically used for multi-stage editor classification)
+ ///
+ public ITokenSource AsTokenSource( ) => new ListTokenSource( tokens, SourceName );
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/RuleContextExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/RuleContextExtensions.cs
new file mode 100644
index 0000000..2efc179
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/RuleContextExtensions.cs
@@ -0,0 +1,36 @@
+// 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
+{
+ /// Extension class for parser rules
+ public static class RuleContextExtensions
+ {
+ /// Gets the from a
+ /// Context to get the location from
+ /// Source Location of the context
+ ///
+ /// The rule must be a or a default location is returned
+ /// as the base doesn't provide access to location information
+ ///
+ public static SourceLocation GetSourceLocation( this RuleContext ctx )
+ {
+ // RuleContext doesn't track line information. Only the character index from beginning of the input...
+ // ParserRuleContext does track enough to form a line based location.
+ return ctx is ParserRuleContext parserRuleCtx
+ ? ParserRuleContextExtensions.GetSourceLocation( parserRuleCtx )
+ : default;
+ }
+
+ /// Gets an enumerable from the children of a
+ /// Context to get children from
+ /// A non-null, but possibly empty enumeration of children
+ public static IEnumerable GetChildren( this RuleContext ctx )
+ {
+ for(int i = 0; i < ctx.ChildCount; ++i)
+ {
+ yield return ctx.GetChild( i );
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/RuleContextNode.cs b/src/Ubiquity.NET.ANTLR.Utils/RuleContextNode.cs
new file mode 100644
index 0000000..325b9f4
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/RuleContextNode.cs
@@ -0,0 +1,21 @@
+// 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
+{
+ /// Implementation of for a
+ public class RuleContextNode
+ : SyntaxNodeBase
+ {
+ /// Initializes a new instance of the class
+ /// Rule context this node wraps
+ public RuleContextNode( RuleContext rule )
+ : base( rule.GetSourceLocation() )
+ {
+ Rule = rule;
+ }
+
+ /// Gets the this node wraps
+ public RuleContext Rule { get; }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/TerminalNodeExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/TerminalNodeExtensions.cs
new file mode 100644
index 0000000..fcf89c2
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/TerminalNodeExtensions.cs
@@ -0,0 +1,17 @@
+// 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 support extensions relating to source locations
+ public static class TerminalNodeExtensions
+ {
+ /// Gets the from an
+ /// Node to get the location from
+ /// of the node
+ public static SourceLocation GetSourceLocation( this ITerminalNode node )
+ {
+ return node.Symbol.GetSourceLocation();
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/TokenExtensions.cs b/src/Ubiquity.NET.ANTLR.Utils/TokenExtensions.cs
new file mode 100644
index 0000000..fabce06
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/TokenExtensions.cs
@@ -0,0 +1,56 @@
+// 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
+{
+ /// Extension methods for an
+ public static class TokenExtensions
+ {
+ private const int EoFTokenType = -1;
+
+ /// Determines if a token is the EOF token
+ /// Token to test
+ /// if the token is for an EOF
+ public static bool IsEOF( this IToken t )
+ {
+ return t.Type == EoFTokenType;
+ }
+
+ /// Gets the length (in characters) of the token
+ /// Token to get the length from
+ /// Length of the token
+ public static int Length( this IToken t )
+ {
+ // StartIndex and StopIndex are INCLUSIVE
+ // start(4) :--v v--: end(6) => 'ABC'
+ // 0123ABC789
+ return t.StopIndex - t.StartIndex + 1;
+ }
+
+ /// Gets the from an
+ /// Token to get the location from
+ /// of the token
+ public static SourceLocation GetSourceLocation( this IToken token )
+ {
+ // Assumes tokens exclude newlines and are therefore on the same line.
+ // This is a VERY reasonable assumption. In the unlikely event that
+ // there is ever a language that includes newlines in a token, then
+ // this would need to include counting the lines in the token to find
+ // the total number of lines covered to get the end correct.
+ // (+1 to column as SourceLocation uses a 1 based position, but ANTLR tokens are 0 based)
+ return new SourceLocation(
+ token.InputStream.SourceName,
+ new SourcePosition( token.Line, token.Column + 1, token.StartIndex ),
+ new SourcePosition( token.Line, token.Column + 1 + token.Length(), token.StopIndex )
+ );
+ }
+
+ /// Gets the source name from an
+ /// Token to get the source from
+ /// Source name from the stream the token came from
+ public static string GetSourceFileName( this IToken t )
+ {
+ return t.InputStream.SourceName;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/TokenNode.cs b/src/Ubiquity.NET.ANTLR.Utils/TokenNode.cs
new file mode 100644
index 0000000..03055c1
--- /dev/null
+++ b/src/Ubiquity.NET.ANTLR.Utils/TokenNode.cs
@@ -0,0 +1,62 @@
+// 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
+{
+ /// wrapper for a Lexical token
+ public class TokenNode
+ : SyntaxNodeBase
+ , ILexicalTokenNode
+ {
+ /// Initializes a new instance of the class
+ /// Token source this node is from
+ /// Location of the token
+ ///
+ /// This form is generally used for error reporting to indicate the location
+ /// and source of a lexical error. In such a case no valid token exists.
+ ///
+ public TokenNode( ITokenSource src, SourceLocation location )
+ : base( location )
+ {
+ Token = null; // no token for this node
+ TokenSource = src;
+ }
+
+ /// Initializes a new instance of the class
+ /// Token for this node
+ ///
+ /// This form uses the location from the token itself, assuming the location
+ /// of the token in the source is the true location.
+ ///
+ public TokenNode( IToken token )
+ : this( token, token.GetSourceLocation() )
+ {
+ }
+
+ /// Initializes a new instance of the class
+ /// Token for this node
+ /// Location of the token
+ ///
+ /// This form of constructor is used when the actual location is different from the
+ /// location from the token. This happens when the lexer the token is from comes
+ /// from lexical pass on a sub string in a larger document. In such cases the location
+ /// in the larger document is desired and is different from that of the token in the
+ /// sub range of the full document.
+ ///
+ public TokenNode( IToken token, SourceLocation location )
+ : base( location )
+ {
+ Token = token;
+ TokenSource = token.TokenSource;
+ }
+
+ ///
+ public int TokenKind => Token?.Type ?? 0;
+
+ /// Gets the that this node is from
+ public ITokenSource TokenSource { get; }
+
+ /// Gets the token for this node or if no token available
+ public IToken? Token { get; }
+ }
+}
diff --git a/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj b/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj
index 76334ea..54bc611 100644
--- a/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj
+++ b/src/Ubiquity.NET.ANTLR.Utils/Ubiquity.NET.ANTLR.Utils.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net10.0;net8.0
enable
True
True
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableAttributeData.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableAttributeData.cs
index b3c7707..3377d86 100644
--- a/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableAttributeData.cs
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/EquatableAttributeData.cs
@@ -25,7 +25,7 @@ public class EquatableAttributeData
/// The to capture equatable information from
public EquatableAttributeData( AttributeData data )
{
- PolyFillExceptionValidators.ThrowIfNull( data );
+ Requires.NotNull( data );
Name = data.GetNamespaceQualifiedName();
ConstructorArguments = [ .. data.ConstructorArguments.Select(e=>(StructurallyEquatableTypedConstant)e) ];
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/NamespaceQualifiedName.cs b/src/Ubiquity.NET.CodeAnalysis.Utils/NamespaceQualifiedName.cs
index 893f563..f3ae527 100644
--- a/src/Ubiquity.NET.CodeAnalysis.Utils/NamespaceQualifiedName.cs
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/NamespaceQualifiedName.cs
@@ -31,14 +31,14 @@ public NamespaceQualifiedName()
/// Unqualified name of the symbol
public NamespaceQualifiedName( IEnumerable namespaceNames, string simpleName )
{
- PolyFillExceptionValidators.ThrowIfNull(namespaceNames);
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(simpleName);
+ Requires.NotNull(namespaceNames);
+ Requires.NotNullOrWhiteSpace(simpleName);
SimpleName = simpleName;
NamespaceNames = [ .. namespaceNames.Select( s => ValidateNamespacePart(s) ) ];
static string ValidateNamespacePart( string s, [CallerArgumentExpression(nameof(s))] string? exp = null )
{
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( s, exp );
+ Requires.NotNullOrWhiteSpace( s, exp );
return s;
}
}
diff --git a/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj b/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj
index 2117c32..43b962b 100644
--- a/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj
+++ b/src/Ubiquity.NET.CodeAnalysis.Utils/Ubiquity.NET.CodeAnalysis.Utils.csproj
@@ -1,6 +1,7 @@
+
netstandard2.0
enable
@@ -57,6 +58,4 @@
-
-
diff --git a/src/Ubiquity.NET.CommandLine.SrcGen.UT/RootCommandAttributeTests.cs b/src/Ubiquity.NET.CommandLine.SrcGen.UT/RootCommandAttributeTests.cs
index 3d592ab..eaf0466 100644
--- a/src/Ubiquity.NET.CommandLine.SrcGen.UT/RootCommandAttributeTests.cs
+++ b/src/Ubiquity.NET.CommandLine.SrcGen.UT/RootCommandAttributeTests.cs
@@ -73,7 +73,8 @@ SourceText expectedContent
ReferenceAssemblies = testRuntime.ReferenceAssemblies,
AdditionalReferences =
{
- TestContext.GetUbiquityNetCommandLineLib( testRuntime )
+ TestContext.GetUbiquityNetCommandLineLib( testRuntime ),
+ TestContext.GetUbiquityNetExtensionsLib( testRuntime )
},
OutputKind = OutputKind.DynamicallyLinkedLibrary, // Don't require a Main() method
GeneratedSources = { (expectedHintPath, expectedContent) }
diff --git a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestFiles/RootCommandAttributeTests/DemoSource_succeeds/expected.cs b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestFiles/RootCommandAttributeTests/DemoSource_succeeds/expected.cs
index e25d996..2408319 100644
--- a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestFiles/RootCommandAttributeTests/DemoSource_succeeds/expected.cs
+++ b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestFiles/RootCommandAttributeTests/DemoSource_succeeds/expected.cs
@@ -55,8 +55,8 @@ file static class Descriptors
Required = true,
}.EnsureFolder();
- internal static readonly global::System.CommandLine.Option Verbosity
- = new global::System.CommandLine.Option("-v")
+ internal static readonly global::System.CommandLine.Option Verbosity
+ = new global::System.CommandLine.Option("-v")
{
Description = "Verbosity Level",
};
diff --git a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestHelpers.cs b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestHelpers.cs
index 9445a86..6244d00 100644
--- a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestHelpers.cs
+++ b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestHelpers.cs
@@ -39,45 +39,39 @@ internal string BuildOutputBinPath
get
{
string runDir = self.TestRunDirectory ?? throw new InvalidOperationException("No test run directory");
- return Path.GetFullPath( Path.Combine( runDir, "..", "..", "bin" ) );
+ string retVal = Path.GetFullPath( Path.Combine( runDir, "..", "..", "bin" ) );
+ return Directory.Exists(retVal)
+ ? retVal
+ : throw new DirectoryNotFoundException( $"Directory not found: '{retVal}'" );
}
}
public MetadataReference GetUbiquityNetCommandLineLib( TestRuntime testRuntime )
{
- string runtimeName = testRuntime switch
+ return self.GetDependentLib( testRuntime, "Ubiquity.NET.CommandLine" );
+ }
+
+ public MetadataReference GetUbiquityNetExtensionsLib( TestRuntime testRuntime )
+ {
+ return self.GetDependentLib( testRuntime, "Ubiquity.NET.Extensions" );
+ }
+
+ public MetadataReference GetDependentLib( TestRuntime testRuntime, string targetName )
+ {
+ string runtimePathPartName = testRuntime switch
{
TestRuntime.Net8_0 => "net8.0",
TestRuntime.Net10_0 => "net10.0",
_ => throw new InvalidEnumArgumentException(nameof(testRuntime), (int)testRuntime, typeof(TestRuntime))
};
- string pathName = Path.Combine( self.BuildOutputBinPath, "Ubiquity.NET.CommandLine", ConfigName, runtimeName, "Ubiquity.NET.CommandLine.dll" );
+ string pathName = Path.Combine( self.BuildOutputBinPath, targetName, ConfigName, runtimePathPartName, $"{targetName}.dll" );
return MetadataReference.CreateFromFile( pathName );
}
}
- #region .NET 10 Reference Assemblies
-
- /// Gets the .NET 10 reference assemblies
- ///
- /// Sadly, Microsoft.CodeAnalysis.Testing.ReferenceAssemblies does not contain any reference for .NET 10
- /// (even the latest version of that lib)
- ///
- ///
- public static ReferenceAssemblies Net10 => LazyNet10Refs.Value;
-
- private static readonly Lazy LazyNet10Refs = new(
- static ()=> new(
- targetFramework: "net10.0",
- referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.0"),
- referenceAssemblyPath: Path.Combine("ref", "net10.0")
- )
- );
- #endregion
-
// There is no way to detect the configuration at runtime to include the correct
- // reference to the DLL so use the compiler define to do the best available.
+ // reference to an assembly so use the compiler define to do the best available.
#if DEBUG
private const string ConfigName = "Debug";
#else
diff --git a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestRuntime.cs b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestRuntime.cs
index 2e3edc4..4654a79 100644
--- a/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestRuntime.cs
+++ b/src/Ubiquity.NET.CommandLine.SrcGen.UT/TestRuntime.cs
@@ -25,7 +25,7 @@ internal Microsoft.CodeAnalysis.Testing.ReferenceAssemblies ReferenceAssemblies
=> self switch
{
TestRuntime.Net8_0 => ReferenceAssemblies.Net.Net80,
- TestRuntime.Net10_0 => TestHelpers.Net10,
+ TestRuntime.Net10_0 => ReferenceAssemblies.Net.Net100,
_ => throw new InvalidEnumArgumentException( nameof( self ), (int)self, typeof( TestRuntime ) ),
};
diff --git a/src/Ubiquity.NET.CommandLine.SrcGen/Ubiquity.NET.CommandLine.SrcGen.csproj b/src/Ubiquity.NET.CommandLine.SrcGen/Ubiquity.NET.CommandLine.SrcGen.csproj
index 62d24c2..78b478c 100644
--- a/src/Ubiquity.NET.CommandLine.SrcGen/Ubiquity.NET.CommandLine.SrcGen.csproj
+++ b/src/Ubiquity.NET.CommandLine.SrcGen/Ubiquity.NET.CommandLine.SrcGen.csproj
@@ -51,9 +51,9 @@
-
+
<__AssemblyPath>$(PkgMicrosoft_Bcl_HashCode)\lib\$(TargetFramework)\*.dll
@@ -107,6 +107,4 @@
-
-
diff --git a/src/Ubiquity.NET.CommandLine.UT/ArgsParsingTests.cs b/src/Ubiquity.NET.CommandLine.UT/ArgsParsingTests.cs
index f5eea7c..f11d2eb 100644
--- a/src/Ubiquity.NET.CommandLine.UT/ArgsParsingTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/ArgsParsingTests.cs
@@ -6,6 +6,8 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Ubiquity.NET.Extensions;
+
namespace Ubiquity.NET.CommandLine.UT
{
[TestClass]
diff --git a/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs b/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs
index 74d5b6c..7f5a2ae 100644
--- a/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/ArgumentExceptionReporterTests.cs
@@ -6,6 +6,11 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Ubiquity.NET.Extensions;
+
+// Disambiguate from test framework type
+using MessageLevel = Ubiquity.NET.Extensions.MessageLevel;
+
namespace Ubiquity.NET.CommandLine.UT
{
[TestClass]
@@ -23,7 +28,7 @@ public void ArgumentExceptionReporterTest( )
var reporter = new ArgumentExceptionReporter(ParamExpressionName);
Assert.IsNotNull( reporter );
Assert.AreEqual( ParamExpressionName, reporter.ArgumentExpression );
- Assert.AreEqual( MsgLevel.Error, reporter.Level );
+ Assert.AreEqual( MessageLevel.Error, reporter.Level );
Assert.AreEqual( Encoding.Unicode, reporter.Encoding );
}
@@ -35,7 +40,7 @@ public void ReportTest( )
// should not throw for a Verbose level message
var verboseMsg = new DiagnosticMessage()
{
- Level = MsgLevel.Verbose,
+ Level = MessageLevel.Verbose,
Text = VerboseMessage,
};
@@ -44,7 +49,7 @@ public void ReportTest( )
// should not throw for an Information level message
var informationMsg = new DiagnosticMessage()
{
- Level = MsgLevel.Information,
+ Level = MessageLevel.Information,
Text = InformationMessage,
};
@@ -53,7 +58,7 @@ public void ReportTest( )
// should not throw for a warning
var warningMsg = new DiagnosticMessage()
{
- Level = MsgLevel.Warning,
+ Level = MessageLevel.Warning,
Text = WarningMessage,
};
@@ -62,7 +67,7 @@ public void ReportTest( )
// should only throw for an error
var errorMsg = new DiagnosticMessage()
{
- Level = MsgLevel.Error,
+ Level = MessageLevel.Error,
Text = ErrorMessage,
};
diff --git a/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs b/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs
index 78fdb9d..f04aca0 100644
--- a/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/CommandLineTests.cs
@@ -9,6 +9,11 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Ubiquity.NET.Extensions;
+
+// Disambiguate from test framework type
+using MessageLevel = Ubiquity.NET.Extensions.MessageLevel;
+
namespace Ubiquity.NET.CommandLine.UT
{
[TestClass]
@@ -183,7 +188,7 @@ public void Extensions_ParseAndInvokeResult_invokes_provided_action( )
bool actionCalled = false;
var settings = CreateTestSettings();
using var stringWriter = new StringWriter();
- var reporter = new TextWriterReporter( MsgLevel.Error, error: stringWriter, warning: stringWriter, information: stringWriter, verbose: stringWriter );
+ var reporter = new TextWriterReporter( MessageLevel.Error, error: stringWriter, warning: stringWriter, information: stringWriter, verbose: stringWriter );
string[] testArgs = ["--option1", "value1"];
@@ -288,7 +293,7 @@ public async Task Extensions_ParseAndInvokeResultAsync_invokes_provided_action(
bool actionCalled = false;
var settings = CreateTestSettings();
using var stringWriter = new StringWriter();
- var reporter = new TextWriterReporter( MsgLevel.Error, error: stringWriter, warning: stringWriter, information: stringWriter, verbose: stringWriter );
+ var reporter = new TextWriterReporter( MessageLevel.Error, error: stringWriter, warning: stringWriter, information: stringWriter, verbose: stringWriter );
string[] testArgs = ["--option1", "value1"];
diff --git a/src/Ubiquity.NET.CommandLine.UT/DiagnosticMessageTests.cs b/src/Ubiquity.NET.CommandLine.UT/DiagnosticMessageTests.cs
index c32f994..b155e7d 100644
--- a/src/Ubiquity.NET.CommandLine.UT/DiagnosticMessageTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/DiagnosticMessageTests.cs
@@ -10,6 +10,9 @@
using Ubiquity.NET.Extensions;
+// Disambiguate from test framework type
+using MessageLevel = Ubiquity.NET.Extensions.MessageLevel;
+
namespace Ubiquity.NET.CommandLine.UT
{
[TestClass]
@@ -18,17 +21,16 @@ public class DiagnosticMessageTests
[TestMethod]
public void Construction_throws_on_invalid_init( )
{
- Assert.AreEqual( default, MsgLevel.None, "None should be the default (invalid) level" );
+ Assert.AreEqual( default, MessageLevel.None, "None should be the default (invalid) level" );
Assert.ThrowsExactly(
static ( ) =>
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = "Has Whitespace",
- Level = MsgLevel.Verbose,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Verbose,
Subcategory = default,
Text = string.Empty,
};
@@ -41,10 +43,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
- Level = MsgLevel.Verbose,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Verbose,
Subcategory = "Has Whitespace",
Text = string.Empty,
};
@@ -57,10 +58,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
Level = default,
- Location = default,
- Origin = default,
Subcategory = default,
Text = string.Empty,
};
@@ -73,10 +73,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
- Level = (MsgLevel)int.MaxValue,
- Location = default,
- Origin = default,
+ Level = (MessageLevel)int.MaxValue,
Subcategory = default,
Text = string.Empty,
};
@@ -89,10 +88,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
- Level = MsgLevel.Verbose,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Verbose,
Subcategory = default,
Text = string.Empty,
};
@@ -105,10 +103,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
- Level = MsgLevel.Verbose,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Verbose,
Subcategory = default,
Text = " \t\n\r\f",
};
@@ -123,10 +120,9 @@ public void Construction_throws_on_invalid_init( )
{
_ = new DiagnosticMessage()
{
+ SourceLocation = default,
Code = default,
- Level = MsgLevel.Verbose,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Verbose,
Subcategory = default,
Text = null,
};
@@ -142,25 +138,24 @@ public void Initialization_sets_proerties_as_expected( )
const string testCode = "CODE1";
const string testSubCategory = "TestSubcategory";
const string testMsg = "This is a test message";
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
var testLoc = new SourceRange(new SourcePosition(1,2,3), new SourcePosition(2,1,4));
var testOrigin = new Uri("file://MyOrigin");
var msg = new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation(testOrigin, testLoc),
Code = testCode,
Level = testLevel,
- Location = testLoc,
- Origin = testOrigin,
Subcategory = testSubCategory,
Text = testMsg,
};
Assert.AreEqual( testCode, msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.AreEqual( testLoc, msg.Location );
- Assert.AreEqual( testOrigin, msg.Origin );
+ Assert.AreEqual( testLoc, msg.SourceLocation.Range );
+ Assert.AreEqual( testOrigin, msg.SourceLocation.Source );
Assert.AreEqual( testSubCategory, msg.Subcategory );
Assert.AreEqual( testMsg, msg.Text );
}
@@ -171,10 +166,9 @@ public void ToString_produces_Text_if_origin_is_null_or_whitespace( )
const string testMsg = "This is a test";
var msg = new DiagnosticMessage( )
{
+ SourceLocation = default,
Code = default,
- Level = MsgLevel.Error,
- Location = default,
- Origin = default,
+ Level = MessageLevel.Error,
Subcategory = default,
Text = testMsg,
};
@@ -190,9 +184,9 @@ public void ToString_produces_correctly_formatted_msg( )
const string testCode = "CODE1";
const string testSubCategory = "Subcategory";
const string testMsg = "This is a test message";
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
- var testLoc = new SourceRange(new SourcePosition(1,2,3), new SourcePosition(2,1,4));
+ var testRange = new SourceRange(new SourcePosition(1,2,3), new SourcePosition(2,1,4));
var testOrigin = new Uri("file://C:/MyOrigin.txt");
// MSBuild format: 'Origin : Subcategory Category Code : Text'
@@ -200,15 +194,14 @@ public void ToString_produces_correctly_formatted_msg( )
var msg = new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation(testOrigin, testRange),
Code = testCode,
Level = testLevel,
- Location = testLoc,
- Origin = testOrigin,
Subcategory = testSubCategory,
Text = testMsg,
};
- string actual = msg.ToString();
+ string actual = msg.ToString("M", null);
Assert.AreEqual( expectedMsBuildMsg, msg.ToString( "M", null ) );
// Test platform specific forms
@@ -217,7 +210,14 @@ public void ToString_produces_correctly_formatted_msg( )
// Test currently assumes all runtime specific forms are MSBuild
// As new runtimes are supported this should validate them.
Assert.AreEqual( expectedMsBuildMsg, msg.ToString() );
- Assert.AreEqual( expectedMsBuildMsg, msg.ToString( "G", CultureInfo.CurrentCulture ) );
+ if(OperatingSystem.IsWindows())
+ {
+ Assert.AreEqual( expectedMsBuildMsg, msg.ToString( "G", CultureInfo.CurrentCulture ) );
+ }
+ else
+ {
+ Assert.Inconclusive("Non-Windows platforms use platform specific generic form, not yet accounted for in this test");
+ }
}
}
}
diff --git a/src/Ubiquity.NET.CommandLine.UT/DiagnosticReporterExtensionsTests.cs b/src/Ubiquity.NET.CommandLine.UT/DiagnosticReporterExtensionsTests.cs
index 5e0f785..6f7fbc6 100644
--- a/src/Ubiquity.NET.CommandLine.UT/DiagnosticReporterExtensionsTests.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/DiagnosticReporterExtensionsTests.cs
@@ -9,6 +9,9 @@
using Ubiquity.NET.Extensions;
+// Disambiguate from test framework type
+using MessageLevel = Ubiquity.NET.Extensions.MessageLevel;
+
namespace Ubiquity.NET.CommandLine.UT
{
[TestClass]
@@ -63,7 +66,7 @@ public void Report_overload_2_Tests( )
// [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
//)
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
const string testValue = "[Boo]";
const string expectedMsg = "This is a test value: [Boo]";
@@ -75,8 +78,8 @@ public void Report_overload_2_Tests( )
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.IsNull( msg.Location );
- Assert.IsNull( msg.Origin );
+ Assert.IsNull( msg.SourceLocation.Source );
+ Assert.AreEqual( msg.SourceLocation.Range, default );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( expectedMsg, msg.Text );
@@ -84,7 +87,7 @@ public void Report_overload_2_Tests( )
// Validate interpolated string handler filters correctly
// Messages captured must be Information+
- reporter = new TestReporter( MsgLevel.Information );
+ reporter = new TestReporter( MessageLevel.Information );
DiagnosticReporterExtensions.Report( reporter, testLevel, $"This is a test value: {SetValue( testValue )}" );
Assert.HasCount( 0, reporter.VerboseMessages );
Assert.HasCount( 0, reporter.AllMessages );
@@ -110,7 +113,7 @@ public void Report_overload_3_Tests( )
// string msg
//)
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
const string testMsg = "This is a test";
var reporter = new TestReporter(); // Default is capture all
@@ -121,13 +124,13 @@ public void Report_overload_3_Tests( )
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.IsNull( msg.Location );
- Assert.IsNull( msg.Origin );
+ Assert.IsNull( msg.SourceLocation.Source );
+ Assert.AreEqual( msg.SourceLocation.Range, default );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( testMsg, msg.Text );
// Messages captured must be Information+
- reporter = new TestReporter( MsgLevel.Information );
+ reporter = new TestReporter( MessageLevel.Information );
DiagnosticReporterExtensions.Report( reporter, testLevel, testMsg );
Assert.HasCount( 0, reporter.VerboseMessages );
Assert.HasCount( 0, reporter.AllMessages );
@@ -143,7 +146,7 @@ public void Report_overload_4_Tests( )
// [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
//)
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
var testLocation = new SourceRange(new SourcePosition(2,3,4), new SourcePosition(3,4,5));
const string testValue = "[Boo]";
const string expectedMsg = "This is a test value: [Boo]";
@@ -156,8 +159,8 @@ public void Report_overload_4_Tests( )
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.AreEqual( testLocation, msg.Location );
- Assert.IsNull( msg.Origin );
+ Assert.AreEqual( testLocation, msg.SourceLocation.Range );
+ Assert.IsNull( msg.SourceLocation.Source );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( expectedMsg, msg.Text );
@@ -165,7 +168,7 @@ public void Report_overload_4_Tests( )
// Validate interpolated string handler filters correctly
// Messages captured must be Information+
- reporter = new TestReporter( MsgLevel.Information );
+ reporter = new TestReporter( MessageLevel.Information );
DiagnosticReporterExtensions.Report( reporter, testLevel, testLocation, $"This is a test value: {SetValue( testValue )}" );
Assert.HasCount( 0, reporter.VerboseMessages );
Assert.HasCount( 0, reporter.AllMessages );
@@ -194,7 +197,7 @@ public void Report_overload_5_Tests( )
// params object[] args
// )
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
var testLocation = new SourceRange(new SourcePosition(2,3,4), new SourcePosition(3,4,5));
var testOrigin = new Uri("file://foo");
const string testFormat = "This is a test value: {0}";
@@ -209,13 +212,13 @@ public void Report_overload_5_Tests( )
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.AreEqual( testLocation, msg.Location );
- Assert.AreEqual( testOrigin, msg.Origin );
+ Assert.AreEqual( testLocation, msg.SourceLocation.Range );
+ Assert.AreEqual( testOrigin, msg.SourceLocation.Source );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( expectedMsg, msg.Text );
// Messages captured must be Information+
- reporter = new TestReporter( MsgLevel.Information );
+ reporter = new TestReporter( MessageLevel.Information );
DiagnosticReporterExtensions.Report( reporter, testLevel, testOrigin, testLocation, testFormat, testValue );
Assert.HasCount( 0, reporter.VerboseMessages );
Assert.HasCount( 0, reporter.AllMessages );
@@ -232,7 +235,7 @@ public void Report_overload6_Tests( )
// [InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
//)
- const MsgLevel level = MsgLevel.Verbose;
+ const MessageLevel level = MessageLevel.Verbose;
var location = new SourceRange(new SourcePosition(2,3,4), new SourcePosition(3,4,5));
var origin = new Uri("file://foo");
string testValue = "[Boo]";
@@ -243,9 +246,9 @@ public void Report_overload6_Tests( )
Assert.HasCount( 1, reporter.VerboseMessages );
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
- Assert.AreEqual( MsgLevel.Verbose, msg.Level );
- Assert.AreEqual( location, msg.Location );
- Assert.AreEqual( origin, msg.Origin );
+ Assert.AreEqual( MessageLevel.Verbose, msg.Level );
+ Assert.AreEqual( location, msg.SourceLocation.Range );
+ Assert.AreEqual( origin, msg.SourceLocation.Source );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( expectedMsg, msg.Text );
@@ -253,7 +256,7 @@ public void Report_overload6_Tests( )
// Validate interpolated string handler filters correctly
// Messages captured must be Information+
- reporter = new TestReporter( MsgLevel.Information );
+ reporter = new TestReporter( MessageLevel.Information );
DiagnosticReporterExtensions.Report( reporter, level, $"This is a test value: {SetValue( testValue )}" );
Assert.HasCount( 0, reporter.VerboseMessages );
Assert.HasCount( 0, reporter.AllMessages );
@@ -287,13 +290,13 @@ public void Report_overload7_Tests( )
var origin = new Uri("file://foo");
string expecteMsg = $"Testing 1, 2, {1.23.ToString(CultureInfo.CurrentCulture)}";
- DiagnosticReporterExtensions.Report( reporter, MsgLevel.Verbose, origin, location, "Testing 1, 2, {0}", 1.23 );
+ DiagnosticReporterExtensions.Report( reporter, MessageLevel.Verbose, origin, location, "Testing 1, 2, {0}", 1.23 );
Assert.HasCount( 1, reporter.VerboseMessages );
var msg = reporter.VerboseMessages[ 0 ];
Assert.IsNull( msg.Code );
- Assert.AreEqual( MsgLevel.Verbose, msg.Level );
- Assert.AreEqual( location, msg.Location );
- Assert.AreEqual( origin, msg.Origin );
+ Assert.AreEqual( MessageLevel.Verbose, msg.Level );
+ Assert.AreEqual( location, msg.SourceLocation.Range );
+ Assert.AreEqual( origin, msg.SourceLocation.Source );
Assert.IsNull( msg.Subcategory );
Assert.AreEqual( expecteMsg, msg.Text );
}
@@ -312,7 +315,7 @@ public void Report_overload8_Tests( )
// string? code = default
// )
- const MsgLevel testLevel = MsgLevel.Verbose;
+ const MessageLevel testLevel = MessageLevel.Verbose;
var testLocation = new SourceRange(new SourcePosition(2,3,4), new SourcePosition(3,4,5));
var testOrigin = new Uri("file://foo");
const string testMsg = "This is a test";
@@ -326,8 +329,8 @@ public void Report_overload8_Tests( )
var msg = reporter.VerboseMessages[ 0 ];
Assert.AreEqual( testCode, msg.Code );
Assert.AreEqual( testLevel, msg.Level );
- Assert.AreEqual( testLocation, msg.Location );
- Assert.AreEqual( testOrigin, msg.Origin );
+ Assert.AreEqual( testLocation, msg.SourceLocation.Range );
+ Assert.AreEqual( testOrigin, msg.SourceLocation.Source );
Assert.AreEqual( testSubcategory, msg.Subcategory );
Assert.AreEqual( testMsg, msg.Text );
}
@@ -336,37 +339,33 @@ public void Report_overload8_Tests( )
= [
new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation( new System.Uri( @"file://foo" ), new SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ) ),
Code = "Code0",
- Level = MsgLevel.Error,
- Location = new Extensions.SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ),
- Origin = new System.Uri( @"file://foo" ),
+ Level = MessageLevel.Error,
Subcategory = "subcategory",
Text = "Text"
},
new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation( new System.Uri( @"file://foo" ), new SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ) ),
Code = "Code1",
- Level = MsgLevel.Warning,
- Location = new Extensions.SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ),
- Origin = new System.Uri( @"file://foo" ),
+ Level = MessageLevel.Warning,
Subcategory = "subcategory",
Text = "Text"
},
new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation( new System.Uri( @"file://foo" ), new SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ) ),
Code = "Code2",
- Level = MsgLevel.Information,
- Location = new Extensions.SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ),
- Origin = new System.Uri( @"file://foo" ),
+ Level = MessageLevel.Information,
Subcategory = "subcategory",
Text = "Text"
},
new DiagnosticMessage( )
{
+ SourceLocation = new SourceLocation( new System.Uri( @"file://foo" ), new SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ) ),
Code = "Code3",
- Level = MsgLevel.Verbose,
- Location = new Extensions.SourceRange( new SourcePosition( 2, 3, 10 ), new SourcePosition( 3, 3, 12 ) ),
- Origin = new System.Uri( @"file://foo" ),
+ Level = MessageLevel.Verbose,
Subcategory = "subcategory",
Text = "Text"
}
diff --git a/src/Ubiquity.NET.CommandLine.UT/TestReporter.cs b/src/Ubiquity.NET.CommandLine.UT/TestReporter.cs
index 8f9cc56..56ded62 100644
--- a/src/Ubiquity.NET.CommandLine.UT/TestReporter.cs
+++ b/src/Ubiquity.NET.CommandLine.UT/TestReporter.cs
@@ -8,29 +8,34 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Ubiquity.NET.Extensions;
+
+// Disambiguate from test framework type
+using MessageLevel = Ubiquity.NET.Extensions.MessageLevel;
+
namespace Ubiquity.NET.CommandLine.UT
{
internal class TestReporter
: IDiagnosticReporter
{
- public TestReporter(MsgLevel level)
+ public TestReporter(MessageLevel level)
{
Level = level;
}
public TestReporter()
- : this( MsgLevel.Verbose ) // Captures ALL messages (Max level)
+ : this( MessageLevel.Verbose ) // Captures ALL messages (Max level)
{
}
- public MsgLevel Level { get; }
+ public MessageLevel Level { get; }
public Encoding Encoding => Encoding.Unicode;
///
///
/// This implementation will test if the of the
- /// message is enabled. If so, then a call is made to the virtual
+ /// 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 )
@@ -46,23 +51,23 @@ public void Report( DiagnosticMessage diagnostic )
/// Gets ALL of the messages provided to this reporter
public List AllMessages { get; } = [];
- /// Gets the messages with a level of
+ /// Gets the messages with a level of
/// This is always an error and tests should validate this is an empty array
- public ImmutableArray NoneMessages => [ .. GetMessages( MsgLevel.None ) ];
+ public ImmutableArray NoneMessages => [ .. GetMessages( MessageLevel.None ) ];
- /// Gets the messages with a level of
- public ImmutableArray VerboseMessages => [ .. GetMessages(MsgLevel.Verbose) ];
+ /// Gets the messages with a level of
+ public ImmutableArray VerboseMessages => [ .. GetMessages(MessageLevel.Verbose) ];
- /// Gets the messages with a level of
- public ImmutableArray InformationMessages => [ .. GetMessages( MsgLevel.Information ) ];
+ /// Gets the messages with a level of
+ public ImmutableArray InformationMessages => [ .. GetMessages( MessageLevel.Information ) ];
- /// Gets the messages with a level of
- public ImmutableArray WarningMessages => [ .. GetMessages( MsgLevel.Warning ) ];
+ /// Gets the messages with a level of
+ public ImmutableArray WarningMessages => [ .. GetMessages( MessageLevel.Warning ) ];
- /// Gets the messages with a level of
- public ImmutableArray ErrorMessages => [ .. GetMessages( MsgLevel.Error ) ];
+ /// Gets the messages with a level of
+ public ImmutableArray ErrorMessages => [ .. GetMessages( MessageLevel.Error ) ];
- private IEnumerable GetMessages(MsgLevel level)
+ private IEnumerable GetMessages(MessageLevel level)
{
return from dm in AllMessages
where dm.Level == level
diff --git a/src/Ubiquity.NET.CommandLine/ArgsParsing.cs b/src/Ubiquity.NET.CommandLine/ArgsParsing.cs
index 457d226..d48287e 100644
--- a/src/Ubiquity.NET.CommandLine/ArgsParsing.cs
+++ b/src/Ubiquity.NET.CommandLine/ArgsParsing.cs
@@ -189,7 +189,7 @@ public static bool TryParse( string[] args, CommandLineSettings settings, out
where T : IRootCommandBuilder, ICommandBinder
{
ArgumentNullException.ThrowIfNull( settings );
- return TryParse( args, settings, new ConsoleReporter( MsgLevel.Information ), out boundValue, out exitCode );
+ return TryParse( args, settings, new ConsoleReporter( MessageLevel.Information ), out boundValue, out exitCode );
}
///
@@ -197,7 +197,7 @@ public static bool TryParse( string[] args, CommandLineSettings settings, out
public static bool TryParse( string[] args, out T? boundValue, out int exitCode )
where T : IRootCommandBuilder, ICommandBinder
{
- return TryParse( args, settings: null, new ConsoleReporter( MsgLevel.Information ), out boundValue, out exitCode );
+ return TryParse( args, settings: null, new ConsoleReporter( MessageLevel.Information ), out boundValue, out exitCode );
}
///
diff --git a/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs b/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs
index 9d871ab..bb95703 100644
--- a/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs
+++ b/src/Ubiquity.NET.CommandLine/GlobalNamespaceImports.cs
@@ -13,15 +13,12 @@ set of namespaces that is NOT consistent or controlled by the developer. THAT is
*/
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;
@@ -31,7 +28,4 @@ set of namespaces that is NOT consistent or controlled by the developer. THAT is
global using System.Threading;
global using System.Threading.Tasks;
-global using AnsiCodes;
-
-global using Ubiquity.NET.CommandLine.InterpolatedStringHandlers;
global using Ubiquity.NET.Extensions;
diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs
deleted file mode 100644
index 52ba40a..0000000
--- 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 23ad500..0000000
--- 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 0e5e069..0000000
--- 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 6bfc643..0000000
--- 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/ReportingSettings.cs b/src/Ubiquity.NET.CommandLine/ReportingSettings.cs
index 2909854..506c0db 100644
--- a/src/Ubiquity.NET.CommandLine/ReportingSettings.cs
+++ b/src/Ubiquity.NET.CommandLine/ReportingSettings.cs
@@ -14,8 +14,8 @@ public static InvocationConfiguration CreateConfig( this IDiagnosticReporter sel
return new()
{
EnableDefaultExceptionHandler = enableDefaultHandler,
- Error = new DiagnosticReportingWriter( self, MsgLevel.Error ),
- Output = new DiagnosticReportingWriter( self, MsgLevel.Information ),
+ Error = new DiagnosticReportingWriter( self, MessageLevel.Error ),
+ Output = new DiagnosticReportingWriter( self, MessageLevel.Information ),
ProcessTerminationTimeout = timeout,
};
}
diff --git a/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj b/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj
index 1290f49..b5bd264 100644
--- a/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj
+++ b/src/Ubiquity.NET.CommandLine/Ubiquity.NET.CommandLine.csproj
@@ -30,7 +30,6 @@
-
diff --git a/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs b/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs
index ff0021d..ef67b35 100644
--- a/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs
+++ b/src/Ubiquity.NET.Extensions.UT/FluentValidation/ExceptionValidationExtensionsTests.cs
@@ -1,8 +1,6 @@
// 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]
@@ -14,7 +12,7 @@ public void ThrowIfNull_throws_expected_exception_when_null( )
{
var ex = Assert.ThrowsExactly(()=>
{
- ExceptionValidationExtensions.ThrowIfNull( null );
+ FluentValidationExtensions.ThrowIfNull( null );
} );
Assert.AreEqual("null", ex.ParamName, "parameter name should match input expression");
}
@@ -24,7 +22,7 @@ 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" );
+ Assert.AreSame(input, FluentValidationExtensions.ThrowIfNull(input), "Fluent API should return input value on success" );
}
[TestMethod]
@@ -33,7 +31,7 @@ public void ThrowIfNull_reports_exception_whith_provided_expression( )
const string exp = "My-Expression";
var ex = Assert.ThrowsExactly(()=>
{
- ExceptionValidationExtensions.ThrowIfNull( null, exp );
+ FluentValidationExtensions.ThrowIfNull( null, exp );
} );
Assert.AreEqual( exp, ex.ParamName, "parameter name should match input expression" );
}
@@ -41,7 +39,7 @@ public void ThrowIfNull_reports_exception_whith_provided_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" );
+ Assert.AreEqual(TestEnum.Max, FluentValidationExtensions.ThrowIfNotDefined(TestEnum.Max), "Fluent API should return input value on success" );
}
[TestMethod]
@@ -51,7 +49,7 @@ public void ThrowIfOutOfRange_does_not_throw_for_inrange_values( )
double min = 0.0;
double max = 2.0;
- Assert.AreEqual(value, ExceptionValidationExtensions.ThrowIfOutOfRange(value, min, max), "Fluent API should return input value on success");
+ Assert.AreEqual(value, FluentValidationExtensions.ThrowIfOutOfRange(value, min, max), "Fluent API should return input value on success");
}
[TestMethod]
@@ -63,7 +61,7 @@ public void ThrowIfOutOfRange_throws_for_out_of_range_values( )
var ex = Assert.ThrowsExactly(()=>
{
- _ = ExceptionValidationExtensions.ThrowIfOutOfRange( value, min, max );
+ _ = FluentValidationExtensions.ThrowIfOutOfRange( value, min, max );
} );
Assert.AreEqual(value, ex.ActualValue);
Assert.AreEqual(nameof(value), ex.ParamName);
@@ -79,7 +77,7 @@ public void ThrowIfOutOfRange_throws_with_custom_expression_for_out_of_range_val
const string exp = "My Expression";
var ex = Assert.ThrowsExactly(()=>
{
- _ = ExceptionValidationExtensions.ThrowIfOutOfRange( value, min, max, exp );
+ _ = FluentValidationExtensions.ThrowIfOutOfRange( value, min, max, exp );
} );
Assert.AreEqual( value, ex.ActualValue );
Assert.AreEqual( exp, ex.ParamName );
@@ -91,14 +89,14 @@ public void ThrowIfNotDefined_throws_for_undefined_values( )
var temp = (TestEnum)4;
var ex = Assert.ThrowsExactly( ( ) =>
{
- ExceptionValidationExtensions.ThrowIfNotDefined(temp);
+ FluentValidationExtensions.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);
+ FluentValidationExtensions.ThrowIfNotDefined(temp2);
} );
Assert.AreEqual( nameof( temp2 ), ex2.ParamName, "parameter name should match input expression" );
@@ -106,7 +104,7 @@ public void ThrowIfNotDefined_throws_for_undefined_values( )
var temp3 = (TestU64Enum)int.MaxValue;
var ex3 = Assert.ThrowsExactly( ( ) =>
{
- ExceptionValidationExtensions.ThrowIfNotDefined(temp3);
+ FluentValidationExtensions.ThrowIfNotDefined(temp3);
} );
Assert.AreEqual( nameof( temp3 ), ex3.ParamName, "parameter name should match input expression" );
@@ -115,7 +113,7 @@ public void ThrowIfNotDefined_throws_for_undefined_values( )
var temp4 = (TestU64Enum)(UInt64.MaxValue - 1);
var ex4 = Assert.ThrowsExactly( ( ) =>
{
- ExceptionValidationExtensions.ThrowIfNotDefined(temp4);
+ FluentValidationExtensions.ThrowIfNotDefined(temp4);
} );
Assert.IsNull( ex4.ParamName, "parameter name not available for non-int formattable enums" );
}
diff --git a/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs b/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs
index fc3dda8..50dc95e 100644
--- a/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs
+++ b/src/Ubiquity.NET.Extensions.UT/PolyFillExceptionValidatorsTests.cs
@@ -3,9 +3,10 @@
// .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
+
+#if NET481
+
+using Ubiquity.NET.PolyFill;
namespace Ubiquity.NET.Extensions.UT
{
diff --git a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj b/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
index 0db6bc4..a0a7ba1 100644
--- a/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
+++ b/src/Ubiquity.NET.Extensions.UT/Ubiquity.NET.Extensions.UT.csproj
@@ -50,6 +50,4 @@
TestResources1.Designer.cs
-
-
diff --git a/src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs b/src/Ubiquity.NET.Extensions/ArgumentExceptionReporter.cs
similarity index 85%
rename from src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs
rename to src/Ubiquity.NET.Extensions/ArgumentExceptionReporter.cs
index b46f70b..8e6c966 100644
--- a/src/Ubiquity.NET.CommandLine/ArgumentExceptionReporter.cs
+++ b/src/Ubiquity.NET.Extensions/ArgumentExceptionReporter.cs
@@ -1,7 +1,7 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// Implementation of that throws for any errors reported.
public class ArgumentExceptionReporter
@@ -18,12 +18,12 @@ public ArgumentExceptionReporter( string exp )
public string ArgumentExpression { get; }
///
- public MsgLevel Level => MsgLevel.Error;
+ public MessageLevel Level => MessageLevel.Error;
///
public Encoding Encoding => Encoding.Unicode;
- /// Any diagnostics with will throw an argument exception
+ /// Any diagnostics with a level at or above will throw an argument exception
///
public void Report( DiagnosticMessage diagnostic )
{
diff --git a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs b/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
index 1df4bc1..0fb445c 100644
--- a/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/AssemblyExtensions.cs
@@ -13,11 +13,7 @@ public static class AssemblyExtensions
/// String contents from the in the assembly or
public static string GetInformationalVersion(this Assembly self, [CallerArgumentExpression( nameof( self ) )] string? exp = null )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self, exp );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self, exp );
-#endif
+ Requires.NotNull( self, exp );
var assemblyVersionAttribute = self.GetCustomAttribute();
diff --git a/src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs b/src/Ubiquity.NET.Extensions/ColoredConsoleReporter.cs
similarity index 54%
rename from src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs
rename to src/Ubiquity.NET.Extensions/ColoredConsoleReporter.cs
index cc66643..2eb690e 100644
--- a/src/Ubiquity.NET.CommandLine/ColoredConsoleReporter.cs
+++ b/src/Ubiquity.NET.Extensions/ColoredConsoleReporter.cs
@@ -1,14 +1,14 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// 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:
+ /// 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.
@@ -16,64 +16,65 @@ public sealed class ColoredConsoleReporter
/// 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)
+ public ColoredConsoleReporter( MessageLevel level = MessageLevel.Information, ImmutableDictionary? colorMapping = null)
: base(level)
{
- ColorMap = colorMapping ?? ImmutableDictionary.Empty;
+ ColorMap = colorMapping ?? ImmutableDictionary.Empty;
}
#if NET10_0_OR_GREATER
- /// Gets the to colorMapping used for coloring
- public ImmutableDictionary ColorMap
+ /// Gets the to colorMapping used for coloring
+ public ImmutableDictionary ColorMap
{
get => field.IsEmpty ? DefaultColorMap : field;
init
{
- ArgumentNullException.ThrowIfNull(value);
+ Requires.NotNull(value);
field = value;
}
}
#else
- /// Gets the to colorMapping used for coloring
- public ImmutableDictionary ColorMap
+ /// Gets the to colorMapping used for coloring
+ public ImmutableDictionary ColorMap
{
get => ColorMapBackingField.IsEmpty ? DefaultColorMap : ColorMapBackingField;
init
{
- ArgumentNullException.ThrowIfNull( value );
-
+ Requires.NotNull( value );
ColorMapBackingField = value;
}
}
- private readonly ImmutableDictionary ColorMapBackingField = DefaultColorMap;
+#pragma warning disable IDE0032 // Use auto property
+ private readonly ImmutableDictionary ColorMapBackingField = DefaultColorMap;
+#pragma warning restore IDE0032 // Use auto property
#endif
/// Gets the default color map for this type
- public static ImmutableDictionary DefaultColorMap { get; }
- = new DictionaryBuilder
+ public static ImmutableDictionary DefaultColorMap { get; }
+ = new DictionaryBuilder
{
- [MsgLevel.Verbose] = Color.LtBlue,
- [MsgLevel.Information] = Color.Default,
- [MsgLevel.Warning] = Color.LtYellow,
- [MsgLevel.Error] = Color.LtRed,
+ [MessageLevel.Verbose] = Color.LtBlue,
+ [MessageLevel.Information] = Color.Default,
+ [MessageLevel.Warning] = Color.LtYellow,
+ [MessageLevel.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 )
+ protected override void ReportMessage( MessageLevel level, string msg )
{
if(!ColorMap.TryGetValue(level, out AnsiCode? color))
{
diff --git a/src/Ubiquity.NET.CommandLine/ConsoleReporter.cs b/src/Ubiquity.NET.Extensions/ConsoleReporter.cs
similarity index 79%
rename from src/Ubiquity.NET.CommandLine/ConsoleReporter.cs
rename to src/Ubiquity.NET.Extensions/ConsoleReporter.cs
index bfea227..803e590 100644
--- a/src/Ubiquity.NET.CommandLine/ConsoleReporter.cs
+++ b/src/Ubiquity.NET.Extensions/ConsoleReporter.cs
@@ -1,28 +1,28 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// Implementation of that reports messages to a
///
- /// Messages with a of are reported to the console's
+ /// 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:
+ /// 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
+ /// The stream is used for . Any other message level
/// goes to .
///
///
[SetsRequiredMembers]
- public ConsoleReporter( MsgLevel level = MsgLevel.Information)
+ public ConsoleReporter( MessageLevel level = MessageLevel.Information)
: base(level, Console.Error, Console.Out, Console.Out, Console.Out)
{
}
diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs b/src/Ubiquity.NET.Extensions/DiagnosticMessage.cs
similarity index 85%
rename from src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs
rename to src/Ubiquity.NET.Extensions/DiagnosticMessage.cs
index da19cd1..4975511 100644
--- a/src/Ubiquity.NET.CommandLine/DiagnosticMessage.cs
+++ b/src/Ubiquity.NET.Extensions/DiagnosticMessage.cs
@@ -1,12 +1,10 @@
// 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.CommandLine
+namespace Ubiquity.NET.Extensions
{
/// Tool Message category
- public enum MsgLevel
+ public enum MessageLevel
{
/// All channels off
None = 0,
@@ -37,11 +35,8 @@ public enum MsgLevel
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 location for the origin of this message
+ public SourceLocation SourceLocation { get; init; }
#if NET10_0_OR_GREATER
/// Gets the subcategory of the message
@@ -60,15 +55,15 @@ public string? Subcategory
}
/// Gets the Level/Category of the message
- public MsgLevel Level
+ public MessageLevel Level
{
get;
init
{
value.ThrowIfNotDefined();
- if(value == MsgLevel.None)
+ if(value == MessageLevel.None)
{
- throw new InvalidEnumArgumentException( nameof( value ), 0, typeof( MsgLevel ) );
+ throw new InvalidEnumArgumentException( nameof( value ), 0, typeof( MessageLevel ) );
}
field = value;
@@ -117,15 +112,15 @@ public string? Subcategory
}
/// Gets the Level/Category of the message
- public MsgLevel Level
+ public MessageLevel Level
{
get => LevelBackingField;
init
{
value.ThrowIfNotDefined();
- if(value == MsgLevel.None)
+ if(value == MessageLevel.None)
{
- throw new InvalidEnumArgumentException( nameof( value ), 0, typeof( MsgLevel ) );
+ throw new InvalidEnumArgumentException( nameof( value ), 0, typeof( MessageLevel ) );
}
LevelBackingField = value;
@@ -153,15 +148,17 @@ public string Text
get => TextBackingField;
init
{
- ArgumentException.ThrowIfNullOrWhiteSpace( value );
+ Requires.NotNullOrWhiteSpace( value );
TextBackingField = value;
}
}
+#pragma warning disable IDE0032 // Use auto property
private readonly string? SubcategoryBackingField;
- private readonly MsgLevel LevelBackingField;
+ private readonly MessageLevel LevelBackingField;
private readonly string? CodeBackingField;
private readonly string TextBackingField;
+#pragma warning restore IDE0032 // Use auto property
#endif
/// Formats this instance using the general runtime specific format
@@ -194,21 +191,21 @@ public string ToString( string? format, IFormatProvider? formatProvider )
// Does not use a format provider as the format is specified by external sources
private string FormatMsBuild()
{
- if(Origin is null || string.IsNullOrWhiteSpace(Origin.AbsoluteUri))
+ if(SourceLocation.Source is null || string.IsNullOrWhiteSpace(SourceLocation.Source.AbsoluteUri))
{
return Text;
}
string locString = string.Empty;
- if(Location is not null)
+ if(SourceLocation != default)
{
- locString = Location.Value.ToString( "M", CultureInfo.InvariantCulture );
+ locString = SourceLocation.Range.ToString( "M", CultureInfo.InvariantCulture );
}
// account for optional values with leading space so that values not provided use no 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();
+ string origin = SourceLocation.Source.IsFile ? SourceLocation.Source.LocalPath : SourceLocation.Source.ToString();
return $"{origin}{locString} :{subCat} {Level}{code} : {Text}";
}
@@ -216,7 +213,12 @@ private string FormatMsBuild()
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1108:BlockStatementsMustNotContainEmbeddedComments", Justification = "Reviewed.")]
private string FormatRuntime(IFormatProvider _)
{
- if(OperatingSystem.IsWindows())
+#if NETSTANDARD2_0
+ bool isWindows = PolyFillOperatingSystem.IsWindows();
+#else
+ bool isWindows = OperatingSystem.IsWindows();
+#endif
+ if(isWindows)
{
return FormatMsBuild();
}
diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs b/src/Ubiquity.NET.Extensions/DiagnosticMessageCollection.cs
similarity index 86%
rename from src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs
rename to src/Ubiquity.NET.Extensions/DiagnosticMessageCollection.cs
index 2630483..cbebcd4 100644
--- a/src/Ubiquity.NET.CommandLine/DiagnosticMessageCollection.cs
+++ b/src/Ubiquity.NET.Extensions/DiagnosticMessageCollection.cs
@@ -1,7 +1,7 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// Reporter that collects diagnostic information without reporting it in any UI/UX
public class DiagnosticMessageCollection
@@ -9,14 +9,14 @@ public class DiagnosticMessageCollection
, 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 )
+ /// Minimal reporting level for this collector [Default is to collect all messaged
+ public DiagnosticMessageCollection( MessageLevel level = MessageLevel.Error )
{
Level = level;
}
///
- public MsgLevel Level { get; }
+ public MessageLevel Level { get; }
///
public Encoding Encoding => Encoding.Unicode;
diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs b/src/Ubiquity.NET.Extensions/DiagnosticReporterExtensions.cs
similarity index 87%
rename from src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs
rename to src/Ubiquity.NET.Extensions/DiagnosticReporterExtensions.cs
index c802998..c7dfa11 100644
--- a/src/Ubiquity.NET.CommandLine/DiagnosticReporterExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/DiagnosticReporterExtensions.cs
@@ -1,7 +1,9 @@
// 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
+using Ubiquity.NET.Extensions.InterpolatedStringHandlers;
+
+namespace Ubiquity.NET.Extensions
{
/// Extensions to for specific message levels
public static class DiagnosticReporterExtensions
@@ -10,7 +12,7 @@ public static class DiagnosticReporterExtensions
/// 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 )
+ public static bool IsEnabled( this IDiagnosticReporter self, MessageLevel level )
{
return self.Level <= level;
}
@@ -28,11 +30,11 @@ public static bool IsEnabled( this IDiagnosticReporter self, MsgLevel level )
/// code for the message
public static void Report(
this IDiagnosticReporter self,
- MsgLevel level,
+ MessageLevel level,
LocalizableString locStr,
IFormatProvider? formatProvider = default,
Uri? origin = default,
- SourceRange? location = default,
+ SourceRange location = default,
string? subCategory = default,
string? code = default
)
@@ -41,8 +43,7 @@ public static void Report(
{
var diagnostic = new DiagnosticMessage()
{
- Origin = origin,
- Location = location,
+ SourceLocation = new SourceLocation(origin, location),
Subcategory = subCategory,
Level = level,
Code = code,
@@ -57,14 +58,13 @@ public static void Report(
/// Reporter to use in reporting the message
/// Message level
/// Message text
- public static void Report( this IDiagnosticReporter self, MsgLevel level, string msg )
+ public static void Report( this IDiagnosticReporter self, MessageLevel level, string msg )
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
var diagnostic = new DiagnosticMessage()
{
- Origin = null,
- Location = default,
+ SourceLocation = default,
Subcategory = default,
Level = level,
Code = default,
@@ -82,26 +82,25 @@ public static void Report( this IDiagnosticReporter self, MsgLevel level, string
/// 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
+ /// . 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,
+ MessageLevel level,
Uri? origin,
SourceRange location,
[InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
- Origin = origin,
- Location = location,
+ SourceLocation = new SourceLocation(origin, location),
Subcategory = default,
Level = level,
Code = default,
@@ -119,13 +118,13 @@ public static void Report(
/// 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
+ /// . 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,
+ MessageLevel level,
SourceRange location,
[InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
)
@@ -139,24 +138,23 @@ public static void Report(
/// 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
+ /// . 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,
+ MessageLevel level,
[InterpolatedStringHandlerArgument( "self", "level" )] DiagnosticReporterInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
- Origin = null,
- Location = default,
+ SourceLocation = default,
Subcategory = default,
Level = level,
Code = default,
@@ -176,19 +174,18 @@ public static void Report(
/// Arguments for the message
public static void Report(
this IDiagnosticReporter self,
- MsgLevel level,
+ MessageLevel level,
Uri? origin,
SourceRange location,
[StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt,
params object[] args
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
var diagnostic = new DiagnosticMessage()
{
- Origin = origin,
- Location = location,
+ SourceLocation = new SourceLocation(origin, location),
Subcategory = default,
Level = level,
Code = default,
@@ -206,7 +203,7 @@ params object[] args
/// Arguments for the message
public static void Report(
this IDiagnosticReporter self,
- MsgLevel level,
+ MessageLevel level,
SourceRange location,
[StringSyntax( StringSyntaxAttribute.CompositeFormat )] string fmt,
params object[] args
@@ -244,7 +241,7 @@ public static void Report( this IDiagnosticReporter self, IEnumerableReports a level localizable message to
+ /// Reports a level localizable message to
/// Reporter to report message to
/// Localizable string form of the message
/// Format provider to use when formatting the string
@@ -262,16 +259,15 @@ public static void Error(
string? subCategory = default
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Error ))
+ if(self.IsEnabled( MessageLevel.Error ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Error,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Error,
Subcategory = subCategory,
Text = locStr.GetText(formatProvider),
};
@@ -283,7 +279,7 @@ public static void 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
+ /// Reports a level message to
/// Reporter to report message to
/// Identifier code for this message
/// Location in the origin that this message refers to
@@ -308,16 +304,15 @@ public static void Error(
params object[] args
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Error ))
+ if(self.IsEnabled( MessageLevel.Error ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Error,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Error,
Subcategory = subCategory,
Text = string.Format(formatProvider, fmt, args),
};
@@ -326,7 +321,7 @@ params object[] args
}
}
- /// Reports a a level message to
+ /// 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
@@ -347,16 +342,15 @@ public static void Error(
[InterpolatedStringHandlerArgument( "self" )] ErrorReportingInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Error,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Error,
Subcategory = subCategory,
Text = handler.GetFormattedText()
};
@@ -365,7 +359,7 @@ public static void Error(
}
}
- /// Reports a level message to
+ /// Reports a level message to
/// Reporter to report message to
/// Exception to report
/// Identifier code for this message
@@ -373,7 +367,7 @@ public static void Error(
/// 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.
+ /// The reporter () may filter out any messages reported for level.
///
public static void Error(
this IDiagnosticReporter self,
@@ -384,16 +378,15 @@ public static void Error(
string? subCategory = null
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Error ))
+ if(self.IsEnabled( MessageLevel.Error ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Error,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Error,
Subcategory = subCategory,
Text = ex.Message,
};
@@ -459,7 +452,7 @@ public static void Error(
#region MsgLevel.Warning
- /// Reports a level localizable message to
+ /// Reports a level localizable message to
/// Reporter to report message to
/// Localizable string form of the message
/// Format provider to use when formatting the string
@@ -477,16 +470,15 @@ public static void Warning(
string? subCategory = default
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Warning ))
+ if(self.IsEnabled( MessageLevel.Warning ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Warning,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Warning,
Subcategory = subCategory,
Text = locStr.GetText(formatProvider),
};
@@ -495,7 +487,7 @@ public static void Warning(
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Warning(
this IDiagnosticReporter self,
@@ -508,16 +500,15 @@ public static void Warning(
params object[] args
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Warning ))
+ if(self.IsEnabled( MessageLevel.Warning ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Warning,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Warning,
Subcategory = subCategory,
Text = string.Format(formatProvider, fmt, args),
};
@@ -526,7 +517,7 @@ params object[] args
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Warning(
this IDiagnosticReporter self,
@@ -537,16 +528,15 @@ public static void Warning(
[InterpolatedStringHandlerArgument( "self" )] WarningReportingInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Warning,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Warning,
Subcategory = subCategory,
Text = handler.GetFormattedText()
};
@@ -612,7 +602,7 @@ public static void Warning(
#region MsgLevel.Information
- /// Reports a level localizable message to
+ /// Reports a level localizable message to
/// Reporter to report message to
/// Localizable string form of the message
/// Format provider to use when formatting the string
@@ -630,16 +620,15 @@ public static void Information(
string? subCategory = default
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Information ))
+ if(self.IsEnabled( MessageLevel.Information ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Information,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Information,
Subcategory = subCategory,
Text = locStr.GetText(formatProvider),
};
@@ -648,7 +637,7 @@ public static void Information(
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Information(
this IDiagnosticReporter self,
@@ -661,16 +650,15 @@ public static void Information(
params object[] args
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Information ))
+ if(self.IsEnabled( MessageLevel.Information ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Information,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Information,
Subcategory = subCategory,
Text = string.Format(formatProvider, fmt, args),
};
@@ -679,7 +667,7 @@ params object[] args
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Information(
this IDiagnosticReporter self,
@@ -690,16 +678,15 @@ public static void Information(
[InterpolatedStringHandlerArgument( "self" )] InformationReportingInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Information,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Information,
Subcategory = subCategory,
Text = handler.GetFormattedText()
};
@@ -766,7 +753,7 @@ public static void Information(
#region MsgLevel.Verbose
- /// Reports a level localizable message to
+ /// Reports a level localizable message to
/// Reporter to report message to
/// Localizable string form of the message
/// Format provider to use when formatting the string
@@ -784,16 +771,15 @@ public static void Verbose(
string? subCategory = default
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Verbose ))
+ if(self.IsEnabled( MessageLevel.Verbose ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Verbose,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Verbose,
Subcategory = subCategory,
Text = locStr.GetText(formatProvider),
};
@@ -802,7 +788,7 @@ public static void Verbose(
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Verbose(
this IDiagnosticReporter self,
@@ -815,16 +801,15 @@ public static void Verbose(
params object[] args
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
- if(self.IsEnabled( MsgLevel.Verbose ))
+ if(self.IsEnabled( MessageLevel.Verbose ))
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Verbose,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Verbose,
Subcategory = subCategory,
Text = string.Format(formatProvider, fmt, args),
};
@@ -833,7 +818,7 @@ params object[] args
}
}
- /// Reports a level message to
+ /// Reports a level message to
///
public static void Verbose(
this IDiagnosticReporter self,
@@ -844,16 +829,15 @@ public static void Verbose(
[InterpolatedStringHandlerArgument( "self" )] VerboseReportingInterpolatedStringHandler handler
)
{
- ArgumentNullException.ThrowIfNull( self );
+ Requires.NotNull( self );
if(handler.IsEnabled)
{
var diagnostic = new DiagnosticMessage()
{
+ SourceLocation = new SourceLocation(origin, location),
Code = code,
- Level = MsgLevel.Verbose,
- Location = location,
- Origin = origin,
+ Level = MessageLevel.Verbose,
Subcategory = subCategory,
Text = handler.GetFormattedText()
};
diff --git a/src/Ubiquity.NET.CommandLine/DiagnosticReportingWriter.cs b/src/Ubiquity.NET.Extensions/DiagnosticReportingWriter.cs
similarity index 89%
rename from src/Ubiquity.NET.CommandLine/DiagnosticReportingWriter.cs
rename to src/Ubiquity.NET.Extensions/DiagnosticReportingWriter.cs
index 930c375..de6114c 100644
--- a/src/Ubiquity.NET.CommandLine/DiagnosticReportingWriter.cs
+++ b/src/Ubiquity.NET.Extensions/DiagnosticReportingWriter.cs
@@ -1,23 +1,23 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// implementation that wraps an
///
/// This is an instance of the adapter pattern to provide a text writer interface that wraps an .
/// Newlines are detected and transformed into a diagnostic reported to the reporter. This will write a diagnostic
- /// when is detected. When a diagnostic is reported then the internal builder is cleared so that
- /// the next line is a new diagnostic. is used to specify the message level to report for all
- /// diagnostics in this writer.
+ /// when is detected. When a diagnostic is reported then the internal builder is
+ /// cleared so that the next line is a new diagnostic. is used to specify the message level
+ /// to report for all diagnostics in this writer.
///
- internal class DiagnosticReportingWriter
+ public class DiagnosticReportingWriter
: StringWriter
{
/// Initializes a new instance of the class.
/// Reporter to create reports with
/// Level of diagnostics to report for this instance
- public DiagnosticReportingWriter( IDiagnosticReporter reporter, MsgLevel msgLevel )
+ public DiagnosticReportingWriter( IDiagnosticReporter reporter, MessageLevel msgLevel )
{
Reporter = reporter;
MsgLevel = msgLevel;
@@ -76,6 +76,8 @@ public override Task FlushAsync( )
);
}
+ // overload supporting cancellation introduced in .NET 8
+#if NET8_0_OR_GREATER
///
public override Task FlushAsync( CancellationToken cancellationToken )
{
@@ -83,9 +85,10 @@ public override Task FlushAsync( CancellationToken cancellationToken )
? Task.FromCanceled( cancellationToken )
: FlushAsync();
}
+#endif
/// Gets the message level reported by this instance
- public MsgLevel MsgLevel { get; }
+ public MessageLevel MsgLevel { get; }
/// Gets the reporter this instance reports diagnostics to
public IDiagnosticReporter Reporter { get; }
diff --git a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs b/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs
deleted file mode 100644
index 902f7f0..0000000
--- a/src/Ubiquity.NET.Extensions/FluentValidation/ExceptionValidationExtensions.cs
+++ /dev/null
@@ -1,157 +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
-{
- // TODO: [API BREAKING CHANGE] move namespace to the "extension" pattern type name
- // so that use of the full type name can disambiguate with any existing
- // method of the same name exists (or an extension). [i.e., An extension type
- // with a static method that has the same arity and type of args but different
- // return type is ambiguous.]
- /*
- internal abstract class SampleBase
- {
- public SampleBase(string foo)
- {
- Foo = foo;
- }
-
- private readonly string Foo;
- }
-
- internal class Sample
- : SampleBase
- {
- public Sample(string foo)
- : base( FluentValidation.ThrowIfNull( foo ) )
- {
- }
- }
- */
-
- // 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 suppressed (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 because 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 careful 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 explicit 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
- {
-#if NET8_0_OR_GREATER
- ArgumentOutOfRangeException.ThrowIfLessThan(self, min, exp);
- ArgumentOutOfRangeException.ThrowIfGreaterThan(self, max, exp);
-#else
- PolyFillExceptionValidators.ThrowIfLessThan( self, min, exp );
- PolyFillExceptionValidators.ThrowIfGreaterThan( self, max, exp );
-#endif
- 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;
- try
- {
-#if NET5_0_OR_GREATER
- if(Enum.IsDefined( self ))
- {
- return self;
- }
-#else
- if(Enum.IsDefined( typeof( T ), self ))
- {
- return self;
- }
-#endif
- 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)
- {
- // bit cast the enum to an nuint as that is a platform specific maximal value
-#if NET8_0_OR_GREATER
- nuint integral = Unsafe.BitCast(self);
-#else
- ref byte refSelf = ref Unsafe.As(ref self);
- nuint integral = Unsafe.ReadUnaligned(ref refSelf);
-#endif
-
- // InvalidEnumArgumentException constructors ONLY provide parameter 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(CultureInfo.CurrentCulture, nameof( Resources.InvalidEnumArgument_NonInt ), exp, integral, 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 1f6ae76..0000000
--- 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
-disambiguation when using down-level 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
-down-level 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/FluentValidationExtensions.cs b/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs
new file mode 100644
index 0000000..43bb0a2
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/FluentValidationExtensions.cs
@@ -0,0 +1,76 @@
+// 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
+{
+ // 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 suppressed (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 or those provided by
+ /// except that these 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...
+ ///
+ public static class FluentValidationExtensions
+ {
+ /// 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 )
+ {
+ Requires.NotNull(self, exp);
+ return 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
+ {
+ Requires.GreaterThanOrEqualTo( self, min, exp );
+ Requires.LessThanOrEqualTo( 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
+ {
+ Requires.Defined(self, exp);
+ return self;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs b/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
index ae0d64b..1676aca 100644
--- a/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
+++ b/src/Ubiquity.NET.Extensions/GlobalNamespaceImports.cs
@@ -26,9 +26,18 @@ set of namespaces that is NOT consistent or controlled by the developer. THAT is
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.Text.RegularExpressions;
global using System.Threading;
+global using System.Threading.Tasks;
+
+global using AnsiCodes;
global using Ubiquity.NET.Extensions.Properties;
+
+#if NETSTANDARD2_0
+global using Ubiquity.NET.PolyFill;
+#endif
diff --git a/src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs b/src/Ubiquity.NET.Extensions/IDiagnosticReporter.cs
similarity index 94%
rename from src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs
rename to src/Ubiquity.NET.Extensions/IDiagnosticReporter.cs
index c73dc56..baa90d8 100644
--- a/src/Ubiquity.NET.CommandLine/IDiagnosticReporter.cs
+++ b/src/Ubiquity.NET.Extensions/IDiagnosticReporter.cs
@@ -1,7 +1,7 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// Interface for UX message reporting
///
@@ -14,7 +14,7 @@ namespace Ubiquity.NET.CommandLine
public interface IDiagnosticReporter
{
/// Gets the current reporting level for this reporter
- MsgLevel Level { get; }
+ MessageLevel Level { get; }
/// Gets the encoding used for this reporter
Encoding Encoding { get; }
diff --git a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs
similarity index 96%
rename from src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs
rename to src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs
index 1789550..fc90253 100644
--- a/src/Ubiquity.NET.CommandLine/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs
+++ b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/DiagnosticReporterInterpolatedStringHandler.cs
@@ -1,7 +1,7 @@
// 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
+namespace Ubiquity.NET.Extensions.InterpolatedStringHandlers
{
/// Interpolated string handler for an
///
@@ -30,16 +30,16 @@ public readonly struct DiagnosticReporterInterpolatedStringHandler
/// 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
+ /// Format provider to use when formatting contents
///
/// 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.
+ /// if the reporter has the level enabled. Thus, it may produce NO message at all if the level is not enabled.
///
public DiagnosticReporterInterpolatedStringHandler(
int literalLength,
int formattedCount,
IDiagnosticReporter reporter,
- MsgLevel level,
+ MessageLevel level,
IFormatProvider? formatProvider = null
)
{
diff --git a/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs
new file mode 100644
index 0000000..bf3c09a
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/ErrorReportingInterpolatedStringHandler.cs
@@ -0,0 +1,57 @@
+// 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.InterpolatedStringHandlers
+{
+ // Unfortunately, in C#, structs can't inherit AND generics don't support a non-type constant argument (for the fixed level)
+ // Therefore this must use classic OO containment instead.
+
+ /// Interpolated string handler for an using a fixed
+ ///
+ [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.
+ ///
+ public ErrorReportingInterpolatedStringHandler(
+ int literalLength,
+ int formattedCount,
+ IDiagnosticReporter reporter,
+ IFormatProvider? formatProvider = null
+ )
+ {
+ InnerHandler = new DiagnosticReporterInterpolatedStringHandler(literalLength, formattedCount, reporter, MessageLevel.Error, formatProvider);
+ }
+
+ ///
+ public bool IsEnabled => InnerHandler.IsEnabled;
+
+ ///
+ public bool AppendLiteral( string s )
+ {
+ return InnerHandler.AppendLiteral( s );
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t )
+ {
+ return InnerHandler.AppendFormatted(t);
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t, string format )
+ where T : IFormattable
+ {
+ return InnerHandler.AppendFormatted(t, format);
+ }
+
+ ///
+ public string GetFormattedText( )
+ {
+ return InnerHandler.GetFormattedText( );
+ }
+
+ private readonly DiagnosticReporterInterpolatedStringHandler InnerHandler;
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs
new file mode 100644
index 0000000..91f292d
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/InformationReportingInterpolatedStringHandler.cs
@@ -0,0 +1,57 @@
+// 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.InterpolatedStringHandlers
+{
+ // Unfortunately, in C#, structs can't inherit AND generics don't support a non-type constant argument (for the fixed level)
+ // Therefore this must use classic OO containment instead.
+
+ /// Interpolated string handler for an using a fixed
+ ///
+ [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.
+ ///
+ public InformationReportingInterpolatedStringHandler(
+ int literalLength,
+ int formattedCount,
+ IDiagnosticReporter reporter,
+ IFormatProvider? formatProvider = null
+ )
+ {
+ InnerHandler = new DiagnosticReporterInterpolatedStringHandler(literalLength, formattedCount, reporter, MessageLevel.Information, formatProvider);
+ }
+
+ ///
+ public bool IsEnabled => InnerHandler.IsEnabled;
+
+ ///
+ public bool AppendLiteral( string s )
+ {
+ return InnerHandler.AppendLiteral( s );
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t )
+ {
+ return InnerHandler.AppendFormatted(t);
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t, string format )
+ where T : IFormattable
+ {
+ return InnerHandler.AppendFormatted(t, format);
+ }
+
+ ///
+ public string GetFormattedText( )
+ {
+ return InnerHandler.GetFormattedText( );
+ }
+
+ private readonly DiagnosticReporterInterpolatedStringHandler InnerHandler;
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs
new file mode 100644
index 0000000..6fd447a
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/VerboseReportingInterpolatedStringHandler.cs
@@ -0,0 +1,57 @@
+// 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.InterpolatedStringHandlers
+{
+ // Unfortunately, in C#, structs can't inherit AND generics don't support a non-type constant argument (for the fixed level)
+ // Therefore this must use classic OO containment instead.
+
+ /// Interpolated string handler for an using a fixed
+ ///
+ [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.
+ ///
+ public VerboseReportingInterpolatedStringHandler(
+ int literalLength,
+ int formattedCount,
+ IDiagnosticReporter reporter,
+ IFormatProvider? formatProvider = null
+ )
+ {
+ InnerHandler = new DiagnosticReporterInterpolatedStringHandler(literalLength, formattedCount, reporter, MessageLevel.Verbose, formatProvider);
+ }
+
+ ///
+ public bool IsEnabled => InnerHandler.IsEnabled;
+
+ ///
+ public bool AppendLiteral( string s )
+ {
+ return InnerHandler.AppendLiteral( s );
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t )
+ {
+ return InnerHandler.AppendFormatted(t);
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t, string format )
+ where T : IFormattable
+ {
+ return InnerHandler.AppendFormatted(t, format);
+ }
+
+ ///
+ public string GetFormattedText( )
+ {
+ return InnerHandler.GetFormattedText( );
+ }
+
+ private readonly DiagnosticReporterInterpolatedStringHandler InnerHandler;
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs
new file mode 100644
index 0000000..5bec4ce
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/InterpolatedStringHandlers/WarningReportingInterpolatedStringHandler.cs
@@ -0,0 +1,57 @@
+// 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.InterpolatedStringHandlers
+{
+ // Unfortunately, in C#, structs can't inherit AND generics don't support a non-type constant argument (for the fixed level)
+ // Therefore this must use classic OO containment instead.
+
+ /// Interpolated string handler for an using a fixed
+ ///
+ [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.
+ ///
+ public WarningReportingInterpolatedStringHandler(
+ int literalLength,
+ int formattedCount,
+ IDiagnosticReporter reporter,
+ IFormatProvider? formatProvider = null
+ )
+ {
+ InnerHandler = new DiagnosticReporterInterpolatedStringHandler(literalLength, formattedCount, reporter, MessageLevel.Warning, formatProvider);
+ }
+
+ ///
+ public bool IsEnabled => InnerHandler.IsEnabled;
+
+ ///
+ public bool AppendLiteral( string s )
+ {
+ return InnerHandler.AppendLiteral( s );
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t )
+ {
+ return InnerHandler.AppendFormatted(t);
+ }
+
+ ///
+ public readonly bool AppendFormatted( T t, string format )
+ where T : IFormattable
+ {
+ return InnerHandler.AppendFormatted(t, format);
+ }
+
+ ///
+ public string GetFormattedText( )
+ {
+ return InnerHandler.GetFormattedText( );
+ }
+
+ private readonly DiagnosticReporterInterpolatedStringHandler InnerHandler;
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/LocalizableResourceString.cs b/src/Ubiquity.NET.Extensions/LocalizableResourceString.cs
index 73e5f24..9dc18a0 100644
--- a/src/Ubiquity.NET.Extensions/LocalizableResourceString.cs
+++ b/src/Ubiquity.NET.Extensions/LocalizableResourceString.cs
@@ -37,15 +37,10 @@ public LocalizableResourceString(
params object?[] formatArguments
)
{
-#if NETCOREAPP2_1_OR_GREATER
- ArgumentNullException.ThrowIfNull( nameOfLocalizableResource );
- ArgumentNullException.ThrowIfNull( resourceManager );
- ArgumentNullException.ThrowIfNull( formatArguments );
-#else
- PolyFillExceptionValidators.ThrowIfNull( nameOfLocalizableResource );
- PolyFillExceptionValidators.ThrowIfNull( resourceManager );
- PolyFillExceptionValidators.ThrowIfNull( formatArguments );
-#endif
+ Requires.NotNull( nameOfLocalizableResource );
+ Requires.NotNull( resourceManager );
+ Requires.NotNull( formatArguments );
+
ResourceManager = resourceManager;
NameOfLocalizableResource = nameOfLocalizableResource;
FormatArguments = formatArguments;
diff --git a/src/Ubiquity.NET.Extensions/PolyFill/PolyFillEncodingExtensions.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillEncodingExtensions.cs
new file mode 100644
index 0000000..305e063
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillEncodingExtensions.cs
@@ -0,0 +1,44 @@
+// 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 NETSTANDARD2_0
+#pragma warning disable IDE0130 // Namespace does not match folder structure
+namespace System.Text
+{
+ /// Utility class to host extensions for poly filling the existing class
+ public static class PolyFillEncodingExtensions
+ {
+ /// Decodes all the bytes in the specified byte span into a string.
+ /// Encoding to extend
+ /// A read-only byte span to decode to a Unicode string
+ /// A string that contains the decoded bytes from the provided read-only span.
+ ///
+ public static unsafe string GetString(this Encoding self, ReadOnlySpan bytes)
+ {
+ fixed (byte* bytesPtr = bytes)
+ {
+ return self.GetString(bytesPtr, bytes.Length);
+ }
+ }
+
+ /// 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" )]
+ public static unsafe int GetBytes( this Encoding self, ReadOnlySpan chars, Span bytes)
+ {
+ unsafe
+ {
+ fixed(char* pSrc = chars)
+ {
+ fixed(byte* pDst = bytes)
+ {
+ return self.GetBytes( pSrc, bytes.Length, pDst, bytes.Length );
+ }
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/src/Ubiquity.NET.Extensions/PolyFill/PolyFillExceptionValidators.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillExceptionValidators.cs
new file mode 100644
index 0000000..61ba455
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillExceptionValidators.cs
@@ -0,0 +1,280 @@
+// 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. While the C#14 extension keyword
+// can provide support for static methods, it requires BOTH source and consumer
+// to compile with C# 14 to work.
+
+#if NETSTANDARD2_0
+#pragma warning disable IDE0130 // Namespace does not match folder structure
+namespace Ubiquity.NET.PolyFill
+{
+ /// poly fill extensions for static methods added in .NET 7
+ public static class PolyFillExceptionValidators
+ {
+ /// Throw an if a string is m empty, or all whitespace.
+ /// input string to test
+ /// expression or name of the string to test; normally provided by compiler
+ /// string is m empty, or all whitespace
+ public static void ThrowIfNullOrWhiteSpace(
+ [NotNullAttribute] string? argument,
+ [CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = null
+ )
+ {
+ ThrowIfNull( argument, paramName);
+
+ // argument is non-null verified by this, sadly older frameworks don't have
+ // attributes to declare that.
+ if(string.IsNullOrWhiteSpace( argument ))
+ {
+ var msg = SR.Build( nameof(Resources.Argument_EmptyOrWhiteSpaceString) );
+ throw new ArgumentException( msg.ToString(), paramName );
+ }
+ }
+
+ /// Throws an exception if the tested argument is
+ /// value to test
+ /// expression for the name of the value; normally provided by compiler
+ /// is
+ public static void ThrowIfNull(
+ [NotNullAttribute] object? argument,
+ [CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = default
+ )
+ {
+ if(argument is null)
+ {
+ throw new ArgumentNullException( paramName );
+ }
+ }
+
+ /// 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(
+ [DoesNotReturnIfAttribute( true )] bool condition,
+ object instance
+ )
+ {
+ if(condition)
+ {
+ throw new ObjectDisposedException( instance?.GetType().FullName );
+ }
+ }
+
+ /// Throws an if is equal to .
+ /// Type of values to compare
+ /// 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, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IEquatable?
+ {
+ if(EqualityComparer.Default.Equals( value, other ))
+ {
+ ThrowEqual( value, other, paramName );
+ }
+ }
+
+ /// Throws an if is not equal to .
+ /// Type of values to compare
+ /// 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, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IEquatable?
+ {
+ if(!EqualityComparer.Default.Equals( value, other ))
+ {
+ ThrowNotEqual( value, other, paramName );
+ }
+ }
+
+ /// Throws an if is greater than .
+ /// Type of values to compare
+ /// 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, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IComparable
+ {
+ if(value.CompareTo( other ) > 0)
+ {
+ ThrowGreater( value, other, paramName );
+ }
+ }
+
+ /// Throws an if is greater than or equal .
+ /// Type of values to compare
+ /// 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, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IComparable
+ {
+ if(value.CompareTo( other ) >= 0)
+ {
+ ThrowGreaterEqual( value, other, paramName );
+ }
+ }
+
+ /// Throws an if is less than .
+ /// Type of values to compare
+ /// The argument to validate as greater 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, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IComparable
+ {
+ if(value.CompareTo( other ) < 0)
+ {
+ ThrowLess( value, other, paramName );
+ }
+ }
+
+ /// Throws an if is less than or equal .
+ /// Type of values to compare
+ /// The argument to validate as greater than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void ThrowIfLessThanOrEqual( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IComparable
+ {
+ if(value.CompareTo( other ) <= 0)
+ {
+ ThrowLessEqual( value, other, paramName );
+ }
+ }
+
+ [DoesNotReturn]
+ private static void ThrowZero( T value, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeNonNegative),
+ paramName,
+ value
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ private static void ThrowNegative( T value, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeNonZero),
+ paramName,
+ value
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ private static void ThrowNegativeOrZero( T value, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero),
+ paramName,
+ value
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowGreater( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeLessOrEqual),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg);
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowGreaterEqual( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeLess),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowLess( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowLessEqual( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeGreater),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowEqual( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeNotEqual),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg);
+ }
+
+ [DoesNotReturn]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ private static void ThrowNotEqual( T value, T other, string? paramName )
+ {
+ string msg = SR.Format(
+ CultureInfo.CurrentCulture,
+ nameof(Resources.ArgumentOutOfRange_Generic_MustBeEqual),
+ paramName,
+ value,
+ other
+ );
+
+ throw new ArgumentOutOfRangeException( paramName, value, msg );
+ }
+ }
+}
+#endif
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillMemoryMarshal.cs
similarity index 75%
rename from src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs
rename to src/Ubiquity.NET.Extensions/PolyFill/PolyFillMemoryMarshal.cs
index 843fe1e..60da6e0 100644
--- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillMemoryMarshal.cs
+++ b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillMemoryMarshal.cs
@@ -1,34 +1,30 @@
-//
-#nullable enable
-
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// 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
-
+#if !NET6_0_OR_GREATER
namespace System.Runtime.InteropServices
{
/// Extensions for to allow down-level compatibility
- internal static class PolyFillMemoryMarshal
+ public static class PolyFillMemoryMarshal
{
-#if !NET6_0_OR_GREATER
/// 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)
+ [CLSCompliant(false)]
+ public static unsafe ReadOnlySpan CreateReadOnlySpanFromNullTerminated(byte* value)
{
return value != null
- ? new global::System.ReadOnlySpan(value, StrLen(value))
+ ? new ReadOnlySpan(value, StrLen(value))
: default;
}
+ // Crude but functional - definitely NOT perf optimized.
private static unsafe int StrLen(byte* p)
{
- // Crude but functional - definitely NOT perf optimized.
int indexOfTerminator = 0;
for(; *p != 0; ++p, ++indexOfTerminator)
{
@@ -36,6 +32,6 @@ private static unsafe int StrLen(byte* p)
return indexOfTerminator;
}
-#endif
}
}
+#endif
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillOperatingSystem.cs
similarity index 64%
rename from src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs
rename to src/Ubiquity.NET.Extensions/PolyFill/PolyFillOperatingSystem.cs
index 1343108..fc93a52 100644
--- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillOperatingSystem.cs
+++ b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillOperatingSystem.cs
@@ -1,25 +1,25 @@
-//
-#nullable enable
-
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// 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 !NET5_0_OR_GREATER
#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
+ ///
+ /// This cannot use the C# 14 extension keyword to add a static method to the existing type. As it only applies IF
+ /// that's not available in the first place.
+ ///
+ [SuppressMessage("Design", "CA1034: Nested types should not be visible", Justification = "extension, broken analyzer")]
+ public static class PolyFillOperatingSystem
{
-#if !NET5_0_OR_GREATER
/// Indicates whether the current application is running on Windows.
/// if the current application is running on Windows; otherwise.
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
public static bool IsWindows()
{
- return global::System.Environment.OSVersion.Platform switch
+ return Environment.OSVersion.Platform switch
{
PlatformID.Win32S or
PlatformID.Win32Windows or
@@ -32,6 +32,6 @@ PlatformID.Win32NT or
// 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
-#endif
}
}
+#endif
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillRuntimeHelpersExtensions.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillRuntimeHelpersExtensions.cs
similarity index 100%
rename from src/Ubiquity.NET.PollyFill.SharedSources/PolyFillRuntimeHelpersExtensions.cs
rename to src/Ubiquity.NET.Extensions/PolyFill/PolyFillRuntimeHelpersExtensions.cs
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillStringExtensions.cs
similarity index 70%
rename from src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs
rename to src/Ubiquity.NET.Extensions/PolyFill/PolyFillStringExtensions.cs
index 5ffb0b8..08e7a6c 100644
--- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillStringExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/PolyFill/PolyFillStringExtensions.cs
@@ -1,7 +1,4 @@
-//
-#nullable enable
-
-// Copyright (c) Ubiquity.NET Contributors. All rights reserved.
+// 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
@@ -9,14 +6,16 @@
#pragma warning disable IDE0130 // Namespace does not match folder structure
-using global::System.Diagnostics;
-using global::System.Collections.Generic;
+#pragma warning disable IDE0005 // Using directive is unnecessary; (No it isn't!)
+// Without this the compiler will try to resolve to an internal type and report in-accessibility.
+using Ubiquity.NET.Extensions;
+#pragma warning restore IDE0005
namespace System
{
/// Polyfill extensions for support not present in older runtimes
- ///
- internal static class PolyFillStringExtensions
+ ///
+ public static class PolyFillStringExtensions
{
#if NETSTANDARD2_0
/// Concatenates the members of a collection, using the specified separator between each member.
@@ -34,14 +33,16 @@ public static string Join( char separator, IEnumerable values )
return string.Join( separator.ToString(), values );
}
+ /// Gets the hash-code for a string using the specified comparison type
+ /// String to get the code for
+ /// Comparison type to use [Only supports the default ]
+ /// Hash code value
+ /// is not the default
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();
+ return comparisonType == StringComparison.Ordinal
+ ? self.GetHashCode()
+ : throw new InvalidEnumArgumentException( nameof( comparisonType ), (int)comparisonType, typeof( StringComparison ) );
}
#endif
@@ -55,10 +56,10 @@ public static int GetHashCode( this string self, StringComparison comparisonType
/// current runtime it is usually not what is desired. In such a case the more explicit
/// is used to specify the precise line ending form to use. (Or Better yet, use Ubiquity.NET.Extensions.StringNormalizer.NormalizeLineEndings(string?, LineEndingKind)) />
///
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "This form explicitly uses the runtime form" )]
+ [SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "This form explicitly uses the runtime form" )]
public static string ReplaceLineEndings( this string self )
{
- return ReplaceLineEndings( self, global::System.Environment.NewLine );
+ return ReplaceLineEndings( self, Environment.NewLine );
}
// This is NOT the most performant implementation, it's going for simplistic polyfill that has
@@ -69,11 +70,11 @@ public static string ReplaceLineEndings( this string self )
/// 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 )]
+ [MethodImpl( MethodImplOptions.AggressiveInlining )]
public static string ReplaceLineEndings( this string self, string replacementText )
{
- global::System.PolyFillExceptionValidators.ThrowIfNull( self );
- global::System.PolyFillExceptionValidators.ThrowIfNull( replacementText );
+ Requires.NotNull( self );
+ Requires.NotNull( replacementText );
string retVal = UnicodeNewLinesRegex.Replace(self, replacementText);
@@ -97,23 +98,23 @@ public static string ReplaceLineEndings( this string self, string replacementTex
// 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.
// Thus these use a lazy pattern to take the cost only once.
- internal static global::System.Text.RegularExpressions.Regex UnicodeNewLinesRegex
+ internal static Regex UnicodeNewLinesRegex
{
- [global::System.Runtime.CompilerServices.MethodImpl( global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining )]
+ [MethodImpl( MethodImplOptions.AggressiveInlining )]
get => LazyUnicodeNewLinesRegex.Value;
}
- internal static global::System.Text.RegularExpressions.Regex SystemNewLinesRegex
+ internal static Regex SystemNewLinesRegex
{
- [global::System.Runtime.CompilerServices.MethodImpl( global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining )]
+ [MethodImpl( MethodImplOptions.AggressiveInlining )]
get => LazySystemNewLinesRegex.Value;
}
- private static global::System.Lazy LazyUnicodeNewLinesRegex
- = new(() => new global::System.Text.RegularExpressions.Regex( UnicodeNewLinesRegExPattern ));
+ private static readonly Lazy LazyUnicodeNewLinesRegex
+ = new(() => new Regex( UnicodeNewLinesRegExPattern ));
- internal static global::System.Lazy LazySystemNewLinesRegex
- = new(() => new global::System.Text.RegularExpressions.Regex( SystemNewLinesRegExPattern ));
+ internal static readonly Lazy LazySystemNewLinesRegex
+ = new(() => new Regex( SystemNewLinesRegExPattern ));
#endif
}
}
diff --git a/src/Ubiquity.NET.Extensions/Properties/SR.cs b/src/Ubiquity.NET.Extensions/Properties/SR.cs
index 9c89d2b..0289367 100644
--- a/src/Ubiquity.NET.Extensions/Properties/SR.cs
+++ b/src/Ubiquity.NET.Extensions/Properties/SR.cs
@@ -11,14 +11,7 @@ internal static class SR
{
internal static LocalizableResourceString Build( [NotNull] string resourceName, params object?[] args )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull(resourceName);
-#else
- if( resourceName is null )
- {
- throw new ArgumentNullException(nameof(resourceName));
- }
-#endif
+ Requires.NotNull(resourceName);
return new LocalizableResourceString( resourceName, Resources.ResourceManager, args );
}
diff --git a/src/Ubiquity.NET.Extensions/Requires.cs b/src/Ubiquity.NET.Extensions/Requires.cs
new file mode 100644
index 0000000..bf09f10
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/Requires.cs
@@ -0,0 +1,212 @@
+// 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 static methods added in .NET 7
+ ///
+ /// This makes the intent a little clearer via the naming AND makes usage consistent
+ /// across multiple runtime versions.
+ ///
+ public static class Requires
+ {
+ /// Throw an if a string is m empty, or all whitespace.
+ /// input string to test
+ /// expression or name of the string to test; normally provided by compiler
+ /// string is m empty, or all whitespace
+ public static void NotNullOrWhiteSpace(
+ [NotNull] string? argument,
+ [CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = null
+ )
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( argument, paramName );
+#else
+ ArgumentException.ThrowIfNullOrWhiteSpace( argument, paramName );
+#endif
+ }
+
+ /// Throws an exception if the tested argument is
+ /// value to test
+ /// expression for the name of the value; normally provided by compiler
+ /// is
+ public static void NotNull(
+ [NotNull] object? argument,
+ [CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = default
+ )
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfNull( argument, paramName );
+#else
+ ArgumentNullException.ThrowIfNull(argument, paramName);
+#endif
+ }
+
+ /// Throws an if is .
+ /// Condition to determine if the instance is disposed
+ /// instance that is tested; Used to get type name for exception
+ /// is
+ ///
+ /// This will throw an if is true.
+ /// That is, this assumes that the expression for is semantically some form of
+ /// "IsDisposed" check. This is the same as the semantics for ObjectDisposedException.ThrowIf if the
+ /// runtime supports that. The result is semantically the same. An exception is thrown if the expression indicates
+ /// that is disposed.
+ ///
+ public static void NotDisposed(
+ [DoesNotReturnIfAttribute( true )] bool isDisposed,
+ object instance
+ )
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIf(isDisposed, instance);
+#else
+ ObjectDisposedException.ThrowIf(isDisposed, instance);
+#endif
+ }
+
+ /// Throws an if is equal to .
+ /// Type of values to compare
+ /// The argument to validate as not equal to .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void Equal( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : IEquatable?
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfNotEqual(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfNotEqual(value, other, paramName);
+#endif
+ }
+
+ /// Throws an if is not equal to .
+ /// Type of values to compare
+ /// The argument to validate as equal to .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void NotEqual( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : global::System.IEquatable?
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfEqual(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfEqual( value, other, paramName );
+#endif
+ }
+
+ /// Throws an if is greater than .
+ /// Type of values to compare
+ /// 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 LessThanOrEqualTo( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : global::System.IComparable
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfGreaterThan(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfGreaterThan( value, other, paramName );
+#endif
+ }
+
+ /// Throws an if is greater than or equal .
+ /// Type of values to compare
+ /// The argument to validate as less than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void LessThan( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : global::System.IComparable
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfGreaterThanOrEqual(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual( value, other, paramName );
+#endif
+ }
+
+ /// Throws an if is less than .
+ /// Type of values to compare
+ /// The argument to validate as greater than or equal than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void GreaterThanOrEqualTo( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : global::System.IComparable
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfLessThan(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfLessThan( value, other, paramName );
+#endif
+ }
+
+ /// Throws an if is less than or equal .
+ /// Type of values to compare
+ /// The argument to validate as greater than .
+ /// The value to compare with .
+ /// The name of the parameter with which corresponds.
+ public static void GreaterThan( T value, T other, [CallerArgumentExpressionAttribute( nameof( value ) )] string? paramName = null )
+ where T : global::System.IComparable
+ {
+#if NETSTANDARD2_0
+ PolyFillExceptionValidators.ThrowIfLessThanOrEqual(value, other, paramName);
+#else
+ ArgumentOutOfRangeException.ThrowIfLessThanOrEqual( value, other, paramName );
+#endif
+ }
+
+ /// 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 void Defined( this T self, [CallerArgumentExpression( nameof( self ) )] string? exp = null )
+ where T : struct, Enum
+ {
+ exp ??= string.Empty;
+ try
+ {
+#if NET5_0_OR_GREATER
+ if(Enum.IsDefined( self ))
+ {
+ return;
+ }
+#else
+ if(Enum.IsDefined( typeof( T ), self ))
+ {
+ return;
+ }
+#endif
+ 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)
+ {
+ // bit cast the enum to an nuint as that is a platform specific maximal value
+#if NET8_0_OR_GREATER
+ nuint integral = Unsafe.BitCast(self);
+#else
+ ref byte refSelf = ref Unsafe.As(ref self);
+ nuint integral = Unsafe.ReadUnaligned(ref refSelf);
+#endif
+
+ // InvalidEnumArgumentException constructors ONLY provide parameter 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( CultureInfo.CurrentCulture, nameof( Resources.InvalidEnumArgument_NonInt ), exp, integral, typeof( T ) )
+ );
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Extensions/SourceLocation.cs b/src/Ubiquity.NET.Extensions/SourceLocation.cs
new file mode 100644
index 0000000..a780892
--- /dev/null
+++ b/src/Ubiquity.NET.Extensions/SourceLocation.cs
@@ -0,0 +1,192 @@
+// 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 location in a source file
+ public readonly record struct SourceLocation
+ : IFormattable
+ {
+ /// Initializes a new instance of the struct.
+ /// Reference to the source file (if any)
+ /// Range in the source
+ [SetsRequiredMembers]
+ public SourceLocation( Uri? source, SourceRange range )
+ {
+ Source = source;
+ Range = range;
+ }
+
+ /// Initializes a new instance of the struct.
+ /// Range in the source
+ [SetsRequiredMembers]
+ public SourceLocation( SourceRange range )
+ : this( (Uri?)null, range )
+ {
+ }
+
+ /// Initializes a new instance of the struct.
+ /// Reference to the source file (if any)
+ /// 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 SourceLocation( Uri? source, SourcePosition start, SourcePosition end = default )
+ : this( source, new SourceRange( start, end ) )
+ {
+ }
+
+ /// 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 SourceLocation( SourcePosition start, SourcePosition end = default )
+ : this( (Uri?)null, new SourceRange( start, end ) )
+ {
+ }
+
+ /// Initializes a new instance of the struct.
+ /// Path to the source file
+ /// Range in the source
+ [SetsRequiredMembers]
+ public SourceLocation( string? filePath, SourceRange range )
+ : this( filePath is null ? new Uri( $"file://{filePath}" ) : null, range )
+ {
+ }
+
+ /// Initializes a new instance of the struct.
+ /// Path to the source file
+ [SetsRequiredMembers]
+ public SourceLocation( string? filePath )
+ : this( filePath, default )
+ {
+ }
+
+ /// Initializes a new instance of the struct.
+ /// Path of the source file
+ /// 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 SourceLocation( string? source, SourcePosition start, SourcePosition end = default )
+ : this( source, new SourceRange( start, end ) )
+ {
+ }
+
+ /// Gets the source file location, if available
+ public Uri? Source { get; }
+
+ /// Gets the location in the source
+ public required SourceRange Range { get; init; }
+
+ /// Offset this location by amounts specified
+ /// relative location to offset
+ /// New location offset from this instance
+ ///
+ /// In essence this location is considered an absolute location and
+ /// is relative to it. The result is that of adding the relative offset to this location.
+ ///
+ public SourceLocation Offset( SourceLocation offset )
+ {
+ return Offset( this, offset );
+ }
+
+ /// Formats this location using the current culture and the generic format
+ /// Formatted string
+ ///
+ /// If specific formatting is required then use
+ /// instead.
+ ///
+ /// This ONLY formats the source location and the range portion of a full message. That is it formats
+ /// the location represented by this instance. Additional formatting and content is usually required
+ /// to form a full diagnostic message.
+ ///
+ ///
+ public override string ToString( )
+ {
+ // use runtime default formatting
+ return ToString( "G", CultureInfo.CurrentCulture );
+ }
+
+ ///
+ ///
+ /// Accepted format strings are:
+ ///
+ /// format stringDescription
+ /// - MMSBuild format used for Windows build tools.
+ /// - GRuntime 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()
+ };
+ }
+
+ /// Offset an absolute location by amounts specified in a relative offset
+ /// Base/Absolute location to apply the offset to
+ /// relative location to offset
+ /// New location offset from
+ ///
+ /// In essence is considered an absolute location and
+ /// is relative to it. The result is that of adding the relative offset to the absolute location.
+ ///
+ public static SourceLocation Offset( SourceLocation baseValue, SourceLocation offset )
+ {
+ // Intentionally not verifying or using the source name as the result is ALWAYS that
+ // of the base value. Offsetting is most commonly used to test a sub string of an original
+ // stream and that sub string doesn't usually refer to the original stream... So in that
+ // case, which the offsetting feature is designed for, the sources are ALWAYS different.
+ return new SourceLocation( baseValue.Source, SourceRange.Offset( baseValue.Range, offset.Range ) );
+ }
+
+ // 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 NET6_0_OR_GREATER // string.Create (Interpolated strings)
+ return string.Create(formatProvider, $"{Source?.LocalPath ?? ""}{Range:M}");
+#else
+ return new StringBuilder(Source?.LocalPath ?? "")
+ .AppendFormat(formatProvider, "{0:M}: ", Range)
+ .ToString();
+#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 NET5_0_OR_GREATER
+ bool isWindows = OperatingSystem.IsWindows();
+#else
+ bool isWindows = PolyFillOperatingSystem.IsWindows();
+#endif
+ if(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/SourcePosition.cs b/src/Ubiquity.NET.Extensions/SourcePosition.cs
index 7c273a0..2ced892 100644
--- a/src/Ubiquity.NET.Extensions/SourcePosition.cs
+++ b/src/Ubiquity.NET.Extensions/SourcePosition.cs
@@ -34,11 +34,7 @@ public required int Column
get;
init
{
-#if NET7_0_OR_GREATER
- ArgumentOutOfRangeException.ThrowIfLessThan(value, 0);
-#else
- PolyFillExceptionValidators.ThrowIfLessThan(value, 0);
-#endif
+ Requires.GreaterThanOrEqualTo(value, 0);
field = value;
}
}
@@ -49,15 +45,59 @@ public required int Line
get;
init
{
-#if NET7_0_OR_GREATER
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(value, 0);
-#else
- PolyFillExceptionValidators.ThrowIfLessThanOrEqual(value, 0);
-#endif
+ Requires.GreaterThan(value, 0);
field = value;
}
}
+ /// Offset an absolute position by amounts specified in a relative offset
+ /// relative position to offset
+ /// New position offset from this position
+ ///
+ /// In essence this instance is considered an absolute position and
+ /// is relative to it. The result is an absolute position that results from adding the relative
+ /// offset to the one represented by this instance.
+ ///
+ public SourcePosition Offset( SourcePosition offset )
+ {
+ return Offset( this, offset );
+ }
+
+ /// Offset an absolute position by amounts specified in a relative offset
+ /// Base/Absolute position to apply the offset to
+ /// relative position to offset
+ /// New position offset from
+ ///
+ /// In essence is considered an absolute position and
+ /// is relative to it. The result is an absolute position that results from adding the relative
+ /// offset to .
+ ///
+ public static SourcePosition Offset( SourcePosition baseValue, SourcePosition offset )
+ {
+ // if the base, or offset is nothing, then the base is the value (NOP)
+ if(offset.Line == 0)
+ {
+ return baseValue;
+ }
+
+ int line = baseValue.Line + offset.Line;
+ if(line == 0)
+ {
+ line = offset.Line;
+ }
+
+ int col = baseValue.Column + offset.Column;
+
+ // if the rhs line is from anything past the first line, then the column in lhs
+ // is of no relevance, only the rhs column is valid.
+ if(offset.Line > 1)
+ {
+ col = offset.Column;
+ }
+
+ return new SourcePosition( line, col, baseValue.Index + offset.Index );
+ }
+
/// Produces a string form of this position
/// string form of the position
public override string ToString( )
diff --git a/src/Ubiquity.NET.Extensions/SourceRange.cs b/src/Ubiquity.NET.Extensions/SourceRange.cs
index 82291d9..3e64343 100644
--- a/src/Ubiquity.NET.Extensions/SourceRange.cs
+++ b/src/Ubiquity.NET.Extensions/SourceRange.cs
@@ -40,6 +40,44 @@ public readonly record struct SourceRange
/// This only has a value if is
public int? Length => !CanSlice ? null : End.Index - Start.Index - 1;
+ /// Determines if the location contains the index based position
+ /// Index based position to test for
+ /// if this location contains the position
+ public bool Contains( int position )
+ {
+ return Start.Index.HasValue
+ && End.Index.HasValue
+ && Start.Index.Value <= position
+ && End.Index.Value >= position;
+ }
+
+ /// Offset this location by amounts specified
+ /// relative location to offset
+ /// New location offset from this instance
+ ///
+ /// In essence this location is considered an absolute location and
+ /// is relative to it. The result is that of adding the relative offset to this location.
+ ///
+ public SourceRange Offset( SourceRange offset )
+ {
+ return Offset( this, offset );
+ }
+
+ /// Offset an absolute location by amounts specified in a relative offset
+ /// Base/Absolute location to apply the offset to
+ /// relative location to offset
+ /// New location offset from
+ ///
+ /// In essence is considered an absolute location and
+ /// is relative to it. The result is that of adding the relative offset to the absolute location.
+ ///
+ public static SourceRange Offset( SourceRange baseValue, SourceRange offset )
+ {
+ var start = SourcePosition.Offset(baseValue.Start, offset.Start);
+ var end = SourcePosition.Offset(baseValue.End, offset.End);
+ return new SourceRange( start, end );
+ }
+
///
public override string ToString( )
{
@@ -50,7 +88,7 @@ public override string ToString( )
///
///
/// Accepted format strings are:
- /// "B" for MSBuild format used for Windows build tools.
+ /// "M" 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]
///
diff --git a/src/Ubiquity.NET.Extensions/StringExtensions.cs b/src/Ubiquity.NET.Extensions/StringExtensions.cs
index 8e56229..d9feca4 100644
--- a/src/Ubiquity.NET.Extensions/StringExtensions.cs
+++ b/src/Ubiquity.NET.Extensions/StringExtensions.cs
@@ -12,13 +12,12 @@ public static partial class StringExtensions
/// if the string contains any line endings
public static bool HasLineEndings( this string self, bool skipUnicodeLineEndigs = false )
{
+ Requires.NotNull( self );
#if NETSTANDARD2_0
- PolyFillExceptionValidators.ThrowIfNull(self);
return skipUnicodeLineEndigs
? PolyFillStringExtensions.SystemNewLinesRegex.IsMatch(self)
: PolyFillStringExtensions.UnicodeNewLinesRegex.IsMatch(self);
#else
- ArgumentNullException.ThrowIfNull(self);
return skipUnicodeLineEndigs
? SystemNewLinesRegex.IsMatch(self)
: UnicodeNewLinesRegex.IsMatch(self);
diff --git a/src/Ubiquity.NET.CommandLine/TextWriterReporter.cs b/src/Ubiquity.NET.Extensions/TextWriterReporter.cs
similarity index 84%
rename from src/Ubiquity.NET.CommandLine/TextWriterReporter.cs
rename to src/Ubiquity.NET.Extensions/TextWriterReporter.cs
index e6c3402..43fefc8 100644
--- a/src/Ubiquity.NET.CommandLine/TextWriterReporter.cs
+++ b/src/Ubiquity.NET.Extensions/TextWriterReporter.cs
@@ -1,7 +1,7 @@
// 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
+namespace Ubiquity.NET.Extensions
{
/// Base class for reporting diagnostics via an instance of
public class TextWriterReporter
@@ -15,14 +15,14 @@ public class TextWriterReporter
/// Verbose writer
[SetsRequiredMembers]
public TextWriterReporter(
- MsgLevel level,
+ MessageLevel level,
TextWriter? error = null,
TextWriter? warning = null,
TextWriter? information = null,
TextWriter? verbose = null
)
{
- ArgumentNullException.ThrowIfNull(error);
+ Requires.NotNull(error);
Level = level;
Error = error ?? TextWriter.Null;
@@ -38,7 +38,7 @@ public required TextWriter Error
get;
init
{
- ArgumentNullException.ThrowIfNull(value);
+ Requires.NotNull(value);
field = value;
}
}
@@ -49,7 +49,7 @@ public TextWriter Warning
get;
init
{
- ArgumentNullException.ThrowIfNull(value);
+ Requires.NotNull(value);
field = value;
}
}
@@ -60,7 +60,7 @@ public TextWriter Information
get;
init
{
- ArgumentNullException.ThrowIfNull(value);
+ Requires.NotNull(value);
field = value;
}
}
@@ -71,7 +71,7 @@ public TextWriter Verbose
get;
init
{
- ArgumentNullException.ThrowIfNull(value);
+ Requires.NotNull(value);
field = value;
}
}
@@ -82,7 +82,7 @@ public required TextWriter Error
get => ErrorBackingField;
init
{
- ArgumentNullException.ThrowIfNull( value );
+ Requires.NotNull( value );
ErrorBackingField = value;
}
}
@@ -93,7 +93,7 @@ public TextWriter Warning
get => WarningBackingField;
init
{
- ArgumentNullException.ThrowIfNull( value );
+ Requires.NotNull( value );
WarningBackingField = value;
}
}
@@ -104,7 +104,7 @@ public TextWriter Information
get => InformationBackingField;
init
{
- ArgumentNullException.ThrowIfNull( value );
+ Requires.NotNull( value );
InformationBackingField = value;
}
}
@@ -115,19 +115,21 @@ public TextWriter Verbose
get => VerboseBackingField;
init
{
- ArgumentNullException.ThrowIfNull( value );
+ Requires.NotNull( value );
VerboseBackingField = value;
}
}
+#pragma warning disable IDE0032 // Use auto property
private readonly TextWriter ErrorBackingField = TextWriter.Null;
private readonly TextWriter WarningBackingField = TextWriter.Null;
private readonly TextWriter InformationBackingField = TextWriter.Null;
private readonly TextWriter VerboseBackingField = TextWriter.Null;
+#pragma warning restore IDE0032 // Use auto property
#endif
///
- public MsgLevel Level { get; init; }
+ public MessageLevel Level { get; init; }
///
public Encoding Encoding => Error.Encoding;
@@ -135,7 +137,7 @@ public TextWriter Verbose
///
///
/// This implementation will test if the of the
- /// message is enabled. If so, then a call is made to the virtual
+ /// 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 )
@@ -157,16 +159,16 @@ public void Report( DiagnosticMessage diagnostic )
/// 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)
+ protected virtual void ReportMessage(MessageLevel 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))
+ MessageLevel.Error => Error,
+ MessageLevel.Warning => Warning,
+ MessageLevel.Information => Information,
+ MessageLevel.Verbose => Verbose,
+ MessageLevel.None => null,
+ _ => throw new InvalidEnumArgumentException(nameof(level), (int)level, typeof(MessageLevel))
};
// A message level of None is always ignored, this will not occur normally as that level is
diff --git a/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj b/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj
index 0a38abd..dd09256 100644
--- a/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj
+++ b/src/Ubiquity.NET.Extensions/Ubiquity.NET.Extensions.csproj
@@ -22,14 +22,18 @@
true
snupkg
en-US
-
-
+
+
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -53,7 +57,4 @@
ResXFileCodeGenerator
-
-
-
diff --git a/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs b/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs
index 38968b2..695b924 100644
--- a/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs
+++ b/src/Ubiquity.NET.InteropHelpers/CStringHandle.cs
@@ -30,11 +30,7 @@ public ReadOnlySpan ReadOnlySpan
{
get
{
-#if NET7_0_OR_GREATER
- ObjectDisposedException.ThrowIf( IsClosed, this );
-#else
- PolyFillExceptionValidators.ThrowIf( IsClosed, this );
-#endif
+ Requires.NotDisposed( IsClosed, this );
unsafe
{
return new( (void*)handle, LazyStrLen.Value );
@@ -50,11 +46,7 @@ public ReadOnlySpan ReadOnlySpan
///
public override string? ToString( )
{
-#if NET7_0_OR_GREATER
- ObjectDisposedException.ThrowIf( IsClosed, this );
-#else
- PolyFillExceptionValidators.ThrowIf( IsClosed, this );
-#endif
+ Requires.NotDisposed( IsClosed, this );
return ManagedString.Value;
}
@@ -69,11 +61,7 @@ public override bool Equals( object? obj )
[SuppressMessage( "Globalization", "CA1307:Specify StringComparison for clarity", Justification = "Matches string API" )]
public override int GetHashCode( )
{
-#if NET7_0_OR_GREATER
- ObjectDisposedException.ThrowIf( IsClosed, this );
-#else
- PolyFillExceptionValidators.ThrowIf( IsClosed, this );
-#endif
+ Requires.NotDisposed( IsClosed, this );
return ToString()?.GetHashCode() ?? 0;
}
@@ -83,7 +71,7 @@ public override int GetHashCode( )
/// A 32-bit signed integer hash code.
public int GetHashCode( StringComparison comparisonType )
{
- ObjectDisposedException.ThrowIf( IsClosed, this );
+ Requires.NotDisposed( IsClosed, this );
return ToString()?.GetHashCode( comparisonType ) ?? 0;
}
#endif
diff --git a/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs b/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs
index c5828e5..54b8970 100644
--- a/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs
+++ b/src/Ubiquity.NET.InteropHelpers/EncodingExtensions.cs
@@ -15,26 +15,6 @@ namespace Ubiquity.NET.InteropHelpers
///
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.
@@ -45,11 +25,7 @@ internal static int GetBytes( this Encoding self, ReadOnlySpan chars, Span
/// is
public static string? MarshalString( this Encoding self, ReadOnlySpan span )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
return span.IsEmpty
? string.Empty // optimization for empty spans
@@ -66,12 +42,7 @@ internal static int GetBytes( this Encoding self, ReadOnlySpan chars, Span
/// is
public static unsafe string? MarshalString( this Encoding self, byte* nativeStringPtr )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
-
+ Requires.NotNull( self );
if(nativeStringPtr is null)
{
return null;
diff --git a/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs b/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs
index ef5847f..4a18bc4 100644
--- a/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs
+++ b/src/Ubiquity.NET.InteropHelpers/LazyEncodedString.cs
@@ -430,11 +430,7 @@ public static LazyEncodedString Join( char separator, params IEnumerable
public static implicit operator ReadOnlySpan( LazyEncodedString self )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
return self.ToReadOnlySpan();
}
diff --git a/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs b/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs
index 81b01d3..237a183 100644
--- a/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs
+++ b/src/Ubiquity.NET.InteropHelpers/RefHandleMarshaller.cs
@@ -47,13 +47,8 @@ public static class RefHandleMarshaller
public static TRetVal WithNativePointer( this THandle[] managedArray, ReturningOp op )
where THandle : SafeHandle
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( managedArray );
- ArgumentNullException.ThrowIfNull( op );
-#else
- PolyFillExceptionValidators.ThrowIfNull( managedArray );
- PolyFillExceptionValidators.ThrowIfNull( op );
-#endif
+ Requires.NotNull( managedArray );
+ Requires.NotNull( op );
unsafe
{
@@ -73,13 +68,8 @@ public static TRetVal WithNativePointer( this THandle[] manage
public static void WithNativePointer( this THandle[] managedArray, VoidOp op )
where THandle : SafeHandle
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( managedArray );
- ArgumentNullException.ThrowIfNull( op );
-#else
- PolyFillExceptionValidators.ThrowIfNull( managedArray );
- PolyFillExceptionValidators.ThrowIfNull( op );
-#endif
+ Requires.NotNull( managedArray );
+ Requires.NotNull( op );
unsafe
{
@@ -99,13 +89,8 @@ private static IMemoryOwner AllocateNativeSpace( int len )
private static void FillNative( Memory nativeSpace, T[] managedArray )
where T : SafeHandle
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( managedArray );
- ArgumentOutOfRangeException.ThrowIfNotEqual( nativeSpace.Length, managedArray.Length );
-#else
- PolyFillExceptionValidators.ThrowIfNull( managedArray );
- PolyFillExceptionValidators.ThrowIfNotEqual( nativeSpace.Length, managedArray.Length );
-#endif
+ Requires.NotNull( managedArray );
+ Requires.NotEqual( nativeSpace.Length, managedArray.Length );
for(int i = 0; i < managedArray.Length; ++i)
{
diff --git a/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj b/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj
index 874a922..6cd1922 100644
--- a/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj
+++ b/src/Ubiquity.NET.InteropHelpers/Ubiquity.NET.InteropHelpers.csproj
@@ -42,6 +42,4 @@
-
-
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.cs
deleted file mode 100644
index 3f1d768..0000000
--- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillEncodingExtensions.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 System.Text
-{
- internal static class PolyFillEncodingExtensions
- {
-#if NETSTANDARD2_0
- public static unsafe string GetString(this Encoding self, ReadOnlySpan bytes)
- {
- fixed (byte* bytesPtr = bytes)
- {
- return self.GetString(bytesPtr, bytes.Length);
- }
- }
-
- public static unsafe int GetBytes( this Encoding self, ReadOnlySpan bytes, Span dest)
- {
- fixed(char* pSrc = bytes)
- {
- fixed(byte* pDst = dest)
- {
- return self.GetBytes( pSrc, bytes.Length, pDst, dest.Length );
- }
- }
- }
-#endif
- }
-}
diff --git a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs b/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs
deleted file mode 100644
index f668f60..0000000
--- a/src/Ubiquity.NET.PollyFill.SharedSources/PolyFillExceptionValidators.cs
+++ /dev/null
@@ -1,321 +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.
-//
-
-
-#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 localization. (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. Also until such a mechanism is found this suppresses the warnings
- // (RS1035) about use of banned APIs when used by a Roslyn component.
- 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
- internal static class PolyFillExceptionValidators
- {
- /// Throw an if a string is m empty, or all whitespace.
- /// input string to test
- /// expression or name of the string to test; normally provided by compiler
- /// string is m empty, or all whitespace
- public static void ThrowIfNullOrWhiteSpace(
- [global::System.Diagnostics.CodeAnalysis.NotNullAttribute] string? argument,
- [global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute( nameof( argument ) )] string? paramName = null
- )
- {
- PolyFillExceptionValidators.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 );
- }
- }
-
- /// Throws an exception 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 );
- }
- }
-
- /// 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 );
- }
- }
-
- /// 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 greater 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 greater 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification="Poly Fill extension API")]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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]
- [global::System.Diagnostics.CodeAnalysis.SuppressMessage( "MicrosoftCodeAnalysis", "RS1035:Banned Symbol", Justification = "Poly Fill extension API" )]
- 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/ReadMe.md b/src/Ubiquity.NET.PollyFill.SharedSources/ReadMe.md
deleted file mode 100644
index 734dbb3..0000000
--- a/src/Ubiquity.NET.PollyFill.SharedSources/ReadMe.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# About
-This library contains extensions that are shared amongst multiple additional projects in
-this repository. 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 different source generator.
-([PolySharp](https://github.com/Sergio0694/PolySharp) for this repo).
-Source generators all see the same input and therefore a source generator is untestable
-without solving the problem of explicitly generating the sources for the poly filled types.
-That is, to test the generator one must include another generator as part of the setup for
-testing. That's something currently unknown and instead of investigating it the problem was
-set aside to move other things forward.
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 de344bb..0000000
--- 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 3ce6efc..0000000
--- 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/AllErrorsMessageLevelMap.cs b/src/Ubiquity.NET.Runtime.Utils/AllErrorsMessageLevelMap.cs
new file mode 100644
index 0000000..5a12254
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/AllErrorsMessageLevelMap.cs
@@ -0,0 +1,16 @@
+// 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
+{
+ /// Default implementation of (Always returns
+ public class AllErrorsMessageLevelMap
+ : IMessageLevelMap
+ {
+ ///
+ public MessageLevel GetLevel( ScopedDiagnosticId id ) => MessageLevel.Error;
+
+ ///
+ public MessageLevel GetLevel( int id ) => MessageLevel.Error;
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/AstNode.cs b/src/Ubiquity.NET.Runtime.Utils/AstNode.cs
index 5775fe7..56929c0 100644
--- a/src/Ubiquity.NET.Runtime.Utils/AstNode.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/AstNode.cs
@@ -8,11 +8,28 @@ public abstract class AstNode
: IAstNode
{
///
- public SourceRange Location { get; }
+ public SourceLocation Location { get; }
///
public abstract IEnumerable Children { get; }
+ ///
+ [SuppressMessage( "CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Only applies to 8.0 builds" )]
+ [SuppressMessage( "Style", "IDE0305:Simplify collection initialization", Justification = "Result is obscure/terse syntax that is anything but more comprehensible" )]
+ public ImmutableList Diagnostics => DiagnosticList;
+
+ ///
+ public void AddDiagnostic( DiagnosticMessage error )
+ {
+ ImmutableInterlocked.Update( ref DiagnosticList, ( l, e ) => l.Add( e ), error );
+ }
+
+ ///
+ public void AddDiagnostics( IEnumerable errors )
+ {
+ ImmutableInterlocked.Update( ref DiagnosticList, ( l, e ) => l.AddRange( e ), errors );
+ }
+
// 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
@@ -34,9 +51,11 @@ public abstract class AstNode
/// Initializes a new instance of the class
/// Location in the source this node represents
- protected AstNode( SourceRange location )
+ protected AstNode( SourceLocation location )
{
Location = location;
}
+
+ private ImmutableList DiagnosticList = [];
}
}
diff --git a/src/Ubiquity.NET.Runtime.Utils/AstNodeExtensions.cs b/src/Ubiquity.NET.Runtime.Utils/AstNodeExtensions.cs
deleted file mode 100644
index 692f955..0000000
--- 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/CodeGeneratorException.cs b/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs
index 3ef654e..f400bbc 100644
--- a/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/CodeGeneratorException.cs
@@ -7,7 +7,7 @@ namespace Ubiquity.NET.Runtime.Utils
///
/// 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
+ /// are mostly handled as instances of or a parse
/// technology specific exception.
///
[Serializable]
diff --git a/src/Ubiquity.NET.Runtime.Utils/DefaultPrefixFormatter.cs b/src/Ubiquity.NET.Runtime.Utils/DefaultPrefixFormatter.cs
new file mode 100644
index 0000000..2a81eaa
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/DefaultPrefixFormatter.cs
@@ -0,0 +1,47 @@
+// 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 that formats a scoped ID based on a prefix and
+ ///
+ /// This performs the simple task of formatting a string code by pre-pending the prefix to the unified code formatted as a string.
+ /// Normally, this is how string codes are formed. Applications are free to use any other formatting appropriate to the application
+ /// by implementing a custom variant of
+ ///
+ public class DefaultPrefixFormatter
+ : IDiagnosticIdFormatter
+ {
+ /// Initializes a new instance of the class.
+ /// Prefix to use for the unified ID
+ /// Mapping to use for conversion from a scoped ID into a unified ID
+ public DefaultPrefixFormatter(string prefix, IDiagnosticIdMap? errorIdMap = null)
+ {
+ Prefix = prefix;
+ IdMap = errorIdMap ?? DiagnosticIdMap.OneToOne;
+ }
+
+ /// Gets the used to produce a unified integral value for a scoped ID
+ public IDiagnosticIdMap IdMap { get; }
+
+ /// Gets the prefix to use when formatting the code
+ public string Prefix { get; }
+
+ ///
+ ///
+ /// This will use to form a unified integral value
+ /// for the scoped ID and then format that with
+ /// into a string for the resulting string ID.
+ ///
+ public string FormatCode( ScopedDiagnosticId id )
+ {
+ return FormatCode(IdMap.MapId(id));
+ }
+
+ ///
+ public string FormatCode( int id )
+ {
+ return $"{Prefix}{id:04}";
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/DiagnosticCollector.cs b/src/Ubiquity.NET.Runtime.Utils/DiagnosticCollector.cs
new file mode 100644
index 0000000..4221210
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/DiagnosticCollector.cs
@@ -0,0 +1,42 @@
+// 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 diagnostics for a given node and it's children
+ public class DiagnosticCollector
+ : AstVisitorBase>
+ {
+ /// Initializes a new instance of the class
+ public DiagnosticCollector( )
+ : base( [] )
+ {
+ }
+
+ ///
+ ///
+ /// This implementation will aggregate the node to the results of diagnostics for all children
+ /// which will, in turn, add any s to the collected results. Thus
+ /// resulting in a final array of errors.
+ ///
+ public override ImmutableList Visit( IAstNode node )
+ {
+ return AggregateResult( node.Diagnostics, VisitChildren( node ) );
+ }
+
+ ///
+ [SuppressMessage( "Style", "IDE0046:Convert to conditional expression", Justification = "Nested Conditionals is not simpler" )]
+ protected override ImmutableList AggregateResult(
+ ImmutableList? aggregate,
+ ImmutableList? newResult
+ )
+ {
+ if(aggregate is null)
+ {
+ return newResult is null ? [] : newResult;
+ }
+
+ return newResult is null ? aggregate : aggregate.AddRange( newResult );
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMap.cs b/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMap.cs
new file mode 100644
index 0000000..b001185
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMap.cs
@@ -0,0 +1,32 @@
+// 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
+{
+ /// An adapter from a dictionary to an
+ public class DiagnosticIdMap
+ : IDiagnosticIdMap
+ {
+ /// Initializes a new instance of the class.
+ /// Mapping to use
+ public DiagnosticIdMap(IImmutableDictionary mapping)
+ {
+ ArgumentNullException.ThrowIfNull(mapping);
+
+ InnerMap = mapping;
+ }
+
+ ///
+ public int MapId( ScopedDiagnosticId id) => InnerMap[id];
+
+ /// Gets an instance of that provides 1:1 mapping (That is, no mapping and ignoring scope)
+ ///
+ /// This is used when the source IDs are already stable and don't change based on scope.
+ /// If the source isn't stable across releases then a custom implementation of
+ /// is used. (Typically via an instance of )
+ ///
+ public static IDiagnosticIdMap OneToOne { get; } = new DiagnosticIdMapNoThrow();
+
+ private readonly IImmutableDictionary InnerMap;
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMapNoThrow.cs b/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMapNoThrow.cs
new file mode 100644
index 0000000..1d20d32
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/DiagnosticIdMapNoThrow.cs
@@ -0,0 +1,46 @@
+// 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 that uses 1:1 mapping for a fallback
+ ///
+ /// This is the most flexible implementation as it includes fall back to a simple 1:1 mapping. This
+ /// allows the mapping provided to use a partial strategy. However, since 1:1 mapping may not be stable
+ /// it has some risks. All mapped values must remain stable, once an ID is published it can never change.
+ /// It may be deprecated but NEVER re-used. It is the responsibility of the application using this
+ /// class to ensure this behavior.
+ ///
+ public class DiagnosticIdMapNoThrow
+ : IDiagnosticIdMap
+ {
+ /// Initializes a new instance of the class.
+ /// Map to use for converting ID values, if none is specified 1:1 mapping is used for all values.
+ ///
+ /// Any ID not provided in is simply returned as(1:1 mapping). Therefore
+ /// the indexer will not throw an exception for keys not found.
+ ///
+ public DiagnosticIdMapNoThrow( IImmutableDictionary? baseMap = null )
+ {
+ InnerMap = baseMap;
+ }
+
+ ///
+ public int MapId( ScopedDiagnosticId id )
+ {
+ // Assume fallback of 1:1 mapping but try and find mapped value if possible.
+ int mappedId = id.Code;
+ if(InnerMap is not null && InnerMap.Count > 0)
+ {
+ if(InnerMap.TryGetValue( id, out int mapping ))
+ {
+ mappedId = mapping;
+ }
+ }
+
+ return mappedId;
+ }
+
+ private readonly IImmutableDictionary? InnerMap;
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/ErrorNode.cs b/src/Ubiquity.NET.Runtime.Utils/ErrorNode.cs
deleted file mode 100644
index 112da58..0000000
--- 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 508cd93..0000000
--- 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/IAstNode.cs b/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs
index d3c2933..5eb198d 100644
--- a/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/IAstNode.cs
@@ -7,11 +7,22 @@ namespace Ubiquity.NET.Runtime.Utils
public interface IAstNode
{
/// Gets the source location covering the original source for the node
- SourceRange Location { get; }
+ SourceLocation Location { get; }
/// Gets a collection of children for the node
IEnumerable Children { get; }
+ /// Gets all diagnostics associated with this node
+ ImmutableList Diagnostics { get; }
+
+ /// Add an error to this node
+ /// Error to attach to this node
+ void AddDiagnostic( DiagnosticMessage error );
+
+ /// Add a range of errors to this node
+ /// Errors to attach to this node
+ void AddDiagnostics( IEnumerable errors );
+
/// 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
@@ -26,7 +37,8 @@ public interface IAstNode
/// Result of visiting this node
TResult? Accept( IAstVisitor visitor, ref readonly TArg arg )
#if NET9_0_OR_GREATER
- where TArg : struct, allows ref struct;
+ where TArg : struct
+ , allows ref struct;
#else
where TArg : struct;
#endif
diff --git a/src/Ubiquity.NET.Runtime.Utils/IAstNodeExtensions.cs b/src/Ubiquity.NET.Runtime.Utils/IAstNodeExtensions.cs
new file mode 100644
index 0000000..f7b742f
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/IAstNodeExtensions.cs
@@ -0,0 +1,66 @@
+// 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
+{
+ /// Extension class for
+ ///
+ /// While C# has a syntax mechanism for providing default implementations of an interface, using them
+ /// is sub-optimal. This will provide extensions for the interface AND any type that implements it without
+ /// all the casting or runtime requirements etc... needed for the default interface methods.
+ ///
+ public static class IAstNodeExtensions
+ {
+ /// Extension method to detect if an has any errors associated with it
+ /// Node to test
+ /// if the node has any errors
+ public static bool IsValid( this IAstNode self )
+ {
+ return !self.Diagnostics.IsEmpty;
+ }
+
+ /// Determines if an contains the specified index based position
+ /// Node to test
+ /// Index based position to test for
+ /// if the node contains
+ public static bool Contains( this IAstNode self, int position )
+ {
+ return self.Location.Range.Contains( position );
+ }
+
+ /// Enumerate items in a node (deepest nodes first)
+ /// Root node to enumerate
+ /// Enumerable of the nodes in a flat ordering
+ ///
+ /// Neither breadth first nor Depth first traversal is what is desired here to maintain structural ordering.
+ /// Effectively what is needed is a reverse Breadth-first ordering. This assumes that children
+ /// are sorted according to document order [Normal condition]. This allows matching/searching, for the
+ /// lowest level node based on a position. (Obviously the highest level node
+ /// "contains" all valid positions for the document! So finding the first match using the depth is simple)
+ ///
+ public static IEnumerable Flatten( this IAstNode root )
+ {
+ var evalStack = new Stack<(IAstNode Root, IEnumerator Iterator)>();
+ evalStack.Push( (root, root.Children.GetEnumerator()) );
+ while(evalStack.Count > 0)
+ {
+ // pop node from top of stack
+ (IAstNode rootNode, IEnumerator it) = evalStack.Pop();
+ if(!it.MoveNext())
+ {
+ // no more children or a leaf node, so yield the node itself
+ yield return rootNode;
+ }
+ else
+ {
+ // push the current state of the node + iterator
+ evalStack.Push( (root, it) );
+
+ // get the child and a new iterator for it's children and push that state
+ IAstNode nextRoot = it.Current;
+ evalStack.Push( (nextRoot, nextRoot.Children.GetEnumerator()) );
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs b/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs
index 4ffe4c6..4a5b752 100644
--- a/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/IAstVisitor.cs
@@ -5,7 +5,7 @@ namespace Ubiquity.NET.Runtime.Utils
{
///
///
- /// This interface is sued for visiting an AST, typically for
+ /// This interface is used for visiting an AST, typically for
/// generating code but may also be used to detect errors in the
/// AST etc..
///
@@ -21,9 +21,9 @@ public interface IAstVisitor
///
/// 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
+ /// 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.)
+ /// requires at least .NET 9 to support allows ref struct, which requires runtime support.)
///
public interface IAstVisitor
#if NET9_0_OR_GREATER
diff --git a/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdFormatter.cs b/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdFormatter.cs
new file mode 100644
index 0000000..fc9af26
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdFormatter.cs
@@ -0,0 +1,25 @@
+// 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 an error code formatter
+ ///
+ /// Implementations of this interface provide conversion of an
+ /// integral error code into a string suitable for use in a
+ /// or general UX. Normally this
+ /// is a simple prefix added to a unified Id value.
+ ///
+ public interface IDiagnosticIdFormatter
+ {
+ /// Converts an into a string form
+ /// Id to convert
+ /// Application specific string form of
+ string FormatCode( ScopedDiagnosticId id );
+
+ /// Converts a unified ID into a string form
+ /// Id to convert
+ /// Application specific string form of
+ string FormatCode( int id );
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdMap.cs b/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdMap.cs
new file mode 100644
index 0000000..d70c097
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/IDiagnosticIdMap.cs
@@ -0,0 +1,27 @@
+// 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 mapping error IDs
+ ///
+ /// This allows use of a parse technology specific ID value that may not be stable
+ /// across releases to a value that is stable. If no mapping is available then this
+ /// uses a 1:1 mapping. (That is, the mapped ID is the same as the one provided).
+ /// This is an extensibility point to allow for instability in the source to map
+ /// to a stable value as once an ID is published it is immutable. It may be deprecated
+ /// but NEVER re-used for any other purpose.
+ ///
+ public interface IDiagnosticIdMap
+ {
+ /// Maps a technology specific, potentially unstable ID, to a stable form
+ /// ID value to map
+ /// Mapped ID
+ ///
+ /// The parameter acts as a sort of namespace for the IDs. This
+ /// allows mapping to take into account that a different source may produce the same value.
+ ///
+ /// ID isn't found or mappable
+ int MapId(ScopedDiagnosticId id);
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/ILexicalTokenNode.cs b/src/Ubiquity.NET.Runtime.Utils/ILexicalTokenNode.cs
new file mode 100644
index 0000000..4265829
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ILexicalTokenNode.cs
@@ -0,0 +1,17 @@
+// 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
+{
+ /// for a lexical token with token kind
+ public interface ILexicalTokenNode
+ : ISyntaxNode
+ {
+ /// Gets the kind of element this node represents
+ int TokenKind { get; }
+
+ // Constraints not expressible in language:
+ // 1) Children != null
+ // 2) Children.Count == 0 && Children.Any() == false
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/IMessageLevelMap.cs b/src/Ubiquity.NET.Runtime.Utils/IMessageLevelMap.cs
new file mode 100644
index 0000000..687e379
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/IMessageLevelMap.cs
@@ -0,0 +1,46 @@
+// 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 an application provided severity mapping
+ ///
+ /// Each application of a parser may have or need different severity values for
+ /// a given unified error. Some may even allow user configuration of diagnostic
+ /// messaging. This interface provides the extension point to allow such mapping.
+ ///
+ public interface IMessageLevelMap
+ {
+ /// Gets the severity for a given
+ /// Scoped id for the diagnostic
+ /// Severity or a default value if the diagnostic id isn't mapped. (MUST NOT THROW)
+ ///
+ ///
+ /// If is the default value, or otherwise not mappable, then the result is a default.
+ /// The exact value for a default depends on the implementation.
+ ///
+ /// This is provided to an API that allows per application instance mapping of severity levels for a
+ /// given diagnostic ID. It is common for a "driver" application to allow user overrides for message severity
+ /// levels. This allows conversion of the code to whatever the application wants. A default that maps all
+ /// diagnostics to is provided in .
+ ///
+ ///
+ MessageLevel GetLevel(ScopedDiagnosticId id);
+
+ /// Gets the severity for a given unified error ID
+ /// Unified id for the error
+ /// Severity or a default value if the error id isn't mapped. (MUST NOT THROW)
+ ///
+ ///
+ /// If is the default value, or otherwise not mappable, then the result is a default.
+ /// The exact value for a default depends on the implementation.
+ ///
+ /// This is provided to an API that allows per application instance mapping of severity levels for a
+ /// given diagnostic ID. It is common for a "driver" application to allow user overrides for message severity
+ /// levels. This allows conversion of the code to whatever the application wants. A default that maps all
+ /// errors to is provided in .
+ ///
+ ///
+ MessageLevel GetLevel( int id );
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/IParseErrorListener.cs b/src/Ubiquity.NET.Runtime.Utils/IParseErrorListener.cs
deleted file mode 100644
index 9cd1c07..0000000
--- 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 745114a..0000000
--- 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
index 7c41339..5558fe8 100644
--- a/src/Ubiquity.NET.Runtime.Utils/IParser.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/IParser.cs
@@ -9,6 +9,14 @@ 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
{
+ /// Gets the reporter to use for reporting any diagnostics
+ ///
+ /// If is provided it is used to report
+ /// any diagnostics found during the parse. If not provided the diagnostics are
+ /// still available with each node in the result.
+ ///
+ IDiagnosticReporter? DiagnosticReporter { get; }
+
/// Try parsing the given input text
/// Text to parse
/// Parse results as an
@@ -19,7 +27,7 @@ public interface IParser
/// implementing .
///
/// If the parse succeeds, but creation of the AST fails, then the result is
- /// an AST tree with some nodes as
+ /// an AST tree with some nodes with diagnostic messages.
///
///
IAstNode? Parse( string txt );
@@ -40,11 +48,11 @@ public static class ParserExtensions
/// Path of the input file to parse
/// Parse results as an
///
- public static IAstNode? ParseFrom(this T self, string sourceFilePath)
+ public static IAstNode? ParseFrom( this T self, string sourceFilePath )
where T : IParser
{
using var rdr = File.OpenText( sourceFilePath );
- return self.Parse(rdr);
+ return self.Parse( rdr );
}
}
}
diff --git a/src/Ubiquity.NET.Runtime.Utils/ISyntaxNode.cs b/src/Ubiquity.NET.Runtime.Utils/ISyntaxNode.cs
new file mode 100644
index 0000000..67ed630
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ISyntaxNode.cs
@@ -0,0 +1,48 @@
+// 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 node in a source tree
+ public interface ISyntaxNode
+ {
+ /// Gets the location in source or a default constructed value if no source location available
+ SourceLocation Location { get; }
+
+ /// Gets immediate child nodes of this node
+ /// This enumeration is NOT ; implementations must provide a valid enumerable even if it is empty
+ IEnumerable Children { get; }
+
+ /// Gets a list all diagnostics associated with this node
+ ImmutableList Diagnostics { get; }
+
+ /// Add a diagnostic to this node
+ /// Error to attach to this node
+ void AddDiagnostic( DiagnosticMessage diagnostic );
+
+ /// Add a range of errors to this node
+ /// Errors to attach to this node
+ void AddDiagnostic( IEnumerable diagnostics );
+
+ /// 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( ISyntaxTreeVisitor 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( ISyntaxTreeVisitor 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/ISyntaxTreeVisitor.cs b/src/Ubiquity.NET.Runtime.Utils/ISyntaxTreeVisitor.cs
new file mode 100644
index 0000000..a81a633
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ISyntaxTreeVisitor.cs
@@ -0,0 +1,36 @@
+// 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 interface ISyntaxTreeVisitor
+ {
+ ///
+ TResult? Visit( ISyntaxNode 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 a syntax tree, typically for generating an AST but may also
+ /// be used to detect errors in the syntax before semantic analysis 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 ISyntaxTreeVisitor
+#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( ISyntaxNode node, ref readonly TArg arg );
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs b/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs
index a3f41aa..a41df77 100644
--- a/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/IVisualizer.cs
@@ -3,6 +3,10 @@
namespace Ubiquity.NET.Runtime.Utils
{
+ // TODO: Remove this, in favor of a more generalized "visualizer" that doesn't care about the form. Instead the REPL is provided
+ // an ImmutableArray that can handle the various forms of input. (ISyntaxNode, IAstNode)...
+ // see: https://github.com/UbiquityDotNET/Ubiquity.NET.Utils/issues/14
+
/// 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
diff --git a/src/Ubiquity.NET.Runtime.Utils/NullNode.cs b/src/Ubiquity.NET.Runtime.Utils/NullNode.cs
index c450644..48bb917 100644
--- a/src/Ubiquity.NET.Runtime.Utils/NullNode.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/NullNode.cs
@@ -8,7 +8,7 @@ public class NullNode
: IAstNode
{
///
- public SourceRange Location { get; } = default;
+ public SourceLocation Location { get; } = default;
///
public IEnumerable Children { get; } = [];
@@ -31,6 +31,21 @@ public class NullNode
return default;
}
+ ///
+ public ImmutableList Diagnostics => [];
+
+ ///
+ public void AddDiagnostic( DiagnosticMessage error )
+ {
+ throw new NotSupportedException( "Cannot add diagnostics to a null node" );
+ }
+
+ ///
+ public void AddDiagnostics( IEnumerable errors )
+ {
+ throw new NotSupportedException( "Cannot add diagnostics to a null node" );
+ }
+
/// Gets a singleton null node instance
public static NullNode Instance => LazyInstance.Value;
diff --git a/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs b/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs
index f741a3e..c9b50c1 100644
--- a/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/ParseErrorCollector.cs
@@ -1,23 +1,27 @@
// 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 DELETE_ME_LATER
namespace Ubiquity.NET.Runtime.Utils
{
- /// Implementation of to collect any errors that occur during a parse
+ /// Implementation of to collect any diagnostics that occur during a parse
public class ParseErrorCollector
- : IParseErrorListener
+ : IDiagnosticReporter
{
+ /// Gets the level setting for this reporter (Always
+ public MessageLevel Level => MessageLevel.Error;
+
///
- ///
- /// This will collect every reported during a parse
- ///
- public void SyntaxError( SyntaxError syntaxError )
+ public Encoding Encoding { get; init; } = Encoding.Unicode;
+
+ public void Report( DiagnosticMessage diagnostic )
{
- ArgumentNullException.ThrowIfNull( syntaxError );
- ErrorNodes = ErrorNodes.Add( new ErrorNode( syntaxError.Location, syntaxError.Id, syntaxError.ToString() ) );
+ throw new NotImplementedException();
}
/// Gets the error nodes found by this listener
- public ImmutableArray ErrorNodes { get; private set; } = [];
+ public ImmutableArray ErrorNodes { get; private set; } = [];
+
}
}
+#endif
diff --git a/src/Ubiquity.NET.Runtime.Utils/ParseErrorDiagnosticAdapter.cs b/src/Ubiquity.NET.Runtime.Utils/ParseErrorDiagnosticAdapter.cs
deleted file mode 100644
index ea151b8..0000000
--- 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/ParseErrorReporterExtensions.cs b/src/Ubiquity.NET.Runtime.Utils/ParseErrorReporterExtensions.cs
new file mode 100644
index 0000000..ccb45dc
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ParseErrorReporterExtensions.cs
@@ -0,0 +1,39 @@
+// 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 extension methods for
+ public static class ParseErrorReporterExtensions
+ {
+ /// Collects and reports all diagnostics in an
+ /// Reporter to use for any diagnostics found
+ /// Node to find diagnostics from
+ /// if any diagnostics were found; if not
+ public static bool CheckAndReportParseDiagnostics( this IDiagnosticReporter self, IAstNode? node )
+ {
+ ArgumentNullException.ThrowIfNull( self );
+
+ if(node is null)
+ {
+ return true;
+ }
+
+ // Gather all diagnostics for the node, if none found
+ // bail out early.
+ var diagnostics = new DiagnosticCollector().Visit(node);
+ if(diagnostics.Count == 0)
+ {
+ return false;
+ }
+
+ // report each diagnostic found
+ foreach(var diagnostic in diagnostics)
+ {
+ self.Report( diagnostic );
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/ParseSource.cs b/src/Ubiquity.NET.Runtime.Utils/ParseSource.cs
new file mode 100644
index 0000000..f9c7ce1
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ParseSource.cs
@@ -0,0 +1,18 @@
+// 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
+{
+ /// Analysis level for error or other classifications of a complete parse
+ public enum ParseSource
+ {
+ /// Lexical level - used to classify errors and other conditions from only a lexical analysis
+ Lexical,
+
+ /// Syntactic level - used to classify errors and other conditions from a parse that produces a parse tree
+ Syntactic,
+
+ /// Syntactic level - used to classify errors and other conditions that result from additional processing of a parse tree
+ Semantic
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs b/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs
index f1ede88..f8c9343 100644
--- a/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/REPLBase.cs
@@ -16,7 +16,7 @@ public abstract class REPLBase
public abstract void ProcessResults( T resultValue );
/// Gets the error logger to use for logging any parse errors
- public IParseErrorReporter ErrorLogger { get; }
+ public IDiagnosticReporter Reporter { get; }
/// Asynchronously runs the REPL loop on the input reader
/// Reader to process the input for
@@ -31,7 +31,7 @@ public abstract class REPLBase
// 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 )
+ where node.IsValid()
select node;
await foreach(IAstNode node in replSeq.WithCancellation( cancelToken ))
@@ -46,19 +46,34 @@ public abstract class REPLBase
}
catch(CodeGeneratorException ex)
{
+ var diagnostic = new DiagnosticMessage()
+ {
+ Code = CodeGeneratorExceptionCode,
+ Level = MessageLevel.Error,
+ SourceLocation = default,
+ Subcategory = default,
+ Text = ex.ToString()
+ };
+
// This is an internal error that is not recoverable.
// Report the error and stop additional processing
- ErrorLogger.ReportError( ex.ToString() );
+ Reporter.Report(diagnostic);
break;
}
}
}
+ /// Gets the string form of the code to use for any created for a
+ ///
+ /// The default value is "CG0001" but derived types may override this if an alternate is desired.
+ ///
+ protected virtual string CodeGeneratorExceptionCode { get; } = "CG0001";
+
/// Initializes a new instance of the class
- /// Logger to use for reporting any errors during parse
- protected REPLBase( IParseErrorReporter logger )
+ /// Diagnostic reporter to report messages to
+ protected REPLBase( IDiagnosticReporter reporter )
{
- ErrorLogger = logger;
+ Reporter = reporter;
}
}
}
diff --git a/src/Ubiquity.NET.Runtime.Utils/ReadMe.md b/src/Ubiquity.NET.Runtime.Utils/ReadMe.md
index aa8da32..1b47a0d 100644
--- a/src/Ubiquity.NET.Runtime.Utils/ReadMe.md
+++ b/src/Ubiquity.NET.Runtime.Utils/ReadMe.md
@@ -1,8 +1,9 @@
# Ubiquity.NET.Runtime.Utils
This library contains support functionality to aid in building a Domain Specific Language
(DSL) runtime, including common implementation of a Read-Evaluate-Print Loop (REPL) used for
-interactive languages. Generally this is used in conjunction with the `Ubiquity.NET.Llvm`
-library to provide custom DSL JIT support.
+interactive languages. Generally, this is used in conjunction with the `Ubiquity.NET.Llvm`
+library to provide custom DSL JIT support though it is useful on it's own for language
+servers etc...
See the [`Kaleidoscope`](https://ubiquitydotnet.github.io/Llvm.NET/llvm/articles/Samples/Kaleidoscope/Kaleidoscope-Overview.html)
-tutorial in the LLVM repository for details on how to use this library.
+tutorial in the LLVM repository for an example use of this library.
diff --git a/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs b/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs
index d77e133..12046e7 100644
--- a/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/ScopeStack.cs
@@ -7,10 +7,11 @@ namespace Ubiquity.NET.Runtime.Utils
/// 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.
+ /// values that is most commonly used in code generation and other language processing scenarios.
+ /// 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
diff --git a/src/Ubiquity.NET.Runtime.Utils/ScopedDiagnosticId.cs b/src/Ubiquity.NET.Runtime.Utils/ScopedDiagnosticId.cs
new file mode 100644
index 0000000..e27c010
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/ScopedDiagnosticId.cs
@@ -0,0 +1,73 @@
+// 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
+{
+ /// Scoped error ID
+ ///
+ /// This library supports three distinct forms of a diagnostic ID.
+ /// 1) The scoped form (this type)
+ /// 2) The unified form (a simple )
+ /// 3) String form used in UX and instances.
+ ///
+ /// This allows the parse scopes to remain independent of each other. They can use diagnostic IDs that overlap.
+ /// is used to provide application specific unification of the scoped ID into a
+ /// unique value. [For example adding an offset to the ID based on the scope etc...] This
+ /// mapping is provided by the application as an extensibility point that allows custom handling of the ID value.
+ ///
+ /// Creation of a diagnostic message will use an instance of to
+ /// format an instance of this type into a string form expected. This is usually an instance of
+ /// but an application is free to use any formatting needed.
+ ///
+ ///
+ public readonly record struct ScopedDiagnosticId
+ {
+ /// Initializes a new instance of the struct.
+ /// Source scope for the ID
+ /// Id relative to
+ ///
+ /// This represents a scoped ID code, to get a singular integral value callers can use
+ /// an instance of to get a unified integral value or use
+ /// an instance of to get a string form. For convenience
+ /// the
+ /// method provides a standard means of producing a from an instance of
+ /// a .
+ ///
+ public ScopedDiagnosticId(ParseSource scope, int code)
+ {
+ Scope = scope;
+ Code = code;
+ }
+
+ /// Gets the scope of the ID
+ public ParseSource Scope { get; }
+
+ /// Gets the scoped ID value
+ public int Code { get; }
+
+ /// Creates a from this instance and provided parameters
+ /// Map to use for determining the message level
+ /// Formatter to use for conversion of this instance to a string form
+ /// Text message of the diagnostic
+ /// Location of the diagnostic
+ /// Subcategory of the diagnostic
+ /// formed from this instance and the parameters provided.
+ public DiagnosticMessage AsDiagnostic(
+ IMessageLevelMap messageLevelMap,
+ IDiagnosticIdFormatter formatter,
+ string message,
+ SourceLocation? location = null,
+ string? subcategory = null
+ )
+ {
+ return new DiagnosticMessage()
+ {
+ Code = formatter.FormatCode(this),
+ Level = messageLevelMap.GetLevel(this),
+ SourceLocation = location ?? default,
+ Subcategory = subcategory,
+ Text = message,
+ };
+ }
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/SyntaxError.cs b/src/Ubiquity.NET.Runtime.Utils/SyntaxError.cs
deleted file mode 100644
index 79cac0e..0000000
--- 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/SyntaxNodeBase.cs b/src/Ubiquity.NET.Runtime.Utils/SyntaxNodeBase.cs
new file mode 100644
index 0000000..bee1595
--- /dev/null
+++ b/src/Ubiquity.NET.Runtime.Utils/SyntaxNodeBase.cs
@@ -0,0 +1,69 @@
+// 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
+{
+ /// Base class for implementations of
+ public abstract class SyntaxNodeBase
+ : ISyntaxNode
+ {
+ /// Initializes a new instance of the class
+ /// Location of the node
+ protected SyntaxNodeBase( SourceLocation location )
+ {
+ Location = location;
+ }
+
+ ///
+ public SourceLocation Location { get; }
+
+ ///
+ public virtual IEnumerable Children
+ => [];
+
+ ///
+ [SuppressMessage( "CodeQuality", "IDE0079:Remove unnecessary suppression", Justification = "Only applies to 8.0 builds" )]
+ [SuppressMessage( "Style", "IDE0305:Simplify collection initialization", Justification = "Result is obscure/terse syntax that is anything but more comprehensible" )]
+ public ImmutableList Diagnostics => DiagnosticList;
+
+ ///
+ public void AddDiagnostic( DiagnosticMessage diagnostic )
+ {
+ ImmutableInterlocked.Update( ref DiagnosticList, (l, e) => l.Add( e ), diagnostic );
+ }
+
+ ///
+ public void AddDiagnostic( IEnumerable diagnostics )
+ {
+ ImmutableInterlocked.Update( ref DiagnosticList, ( l, e ) => l.AddRange( e ), diagnostics );
+ }
+
+ /// 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
+ public virtual TResult? Accept( ISyntaxTreeVisitor visitor )
+ {
+ return visitor.Visit( this );
+ }
+
+ /// 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
+ public virtual TResult? Accept( ISyntaxTreeVisitor visitor, ref readonly TArg arg )
+#if NET9_0_OR_GREATER
+ where TArg : struct
+ , allows ref struct
+#else
+ where TArg : struct
+#endif
+ {
+ return visitor.Visit( this, in arg );
+ }
+
+ private ImmutableList DiagnosticList = [];
+ }
+}
diff --git a/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs b/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs
index 3a0bee1..1838211 100644
--- a/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs
+++ b/src/Ubiquity.NET.Runtime.Utils/VisualizationKind.cs
@@ -3,6 +3,10 @@
namespace Ubiquity.NET.Runtime.Utils
{
+ // TODO: Remove this, in favor of a more generalized "visualizer" that doesn't care about the form. Instead the REPL is provided
+ // an ImmutableArray that can handle the various forms of input. (ISyntaxNode, IAstNode)...
+ // see: https://github.com/UbiquityDotNET/Ubiquity.NET.Utils/issues/14
+
/// Enumeration to define the kinds of diagnostic intermediate data to generate from a runtime/language AST
[Flags]
public enum VisualizationKind
diff --git a/src/Ubiquity.NET.SourceGenerator.Test.Utils/VerifierExtensions.cs b/src/Ubiquity.NET.SourceGenerator.Test.Utils/VerifierExtensions.cs
index 15897cd..13cc67e 100644
--- a/src/Ubiquity.NET.SourceGenerator.Test.Utils/VerifierExtensions.cs
+++ b/src/Ubiquity.NET.SourceGenerator.Test.Utils/VerifierExtensions.cs
@@ -329,7 +329,7 @@ public void AreEqual(
}
string uniDiff = expected.UniDiff(actual);
- Verify.True( string.IsNullOrWhiteSpace( uniDiff ), $"{message}Differences:\n{uniDiff}" );
+ Verify.True( string.IsNullOrWhiteSpace( uniDiff ), $"{message}\nDifferences:\n{uniDiff}" );
}
}
diff --git a/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs b/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs
index 8175fda..5ac082a 100644
--- a/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs
+++ b/src/Ubiquity.NET.SrcGeneration.UT/StringExtensionTests.cs
@@ -1,8 +1,6 @@
// 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" )]
diff --git a/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs b/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs
index 45a1e91..b8fefac 100644
--- a/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs
+++ b/src/Ubiquity.NET.SrcGeneration.UT/TestContextExtensions.cs
@@ -1,8 +1,6 @@
// 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]
diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs
index 4ee87b8..e31c228 100644
--- a/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs
+++ b/src/Ubiquity.NET.SrcGeneration/CSharp/CSharpLanguage.cs
@@ -138,11 +138,7 @@ public static string AsLiteral( char value )
///
public static string MakeIdentifier( this string self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
// always replace invalid characters
// TODO: more sophisticated Regex that matches anything NOT a valid identifier char
diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs
index 2654a70..84d8f89 100644
--- a/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs
+++ b/src/Ubiquity.NET.SrcGeneration/CSharp/IndentedTextWriterExtensions.cs
@@ -22,11 +22,7 @@ public static void WriteAutoGeneratedComment(
bool writeClosingNewLine = false
)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
using var scope = self.WriteAutoGeneratedCommentBlock(toolName, toolVersion, writeClosingNewLine: writeClosingNewLine);
@@ -54,15 +50,9 @@ public static void WriteAutoGeneratedComment(
///
public static IDisposable WriteAutoGeneratedCommentBlock( this IndentedTextWriter self, string toolName, string toolVersion, bool writeClosingNewLine = false )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentException.ThrowIfNullOrWhiteSpace( toolName );
- ArgumentException.ThrowIfNullOrWhiteSpace( toolVersion );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( toolName );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( toolVersion );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNullOrWhiteSpace( toolName );
+ Requires.NotNullOrWhiteSpace( toolVersion );
string open = $"""
// ------------------------------------------------------------------------------
@@ -87,13 +77,8 @@ public static IDisposable WriteAutoGeneratedCommentBlock( this IndentedTextWrite
/// Disposable scope for the namespace (closes the scope on dispose)
public static IDisposable Namespace( this IndentedTextWriter self, string namespaceName, bool writeClosingNewLine = false )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentException.ThrowIfNullOrWhiteSpace(namespaceName);
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(namespaceName);
-#endif
+ Requires.NotNull( self );
+ Requires.NotNullOrWhiteSpace(namespaceName);
return self.Scope( $"namespace {namespaceName}", writeClosingNewLine: writeClosingNewLine );
}
@@ -106,11 +91,7 @@ public static IDisposable Namespace( this IndentedTextWriter self, string namesp
/// Disposable scope that closes the struct declaration on
public static IDisposable Struct( this IndentedTextWriter self, string? access, string structName, bool writeClosingNewLine = false )
{
-#if NET8_0_OR_GREATER
- ArgumentException.ThrowIfNullOrWhiteSpace( structName );
-#else
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( structName );
-#endif
+ Requires.NotNullOrWhiteSpace( structName );
access ??= string.Empty;
return self.Scope( $"{access} struct {structName}", writeClosingNewLine: writeClosingNewLine );
@@ -154,11 +135,7 @@ public static IDisposable Scope( this IndentedTextWriter self, string? leadingLi
/// If is , empty, or all whitespace then this is a NOP
public static void MultiLineComment( this IndentedTextWriter self, string? txt, bool writeClosingNewLine = false )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
if(string.IsNullOrWhiteSpace(txt))
{
@@ -186,11 +163,7 @@ public static void MultiLineComment( this IndentedTextWriter self, string? txt,
/// Disposable scope that closes the class declaration on
public static IDisposable Class( this IndentedTextWriter self, string? access, string className, bool writeClosingNewLine = false )
{
-#if NET8_0_OR_GREATER
- ArgumentException.ThrowIfNullOrWhiteSpace( className );
-#else
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( className );
-#endif
+ Requires.NotNullOrWhiteSpace( className );
access ??= string.Empty;
return self.Scope( $"{access} class {className}", writeClosingNewLine: writeClosingNewLine );
diff --git a/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs
index a735679..3497baa 100644
--- a/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs
+++ b/src/Ubiquity.NET.SrcGeneration/CSharp/TextWriterExtensions.cs
@@ -14,13 +14,9 @@ public static class TextWriterExtensions
/// arguments for the attribute
public static void WriteAttributeLine( this TextWriter self, string attributeName, params string[] attribArgs )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentException.ThrowIfNullOrWhiteSpace(attributeName);
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(attributeName);
-#endif
+ Requires.NotNull( self );
+ Requires.NotNullOrWhiteSpace(attributeName);
+
self.WriteAttribute( attributeName, attribArgs );
self.WriteLine();
}
@@ -31,13 +27,9 @@ public static void WriteAttributeLine( this TextWriter self, string attributeNam
/// arguments for the attribute
public static void WriteAttribute(this TextWriter self, string attributeName, params string[] attribArgs )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentException.ThrowIfNullOrWhiteSpace( attributeName );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(attributeName);
-#endif
+ Requires.NotNull( self );
+ Requires.NotNullOrWhiteSpace(attributeName);
+
self.Write( $"[{attributeName}" );
if(attribArgs.Length > 0)
{
@@ -52,11 +44,7 @@ public static void WriteAttribute(this TextWriter self, string attributeName, pa
/// Text to include in the summary (Nothing is written if this is or all whitespace
public static void WriteSummaryComment(this TextWriter self, string? description )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
if(!string.IsNullOrWhiteSpace( description ))
{
@@ -73,11 +61,7 @@ public static void WriteSummaryComment(this TextWriter self, string? description
///
public static void WriteRemarksComment( this TextWriter self, string? txt )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
if(string.IsNullOrWhiteSpace( txt ))
{
@@ -99,24 +83,22 @@ public static void WriteRemarksComment( this TextWriter self, string? txt )
/// 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
+ /// 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
+ /// 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 )
+ public static void WriteSummaryAndRemarksComments( this TextWriter self, string? remarksText, string? defaultSummary = null )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
- if(string.IsNullOrWhiteSpace( txt ))
+ // If there is no remarks try the summary
+ if(string.IsNullOrWhiteSpace( remarksText ))
{
+ // if summary is not empty write it out, otherwise this is a NOP
if(!string.IsNullOrWhiteSpace( defaultSummary ))
{
self.WriteLine( $"/// {defaultSummary!.Trim()}" );
@@ -125,14 +107,11 @@ public static void WriteSummaryAndRemarksComments( this TextWriter self, string?
return;
}
-#if NET8_0_OR_GREATER
- ArgumentException.ThrowIfNullOrWhiteSpace( defaultSummary );
-#else
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace( defaultSummary );
-#endif
+ // remarks text exists, so a summary MUST exist
+ Requires.NotNullOrWhiteSpace( defaultSummary );
self.WriteLine( $"/// {defaultSummary.Trim()}" );
- string[] lines = [ .. txt!.GetCommentLines() ];
+ string[] lines = [ .. remarksText!.GetCommentLines() ];
if(lines.Length > 0)
{
// summary + remarks.
@@ -152,13 +131,8 @@ public static void WriteSummaryAndRemarksComments( this TextWriter self, string?
/// Namespace for the using directive
public static void WriteUsingDirective(this TextWriter self, string namespaceName )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentException.ThrowIfNullOrWhiteSpace(namespaceName);
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNullOrWhiteSpace(namespaceName);
-#endif
+ Requires.NotNull( self );
+ Requires.NotNullOrWhiteSpace(namespaceName);
self.WriteLine( $"using {namespaceName};" );
}
@@ -167,11 +141,7 @@ public static void WriteUsingDirective(this TextWriter self, string namespaceNam
/// Writer to write the line to
public static void WriteEmptyCommentLine(this TextWriter self)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
self.WriteLine("//");
}
@@ -184,13 +154,9 @@ public static void WriteEmptyCommentLine(this TextWriter self)
/// is null
public static void WriteCommentLine( this TextWriter self, string comment)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentNullException.ThrowIfNull( comment );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNull( comment );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNull( comment );
+
if(comment.HasLineEndings())
{
throw new FormatException( "Single line comments cannot contain line endings" );
@@ -208,13 +174,9 @@ public static void WriteCommentLine( this TextWriter self, string comment)
/// is null
public static void WriteCommentLines( this TextWriter self, params string[] commentLines)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentNullException.ThrowIfNull( commentLines );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNull( commentLines );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNull( commentLines );
+
WriteCommentLines(self, (IEnumerable)commentLines);
}
#endif
@@ -231,13 +193,8 @@ public static void WriteCommentLines( this TextWriter self, params IEnumerable commentLines)
#endif
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentNullException.ThrowIfNull( commentLines );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNull( commentLines );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNull( commentLines );
foreach(string comment in commentLines)
{
@@ -256,13 +213,8 @@ public static void WriteCommentLines( this TextWriter self, IEnumerable
///
public static void WriteCommentLines( this TextWriter self, string commentTextBlock)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentNullException.ThrowIfNull( commentTextBlock );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNull( commentTextBlock );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNull( commentTextBlock );
// SplitLines already handles the new lines so optimize by doing a simple write
foreach(string comment in commentTextBlock.SplitLines(StringSplitOptions2.TrimEntries))
diff --git a/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs b/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs
index 6934e82..87c6008 100644
--- a/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs
+++ b/src/Ubiquity.NET.SrcGeneration/IndentedTextWriterExtensions.cs
@@ -34,15 +34,9 @@ public static IDisposable Block(
bool writeClosingNewLine = false
)
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
- ArgumentNullException.ThrowIfNull( open );
- ArgumentNullException.ThrowIfNull( close );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
- PolyFillExceptionValidators.ThrowIfNull( open );
- PolyFillExceptionValidators.ThrowIfNull( close );
-#endif
+ Requires.NotNull( self );
+ Requires.NotNull( open );
+ Requires.NotNull( close );
if(leadingLine is not null)
{
@@ -81,11 +75,7 @@ public static IDisposable Block(
/// Disposable that when invoked, will reduce the indentation.
public static IDisposable PushIndent( this IndentedTextWriter self )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
++self.Indent;
@@ -99,11 +89,7 @@ public static IDisposable PushIndent( this IndentedTextWriter self )
/// Writer to apply extension method to
public static void WriteEmptyLine( this IndentedTextWriter self )
{
-#if NET6_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
self.WriteLineNoTabs( string.Empty );
}
diff --git a/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs b/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs
index 2669a9e..d58537d 100644
--- a/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs
+++ b/src/Ubiquity.NET.SrcGeneration/StringExtensions.cs
@@ -24,11 +24,7 @@ public static IEnumerable GetCommentLines(
StringSplitOptions2 options = StringSplitOptions2.TrimEntries
)
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
// For now, naive conversion - just splits on newlines
// more sophisticated implementation could split on word boundaries based on length...
@@ -47,11 +43,7 @@ public static IEnumerable GetCommentLines(
///
public static string EscapeComment( this string self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
// For now, the only escape is a newline "\\n"
#if NETSTANDARD2_0
@@ -83,11 +75,7 @@ public static string EscapeComment( this string self )
///
public static IEnumerable SplitLines( this string self, StringSplitOptions2 splitOptions = StringSplitOptions2.None )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
#if !NET5_0_OR_GREATER
// StringSplitOptions.TrimeEntries member is not available, do it the hard/slow way
@@ -115,11 +103,7 @@ public static IEnumerable SplitLines( this string self, StringSplitOptio
/// This will perform escaping of characters for XML such as conversion of `&` into `&` etc...
public static string MakeXmlSafe( this string self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
return new XText( self ).ToString();
}
@@ -129,11 +113,7 @@ public static string MakeXmlSafe( this string self )
/// Sequence of XML escaped strings
public static IEnumerable EscapeForXML( this IEnumerable self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
return from s in self
select MakeXmlSafe( s );
@@ -145,11 +125,7 @@ public static IEnumerable EscapeForXML( this IEnumerable self )
///
public static IEnumerable EscapeForComment( this IEnumerable self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
return from s in self
select EscapeComment( s );
@@ -170,11 +146,7 @@ public static IEnumerable EscapeForComment( this IEnumerable sel
///
public static IEnumerable SkipDuplicates( this IEnumerable self )
{
-#if NET8_0_OR_GREATER
- ArgumentNullException.ThrowIfNull( self );
-#else
- PolyFillExceptionValidators.ThrowIfNull( self );
-#endif
+ Requires.NotNull( self );
string? oldVal = null;
return self.Where( ( s ) =>
diff --git a/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj b/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj
index fda7b95..e2f5fac 100644
--- a/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj
+++ b/src/Ubiquity.NET.SrcGeneration/Ubiquity.NET.SrcGeneration.csproj
@@ -41,7 +41,4 @@
-
-
-
diff --git a/src/Ubiquity.NET.Utils.slnx b/src/Ubiquity.NET.Utils.slnx
index 409350d..faa638e 100644
--- a/src/Ubiquity.NET.Utils.slnx
+++ b/src/Ubiquity.NET.Utils.slnx
@@ -47,7 +47,6 @@
-