committed by
GitHub
101 changed files with 907 additions and 611 deletions
@ -1,281 +1,48 @@ |
|||||
using System; |
using System; |
||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||
using System.Diagnostics.CodeAnalysis; |
|
||||
using System.IO; |
using System.IO; |
||||
using System.Linq; |
|
||||
using System.Reflection; |
using System.Reflection; |
||||
#if !BUILDTASK
|
|
||||
using Avalonia.Platform.Internal; |
|
||||
using Avalonia.Utilities; |
|
||||
#endif
|
|
||||
|
|
||||
namespace Avalonia.Platform |
namespace Avalonia.Platform; |
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Loads assets compiled into the application binary.
|
|
||||
/// </summary>
|
|
||||
public class AssetLoader |
|
||||
#if !BUILDTASK
|
#if !BUILDTASK
|
||||
: IAssetLoader |
/// <inheritdoc cref="IAssetLoader"/>
|
||||
#endif
|
#endif
|
||||
{ |
public static class AssetLoader |
||||
|
{ |
||||
#if !BUILDTASK
|
#if !BUILDTASK
|
||||
private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver(); |
private static IAssetLoader GetAssetLoader() => AvaloniaLocator.Current.GetRequiredService<IAssetLoader>(); |
||||
|
|
||||
private AssemblyDescriptor? _defaultResmAssembly; |
|
||||
|
|
||||
/// <remarks>
|
|
||||
/// Introduced for tests.
|
|
||||
/// </remarks>
|
|
||||
internal static void SetAssemblyDescriptorResolver(IAssemblyDescriptorResolver resolver) => |
|
||||
s_assemblyDescriptorResolver = resolver; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="AssetLoader"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="assembly">
|
|
||||
/// The default assembly from which to load resm: assets for which no assembly is specified.
|
|
||||
/// </param>
|
|
||||
public AssetLoader(Assembly? assembly = null) |
|
||||
{ |
|
||||
if (assembly == null) |
|
||||
assembly = Assembly.GetEntryAssembly(); |
|
||||
if (assembly != null) |
|
||||
_defaultResmAssembly = new AssemblyDescriptor(assembly); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Sets the default assembly from which to load assets for which no assembly is specified.
|
|
||||
/// </summary>
|
|
||||
/// <param name="assembly">The default assembly.</param>
|
|
||||
public void SetDefaultAssembly(Assembly assembly) |
|
||||
{ |
|
||||
_defaultResmAssembly = new AssemblyDescriptor(assembly); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Checks if an asset with the specified URI exists.
|
|
||||
/// </summary>
|
|
||||
/// <param name="uri">The URI.</param>
|
|
||||
/// <param name="baseUri">
|
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
|
||||
/// </param>
|
|
||||
/// <returns>True if the asset could be found; otherwise false.</returns>
|
|
||||
public bool Exists(Uri uri, Uri? baseUri = null) |
|
||||
{ |
|
||||
return TryGetAsset(uri, baseUri, out _); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Opens the asset with the requested URI.
|
|
||||
/// </summary>
|
|
||||
/// <param name="uri">The URI.</param>
|
|
||||
/// <param name="baseUri">
|
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
|
||||
/// </param>
|
|
||||
/// <returns>A stream containing the asset contents.</returns>
|
|
||||
/// <exception cref="FileNotFoundException">
|
|
||||
/// The asset could not be found.
|
|
||||
/// </exception>
|
|
||||
public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Opens the asset with the requested URI and returns the asset stream and the
|
|
||||
/// assembly containing the asset.
|
|
||||
/// </summary>
|
|
||||
/// <param name="uri">The URI.</param>
|
|
||||
/// <param name="baseUri">
|
|
||||
/// A base URI to use if <paramref name="uri"/> is relative.
|
|
||||
/// </param>
|
|
||||
/// <returns>
|
|
||||
/// The stream containing the resource contents together with the assembly.
|
|
||||
/// </returns>
|
|
||||
/// <exception cref="FileNotFoundException">
|
|
||||
/// The asset could not be found.
|
|
||||
/// </exception>
|
|
||||
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) |
|
||||
{ |
|
||||
if (TryGetAsset(uri, baseUri, out var assetDescriptor)) |
|
||||
{ |
|
||||
return (assetDescriptor.GetStream(), assetDescriptor.Assembly); |
|
||||
} |
|
||||
|
|
||||
throw new FileNotFoundException($"The resource {uri} could not be found."); |
|
||||
} |
|
||||
|
|
||||
public Assembly? GetAssembly(Uri uri, Uri? baseUri) |
|
||||
{ |
|
||||
if (!uri.IsAbsoluteUri && baseUri != null) |
|
||||
{ |
|
||||
uri = new Uri(baseUri, uri); |
|
||||
} |
|
||||
|
|
||||
if (TryGetAssembly(uri, out var assemblyDescriptor)) |
|
||||
{ |
|
||||
return assemblyDescriptor.Assembly; |
|
||||
} |
|
||||
|
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets all assets of a folder and subfolders that match specified uri.
|
|
||||
/// </summary>
|
|
||||
/// <param name="uri">The URI.</param>
|
|
||||
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
|
|
||||
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
|
|
||||
public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri) |
|
||||
{ |
|
||||
if (uri.IsAbsoluteResm()) |
|
||||
{ |
|
||||
if (!TryGetAssembly(uri, out var assembly)) |
|
||||
{ |
|
||||
assembly = _defaultResmAssembly; |
|
||||
} |
|
||||
|
|
||||
return assembly?.Resources? |
|
||||
.Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) |
|
||||
.Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? |
|
||||
Enumerable.Empty<Uri>(); |
|
||||
} |
|
||||
|
|
||||
uri = uri.EnsureAbsolute(baseUri); |
|
||||
|
|
||||
if (uri.IsAvares()) |
|
||||
{ |
|
||||
if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) |
|
||||
{ |
|
||||
return Enumerable.Empty<Uri>(); |
|
||||
} |
|
||||
|
|
||||
if (assembly?.AvaloniaResources == null) |
/// <inheritdoc cref="IAssetLoader.SetDefaultAssembly"/>
|
||||
{ |
public static void SetDefaultAssembly(Assembly assembly) => GetAssetLoader().SetDefaultAssembly(assembly); |
||||
return Enumerable.Empty<Uri>(); |
|
||||
} |
|
||||
|
|
||||
if (path.Length > 0 && path[path.Length - 1] != '/') |
/// <inheritdoc cref="IAssetLoader.Exists"/>
|
||||
{ |
public static bool Exists(Uri uri, Uri? baseUri = null) => GetAssetLoader().Exists(uri, baseUri); |
||||
path += '/'; |
|
||||
} |
|
||||
|
|
||||
return assembly.AvaloniaResources |
/// <inheritdoc cref="IAssetLoader.Open"/>
|
||||
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) |
public static Stream Open(Uri uri, Uri? baseUri = null) => GetAssetLoader().Open(uri, baseUri); |
||||
.Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); |
|
||||
} |
|
||||
|
|
||||
return Enumerable.Empty<Uri>(); |
/// <inheritdoc cref="IAssetLoader.OpenAndGetAssembly"/>
|
||||
} |
public static (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) |
||||
|
=> GetAssetLoader().OpenAndGetAssembly(uri, baseUri); |
||||
|
|
||||
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) |
/// <inheritdoc cref="IAssetLoader.GetAssembly"/>
|
||||
{ |
public static Assembly? GetAssembly(Uri uri, Uri? baseUri = null) |
||||
assetDescriptor = null; |
=> GetAssetLoader().GetAssembly(uri, baseUri); |
||||
|
|
||||
if (uri.IsAbsoluteResm()) |
/// <inheritdoc cref="IAssetLoader.GetAssets"/>
|
||||
{ |
public static IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri) |
||||
if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) |
=> GetAssetLoader().GetAssets(uri, baseUri); |
||||
{ |
|
||||
assembly = _defaultResmAssembly; |
|
||||
} |
|
||||
|
|
||||
if (assembly?.Resources != null) |
|
||||
{ |
|
||||
var resourceKey = uri.AbsolutePath; |
|
||||
|
|
||||
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
uri = uri.EnsureAbsolute(baseUri); |
|
||||
|
|
||||
if (uri.IsAvares()) |
|
||||
{ |
|
||||
if (TryGetResAsmAndPath(uri, out var assembly, out var path)) |
|
||||
{ |
|
||||
if (assembly.AvaloniaResources == null) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) |
|
||||
{ |
|
||||
path = uri.GetUnescapeAbsolutePath(); |
|
||||
|
|
||||
if (TryLoadAssembly(uri.Authority, out assembly)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) |
|
||||
{ |
|
||||
assembly = null; |
|
||||
|
|
||||
if (uri != null) |
|
||||
{ |
|
||||
if (!uri.IsAbsoluteUri) |
|
||||
{ |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
if (uri.IsResm()) |
|
||||
{ |
|
||||
var assemblyName = uri.GetAssemblyNameFromQuery(); |
|
||||
|
|
||||
if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) |
|
||||
{ |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) |
|
||||
{ |
|
||||
assembly = null; |
|
||||
|
|
||||
try |
|
||||
{ |
|
||||
assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName); |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
catch (Exception) { } |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
#endif
|
#endif
|
||||
|
|
||||
public static void RegisterResUriParsers() |
internal static void RegisterResUriParsers() |
||||
{ |
{ |
||||
if (!UriParser.IsKnownScheme("avares")) |
if (!UriParser.IsKnownScheme("avares")) |
||||
UriParser.Register(new GenericUriParser( |
UriParser.Register(new GenericUriParser( |
||||
GenericUriParserOptions.GenericAuthority | |
GenericUriParserOptions.GenericAuthority | |
||||
GenericUriParserOptions.NoUserInfo | |
GenericUriParserOptions.NoUserInfo | |
||||
GenericUriParserOptions.NoPort | |
GenericUriParserOptions.NoPort | |
||||
GenericUriParserOptions.NoQuery | |
GenericUriParserOptions.NoQuery | |
||||
GenericUriParserOptions.NoFragment), "avares", -1); |
GenericUriParserOptions.NoFragment), "avares", -1); |
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,255 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.IO; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using Avalonia.Platform.Internal; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Platform; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Loads assets compiled into the application binary.
|
||||
|
/// </summary>
|
||||
|
internal class StandardAssetLoader : IAssetLoader |
||||
|
{ |
||||
|
private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; |
||||
|
private AssemblyDescriptor? _defaultResmAssembly; |
||||
|
|
||||
|
public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) |
||||
|
{ |
||||
|
if (assembly == null) |
||||
|
assembly = Assembly.GetEntryAssembly(); |
||||
|
if (assembly != null) |
||||
|
_defaultResmAssembly = new AssemblyDescriptor(assembly); |
||||
|
_assemblyDescriptorResolver = resolver; |
||||
|
} |
||||
|
|
||||
|
public StandardAssetLoader(Assembly? assembly = null) : this(new AssemblyDescriptorResolver(), assembly) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the default assembly from which to load assets for which no assembly is specified.
|
||||
|
/// </summary>
|
||||
|
/// <param name="assembly">The default assembly.</param>
|
||||
|
public void SetDefaultAssembly(Assembly assembly) |
||||
|
{ |
||||
|
_defaultResmAssembly = new AssemblyDescriptor(assembly); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Checks if an asset with the specified URI exists.
|
||||
|
/// </summary>
|
||||
|
/// <param name="uri">The URI.</param>
|
||||
|
/// <param name="baseUri">
|
||||
|
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
|
/// </param>
|
||||
|
/// <returns>True if the asset could be found; otherwise false.</returns>
|
||||
|
public bool Exists(Uri uri, Uri? baseUri = null) |
||||
|
{ |
||||
|
return TryGetAsset(uri, baseUri, out _); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Opens the asset with the requested URI.
|
||||
|
/// </summary>
|
||||
|
/// <param name="uri">The URI.</param>
|
||||
|
/// <param name="baseUri">
|
||||
|
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
|
/// </param>
|
||||
|
/// <returns>A stream containing the asset contents.</returns>
|
||||
|
/// <exception cref="FileNotFoundException">
|
||||
|
/// The asset could not be found.
|
||||
|
/// </exception>
|
||||
|
public Stream Open(Uri uri, Uri? baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Opens the asset with the requested URI and returns the asset stream and the
|
||||
|
/// assembly containing the asset.
|
||||
|
/// </summary>
|
||||
|
/// <param name="uri">The URI.</param>
|
||||
|
/// <param name="baseUri">
|
||||
|
/// A base URI to use if <paramref name="uri"/> is relative.
|
||||
|
/// </param>
|
||||
|
/// <returns>
|
||||
|
/// The stream containing the resource contents together with the assembly.
|
||||
|
/// </returns>
|
||||
|
/// <exception cref="FileNotFoundException">
|
||||
|
/// The asset could not be found.
|
||||
|
/// </exception>
|
||||
|
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) |
||||
|
{ |
||||
|
if (TryGetAsset(uri, baseUri, out var assetDescriptor)) |
||||
|
{ |
||||
|
return (assetDescriptor.GetStream(), assetDescriptor.Assembly); |
||||
|
} |
||||
|
|
||||
|
throw new FileNotFoundException($"The resource {uri} could not be found."); |
||||
|
} |
||||
|
|
||||
|
public Assembly? GetAssembly(Uri uri, Uri? baseUri) |
||||
|
{ |
||||
|
if (!uri.IsAbsoluteUri && baseUri != null) |
||||
|
{ |
||||
|
uri = new Uri(baseUri, uri); |
||||
|
} |
||||
|
|
||||
|
if (TryGetAssembly(uri, out var assemblyDescriptor)) |
||||
|
{ |
||||
|
return assemblyDescriptor.Assembly; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets all assets of a folder and subfolders that match specified uri.
|
||||
|
/// </summary>
|
||||
|
/// <param name="uri">The URI.</param>
|
||||
|
/// <param name="baseUri">Base URI that is used if <paramref name="uri"/> is relative.</param>
|
||||
|
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
|
||||
|
public IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri) |
||||
|
{ |
||||
|
if (uri.IsAbsoluteResm()) |
||||
|
{ |
||||
|
if (!TryGetAssembly(uri, out var assembly)) |
||||
|
{ |
||||
|
assembly = _defaultResmAssembly; |
||||
|
} |
||||
|
|
||||
|
return assembly?.Resources? |
||||
|
.Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath())) |
||||
|
.Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? |
||||
|
Enumerable.Empty<Uri>(); |
||||
|
} |
||||
|
|
||||
|
uri = uri.EnsureAbsolute(baseUri); |
||||
|
|
||||
|
if (uri.IsAvares()) |
||||
|
{ |
||||
|
if (!TryGetResAsmAndPath(uri, out var assembly, out var path)) |
||||
|
{ |
||||
|
return Enumerable.Empty<Uri>(); |
||||
|
} |
||||
|
|
||||
|
if (assembly?.AvaloniaResources == null) |
||||
|
{ |
||||
|
return Enumerable.Empty<Uri>(); |
||||
|
} |
||||
|
|
||||
|
if (path.Length > 0 && path[path.Length - 1] != '/') |
||||
|
{ |
||||
|
path += '/'; |
||||
|
} |
||||
|
|
||||
|
return assembly.AvaloniaResources |
||||
|
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) |
||||
|
.Select(x => new Uri($"avares://{assembly.Name}{x.Key}")); |
||||
|
} |
||||
|
|
||||
|
return Enumerable.Empty<Uri>(); |
||||
|
} |
||||
|
|
||||
|
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) |
||||
|
{ |
||||
|
assetDescriptor = null; |
||||
|
|
||||
|
if (uri.IsAbsoluteResm()) |
||||
|
{ |
||||
|
if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly)) |
||||
|
{ |
||||
|
assembly = _defaultResmAssembly; |
||||
|
} |
||||
|
|
||||
|
if (assembly?.Resources != null) |
||||
|
{ |
||||
|
var resourceKey = uri.AbsolutePath; |
||||
|
|
||||
|
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
uri = uri.EnsureAbsolute(baseUri); |
||||
|
|
||||
|
if (uri.IsAvares()) |
||||
|
{ |
||||
|
if (TryGetResAsmAndPath(uri, out var assembly, out var path)) |
||||
|
{ |
||||
|
if (assembly.AvaloniaResources == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path) |
||||
|
{ |
||||
|
path = uri.GetUnescapeAbsolutePath(); |
||||
|
|
||||
|
if (TryLoadAssembly(uri.Authority, out assembly)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) |
||||
|
{ |
||||
|
assembly = null; |
||||
|
|
||||
|
if (uri != null) |
||||
|
{ |
||||
|
if (!uri.IsAbsoluteUri) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
if (uri.IsResm()) |
||||
|
{ |
||||
|
var assemblyName = uri.GetAssemblyNameFromQuery(); |
||||
|
|
||||
|
if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly) |
||||
|
{ |
||||
|
assembly = null; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
assembly = _assemblyDescriptorResolver.GetAssembly(assemblyName); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
catch (Exception) { } |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<IncludeBuildOutput>false</IncludeBuildOutput> |
||||
|
<PackageId>Avalonia.Analyzers</PackageId> |
||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> |
||||
|
<IsPackable>true</IsPackable> |
||||
|
<IsRoslynComponent>true</IsRoslynComponent> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all"/> |
||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" PrivateAssets="all" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\..\build\TrimmingEnable.props" /> |
||||
|
<Import Project="..\..\..\build\NullableEnable.props" /> |
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
|
// attributes that are applied to this project.
|
||||
|
// Project-level suppressions either have no target or are given
|
||||
|
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
|
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
|
||||
|
[assembly: SuppressMessage("MicrosoftCodeAnalysisReleaseTracking", "RS2008:Enable analyzer release tracking")] |
||||
@ -0,0 +1,59 @@ |
|||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using Microsoft.CodeAnalysis; |
||||
|
using Microsoft.CodeAnalysis.CSharp; |
||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax; |
||||
|
using Microsoft.CodeAnalysis.Diagnostics; |
||||
|
|
||||
|
namespace Avalonia.Analyzers; |
||||
|
|
||||
|
[DiagnosticAnalyzer(LanguageNames.CSharp)] |
||||
|
public class OnPropertyChangedOverrideAnalyzer : DiagnosticAnalyzer |
||||
|
{ |
||||
|
public const string DiagnosticId = "AVA2001"; |
||||
|
|
||||
|
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( |
||||
|
DiagnosticId, |
||||
|
"Missing invoke base.OnPropertyChanged", |
||||
|
"Method '{0}' do not invoke base.{0}", |
||||
|
"Potential issue", |
||||
|
DiagnosticSeverity.Warning, |
||||
|
isEnabledByDefault: true, |
||||
|
description: "The OnPropertyChanged of the base class was not invoked in the override method declaration, which could lead to unwanted behavior."); |
||||
|
|
||||
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); |
||||
|
|
||||
|
public override void Initialize(AnalysisContext context) |
||||
|
{ |
||||
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); |
||||
|
context.EnableConcurrentExecution(); |
||||
|
context.RegisterSyntaxNodeAction(AnalyzeMethod, SyntaxKind.MethodDeclaration); |
||||
|
} |
||||
|
|
||||
|
private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) |
||||
|
{ |
||||
|
var method = (MethodDeclarationSyntax)context.Node; |
||||
|
if (context.SemanticModel.GetDeclaredSymbol(method, context.CancellationToken) is IMethodSymbol currentMethod |
||||
|
&& currentMethod.Name == "OnPropertyChanged" |
||||
|
&& currentMethod.OverriddenMethod is IMethodSymbol originalMethod) |
||||
|
{ |
||||
|
var baseInvocations = method.Body?.DescendantNodes().OfType<BaseExpressionSyntax>(); |
||||
|
if (baseInvocations?.Any() == true) |
||||
|
{ |
||||
|
foreach (var baseInvocation in baseInvocations) |
||||
|
{ |
||||
|
if (baseInvocation.Parent is SyntaxNode parent) |
||||
|
{ |
||||
|
var targetSymbol = context.SemanticModel.GetSymbolInfo(parent, context.CancellationToken); |
||||
|
if (SymbolEqualityComparer.Default.Equals(targetSymbol.Symbol, originalMethod)) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
context.ReportDiagnostic(Diagnostic.Create(Rule, currentMethod.Locations[0], currentMethod.Name)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,17 +0,0 @@ |
|||||
<Project Sdk="Microsoft.NET.Sdk"> |
|
||||
|
|
||||
<PropertyGroup> |
|
||||
<TargetFramework>netstandard2.0</TargetFramework> |
|
||||
<Nullable>enable</Nullable> |
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> |
|
||||
</PropertyGroup> |
|
||||
|
|
||||
<ItemGroup> |
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4"> |
|
||||
<PrivateAssets>all</PrivateAssets> |
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|
||||
</PackageReference> |
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" /> |
|
||||
</ItemGroup> |
|
||||
|
|
||||
</Project> |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue