diff --git a/.gitignore b/.gitignore index 45b73ea..b1001c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +src/ClassExplorer/Generated/ + # Project specific files tools/dotnet tools/opencover diff --git a/ClassExplorer.build.ps1 b/ClassExplorer.build.ps1 index 6f2eb49..bc09f76 100644 --- a/ClassExplorer.build.ps1 +++ b/ClassExplorer.build.ps1 @@ -18,29 +18,7 @@ $testModuleManifestSplat = @{ } $manifest = Test-ModuleManifest @testModuleManifestSplat - -$script:Settings = @{ - Name = $moduleName - Manifest = $manifest - Version = $manifest.Version - ShouldTest = $true -} - -$script:Folders = @{ - PowerShell = "$PSScriptRoot\module" - CSharp = "$PSScriptRoot\src" - Build = '{0}\src\{1}\bin\{2}' -f $PSScriptRoot, $moduleName, $Configuration - Release = '{0}\Release\{1}\{2}' -f $PSScriptRoot, $moduleName, $manifest.Version - Docs = "$PSScriptRoot\docs" - Test = "$PSScriptRoot\test" - Results = "$PSScriptRoot\testresults" -} - -$script:Discovery = @{ - HasDocs = Test-Path ('{0}\{1}\*.md' -f $Folders.Docs, $PSCulture) - HasTests = Test-Path ('{0}\*.Tests.ps1' -f $Folders.Test) - IsUnix = $PSVersionTable.PSEdition -eq "Core" -and -not $IsWindows -} +$moduleVersion = $manifest.Version $tools = "$PSScriptRoot\tools" $script:GetDotNet = Get-Command $tools\GetDotNet.ps1 @@ -48,12 +26,88 @@ $script:AssertModule = Get-Command $tools\AssertRequiredModule.ps1 $script:GetOpenCover = Get-Command $tools\GetOpenCover.ps1 $script:GenerateSignatureMarkdown = Get-Command $tools\GenerateSignatureMarkdown.ps1 +function RemakeFolder { + [CmdletBinding()] + param( + [ValidateNotNullOrEmpty()] + [string] $LiteralPath + ) + end { + $ErrorActionPreference = 'Stop' + if (Test-Path -LiteralPath $LiteralPath) { + Remove-Item -LiteralPath $LiteralPath -Recurse + } + + $null = New-Item -ItemType Directory -Path $LiteralPath + } +} + +function GetArtifactPath { + [CmdletBinding()] + param( + [ValidateNotNullOrEmpty()] + [string] $FileName, + + [switch] $Legacy + ) + end { + $moduleName = $script:ModuleName + $config = $script:Configuration + $legacyTarget = $script:LegacyTarget + $modernTarget = $script:ModernTarget + + $target = $modernTarget + if ($Legacy) { + $target = $legacyTarget + } + + if (-not $FileName) { + return "./artifacts/publish/$moduleName/${config}_${target}" + } + + return "./artifacts/publish/$moduleName/${config}_${target}/$FileName" + } +} + +task GetProjectInfo { + $script:ModernTarget = $null + $script:LegacyTarget = $null + if (Test-Path -LiteralPath ./Directory.Build.props) { + $content = Get-Content -Raw -LiteralPath ./Directory.Build.props + if ($content -match '(?[^<]+)') { + $script:ModernTarget = $matches['target'] + } + + if ($content -match '(?[^<]+)') { + $script:LegacyTarget = $matches['target'] + } + } + + + $script:ModuleName = $ModuleName = 'ClassExplorer' + $testModuleManifestSplat = @{ + ErrorAction = 'Ignore' + WarningAction = 'Ignore' + Path = "./module/$ModuleName.psd1" + } + + $manifest = Test-ModuleManifest @testModuleManifestSplat + $script:ModuleVersion = $manifest.Version + $script:_IsWindows = $true + $runtimeInfoType = 'System.Runtime.InteropServices.RuntimeInformation' -as [type] + try { + if ($null -ne $runtimeInfoType) { + $script:_IsWindows = $runtimeInfoType::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) + } + } catch { } +} + task AssertDotNet { - $script:dotnet = & $GetDotNet -Unix:$Discovery.IsUnix + $script:dotnet = & $GetDotNet -Unix:(-not $script:_IsWindows) } task AssertOpenCover -If { $GenerateCodeCoverage.IsPresent } { - if ($Discovery.IsUnix) { + if (-not $script:_IsWindows) { Write-Warning 'Generating code coverage from .NET core is currently unsupported, disabling code coverage generation.' $script:GenerateCodeCoverage = $false return @@ -63,66 +117,77 @@ task AssertOpenCover -If { $GenerateCodeCoverage.IsPresent } { } task AssertRequiredModules { - & $AssertModule Pester 5.3.0 -Force:$Force.IsPresent - & $AssertModule InvokeBuild 5.8.4 -Force:$Force.IsPresent + & $AssertModule Pester 5.7.1 -Force:$Force.IsPresent + & $AssertModule InvokeBuild 5.14.22 -Force:$Force.IsPresent & $AssertModule platyPS 0.14.2 -Force:$Force.IsPresent - & $AssertModule Yayaml 0.1.1 -Force:$Force.IsPresent + & $AssertModule Yayaml 0.7.0 -Force:$Force.IsPresent } task AssertDevDependencies -Jobs AssertDotNet, AssertOpenCover, AssertRequiredModules task Clean { - if ($PSScriptRoot -and (Test-Path $PSScriptRoot\Release)) { - Remove-Item $PSScriptRoot\Release -Recurse - } - - $null = New-Item $Folders.Release -ItemType Directory - if (Test-Path $Folders.Results) { - Remove-Item $Folders.Results -Recurse - } - - $null = New-Item $Folders.Results -ItemType Directory + RemakeFolder ./Release + RemakeFolder ./testresults & $dotnet clean --verbosity quiet -nologo } -task BuildDocs -If { $Discovery.HasDocs } { - $sourceDocs = "$PSScriptRoot\docs\$PSCulture" - $releaseDocs = '{0}\{1}' -f $Folders.Release, $PSCulture +task BuildDocs -If { Test-Path ./docs/$PSCulture/*.md } { + $releaseDocs = "./Release/ClassExplorer/$moduleVersion" + $null = New-Item $releaseDocs/$PSCulture -ItemType Directory -Force -ErrorAction Ignore + $null = New-ExternalHelp -Path ./docs/$PSCulture -OutputPath $releaseDocs/$PSCulture - $null = New-Item $releaseDocs -ItemType Directory -Force -ErrorAction SilentlyContinue - $null = New-ExternalHelp -Path $sourceDocs -OutputPath $releaseDocs - - & $GenerateSignatureMarkdown.Source -AboutHelp $releaseDocs\about_Type_Signatures.help.txt - & $GenerateSignatureMarkdown.Source $PSScriptRoot\docs\en-US\about_Type_Signatures.help.md + & $GenerateSignatureMarkdown.Source -AboutHelp $releaseDocs/about_Type_Signatures.help.txt + & $GenerateSignatureMarkdown.Source ./docs/en-US/about_Type_Signatures.help.md } task BuildDll { - if (-not $Discovery.IsUnix) { - & $dotnet publish --configuration $Configuration --framework net471 --verbosity quiet -nologo + if ($script:_IsWindows) { + & $dotnet publish --configuration $Configuration --framework $script:LegacyTarget --verbosity quiet -nologo } - & $dotnet publish --configuration $Configuration --framework netcoreapp3.1 --verbosity quiet -nologo + + & $dotnet publish --configuration $Configuration --framework $script:ModernTarget --verbosity quiet -nologo } task CopyToRelease { - $powershellSource = '{0}\*' -f $Folders.PowerShell - $release = $Folders.Release - $releaseDesktopBin = "$release\bin\Desktop" - $releaseCoreBin = "$release\bin\Core" - $sourceDesktopBin = '{0}\net471\publish\*' -f $Folders.Build - $sourceCoreBin = '{0}\netcoreapp3.1\publish\*' -f $Folders.Build - Copy-Item -Path $powershellSource -Destination $release -Recurse -Force + $version = $script:ModuleVersion + $modern = $script:ModernTarget + $legacy = $script:LegacyTarget - if (-not $Discovery.IsUnix) { - $null = New-Item $releaseDesktopBin -Force -ItemType Directory - Copy-Item -Path $sourceDesktopBin -Destination $releaseDesktopBin -Force + $releasePath = "./Release/ClassExplorer/$version" + if (-not (Test-Path -LiteralPath $releasePath)) { + $null = New-Item $releasePath -ItemType Directory } - $null = New-Item $releaseCoreBin -Force -ItemType Directory - Copy-Item -Path $sourceCoreBin -Destination $releaseCoreBin -Force + Copy-Item -Path ./module/* -Destination $releasePath -Recurse -Force + + if ($script:_IsWindows) { + $null = New-Item $releasePath/bin/Legacy -Force -ItemType Directory + $legacyFiles = ( + 'ClassExplorer.dll', + 'ClassExplorer.pdb', + 'System.Buffers.dll', + 'System.Collections.Immutable.dll', + 'System.Memory.dll', + 'System.Numerics.Vectors.dll', + 'System.Runtime.CompilerServices.Unsafe.dll') + + foreach ($file in $legacyFiles) { + Copy-Item -Force -LiteralPath ./artifacts/publish/ClassExplorer/${Configuration}_$legacy/$file -Destination $releasePath/bin/Legacy + } + } + + $null = New-Item $releasePath/bin/Modern -Force -ItemType Directory + $modernFiles = ( + 'ClassExplorer.dll', + 'ClassExplorer.pdb', + 'ClassExplorer.deps.json') + foreach ($file in $modernFiles) { + Copy-Item -Force -LiteralPath ./artifacts/publish/ClassExplorer/${Configuration}_$modern/$file -Destination $releasePath/bin/Modern + } } -task DoTest -If { $Discovery.HasTests -and $Settings.ShouldTest } { - if ($Discovery.IsUnix) { +task DoTest -If { Test-Path ./test/*.ps1 } { + if (-not $script:_IsWindows) { $scriptString = ' $projectPath = "{0}" Invoke-Pester "$projectPath" -OutputFormat NUnitXml -OutputFile "$projectPath\testresults\pester.xml" @@ -141,16 +206,16 @@ task DoTest -If { $Discovery.HasTests -and $Settings.ShouldTest } { $scriptString)) $powershellCommand = 'powershell' - if ($Discovery.IsUnix) { + if ($PSVersionTable.PSVersion.Major -gt 5) { $powershellCommand = 'pwsh' } - $powershell = (Get-Command $powershellCommand).Source + $powershell = (Get-Command -CommandType Application $powershellCommand).Source if ($GenerateCodeCoverage.IsPresent) { # OpenCover needs full pdb's. I'm very open to suggestions for streamlining this... # & $dotnet clean - & $dotnet publish --configuration $Configuration --framework net471 --verbosity quiet -nologo /p:DebugType=Full + & $dotnet publish --configuration $Configuration --framework $script:LegacyTarget --verbosity quiet --nologo /p:DebugType=Full $moduleName = $Settings.Name $release = '{0}\bin\Desktop\{1}' -f $Folders.Release, $moduleName @@ -212,7 +277,7 @@ task DoPublish { Publish-Module -Name $Folders.Release -NuGetApiKey $apiKey -Force:$Force.IsPresent } -task Build -Jobs AssertDevDependencies, Clean, BuildDll, CopyToRelease, BuildDocs +task Build -Jobs GetProjectInfo, AssertDevDependencies, Clean, BuildDll, CopyToRelease, BuildDocs task Test -Jobs Build, DoTest diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..76d7987 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + net8.0 + net471 + $(MSBuildThisFileDirectory)artifacts + + + diff --git a/module/ClassExplorer.psm1 b/module/ClassExplorer.psm1 index 8d66c13..d16ac31 100644 --- a/module/ClassExplorer.psm1 +++ b/module/ClassExplorer.psm1 @@ -1,7 +1,7 @@ if (-not $PSVersionTable.PSEdition -or $PSVersionTable.PSEdition -eq 'Desktop') { - Import-Module "$PSScriptRoot/bin/Desktop/ClassExplorer.dll" + Import-Module "$PSScriptRoot/bin/Legacy/ClassExplorer.dll" } else { - Import-Module "$PSScriptRoot/bin/Core/ClassExplorer.dll" + Import-Module "$PSScriptRoot/bin/Modern/ClassExplorer.dll" } if (-not $env:CLASS_EXPLORER_TRUE_CHARACTER) { diff --git a/src/ClassExplorer/ClassExplorer.csproj b/src/ClassExplorer/ClassExplorer.csproj index 195af23..a2359c4 100644 --- a/src/ClassExplorer/ClassExplorer.csproj +++ b/src/ClassExplorer/ClassExplorer.csproj @@ -1,41 +1,52 @@ - netcoreapp3.1;net471 - + $(ModernTarget);$(LegacyTarget) + preview true enable - + true + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - + + - - - ResXFileCodeGenerator - $(IntermediateOutputPath)\SR.Designer.cs - SR.Designer.cs + true + true + false CSharp ClassExplorer SR - - - + + + true + Generated + + + + + + diff --git a/src/ClassExplorer/IsExternalInit.cs b/src/ClassExplorer/IsExternalInit.cs deleted file mode 100644 index 7fecf64..0000000 --- a/src/ClassExplorer/IsExternalInit.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - internal sealed class IsExternalInit - { - } -} diff --git a/src/ClassExplorer/Nullable.cs b/src/ClassExplorer/Nullable.cs deleted file mode 100644 index 97c2603..0000000 --- a/src/ClassExplorer/Nullable.cs +++ /dev/null @@ -1,140 +0,0 @@ -#if !NETCOREAPP - -namespace System.Diagnostics.CodeAnalysis -{ - /// Specifies that null is allowed as an input even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] - internal sealed class AllowNullAttribute : Attribute { } - - /// Specifies that null is disallowed as an input even if the corresponding type allows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] - internal sealed class DisallowNullAttribute : Attribute { } - - /// Specifies that an output may be null even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - internal sealed class MaybeNullAttribute : Attribute { } - - /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - internal sealed class NotNullAttribute : Attribute { } - - /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class MaybeNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter may be null. - /// - public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } - } - - /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class NotNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } - } - - /// Specifies that the output will be non-null if the named parameter is non-null. - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] - internal sealed class NotNullIfNotNullAttribute : Attribute - { - /// Initializes the attribute with the associated parameter name. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - /// - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// Gets the associated parameter name. - public string ParameterName { get; } - } - - /// Applied to a method that will never return under any circumstance. - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - internal sealed class DoesNotReturnAttribute : Attribute { } - - /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] - internal sealed class DoesNotReturnIfAttribute : Attribute - { - /// Initializes the attribute with the specified parameter value. - /// - /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to - /// the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - - /// Gets the condition parameter value. - public bool ParameterValue { get; } - } - - /// Specifies that the method or property will ensure that the listed field and property members have not-null values. - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] - internal sealed class MemberNotNullAttribute : Attribute - { - /// Initializes the attribute with a field or property member. - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullAttribute(string member) => Members = new[] { member }; - - /// Initializes the attribute with the list of field and property members. - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullAttribute(params string[] members) => Members = members; - - /// Gets field or property member names. - public string[] Members { get; } - } - - /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] - internal sealed class MemberNotNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = new[] { member }; - } - - /// Initializes the attribute with the specified return value condition and list of field and property members. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } - - /// Gets field or property member names. - public string[] Members { get; } - } -} - -#endif diff --git a/src/ClassExplorer/Range.cs b/src/ClassExplorer/Range.cs deleted file mode 100644 index b0f74c4..0000000 --- a/src/ClassExplorer/Range.cs +++ /dev/null @@ -1,114 +0,0 @@ -#if NETFRAMEWORK - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace System -{ - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// - internal readonly struct Range : IEquatable - { - /// Represent the inclusive start index of the Range. - public Index Start { get; } - - /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals([NotNullWhen(true)] object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() - { - return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); - } - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { - return Start.ToString() + ".." + End.ToString(); - } - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start; - Index startIndex = Start; - if (startIndex.IsFromEnd) - start = length - startIndex.Value; - else - start = startIndex.Value; - - int end; - Index endIndex = End; - if (endIndex.IsFromEnd) - end = length - endIndex.Value; - else - end = endIndex.Value; - - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); - } - - return (start, end - start); - } - - private static class ExceptionArgument - { - public const string length = nameof(length); - } - - private static class ThrowHelper - { - [DoesNotReturn, MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRangeException(string parameterName) - { - throw new ArgumentOutOfRangeException(parameterName); - } - } - } -} - -#endif diff --git a/tools/AssertRequiredModule.ps1 b/tools/AssertRequiredModule.ps1 index fef8139..7acdaa1 100644 --- a/tools/AssertRequiredModule.ps1 +++ b/tools/AssertRequiredModule.ps1 @@ -41,7 +41,7 @@ end { # TODO: Install required versions into the tools folder try { - Import-Module @importModuleSplat -Force + Import-Module @importModuleSplat -Force -ErrorAction Stop } catch [System.IO.FileNotFoundException] { Install-Module $Name -MinimumVersion $RequiredVersion -AllowPrerelease:$Prerelease -Force:$Force -Scope CurrentUser Import-Module @importModuleSplat -Force