Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions docfx/CodeAnalysis/api/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# About
Ubiquity.NET.CodeAnalysis contains general extensions for .NET.
Ubiquity.NET.CodeAnlysis.Utils contains support for Roslyn components doing code analysis,
fixes, and source generation within the Roslyn compiler.

## Key support
TBD

* Support for caching of generation scan results (via `IEquatable<T>`)
* Debug diagnostic asserts for class vs. struct trade-offs
* Capturing symbol information in a cacheable fashion
* Generation of diagnostics for issues detected in a generator<a href="#footnote_1"><sup>1</sup></a>
* Create a `SourceText` from a `StringBuilder` to allow generation to remain independent of
the Roslyn CodeAnalysis types.

------
<sup id="footnote_1">1</sup> Generators creating diagnostics is generally not recommended.
The official
[Incremental Generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md)
cook book recommends against it. A generator should generally ignore invalid input and fail
silently by ignoring the problem. An analyzer can produce the diagnostics for problems. At
most a generator can use this support to report critical problems that prevent the generator
from running at all.
22 changes: 21 additions & 1 deletion docfx/CodeAnalysis/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# About
Ubiquity.NET.CodeAnlysis.Utils contains support for CodeAnalysis
The Ubiquity.NET.CodeAnalysis.Utils package contains support for building Roslyn analyzers,
fixers, and source generator. As such it specifically targets .NET Standard 2.0 and has no
dependencies on anything targeting a later runtime. If an analyzer (or any Roslyn extension)
depends on this package the dependencies for the package must be included in the extension's
package.

## Key support

* Support for caching of generation scan results (via `IEquatable<T>`)
* Debug diagnostic asserts for class vs. struct trade-offs
* Capturing symbol information in a cacheable fashion
* Generation of diagnostics for issues detected in a generator<a href="#footnote_1"><sup>1</sup></a>
* Create a `SourceText` from a `StringBuilder` to allow generation to remain independent of
the Roslyn CodeAnalysis types.

------
<sup id="footnote_1">1</sup> Generators creating diagnostics is generally not recommended.
The official
[Incremental Generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md)
cook book recommends against it. A generator should normally ignore invalid input and fail
silently by ignoring the problem. An analyzer can produce the diagnostics for problems. At
most a generator can use this support to report critical problems that prevent the generator
from running at all.
1 change: 1 addition & 0 deletions docfx/IgnoredWords.dic
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
antlr
arity
bool
cacheable
initializer
interop
marshalling
Expand Down
9 changes: 8 additions & 1 deletion docfx/documentation.msbuildproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,19 @@
</ItemGroup>

<ItemGroup>
<!--Everything in the SrcGeneration sub-folders except the API folder as that contains generated files -->
<!--Everything in the SrcGeneration test utilities sub-folders except the API folder as that contains generated files -->
<None Include="src-gen-test-utils/**" Exclude="src-gen-test-utils/api/**" />
<!-- Explicitly call out the non-generated files in the API folder-->
<None Include="src-gen-test-utils/api/index.md" />
</ItemGroup>

<ItemGroup>
<!--Everything in the CodeAnalysis utilities sub-folders except the API folder as that contains generated files -->
<None Include="CodeAnalysis/**" Exclude="CodeAnalysis/api/**" />
<!-- Explicitly call out the non-generated files in the API folder-->
<None Include="CodeAnalysis/api/index.md" />
</ItemGroup>

<Target Name="AlwaysRun" BeforeTargets="AfterBuild">
<Message Importance="High" Text="NOTE: Building $(MSBuildProjectFile) does NOTHING, docs are built using the docfx tool. This project is simply a convenient placeholder for organizing/editing files" />
</Target>
Expand Down
4 changes: 1 addition & 3 deletions src/Ubiquity.NET.ANTLR.Utils/LocationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,10 @@ public static SourceRange GetSourceRange( this IToken token )
{
ArgumentNullException.ThrowIfNull( token );

// TODO: Q: Should this account for a newline in the token?
// A: Probably not, as a token can't span a newline.
return new SourceRange(
new(token.Line, token.Column, token.StartIndex),
new(token.Line, token.Column + token.Text.Length, token.StopIndex)
);
);
}
}
}
2 changes: 1 addition & 1 deletion src/Ubiquity.NET.ANTLR.Utils/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This library provides general extensions to ANTLR including adapter bindings for
`Ubiquity.NET.Runtime.IParseErrorListener`.
- This allows building consumers that deal with errors and remain independent of the
parsing technology.
* Extension functions that provides commonly used support for ANTLR
* Extension functions that provide commonly used support for ANTLR
- Get a character interval from a ParserRuleContext with support for the standard EOF
rule.
- Gets the source stream from an IRecognizer
Expand Down
6 changes: 3 additions & 3 deletions src/Ubiquity.NET.CodeAnalysis.Utils/DebugAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ namespace Ubiquity.NET.CodeAnalysis.Utils
/// <summary>Utility class to support Debug asserts</summary>
public static class DebugAssert
{
/// <summary>Tests if a structure size is < 16 bytes and generates a debug assertion if not</summary>
/// <summary>Tests if a structure size is &lt; 16 bytes and generates a debug assertion if not</summary>
/// <typeparam name="T">Type of the struct to test</typeparam>
/// <remarks>
/// <para>This uses a runtime debug assert as it isn't possible to know the size at compile time of a managed struct.
/// The `sizeof` doesn't apply for anything with a managed reference or a native pointer sized member
/// as such sizes depend on the actual runtime used.</para>
/// <note type="important">
/// This function ONLY operates in a debug build. That is, this is the compiler will elide calls to this method
/// at the call site unless the "DEBUG" symbol is defined as it has a <see cref="ConditionalAttribute"/> attached to it.
/// This function ONLY operates in a debug build. That is, the compiler will elide calls to this method
/// at the <em><b>call site</b></em> unless the "DEBUG" symbol is defined as it has a <see cref="ConditionalAttribute"/> attached to it.
/// </note>
/// </remarks>
/// <seealso href="https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct"/>
Expand Down
2 changes: 1 addition & 1 deletion src/Ubiquity.NET.CodeAnalysis.Utils/DiagnosticInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public override bool Equals( object obj )
/// <inheritdoc/>
public override int GetHashCode( )
{
// sadly this will re-hash the hashcode computed for the structure, but there is no way
// sadly this will re-hash the hash code computed for the structure, but there is no way
// to combine the result of a hash with other things. (The overload of Add(int) is private)
// The generic Add<T>() will call the type's GetHashCode() and ignores the implementation of
// IStructuralEquatable.
Expand Down
6 changes: 4 additions & 2 deletions src/Ubiquity.NET.CodeAnalysis.Utils/EquatableAttributeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public EquatableAttributeData( AttributeData data )

/// <summary>Gets the constant for a named argument</summary>
/// <param name="argName">Name of the argument to fetch</param>
/// <returns>Optional value for the named argument (<see cref="Optional.HasValue"/> is false if <paramref name="argName"/> isn't provided)</returns>
/// <returns>Optional value for the named argument (<see cref="Optional{T}.HasValue"/> is false if <paramref name="argName"/> isn't provided)</returns>
public Optional<StructurallyEquatableTypedConstant> GetNamedArgValue( string argName )
{
return NamedArguments.TryGetValue( argName, out StructurallyEquatableTypedConstant typedConst )
Expand Down Expand Up @@ -76,7 +76,7 @@ public Optional<T> GetNamedArgValue<T>( string name )
/// <param name="argName">Name of the attribute argument</param>
/// <returns><see cref="Optional{T}"/> for the array of values</returns>
/// <remarks>
/// The <paramref name="name"/> name may not be specified in which case the result
/// The <paramref name="argName"/> name may not be specified in which case the result
/// will have not value (<see cref="Optional{T}.HasValue"/> is false). It is also
/// possible that it was specified AND that the value is null (if T is a nullable type
/// or a default instance if it is not.) Thus it is important to examine the return
Expand Down Expand Up @@ -139,6 +139,8 @@ public override bool Equals( object obj )
&& Equals( other );
}

/// <summary>Implicit cast from <see cref="AttributeData"/></summary>
/// <param name="data">Dat to convert</param>
[SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Implicit cast for public constructor" )]
public static implicit operator EquatableAttributeData( AttributeData data )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public override int GetHashCode( )

/// <summary>Gets a sequence of the names of all values.</summary>
/// <remarks>
/// These names are keys for the values used in <see cref="this[]"/> and
/// These names are keys for the values used in <see cref="this[NamespaceQualifiedName]"/> and
/// <see cref="TryGetValue(NamespaceQualifiedName, out EquatableAttributeData)"/>.
/// </remarks>
public IEnumerable<NamespaceQualifiedName> Keys => InnerDictionary.Keys;
Expand All @@ -104,6 +104,8 @@ public bool TryGetValue( NamespaceQualifiedName key, [MaybeNullWhen( false )] ou
return InnerDictionary.TryGetValue( key, out item );
}

/// <summary>Implicit cast from <see cref="ImmutableArray{T}"/></summary>
/// <param name="attributes">Immutable array of attribute data to form a keyed collection from</param>
[SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Simple wrapper over public constructor" )]
public static implicit operator EquatableAttributeDataCollection( ImmutableArray<EquatableAttributeData> attributes )
{
Expand Down
12 changes: 12 additions & 0 deletions src/Ubiquity.NET.CodeAnalysis.Utils/EquatableDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,19 @@ public bool Equals( EquatableDictionary<TKey, TValue> other )
return ((IStructuralEquatable)this).Equals( other, EqualityComparer<KeyValuePair<TKey, TValue>>.Default);
}

/// <summary>Test two instances for equality</summary>
/// <param name="left">Left side of the comparison</param>
/// <param name="right">Right side of the comparison</param>
/// <returns>True if the values are equal and false if not</returns>
public static bool operator ==( EquatableDictionary<TKey, TValue> left, EquatableDictionary<TKey, TValue> right )
{
return left.Equals( right );
}

/// <summary>Test two instances for inequality</summary>
/// <param name="left">Left side of the comparison</param>
/// <param name="right">Right side of the comparison</param>
/// <returns>False if the values are not equal and true if not</returns>
public static bool operator !=( EquatableDictionary<TKey, TValue> left, EquatableDictionary<TKey, TValue> right )
{
return !(left == right);
Expand Down Expand Up @@ -206,12 +214,16 @@ public bool TryGetValue( TKey key, out TValue value )

#endregion

/// <summary>Implicit cast from <see cref="ImmutableDictionary{TKey, TValue}"/></summary>
/// <param name="dictionaryToWrap">Dictionary to wrap</param>
[SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Implicit cast for public constructor" )]
public static implicit operator EquatableDictionary<TKey, TValue>( ImmutableDictionary<TKey, TValue> dictionaryToWrap )
{
return new(dictionaryToWrap);
}

/// <summary>Implicit cast from <see cref="ImmutableSortedDictionary{TKey, TValue}"/></summary>
/// <param name="dictionaryToWrap">Dictionary to wrap</param>
[SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Implicit cast for public constructor" )]
public static implicit operator EquatableDictionary<TKey, TValue>( ImmutableSortedDictionary<TKey, TValue> dictionaryToWrap )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public virtual string ToString( string format, IFormatProvider? formatProvider )
: customFormatter.Format(format, this, formatProvider);
}

/// <summary>Implicit conversion to a string (Shorthand for calling <see cref="ToString"/></summary>
/// <summary>Implicit conversion to a string (Shorthand for calling <see cref="ToString()"/></summary>
/// <param name="self">Name to convert</param>
public static implicit operator string( NamespaceQualifiedName self )
{
Expand Down
55 changes: 55 additions & 0 deletions src/Ubiquity.NET.CodeAnalysis.Utils/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,58 @@ and source generator. As such it specifically targets .NET Standard 2.0 and has
dependencies on anything targeting a later runtime. If an analyzer (or any Roslyn extension)
depends on this package the dependencies for the package must be included in the extension's
package.

## Key support

* Support for caching of generation scan results (via `IEquatable<T>`)
* Debug diagnostic asserts for class vs. struct trade-offs
* Capturing symbol information in a cacheable fashion
* Generation of diagnostics for issues detected in a generator<a href="#footnote_1"><sup>1</sup></a>
* Create a `SourceText` from a `StringBuilder` to allow generation to remain independent of
the Roslyn CodeAnalysis types.

## Dependencies
| Name | Version | Description |
|------|---------|-------------|
| Microsoft.Bcl.HashCode | 6.0.0 | Poly fill library for HashCode support |
| Microsoft.CodeAnalysis.Analyzers | 4.14.0 | Roslyn Analyzer library |
| Microsoft.CodeAnalysis.CSharp | 5.0.0 | Roslyn library specifically for C# |

Microsoft.CodeAnalysis.* is assumed already present for a Roslyn component and therefor is
not required in the package.

Full documentation of the support provided by this package is available in the online
[documentation](https://ubiquitydotnet.github.io/Ubiquity.NET.Utils/CodeAnalysis/index.html).

### Example of use

``` XML
<Project Sdk="Microsoft.NET.Sdk">

<!-- ... -->

<PackageReference Include="Microsoft.Bcl.HashCode" PrivateAssets="all" GeneratePathProperty="true">
<__AssemblyPath>$(PkgMicrosoft_Bcl_HashCode)\lib\$(TargetFramework)\*.dll</__AssemblyPath>
</PackageReference>

<Target Name="GetDependencyTargetPaths" BeforeTargets="GetTargetPath">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Condition="'%(PackageReference.__AssemblyPath)' != ''" Include="%(PackageReference.__AssemblyPath)" IncludeRuntimeDependency="false" />
</ItemGroup>

<!-- Pack all PackageReferences with _AssemblyPath metadata -->
<ItemGroup>
<None Condition="'%(PackageReference.__AssemblyPath)' != ''" Include="%(PackageReference.__AssemblyPath)" Visible="false" Pack="true" PackagePath="analyzers/dotnet/cs" />
</ItemGroup>
</Target>
</project>
```

------
<sup id="footnote_1">1</sup> Generators creating diagnostics is generally not recommended.
The official
[Incremental Generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.cookbook.md)
cook book recommends against it. A generator should normally ignore invalid input and fail
silently by ignoring the problem. An analyzer can produce the diagnostics for problems. At
most a generator can use this support to report critical problems that prevent the generator
from running at all.
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,19 @@ public override int GetHashCode( )
return StructuralTypedConstantComparer.Default.GetHashCode(InnerConst);
}

/// <summary>Test two instances for equality</summary>
/// <param name="left">Left side of the comparison</param>
/// <param name="right">Right side of the comparison</param>
/// <returns>True if the values are equal and false if not</returns>
public static bool operator ==( StructurallyEquatableTypedConstant left, StructurallyEquatableTypedConstant right )
{
return left.Equals(right);
}

/// <summary>Test two instances for inequality</summary>
/// <param name="left">Left side of the comparison</param>
/// <param name="right">Right side of the comparison</param>
/// <returns>False if the values are not equal and true if not</returns>
public static bool operator !=( StructurallyEquatableTypedConstant left, StructurallyEquatableTypedConstant right )
{
return !(left == right);
Expand All @@ -87,12 +95,16 @@ public TypedConstant ToTypedConstant()

private readonly TypedConstant InnerConst;

/// <summary>Implicit cast from <see cref="TypedConstant"/></summary>
/// <param name="other">Constant to cast</param>
[SuppressMessage( "Usage", "CA2225:Operator overloads have named alternates", Justification = "Simple alternate for existing constructor" )]
public static implicit operator StructurallyEquatableTypedConstant( TypedConstant other )
{
return new( other );
}

/// <summary>Implicit cast from <see cref="StructurallyEquatableTypedConstant"/></summary>
/// <param name="other">Constant to cast</param>
public static explicit operator TypedConstant( StructurallyEquatableTypedConstant other )
{
return other.ToTypedConstant();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
errors in most cases, but sometimes can end up as runtime errors! BEWARE!
-->
<LangVersion>12</LangVersion>
<GenerateDocumentationFile>True</GenerateDocumentationFile>

<!--NuGet packaging support -->
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
Expand All @@ -26,7 +27,6 @@
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>

</PropertyGroup>

<ItemGroup>
Expand Down
5 changes: 1 addition & 4 deletions src/Ubiquity.NET.CommandLine.SrcGen/CommandLineAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ private static void OnProperty( SymbolAnalysisContext context )
handler( context, propSymbol, attrib.GetLocation(), attribs );
}
}

// TODO: WARN if type of property is nullable AND marked as required. That won't produce an error but probably
// not the behavior intended...
}

private static void OnOptionAttribute(
Expand Down Expand Up @@ -204,7 +201,7 @@ string typeConstraintName
context.ReportDiagnostic( Diagnostics.MissingConstraintAttribute( attribLoc, typeConstraintName ) );
}

// TODO: validate an Argument attribute or Option attribute
// TODO: validate an Argument attribute OR Option attribute
}

/// <summary>Verifies a property has an expected type</summary>
Expand Down
Loading
Loading