From 090e96e0ec6beb8ed2be973ff5b46ecef821a46b Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Sat, 29 Aug 2026 09:11:48 +0200 Subject: [PATCH] Apply Rider Safe Cleanup across the codebase Removes unused usings, reformats wrapped members, and arranges qualifiers/braces per the Safe Cleanup profile in fallout.slnx.DotSettings. No behavior or public API changes. --- build/Build.CI.GitHubActions.cs | 47 +- build/Build.CodeGeneration.cs | 7 +- build/Build.Contributors.cs | 14 +- build/Build.GlobalSolution.cs | 11 +- build/Build.Licenses.cs | 29 +- build/Build.PublicApi.cs | 12 +- build/Build.Stargazers.cs | 30 +- build/Build.Terminal.cs | 4 +- build/Build.cs | 80 ++- fallout.slnx.DotSettings | 20 +- src/Fallout.Build.Shared/CompletionUtility.cs | 31 +- src/Fallout.Build.Shared/Constants.cs | 20 +- src/Fallout.Build.Shared/LegacyEnvironment.cs | 10 + src/Fallout.Build.Shared/Notifications.cs | 9 + .../ArgumentsFromGitCommitMessageAttribute.cs | 10 +- ...ShutdownDotNetAfterServerBuildAttribute.cs | 2 - ...sualStudioEnvironmentVariablesAttribute.cs | 1 - .../Attributes/VerbosityMappingAttribute.cs | 23 +- .../BuildServerConfigurationGeneration.cs | 3 - ...verConfigurationGenerationAttributeBase.cs | 4 +- src/Fallout.Build/CICD/CIAttribute.cs | 1 - .../CICD/ChainedConfigurationAttributeBase.cs | 25 +- .../CICD/ConfigurationAttributeBase.cs | 17 +- src/Fallout.Build/CICD/ConfigurationEntity.cs | 2 - src/Fallout.Build/CICD/CustomFileWriter.cs | 1 - ...erateBuildServerConfigurationsAttribute.cs | 4 +- src/Fallout.Build/CICD/IBuildServer.cs | 3 +- .../CICD/IConfigurationGenerator.cs | 7 +- ...dServerConfigurationGenerationAttribute.cs | 11 + src/Fallout.Build/CICD/NoConvertAttribute.cs | 1 - src/Fallout.Build/CICD/Partition.cs | 19 +- src/Fallout.Build/CICD/PartitionAttribute.cs | 10 +- src/Fallout.Build/ControlFlow.cs | 18 +- src/Fallout.Build/Execution/BuildContext.cs | 2 + src/Fallout.Build/Execution/BuildExecutor.cs | 30 +- src/Fallout.Build/Execution/BuildManager.cs | 8 +- .../Execution/DelegateRequirementService.cs | 17 +- .../Execution/ExecutableTarget.cs | 25 + .../Execution/ExecutableTargetFactory.cs | 60 +- .../Execution/ExecutionPlanner.cs | 7 +- .../Execution/ExecutionStatus.cs | 3 +- .../BuildExtensionAttributeBase.cs | 2 +- .../ToolInjectionAttributeBase.cs | 4 +- .../ValueInjectionAttributeBase.cs | 8 +- .../ArgumentsFromParametersFileAttribute.cs | 31 +- .../Execution/Extensions/EventInvoker.cs | 4 +- .../Extensions/HandleHelpRequestsAttribute.cs | 17 +- .../Extensions/HandlePlanRequestsAttribute.cs | 34 +- ...dleReSharperSurrogateArgumentsAttribute.cs | 2 + .../HandleShellCompletionAttribute.cs | 15 +- .../HandleVisualStudioDebuggingAttribute.cs | 4 +- .../InjectNonParameterValuesAttribute.cs | 6 + .../InjectParameterValuesAttribute.cs | 4 +- .../SerializeBuildGraphAttribute.cs | 3 +- .../SerializeBuildServerStateAttribute.cs | 5 +- .../Extensions/UpdateNotificationAttribute.cs | 3 +- .../Execution/ParameterService.Statics.cs | 17 +- .../Execution/ParameterService.cs | 22 +- .../Execution/TargetDefinition.cs | 69 ++- .../Execution/TargetExecutionException.cs | 1 - .../Execution/ToolRequirementService.cs | 55 +- .../Execution/ValueInjectionUtility.cs | 28 +- src/Fallout.Build/FalloutBuild.Events.cs | 4 +- src/Fallout.Build/FalloutBuild.Interface.cs | 16 + src/Fallout.Build/FalloutBuild.Output.cs | 24 +- src/Fallout.Build/FalloutBuild.Statics.cs | 16 +- src/Fallout.Build/FalloutBuild.cs | 29 +- src/Fallout.Build/Host.Theming.cs | 4 +- src/Fallout.Build/Host.cs | 10 + src/Fallout.Build/IFalloutBuild.cs | 29 +- src/Fallout.Build/ITargetDefinition.cs | 9 +- src/Fallout.Build/LevelMapping.cs | 1 - src/Fallout.Build/LogLevel.cs | 3 - src/Fallout.Build/Logging.cs | 51 +- src/Fallout.Build/ParameterAttribute.cs | 3 +- src/Fallout.Build/RequiresAttribute.cs | 1 - src/Fallout.Build/Target.cs | 1 - src/Fallout.Build/Terminal.cs | 1 - .../Theming/AnsiConsoleHostTheme.cs | 18 +- src/Fallout.Build/Theming/IHostTheme.cs | 5 +- .../Theming/SystemConsoleHostTheme.cs | 77 ++- src/Fallout.Build/Utilities/ConsoleUtility.cs | 25 +- .../Utilities/CredentialStore.cs | 9 + src/Fallout.Build/Utilities/IConsole.cs | 3 + src/Fallout.Build/Utilities/SchemaUtility.cs | 171 +++++- src/Fallout.Build/Utilities/SystemConsole.cs | 1 - src/Fallout.Build/VCS/GitRepository.cs | 52 +- .../VCS/GitRepositoryExtensions.cs | 4 +- src/Fallout.Build/Verbosity.cs | 3 - src/Fallout.Cli/BuildProjectResolver.cs | 5 +- src/Fallout.Cli/BuildScaffolder.cs | 23 +- src/Fallout.Cli/CakeConverter.cs | 49 +- src/Fallout.Cli/CommandDispatcher.cs | 8 +- src/Fallout.Cli/Commands/AddPackageCommand.cs | 23 +- src/Fallout.Cli/Commands/CakeCleanCommand.cs | 2 + .../Commands/CakeConvertCommand.cs | 9 +- src/Fallout.Cli/Commands/CompleteCommand.cs | 9 +- .../Commands/GetConfigurationCommand.cs | 1 - .../Navigation/GetNextDirectoryCommand.cs | 1 - .../Commands/Navigation/NavigationSession.cs | 7 +- .../Navigation/PopDirectoryCommand.cs | 2 +- .../PushWithCurrentRootDirectoryCommand.cs | 3 +- .../PushWithParentRootDirectoryCommand.cs | 1 - src/Fallout.Cli/Commands/RunCommand.cs | 2 + src/Fallout.Cli/Commands/SecretsCommand.cs | 19 +- src/Fallout.Cli/Commands/SetupCommand.cs | 34 +- src/Fallout.Cli/Commands/TriggerCommand.cs | 3 +- src/Fallout.Cli/Commands/UpdateCommand.cs | 32 +- src/Fallout.Cli/PackageManager.cs | 4 +- src/Fallout.Cli/Program.cs | 6 +- .../Prompts/SpectreConsolePrompts.cs | 3 +- .../Rewriting/Cake/AbsolutePathRewriter.cs | 61 +- .../Rewriting/Cake/ClassRewriter.cs | 53 +- .../Rewriting/Cake/CodeAnalysisExtensions.cs | 15 +- .../Rewriting/Cake/FormattingRewriter.cs | 27 +- .../Rewriting/Cake/IdentifierNameRewriter.cs | 8 +- .../Rewriting/Cake/InvocationRewriter.cs | 21 +- .../Rewriting/Cake/MemberAccessRewriter.cs | 6 +- .../Rewriting/Cake/ParameterRewriter.cs | 26 +- .../Rewriting/Cake/RegularFieldRewriter.cs | 8 +- .../Cake/RemoveUsingDirectivesRewriter.cs | 4 +- .../Cake/RenameFieldIdentifierRewriter.cs | 5 +- .../Rewriting/Cake/SafeSyntaxRewriter.cs | 3 +- .../Cake/TargetDefinitionRewriter.cs | 14 +- .../Rewriting/Cake/ToolInvocationRewriter.cs | 62 +- .../CheckPathEnvironmentVariableAttribute.cs | 4 +- .../FileSystemGlobbingAttributeBase.cs | 3 +- .../Attributes/GitRepositoryAttribute.cs | 4 +- .../Attributes/GlobbingOptionsAttribute.cs | 2 - .../HandleSingleFileExecutionAttribute.cs | 19 +- .../LatestGitHubReleaseAttribute.cs | 9 +- .../Attributes/LatestMavenVersionAttribute.cs | 6 +- .../Attributes/LatestMyGetVersionAttribute.cs | 3 +- .../Attributes/LatestNpmVersionAttribute.cs | 6 +- .../Attributes/LatestNuGetVersionAttribute.cs | 11 +- .../Attributes/LocalPathAttribute.cs | 2 +- .../Attributes/NpmPackageAttribute.cs | 4 +- .../Attributes/NuGetPackageAttribute.cs | 1 - .../Attributes/PathVariableAttribute.cs | 1 - .../Attributes/SolutionAttribute.cs | 8 +- src/Fallout.Common/CI/AppVeyor/AppVeyor.cs | 41 +- .../CI/AppVeyor/AppVeyorAttribute.cs | 89 +-- .../CI/AppVeyor/AppVeyorImage.cs | 45 +- .../CI/AppVeyor/AppVeyorSecretAttribute.cs | 12 +- .../CI/AppVeyor/AppVeyorService.cs | 3 - .../Configuration/AppVeyorBranches.cs | 3 +- .../Configuration/AppVeyorConfiguration.cs | 36 +- .../AppVeyorCustomWriterExtensions.cs | 1 - .../AzurePipelines/AzurePipelines.Theming.cs | 2 + .../CI/AzurePipelines/AzurePipelines.cs | 51 +- .../AzurePipelines/AzurePipelinesAttribute.cs | 174 +++--- .../AzurePipelinesBuildReason.cs | 5 +- .../AzurePipelinesCachePaths.cs | 5 +- .../AzurePipelinesCodeCoverageToolType.cs | 5 +- .../CI/AzurePipelines/AzurePipelinesImage.cs | 41 +- .../AzurePipelines/AzurePipelinesIssueType.cs | 5 +- .../AzurePipelines/AzurePipelinesJobStatus.cs | 5 +- .../AzurePipelinesRepositoryType.cs | 5 +- .../AzurePipelinesTestResultsType.cs | 3 - .../AzurePipelineCheckoutStep.cs | 3 + .../Configuration/AzurePipelinesCacheStep.cs | 6 +- .../Configuration/AzurePipelinesCmdStep.cs | 9 +- .../AzurePipelinesCustomWriterExtensions.cs | 1 - .../AzurePipelinesDownloadStep.cs | 5 +- .../Configuration/AzurePipelinesJob.cs | 6 +- .../AzurePipelinesPublishStep.cs | 5 +- .../Configuration/AzurePipelinesStage.cs | 5 +- .../Configuration/AzurePipelinesStep.cs | 5 +- .../AzurePipelinesVcsPushTrigger.cs | 12 + src/Fallout.Common/CI/Bamboo/Bamboo.cs | 19 + src/Fallout.Common/CI/Bitbucket/Bitbucket.cs | 5 +- src/Fallout.Common/CI/Bitrise/Bitrise.cs | 26 +- .../GitHubActionsActionReference.cs | 8 + .../GitHubActionsArtifactStep.cs | 6 +- .../Configuration/GitHubActionsCacheStep.cs | 8 +- .../GitHubActionsCheckoutStep.cs | 27 +- .../GitHubActionsConfiguration.cs | 12 +- .../Configuration/GitHubActionsCustomStep.cs | 44 +- .../GitHubActionsDetailedTrigger.cs | 3 - .../Configuration/GitHubActionsJob.cs | 13 +- .../Configuration/GitHubActionsRunStep.cs | 1 + .../GitHubActionsScheduledTrigger.cs | 2 - .../Configuration/GitHubActionsStep.cs | 3 - .../GitHubActionsStepPipeline.cs | 18 +- .../Configuration/GitHubActionsVcsTrigger.cs | 7 +- .../GitHubActionsWorkflowDispatchInput.cs | 5 + .../GitHubActionsWorkflowDispatchTrigger.cs | 27 +- .../CI/GitHubActions/GitHubActions.Client.cs | 5 +- .../CI/GitHubActions/GitHubActions.Theming.cs | 2 + .../CI/GitHubActions/GitHubActions.cs | 11 + .../GitHubActions/GitHubActionsAttribute.cs | 294 +++++++--- .../CI/GitHubActions/GitHubActionsDefaults.cs | 3 - .../CI/GitHubActions/GitHubActionsImage.cs | 59 +- .../GitHubActionsInputAttribute.cs | 6 + .../GitHubActions/GitHubActionsInputType.cs | 19 +- .../GitHubActions/GitHubActionsPermissions.cs | 53 +- .../GitHubActions/GitHubActionsSubmodules.cs | 3 - .../CI/GitHubActions/GitHubActionsTrigger.cs | 13 +- src/Fallout.Common/CI/GitLab/GitLab.cs | 40 +- .../CI/GitLab/GitLabProjectVisibility.cs | 5 +- src/Fallout.Common/CI/Jenkins/Jenkins.cs | 5 +- .../SpaceAutomationConfiguration.cs | 5 + .../Configuration/SpaceAutomationContainer.cs | 11 +- .../SpaceAutomationCronScheduleTrigger.cs | 4 +- .../SpaceAutomationCustomWriterExtensions.cs | 4 + .../SpaceAutomationPushTrigger.cs | 11 +- .../Configuration/SpaceAutomationResources.cs | 12 +- .../Configuration/SpaceAutomationTrigger.cs | 5 +- .../CI/SpaceAutomation/SpaceAutomation.cs | 19 +- .../SpaceAutomationAttribute.cs | 90 +-- .../TeamCityArtifactDependency.cs | 3 +- .../Configuration/TeamCityBuildType.cs | 35 +- .../Configuration/TeamCityBuildTypeVcsRoot.cs | 9 +- .../Configuration/TeamCityConfiguration.cs | 3 +- .../TeamCityConfigurationParameter.cs | 12 +- .../TeamCityCustomWriterExtensions.cs | 4 + .../Configuration/TeamCityDependency.cs | 3 - .../TeamCityDependencyFailureAction.cs | 3 - .../TeamCityFinishBuildTrigger.cs | 2 - .../TeamCityKeyValueParameter.cs | 3 +- .../Configuration/TeamCityParameter.cs | 3 - .../Configuration/TeamCityParameterDisplay.cs | 3 - .../Configuration/TeamCityParameterType.cs | 3 - .../TeamCity/Configuration/TeamCityProject.cs | 11 +- .../Configuration/TeamCityScheduledTrigger.cs | 8 +- .../TeamCitySnapshotDependency.cs | 4 +- .../TeamCity/Configuration/TeamCityTrigger.cs | 3 - .../TeamCity/Configuration/TeamCityVcsRoot.cs | 2 - .../Configuration/TeamCityVcsTrigger.cs | 3 +- .../TeamCity/DotNetBuildSettingsExtensions.cs | 3 +- .../TeamCity/DotNetTestSettingsExtensions.cs | 5 +- .../CI/TeamCity/MSBuildSettingsExtensions.cs | 2 - .../CI/TeamCity/TeamCity.Theming.cs | 3 + src/Fallout.Common/CI/TeamCity/TeamCity.cs | 50 +- .../CI/TeamCity/TeamCityAttribute.cs | 238 +++++--- .../CI/TeamCity/TeamCityImportTool.cs | 5 +- .../CI/TeamCity/TeamCityImportType.cs | 5 +- .../TeamCity/TeamCityNoDataPublishedAction.cs | 3 - .../CI/TeamCity/TeamCityStatus.cs | 2 - .../CI/TeamCity/TeamCityTokenAttribute.cs | 2 +- src/Fallout.Common/CI/TravisCI/TravisCI.cs | 29 +- .../CI/TravisCI/TravisCIEventType.cs | 5 +- src/Fallout.Common/ChangeLog/ChangeLog.cs | 2 +- .../ChangeLog/ChangeLogTasks.cs | 25 +- src/Fallout.Common/ChangeLog/ReleaseNotes.cs | 1 - src/Fallout.Common/Gitter/GitterTasks.cs | 6 +- src/Fallout.Common/IO/FtpTasks.cs | 3 + src/Fallout.Common/IO/HttpTasks.cs | 7 +- src/Fallout.Common/IO/TextTasks.cs | 3 - src/Fallout.Common/IO/XmlElementBuilder.cs | 7 + src/Fallout.Common/IO/XmlTasks.cs | 2 + .../ProjectModel/TransitionShims.cs | 8 +- .../Tools/AzureKeyVault/AzureKeyVault.cs | 114 ++-- .../AzureKeyVault/AzureKeyVaultAttribute.cs | 17 +- .../AzureKeyVaultAttributeBase.cs | 64 +- .../AzureKeyVault/AzureKeyVaultCertificate.cs | 19 +- .../AzureKeyVaultCertificateAttribute.cs | 34 +- .../AzureKeyVaultConfigurationAttribute.cs | 103 ++-- .../Tools/AzureKeyVault/AzureKeyVaultKey.cs | 16 +- .../AzureKeyVaultKeyAttribute.cs | 24 +- .../AzureKeyVaultSecretAttribute.cs | 26 +- .../Tools/AzureKeyVault/AzureKeyVaultTasks.cs | 68 +-- .../Tools/CorFlags/CorFlagsSettings.cs | 4 +- .../Coverlet/CoverletSettingsExtensions.cs | 4 +- .../DockerTargetDefinitionExtensions.cs | 23 +- .../Tools/Docker/DockerTasks.cs | 4 +- .../TeamCitySetDotCoverHomePathAttribute.cs | 2 - .../Tools/DotNet/DotNetRuntimeIdentifier.cs | 201 +++++-- .../Tools/DotNet/DotNetTasks.cs | 14 +- src/Fallout.Common/Tools/Git/GitTasks.cs | 3 +- .../Tools/GitHub/GitHubTasks.cs | 36 +- .../Tools/GitVersion/GitVersionAttribute.cs | 16 +- .../Tools/GitVersion/GitVersionTasks.cs | 11 +- .../GitVersion/NumberToStringJsonConverter.cs | 4 +- .../Tools/InnoSetup/InnoSetupSettings.cs | 5 +- .../Tools/MSBuild/MSBuildPlatform.cs | 3 - .../Tools/MSBuild/MSBuildProject.cs | 4 +- .../MSBuild/MSBuildSettingsExtensions.cs | 3 - .../Tools/MSBuild/MSBuildTasks.cs | 17 +- .../Tools/MSBuild/MSBuildToolPathResolver.cs | 39 +- .../Tools/MSBuild/MSBuildVersion.cs | 3 - src/Fallout.Common/Tools/MSpec/MSpecTasks.cs | 4 +- .../Tools/Mastodon/MastodonTasks.cs | 1 + .../Tools/MinVer/MinVerAttribute.cs | 5 +- .../Tools/MinVer/MinVerTasks.cs | 15 +- .../NerdbankGitVersioningAttribute.cs | 1 - .../NerdbankGitVersioningTasks.cs | 4 +- .../Tools/Netlify/NetlifyTasks.cs | 1 - src/Fallout.Common/Tools/Npm/NpmTasks.cs | 3 +- .../Tools/OctoVersion/OctoVersionAttribute.cs | 6 +- .../Tools/OctoVersion/OctoVersionTasks.cs | 5 +- .../Tools/PowerShell/PowerShellTasks.cs | 3 +- .../Tools/Pulumi/PulumiTasks.cs | 3 +- .../Tools/SignPath/SignPathTasks.cs | 62 +- src/Fallout.Common/Tools/Slack/SlackTasks.cs | 8 +- .../Tools/Teams/Teams.Extensions.cs | 1 + src/Fallout.Common/Tools/Teams/TeamsTasks.cs | 4 +- .../Tools/Twitter/TwitterTasks.cs | 20 +- .../Tools/Unity/Logging/BlockMatcher.cs | 11 +- .../Tools/Unity/Logging/FileWatcher.cs | 23 +- .../Tools/Unity/Logging/LineMatcher.cs | 5 +- .../Tools/Unity/Logging/LogParser.cs | 5 +- .../Tools/Unity/Logging/MatchType.cs | 5 +- .../Tools/Unity/Logging/MatchedBlock.cs | 7 +- .../Tools/Unity/Logging/UnityLogLevel.cs | 5 +- src/Fallout.Common/Tools/Unity/UnityTasks.cs | 14 +- .../Tools/VSWhere/VSWhereTasks.cs | 3 +- .../Tools/Xunit/Xunit2SettingsExtensions.cs | 1 - src/Fallout.Common/Tools/Xunit/XunitTasks.cs | 7 +- .../Utilities/TemplateUtility.cs | 27 +- src/Fallout.Components/Configuration.cs | 15 +- src/Fallout.Components/ICompile.cs | 7 +- .../ICreateGitHubRelease.cs | 21 +- src/Fallout.Components/IGlobalTool.cs | 3 +- src/Fallout.Components/IHasArtifacts.cs | 4 +- src/Fallout.Components/IHasChangelog.cs | 7 +- src/Fallout.Components/IHasConfiguration.cs | 9 +- src/Fallout.Components/IHasGitRepository.cs | 8 +- src/Fallout.Components/IHasGitVersion.cs | 4 +- .../IHasNerdbankGitVersioning.cs | 8 +- src/Fallout.Components/IHasReports.cs | 4 +- src/Fallout.Components/IHasSolution.cs | 8 +- .../IHasTwitterCredentials.cs | 25 +- src/Fallout.Components/IPack.cs | 4 +- src/Fallout.Components/IPublish.cs | 27 +- src/Fallout.Components/IReportCoverage.cs | 10 +- src/Fallout.Components/IRestore.cs | 4 +- src/Fallout.Components/ISignPackages.cs | 14 +- src/Fallout.Components/ITest.cs | 11 +- src/Fallout.Components/PublishTarget.cs | 5 +- .../Graph/StronglyConnectedComponentFinder.cs | 7 +- src/Fallout.Core/Planning/TopoSort.cs | 7 + .../CodeGenerationTask.cs | 7 +- src/Fallout.MSBuildTasks/ContextAwareTask.cs | 16 +- .../EmbedPackagesForSelfContainedTask.cs | 8 +- .../PackPackageToolsTask.cs | 10 +- .../TaskItemExtensions.cs | 1 - .../NukeMigrationAnalyzer.cs | 35 +- .../NukeMigrationCodeFix.cs | 11 +- .../Steps/CleanupBootstrapScriptsStep.cs | 6 +- .../Steps/ResolveFalloutVersionStep.cs | 6 +- .../Steps/RewriteCsprojsStep.cs | 23 +- .../Steps/VerifyBuildTargetFrameworkStep.cs | 4 +- .../Project.GetMSBuildProject.cs | 2 - src/Fallout.ProjectModel/Project.Items.cs | 8 +- src/Fallout.ProjectModel/Project.Misc.cs | 7 +- .../Project.Properties.cs | 4 +- src/Fallout.ProjectModel/ProjectModelTasks.cs | 29 +- .../CodeAnalysisExtensions.cs | 5 +- .../Shims/FalloutBuild.cs | 5 +- .../StronglyTypedSolutionGenerator.cs | 12 +- .../TransitionShimGenerator.cs | 389 ++++++++++--- .../CodeGenerator.cs | 18 +- .../Generators/DataClassExtensionGenerator.cs | 26 +- .../Generators/DataClassGenerator.cs | 63 +- .../Generators/EnumerationGenerator.cs | 3 +- .../Generators/ModelExtensions.cs | 1 - .../Generators/StringExtensions.cs | 5 +- .../Generators/TaskGenerator.cs | 83 +-- .../Generators/ToolGenerator.cs | 31 +- .../Generators/WriterExtensions.cs | 34 +- .../Model/DataClass.cs | 2 - .../Model/Enumeration.cs | 2 - .../Model/IDeprecatable.cs | 8 +- .../Model/Property.cs | 5 +- .../Model/RegexPatterns.cs | 5 +- src/Fallout.Tooling.Generator/Model/Task.cs | 2 - src/Fallout.Tooling.Generator/Model/Tool.cs | 26 +- .../ReferenceUpdater.cs | 16 +- .../ToolSerializer.cs | 25 +- .../Writers/DataClassWriter.cs | 5 +- .../Writers/IWriter.cs | 1 - .../Writers/IWriterWrapper.cs | 3 - .../Writers/TaskWriter.cs | 3 +- .../Writers/ToolWriter.cs | 2 +- .../Writers/WriterExtensions.cs | 18 + src/Fallout.Tooling/ArgumentStringHandler.cs | 1 - src/Fallout.Tooling/Configure.cs | 11 +- src/Fallout.Tooling/DelegateHelper.cs | 10 +- src/Fallout.Tooling/Enumeration.cs | 12 +- .../EnumerationJsonConverter.cs | 9 +- src/Fallout.Tooling/IProcess.cs | 1 - src/Fallout.Tooling/NpmToolPathResolver.cs | 6 +- src/Fallout.Tooling/NuGetPackageResolver.cs | 72 ++- src/Fallout.Tooling/NuGetToolPathResolver.cs | 30 +- src/Fallout.Tooling/NuGetVersionResolver.cs | 3 +- src/Fallout.Tooling/Options.Combinatorial.cs | 6 +- src/Fallout.Tooling/Options.Modify.cs | 4 +- src/Fallout.Tooling/Options.cs | 28 +- src/Fallout.Tooling/Output.cs | 5 +- src/Fallout.Tooling/OutputType.cs | 5 +- src/Fallout.Tooling/PaketPackageResolver.cs | 2 - src/Fallout.Tooling/Process2.cs | 6 +- src/Fallout.Tooling/ProcessExtensions.cs | 4 + src/Fallout.Tooling/ProcessTasks.cs | 70 ++- src/Fallout.Tooling/Tool.cs | 1 - src/Fallout.Tooling/ToolExecutor.cs | 1 - src/Fallout.Tooling/ToolOptions.Arguments.cs | 110 +++- src/Fallout.Tooling/ToolOptions.Properties.cs | 146 +++-- src/Fallout.Tooling/ToolPathResolver.cs | 17 +- src/Fallout.Tooling/ToolRequirement.cs | 14 +- src/Fallout.Tooling/ToolResolver.cs | 5 +- src/Fallout.Tooling/ToolTasks.ExitHandler.cs | 8 +- src/Fallout.Tooling/ToolTasks.Logger.cs | 9 + src/Fallout.Tooling/ToolTasks.Run.cs | 5 +- src/Fallout.Tooling/ToolTasks.ToolPath.cs | 15 +- src/Fallout.Tooling/ToolTasks.cs | 6 +- src/Fallout.Tooling/ToolingExtensions.cs | 10 + .../CompressionExtensions.cs | 21 +- src/Fallout.Utilities.IO.Globbing/Globbing.cs | 22 +- .../GlobbingCaseSensitivity.cs | 3 - .../HttpClient.Request.cs | 7 +- .../HttpRequest.Authentication.cs | 3 +- .../HttpRequest.Content.cs | 5 +- .../HttpResponse.Assert.cs | 11 +- .../HttpResponse.Body.cs | 3 +- src/Fallout.Utilities/ArgumentParser.cs | 47 +- src/Fallout.Utilities/Assert.cs | 59 +- src/Fallout.Utilities/AsyncHelper.cs | 1 - .../Collections/Array.Deconstruct.cs | 3 +- .../Collections/Dictionary.AddDictionary.cs | 6 + .../Collections/Dictionary.AddKeyValue.cs | 4 +- .../Collections/Dictionary.AsReadOnly.cs | 1 - .../Collections/Dictionary.GetOrDefault.cs | 1 - .../Collections/Dictionary.SetKeyValue.cs | 4 +- .../Collections/Dictionary.ToGeneric.cs | 9 +- .../Collections/Enumerable.AsEnumerable.cs | 7 +- .../Collections/Enumerable.Concat.cs | 10 +- .../Collections/Enumerable.Distinct.cs | 15 +- .../Collections/Enumerable.ForEach.cs | 8 +- .../Collections/Enumerable.Random.cs | 5 +- .../Collections/Enumerable.Sequence.cs | 3 +- .../Collections/Enumerable.ToDictionary.cs | 6 +- .../Collections/Enumerable.TopologicalSort.cs | 13 +- .../Collections/Lookup.ToLookupTable.cs | 6 +- .../Collections/LookupTable.cs | 2 + .../Collections/Object.DescendantsAndSelf.cs | 14 +- .../Converters/AbsolutePathJsonConverter.cs | 2 + src/Fallout.Utilities/DelegateDisposable.cs | 1 - .../Disposable.CombineWith.cs | 1 - .../EnvironmentInfo.Platform.cs | 12 +- .../EnvironmentInfo.SpecialFolder.cs | 3 +- .../EnvironmentInfo.Variables.cs | 5 +- src/Fallout.Utilities/EnvironmentInfo.cs | 2 +- .../IO/AbsolutePath.Children.cs | 13 +- .../IO/AbsolutePath.Create.cs | 5 +- .../IO/AbsolutePath.Delete.cs | 4 + .../IO/AbsolutePath.Exists.cs | 8 +- src/Fallout.Utilities/IO/AbsolutePath.Find.cs | 1 - src/Fallout.Utilities/IO/AbsolutePath.Hash.cs | 3 +- src/Fallout.Utilities/IO/AbsolutePath.Misc.cs | 6 +- .../IO/AbsolutePath.MoveCopy.cs | 36 +- .../IO/AbsolutePath.ReadWrite.cs | 7 +- src/Fallout.Utilities/IO/AbsolutePath.cs | 16 +- .../IO/IAbsolutePathHolder.cs | 3 - src/Fallout.Utilities/IO/PathConstruction.cs | 77 ++- src/Fallout.Utilities/IO/RelativePath.cs | 5 +- src/Fallout.Utilities/IO/UnixRelativePath.cs | 1 - src/Fallout.Utilities/IO/WinRelativePath.cs | 1 - src/Fallout.Utilities/Lazy.cs | 1 - src/Fallout.Utilities/Object.Apply.cs | 1 - src/Fallout.Utilities/Object.Clone.cs | 4 +- src/Fallout.Utilities/Object.When.cs | 1 - .../Reflection/Assembly.Versioning.cs | 2 - .../ReflectionUtility.Conversion.cs | 17 +- .../ReflectionUtility.DisplayText.cs | 46 +- .../ReflectionUtility.Expressions.cs | 7 +- .../ReflectionUtility.Invocation.cs | 13 +- .../Reflection/ReflectionUtility.cs | 20 +- .../Security/EncryptionUtility.cs | 41 +- ...xcludeAssemblyFromCodeCoverageAttribute.cs | 4 +- src/Fallout.Utilities/Shims/IsExternalInit.cs | 1 + src/Fallout.Utilities/Task.WaitAll.cs | 5 +- .../Text/String.Emptiness.cs | 1 - src/Fallout.Utilities/Text/String.Escape.cs | 5 +- src/Fallout.Utilities/Text/String.GetHash.cs | 1 - src/Fallout.Utilities/Text/String.Indent.cs | 1 - src/Fallout.Utilities/Text/String.Join.cs | 8 +- .../Text/String.KnownWords.cs | 2 +- .../Text/String.PrependAppend.cs | 3 - src/Fallout.Utilities/Text/String.Quoting.cs | 15 +- src/Fallout.Utilities/Text/String.Repeat.cs | 3 +- src/Fallout.Utilities/Text/String.Replace.cs | 3 +- src/Fallout.Utilities/Text/String.Split.cs | 24 +- src/Fallout.Utilities/Text/String.Trim.cs | 11 +- src/Fallout.Utilities/Text/String.Truncate.cs | 3 - .../Text/Xml/XElement.GetAttributeValue.cs | 6 +- .../Text/Xml/XNode.XPathEvaluateValue.cs | 19 +- .../Text/Xml/XmlExtensions.cs | 2 +- src/Fallout.Utilities/Url.WithUtmValues.cs | 12 +- .../Model/BuildTypeNames.cs | 1 + .../Model/ConfigurationRuleFollower.cs | 25 +- .../Model/ModelHelper.cs | 12 +- .../Model/PlatformNames.cs | 4 +- .../Model/ProjectConfigMapping.cs | 13 +- .../Model/ProjectTypeTable.BuiltInTypes.cs | 159 +++-- .../Model/ProjectTypeTable.cs | 106 ++-- .../Model/PropertyContainerModel.cs | 14 +- .../Model/SolutionArgumentException.cs | 10 +- ...onConfigurationMap.DimensionDiffTracker.cs | 34 +- ...tionConfigurationMap.ProjectDiffTracker.cs | 43 +- .../Model/SolutionConfigurationMap.Rules.cs | 99 ++-- .../Model/SolutionConfigurationMap.cs | 93 +-- .../Model/SolutionException.cs | 65 ++- .../Model/SolutionFolderModel.cs | 67 +-- .../Model/SolutionItemModel.cs | 58 +- .../Model/SolutionModel.cs | 239 ++++---- .../Model/SolutionProjectModel.cs | 103 ++-- .../Model/SolutionPropertyBag.cs | 51 +- .../Model/StringTable.cs | 12 +- .../Model/VisualStudioProperties.cs | 32 +- .../RequiredNetFramework.cs | 16 +- .../Serializer/SingleFileSerializerBase.cs | 21 +- .../Serializer/SlnV12/SectionName.cs | 26 +- .../SlnV12/SlnFileV12Serializer.Reader.cs | 142 +++-- .../SlnV12/SlnFileV12Serializer.Writer.cs | 75 +-- .../Serializer/SlnV12/SlnFileV12Serializer.cs | 20 +- .../Serializer/SlnV12/SlnV12Extensions.cs | 118 ++-- .../Serializer/SlnV12/SlnV12ModelExtension.cs | 2 +- .../Serializer/Xml/Keywords.cs | 66 +-- .../Serializer/Xml/LineInfoXmlDocument.cs | 18 +- .../Serializer/Xml/SlnXMLSerializer.Reader.cs | 12 +- .../Serializer/Xml/SlnXMLSerializer.Writer.cs | 30 +- .../Serializer/Xml/SlnXMLSerializer.cs | 10 +- .../Serializer/Xml/SlnXmlModelExtension.cs | 8 +- .../ItemConfigurationRulesList.cs | 58 +- .../Xml/XmlDecorators/ItemRefList`1.cs | 24 +- .../Serializer/Xml/XmlDecorators/SlnxFile.cs | 61 +- .../Xml/XmlDecorators/XmlBuildDependency.cs | 2 +- .../Xml/XmlDecorators/XmlBuildType.cs | 2 +- .../Xml/XmlDecorators/XmlConfiguration.cs | 30 +- .../Xml/XmlDecorators/XmlConfigurations.cs | 47 +- .../XmlDecorators/XmlContainer.ApplyModel.cs | 82 +-- .../Xml/XmlDecorators/XmlContainer.cs | 11 +- .../XmlContainerWithProperties.cs | 12 +- .../XmlDecorators/XmlDecorator.ApplyModel.cs | 24 +- .../Xml/XmlDecorators/XmlDecorator.cs | 53 +- .../Serializer/Xml/XmlDecorators/XmlFile.cs | 2 +- .../Serializer/Xml/XmlDecorators/XmlFolder.cs | 59 +- .../Xml/XmlDecorators/XmlPlatform.cs | 2 +- .../XmlDecorators/XmlProject.ApplyModel.cs | 31 +- .../Xml/XmlDecorators/XmlProject.cs | 74 +-- .../Xml/XmlDecorators/XmlProjectType.cs | 98 ++-- .../Xml/XmlDecorators/XmlProperties.cs | 24 +- .../Xml/XmlDecorators/XmlProperty.cs | 12 +- .../XmlDecorators/XmlSolution.ApplyModel.cs | 21 +- .../Xml/XmlDecorators/XmlSolution.cs | 78 +-- .../Serializer/Xml/XmlDomUtilities.cs | 4 +- .../Serializer/Xml/XmlElementAttributes.cs | 8 +- .../Serializer/Xml/XmlElementSubElements.cs | 14 +- .../Xml/XmlElementSubElementsEnumerable.cs | 2 +- .../Utilities/Argument.cs | 7 +- .../Utilities/CollectionExtensions.cs | 12 +- .../Utilities/Lictionary`2.cs | 58 +- .../Utilities/ListBuilderStruct`1.cs | 72 ++- .../Utilities/ListStructEnumerable`1.cs | 14 +- .../Utilities/PathExtensions.cs | 27 +- .../Utilities/SpanExtensions.cs | 294 +++++----- .../Utilities/StringExtensions.cs | 14 +- .../Utilities/StringTokenizer.cs | 118 ++-- src/Persistence/Fallout.Solution/Model.cs | 26 +- .../SolutionModelExtensions.cs | 2 +- src/Shims/Nuke.Build/ShimMarker.cs | 4 +- src/Shims/Nuke.Common/CI/AppVeyor/AppVeyor.cs | 8 +- .../CI/AzurePipelines/AzurePipelines.cs | 4 +- src/Shims/Nuke.Common/CI/Bamboo/Bamboo.cs | 4 +- .../Nuke.Common/CI/Bitbucket/Bitbucket.cs | 4 +- src/Shims/Nuke.Common/CI/Bitrise/Bitrise.cs | 4 +- .../CI/GitHubActions/GitHubActions.cs | 4 +- src/Shims/Nuke.Common/CI/GitLab/GitLab.cs | 4 +- src/Shims/Nuke.Common/CI/Jenkins/Jenkins.cs | 4 +- src/Shims/Nuke.Common/CI/TeamCity/TeamCity.cs | 4 +- src/Shims/Nuke.Common/CI/TravisCI/TravisCI.cs | 4 +- src/Shims/Nuke.Common/ShimMarker.cs | 12 +- .../Utilities/DelegateDisposable.cs | 6 +- src/Shims/Nuke.Components/IHazArtifacts.cs | 7 +- src/Shims/Nuke.Components/IHazChangelog.cs | 7 +- .../Nuke.Components/IHazConfiguration.cs | 7 +- .../Nuke.Components/IHazGitRepository.cs | 7 +- src/Shims/Nuke.Components/IHazGitVersion.cs | 7 +- .../IHazNerdbankGitVersioning.cs | 7 +- src/Shims/Nuke.Components/IHazReports.cs | 7 +- src/Shims/Nuke.Components/IHazSolution.cs | 7 +- .../Nuke.Components/IHazTwitterCredentials.cs | 7 +- src/Shims/Nuke.Components/ShimMarker.cs | 4 +- .../SlnxFixtureBuilder.cs | 1 + .../SlnxParseBenchmarks.cs | 4 +- .../Consumers/Fallout.Consumer.Local/Build.cs | 10 +- .../Consumers/Fallout.Consumer.NuGet/Build.cs | 6 +- .../Build.cs | 10 +- tests/Consumers/Nuke.Consumer/Build.cs | 8 +- .../BlockBannerGeometrySpecs.cs | 1 + .../Fallout.Build.Specs/BuildContextSpecs.cs | 9 +- .../Fallout.Build.Specs/BuildExecutorSpecs.cs | 50 +- .../BuildGraphUtilitySpecs.cs | 73 ++- .../BuiltInParameterTextSpecs.cs | 4 +- .../CompletionUtilitySpecs.cs | 201 +++++-- .../ConsoleOutputCharacterSpecs.cs | 18 +- tests/Fallout.Build.Specs/ControlFlowSpecs.cs | 3 +- .../DefaultInterfaceExecutionSpecs.cs | 13 +- .../DuplicateParameterSpecs.cs | 9 +- .../ExecutableTargetFactorySpecs.cs | 19 +- .../ExecutionPlannerSpecs.cs | 74 ++- .../Fallout.Build.Specs/GitRepositorySpecs.cs | 9 +- .../GitRepositoryWorktreeSpecs.cs | 12 +- tests/Fallout.Build.Specs/HostInitializer.cs | 1 - .../Fallout.Build.Specs/InMemorySinkSpecs.cs | 2 +- .../ParameterServiceSpecs.cs | 70 ++- .../PromptConfirmationGlyphSpecs.cs | 5 +- .../Fallout.Build.Specs/SchemaUtilitySpecs.cs | 60 +- .../Utilities/ConsoleUtilitySpecs.cs | 11 +- .../Utilities/ValidInputData.cs | 33 +- .../ValueInjectionUtilitySpecs.cs | 2 +- .../VerbosityLevelMappingSpecs.cs | 6 +- .../VerifySpecsInitializer.cs | 1 - .../BuildProjectResolverSpecs.cs | 7 +- .../Fallout.Cli.Specs/CakeConversionSpecs.cs | 10 +- .../CommandDispatcherSpecs.cs | 63 +- .../Commands/CakeCleanCommandSpecs.cs | 7 +- .../Commands/CompleteCommandSpecs.cs | 2 +- .../Commands/FakeConsolePrompts.cs | 4 + .../Commands/GetConfigurationCommandSpecs.cs | 2 +- .../Commands/SecretsCommandSpecs.cs | 2 +- .../Commands/UpdateCommandSpecs.cs | 16 +- .../ConfigurationReaderSpecs.cs | 6 +- .../UpdateSolutionFileContentSpecs.cs | 93 +-- .../VerifySpecsInitializer.cs | 1 - .../CI/AzurePipelinesJobSpecs.cs | 29 +- .../CI/ConfigurationGenerationSpecs.cs | 548 ++++++++++++++---- .../CI/GitHubActionsActionReferenceSpecs.cs | 84 ++- .../CI/GitHubActionsArtifactStepSpecs.cs | 10 +- .../CI/GitHubActionsCustomStepSpecs.cs | 102 +++- .../GitHubActionsCustomStepValidationSpecs.cs | 126 +++- .../CI/GitHubActionsDispatchInputSpecs.cs | 67 ++- .../CI/GitHubActionsEnvValidationSpecs.cs | 37 +- .../CI/GitHubActionsInputValidationSpecs.cs | 60 +- ...itHubActionsRunsOnLabelsValidationSpecs.cs | 72 ++- .../CI/GitHubActionsStepInjectionSpecs.cs | 93 ++- .../CI/GitHubActionsStepPipelineSpecs.cs | 51 +- ...tHubActionsWorkflowDispatchTriggerSpecs.cs | 73 ++- .../CI/ISpecConfigurationGenerator.cs | 2 - .../CI/SpecAppVeyorAttribute.cs | 2 - .../CI/SpecAzurePipelinesAttribute.cs | 2 - .../CI/SpecGitHubActionsAttribute.cs | 6 +- .../CI/SpecSpaceAutomationAttribute.cs | 2 - .../CI/SpecTeamCityAttribute.cs | 2 - tests/Fallout.Common.Specs/CISpecs.cs | 22 +- .../ChangelogTasksSpecs.cs | 16 +- .../Fallout.Common.Specs/GitHubTasksSpecs.cs | 8 +- .../GitVersionParseSpecs.cs | 66 +-- tests/Fallout.Common.Specs/SettingsSpecs.cs | 12 +- .../VerifySpecsInitializer.cs | 1 - tests/Fallout.Common.Specs/XmlTasksSpecs.cs | 4 +- .../PublishPackageRouterSpecs.cs | 33 +- .../ArchitectureFitnessSpecs.cs | 3 +- tests/Fallout.Core.Specs/TopoSortSpecs.cs | 70 ++- .../AnalyzerSpecHarness.cs | 4 +- .../NukeMigrationAnalyzerSpecs.cs | 158 ++--- .../NukeMigrationCodeFixSpecs.cs | 240 ++++---- .../RealWorldSmokeSpecs.cs | 90 +-- .../RewriteBootstrapScriptsStepSpecs.cs | 41 +- .../RewriteCsFilesStepSpecs.cs | 8 +- .../RewriteCsprojsStepSpecs.cs | 21 +- .../ProjectModelSpecs.cs | 2 +- tests/Fallout.Solution.Specs/SolutionSpec.cs | 4 +- .../StronglyTypedSolutionGeneratorSpecs.cs | 15 +- .../TransitionShimGeneratorSpecs.cs | 153 ++--- .../ArgumentStringHandlerSpecs.cs | 11 +- .../NuGetPackageResolverSpecs.cs | 18 +- tests/Fallout.Tooling.Specs/OptionsSpecs.cs | 95 ++- .../ToolOptionsArgumentsSpecs.cs | 344 +++++++++-- .../ToolPathResolverResetSpecs.cs | 2 +- .../ToolTasksLoggerSpecs.cs | 84 ++- .../ToolTasksToolPathSpecs.cs | 6 +- .../ArgumentParserSpecs.cs | 160 ++++- .../Collections/DictionaryExtensionsSpecs.cs | 11 +- .../Collections/EnumerableExtensionsSpecs.cs | 24 +- .../Collections/LookupTableSpecs.cs | 16 +- .../EnvironmentInfoSpecs.cs | 4 +- .../IO/CompressionTasksSpecs.cs | 5 +- .../IO/FileSystemDependentSpecs.cs | 10 +- .../IO/MoveCopySpecs.cs | 19 +- .../IO/PathConstructionSpecs.cs | 148 ++++- .../Reflection/ReflectionUtilitySpecs.cs | 2 +- .../Text/SerializationSpecs.cs | 26 +- .../Text/StringExtensionsSpecs.cs | 6 +- .../HostDetectionSpecs.cs | 7 +- .../SampleConsumerBuild.cs | 31 +- .../SampleConsumerBuild.cs | 21 +- 689 files changed, 10560 insertions(+), 5153 deletions(-) diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 4fb503a1e..a744faa47 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -42,9 +42,25 @@ CheckoutRef = "${{ github.head_ref }}", // PRs targeting develop, main, or any release/* / support/* branch — all // long-lived and protected; all require the ubuntu-latest check. - OnPullRequestBranches = new[] { DevelopBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, - OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, - InvokedTargets = new[] { nameof(VerifyGeneratedTools), nameof(ITest.Test), nameof(IPack.Pack) }, + OnPullRequestBranches = new[] + { + DevelopBranch, + MainBranch, + ReleaseBranchPattern, + SupportBranchPattern + }, + OnPullRequestExcludePaths = new[] + { + "docs/**", + ".assets/**", + "**/*.md" + }, + InvokedTargets = new[] + { + nameof(VerifyGeneratedTools), + nameof(ITest.Test), + nameof(IPack.Pack) + }, PublishArtifacts = false)] [GitHubActions( "build-cross-platform", @@ -53,13 +69,30 @@ FetchDepth = 0, ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", ConcurrencyCancelInProgress = true, - OnPushTags = new[] { "v*" }, + OnPushTags = new[] + { + "v*" + }, // main is the production trunk now (ADR-0009). A PR into it is always a // release/vX.Y GA merge or a hotfix, so it belongs on this release-intent gate, // alongside release/* and support/*. - OnPullRequestBranches = new[] { MainBranch, ReleaseBranchPattern, SupportBranchPattern }, - OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, - InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, + OnPullRequestBranches = new[] + { + MainBranch, + ReleaseBranchPattern, + SupportBranchPattern + }, + OnPullRequestExcludePaths = new[] + { + "docs/**", + ".assets/**", + "**/*.md" + }, + InvokedTargets = new[] + { + nameof(ITest.Test), + nameof(IPack.Pack) + }, PublishArtifacts = false)] partial class Build { diff --git a/build/Build.CodeGeneration.cs b/build/Build.CodeGeneration.cs index 9e3ae784b..dc5127c6d 100644 --- a/build/Build.CodeGeneration.cs +++ b/build/Build.CodeGeneration.cs @@ -1,5 +1,4 @@ -using System; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities.Collections; using static Fallout.CodeGeneration.CodeGenerator; @@ -9,6 +8,7 @@ partial class Build { AbsolutePath SpecificationsDirectory => RootDirectory / "src" / "Fallout.Common" / "Tools"; + AbsolutePath ReferencesDirectory => RootDirectory / "docs" / "cli-tools"; // Hardcoded rather than derived from the locally-resolved git remote (e.g. via @@ -32,7 +32,8 @@ partial class Build GenerateCode( x, namespaceProvider: x => $"Fallout.Common.Tools.{x.Name}", - sourceFileProvider: x => $"https://github.com/{CanonicalRepositoryIdentifier}/blob/{DevelopBranch}/{RootDirectory.GetUnixRelativePathTo(x.SpecificationFile)}")); + sourceFileProvider: x => + $"https://github.com/{CanonicalRepositoryIdentifier}/blob/{DevelopBranch}/{RootDirectory.GetUnixRelativePathTo(x.SpecificationFile)}")); }); // CI gate: `GenerateTools` only runs when a contributor remembers to invoke it, so a .json diff --git a/build/Build.Contributors.cs b/build/Build.Contributors.cs index a3910c293..36c3f16b4 100644 --- a/build/Build.Contributors.cs +++ b/build/Build.Contributors.cs @@ -9,6 +9,7 @@ partial class Build { AbsolutePath ContributorsFile => RootDirectory / "CONTRIBUTORS.md"; + AbsolutePath ContributorsCacheFile => TemporaryDirectory / "contributors.dat"; Target UpdateContributors => _ => _ @@ -16,15 +17,23 @@ partial class Build { var previousContributors = ContributorsCacheFile.Existing()?.ReadAllLines() ?? new string[0]; - var repositoryDirectories = new[] { RootDirectory / ".git" } + var repositoryDirectories = new[] + { + RootDirectory / ".git" + } .Concat(ExternalRepositoriesDirectory.GlobDirectories("*/.git")); + var contributors = repositoryDirectories .SelectMany(x => Git(@"log --pretty=""%an|%ae%n%cn|%ce""", workingDirectory: x, logOutput: false)) .Select(x => x.Text) .Distinct().ToList() .Select(x => x.Split('|')) .ForEachLazy(x => Assert.Count(x, length: 2)) - .Select(x => new { Name = x[0], Email = x[1] }).ToList(); + .Select(x => new + { + Name = x[0], + Email = x[1] + }).ToList(); var newContributors = contributors.Where(x => !previousContributors.Contains(x.Email)); @@ -32,6 +41,7 @@ partial class Build { var content = (ContributorsFile.Existing()?.ReadAllLines() ?? new string[0]) .Concat($"- {newContributor.Name}").OrderBy(x => x); + ContributorsFile.WriteAllLines(content, Encoding.Default); Git($"add {ContributorsFile}"); diff --git a/build/Build.GlobalSolution.cs b/build/Build.GlobalSolution.cs index f6878b4b3..f6f4185ad 100644 --- a/build/Build.GlobalSolution.cs +++ b/build/Build.GlobalSolution.cs @@ -1,23 +1,24 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; using Fallout.Common; using Fallout.Common.Git; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Tools.GitHub; -using Fallout.Common.Utilities; +using Fallout.Solutions; using Fallout.Utilities.Text.Yaml; using static Fallout.Common.ControlFlow; using static Fallout.Common.Tools.Git.GitTasks; partial class Build { - [Parameter] readonly bool UseHttps; + [Parameter] + readonly bool UseHttps; AbsolutePath GlobalSolution => RootDirectory / "fallout-global.sln"; + AbsolutePath ExternalRepositoriesDirectory => RootDirectory / "external"; + AbsolutePath ExternalRepositoriesFile => ExternalRepositoriesDirectory / "repositories.yml"; IEnumerable ExternalSolutions @@ -38,7 +39,9 @@ IEnumerable ExternalRepositories var origin = UseHttps ? repository.HttpsUrl : repository.SshUrl; if (!Directory.Exists(repositoryDirectory)) + { Git($"clone {origin} {repositoryDirectory} --progress"); + } else { SuppressErrors(() => Git($"remote add origin {origin}", repositoryDirectory)); diff --git a/build/Build.Licenses.cs b/build/Build.Licenses.cs index 833e45b38..be58446db 100644 --- a/build/Build.Licenses.cs +++ b/build/Build.Licenses.cs @@ -1,7 +1,7 @@ -using Fallout.Common; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Fallout.Common; using Fallout.Common.IO; using Fallout.Components; using Serilog; @@ -13,18 +13,18 @@ partial class Build IEnumerable<(string Project, string Url)> Licenses => new[] - { - ("Glob", "https://raw.githubusercontent.com/kthompson/glob/develop/LICENSE"), - ("ICSharpCode.SharpZipLib", "https://raw.githubusercontent.com/icsharpcode/SharpZipLib/master/LICENSE.txt"), - ("Microsoft.Build", "https://raw.githubusercontent.com/dotnet/msbuild/main/LICENSE"), - ("Microsoft.CodeAnalysis", "https://raw.githubusercontent.com/dotnet/roslyn/main/License.txt"), - ("Newtonsoft.Json", "https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/LICENSE.md"), - ("NuGet", "https://raw.githubusercontent.com/NuGet/NuGet.Client/dev/LICENSE.txt"), - ("Octokit", "https://raw.githubusercontent.com/octokit/octokit.net/main/LICENSE.txt"), - ("Serilog", "https://raw.githubusercontent.com/serilog/serilog/dev/LICENSE"), - ("Spectre.Console", "https://raw.githubusercontent.com/spectreconsole/spectre.console/main/LICENSE.md"), - ("YamlDotNet", "https://raw.githubusercontent.com/aaubry/YamlDotNet/master/LICENSE.txt") - }; + { + ("Glob", "https://raw.githubusercontent.com/kthompson/glob/develop/LICENSE"), + ("ICSharpCode.SharpZipLib", "https://raw.githubusercontent.com/icsharpcode/SharpZipLib/master/LICENSE.txt"), + ("Microsoft.Build", "https://raw.githubusercontent.com/dotnet/msbuild/main/LICENSE"), + ("Microsoft.CodeAnalysis", "https://raw.githubusercontent.com/dotnet/roslyn/main/License.txt"), + ("Newtonsoft.Json", "https://raw.githubusercontent.com/JamesNK/Newtonsoft.Json/master/LICENSE.md"), + ("NuGet", "https://raw.githubusercontent.com/NuGet/NuGet.Client/dev/LICENSE.txt"), + ("Octokit", "https://raw.githubusercontent.com/octokit/octokit.net/main/LICENSE.txt"), + ("Serilog", "https://raw.githubusercontent.com/serilog/serilog/dev/LICENSE"), + ("Spectre.Console", "https://raw.githubusercontent.com/spectreconsole/spectre.console/main/LICENSE.md"), + ("YamlDotNet", "https://raw.githubusercontent.com/aaubry/YamlDotNet/master/LICENSE.txt") + }; Target DownloadLicenses => _ => _ .After() @@ -38,6 +38,7 @@ partial class Build await HttpDownloadFileAsync(x.Url, LicensesDirectory / $"{x.Project}.txt"); Log.Information("Downloaded license for {Project}", x.Project); }); + Task.WaitAll(downloadTasks.ToArray()); }); } diff --git a/build/Build.PublicApi.cs b/build/Build.PublicApi.cs index c14305a40..f3e872914 100644 --- a/build/Build.PublicApi.cs +++ b/build/Build.PublicApi.cs @@ -55,16 +55,24 @@ partial class Build bool DefaultFilter(MemberInfo member) { if (member is PropertyInfo) + { return false; + } if (member is Type && !member.IsPublic()) + { return false; + } - if (!(member.IsPublic() || member.IsFamily() && !member.DeclaringType.NotNull().IsSealed)) + if (!(member.IsPublic() || (member.IsFamily() && !member.DeclaringType.NotNull().IsSealed))) + { return false; + } if (member is FieldInfo { IsSpecialName: true }) + { return false; + } return true; } @@ -78,7 +86,9 @@ bool DefaultFilter(MemberInfo member) .ThenBy(x => x.Name); foreach (var member in members) + { builder.AppendLine($"- {member.GetDisplayText()}"); + } builder.AppendLine(); } diff --git a/build/Build.Stargazers.cs b/build/Build.Stargazers.cs index 72d5449ba..82c9eb473 100644 --- a/build/Build.Stargazers.cs +++ b/build/Build.Stargazers.cs @@ -15,24 +15,36 @@ partial class Build var stargazerUsers = await GitHubTasks.GitHubClient.Activity.Starring.GetAllStargazers( GitRepository.GetGitHubOwner(), GitRepository.GetGitHubName()); + var stargazerEntries = stargazerUsers.Select(async x => { var user = await GitHubTasks.GitHubClient.User.Get(x.Login); return new[] - { - user.Login.DoubleQuote(), - user.Name.DoubleQuote(), - user.Company.DoubleQuote(), - user.Location.DoubleQuote(), - user.Email.DoubleQuote(), - user.Blog.DoubleQuote() - }; + { + user.Login.DoubleQuote(), + user.Name.DoubleQuote(), + user.Company.DoubleQuote(), + user.Location.DoubleQuote(), + user.Email.DoubleQuote(), + user.Blog.DoubleQuote() + }; }).ToList(); await Task.WhenAll(stargazerEntries); StargazersFile.WriteAllLines( - new[] { new[] { "Login", "Name", "Company", "Location", "Email", "Blog" } } + new[] + { + new[] + { + "Login", + "Name", + "Company", + "Location", + "Email", + "Blog" + } + } .Concat(stargazerEntries.Select(x => x.Result).OrderBy(x => x.First())) .Select(x => x.JoinComma())); }); diff --git a/build/Build.Terminal.cs b/build/Build.Terminal.cs index 0a6f8e90c..edbf0f570 100644 --- a/build/Build.Terminal.cs +++ b/build/Build.Terminal.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.Execution; [DisableDefaultOutput( diff --git a/build/Build.cs b/build/Build.cs index 7ab67e3e6..fac62d358 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -1,21 +1,24 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using NuGet.Packaging; +using System.Text; using Fallout.Common; using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.Execution; using Fallout.Common.Git; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using Fallout.Common.Tools.GitHub; using Fallout.Common.Utilities; using Fallout.Components; +using Fallout.Solutions; +using Octokit; using static Fallout.Common.ControlFlow; using static Fallout.Common.Tools.DotNet.DotNetTasks; +using Project = Fallout.Solutions.Project; [DotNetVerbosityMapping] [ShutdownDotNetAfterServerBuild] @@ -34,14 +37,18 @@ partial class Build { public static int Main() => Execute(x => ((IPack)x).Pack); - [CI] readonly GitHubActions GitHubActions; + [CI] + readonly GitHubActions GitHubActions; GitRepository GitRepository => From().GitRepository; - [Solution(GenerateProjects = true)] readonly Solution Solution; + [Solution(GenerateProjects = true)] + readonly Solution Solution; + Fallout.Solutions.Solution IHasSolution.Solution => Solution; AbsolutePath OutputDirectory => RootDirectory / "output"; + AbsolutePath SourceDirectory => RootDirectory / "source"; // The literal GitFlow develop branch (ADR-0009, replacing ADR-0004/ADR-0008). This @@ -79,13 +86,16 @@ partial class Build // (major.minor from version.json + git-height as patch). ThisAssembly is generated by // NB.GV at compile time; AssemblyInformationalVersion looks like "10.3.5+abc1234" so we // strip the "+commitsha" tail. #81. - [Parameter] readonly bool Major; + [Parameter] + readonly bool Major; + string MajorMinorPatchVersion => Major ? $"{ParseMajor(ThisAssembly.AssemblyInformationalVersion) + 1}.0.0" : ThisAssembly.AssemblyInformationalVersion.Split('+')[0]; + string MilestoneTitle => $"v{MajorMinorPatchVersion}"; - private static int ParseMajor(string informationalVersion) + static int ParseMajor(string informationalVersion) => int.Parse(informationalVersion.Split('.')[0]); AbsolutePath IHasArtifacts.ArtifactsDirectory => RootDirectory / "output"; @@ -106,12 +116,16 @@ private static int ParseMajor(string informationalVersion) .When(!ScheduledTargets.Contains(((IPublish)this).Publish) && !ScheduledTargets.Contains(Install), _ => _ .ClearProperties()); - IEnumerable<(Fallout.Solutions.Project Project, string Framework)> ICompile.PublishConfigurations => - from project in new[] { Solution.Fallout_Cli, Solution.Fallout_MSBuildTasks } + IEnumerable<(Project Project, string Framework)> ICompile.PublishConfigurations => + from project in new[] + { + Solution.Fallout_Cli, + Solution.Fallout_MSBuildTasks + } from framework in project.GetTargetFrameworks() select (project, framework); - IEnumerable ITest.TestProjects => Partition.GetCurrent(Solution.GetAllProjects("*.Specs")); + IEnumerable ITest.TestProjects => Partition.GetCurrent(Solution.GetAllProjects("*.Specs")); [Parameter] public int TestDegreeOfParallelism { get; } = 1; @@ -121,6 +135,7 @@ from framework in project.GetTargetFrameworks() .Partition(2); bool IReportCoverage.CreateCoverageHtmlReport => true; + bool IReportCoverage.ReportToCodecov => false; // Local Terminal runs use a placeholder version so packed nupkgs don't @@ -132,7 +147,9 @@ from framework in project.GetTargetFrameworks() string DefaultDeploymentVersion => "9999.0.0"; - [Parameter] [Secret] readonly string NuGetApiKey; + [Parameter] + [Secret] + readonly string NuGetApiKey; // Two publish channels (FALLOUT001 — see IPublish.PublishTargets). Routing replaces the // old single-feed push + the hand-rolled `dotnet nuget push` in the workflows (#333): @@ -155,8 +172,14 @@ from framework in project.GetTargetFrameworks() Name = "nuget.org", Source = "https://api.nuget.org/v3/index.json", ApiKey = NuGetApiKey, - IncludePackages = new[] { "Fallout.*" }, - ExcludePackages = new[] { "Nuke.*" }, + IncludePackages = new[] + { + "Fallout.*" + }, + ExcludePackages = new[] + { + "Nuke.*" + }, }, }; #pragma warning restore FALLOUT001 @@ -188,6 +211,7 @@ IEnumerable NuGetPackageFiles }); string ICreateGitHubRelease.Name => MajorMinorPatchVersion; + IEnumerable ICreateGitHubRelease.AssetFiles => NuGetPackageFiles; // Rewritten override (not inheriting the base) so we can build a rich @@ -207,7 +231,7 @@ IEnumerable NuGetPackageFiles .Executes(async () => { var client = GitHubTasks.GitHubClient; - client.Credentials = new global::Octokit.Credentials(From().GitHubToken.NotNull()); + client.Credentials = new Credentials(From().GitHubToken.NotNull()); var owner = GitRepository.GetGitHubOwner(); var name = GitRepository.GetGitHubName(); var version = MajorMinorPatchVersion; @@ -217,22 +241,26 @@ IEnumerable NuGetPackageFiles // "v10.2.0" form). First match wins. var milestones = await client.Issue.Milestone.GetAllForRepository( owner, name, - new global::Octokit.MilestoneRequest { State = global::Octokit.ItemStateFilter.All }); + new MilestoneRequest + { + State = ItemStateFilter.All + }); + var milestone = milestones.FirstOrDefault(m => m.Title.StartsWith(version, StringComparison.Ordinal) || m.Title.StartsWith($"v{version}", StringComparison.Ordinal)); var milestoneItems = milestone == null - ? Array.Empty() - : (await client.Issue.GetAllForRepository(owner, name, new global::Octokit.RepositoryIssueRequest + ? Array.Empty() + : (await client.Issue.GetAllForRepository(owner, name, new RepositoryIssueRequest { Milestone = milestone.Number.ToString(), - State = global::Octokit.ItemStateFilter.Closed + State = ItemStateFilter.Closed })).ToArray(); // Build the release body. GitHub will append its auto-generated // PR-since-last-tag list because GenerateReleaseNotes=true. - var body = new System.Text.StringBuilder(); + var body = new StringBuilder(); if (milestone != null) { body.AppendLine($"**Milestone:** [{milestone.Title}]({milestone.HtmlUrl})"); @@ -245,7 +273,10 @@ IEnumerable NuGetPackageFiles { body.AppendLine("### Pull requests in this milestone"); foreach (var pr in pulls) + { body.AppendLine($"- {pr.Title} (#{pr.Number}) — @{pr.User.Login}"); + } + body.AppendLine(); } @@ -253,16 +284,19 @@ IEnumerable NuGetPackageFiles { body.AppendLine("### Issues closed in this milestone"); foreach (var issue in issues) + { body.AppendLine($"- {issue.Title} (#{issue.Number})"); + } + body.AppendLine(); } } - global::Octokit.Release release; + Release release; try { release = await client.Repository.Release.Create(owner, name, - new global::Octokit.NewRelease(version) + new NewRelease(version) { Name = version, Body = body.ToString(), @@ -278,8 +312,8 @@ IEnumerable NuGetPackageFiles foreach (var assetPath in NuGetPackageFiles) { - await using var assetStream = System.IO.File.OpenRead(assetPath); - await client.Repository.Release.UploadAsset(release, new global::Octokit.ReleaseAssetUpload + await using var assetStream = File.OpenRead(assetPath); + await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload { FileName = assetPath.Name, ContentType = "application/octet-stream", @@ -289,7 +323,9 @@ IEnumerable NuGetPackageFiles // Notify each milestone item that it's shipped. foreach (var item in milestoneItems) + { await client.Issue.Comment.Create(owner, name, item.Number, $"Released in {version}! 🎉"); + } }); Target Install => _ => _ diff --git a/fallout.slnx.DotSettings b/fallout.slnx.DotSettings index bc4f2c67a..b1114143e 100644 --- a/fallout.slnx.DotSettings +++ b/fallout.slnx.DotSettings @@ -6,7 +6,7 @@ True HINT DO_NOT_SHOW - <?xml version="1.0" encoding="utf-16"?><Profile name="Safe Cleanup"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" RemoveRedundantParentheses="True" /><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + <?xml version="1.0" encoding="utf-16"?><Profile name="Safe Cleanup"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" RemoveRedundantParentheses="True" ArrangeNullCheckingPattern="True" /><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><IDEA_SETTINGS>&lt;profile version="1.0"&gt; &lt;option name="myName" value="Safe Cleanup" /&gt; &lt;inspection_tool class="ES6ShorthandObjectProperty" enabled="false" level="WARNING" enabled_by_default="false" /&gt; &lt;inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="WARNING" enabled_by_default="false" /&gt; @@ -21,6 +21,12 @@ &lt;inspection_tool class="UnnecessaryReturnJS" enabled="false" level="WARNING" enabled_by_default="false" /&gt; &lt;inspection_tool class="WrongPropertyKeyValueDelimiter" enabled="false" level="WEAK WARNING" enabled_by_default="false" /&gt; &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id=""&gt; + &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; + &lt;/Language&gt; + &lt;Language id="CMake"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; &lt;Language id="CSS"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;Rearrange&gt;false&lt;/Rearrange&gt; @@ -28,6 +34,9 @@ &lt;Language id="EditorConfig"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; + &lt;Language id="GdScript"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; &lt;Language id="HTML"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;Rearrange&gt;false&lt;/Rearrange&gt; @@ -56,8 +65,8 @@ &lt;Language id="Markdown"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; - &lt;Language id="PowerShell"&gt; - &lt;Reformat&gt;false&lt;/Reformat&gt; + &lt;Language id="Mermaid"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; &lt;/Language&gt; &lt;Language id="Properties"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; @@ -65,6 +74,9 @@ &lt;Language id="RELAX-NG"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; + &lt;Language id="Razor"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; &lt;Language id="SQL"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; @@ -79,7 +91,7 @@ &lt;Language id="yaml"&gt; &lt;Reformat&gt;false&lt;/Reformat&gt; &lt;/Language&gt; -&lt;/profile&gt;</RIDER_SETTINGS><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> +&lt;/profile&gt;</RIDER_SETTINGS><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSharpReformatComments>True</CSharpReformatComments></Profile> Safe Cleanup Required Required diff --git a/src/Fallout.Build.Shared/CompletionUtility.cs b/src/Fallout.Build.Shared/CompletionUtility.cs index 4c8d5c151..6f3e92df7 100644 --- a/src/Fallout.Build.Shared/CompletionUtility.cs +++ b/src/Fallout.Build.Shared/CompletionUtility.cs @@ -30,7 +30,9 @@ public static IReadOnlyDictionary GetItemsFromSchema( var parameterProperties = rootElement.GetProperty("definitions").TryGetProperty("FalloutBuild", out var baseSchema) ? baseSchema.GetProperty("properties").EnumerateObject() - .Concat(rootElement.GetProperty("allOf")[0].TryGetProperty("properties", out var properties) ? properties.EnumerateObject() : []) + .Concat(rootElement.GetProperty("allOf")[0].TryGetProperty("properties", out var properties) + ? properties.EnumerateObject() + : []) : definitions["build"].Value.GetProperty("properties").EnumerateObject(); return parameterProperties @@ -42,13 +44,16 @@ public static IReadOnlyDictionary GetItemsFromSchema( string[] GetValues(JsonProperty property) { if (property.Name == Constants.LoadedLocalProfilesParameterName) + { return profileNames.ToArray(); + } if (property.Value.TryGetProperty("type", out var typeProperty)) { var types = typeProperty.ValueKind != JsonValueKind.Array ? [typeProperty.GetString()] : typeProperty.EnumerateArray().Select(x => x.GetString()).ToArray(); + if (types.ContainsAnyOrdinalIgnoreCase("string", "boolean", "integer")) { return property.Value.TryGetProperty("enum", out var enumProperty) @@ -57,10 +62,14 @@ string[] GetValues(JsonProperty property) } if (types.Single() == "array") + { return GetValues(property.Value.EnumerateObject().Single(x => x.Name == "items")); + } if (types.Single() == "object") + { return null; + } } else if (property.Value.TryGetProperty("$ref", out var refProperty)) { @@ -77,7 +86,10 @@ public static IEnumerable GetRelevantItems( string words, IReadOnlyDictionary completionItems) { - completionItems = new Dictionary(completionItems.ToDictionary(x => x.Key, x => x.Value), StringComparer.OrdinalIgnoreCase).AsReadOnly(); + completionItems = + new Dictionary(completionItems.ToDictionary(x => x.Key, x => x.Value), + StringComparer.OrdinalIgnoreCase).AsReadOnly(); + var suggestedItems = new List(); ICollection parts = words.Split(separator: ' '); @@ -90,6 +102,7 @@ void AddParameters() var useDashes = currentWord == null || currentWord.TrimStart('-').Length == 0 || currentWord.StartsWith("--"); + var items = completionItems.Keys .Except(parameters, StringComparer.InvariantCultureIgnoreCase) .Select(x => useDashes @@ -110,7 +123,9 @@ void AddTargetsOrValues(string parameter) new string[0]; if (parameter.EqualsOrdinalIgnoreCase(Constants.InvokedTargetsParameterName)) + { items = items.Select(x => x.SplitCamelHumpsWithKnownWords().JoinDash()); + } AddItems(items); } @@ -120,7 +135,9 @@ void AddItems(IEnumerable items) foreach (var item in items) { if (currentWord == null) + { suggestedItems.Add(item); + } else if (item.StartsWithOrdinalIgnoreCase(currentWord)) { var normalizedItem = item.ReplaceRegex(currentWord, _ => currentWord, RegexOptions.IgnoreCase); @@ -128,9 +145,13 @@ void AddItems(IEnumerable items) { var letters = currentWord.Where(char.IsLetter).ToList(); if (letters.All(char.IsUpper)) + { normalizedItem = normalizedItem.ToUpperInvariant(); + } else if (letters.All(char.IsLower)) + { normalizedItem = normalizedItem.ToLowerInvariant(); + } } suggestedItems.Add(normalizedItem); @@ -139,12 +160,18 @@ void AddItems(IEnumerable items) } if (lastParameter == null) + { AddTargetsOrValues(Constants.InvokedTargetsParameterName); + } else if (currentWord != lastParameter) + { AddTargetsOrValues(lastParameter); + } if (currentWord == null || ArgumentParser.IsArgument(currentWord)) + { AddParameters(); + } return suggestedItems; } diff --git a/src/Fallout.Build.Shared/Constants.cs b/src/Fallout.Build.Shared/Constants.cs index 9c1750a8c..ceb08a133 100644 --- a/src/Fallout.Build.Shared/Constants.cs +++ b/src/Fallout.Build.Shared/Constants.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,7 +13,9 @@ namespace Fallout.Common; internal static class Constants { internal const string FalloutFileName = FalloutDirectoryName; + internal const string FalloutDirectoryName = ".fallout"; + // Legacy directory name from the pre-Fallout era. Read-only: lets existing // consumer projects keep building until they migrate (manually or via the // forthcoming Fallout.Migrate CLI). New setups always use .fallout/. @@ -60,7 +61,8 @@ internal static class Constants internal const string UpstreamNukeRepositoryGit = $"{UpstreamNukeRepository}.git"; internal static AbsolutePath GlobalTemporaryDirectory => Path.GetTempPath(); - internal static AbsolutePath GlobalFalloutDirectory => EnvironmentInfo.SpecialFolder(SpecialFolders.UserProfile) / ".fallout"; + + internal static AbsolutePath GlobalFalloutDirectory => EnvironmentInfo.SpecialFolder(SpecialFolders.UserProfile) / ".fallout"; internal static AbsolutePath TryGetRootDirectoryFrom(AbsolutePath startDirectory, bool includeLegacy = true) { @@ -68,9 +70,10 @@ internal static AbsolutePath TryGetRootDirectoryFrom(AbsolutePath startDirectory .DescendantsAndSelf(x => x.Parent) .FirstOrDefault(x => x.GetDirectories(FalloutDirectoryName).Any() || x.GetDirectories(LegacyNukeDirectoryName).Any() || - includeLegacy && x.GetFiles(FalloutFileName).Any()) + (includeLegacy && x.GetFiles(FalloutFileName).Any())) ?.FullName; - return rootDirectory != GlobalFalloutDirectory.Parent ? (AbsolutePath) rootDirectory : null; + + return rootDirectory != GlobalFalloutDirectory.Parent ? (AbsolutePath)rootDirectory : null; } internal static bool IsLegacy(AbsolutePath rootDirectory) @@ -82,7 +85,10 @@ internal static AbsolutePath GetFalloutDirectory(AbsolutePath rootDirectory) { var newDir = rootDirectory / FalloutDirectoryName; if (Directory.Exists(newDir)) + { return newDir; + } + var legacyDir = rootDirectory / LegacyNukeDirectoryName; return Directory.Exists(legacyDir) ? legacyDir : newDir; } @@ -129,7 +135,8 @@ internal static AbsolutePath GetDefaultParametersFile(AbsolutePath rootDirectory internal static IEnumerable GetParametersProfileFiles(AbsolutePath rootDirectory) { - return new DirectoryInfo(GetFalloutDirectory(rootDirectory)).GetFiles($"{ParametersFilePrefix}.*.json", SearchOption.TopDirectoryOnly) + return new DirectoryInfo(GetFalloutDirectory(rootDirectory)) + .GetFiles($"{ParametersFilePrefix}.*.json", SearchOption.TopDirectoryOnly) .Select(x => (AbsolutePath)x.FullName); } @@ -165,6 +172,7 @@ internal static string GetLegacyCredentialStoreName(AbsolutePath rootDirectory, internal static string GetProfilePasswordParameterName(string profile) { - return $"PARAMS_{profile.TrimStart(DefaultProfileName).ToUpperInvariant().Replace(".", "_")}_KEY".Replace("_", string.Empty); + return $"PARAMS_{profile.TrimStart(DefaultProfileName).ToUpperInvariant().Replace(".", "_")}_KEY".Replace("_", + string.Empty); } } diff --git a/src/Fallout.Build.Shared/LegacyEnvironment.cs b/src/Fallout.Build.Shared/LegacyEnvironment.cs index 28f195384..bab13a563 100644 --- a/src/Fallout.Build.Shared/LegacyEnvironment.cs +++ b/src/Fallout.Build.Shared/LegacyEnvironment.cs @@ -21,11 +21,15 @@ public static string Read(string preferredName, string legacyName) { var preferred = Environment.GetEnvironmentVariable(preferredName); if (!string.IsNullOrEmpty(preferred)) + { return preferred; + } var legacy = Environment.GetEnvironmentVariable(legacyName); if (string.IsNullOrEmpty(legacy)) + { return null; + } WarnOnce(legacyName, preferredName); return legacy; @@ -37,10 +41,14 @@ public static string Read(string preferredName, string legacyName) public static string ReadFromVariables(IReadOnlyDictionary variables, string preferredName, string legacyName) { if (variables.TryGetValue(preferredName, out var preferred) && !string.IsNullOrEmpty(preferred)) + { return preferred; + } if (!variables.TryGetValue(legacyName, out var legacy) || string.IsNullOrEmpty(legacy)) + { return null; + } WarnOnce(legacyName, preferredName); return legacy; @@ -51,7 +59,9 @@ private static void WarnOnce(string legacyName, string preferredName) lock (WarnedLegacyKeysLock) { if (!WarnedLegacyKeys.Add(legacyName)) + { return; + } } Console.Error.WriteLine( diff --git a/src/Fallout.Build.Shared/Notifications.cs b/src/Fallout.Build.Shared/Notifications.cs index 33b7c2299..a027db0e4 100644 --- a/src/Fallout.Build.Shared/Notifications.cs +++ b/src/Fallout.Build.Shared/Notifications.cs @@ -12,13 +12,16 @@ namespace Fallout.Build.Shared; internal record Notification(string Title, string Text, Link[] Links) { public string Title { get; } = Title; + public string Text { get; } = Text; + public Link[] Links { get; } = Links; } internal record Link(string Text, string Url) { public string Text { get; } = Text; + public string Url { get; } = Url; } @@ -53,7 +56,9 @@ private async Task GetNotificationInternal() var httpClient = new HttpClient(); var response = await httpClient.GetAsync(NotificationEndpoint); if (!response.IsSuccessStatusCode) + { return null; + } var content = await response.Content.ReadAsStringAsync(); var json = JsonDocument.Parse(content).RootElement; @@ -62,8 +67,11 @@ private async Task GetNotificationInternal() .Where(IsApplicable) .Select(x => (Json: x, File: notificationDirectory / x.ToString().GetMD5Hash())) .FirstOrDefault(x => !x.File.Exists()); + if (notification.File == null) + { return null; + } notification.File.TouchFile(); @@ -83,6 +91,7 @@ Link GetLink(JsonElement obj) medium: UtmMedium, source: utmSource, campaign: obj.GetProperty("campaign").GetString()); + return new Link( Text: obj.GetProperty("title").GetString(), Url: originalUrl.AbsoluteUri); diff --git a/src/Fallout.Build/Attributes/ArgumentsFromGitCommitMessageAttribute.cs b/src/Fallout.Build/Attributes/ArgumentsFromGitCommitMessageAttribute.cs index 4dcd435f0..498e9e76f 100644 --- a/src/Fallout.Build/Attributes/ArgumentsFromGitCommitMessageAttribute.cs +++ b/src/Fallout.Build/Attributes/ArgumentsFromGitCommitMessageAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Fallout.Common.CI; using Fallout.Common.Git; @@ -15,18 +14,25 @@ public class ArgumentsFromGitCommitMessageAttribute : BuildExtensionAttributeBas public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (BuildServerConfigurationGeneration.IsActive) + { return; + } var commit = GitRepository.GetCommitFromCI(); if (commit == null) + { return; + } var git = ToolResolver.GetEnvironmentOrPathTool("git"); var lastLine = git.Invoke($"show -s --format=%B {commit}", logInvocation: false, logOutput: false) .Select(x => x.Text) .LastOrDefault(x => !x.IsNullOrEmpty()); + if (!lastLine?.StartsWithOrdinalIgnoreCase(Prefix) ?? true) + { return; + } var arguments = lastLine[Prefix.Length..].TrimStart(); ParameterService.Instance.ArgumentsFromCommitMessageService = new ArgumentParser(arguments); diff --git a/src/Fallout.Build/Attributes/ShutdownDotNetAfterServerBuildAttribute.cs b/src/Fallout.Build/Attributes/ShutdownDotNetAfterServerBuildAttribute.cs index 36ea254a7..01d7a8168 100644 --- a/src/Fallout.Build/Attributes/ShutdownDotNetAfterServerBuildAttribute.cs +++ b/src/Fallout.Build/Attributes/ShutdownDotNetAfterServerBuildAttribute.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Execution; using Fallout.Common.Tooling; diff --git a/src/Fallout.Build/Attributes/UnsetVisualStudioEnvironmentVariablesAttribute.cs b/src/Fallout.Build/Attributes/UnsetVisualStudioEnvironmentVariablesAttribute.cs index f8ca1f93f..5fb4c6fad 100644 --- a/src/Fallout.Build/Attributes/UnsetVisualStudioEnvironmentVariablesAttribute.cs +++ b/src/Fallout.Build/Attributes/UnsetVisualStudioEnvironmentVariablesAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Utilities.Collections; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Build/Attributes/VerbosityMappingAttribute.cs b/src/Fallout.Build/Attributes/VerbosityMappingAttribute.cs index 19ef0a3f8..e29a4675f 100644 --- a/src/Fallout.Build/Attributes/VerbosityMappingAttribute.cs +++ b/src/Fallout.Build/Attributes/VerbosityMappingAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Execution; using Fallout.Common.Utilities.Collections; @@ -16,9 +15,13 @@ public VerbosityMappingAttribute(Type targetType) } public string Quiet { get; set; } + public string Minimal { get; set; } + public string Information { get; set; } + public string Normal { get; set; } + public string Verbose { get; set; } public void OnBuildInitialized( @@ -32,15 +35,29 @@ object GetMappedValue(string name) .GetValue(obj: null); if (Quiet != null) + { VerbosityMapping.Mappings.Add(targetType, (Verbosity.Quiet, GetMappedValue(Quiet))); + } + if (Minimal != null) + { VerbosityMapping.Mappings.Add(targetType, (Verbosity.Minimal, GetMappedValue(Minimal))); + } + if (Information != null) + { VerbosityMapping.Mappings.Add(targetType, (Verbosity.Information, GetMappedValue(Information))); + } + if (Normal != null) + { VerbosityMapping.Mappings.Add(targetType, (Verbosity.Normal, GetMappedValue(Normal))); + } + if (Verbose != null) + { VerbosityMapping.Mappings.Add(targetType, (Verbosity.Verbose, GetMappedValue(Verbose))); + } } } @@ -53,13 +70,17 @@ public static void Apply(ToolOptions options) foreach (var property in options.GetType().GetProperties()) { if (!Mappings.Contains(property.PropertyType)) + { continue; + } var mappings = Mappings[property.PropertyType]; foreach (var (verbosity, mappedVerbosity) in mappings) { if (verbosity == FalloutBuild.Verbosity) + { options.Set(property.Name, mappedVerbosity); + } } } } diff --git a/src/Fallout.Build/CICD/BuildServerConfigurationGeneration.cs b/src/Fallout.Build/CICD/BuildServerConfigurationGeneration.cs index 2c5986940..4806543ee 100644 --- a/src/Fallout.Build/CICD/BuildServerConfigurationGeneration.cs +++ b/src/Fallout.Build/CICD/BuildServerConfigurationGeneration.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI; public static class BuildServerConfigurationGeneration diff --git a/src/Fallout.Build/CICD/BuildServerConfigurationGenerationAttributeBase.cs b/src/Fallout.Build/CICD/BuildServerConfigurationGenerationAttributeBase.cs index 0a30f7a4b..ce81a30f6 100644 --- a/src/Fallout.Build/CICD/BuildServerConfigurationGenerationAttributeBase.cs +++ b/src/Fallout.Build/CICD/BuildServerConfigurationGenerationAttributeBase.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Reflection; using Fallout.Common.Execution; using Fallout.Common.Utilities.Collections; diff --git a/src/Fallout.Build/CICD/CIAttribute.cs b/src/Fallout.Build/CICD/CIAttribute.cs index 9778a4b2c..9d9c99c80 100644 --- a/src/Fallout.Build/CICD/CIAttribute.cs +++ b/src/Fallout.Build/CICD/CIAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; diff --git a/src/Fallout.Build/CICD/ChainedConfigurationAttributeBase.cs b/src/Fallout.Build/CICD/ChainedConfigurationAttributeBase.cs index 16aaa2eec..17f2d2ffb 100644 --- a/src/Fallout.Build/CICD/ChainedConfigurationAttributeBase.cs +++ b/src/Fallout.Build/CICD/ChainedConfigurationAttributeBase.cs @@ -11,11 +11,17 @@ public abstract class ChainedConfigurationAttributeBase : ConfigurationAttribute public override IEnumerable IrrelevantTargetNames => NonEntryTargets.Concat(ExcludedTargets); public string[] NonEntryTargets { get; set; } = new string[0]; + public string[] ExcludedTargets { get; set; } = new string[0]; - protected IEnumerable GetInvokedTargets(ExecutableTarget executableTarget, IReadOnlyCollection relevantTargets) + protected IEnumerable GetInvokedTargets(ExecutableTarget executableTarget, + IReadOnlyCollection relevantTargets) { - var invokedTargets = new List { executableTarget }; + var invokedTargets = new List + { + executableTarget + }; + ICollection additionalInvokedTargets; do { @@ -24,12 +30,21 @@ protected IEnumerable GetInvokedTargets(ExecutableTarget execu .Except(invokedTargets) .Where(x => NonEntryTargets.Contains(x.Name)) .Where(x => !ExcludedTargets.Contains(x.Name)).ToList(); + invokedTargets.AddRange(additionalInvokedTargets); - } while (additionalInvokedTargets.Count > 0); + } + while (additionalInvokedTargets.Count > 0); - Assert.True(invokedTargets.Except(new[] { executableTarget }).Count(x => x.PartitionSize != null) == 0, + Assert.True(invokedTargets.Except(new[] + { + executableTarget + }).Count(x => x.PartitionSize != null) == 0, $"Non-entry targets for {executableTarget.Name} cannot define partitions"); - return ExecutionPlanner.GetExecutionPlan(invokedTargets, new[] { executableTarget.Name }); + + return ExecutionPlanner.GetExecutionPlan(invokedTargets, new[] + { + executableTarget.Name + }); } protected IEnumerable GetTargetDependencies(ExecutableTarget executableTarget) diff --git a/src/Fallout.Build/CICD/ConfigurationAttributeBase.cs b/src/Fallout.Build/CICD/ConfigurationAttributeBase.cs index aa2304d21..a8dbe0b9a 100644 --- a/src/Fallout.Build/CICD/ConfigurationAttributeBase.cs +++ b/src/Fallout.Build/CICD/ConfigurationAttributeBase.cs @@ -15,16 +15,23 @@ public abstract class ConfigurationAttributeBase : Attribute, IConfigurationGene public IFalloutBuild Build { get; internal set; } public string DisplayName => HostType.Name + (string.IsNullOrEmpty(IdPostfix) ? string.Empty : $" ({IdPostfix})"); + public string HostName => HostType.Name; + public string Id => HostName + (string.IsNullOrEmpty(IdPostfix) ? string.Empty : $"_{IdPostfix}"); + public virtual string IdPostfix => string.Empty; public bool AutoGenerate { get; set; } = true; + public abstract Type HostType { get; } + public abstract AbsolutePath ConfigurationFile { get; } + public abstract IEnumerable GeneratedFiles { get; } public abstract IEnumerable RelevantTargetNames { get; } + public abstract IEnumerable IrrelevantTargetNames { get; } public abstract CustomFileWriter CreateWriter(StreamWriter streamWriter); @@ -43,9 +50,13 @@ public abstract class ConfigurationAttributeBase : Attribute, IConfigurationGene public void Generate(IReadOnlyCollection executableTargets) { var relevantTargets = RelevantTargetNames - .SelectMany(x => ExecutionPlanner.GetExecutionPlan(executableTargets, new[] { x })) + .SelectMany(x => ExecutionPlanner.GetExecutionPlan(executableTargets, new[] + { + x + })) .Distinct() .Where(x => !IrrelevantTargetNames.Contains(x.Name)).ToList(); + var configuration = GetConfiguration(relevantTargets); using var stream = CreateStream(); @@ -57,7 +68,9 @@ public void Generate(IReadOnlyCollection executableTargets) writer.WriteComment(); writer.WriteComment(" - To turn off auto-generation set:"); writer.WriteComment(); - writer.WriteComment($" [{GetType().Name.TrimEnd(nameof(Attribute))} ({nameof(IConfigurationGenerator.AutoGenerate)} = false)]"); + writer.WriteComment( + $" [{GetType().Name.TrimEnd(nameof(Attribute))} ({nameof(IConfigurationGenerator.AutoGenerate)} = false)]"); + writer.WriteComment(); writer.WriteComment(" - To trigger manual generation invoke:"); writer.WriteComment(); diff --git a/src/Fallout.Build/CICD/ConfigurationEntity.cs b/src/Fallout.Build/CICD/ConfigurationEntity.cs index f04a12d98..967794b64 100644 --- a/src/Fallout.Build/CICD/ConfigurationEntity.cs +++ b/src/Fallout.Build/CICD/ConfigurationEntity.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI; diff --git a/src/Fallout.Build/CICD/CustomFileWriter.cs b/src/Fallout.Build/CICD/CustomFileWriter.cs index e8bdd5798..609726703 100644 --- a/src/Fallout.Build/CICD/CustomFileWriter.cs +++ b/src/Fallout.Build/CICD/CustomFileWriter.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Build/CICD/GenerateBuildServerConfigurationsAttribute.cs b/src/Fallout.Build/CICD/GenerateBuildServerConfigurationsAttribute.cs index bcc716412..7da67e14a 100644 --- a/src/Fallout.Build/CICD/GenerateBuildServerConfigurationsAttribute.cs +++ b/src/Fallout.Build/CICD/GenerateBuildServerConfigurationsAttribute.cs @@ -14,9 +14,11 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge { var configurationId = ParameterService.GetParameter(ConfigurationParameterName); if (configurationId == null) + { return; + } - Assert.NotNull(Build.RootDirectory); + Build.RootDirectory.NotNull(); var generator = GetGenerators(Build) .Where(x => x.Id == configurationId) diff --git a/src/Fallout.Build/CICD/IBuildServer.cs b/src/Fallout.Build/CICD/IBuildServer.cs index 46cdc1804..4cb21a16f 100644 --- a/src/Fallout.Build/CICD/IBuildServer.cs +++ b/src/Fallout.Build/CICD/IBuildServer.cs @@ -1,5 +1,4 @@ - -namespace Fallout.Common.CI; +namespace Fallout.Common.CI; public interface IBuildServer { diff --git a/src/Fallout.Build/CICD/IConfigurationGenerator.cs b/src/Fallout.Build/CICD/IConfigurationGenerator.cs index f6c424531..bf12ce9a9 100644 --- a/src/Fallout.Build/CICD/IConfigurationGenerator.cs +++ b/src/Fallout.Build/CICD/IConfigurationGenerator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Execution; using Fallout.Common.IO; @@ -9,13 +8,17 @@ namespace Fallout.Common.CI; public interface IConfigurationGenerator { string Id { get; } + string DisplayName { get; } + string HostName { get; } bool AutoGenerate { get; } + Type HostType { get; } + IEnumerable GeneratedFiles { get; } void Generate(IReadOnlyCollection executableTargets); void SerializeState(); -} \ No newline at end of file +} diff --git a/src/Fallout.Build/CICD/InvokeBuildServerConfigurationGenerationAttribute.cs b/src/Fallout.Build/CICD/InvokeBuildServerConfigurationGenerationAttribute.cs index a2133dfae..44883a0d6 100644 --- a/src/Fallout.Build/CICD/InvokeBuildServerConfigurationGenerationAttribute.cs +++ b/src/Fallout.Build/CICD/InvokeBuildServerConfigurationGenerationAttribute.cs @@ -15,20 +15,29 @@ public class InvokeBuildServerConfigurationGenerationAttribute public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (Build.IsServerBuild || Build.IsInterceptorExecution) + { return; + } var hasConfigurationChanged = GetGenerators(Build) .Where(x => x.AutoGenerate) .AsParallel() .Select(HasConfigurationChanged).ToList(); + if (hasConfigurationChanged.All(x => !x)) + { return; + } if (Build.Help) + { return; + } if (Console.IsInputRedirected) + { return; + } Host.Information("Press any key to continue ..."); Console.ReadKey(); @@ -55,7 +64,9 @@ private bool HasConfigurationChanged(IConfigurationGenerator generator) .Select(x => Build.RootDirectory.GetRelativePathTo(x)).ToList(); if (changedFiles.Count == 0) + { return false; + } // TODO: multi-line logging Log.Warning("Configuration files for {Configuration} have changed.", generator.DisplayName); diff --git a/src/Fallout.Build/CICD/NoConvertAttribute.cs b/src/Fallout.Build/CICD/NoConvertAttribute.cs index 6b7faf4ae..da8502267 100644 --- a/src/Fallout.Build/CICD/NoConvertAttribute.cs +++ b/src/Fallout.Build/CICD/NoConvertAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.Common.CI; diff --git a/src/Fallout.Build/CICD/Partition.cs b/src/Fallout.Build/CICD/Partition.cs index 1f66d8df7..b175145cb 100644 --- a/src/Fallout.Build/CICD/Partition.cs +++ b/src/Fallout.Build/CICD/Partition.cs @@ -2,16 +2,20 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; -using System.Linq; namespace Fallout.Common.CI; [TypeConverter(typeof(TypeConverter))] public class Partition { - public static Partition Single { get; } = new() { Part = 1, Total = 1 }; + public static Partition Single { get; } = new() + { + Part = 1, + Total = 1 + }; public int Part { get; set; } + public int Total { get; set; } public class TypeConverter : System.ComponentModel.TypeConverter @@ -27,10 +31,10 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { var values = stringValue.Split('/'); return new Partition - { - Part = int.Parse(values[0]), - Total = int.Parse(values[1]) - }; + { + Part = int.Parse(values[0]), + Total = int.Parse(values[1]) + }; } return base.ConvertFrom(context, culture, value); @@ -48,7 +52,10 @@ public IEnumerable GetCurrent(IEnumerable enumerable) foreach (var item in enumerable) { if (i == Part - 1) + { yield return item; + } + i = (i + 1) % Total; } } diff --git a/src/Fallout.Build/CICD/PartitionAttribute.cs b/src/Fallout.Build/CICD/PartitionAttribute.cs index 14ed80c19..f1ba8b5ff 100644 --- a/src/Fallout.Build/CICD/PartitionAttribute.cs +++ b/src/Fallout.Build/CICD/PartitionAttribute.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Reflection; namespace Fallout.Common.CI; @@ -19,7 +17,11 @@ public override object GetValue(MemberInfo member, object instance) { var part = ParameterService.GetParameter(member); return part.HasValue - ? new Partition { Part = part.Value, Total = Total } + ? new Partition + { + Part = part.Value, + Total = Total + } : Partition.Single; } -} \ No newline at end of file +} diff --git a/src/Fallout.Build/ControlFlow.cs b/src/Fallout.Build/ControlFlow.cs index 858e3fe7a..e535c41cf 100644 --- a/src/Fallout.Build/ControlFlow.cs +++ b/src/Fallout.Build/ControlFlow.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Fallout.Common.Utilities; using Serilog; + #pragma warning disable CS0618 // ReSharper disable CompareNonConstrainedGenericWithNull @@ -20,7 +21,8 @@ public static void SuppressErrors(Action action, bool includeStackTrace = false, SuppressErrorsIf(condition: true, action, logWarning: logWarning); } - public static T SuppressErrors(Func action, T defaultValue = default, bool includeStackTrace = false, bool logWarning = true) + public static T SuppressErrors(Func action, T defaultValue = default, bool includeStackTrace = false, + bool logWarning = true) { return (T)SuppressErrorsIf(condition: true, action, defaultValue, logWarning); } @@ -37,7 +39,9 @@ private static object SuppressErrorsIf( bool logWarning = true) { if (!condition) + { return defaultValue; + } try { @@ -46,7 +50,9 @@ private static object SuppressErrorsIf( catch (Exception exception) { if (logWarning) + { Log.Warning(exception.Unwrap(), "Exception was suppressed"); + } return defaultValue; } @@ -76,7 +82,9 @@ public static void ExecuteWithRetry( lastException = exception; if (attempt + 1 >= retryAttempts) + { break; + } logAction($"Attempt #{attempt + 1} failed with: {exception.Message}"); if (delay != null) @@ -92,9 +100,9 @@ public static void ExecuteWithRetry( } Assert.Fail(new[] - { - $"Execution failed permanently after {retryAttempts} attempts.", - $"Last attempt failed with: {lastException!.Message}" - }.JoinNewLine()); + { + $"Execution failed permanently after {retryAttempts} attempts.", + $"Last attempt failed with: {lastException!.Message}" + }.JoinNewLine()); } } diff --git a/src/Fallout.Build/Execution/BuildContext.cs b/src/Fallout.Build/Execution/BuildContext.cs index aeb4256a6..3e0c8673a 100644 --- a/src/Fallout.Build/Execution/BuildContext.cs +++ b/src/Fallout.Build/Execution/BuildContext.cs @@ -59,7 +59,9 @@ public void Dispose() // The rest is shared process-wide state, so only the context that is still Current may reset // it — a superseded scope disposing out of order must not clobber the newer run. if (!ReferenceEquals(currentInstance.Value, this)) + { return; + } Logging.InMemorySink.Instance.Clear(); ValueInjectionUtility.ClearCache(); diff --git a/src/Fallout.Build/Execution/BuildExecutor.cs b/src/Fallout.Build/Execution/BuildExecutor.cs index 5f8869dae..eb3d86988 100644 --- a/src/Fallout.Build/Execution/BuildExecutor.cs +++ b/src/Fallout.Build/Execution/BuildExecutor.cs @@ -49,7 +49,9 @@ public static void Execute(FalloutBuild build, IReadOnlyCollection skipp void ExecuteAssuredTargets() { - var assuredScheduledTargets = build.ExecutionPlan.Where(x => x.AssuredAfterFailure && x.Status == ExecutionStatus.Scheduled); + var assuredScheduledTargets = + build.ExecutionPlan.Where(x => x.AssuredAfterFailure && x.Status == ExecutionStatus.Scheduled); + assuredScheduledTargets.ForEach(x => Execute(build, x, previouslyExecutedTargets, failureMode: true)); } } @@ -60,12 +62,15 @@ private static IReadOnlyCollection UpdateInvocationHash(FalloutBuild bui var invocation = EnvironmentInfo.CommandLineArguments .Where(x => !x.StartsWith("-") || x.TrimStart("-").EqualsOrdinalIgnoreCase(continueParameterName)) .JoinSpace(); + var invocationHash = invocation.GetMD5Hash(); IReadOnlyCollection GetPreviouslyExecutedTargets() { if (!build.Continue || !BuildAttemptFile.Exists()) + { return new string[0]; + } var previousBuild = BuildAttemptFile.ReadAllLines(); if (previousBuild.FirstOrDefault() != invocationHash) @@ -78,7 +83,11 @@ IReadOnlyCollection GetPreviouslyExecutedTargets() } var previouslyExecutedTargets = GetPreviouslyExecutedTargets(); - BuildAttemptFile.WriteAllLines(new[] { invocationHash }.Concat(previouslyExecutedTargets)); + BuildAttemptFile.WriteAllLines(new[] + { + invocationHash + }.Concat(previouslyExecutedTargets)); + return previouslyExecutedTargets; } @@ -112,7 +121,9 @@ private static void Execute( try { if (target.Intercept == null || !target.Intercept.Invoke()) + { target.Actions.ForEach(x => x()); + } target.Stopwatch.Stop(); target.Status = ExecutionStatus.Succeeded; @@ -137,12 +148,15 @@ private static void Execute( build.ExecuteExtension(x => x.OnTargetFailed(target)); if (!target.ProceedAfterFailure && !failureMode) + { throw new TargetExecutionException(target.Name, exception); + } } } } - private static bool CheckConditions(IFalloutBuild build, ExecutableTarget target, IEnumerable<(string Text, Func Delegate)> conditions) + private static bool CheckConditions(IFalloutBuild build, ExecutableTarget target, + IEnumerable<(string Text, Func Delegate)> conditions) { string Format(string condition) => condition @@ -177,7 +191,9 @@ string Format(string condition) private static void MarkTargetSkipped(IFalloutBuild build, ExecutableTarget target, string reason = null) { if (target.Status != ExecutionStatus.Scheduled) + { return; + } if (!target.Invoked) { @@ -186,7 +202,9 @@ private static void MarkTargetSkipped(IFalloutBuild build, ExecutableTarget targ } if (target.DependencyBehavior == DependencyBehavior.Execute) + { return; + } bool HasOtherDependencies(ExecutableTarget dependentTarget) => build.ExecutionPlan @@ -195,11 +213,15 @@ bool HasOtherDependencies(ExecutableTarget dependentTarget) var skippableDependencies = target.ExecutionDependencies.Concat(target.Triggers) .Where(x => !HasOtherDependencies(x)).ToList(); + skippableDependencies.ForEach(x => MarkTargetSkipped(build, x, $"because of {target.Name}")); } private static void AppendToBuildAttemptFile(string value) { - File.AppendAllLines(BuildAttemptFile, new[] { value }); + File.AppendAllLines(BuildAttemptFile, new[] + { + value + }); } } diff --git a/src/Fallout.Build/Execution/BuildManager.cs b/src/Fallout.Build/Execution/BuildManager.cs index 33fe49dc3..256922305 100644 --- a/src/Fallout.Build/Execution/BuildManager.cs +++ b/src/Fallout.Build/Execution/BuildManager.cs @@ -1,14 +1,14 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Text; -using Microsoft.Extensions.DependencyModel; using Fallout.Common.Tooling; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Microsoft.Extensions.DependencyModel; using Serilog; + #pragma warning disable CA2255 namespace Fallout.Common.Execution; @@ -57,7 +57,9 @@ public static int Execute(Expression>[] defaultTargetExpressi NpmToolPathResolver.NpmPackageJsonFile = build.NpmPackageJsonFile; if (!build.NoLogo) + { build.WriteLogo(); + } // TODO: move InvokedTargets to ExecutableTargetFactory build.ExecutionPlan = ExecutionPlanner.GetExecutionPlan( @@ -96,7 +98,9 @@ public static int Execute(Expression>[] defaultTargetExpressi void Finish() { if (build.ExecutionPlan == null) + { return; + } foreach (var target in build.ExecutionPlan) { diff --git a/src/Fallout.Build/Execution/DelegateRequirementService.cs b/src/Fallout.Build/Execution/DelegateRequirementService.cs index 88caf5db1..a0e9fcb6d 100644 --- a/src/Fallout.Build/Execution/DelegateRequirementService.cs +++ b/src/Fallout.Build/Execution/DelegateRequirementService.cs @@ -20,18 +20,26 @@ public static void ValidateRequirements(IFalloutBuild build, IReadOnlyCollection { if (requirement is Expression> boolExpression) // TODO: same as HasSkippingCondition.GetSkipReason + { Assert.True(boolExpression.Compile().Invoke(), $"Target '{target.Name}' requires '{requirement.Body}'"); + } else if (IsMemberNullOrEmpty(requirement.GetMemberInfo(), build, target)) - Assert.Fail($"Target '{target.Name}' requires member '{GetMemberName(requirement.GetMemberInfo())}' to be not null or empty"); + { + Assert.Fail( + $"Target '{target.Name}' requires member '{GetMemberName(requirement.GetMemberInfo())}' to be not null or empty"); + } } var requiredMembers = ValueInjectionUtility.GetInjectionMembers(build.GetType()) .Select(x => x.Member) .Where(x => x.HasCustomAttribute()); + foreach (var member in requiredMembers) { if (IsMemberNullOrEmpty(member, build)) + { Assert.Fail($"Member '{GetMemberName(member)}' is required to be not null or empty"); + } } } @@ -46,7 +54,9 @@ private static bool IsMemberNullOrEmpty(MemberInfo member, IFalloutBuild build, $"Member '{GetMemberName(member)}' is required {from}but not marked with an injection attribute."); if (build.Host is Terminal) + { TryInjectValueInteractive(member, build); + } return member.GetMemberType() != typeof(string) ? member.GetValue(build) == null @@ -56,14 +66,19 @@ private static bool IsMemberNullOrEmpty(MemberInfo member, IFalloutBuild build, private static void TryInjectValueInteractive(MemberInfo member, IFalloutBuild build) { if (!member.HasCustomAttribute()) + { return; + } if (member is PropertyInfo property && !property.CanWrite) + { return; + } var memberType = member.GetMemberType(); var nameOrDescription = ParameterService.GetParameterDescription(member) ?? ParameterService.GetParameterMemberName(member); + var text = $"{nameOrDescription.TrimEnd('.')}:"; while (member.GetValue(build) == null) diff --git a/src/Fallout.Build/Execution/ExecutableTarget.cs b/src/Fallout.Build/Execution/ExecutableTarget.cs index c4a9501c2..e92801097 100644 --- a/src/Fallout.Build/Execution/ExecutableTarget.cs +++ b/src/Fallout.Build/Execution/ExecutableTarget.cs @@ -6,6 +6,7 @@ using System.Reflection; using Fallout.Common.Tooling; using Fallout.Common.Utilities.Collections; + // ReSharper disable MissingBaseTypeHighlighting namespace Fallout.Common.Execution; @@ -14,40 +15,62 @@ namespace Fallout.Common.Execution; public class ExecutableTarget : ITargetModel { internal TargetDefinition Definition { get; set; } + internal Stopwatch Stopwatch { get; } = new(); + internal Func Intercept { get; set; } public MemberInfo Member { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public bool Listed { get; set; } + public Delegate Factory { get; set; } + public List<(string Text, Func Delegate)> DynamicConditions { get; internal set; } = new(); + public List<(string Text, Func Delegate)> StaticConditions { get; internal set; } = new(); + public DependencyBehavior DependencyBehavior { get; internal set; } + public bool AssuredAfterFailure { get; internal set; } + public bool ProceedAfterFailure { get; internal set; } + public List DelegateRequirements { get; internal set; } = new(); + public List ToolRequirements { get; internal set; } = new(); + public List Actions { get; internal set; } = new(); public List ExecutionDependencies { get; } = new(); + public List OrderDependencies { get; } = new(); + public List TriggerDependencies { get; } = new(); + public List Triggers { get; } = new(); public IReadOnlyCollection AllDependencies => ExecutionDependencies.Concat(OrderDependencies).Concat(TriggerDependencies).ToList(); public LookupTable ArtifactDependencies { get; internal set; } = new(); + public List ArtifactProducts { get; internal set; } = new(); public int? PartitionSize { get; set; } public TimeSpan Duration => Stopwatch.Elapsed; + public bool IsDefault { get; set; } + public ExecutionStatus Status { get; set; } + public bool Invoked { get; set; } + public Dictionary SummaryInformation { get; internal set; } = new(); public string Skipped @@ -64,6 +87,8 @@ public string OnlyWhen // ITargetModel — read-only projection over the live dependency lists for Fallout.Core consumers. IReadOnlyCollection ITargetModel.ExecutionDependencyNames => ExecutionDependencies.Select(x => x.Name).ToList(); + IReadOnlyCollection ITargetModel.OrderDependencyNames => OrderDependencies.Select(x => x.Name).ToList(); + IReadOnlyCollection ITargetModel.TriggerNames => Triggers.Select(x => x.Name).ToList(); } diff --git a/src/Fallout.Build/Execution/ExecutableTargetFactory.cs b/src/Fallout.Build/Execution/ExecutableTargetFactory.cs index 137146b8f..496940757 100644 --- a/src/Fallout.Build/Execution/ExecutableTargetFactory.cs +++ b/src/Fallout.Build/Execution/ExecutableTargetFactory.cs @@ -29,32 +29,33 @@ ExecutableTarget Create(PropertyInfo property) .Select(x => x.GetProperty(property.Name)) .Where(x => x != null && x.DeclaringType == x.ReflectedType) .Reverse().ToList(); + var definition = new TargetDefinition(property, build, new Stack(baseMembers)); - var factory = (Delegate) property.GetValue(build); + var factory = (Delegate)property.GetValue(build); factory.DynamicInvokeUnwrap(definition); return new ExecutableTarget - { - Name = definition.Name, - Member = property, - Definition = definition, - Intercept = definition.Intercept, - Description = definition.Description, - Factory = factory, - IsDefault = defaultTargets.Contains(factory), - DynamicConditions = definition.DynamicConditions, - StaticConditions = definition.StaticConditions, - DependencyBehavior = definition.DependencyBehavior, - ProceedAfterFailure = definition.IsProceedAfterFailure, - AssuredAfterFailure = definition.IsAssuredAfterFailure || factory is Cleanup, - DelegateRequirements = definition.DelegateRequirements, - ToolRequirements = definition.ToolRequirements, - Actions = definition.Actions, - Listed = !definition.IsInternal, - PartitionSize = definition.PartitionSize, - ArtifactProducts = definition.ArtifactProducts - }; + { + Name = definition.Name, + Member = property, + Definition = definition, + Intercept = definition.Intercept, + Description = definition.Description, + Factory = factory, + IsDefault = defaultTargets.Contains(factory), + DynamicConditions = definition.DynamicConditions, + StaticConditions = definition.StaticConditions, + DependencyBehavior = definition.DependencyBehavior, + ProceedAfterFailure = definition.IsProceedAfterFailure, + AssuredAfterFailure = definition.IsAssuredAfterFailure || factory is Cleanup, + DelegateRequirements = definition.DelegateRequirements, + ToolRequirements = definition.ToolRequirements, + Actions = definition.Actions, + Listed = !definition.IsInternal, + PartitionSize = definition.PartitionSize, + ArtifactProducts = definition.ArtifactProducts + }; } var executables = targetProperties.Select(Create).ToList(); @@ -70,13 +71,17 @@ IEnumerable GetDependencies( Func> indirectDependenciesSelector) { foreach (var factoryDependency in directDependenciesSelector(executable.Definition)) + { yield return executables.Single(x => x.Factory == factoryDependency); + } foreach (var otherExecutables in executables.Where(x => x != executable)) { var otherDependencies = indirectDependenciesSelector(otherExecutables.Definition); if (otherDependencies.Any(x => x == executable.Factory)) + { yield return otherExecutables; + } } } @@ -97,6 +102,7 @@ IEnumerable GetDependencies( var cleanup = executables .Where(x => x.Factory is Cleanup) .Single(x => x.Member.DeclaringType == executable.Member.DeclaringType); + executable.Triggers.Add(cleanup); cleanup.TriggerDependencies.Add(executable); } @@ -105,7 +111,10 @@ IEnumerable GetDependencies( { var dependency = executables.Single(x => x.Factory.Equals(artifactDependency.Key)); foreach (var artifacts in artifactDependency) - executable.ArtifactDependencies.AddRange(dependency, artifacts.Length > 0 ? artifacts : dependency.ArtifactProducts); + { + executable.ArtifactDependencies.AddRange(dependency, + artifacts.Length > 0 ? artifacts : dependency.ArtifactProducts); + } } } @@ -113,7 +122,12 @@ internal static IEnumerable GetTargetProperties(Type buildType) { // TODO: static targets? return buildType.GetAllMembers( - x => x is PropertyInfo property && new[] { typeof(Target), typeof(Setup), typeof(Cleanup) }.Contains(property.PropertyType), + x => x is PropertyInfo property && new[] + { + typeof(Target), + typeof(Setup), + typeof(Cleanup) + }.Contains(property.PropertyType), bindingFlags: ReflectionUtility.Instance, allowAmbiguity: false, filterQuasiOverridden: true).Cast(); diff --git a/src/Fallout.Build/Execution/ExecutionPlanner.cs b/src/Fallout.Build/Execution/ExecutionPlanner.cs index 67d3f6cd9..93156cbbc 100644 --- a/src/Fallout.Build/Execution/ExecutionPlanner.cs +++ b/src/Fallout.Build/Execution/ExecutionPlanner.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.Common.Utilities; @@ -29,8 +28,10 @@ public static IReadOnlyCollection GetExecutionPlan( .SelectMany(x => x.Triggers) .Except(executionPlan) .Where(executableTargets.Contains).ToList(); + invokedTargets = executionPlan.Concat(additionallyTriggered).ToList(); - } while (additionallyTriggered.Count > 0); + } + while (additionallyTriggered.Count > 0); return executionPlan.ForEachLazy(x => x.Status = ExecutionStatus.Scheduled).ToList(); } @@ -67,7 +68,9 @@ private static IReadOnlyCollection GetExecutionPlanInternal( if (!(invokedTargets != null && invokedTargets.Contains(executableTarget)) && !(invokedTargets == null && executableTarget.IsDefault) && !scheduledTargets.SelectMany(x => x.ExecutionDependencies).Contains(executableTarget)) + { continue; + } scheduledTargets.Add(executableTarget); } diff --git a/src/Fallout.Build/Execution/ExecutionStatus.cs b/src/Fallout.Build/Execution/ExecutionStatus.cs index f98f50e1d..f454ccfa6 100644 --- a/src/Fallout.Build/Execution/ExecutionStatus.cs +++ b/src/Fallout.Build/Execution/ExecutionStatus.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; +using Fallout.Common.Execution; // Moved to Fallout.Core in v11 (issue #88). Forwarded so existing consumers that reference // Fallout.Build keep resolving Fallout.Common.Execution.ExecutionStatus without a recompile. // The forwarder can be dropped in 12.0 once the plugin SDK is the canonical reference. -[assembly: TypeForwardedTo(typeof(Fallout.Common.Execution.ExecutionStatus))] +[assembly: TypeForwardedTo(typeof(ExecutionStatus))] diff --git a/src/Fallout.Build/Execution/Extensibility/BuildExtensionAttributeBase.cs b/src/Fallout.Build/Execution/Extensibility/BuildExtensionAttributeBase.cs index ad53bb3e4..1feb2d159 100644 --- a/src/Fallout.Build/Execution/Extensibility/BuildExtensionAttributeBase.cs +++ b/src/Fallout.Build/Execution/Extensibility/BuildExtensionAttributeBase.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Execution; @@ -13,6 +12,7 @@ public interface IBuildExtension public abstract class BuildExtensionAttributeBase : Attribute, IBuildExtension { public IFalloutBuild Build { get; internal set; } + public virtual float Priority { get; set; } } diff --git a/src/Fallout.Build/Execution/Extensibility/ToolInjectionAttributeBase.cs b/src/Fallout.Build/Execution/Extensibility/ToolInjectionAttributeBase.cs index 01fd5cf3c..2d5d4bec3 100644 --- a/src/Fallout.Build/Execution/Extensibility/ToolInjectionAttributeBase.cs +++ b/src/Fallout.Build/Execution/Extensibility/ToolInjectionAttributeBase.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; using Fallout.Common.ValueInjection; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Build/Execution/Extensibility/ValueInjectionAttributeBase.cs b/src/Fallout.Build/Execution/Extensibility/ValueInjectionAttributeBase.cs index 70105b78b..a79f0dd98 100644 --- a/src/Fallout.Build/Execution/Extensibility/ValueInjectionAttributeBase.cs +++ b/src/Fallout.Build/Execution/Extensibility/ValueInjectionAttributeBase.cs @@ -20,7 +20,9 @@ public object TryGetValue(MemberInfo member, object instance) catch (Exception exception) { if (!SuppressWarnings && !member.HasCustomAttribute()) + { Log.Warning(exception.Unwrap(), "Could not inject value for {Member}", member.GetDisplayName()); + } return null; } @@ -29,6 +31,7 @@ public object TryGetValue(MemberInfo member, object instance) public abstract object GetValue(MemberInfo member, object instance); public virtual int Priority => 0; + public virtual bool SuppressWarnings => false; protected T GetMemberValue(string memberName, object instance) @@ -42,7 +45,10 @@ protected T GetMemberValue(string memberName, object instance) filterQuasiOverridden: true) .FirstOrDefault() .NotNull($"No member '{memberName}' found in '{type.Name}'"); - Assert.True(typeof(T).IsAssignableFrom(member.GetMemberType()), $"Member '{type.Name}.{member.Name}' must be of type '{typeof(T).Name}'"); + + Assert.True(typeof(T).IsAssignableFrom(member.GetMemberType()), + $"Member '{type.Name}.{member.Name}' must be of type '{typeof(T).Name}'"); + return member.GetValue(instance); } diff --git a/src/Fallout.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs b/src/Fallout.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs index cb344c29c..093675c28 100644 --- a/src/Fallout.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/ArgumentsFromParametersFileAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; @@ -18,8 +17,9 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge { // TODO: probably remove if (!Constants.GetFalloutDirectory(FalloutBuild.RootDirectory).DirectoryExists()) + { return; - + } // IEnumerable ConvertToArguments(string profile, string name, string[] values) // { @@ -45,9 +45,13 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge // : value; var parameterMembers = ValueInjectionUtility.GetParameterMembers(Build.GetType(), includeUnlisted: true); - var parameterObjectsAndProfiles = new[] { (File: Constants.GetDefaultParametersFile(FalloutBuild.RootDirectory), Profile: Constants.DefaultProfileName) } + var parameterObjectsAndProfiles = new[] + { + (File: Constants.GetDefaultParametersFile(FalloutBuild.RootDirectory), Profile: Constants.DefaultProfileName) + } .Where(x => File.Exists(x.File)) - .Concat(FalloutBuild.LoadedLocalProfiles.Select(x => (File: Constants.GetParametersProfileFile(FalloutBuild.RootDirectory, x), Profile: x))) + .Concat(FalloutBuild.LoadedLocalProfiles.Select(x => + (File: Constants.GetParametersProfileFile(FalloutBuild.RootDirectory, x), Profile: x))) .ForEachLazy(x => Assert.FileExists(x.File)) .Select(x => (JsonObject: JsonNode.Parse(File.ReadAllText(x.File)).NotNull().AsObject(), x.Profile)) .Reverse(); @@ -57,7 +61,8 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge string DecryptValue(string profile, string name, string value) => EncryptionUtility.Decrypt( value, - passwords[profile] = passwords.GetValueOrDefault(profile) ?? CredentialStore.GetPassword(profile, Build.RootDirectory), + passwords[profile] = passwords.GetValueOrDefault(profile) ?? + CredentialStore.GetPassword(profile, Build.RootDirectory), name); ParameterService.Instance.ArgumentsFromFilesService = (parameter, destinationType) => @@ -65,17 +70,27 @@ string DecryptValue(string profile, string name, string value) var (value, profile) = parameterObjectsAndProfiles.Select(x => (Value: x.JsonObject[parameter], x.Profile)) .Where(x => x.Value != null) .FirstOrDefault(); + if (value == null) + { return null; + } + + var member = parameterMembers.SingleOrDefault(x => + ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(parameter)); - var member = parameterMembers.SingleOrDefault(x => ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(parameter)); var scalarType = member?.GetMemberType().GetScalarType(); if (typeof(IAbsolutePathHolder).IsAssignableFrom(scalarType)) - return value.GetValue().Apply(x => !PathConstruction.HasPathRoot(x) ? FalloutBuild.RootDirectory / x : (AbsolutePath)x); + { + return value.GetValue().Apply(x => + !PathConstruction.HasPathRoot(x) ? FalloutBuild.RootDirectory / x : (AbsolutePath)x); + } if ((member?.HasCustomAttribute() ?? false) && !BuildServerConfigurationGeneration.IsActive) + { return DecryptValue(profile, parameter, value.GetValue()); + } return value.Deserialize(destinationType); }; diff --git a/src/Fallout.Build/Execution/Extensions/EventInvoker.cs b/src/Fallout.Build/Execution/Extensions/EventInvoker.cs index d435e3d15..4dc266c2f 100644 --- a/src/Fallout.Build/Execution/Extensions/EventInvoker.cs +++ b/src/Fallout.Build/Execution/Extensions/EventInvoker.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Build/Execution/Extensions/HandleHelpRequestsAttribute.cs b/src/Fallout.Build/Execution/Extensions/HandleHelpRequestsAttribute.cs index d7a6c50db..1e3ac2583 100644 --- a/src/Fallout.Build/Execution/Extensions/HandleHelpRequestsAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/HandleHelpRequestsAttribute.cs @@ -35,10 +35,13 @@ public string GetTargetsText() var dependencies = target.ExecutionDependencies.Count > 0 ? $" -> {target.ExecutionDependencies.Select(x => x.Name).JoinCommaSpace()}" : string.Empty; + var targetEntry = target.Name + (target.IsDefault ? " (default)" : string.Empty); builder.AppendLine($" {targetEntry.PadRight(padRightTargets)}{dependencies}"); if (!string.IsNullOrWhiteSpace(target.Description)) + { builder.AppendLine($" {target.Description}"); + } } return builder.ToString(); @@ -55,12 +58,18 @@ public string GetParametersText() List SplitLines(string text) { var words = new Queue(text.Split(' ').ToList()); - var lines = new List { string.Empty }; + var lines = new List + { + string.Empty + }; + foreach (var word in words) { var nextLength = padRightParameter + 6 + lines.Last().Length + word.Length; if (nextLength >= Console.BufferWidth || nextLength > 90) + { lines.Add(string.Empty); + } lines[lines.Count - 1] = $"{lines.Last()} {word}"; } @@ -76,17 +85,23 @@ void PrintParameter(MemberInfo parameter) ?.Replace("{default_target}", defaultTargets.Count > 0 ? defaultTargets.JoinCommaSpace() : "") .TrimEnd(".").Append(".") ?? ""); + var parameterName = ParameterService.GetParameterDashedName(parameter); builder.AppendLine($" --{parameterName.PadRight(padRightParameter)} {description.First()}"); foreach (var line in description.Skip(count: 1)) + { builder.AppendLine($"{' '.Repeat(padRightParameter + 6)}{line}"); + } } builder.AppendLine("Parameters:"); var customParameters = parameters.Where(x => x.DeclaringType != typeof(FalloutBuild)).ToList(); if (customParameters.Count > 0) + { builder.AppendLine(); + } + customParameters.ForEach(PrintParameter); builder.AppendLine(); diff --git a/src/Fallout.Build/Execution/Extensions/HandlePlanRequestsAttribute.cs b/src/Fallout.Build/Execution/Extensions/HandlePlanRequestsAttribute.cs index 8d23c44aa..a4c7c0d05 100644 --- a/src/Fallout.Build/Execution/Extensions/HandlePlanRequestsAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/HandlePlanRequestsAttribute.cs @@ -40,10 +40,10 @@ public void ShowPlan() // Workaround for https://github.com/dotnet/corefx/issues/10361 Process.Start(new ProcessStartInfo - { - FileName = HtmlFile, - UseShellExecute = true - }); + { + FileName = HtmlFile, + UseShellExecute = true + }); } // ReSharper disable once CognitiveComplexity @@ -54,22 +54,32 @@ private string GetGraphDefinition() { var dependencies = Build.ExecutableTargets.Where(x => x.AllDependencies.Contains(executableTarget)).ToList(); if (dependencies.Count == 0) + { builder.AppendLine(executableTarget.Name); + } else { foreach (var dependency in dependencies) { if (dependency.ExecutionDependencies.Contains(executableTarget)) + { builder.AppendLine($"{executableTarget.Name} --> {dependency.Name}"); + } else if (dependency.OrderDependencies.Contains(executableTarget)) + { builder.AppendLine($"{executableTarget.Name} -.-> {dependency.Name}"); + } else if (dependency.TriggerDependencies.Contains(executableTarget)) + { builder.AppendLine($"{executableTarget.Name} ==> {dependency.Name}"); + } } } - if (executableTarget.Listed == false) + if (!executableTarget.Listed) + { builder.AppendLine($"class {executableTarget.Name} unlisted"); + } } return builder.ToString(); @@ -82,21 +92,31 @@ private string GetEvents() // When not hovering anything, highlight the default plan var defaultPlan = executableTargets.Where(x => x.IsDefault) - .SelectMany(x => ExecutionPlanner.GetExecutionPlan(executableTargets, new[] { x.Name })) + .SelectMany(x => ExecutionPlanner.GetExecutionPlan(executableTargets, new[] + { + x.Name + })) .Distinct().ToList(); + defaultPlan.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").addClass('highlight');")); foreach (var executableTarget in executableTargets) { - var executionPlan = ExecutionPlanner.GetExecutionPlan(executableTargets, new[] { executableTarget.Name }); + var executionPlan = ExecutionPlanner.GetExecutionPlan(executableTargets, new[] + { + executableTarget.Name + }); + builder .AppendLine($@" $(""#{executableTarget.Name}"").hover(") .AppendLine(" function() {"); + executableTargets.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").removeClass('highlight');")); executionPlan.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").addClass('highlight');")); builder .AppendLine(" },") .AppendLine(" function() {"); + executionPlan.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").removeClass('highlight');")); defaultPlan.ForEach(x => builder.AppendLine($@" $(""#{x.Name}"").addClass('highlight');")); builder diff --git a/src/Fallout.Build/Execution/Extensions/HandleReSharperSurrogateArgumentsAttribute.cs b/src/Fallout.Build/Execution/Extensions/HandleReSharperSurrogateArgumentsAttribute.cs index 4942df6bd..05c3d6a8a 100644 --- a/src/Fallout.Build/Execution/Extensions/HandleReSharperSurrogateArgumentsAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/HandleReSharperSurrogateArgumentsAttribute.cs @@ -14,7 +14,9 @@ internal class HandleReSharperSurrogateArgumentsAttribute : BuildExtensionAttrib public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (!ReSharperSurrogateFile.Exists()) + { return; + } var argumentLines = ReSharperSurrogateFile.ReadAllLines(); var lastWriteTime = File.GetLastWriteTime(ReSharperSurrogateFile); diff --git a/src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs b/src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs index d3b5a1d57..fbe23a11f 100644 --- a/src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs @@ -13,7 +13,9 @@ internal class HandleShellCompletionAttribute : BuildExtensionAttributeBase, IOn public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (BuildServerConfigurationGeneration.IsActive) + { return; + } if (IsLegacy(Build.RootDirectory)) { @@ -24,6 +26,7 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge "You can convert to the new-style .nuke directory by calling:", " fallout :update" }.JoinNewLine()); + Environment.Exit(exitCode: -1); } else if (Build.BuildProjectFile != null) @@ -36,10 +39,10 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge if (!parametersFile.Exists()) { parametersFile.WriteAllText($$""" - { - "$schema": "./{{BuildSchemaFileName}}" - } - """); + { + "$schema": "./{{BuildSchemaFileName}}" + } + """); } } else if (ParameterService.GetPositionalArgument(0) == ":complete") @@ -51,12 +54,16 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge var words = EnvironmentInfo.CommandLineArguments.Skip(2).JoinSpace(); var relevantCompletionItems = CompletionUtility.GetRelevantItems(words, completionItems); foreach (var item in relevantCompletionItems) + { Console.WriteLine(item); + } Environment.Exit(exitCode: 0); } if (ParameterService.GetParameter(CompletionParameterName)) + { Environment.Exit(exitCode: 0); + } } } diff --git a/src/Fallout.Build/Execution/Extensions/HandleVisualStudioDebuggingAttribute.cs b/src/Fallout.Build/Execution/Extensions/HandleVisualStudioDebuggingAttribute.cs index 492aeeca9..bb1075ab4 100644 --- a/src/Fallout.Build/Execution/Extensions/HandleVisualStudioDebuggingAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/HandleVisualStudioDebuggingAttribute.cs @@ -1,7 +1,5 @@ -using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading; using Fallout.Common.IO; @@ -16,7 +14,9 @@ internal class HandleVisualStudioDebuggingAttribute : BuildExtensionAttributeBas public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (!ParameterService.GetParameter(Constants.VisualStudioDebugParameterName)) + { return; + } VisualStudioDebugFile.WriteAllText(Process.GetCurrentProcess().Id.ToString()); Assert.True(SpinWait.SpinUntil(() => Debugger.IsAttached, millisecondsTimeout: TimeoutInMilliseconds), diff --git a/src/Fallout.Build/Execution/Extensions/InjectNonParameterValuesAttribute.cs b/src/Fallout.Build/Execution/Extensions/InjectNonParameterValuesAttribute.cs index 5f738ada6..5ae6a7b27 100644 --- a/src/Fallout.Build/Execution/Extensions/InjectNonParameterValuesAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/InjectNonParameterValuesAttribute.cs @@ -16,14 +16,20 @@ public void OnBuildInitialized( ValueInjectionUtility.InjectValues(Build, (member, attribute) => { if (attribute.GetType() == typeof(ParameterAttribute)) + { return false; + } if (!Build.GetType().HasCustomAttribute() && !member.HasCustomAttribute()) + { return true; + } if (member.HasCustomAttribute()) + { return false; + } var requiredMembers = executionPlan.SelectMany(x => x.DelegateRequirements) .Where(x => x is not Expression>) diff --git a/src/Fallout.Build/Execution/Extensions/InjectParameterValuesAttribute.cs b/src/Fallout.Build/Execution/Extensions/InjectParameterValuesAttribute.cs index 449c23b01..4c7c07e23 100644 --- a/src/Fallout.Build/Execution/Extensions/InjectParameterValuesAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/InjectParameterValuesAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Fallout.Common.Execution; namespace Fallout.Common.ValueInjection; diff --git a/src/Fallout.Build/Execution/Extensions/SerializeBuildGraphAttribute.cs b/src/Fallout.Build/Execution/Extensions/SerializeBuildGraphAttribute.cs index a621f3847..667836aeb 100644 --- a/src/Fallout.Build/Execution/Extensions/SerializeBuildGraphAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/SerializeBuildGraphAttribute.cs @@ -3,6 +3,7 @@ using System.Reflection; using Fallout.Common.Execution; using Fallout.Common.IO; +using Serilog; namespace Fallout.Build.Execution.Extensions; @@ -31,7 +32,7 @@ public void OnBuildInitialized( catch (Exception exception) { // Emission is a convenience for editor tooling — never let it break a build. - Serilog.Log.Verbose(exception, "Failed to emit {GraphFileName}", GraphFileName); + Log.Verbose(exception, "Failed to emit {GraphFileName}", GraphFileName); } } diff --git a/src/Fallout.Build/Execution/Extensions/SerializeBuildServerStateAttribute.cs b/src/Fallout.Build/Execution/Extensions/SerializeBuildServerStateAttribute.cs index 80eaeebd6..b4388f738 100644 --- a/src/Fallout.Build/Execution/Extensions/SerializeBuildServerStateAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/SerializeBuildServerStateAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Fallout.Common.Execution; namespace Fallout.Common.CI; @@ -13,4 +12,4 @@ public void OnBuildFinished() .FirstOrDefault(x => x.HostType == Build.Host.GetType()) ?.SerializeState(); } -} \ No newline at end of file +} diff --git a/src/Fallout.Build/Execution/Extensions/UpdateNotificationAttribute.cs b/src/Fallout.Build/Execution/Extensions/UpdateNotificationAttribute.cs index a4da113b5..6254624bb 100644 --- a/src/Fallout.Build/Execution/Extensions/UpdateNotificationAttribute.cs +++ b/src/Fallout.Build/Execution/Extensions/UpdateNotificationAttribute.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Fallout.Common.Utilities; using static Fallout.Common.Constants; @@ -22,7 +21,9 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge public void OnBuildFinished() { if (Build.IsServerBuild && ShouldNotify) + { Notify(); + } } private bool ShouldNotify => !Directory.Exists(GetFalloutDirectory(Build.RootDirectory)) && diff --git a/src/Fallout.Build/Execution/ParameterService.Statics.cs b/src/Fallout.Build/Execution/ParameterService.Statics.cs index e8e026b13..6904e3f37 100644 --- a/src/Fallout.Build/Execution/ParameterService.Statics.cs +++ b/src/Fallout.Build/Execution/ParameterService.Statics.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using Fallout.Common.Utilities; @@ -14,7 +13,7 @@ internal partial class ParameterService public static T GetParameter(string name, char? separator = null) { - return (T) Instance.GetParameter(name, typeof(T), separator); + return (T)Instance.GetParameter(name, typeof(T), separator); } public static T GetParameter(Expression> expression) @@ -29,12 +28,12 @@ public static T GetParameter(Expression> expression) public static T GetParameter(MemberInfo member, Type destinationType = null) { - return (T) GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetParameter); + return (T)GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetParameter); } public static T GetNamedArgument(string parameterName, char? separator = null) { - return (T) Instance.GetCommandLineArgument(parameterName, typeof(T), separator); + return (T)Instance.GetCommandLineArgument(parameterName, typeof(T), separator); } public static T GetNamedArgument(Expression> expression) @@ -49,17 +48,17 @@ public static T GetNamedArgument(Expression> expression) public static T GetNamedArgument(MemberInfo member, Type destinationType = null) { - return (T) GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetCommandLineArgument); + return (T)GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetCommandLineArgument); } public static T GetPositionalArgument(int position, char? separator = null) { - return (T) Instance.GetCommandLineArgument(position, typeof(T), separator); + return (T)Instance.GetCommandLineArgument(position, typeof(T), separator); } public static T[] GetAllPositionalArguments(char? separator = null) { - return (T[]) Instance.GetPositionalCommandLineArguments(typeof(T), separator); + return (T[])Instance.GetPositionalCommandLineArguments(typeof(T), separator); } public static T GetVariable(Expression> expression) @@ -74,12 +73,12 @@ public static T GetVariable(Expression> expression) public static T GetVariable(MemberInfo member, Type destinationType = null) { - return (T) GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetEnvironmentVariable); + return (T)GetFromMemberInfo(member, destinationType ?? typeof(T), Instance.GetEnvironmentVariable); } public static T GetVariable(string parameterName, char? separator = null) { - return (T) Instance.GetEnvironmentVariable(parameterName, typeof(T), separator); + return (T)Instance.GetEnvironmentVariable(parameterName, typeof(T), separator); } public static bool HasArgument(MemberInfo member) diff --git a/src/Fallout.Build/Execution/ParameterService.cs b/src/Fallout.Build/Execution/ParameterService.cs index 9bc8eb6f5..47d6506b4 100644 --- a/src/Fallout.Build/Execution/ParameterService.cs +++ b/src/Fallout.Build/Execution/ParameterService.cs @@ -28,6 +28,7 @@ public ParameterService( } private ArgumentParser ArgumentsParser => argumentParserProvider.Invoke(); + private IReadOnlyDictionary Variables => environmentVariablesProvider.Invoke(); public static bool IsParameter(string value) @@ -77,17 +78,20 @@ public static string GetParameterDescription(MemberInfo member) IEnumerable<(string Text, object Object)> TryGetFromValueProvider() { if (attribute.ValueProviderMember == null) + { return null; + } var valueProviderType = attribute.ValueProviderType ?? instance.GetType(); var valueProvider = valueProviderType .GetMember(attribute.ValueProviderMember, All) .SingleOrDefault() .NotNull($"No single provider '{valueProviderType.Name}.{member.Name}' found"); + Assert.True(valueProvider.GetMemberType() == typeof(IEnumerable), $"Value provider '{valueProvider.Name}' must be of type '{typeof(IEnumerable).GetDisplayShortName()}'"); - return valueProvider.GetValue>(instance).Select(x => (x, (object) x)); + return valueProvider.GetValue>(instance).Select(x => (x, (object)x)); } IEnumerable<(string Text, object Object)> TryGetFromEnumerationClass() => @@ -102,6 +106,7 @@ public static string GetParameterDescription(MemberInfo member) : Nullable.GetUnderlyingType(memberType) is { } underlyingType && underlyingType.IsEnum ? underlyingType : null; + return enumType != null ? enumType.GetEnumNames().Select(x => (x, Enum.Parse(enumType, x))) : null; @@ -190,13 +195,20 @@ static string GetTrimmedName(string name) var alternativeValues = Variables .Where(x => GetTrimmedName(x.Key).EqualsOrdinalIgnoreCase(trimmedVariableName) || GetTrimmedName(x.Key).EqualsOrdinalIgnoreCase($"NUKE{trimmedVariableName}")).ToList(); + if (alternativeValues.Count > 1) + { Log.Warning("Could not resolve {VariableName} since multiple values are provided", variableName); + } if (alternativeValues.Count == 1) + { value = alternativeValues.Single().Value; + } else + { return destinationType.GetDefaultValue(); + } } try @@ -205,7 +217,13 @@ static string GetTrimmedName(string name) } catch (Exception ex) { - Assert.Fail(new[] { ex.Message, $"Resolving parameter '{variableName}' failed. Environment variable was:", value }.JoinNewLine()); + Assert.Fail(new[] + { + ex.Message, + $"Resolving parameter '{variableName}' failed. Environment variable was:", + value + }.JoinNewLine()); + // ReSharper disable once HeuristicUnreachableCode return null; } diff --git a/src/Fallout.Build/Execution/TargetDefinition.cs b/src/Fallout.Build/Execution/TargetDefinition.cs index 0b9fd63a8..049ce13c4 100644 --- a/src/Fallout.Build/Execution/TargetDefinition.cs +++ b/src/Fallout.Build/Execution/TargetDefinition.cs @@ -22,29 +22,49 @@ public TargetDefinition(PropertyInfo target, IFalloutBuild build, Stack Target.GetDisplayShortName(); + public IFalloutBuild Build { get; } internal Func Intercept { get; set; } internal string Description { get; set; } + internal List<(string Text, Func Delegate)> DynamicConditions { get; } = new(); + internal List<(string Text, Func Delegate)> StaticConditions { get; } = new(); + internal List DelegateRequirements { get; } = new(); + internal List ToolRequirements { get; } = new(); + internal List DependsOnTargets { get; } = new(); + internal List DependentForTargets { get; } = new(); + internal List Actions { get; } = new(); + internal DependencyBehavior DependencyBehavior { get; private set; } + internal bool IsProceedAfterFailure { get; private set; } + internal bool IsAssuredAfterFailure { get; private set; } + internal bool IsInternal { get; private set; } + internal List BeforeTargets { get; } = new(); + internal List AfterTargets { get; } = new(); + internal List TriggersTargets { get; } = new(); + internal List TriggeredByTargets { get; } = new(); + internal int? PartitionSize { get; private set; } + internal List ArtifactProducts { get; } = new(); + internal LookupTable ArtifactDependencies { get; } = new(); ITargetDefinition ITargetDefinition.Description(string description) @@ -77,7 +97,7 @@ public ITargetDefinition DependsOn(params Target[] targets) public ITargetDefinition DependsOn(params Func[] targets) { - return DependsOn(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return DependsOn(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryDependsOn(params Func[] targets) @@ -93,7 +113,7 @@ public ITargetDefinition DependentFor(params Target[] targets) public ITargetDefinition DependentFor(params Func[] targets) { - return DependentFor(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return DependentFor(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryDependentFor(params Func[] targets) @@ -113,14 +133,16 @@ public ITargetDefinition OnlyWhenStatic(Func condition, string conditionEx return this; } - public ITargetDefinition Requires(Expression> parameterRequirement, params Expression>[] parameterRequirements) + public ITargetDefinition Requires(Expression> parameterRequirement, + params Expression>[] parameterRequirements) where T : class { DelegateRequirements.AddRange(parameterRequirement.Concat(parameterRequirements)); return this; } - public ITargetDefinition Requires(Expression> parameterRequirement, params Expression>[] parameterRequirements) + public ITargetDefinition Requires(Expression> parameterRequirement, + params Expression>[] parameterRequirements) where T : struct { DelegateRequirements.AddRange(parameterRequirement.Concat(parameterRequirements)); @@ -153,6 +175,7 @@ public ITargetDefinition Requires(Expression> tool, params Expression .Select(x => x.GetMemberInfo()) .Select(x => x.GetCustomAttribute().NotNull().GetRequirement(x)) .WhereNotNull(); + ToolRequirements.AddRange(requirements); return this; } @@ -171,7 +194,7 @@ public ITargetDefinition Before(params Target[] targets) public ITargetDefinition Before(params Func[] targets) { - return Before(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return Before(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryBefore(params Func[] targets) @@ -187,7 +210,7 @@ public ITargetDefinition After(params Target[] targets) public ITargetDefinition After(params Func[] targets) { - return After(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return After(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryAfter(params Func[] targets) @@ -203,7 +226,7 @@ public ITargetDefinition Triggers(params Target[] targets) public ITargetDefinition Triggers(params Func[] targets) { - return Triggers(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return Triggers(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryTriggers(params Func[] targets) @@ -219,7 +242,7 @@ public ITargetDefinition TriggeredBy(params Target[] targets) public ITargetDefinition TriggeredBy(params Func[] targets) { - return TriggeredBy(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return TriggeredBy(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition TryTriggeredBy(params Func[] targets) @@ -250,6 +273,7 @@ public ITargetDefinition Base() Assert.True(baseMembers.Count > 0, $"Target '{Target.DeclaringType}.{Target.Name}' does not have any base members." + $" To inherit from an interface default implementation, use {nameof(Inherit)} instead."); + Inherit(baseMembers.Pop().GetValueNonVirtual(Build)); return this; } @@ -264,7 +288,11 @@ public ITargetDefinition Inherit(params Expression>[] targets { var properties = targets.Length > 0 ? targets.Select(x => x.GetMemberInfo()) - : new[] { GetSingleTargetProperty("shorthand inheritance") }; + : new[] + { + GetSingleTargetProperty("shorthand inheritance") + }; + Inherit(properties.Select(x => x.GetValueNonVirtual(Build)).ToArray()); return this; } @@ -283,7 +311,7 @@ public ITargetDefinition Consumes(params Target[] targets) public ITargetDefinition Consumes(params Func[] targets) { - return Consumes(GetTargetsOrShorthand(targets.Select(x => x((T) Build)).ToArray())); + return Consumes(GetTargetsOrShorthand(targets.Select(x => x((T)Build)).ToArray())); } public ITargetDefinition Consumes(Target target, params string[] artifacts) @@ -294,7 +322,7 @@ public ITargetDefinition Consumes(Target target, params string[] artifacts) public ITargetDefinition Consumes(Func target, params string[] artifacts) { - return Consumes(target.Invoke((T) Build), artifacts); + return Consumes(target.Invoke((T)Build), artifacts); } public ITargetDefinition Consumes(params string[] artifacts) @@ -306,8 +334,12 @@ public ITargetDefinition DependsOnContext() where T : IFalloutBuild { Assert.True(Build is T, $"'{Build.GetType().Name}' must implement context '{typeof(T).Name}'"); - var setup = (Setup) GetSingleTargetProperty("context dependency (missing setup)", targetType: typeof(Setup)).GetValue(Build); - var cleanup = (Cleanup) GetSingleTargetProperty("context dependency (missing cleanup)", targetType: typeof(Cleanup)).GetValue(Build); + var setup = (Setup)GetSingleTargetProperty("context dependency (missing setup)", targetType: typeof(Setup)) + .GetValue(Build); + + var cleanup = (Cleanup)GetSingleTargetProperty("context dependency (missing cleanup)", targetType: typeof(Cleanup)) + .GetValue(Build); + DependsOnTargets.Add(setup); BeforeTargets.Add(cleanup); return this; @@ -324,17 +356,24 @@ private Target[] GetTargetsOrShorthand(params Target[] targets) { return targets.Length > 0 ? targets - : new[] { (Target) GetSingleTargetProperty("shorthand dependency").GetValue(Build) }; + : new[] + { + (Target)GetSingleTargetProperty("shorthand dependency").GetValue(Build) + }; } private PropertyInfo GetSingleTargetProperty(string kind, Type targetType = null) { var interfaceTargets = typeof(T).GetProperties(ReflectionUtility.Instance) .Where(x => x.PropertyType == (targetType ?? typeof(Target))).ToList(); + if (interfaceTargets.Count != 1) { Assert.Fail($"Target '{Target.DeclaringType}.{Target.Name}' cannot have {kind} on component '{typeof(T).Name}'." - .Concat(new[] { interfaceTargets.Count > 1 ? "Too many relevant targets:" : "No relevant targets." }) + .Concat(new[] + { + interfaceTargets.Count > 1 ? "Too many relevant targets:" : "No relevant targets." + }) .Concat(interfaceTargets.Select(x => $" - {x.Name}")).JoinNewLine()); } diff --git a/src/Fallout.Build/Execution/TargetExecutionException.cs b/src/Fallout.Build/Execution/TargetExecutionException.cs index d2e36c91f..7fab7d422 100644 --- a/src/Fallout.Build/Execution/TargetExecutionException.cs +++ b/src/Fallout.Build/Execution/TargetExecutionException.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Runtime.Serialization; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Build/Execution/ToolRequirementService.cs b/src/Fallout.Build/Execution/ToolRequirementService.cs index 229947250..b84883049 100644 --- a/src/Fallout.Build/Execution/ToolRequirementService.cs +++ b/src/Fallout.Build/Execution/ToolRequirementService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Fallout.Common.IO; @@ -24,7 +23,9 @@ public static void EnsureToolRequirements(IFalloutBuild build, IReadOnlyCollecti private static void InstallNuGetPackages(IReadOnlyCollection requirements, IFalloutBuild build) { if (requirements.Count == 0) + { return; + } var projectFile = build.TemporaryDirectory / "nuget.csproj"; NuGetToolPathResolver.NuGetPackagesConfigFile = projectFile; @@ -34,23 +35,25 @@ private static void InstallNuGetPackages(IReadOnlyCollection x.PackageId, x => $"[{x.Version}]"); var content = $""" - + - - net10.0 - + + net10.0 + - + - - {groupedPackages.Select(x => $""" """).JoinNewLine()} - + + {groupedPackages.Select(x => $""" """).JoinNewLine()} + - - """; + + """; if (projectFile.Exists() && projectFile.ReadAllText().StartsWith(content)) + { return; + } Log.Information("Installing NuGet packages..."); packages.ForEach(x => Log.Verbose("Installing {Id} ({Version})...", x.PackageId, x.Version)); @@ -63,7 +66,9 @@ private static void InstallNuGetPackages(IReadOnlyCollection requirements, IFalloutBuild build) { if (requirements.Count == 0) + { return; + } var packageJsonFile = build.TemporaryDirectory / "package.json"; NpmToolPathResolver.NpmPackageJsonFile = packageJsonFile; @@ -71,15 +76,17 @@ private static void InstallNpmPackages(IReadOnlyCollection x.PackageId).ToList(); var content = $$""" - { - "dependencies": { - {{packages.Select(x => $""" "{x.PackageId}": "{x.Version}",""").JoinNewLine().TrimEnd(',')}} - } - } - """; + { + "dependencies": { + {{packages.Select(x => $""" "{x.PackageId}": "{x.Version}",""").JoinNewLine().TrimEnd(',')}} + } + } + """; if (packageJsonFile.Exists() && packageJsonFile.ReadAllText().StartsWith(content)) + { return; + } Log.Information("Installing NPM packages..."); packages.ForEach(x => Log.Verbose("Installing {Id} ({Version})...", x.PackageId, x.Version)); @@ -92,7 +99,9 @@ private static void InstallNpmPackages(IReadOnlyCollection requirements, IFalloutBuild build) { if (requirements.Count == 0) + { return; + } var packages = requirements.OrderBy(x => x.PackageId).ToList(); Assert.True(EnvironmentInfo.IsLinux, "AptGet is only available on Linux"); @@ -100,13 +109,15 @@ private static void InstallAptGetPackages(IReadOnlyCollection $" {x.PackageId} \\").JoinNewLine().TrimEnd("\\")} - """; + apt-get update + apt-get install -y \ + {packages.Select(x => $" {x.PackageId} \\").JoinNewLine().TrimEnd("\\")} + """; if (installScript.Exists() && installScript.ReadAllText().StartsWith(content)) + { return; + } Log.Information("Installing AptGet packages..."); packages.ForEach(x => Log.Verbose("Installing {Id}...", x.PackageId)); diff --git a/src/Fallout.Build/Execution/ValueInjectionUtility.cs b/src/Fallout.Build/Execution/ValueInjectionUtility.cs index d1257f86c..48c0b6e60 100644 --- a/src/Fallout.Build/Execution/ValueInjectionUtility.cs +++ b/src/Fallout.Build/Execution/ValueInjectionUtility.cs @@ -38,13 +38,14 @@ object GetValue() return attribute.TryGetValue(parameter, instance); } - return (T) (valueCache[parameter] = valueCache.GetValueOrDefault(parameter) ?? GetValue()); + return (T)(valueCache[parameter] = valueCache.GetValueOrDefault(parameter) ?? GetValue()); } public static void InjectValues(T instance = default, Func filter = null) { filter ??= (_, _) => true; - InjectValuesInternal(instance, GetInjectionMembers(instance?.GetType() ?? typeof(T)).Where(x => filter(x.Member, x.Attribute))); + InjectValuesInternal(instance, + GetInjectionMembers(instance?.GetType() ?? typeof(T)).Where(x => filter(x.Member, x.Attribute))); } private static void InjectValuesInternal( @@ -59,27 +60,34 @@ private static void InjectValuesInternal( { if (member.DeclaringType == typeof(FalloutBuild) && !new[] - { - nameof(FalloutBuild.Plan), - nameof(FalloutBuild.Help), - nameof(FalloutBuild.Continue), - nameof(FalloutBuild.NoLogo), - nameof(FalloutBuild.Verbosity), - nameof(FalloutBuild.Partition) - }.Contains(member.Name)) + { + nameof(FalloutBuild.Plan), + nameof(FalloutBuild.Help), + nameof(FalloutBuild.Continue), + nameof(FalloutBuild.NoLogo), + nameof(FalloutBuild.Verbosity), + nameof(FalloutBuild.Partition) + }.Contains(member.Name)) + { continue; + } if (member.ReflectedType.NotNull().IsInterface) + { continue; + } attribute.Build = instance as IFalloutBuild; var value = attribute.TryGetValue(member, instance); if (value == null) + { continue; + } var valueType = value.GetType(); Assert.True(member.GetMemberType().IsAssignableFrom(valueType), $"Member '{member.Name}' must be of type '{valueType.Name}' to get its valued injected from '{attribute.GetType().Name}'"); + member.SetValue(instance, value); } } diff --git a/src/Fallout.Build/FalloutBuild.Events.cs b/src/Fallout.Build/FalloutBuild.Events.cs index 2b44e2a19..164b62c47 100644 --- a/src/Fallout.Build/FalloutBuild.Events.cs +++ b/src/Fallout.Build/FalloutBuild.Events.cs @@ -14,6 +14,7 @@ namespace Fallout.Common; public abstract partial class FalloutBuild { private IReadOnlyCollection BuildExtensions { get; } + IReadOnlyCollection IFalloutBuild.BuildExtensions => BuildExtensions; protected FalloutBuild() @@ -30,7 +31,8 @@ internal void ExecuteExtension(Expression> action { BuildExtensions .OfType() - .ForEachLazy(x => Log.Verbose("{Type}.{Method} ({Priority})", x.GetType().Name.TrimEnd(nameof(Attribute)), action.GetMemberInfo().Name, x.Priority)) + .ForEachLazy(x => Log.Verbose("{Type}.{Method} ({Priority})", x.GetType().Name.TrimEnd(nameof(Attribute)), + action.GetMemberInfo().Name, x.Priority)) .ForEach(action.Compile()); } diff --git a/src/Fallout.Build/FalloutBuild.Interface.cs b/src/Fallout.Build/FalloutBuild.Interface.cs index 815f1c712..aa2126bb8 100644 --- a/src/Fallout.Build/FalloutBuild.Interface.cs +++ b/src/Fallout.Build/FalloutBuild.Interface.cs @@ -10,23 +10,39 @@ namespace Fallout.Common; public abstract partial class FalloutBuild { IReadOnlyCollection IFalloutBuild.ExecutableTargets => ExecutableTargets; + bool IFalloutBuild.IsInterceptorExecution => IsInterceptorExecution; + string[] IFalloutBuild.LoadedLocalProfiles => LoadedLocalProfiles; + bool IFalloutBuild.IsOutputEnabled(DefaultOutput output) => IsOutputEnabled(output); AbsolutePath IFalloutBuild.RootDirectory => RootDirectory; + AbsolutePath IFalloutBuild.TemporaryDirectory => TemporaryDirectory; + AbsolutePath IFalloutBuild.BuildAssemblyFile => BuildAssemblyFile; + AbsolutePath IFalloutBuild.BuildAssemblyDirectory => BuildAssemblyDirectory; + AbsolutePath IFalloutBuild.BuildProjectDirectory => BuildProjectDirectory; + AbsolutePath IFalloutBuild.BuildProjectFile => BuildProjectFile; + Verbosity IFalloutBuild.Verbosity => Verbosity; + Host IFalloutBuild.Host => Host; + bool IFalloutBuild.Plan => Plan; + bool IFalloutBuild.Help => Help; + bool IFalloutBuild.NoLogo => NoLogo; + bool IFalloutBuild.IsLocalBuild => IsLocalBuild; + bool IFalloutBuild.IsServerBuild => IsServerBuild; + bool IFalloutBuild.Continue => Continue; T IFalloutBuild.TryGetValue(Expression> parameterExpression) diff --git a/src/Fallout.Build/FalloutBuild.Output.cs b/src/Fallout.Build/FalloutBuild.Output.cs index b8eab31fa..e32a7efba 100644 --- a/src/Fallout.Build/FalloutBuild.Output.cs +++ b/src/Fallout.Build/FalloutBuild.Output.cs @@ -10,10 +10,14 @@ partial class FalloutBuild internal void WriteLogo() { if (IsInterceptorExecution) + { return; + } if (IsOutputEnabled(DefaultOutput.Logo)) + { Host.WriteLogo(); + } Host.Information($"Fallout Execution Engine {typeof(FalloutBuild).Assembly.GetInformationalText()}"); Host.Information(); @@ -25,11 +29,15 @@ bool CanCollapse() => Host.GetType().GetMethod(nameof(Host.WriteBlock), ReflectionUtility.Instance | BindingFlags.DeclaredOnly) != null; if (IsInterceptorExecution) + { return DelegateDisposable.CreateBracket(); + } - if (IsOutputEnabled(DefaultOutput.TargetHeader) && !CanCollapse() || - IsOutputEnabled(DefaultOutput.TargetCollapse) && CanCollapse()) + if ((IsOutputEnabled(DefaultOutput.TargetHeader) && !CanCollapse()) || + (IsOutputEnabled(DefaultOutput.TargetCollapse) && CanCollapse())) + { return Host.WriteBlock(target); + } return DelegateDisposable.CreateBracket(); } @@ -37,28 +45,40 @@ bool CanCollapse() => internal void WriteErrorsAndWarnings() { if (IsInterceptorExecution) + { return; + } if (IsOutputEnabled(DefaultOutput.ErrorsAndWarnings)) + { Host.WriteErrorsAndWarnings(); + } } internal void WriteTargetOutcome() { if (IsInterceptorExecution) + { return; + } if (IsOutputEnabled(DefaultOutput.TargetOutcome)) + { Host.WriteTargetOutcome(this); + } } internal void WriteBuildOutcome() { if (IsInterceptorExecution) + { return; + } if (IsOutputEnabled(DefaultOutput.BuildOutcome)) + { Host.WriteBuildOutcome(this); + } } private bool IsOutputEnabled(DefaultOutput output) diff --git a/src/Fallout.Build/FalloutBuild.Statics.cs b/src/Fallout.Build/FalloutBuild.Statics.cs index 0f2415122..7695ae204 100644 --- a/src/Fallout.Build/FalloutBuild.Statics.cs +++ b/src/Fallout.Build/FalloutBuild.Statics.cs @@ -80,23 +80,28 @@ public static Verbosity Verbosity public static string[] LoadedLocalProfiles { get; } public static bool IsLocalBuild => !IsServerBuild; + public static bool IsServerBuild => Host is IBuildServer; private static AbsolutePath GetRootDirectory() { var parameterValue = ParameterService.GetParameter(() => RootDirectory); if (parameterValue != null) + { return parameterValue; + } if (ParameterService.GetParameter(() => RootDirectory)) + { return EnvironmentInfo.WorkingDirectory; + } return TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory) .NotNull(new[] - { - $"Could not locate '{FalloutDirectoryName}' directory/file while walking up from '{EnvironmentInfo.WorkingDirectory}'.", - "Either create a directory/file to mark the root directory, or add '--root [path]' to the invocation." - }.JoinNewLine()); + { + $"Could not locate '{FalloutDirectoryName}' directory/file while walking up from '{EnvironmentInfo.WorkingDirectory}'.", + "Either create a directory/file to mark the root directory, or add '--root [path]' to the invocation." + }.JoinNewLine()); } private static AbsolutePath GetBuildAssemblyFile() @@ -109,6 +114,7 @@ private static AbsolutePath GetBuildAssemblyFile() assemblyName.StartsWith("ReSharperTestRunner") || assemblyName == "testhost", $"Assembly name was {assemblyName.SingleQuote()}"); + return null; } @@ -122,7 +128,9 @@ private static AbsolutePath GetBuildAssemblyFile() private static AbsolutePath GetBuildProjectFile(AbsolutePath buildAssemblyDirectory) { if (buildAssemblyDirectory == null) + { return null; + } return new DirectoryInfo(buildAssemblyDirectory) .DescendantsAndSelf(x => x.Parent) diff --git a/src/Fallout.Build/FalloutBuild.cs b/src/Fallout.Build/FalloutBuild.cs index 824905fb8..c95aee156 100644 --- a/src/Fallout.Build/FalloutBuild.cs +++ b/src/Fallout.Build/FalloutBuild.cs @@ -90,32 +90,38 @@ protected static int Execute(params Expression>[] defaultTarg [Parameter("List of targets to be skipped. Empty list skips all dependencies.", Name = SkippedTargetsParameterName, Separator = TargetsSeparator)] - public IReadOnlyCollection SkippedTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Skipped).ToList(); + public IReadOnlyCollection SkippedTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Skipped).ToList(); /// /// Gets the list of targets that are scheduled. /// - public IReadOnlyCollection ScheduledTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Scheduled).ToList(); + public IReadOnlyCollection ScheduledTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Scheduled).ToList(); /// /// Gets the list of targets that are running. /// - public IReadOnlyCollection RunningTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Running).ToList(); + public IReadOnlyCollection RunningTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Running).ToList(); /// /// Gets the list of targets that were aborted. /// - public IReadOnlyCollection AbortedTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Aborted).ToList(); + public IReadOnlyCollection AbortedTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Aborted).ToList(); /// /// Gets the list of targets that have failed. /// - public IReadOnlyCollection FailedTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Failed).ToList(); + public IReadOnlyCollection FailedTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Failed).ToList(); /// /// Gets the list of targets that have succeeded. /// - public IReadOnlyCollection SucceededTargets => ExecutionPlan.Where(x => x.Status == ExecutionStatus.Succeeded).ToList(); + public IReadOnlyCollection SucceededTargets => + ExecutionPlan.Where(x => x.Status == ExecutionStatus.Succeeded).ToList(); /// /// Gets the list of targets that have been finished (failed or succeeded). @@ -171,7 +177,9 @@ protected static int Execute(params Expression>[] defaultTarg : GlobalFalloutDirectory / "packages" : null; - internal IEnumerable TargetNames => ExecutableTargetFactory.GetTargetProperties(GetType()).Select(x => x.GetDisplayShortName()); + internal IEnumerable TargetNames => + ExecutableTargetFactory.GetTargetProperties(GetType()).Select(x => x.GetDisplayShortName()); + internal IEnumerable HostNames => Host.AvailableTypes.Select(x => x.Name); public bool IsSucceeding => !IsFailing; @@ -190,7 +198,8 @@ ExecutionStatus.Aborted or /// public int? ExitCode { get; set; } - private bool IsInterceptorExecution => LegacyEnvironment.Read(InterceptorEnvironmentKey, LegacyInterceptorEnvironmentKey) == "1"; + private bool IsInterceptorExecution => + LegacyEnvironment.Read(InterceptorEnvironmentKey, LegacyInterceptorEnvironmentKey) == "1"; public void ReportSummary(Configure> configurator = null) { @@ -200,7 +209,9 @@ public void ReportSummary(Configure> configurator = n internal void ReportSummary(ExecutableTarget target, Configure> configurator) { - target.SummaryInformation = configurator.InvokeSafe(new Dictionary()).ToDictionary(x => x.Key, x => x.Value); + target.SummaryInformation = + configurator.InvokeSafe(new Dictionary()).ToDictionary(x => x.Key, x => x.Value); + ExecuteExtension(x => x.OnTargetSummaryUpdated(this, target)); } } diff --git a/src/Fallout.Build/Host.Theming.cs b/src/Fallout.Build/Host.Theming.cs index 5ce359718..6dbf95ecb 100644 --- a/src/Fallout.Build/Host.Theming.cs +++ b/src/Fallout.Build/Host.Theming.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Execution; +using Fallout.Common.Execution; namespace Fallout.Common; diff --git a/src/Fallout.Build/Host.cs b/src/Fallout.Build/Host.cs index e0e8bdbac..1ab513d1f 100644 --- a/src/Fallout.Build/Host.cs +++ b/src/Fallout.Build/Host.cs @@ -95,7 +95,9 @@ protected internal virtual bool FilterMessage(string message) protected internal virtual void WriteErrorsAndWarnings() { if (Logging.InMemorySink.Instance.LogEvents.Count == 0) + { return; + } // TODO: move to Logging using (WriteBlock("Errors & Warnings")) @@ -191,9 +193,13 @@ protected internal virtual void WriteBuildOutcome(IFalloutBuild build) { Debug(); if (build.IsSucceeding) + { Success($"Build succeeded on {DateTime.Now.ToString(CultureInfo.CurrentCulture)}. \(^ᴗ^)/"); + } else + { Error($"Build failed on {DateTime.Now.ToString(CultureInfo.CurrentCulture)}. (╯°□°)╯︵ ┻━┻"); + } } internal class LogEventSink : ILogEventSink @@ -208,9 +214,13 @@ public LogEventSink(Host host) public void Emit(LogEvent logEvent) { if (logEvent.Level is LogEventLevel.Warning) + { host.ReportWarning(logEvent.RenderMessage(), logEvent.Exception?.ToString()); + } else if (logEvent.Level is LogEventLevel.Error or LogEventLevel.Fatal) + { host.ReportError(logEvent.RenderMessage(), logEvent.Exception?.ToString()); + } } } } diff --git a/src/Fallout.Build/IFalloutBuild.cs b/src/Fallout.Build/IFalloutBuild.cs index 305990f44..6767b4b86 100644 --- a/src/Fallout.Build/IFalloutBuild.cs +++ b/src/Fallout.Build/IFalloutBuild.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using Fallout.Common.CI; using Fallout.Common.Execution; @@ -14,41 +13,69 @@ public interface IFalloutBuild void ReportSummary(Configure> configurator = null); internal IReadOnlyCollection ExecutableTargets { get; } + internal IReadOnlyCollection BuildExtensions { get; } + internal bool IsInterceptorExecution { get; } + internal string[] LoadedLocalProfiles { get; } + internal bool IsOutputEnabled(DefaultOutput output); IReadOnlyCollection ExecutionPlan { get; } + IReadOnlyCollection InvokedTargets { get; } + IReadOnlyCollection SkippedTargets { get; } + IReadOnlyCollection ScheduledTargets { get; } + IReadOnlyCollection RunningTargets { get; } + IReadOnlyCollection AbortedTargets { get; } + IReadOnlyCollection FailedTargets { get; } + IReadOnlyCollection SucceededTargets { get; } + IReadOnlyCollection FinishedTargets { get; } bool IsSucceeding { get; } + bool IsFailing { get; } + bool IsFinished { get; } + int? ExitCode { get; set; } AbsolutePath RootDirectory { get; } + AbsolutePath TemporaryDirectory { get; } + AbsolutePath BuildAssemblyFile { get; } + AbsolutePath BuildAssemblyDirectory { get; } + AbsolutePath BuildProjectDirectory { get; } + AbsolutePath BuildProjectFile { get; } Verbosity Verbosity { get; } + Host Host { get; } + bool Plan { get; } + bool Help { get; } + bool NoLogo { get; } + bool IsLocalBuild { get; } + bool IsServerBuild { get; } + bool Continue { get; } + Partition Partition { get; } T TryGetValue(Expression> parameterExpression) where T : class; diff --git a/src/Fallout.Build/ITargetDefinition.cs b/src/Fallout.Build/ITargetDefinition.cs index 6e7af941a..deff519ed 100644 --- a/src/Fallout.Build/ITargetDefinition.cs +++ b/src/Fallout.Build/ITargetDefinition.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -65,12 +64,16 @@ public interface ITargetDefinition /// /// Adds a set of conditions that will be checked before executing this target. /// - ITargetDefinition OnlyWhenDynamic(Func condition, [CallerArgumentExpression("condition")] string conditionExpression = null); + ITargetDefinition OnlyWhenDynamic(Func condition, + [CallerArgumentExpression("condition")] + string conditionExpression = null); /// /// Adds a set of conditions that will be checked prior to build execution. /// - ITargetDefinition OnlyWhenStatic(Func condition, [CallerArgumentExpression("condition")] string conditionExpression = null); + ITargetDefinition OnlyWhenStatic(Func condition, + [CallerArgumentExpression("condition")] + string conditionExpression = null); /// /// Adds a required parameter that will be checked prior to build execution. diff --git a/src/Fallout.Build/LevelMapping.cs b/src/Fallout.Build/LevelMapping.cs index 26f6f25c1..e0bd4ee40 100644 --- a/src/Fallout.Build/LevelMapping.cs +++ b/src/Fallout.Build/LevelMapping.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Serilog.Events; namespace Fallout.Common; diff --git a/src/Fallout.Build/LogLevel.cs b/src/Fallout.Build/LogLevel.cs index 778fc497f..903b22af9 100644 --- a/src/Fallout.Build/LogLevel.cs +++ b/src/Fallout.Build/LogLevel.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common; /// diff --git a/src/Fallout.Build/Logging.cs b/src/Fallout.Build/Logging.cs index 978674058..714265f2b 100644 --- a/src/Fallout.Build/Logging.cs +++ b/src/Fallout.Build/Logging.cs @@ -19,13 +19,17 @@ public static class Logging { public static readonly LoggingLevelSwitch LevelSwitch = new(); - internal static bool SupportsAnsiOutput => Environment.GetEnvironmentVariable("TERM") is { } term && term.StartsWithOrdinalIgnoreCase("xterm"); + internal static bool SupportsAnsiOutput => + Environment.GetEnvironmentVariable("TERM") is { } term && term.StartsWithOrdinalIgnoreCase("xterm"); + internal static IHostTheme DefaultTheme { get; } = SupportsAnsiOutput ? AnsiConsoleHostTheme.Default256AnsiColorTheme : SystemConsoleHostTheme.DefaultSystemColorTheme; internal static string ErrorsAndWarningsOutputTemplate => "[{Level:u3}] {ExecutingTarget}: {Message:l}{NewLine}"; + internal static string StandardOutputTemplate => "[{Level:u3}] {Message:l}{NewLine}{Exception}"; + internal static string TimestampOutputTemplate => $"{{Timestamp:HH:mm:ss}} {StandardOutputTemplate}"; private const int TargetNameLength = 20; @@ -45,6 +49,7 @@ public static void Configure(IFalloutBuild build = null) Log.Logger = new LoggerConfiguration() .WriteTo.Console(new CompactJsonFormatter()) .CreateLogger(); + return; } @@ -75,7 +80,9 @@ public static LoggerConfiguration ConfigureLevel(this LoggerConfiguration config public static LoggerConfiguration ConfigureFilter(this LoggerConfiguration configuration, IFalloutBuild build) { if (build == null) + { return configuration; + } return configuration.Filter.ByExcluding(x => build.Host.FilterMessage(x.MessageTemplate.Text)); } @@ -94,7 +101,9 @@ public static LoggerConfiguration ConfigureConsole(this LoggerConfiguration conf public static LoggerConfiguration ConfigureHost(this LoggerConfiguration configuration, IFalloutBuild build) { if (build == null) + { return configuration; + } return configuration .WriteTo.Sink(new Host.LogEventSink(build.Host), restrictedToMinimumLevel: LogEventLevel.Warning); @@ -103,7 +112,9 @@ public static LoggerConfiguration ConfigureHost(this LoggerConfiguration configu public static LoggerConfiguration ConfigureInMemory(this LoggerConfiguration configuration, IFalloutBuild build) { if (build == null) + { return configuration; + } return configuration .WriteTo.Sink(InMemorySink.Instance, LogEventLevel.Warning); @@ -112,22 +123,28 @@ public static LoggerConfiguration ConfigureInMemory(this LoggerConfiguration con public static LoggerConfiguration ConfigureFiles(this LoggerConfiguration configuration, IFalloutBuild build) { if (build == null || build.Host is IBuildServer) + { return configuration; + } var buildLogFile = build.TemporaryDirectory / "build.log"; return configuration .WriteTo.File( path: buildLogFile, - outputTemplate: $"{{Timestamp:HH:mm:ss.fff}} | {{Level:u1}} | {{ExecutingTarget,-{TargetNameLength}}} | {{Message:l}}{{NewLine}}{{Exception}}") + outputTemplate: + $"{{Timestamp:HH:mm:ss.fff}} | {{Level:u1}} | {{ExecutingTarget,-{TargetNameLength}}} | {{Message:l}}{{NewLine}}{{Exception}}") .WriteTo.File( path: Path.ChangeExtension(buildLogFile, $".{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log"), - outputTemplate: $"{{Level:u1}} | {{ExecutingTarget,-{TargetNameLength}}} | {{Message:l}}{{NewLine}}{{Exception}}"); + outputTemplate: + $"{{Level:u1}} | {{ExecutingTarget,-{TargetNameLength}}} | {{Message:l}}{{NewLine}}{{Exception}}"); } private static void DeleteOldLogFiles(IFalloutBuild build) { if (BuildServerConfigurationGeneration.IsActive) + { return; + } build.TemporaryDirectory.GlobFiles("build.*.log").OrderByDescending(x => x.ToString()).Skip(5) .ForEach(x => x.DeleteFile()); @@ -146,29 +163,53 @@ internal static void Test() const string Reset = "\u001b[0m"; for (var i = 30; i < 47; i++) + { Console.Write($"{Esc}{i}m{i} {Reset} "); + } + Console.WriteLine(); for (var i = 30; i < 47; i++) + { Console.Write($"{Esc}{i};1m{i};1{Reset} "); + } + Console.WriteLine(); for (var i = 30; i < 47; i++) + { Console.Write($"{Esc}{i};2m{i};2{Reset} "); + } + Console.WriteLine(); for (var i = 30; i < 47; i++) + { Console.Write($"{Esc}{i};3m{i};2{Reset} "); + } + Console.WriteLine(); for (var i = 90; i < 107; i++) + { Console.Write($"{Esc}{i}m{i} {Reset} "); + } + Console.WriteLine(); for (var i = 90; i < 107; i++) + { Console.Write($"{Esc}{i};1m{i};1{Reset} "); + } + Console.WriteLine(); for (var i = 90; i < 107; i++) + { Console.Write($"{Esc}{i};2m{i};2{Reset} "); + } + Console.WriteLine(); for (var i = 90; i < 107; i++) + { Console.Write($"{Esc}{i};3m{i};2{Reset} "); + } + Console.WriteLine(); for (var i = 0; i < 255; i++) @@ -176,7 +217,9 @@ internal static void Test() var code = i.ToString().PadLeft(3, '0'); Console.Write($"{Esc}38;5;{code}m{code}{Reset} "); if ((i + 1) % 16 == 0) + { Console.WriteLine(); + } } Console.WriteLine(); @@ -186,7 +229,9 @@ internal static void Test() var code = i.ToString().PadLeft(3, '0'); Console.Write($"{Esc}38;5;{code};1m{code}{Reset} "); if ((i + 1) % 16 == 0) + { Console.WriteLine(); + } } } diff --git a/src/Fallout.Build/ParameterAttribute.cs b/src/Fallout.Build/ParameterAttribute.cs index 8767af603..d4df99d16 100644 --- a/src/Fallout.Build/ParameterAttribute.cs +++ b/src/Fallout.Build/ParameterAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; @@ -83,7 +82,7 @@ public class OnDemandValueInjectionAttribute : Attribute; [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class OnDemandAttribute : Attribute; -[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)] +[AttributeUsage(AttributeTargets.Interface)] public class ParameterPrefixAttribute : Attribute { public ParameterPrefixAttribute(string prefix) diff --git a/src/Fallout.Build/RequiresAttribute.cs b/src/Fallout.Build/RequiresAttribute.cs index ffde922ed..729845fbe 100644 --- a/src/Fallout.Build/RequiresAttribute.cs +++ b/src/Fallout.Build/RequiresAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; using Fallout.Common.Tooling; diff --git a/src/Fallout.Build/Target.cs b/src/Fallout.Build/Target.cs index 38dd3c3e8..b31363989 100644 --- a/src/Fallout.Build/Target.cs +++ b/src/Fallout.Build/Target.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.Common.Execution; diff --git a/src/Fallout.Build/Terminal.cs b/src/Fallout.Build/Terminal.cs index 2ed103e21..35f7caf84 100644 --- a/src/Fallout.Build/Terminal.cs +++ b/src/Fallout.Build/Terminal.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Build/Theming/AnsiConsoleHostTheme.cs b/src/Fallout.Build/Theming/AnsiConsoleHostTheme.cs index b6ecd58d0..ef3fc428d 100644 --- a/src/Fallout.Build/Theming/AnsiConsoleHostTheme.cs +++ b/src/Fallout.Build/Theming/AnsiConsoleHostTheme.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Utilities; using Serilog.Sinks.SystemConsole.Themes; namespace Fallout.Common.Execution.Theming; -public class AnsiConsoleHostTheme : AnsiConsoleTheme, IHostTheme +public class AnsiConsoleHostTheme( + string successCode, + IReadOnlyDictionary styles) + : AnsiConsoleTheme(styles), IHostTheme { public static IHostTheme Default256AnsiColorTheme => new AnsiConsoleHostTheme( "\u001B[32;1m", @@ -30,19 +32,9 @@ public class AnsiConsoleHostTheme : AnsiConsoleTheme, IHostTheme [ConsoleThemeStyle.LevelFatal] = "\u001B[38;5;231;1m\u001B[48;5;196m" }); - private readonly string successCode; - private readonly IReadOnlyDictionary styles; + private readonly IReadOnlyDictionary styles = styles; private const string AnsiStyleReset = "\u001B[0m"; - public AnsiConsoleHostTheme( - string successCode, - IReadOnlyDictionary styles) - : base(styles) - { - this.successCode = successCode; - this.styles = styles; - } - public void WriteSuccess(string text) { Write(text, successCode); diff --git a/src/Fallout.Build/Theming/IHostTheme.cs b/src/Fallout.Build/Theming/IHostTheme.cs index 628e607da..e255227ec 100644 --- a/src/Fallout.Build/Theming/IHostTheme.cs +++ b/src/Fallout.Build/Theming/IHostTheme.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Execution.Theming; +namespace Fallout.Common.Execution.Theming; public interface IHostTheme { diff --git a/src/Fallout.Build/Theming/SystemConsoleHostTheme.cs b/src/Fallout.Build/Theming/SystemConsoleHostTheme.cs index aeab54e83..62a7c31b5 100644 --- a/src/Fallout.Build/Theming/SystemConsoleHostTheme.cs +++ b/src/Fallout.Build/Theming/SystemConsoleHostTheme.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Fallout.Common.Utilities; -using Serilog; using Serilog.Sinks.SystemConsole.Themes; namespace Fallout.Common.Execution.Theming; @@ -9,25 +8,71 @@ namespace Fallout.Common.Execution.Theming; public class SystemConsoleHostTheme : SystemConsoleTheme, IHostTheme { public static IHostTheme DefaultSystemColorTheme => new SystemConsoleHostTheme( - new SystemConsoleThemeStyle { Foreground = ConsoleColor.Green }, + new SystemConsoleThemeStyle + { + Foreground = ConsoleColor.Green + }, new Dictionary { [ConsoleThemeStyle.Text] = new(), - [ConsoleThemeStyle.SecondaryText] = new() { Foreground = ConsoleColor.Gray }, - [ConsoleThemeStyle.TertiaryText] = new() { Foreground = ConsoleColor.Gray }, - [ConsoleThemeStyle.Name] = new() { Foreground = ConsoleColor.Blue }, - [ConsoleThemeStyle.Invalid] = new() { Foreground = ConsoleColor.DarkRed }, - [ConsoleThemeStyle.Null] = new() { Foreground = ConsoleColor.Magenta }, - [ConsoleThemeStyle.String] = new() { Foreground = ConsoleColor.Magenta }, - [ConsoleThemeStyle.Number] = new() { Foreground = ConsoleColor.Magenta }, - [ConsoleThemeStyle.Boolean] = new() { Foreground = ConsoleColor.Magenta }, - [ConsoleThemeStyle.Scalar] = new() { Foreground = ConsoleColor.Magenta }, - [ConsoleThemeStyle.LevelVerbose] = new() { Foreground = ConsoleColor.Gray }, + [ConsoleThemeStyle.SecondaryText] = new() + { + Foreground = ConsoleColor.Gray + }, + [ConsoleThemeStyle.TertiaryText] = new() + { + Foreground = ConsoleColor.Gray + }, + [ConsoleThemeStyle.Name] = new() + { + Foreground = ConsoleColor.Blue + }, + [ConsoleThemeStyle.Invalid] = new() + { + Foreground = ConsoleColor.DarkRed + }, + [ConsoleThemeStyle.Null] = new() + { + Foreground = ConsoleColor.Magenta + }, + [ConsoleThemeStyle.String] = new() + { + Foreground = ConsoleColor.Magenta + }, + [ConsoleThemeStyle.Number] = new() + { + Foreground = ConsoleColor.Magenta + }, + [ConsoleThemeStyle.Boolean] = new() + { + Foreground = ConsoleColor.Magenta + }, + [ConsoleThemeStyle.Scalar] = new() + { + Foreground = ConsoleColor.Magenta + }, + [ConsoleThemeStyle.LevelVerbose] = new() + { + Foreground = ConsoleColor.Gray + }, [ConsoleThemeStyle.LevelDebug] = new(), - [ConsoleThemeStyle.LevelInformation] = new() { Foreground = ConsoleColor.Cyan }, - [ConsoleThemeStyle.LevelWarning] = new() { Foreground = ConsoleColor.Yellow }, - [ConsoleThemeStyle.LevelError] = new() { Foreground = ConsoleColor.Red }, - [ConsoleThemeStyle.LevelFatal] = new() { Foreground = ConsoleColor.White, Background = ConsoleColor.Red } + [ConsoleThemeStyle.LevelInformation] = new() + { + Foreground = ConsoleColor.Cyan + }, + [ConsoleThemeStyle.LevelWarning] = new() + { + Foreground = ConsoleColor.Yellow + }, + [ConsoleThemeStyle.LevelError] = new() + { + Foreground = ConsoleColor.Red + }, + [ConsoleThemeStyle.LevelFatal] = new() + { + Foreground = ConsoleColor.White, + Background = ConsoleColor.Red + } }); private readonly SystemConsoleThemeStyle successStyle; diff --git a/src/Fallout.Build/Utilities/ConsoleUtility.cs b/src/Fallout.Build/Utilities/ConsoleUtility.cs index 2f8fcb90e..eb4a33de2 100644 --- a/src/Fallout.Build/Utilities/ConsoleUtility.cs +++ b/src/Fallout.Build/Utilities/ConsoleUtility.cs @@ -38,7 +38,9 @@ internal static bool IsValidInputKey(ConsoleKeyInfo key) => public static string PromptForInput(string question, string defaultValue) { if (IsInterrupted) + { return defaultValue; + } Host.Information(question); @@ -63,12 +65,19 @@ public static string PromptForInput(string question, string defaultValue) key = ConsoleWrapper.ReadKey(intercept: true); if (IsValidInputKey(key)) + { input.Append(key.KeyChar); + } else if (key.Key == ConsoleKey.Backspace && input.Length > 0) + { input.Remove(input.Length - 1, length: 1); + } else if (key.Key == InterruptKey) + { IsInterrupted = true; - } while (key.Key is not (ConfirmationKey or InterruptKey)); + } + } + while (key.Key is not (ConfirmationKey or InterruptKey)); var result = input.Length > 0 ? input.ToString() : defaultValue; ConsoleWrapper.CursorLeft = 0; @@ -80,7 +89,9 @@ public static string PromptForInput(string question, string defaultValue) public static T PromptForChoice(string question, params (T Value, string Description)[] options) { if (IsInterrupted) + { return options.First().Value; + } var selection = 0; ConsoleKey key; @@ -99,17 +110,25 @@ public static T PromptForChoice(string question, params (T Value, string Desc key = ConsoleWrapper.ReadKey(intercept: true).Key; if (key == ConsoleKey.UpArrow) + { selection--; + } else if (key == ConsoleKey.DownArrow) + { selection++; + } else if (key == InterruptKey) + { IsInterrupted = true; + } selection = Math.Max(val1: 0, Math.Min(options.Length - 1, selection)); ConsoleWrapper.CursorTop -= options.Length; foreach (var unused in options) + { ConsoleWrapper.WriteLine(' '.Repeat(BufferWidth)); + } ConsoleWrapper.CursorTop -= options.Length; } @@ -133,8 +152,8 @@ public static string ReadSecret() if (secret.Length > 0) { var charsToRemove = - (key.Modifiers & ConsoleModifiers.Control) != 0 && !EnvironmentInfo.IsOsx || - (key.Modifiers & ConsoleModifiers.Alt) != 0 && EnvironmentInfo.IsOsx + ((key.Modifiers & ConsoleModifiers.Control) != 0 && !EnvironmentInfo.IsOsx) || + ((key.Modifiers & ConsoleModifiers.Alt) != 0 && EnvironmentInfo.IsOsx) ? secret.Length : 1; diff --git a/src/Fallout.Build/Utilities/CredentialStore.cs b/src/Fallout.Build/Utilities/CredentialStore.cs index 58c2aa9af..fb0ad5e80 100644 --- a/src/Fallout.Build/Utilities/CredentialStore.cs +++ b/src/Fallout.Build/Utilities/CredentialStore.cs @@ -16,7 +16,9 @@ public static void DeletePassword(string name) $"delete-generic-password -a {EnvironmentInfo.Variables["LOGNAME"]} -s {name.DoubleQuoteIfNeeded()}", logInvocation: false, logOutput: false).AssertZeroExitCode(); + break; + default: throw new NotSupportedException(EnvironmentInfo.Platform.ToString()); } @@ -32,7 +34,9 @@ public static void SavePassword(string name, string password) $"add-generic-password -T \"\" -a {EnvironmentInfo.Variables["LOGNAME"]} -s {name.DoubleQuoteIfNeeded()} -w {password}", logInvocation: false, logOutput: false).AssertZeroExitCode(); + break; + default: throw new NotSupportedException(EnvironmentInfo.Platform.ToString()); } @@ -48,10 +52,12 @@ public static string TryGetPassword(string name) $"find-generic-password -w -a {EnvironmentInfo.Variables["LOGNAME"]} -s {name.DoubleQuoteIfNeeded()}", logInvocation: false, logOutput: false); + process.WaitForExit(); return process.ExitCode == 0 ? process.Output.Single().Text : null; + default: return null; } @@ -81,12 +87,15 @@ private static string TryGetLegacyPasswordWithWarning(string legacyName, string { var password = TryGetPassword(legacyName); if (password == null) + { return null; + } Console.Error.WriteLine( $"warning FALLOUT003: Found credentials under legacy keychain entry '{legacyName}'. " + $"Falling back to legacy entry for this run. Re-run `dotnet fallout :secrets` to migrate to '{newName}'. " + "The legacy entry will no longer be read in 11.0."); + return password; } diff --git a/src/Fallout.Build/Utilities/IConsole.cs b/src/Fallout.Build/Utilities/IConsole.cs index 52cfc69b0..f94500105 100644 --- a/src/Fallout.Build/Utilities/IConsole.cs +++ b/src/Fallout.Build/Utilities/IConsole.cs @@ -6,8 +6,11 @@ namespace Fallout.Build.Utilities; public interface IConsole { int BufferWidth { get; } + int CursorLeft { get; set; } + int CursorTop { get; set; } + void Write(string value, Color? color = null); void WriteLine(); void WriteLine(string value, Color? color = null); diff --git a/src/Fallout.Build/Utilities/SchemaUtility.cs b/src/Fallout.Build/Utilities/SchemaUtility.cs index 9dd1c6374..7a126dcaa 100644 --- a/src/Fallout.Build/Utilities/SchemaUtility.cs +++ b/src/Fallout.Build/Utilities/SchemaUtility.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -7,7 +6,6 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; -using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; @@ -55,8 +53,12 @@ private static JsonObject BuildSchema(IFalloutBuild build) .Select(x => x.GetDisplayShortName()) .OrderBy(x => x, StringComparer.OrdinalIgnoreCase) .ToList(); + ctx.Definitions["ExecutableTarget"] = targetNames.Count == 0 - ? new JsonObject { ["type"] = "string" } + ? new JsonObject + { + ["type"] = "string" + } : StringEnumSchema(targetNames); // Reflected, not hardcoded: the list used to be spelled out here and drifted from the enum @@ -94,7 +96,9 @@ private static JsonObject BuildSchema(IFalloutBuild build) } if (userProperties != null) + { userSchema["properties"] = userProperties; + } // BuildProjectFile is read by the Fallout global tool's in-tool runner from .fallout/parameters.json // (see Fallout.Cli.BuildProjectResolver). It's not a [Parameter] on the build itself, but we @@ -103,14 +107,27 @@ private static JsonObject BuildSchema(IFalloutBuild build) baseProperties["BuildProjectFile"] = new JsonObject { ["type"] = new JsonArray("null", "string"), - ["description"] = "Path to the build project (.csproj) relative to the repository root. Defaults to 'build/_build.csproj' when unset. Read by the Fallout global tool's in-tool runner." + ["description"] = + "Path to the build project (.csproj) relative to the repository root. Defaults to 'build/_build.csproj' when unset. Read by the Fallout global tool's in-tool runner." }; // Force the framework parameters Skip/Target to reference the ExecutableTarget definition. if (baseProperties[InvokedTargetsParameterName] is JsonObject targetProp) - targetProp["items"] = new JsonObject { ["$ref"] = DefinitionsPrefix + "ExecutableTarget" }; + { + targetProp["items"] = new JsonObject + { + ["$ref"] = DefinitionsPrefix + "ExecutableTarget" + }; + } + if (baseProperties[SkippedTargetsParameterName] is JsonObject skipProp) - skipProp["items"] = new JsonObject { ["$ref"] = DefinitionsPrefix + "ExecutableTarget" }; + { + skipProp["items"] = new JsonObject + { + ["$ref"] = DefinitionsPrefix + "ExecutableTarget" + }; + } + ReplaceWithRef(baseProperties, nameof(FalloutBuild.Host), "Host"); ReplaceWithRef(baseProperties, nameof(FalloutBuild.Verbosity), "Verbosity"); @@ -121,11 +138,22 @@ private static JsonObject BuildSchema(IFalloutBuild build) // were added — JsonObject preserves insertion order — so we move complex types to the front. var orderedDefinitions = new JsonObject(); foreach (var kvp in ctx.Definitions.Where(x => !IsWellKnownDefinition(x.Key))) + { orderedDefinitions[kvp.Key] = kvp.Value; - foreach (var key in new[] { "Host", "ExecutableTarget", "Verbosity", nameof(FalloutBuild) }) + } + + foreach (var key in new[] + { + "Host", + "ExecutableTarget", + "Verbosity", + nameof(FalloutBuild) + }) { if (ctx.Definitions.TryGetValue(key, out var value)) + { orderedDefinitions[key] = value; + } } return new JsonObject @@ -134,7 +162,10 @@ private static JsonObject BuildSchema(IFalloutBuild build) ["definitions"] = orderedDefinitions, ["allOf"] = new JsonArray( userSchema, - new JsonObject { ["$ref"] = DefinitionsPrefix + nameof(FalloutBuild) }) + new JsonObject + { + ["$ref"] = DefinitionsPrefix + nameof(FalloutBuild) + }) }; } @@ -145,20 +176,30 @@ private static JsonObject ReorderArraySchemaWithDescription(JsonObject schema, s { schema.Remove(key); if (key == "items") + { ordered["description"] = description; + } + ordered[key] = value; } + return ordered; } private static void ReplaceWithRef(JsonObject properties, string propertyName, string definitionName) { if (properties[propertyName] is not JsonObject existing) + { return; + } + var description = existing["description"]?.GetValue(); var replacement = new JsonObject(); if (description != null) + { replacement["description"] = description; + } + replacement["$ref"] = DefinitionsPrefix + definitionName; properties[propertyName] = replacement; } @@ -185,7 +226,10 @@ private static JsonObject BuildPropertySchema(MemberInfo member, IFalloutBuild b var attributeType = member.GetCustomAttribute().NotNull().GetType(); var schema = attributeType == typeof(ParameterAttribute) ? SchemaForType(memberType, ctx) - : new JsonObject { ["type"] = "string" }; + : new JsonObject + { + ["type"] = "string" + }; var description = ParameterService.GetParameterDescription(member); if (description != null) @@ -203,7 +247,9 @@ private static JsonObject BuildPropertySchema(MemberInfo member, IFalloutBuild b } if (member.HasCustomAttribute()) + { schema["default"] = "Secrets must be entered via 'fallout :secrets [profile]'"; + } // Override-with-enumeration: parameters with a value set become string enums (or array-of-string-enums). var valueSet = ParameterService.GetParameterValueSet(member, build)?.Select(x => x.Text).ToList(); @@ -211,11 +257,19 @@ private static JsonObject BuildPropertySchema(MemberInfo member, IFalloutBuild b { var collection = memberType.IsCollectionLike(); var enumSchema = collection - ? new JsonObject { ["type"] = "array", ["items"] = StringEnumSchema(valueSet) } + ? new JsonObject + { + ["type"] = "array", + ["items"] = StringEnumSchema(valueSet) + } : StringEnumSchema(valueSet); + // Preserve the description we just set. if (schema["description"] is JsonNode desc) + { enumSchema["description"] = desc.GetValue(); + } + schema = enumSchema; } @@ -232,27 +286,70 @@ private static JsonObject SchemaForType(Type type, SchemaContext ctx) { var nullableUnderlying = Nullable.GetUnderlyingType(type); if (nullableUnderlying != null) + { return SchemaForType(nullableUnderlying, ctx); + } + + if (type == typeof(bool)) + { + return new JsonObject + { + ["type"] = "boolean" + }; + } + + if (type == typeof(string)) + { + return new JsonObject + { + ["type"] = "string" + }; + } - if (type == typeof(bool)) return new JsonObject { ["type"] = "boolean" }; - if (type == typeof(string)) return new JsonObject { ["type"] = "string" }; if (type == typeof(int) || type == typeof(long) || type == typeof(short) || type == typeof(byte)) - return new JsonObject { ["type"] = "integer", ["format"] = type == typeof(long) ? "int64" : "int32" }; + { + return new JsonObject + { + ["type"] = "integer", + ["format"] = type == typeof(long) ? "int64" : "int32" + }; + } + if (type == typeof(double) || type == typeof(float) || type == typeof(decimal)) - return new JsonObject { ["type"] = "number" }; + { + return new JsonObject + { + ["type"] = "number" + }; + } // AbsolutePath, Solution, Project — anything with a string-roundtripping TypeConverter — renders as a plain string. if (HasStringTypeConverter(type)) - return new JsonObject { ["type"] = "string" }; + { + return new JsonObject + { + ["type"] = "string" + }; + } if (typeof(Enumeration).IsAssignableFrom(type)) + { return BuildEnumerationSchema(type); + } if (type.IsEnum) + { return StringEnumSchema(Enum.GetNames(type)); + } if (type.IsArray) - return new JsonObject { ["type"] = "array", ["items"] = SchemaForType(type.GetElementType()!, ctx) }; + { + return new JsonObject + { + ["type"] = "array", + ["items"] = SchemaForType(type.GetElementType()!, ctx) + }; + } if (type.IsGenericType) { @@ -260,7 +357,11 @@ private static JsonObject SchemaForType(Type type, SchemaContext ctx) if (def == typeof(IReadOnlyList<>) || def == typeof(IReadOnlyCollection<>) || def == typeof(List<>) || def == typeof(IEnumerable<>)) { - return new JsonObject { ["type"] = "array", ["items"] = SchemaForType(type.GetGenericArguments()[0], ctx) }; + return new JsonObject + { + ["type"] = "array", + ["items"] = SchemaForType(type.GetGenericArguments()[0], ctx) + }; } } @@ -275,6 +376,7 @@ private static JsonObject BuildEnumerationSchema(Type enumerationType) .Where(f => enumerationType.IsAssignableFrom(f.FieldType)) .Select(f => f.Name) .ToList(); + return StringEnumSchema(values); } @@ -294,7 +396,10 @@ private static JsonObject BuildComplexTypeReference(Type type, SchemaContext ctx // ref in `oneOf: [{"type": "null"}, {"$ref": ...}]`. Reference types appear directly via $ref. // The Nullable handling at the caller adds the null sibling if needed for value types; reference // types stay plain $ref. - return new JsonObject { ["$ref"] = DefinitionsPrefix + name }; + return new JsonObject + { + ["$ref"] = DefinitionsPrefix + name + }; } private static JsonObject BuildObjectSchema(Type type, SchemaContext ctx) @@ -333,17 +438,28 @@ private static JsonObject WrapNullable(Type memberType, JsonObject schema) return new JsonObject { ["oneOf"] = new JsonArray( - new JsonObject { ["type"] = "null" }, - new JsonObject { ["$ref"] = refNode.GetValue() }) + new JsonObject + { + ["type"] = "null" + }, + new JsonObject + { + ["$ref"] = refNode.GetValue() + }) }; } + if (schema["type"] is JsonValue typeValue) { var typeName = typeValue.GetValue(); if (typeName == "array") + { schema["type"] = new JsonArray("array", "null"); + } else if (typeName != "object") + { schema["type"] = new JsonArray("null", typeName); + } } } @@ -357,27 +473,40 @@ private static JsonObject WrapNullable(Type memberType, JsonObject schema) foreach (var field in type.GetFields(bindingFlags)) { if (field.IsPrivate || field.IsStatic || field.IsInitOnly || field.IsLiteral) + { continue; + } + yield return (field.Name, field.FieldType, field); } foreach (var property in type.GetProperties(bindingFlags)) { if (property.GetMethod is null || property.GetMethod.IsPrivate || property.GetMethod.IsStatic) + { continue; + } + yield return (property.Name, property.PropertyType, property); } } private static bool HasStringTypeConverter(Type type) { - if (type == typeof(string)) return false; + if (type == typeof(string)) + { + return false; + } + // The AbsolutePath / Solution / Project / Configuration story: any user-declared // [TypeConverter] that can deserialize from a string round-trips through a string in JSON. // (Most converters skip overriding CanConvertTo(string) because the base ToString fallback // is sufficient, so we don't enforce the symmetric check.) if (type.GetCustomAttribute(inherit: true) is null) + { return false; + } + try { return TypeDescriptor.GetConverter(type)?.CanConvertFrom(typeof(string)) ?? false; diff --git a/src/Fallout.Build/Utilities/SystemConsole.cs b/src/Fallout.Build/Utilities/SystemConsole.cs index f8809fb7b..17981fe8f 100644 --- a/src/Fallout.Build/Utilities/SystemConsole.cs +++ b/src/Fallout.Build/Utilities/SystemConsole.cs @@ -1,6 +1,5 @@ using System; using System.Drawing; -using System.Runtime.InteropServices; using Fallout.Common; namespace Fallout.Build.Utilities; diff --git a/src/Fallout.Build/VCS/GitRepository.cs b/src/Fallout.Build/VCS/GitRepository.cs index 787b1ff54..baefc2fea 100644 --- a/src/Fallout.Build/VCS/GitRepository.cs +++ b/src/Fallout.Build/VCS/GitRepository.cs @@ -48,7 +48,8 @@ public static GitRepository FromLocalDirectory(AbsolutePath directory) var commit = GetCommitFromCI() ?? GetCommitFromHead(metadata.GitDirectory, head); var tags = GetTagsFromCommit(metadata.GitDirectory, commit); var (remoteName, remoteBranch) = GetRemoteNameAndBranch(metadata.GitDirectory, branch); - var (protocol, endpoint, identifier) = GetRemoteConnectionFromConfig(metadata.GitDirectory, remoteName ?? FallbackRemoteName); + var (protocol, endpoint, identifier) = + GetRemoteConnectionFromConfig(metadata.GitDirectory, remoteName ?? FallbackRemoteName); return new GitRepository( protocol, @@ -99,7 +100,9 @@ private static GitMetadata GetWorktreeInfoFromGit(AbsolutePath directory) try { // Get all information in one call - var process = ProcessTasks.StartProcess("git", "rev-parse --show-toplevel --git-common-dir --symbolic-full-name HEAD", workingDirectory: directory, logOutput: false); + var process = ProcessTasks.StartProcess("git", "rev-parse --show-toplevel --git-common-dir --symbolic-full-name HEAD", + workingDirectory: directory, logOutput: false); + process.AssertZeroExitCode(); var lines = process.Output @@ -109,7 +112,8 @@ private static GitMetadata GetWorktreeInfoFromGit(AbsolutePath directory) if (lines.Length < 3) { - throw new InvalidOperationException($"Expected 3 lines from 'git rev-parse --show-toplevel --git-common-dir --symbolic-full-name HEAD' but got {lines.Length} lines: [{string.Join(", ", lines.Select(l => $"'{l}'"))}]"); + throw new InvalidOperationException( + $"Expected 3 lines from 'git rev-parse --show-toplevel --git-common-dir --symbolic-full-name HEAD' but got {lines.Length} lines: [{string.Join(", ", lines.Select(l => $"'{l}'"))}]"); } var rootDirectory = lines[0].Trim(); @@ -120,7 +124,9 @@ private static GitMetadata GetWorktreeInfoFromGit(AbsolutePath directory) // In this case, get the actual commit SHA if (head == "HEAD") { - var commitProcess = ProcessTasks.StartProcess("git", "rev-parse HEAD", workingDirectory: directory, logOutput: false); + var commitProcess = + ProcessTasks.StartProcess("git", "rev-parse HEAD", workingDirectory: directory, logOutput: false); + commitProcess.AssertZeroExitCode(); head = commitProcess.Output @@ -140,18 +146,24 @@ private static GitMetadata GetWorktreeInfoFromGit(AbsolutePath directory) private static (string Name, string Branch) GetRemoteNameAndBranch(AbsolutePath gitDirectory, string branch) { if (branch == null) + { return (null, null); + } var configFile = gitDirectory / "config"; if (!configFile.Exists()) { var commonDir = GetGitCommonDirectory(gitDirectory); if (commonDir != null) + { configFile = commonDir / "config"; + } } if (!configFile.Exists()) + { return (null, null); + } var configFileContent = configFile.ReadAllLines(); @@ -184,21 +196,29 @@ internal static string GetHeadIfAttached(string head) internal static string GetCommitFromHead(AbsolutePath gitDirectory, string head) { if (head == null) + { return null; + } if (!head.StartsWith("refs/heads/")) + { return head; + } var headRefFile = gitDirectory / head; if (headRefFile.Exists()) + { return headRefFile.ReadAllLines().First(); + } var commonDir = GetGitCommonDirectory(gitDirectory); if (commonDir != null) { headRefFile = commonDir / head; if (headRefFile.Exists()) + { return headRefFile.ReadAllLines().First(); + } } var commit = GetPackedRefs(gitDirectory) @@ -232,7 +252,9 @@ internal static string GetCommitFromCI() private static IReadOnlyCollection GetTagsFromCommit(AbsolutePath gitDirectory, string commit) { if (commit == null) + { return Array.Empty(); + } var commonDir = GetGitCommonDirectory(gitDirectory); @@ -268,7 +290,9 @@ private static IReadOnlyCollection GetTagsFromCommit(AbsolutePath gitDir { var packedRefsFile = gitDirectory / "packed-refs"; if (!packedRefsFile.Exists()) + { return Array.Empty<(string Commit, string Reference)>(); + } return packedRefsFile.ReadAllLines() .Where(x => !x.StartsWith("#") && !x.StartsWith("^")) @@ -280,12 +304,14 @@ private static (GitProtocol Protocol, string Endpoint, string Identifier) GetRem { var regex = new Regex( @"^(?'protocol'\w+)?(\:\/\/)?(?>(?'user'.*)@)?(?'endpoint'[^\/:]+)(?>\:(?'port'\d+))?[\/:](?'identifier'.*?)\/?(?>\.git)?$"); + var match = regex.Match(url.NotNull().Trim()); Assert.True(match.Success, $"Url '{url}' could not be parsed."); var protocol = match.Groups["protocol"].Value.EqualsOrdinalIgnoreCase(GitProtocol.Https.ToString()) ? GitProtocol.Https : GitProtocol.Ssh; + return (protocol, match.Groups["endpoint"].Value, match.Groups["identifier"].Value); } @@ -298,11 +324,15 @@ private static (GitProtocol? Protocol, string Endpoint, string Identifier) GetRe { var commonDir = GetGitCommonDirectory(gitDirectory); if (commonDir != null) + { configFile = commonDir / "config"; + } } if (!configFile.Exists()) + { return (null, null, null); + } var configFileContent = configFile.ReadAllLines(); var url = configFileContent @@ -315,7 +345,9 @@ private static (GitProtocol? Protocol, string Endpoint, string Identifier) GetRe .Trim(); if (url == null) + { return (null, null, null); + } return GetRemoteConnectionFromUrl(url); } @@ -324,7 +356,9 @@ private static AbsolutePath GetGitCommonDirectory(AbsolutePath gitDirectory) { var commondirFile = gitDirectory / "commondir"; if (!commondirFile.Exists()) + { return null; + } var path = commondirFile.ReadAllText().Trim(); return Path.IsPathRooted(path) ? path : gitDirectory / path; @@ -355,19 +389,19 @@ public GitRepository( } /// Default protocol for the repository. - public GitProtocol? Protocol { get; private set; } + public GitProtocol? Protocol { get; } /// Endpoint for the repository. For instance github.com. - public string Endpoint { get; private set; } + public string Endpoint { get; } /// Identifier of the repository. - public string Identifier { get; private set; } + public string Identifier { get; } /// Local path from which the repository was parsed. - public AbsolutePath LocalDirectory { get; private set; } + public AbsolutePath LocalDirectory { get; } /// Current head; null if parsed from URL. - public string Head { get; private set; } + public string Head { get; } /// Current commit; null if parsed from URL. public string Commit { get; } diff --git a/src/Fallout.Build/VCS/GitRepositoryExtensions.cs b/src/Fallout.Build/VCS/GitRepositoryExtensions.cs index aa9806ae8..033c6a8fe 100644 --- a/src/Fallout.Build/VCS/GitRepositoryExtensions.cs +++ b/src/Fallout.Build/VCS/GitRepositoryExtensions.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.Git; diff --git a/src/Fallout.Build/Verbosity.cs b/src/Fallout.Build/Verbosity.cs index e22f1515c..7b2f99855 100644 --- a/src/Fallout.Build/Verbosity.cs +++ b/src/Fallout.Build/Verbosity.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common; /// diff --git a/src/Fallout.Cli/BuildProjectResolver.cs b/src/Fallout.Cli/BuildProjectResolver.cs index 98d24e6b4..8203f2f74 100644 --- a/src/Fallout.Cli/BuildProjectResolver.cs +++ b/src/Fallout.Cli/BuildProjectResolver.cs @@ -17,7 +17,7 @@ internal static class BuildProjectResolver public static AbsolutePath Resolve(AbsolutePath rootDirectory) { - Assert.NotNull(rootDirectory); + rootDirectory.NotNull(); var parametersFile = GetDefaultParametersFile(rootDirectory); if (File.Exists(parametersFile)) @@ -28,8 +28,10 @@ public static AbsolutePath Resolve(AbsolutePath rootDirectory) var configuredPath = Path.IsPathRooted(configured) ? (AbsolutePath)configured : rootDirectory / configured; + Assert.True(File.Exists(configuredPath), $"BuildProjectFile '{configured}' from '{parametersFile}' does not exist (resolved as '{configuredPath}')."); + return configuredPath; } } @@ -38,6 +40,7 @@ public static AbsolutePath Resolve(AbsolutePath rootDirectory) Assert.True(File.Exists(defaultPath), $"Could not locate build project. Looked for '{defaultPath}' (convention) and 'BuildProjectFile' is not set in '{parametersFile}'. " + $"Run 'fallout :setup' to scaffold a build, or set 'BuildProjectFile' in '{parametersFile}'."); + return defaultPath; } } diff --git a/src/Fallout.Cli/BuildScaffolder.cs b/src/Fallout.Cli/BuildScaffolder.cs index 1f1c79bc4..376c08eb9 100644 --- a/src/Fallout.Cli/BuildScaffolder.cs +++ b/src/Fallout.Cli/BuildScaffolder.cs @@ -17,7 +17,10 @@ internal interface IBuildScaffolder { void WriteBuildScripts(AbsolutePath scriptDirectory, AbsolutePath rootDirectory); void WriteConfigurationFile(AbsolutePath rootDirectory, AbsolutePath solutionFile); - void UpdateSolutionFileContent(List content, string buildProjectFileRelative, string buildProjectGuid, string buildProjectName); + + void UpdateSolutionFileContent(List content, string buildProjectFileRelative, string buildProjectGuid, + string buildProjectName); + void UpdateSolutionXmlFileContent(XDocument content, string buildProjectFileRelative); string[] GetTemplate(string templateName); } @@ -80,22 +83,35 @@ public void WriteBuildScripts( void MakeExecutable(AbsolutePath scriptFile) { if (rootDirectory.ContainsDirectory(".git")) + { StartProcess("git", $"update-index --add --chmod=+x {scriptFile}", logInvocation: false, logOutput: false); + } if (rootDirectory.ContainsDirectory(".svn")) + { StartProcess("svn", $"propset svn:executable on {scriptFile}", logInvocation: false, logOutput: false); + } if (IsUnix) + { StartProcess("chmod", $"+x {scriptFile}", logInvocation: false, logOutput: false); + } } } public void WriteConfigurationFile(AbsolutePath rootDirectory, AbsolutePath solutionFile) { var parametersFile = GetDefaultParametersFile(rootDirectory); - var dictionary = new Dictionary { ["$schema"] = BuildSchemaFileName }; + var dictionary = new Dictionary + { + ["$schema"] = BuildSchemaFileName + }; + if (solutionFile != null) + { dictionary["Solution"] = rootDirectory.GetUnixRelativePathTo(solutionFile).ToString(); + } + parametersFile.WriteJson(dictionary, JsonExtensions.DefaultSerializerOptions); } @@ -106,7 +122,9 @@ public void UpdateSolutionFileContent( string buildProjectName) { if (content.Any(x => x.Contains(buildProjectFileRelative))) + { return; + } var globalIndex = content.IndexOf("Global"); Assert.True(globalIndex != -1, "Could not find a 'Global' section in solution file"); @@ -138,6 +156,7 @@ public void UpdateSolutionFileContent( content.Insert(globalIndex, $"Project(\"{{{PROJECT_KIND}}}\") = \"{buildProjectName}\", \"{buildProjectFileRelative}\", \"{{{buildProjectGuid}}}\""); + content.Insert(globalIndex + 1, "EndProject"); } diff --git a/src/Fallout.Cli/CakeConverter.cs b/src/Fallout.Cli/CakeConverter.cs index 85ea20fa5..b7c26c68b 100644 --- a/src/Fallout.Cli/CakeConverter.cs +++ b/src/Fallout.Cli/CakeConverter.cs @@ -1,13 +1,14 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; +using Fallout.Cli.Rewriting.Cake; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Utilities; -using Fallout.Cli.Rewriting.Cake; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using static Fallout.Common.Constants; using static Fallout.Common.EnvironmentInfo; @@ -28,20 +29,20 @@ public static string GetConvertedContent(string content) var options = new CSharpParseOptions(LanguageVersion.Latest, DocumentationMode.None, SourceCodeKind.Script); var syntaxTree = CSharpSyntaxTree.ParseText(content, options); return new CSharpSyntaxRewriter[] - { - new RemoveUsingDirectivesRewriter(), - new RenameFieldIdentifierRewriter(), - new ParameterRewriter(), - new AbsolutePathRewriter(), - new RegularFieldRewriter(), - new TargetDefinitionRewriter(), - new InvocationRewriter(), - new MemberAccessRewriter(), - new IdentifierNameRewriter(), - new ToolInvocationRewriter(), - new ClassRewriter(), - new FormattingRewriter() - }.Aggregate(syntaxTree.GetRoot(), (root, rewriter) => rewriter.Visit(root.NormalizeWhitespace(elasticTrivia: true))) + { + new RemoveUsingDirectivesRewriter(), + new RenameFieldIdentifierRewriter(), + new ParameterRewriter(), + new AbsolutePathRewriter(), + new RegularFieldRewriter(), + new TargetDefinitionRewriter(), + new InvocationRewriter(), + new MemberAccessRewriter(), + new IdentifierNameRewriter(), + new ToolInvocationRewriter(), + new ClassRewriter(), + new FormattingRewriter() + }.Aggregate(syntaxTree.GetRoot(), (root, rewriter) => rewriter.Visit(root.NormalizeWhitespace(elasticTrivia: true))) .ToFullString(); } @@ -57,13 +58,19 @@ public static string GetConvertedContent(string content) var packageId = match.Groups["packageId"].Value; var packageVersion = match.Groups["version"].Value; if (packageVersion.IsNullOrEmpty()) - packageVersion = AsyncHelper.RunSync(() => NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false)); - yield return new(packageType, packageId, packageVersion); + { + packageVersion = AsyncHelper.RunSync(() => + NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false)); + } + + yield return new ValueTuple(packageType, packageId, packageVersion); } } - return GetPackages(PackageManager.DownloadType, @"#tool ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""") - .Concat(GetPackages(PackageManager.ReferenceType, @"#addin ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""")) + return GetPackages(PackageManager.DownloadType, + @"#tool ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""") + .Concat(GetPackages(PackageManager.ReferenceType, + @"#addin ""nuget:\?package=(?'packageId'[\w\d\.]+)(&version=(?'version'[\w\d\.]+))?S*""")) .Where(x => !x.Id.ContainsOrdinalIgnoreCase("Cake")); } } diff --git a/src/Fallout.Cli/CommandDispatcher.cs b/src/Fallout.Cli/CommandDispatcher.cs index 11948a394..52c12d7c7 100644 --- a/src/Fallout.Cli/CommandDispatcher.cs +++ b/src/Fallout.Cli/CommandDispatcher.cs @@ -7,7 +7,6 @@ using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; -using Fallout.Common.Utilities.Collections; namespace Fallout.Cli; @@ -48,7 +47,7 @@ public async Task DispatchAsync(string[] args, AbsolutePath rootDirectory, if (rootDirectory == null) { return prompts.PromptForConfirmation( - $"Could not find {Constants.FalloutDirectoryName} directory/file. Do you want to setup a build?") + $"Could not find {Constants.FalloutDirectoryName} directory/file. Do you want to setup a build?") ? await GetRequired("setup").ExecuteAsync(Array.Empty(), rootDirectory: null, buildScript: null) : 0; } @@ -61,7 +60,10 @@ public async Task DispatchAsync(string[] args, AbsolutePath rootDirectory, private IFalloutCommand Resolve(string token) { return commands.SingleOrDefault(x => Normalize(x.Name).EqualsOrdinalIgnoreCase(Normalize(token))) - .NotNull(new[] { $"Command '{token}' is not supported, available commands are:" } + .NotNull(new[] + { + $"Command '{token}' is not supported, available commands are:" + } .Concat(commands.Select(x => $" - {x.Name}").OrderBy(x => x)).JoinNewLine()); } diff --git a/src/Fallout.Cli/Commands/AddPackageCommand.cs b/src/Fallout.Cli/Commands/AddPackageCommand.cs index ec618e954..d0258a33d 100644 --- a/src/Fallout.Cli/Commands/AddPackageCommand.cs +++ b/src/Fallout.Cli/Commands/AddPackageCommand.cs @@ -3,26 +3,17 @@ using Fallout.Common; using Fallout.Common.Execution; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; +using Fallout.Solutions; namespace Fallout.Cli.Commands; /// /// fallout :add-package: adds (or upgrades) a NuGet package reference in the build project. /// -internal sealed class AddPackageCommand : IFalloutCommand +internal sealed class AddPackageCommand(IConfigurationReader configuration, IPackageManager packages) : IFalloutCommand { - private readonly IConfigurationReader configuration; - private readonly IPackageManager packages; - - public AddPackageCommand(IConfigurationReader configuration, IPackageManager packages) - { - this.configuration = configuration; - this.packages = packages; - } - public string Name => "add-package"; public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) @@ -36,20 +27,24 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A (EnvironmentInfo.GetNamedArgument("version") ?? args.ElementAtOrDefault(1) ?? await NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false) ?? - NuGetPackageResolver.GetGlobalInstalledPackage(packageId, version: null, packagesConfigFile: null)?.Version.ToString()) + NuGetPackageResolver.GetGlobalInstalledPackage(packageId, version: null, packagesConfigFile: null)?.Version + .ToString()) .NotNull("packageVersion != null"); - var configuration = this.configuration.Read(buildScript, evaluate: true); - var buildProjectFile = configuration[ConfigurationReader.BuildProjectFileKey]; + var configuration1 = configuration.Read(buildScript, evaluate: true); + var buildProjectFile = configuration1[ConfigurationReader.BuildProjectFileKey]; Host.Information($"Installing {packageId}/{packageVersion} to {buildProjectFile} ..."); packages.AddOrReplacePackage(packageId, packageVersion, PackageManager.DownloadType, buildProjectFile); DotNetTasks.DotNet($"restore {buildProjectFile}"); var installedPackage = NuGetPackageResolver.GetGlobalInstalledPackage(packageId, packageVersion, packagesConfigFile: null) .NotNull("installedPackage != null"); + var hasToolsDirectory = installedPackage.Directory.GlobDirectories("tools").Any(); if (!hasToolsDirectory) + { packages.AddOrReplacePackage(packageId, packageVersion, PackageManager.ReferenceType, buildProjectFile); + } Host.Information($"Done installing {packageId}/{packageVersion} to {buildProjectFile}"); return 0; diff --git a/src/Fallout.Cli/Commands/CakeCleanCommand.cs b/src/Fallout.Cli/Commands/CakeCleanCommand.cs index 182f1b489..87d7a262f 100644 --- a/src/Fallout.Cli/Commands/CakeCleanCommand.cs +++ b/src/Fallout.Cli/Commands/CakeCleanCommand.cs @@ -27,7 +27,9 @@ private int Execute() cakeFiles.ForEach(x => Host.Debug($" - {x}")); if (prompts.PromptForConfirmation("Delete?")) + { cakeFiles.ForEach(x => x.DeleteFile()); + } return 0; } diff --git a/src/Fallout.Cli/Commands/CakeConvertCommand.cs b/src/Fallout.Cli/Commands/CakeConvertCommand.cs index c8101556f..a6b8dc603 100644 --- a/src/Fallout.Cli/Commands/CakeConvertCommand.cs +++ b/src/Fallout.Cli/Commands/CakeConvertCommand.cs @@ -1,12 +1,12 @@ using System.Collections.Generic; -using System.Threading.Tasks; using System.Linq; +using System.Threading.Tasks; using Fallout.Cli.Prompts; using Fallout.Common; using Fallout.Common.Execution; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Utilities; +using Fallout.Solutions; using static Fallout.Common.EnvironmentInfo; namespace Fallout.Cli.Commands; @@ -57,7 +57,10 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A Host.Debug(); if (!prompts.PromptForConfirmation("Continue?")) + { return 0; + } + Host.Debug(); if (buildScript == null && @@ -83,7 +86,9 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A { var packages = CakeConverter.GetCakeFiles().SelectMany(x => CakeConverter.GetPackages(x.ReadAllText())); foreach (var package in packages) + { this.packages.AddOrReplacePackage(package.Id, package.Version, package.Type, buildProjectFile); + } } return 0; diff --git a/src/Fallout.Cli/Commands/CompleteCommand.cs b/src/Fallout.Cli/Commands/CompleteCommand.cs index 6dbfec815..8ade25c07 100644 --- a/src/Fallout.Cli/Commands/CompleteCommand.cs +++ b/src/Fallout.Cli/Commands/CompleteCommand.cs @@ -1,7 +1,7 @@ using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; @@ -25,11 +25,15 @@ public Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, Absolut private int Execute(string[] args, AbsolutePath rootDirectory) { if (rootDirectory == null) + { return 0; + } var words = args.Single(); if (!words.StartsWithOrdinalIgnoreCase(CommandName)) + { return 0; + } words = words[CommandName.Length..].TrimStart(); @@ -47,8 +51,11 @@ private int Execute(string[] args, AbsolutePath rootDirectory) var completionItems = IsLegacy(rootDirectory) ? completionFile.ReadYaml>() : CompletionUtility.GetItemsFromSchema(buildSchemaFile, GetProfileNames(rootDirectory)); + foreach (var item in CompletionUtility.GetRelevantItems(words, completionItems)) + { Console.WriteLine(item); + } return 0; } diff --git a/src/Fallout.Cli/Commands/GetConfigurationCommand.cs b/src/Fallout.Cli/Commands/GetConfigurationCommand.cs index b19762dc4..43f9dd4bb 100644 --- a/src/Fallout.Cli/Commands/GetConfigurationCommand.cs +++ b/src/Fallout.Cli/Commands/GetConfigurationCommand.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Fallout.Common; using Fallout.Common.IO; -using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; namespace Fallout.Cli.Commands; diff --git a/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs index 64ce23451..d2d584d81 100644 --- a/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs +++ b/src/Fallout.Cli/Commands/Navigation/GetNextDirectoryCommand.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using System.Linq; using Fallout.Common; using Fallout.Common.IO; diff --git a/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs b/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs index 6b283a9f3..0243d7405 100644 --- a/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs +++ b/src/Fallout.Cli/Commands/Navigation/NavigationSession.cs @@ -3,7 +3,6 @@ using System.Linq; using Fallout.Common; using Fallout.Common.IO; -using Fallout.Common.Utilities; using static Fallout.Common.Constants; namespace Fallout.Cli.Commands.Navigation; @@ -34,7 +33,11 @@ public static int PushAndSetNext(Func directoryProvider) { try { - var content = SessionFile.Existing()?.ReadAllLines().ToList() ?? new List { null }; + var content = SessionFile.Existing()?.ReadAllLines().ToList() ?? new List + { + null + }; + content[0] = directoryProvider.Invoke(); content.Insert(index: 1, EnvironmentInfo.WorkingDirectory); SessionFile.WriteAllLines(content); diff --git a/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs index 8b0812568..d15571b92 100644 --- a/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs +++ b/src/Fallout.Cli/Commands/Navigation/PopDirectoryCommand.cs @@ -1,6 +1,6 @@ using System; -using System.Threading.Tasks; using System.Linq; +using System.Threading.Tasks; using Fallout.Common.IO; namespace Fallout.Cli.Commands.Navigation; diff --git a/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs index 38f211a2b..e349927e8 100644 --- a/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs +++ b/src/Fallout.Cli/Commands/Navigation/PushWithCurrentRootDirectoryCommand.cs @@ -1,7 +1,6 @@ -using Fallout.Common; using System.Threading.Tasks; +using Fallout.Common; using Fallout.Common.IO; -using Fallout.Common.Utilities; namespace Fallout.Cli.Commands.Navigation; diff --git a/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs b/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs index c6af577db..3c491e703 100644 --- a/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs +++ b/src/Fallout.Cli/Commands/Navigation/PushWithParentRootDirectoryCommand.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Fallout.Common; using Fallout.Common.IO; -using Fallout.Common.Utilities; using static Fallout.Common.Constants; namespace Fallout.Cli.Commands.Navigation; diff --git a/src/Fallout.Cli/Commands/RunCommand.cs b/src/Fallout.Cli/Commands/RunCommand.cs index 374bbf7ac..c7a4a6662 100644 --- a/src/Fallout.Cli/Commands/RunCommand.cs +++ b/src/Fallout.Cli/Commands/RunCommand.cs @@ -46,6 +46,7 @@ private static string ResolveDotnet(AbsolutePath rootDirectory) Assert.True(File.Exists(shimPath), $"Could not locate 'dotnet'. Tried PATH and '{shimPath}'. " + $"Run './build.sh' (Unix) or './build.ps1' (Windows) once to provision .NET locally, then retry."); + return shimPath; } @@ -107,6 +108,7 @@ private static IEnumerable GetRunArguments(AbsolutePath buildProjectFile "--no-build", "--" }; + args.AddRange(forwardedArgs); return args; } diff --git a/src/Fallout.Cli/Commands/SecretsCommand.cs b/src/Fallout.Cli/Commands/SecretsCommand.cs index af96972f9..dd5331e74 100644 --- a/src/Fallout.Cli/Commands/SecretsCommand.cs +++ b/src/Fallout.Cli/Commands/SecretsCommand.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -using System.Threading.Tasks; using System.Linq; +using System.Threading.Tasks; using Fallout.Cli.Prompts; using Fallout.Common; using Fallout.Common.IO; @@ -42,6 +42,7 @@ private int Execute(string[] args, AbsolutePath rootDirectory) GetBuildSchemaFile(rootDirectory.NotNull("No root directory")), filter: x => x.Value.TryGetProperty("default", out _)) .Select(x => x.Key).ToList(); + if (secretParameters.Count == 0) { Host.Information($"There are no parameters marked with {nameof(SecretAttribute)}"); @@ -76,8 +77,11 @@ private int Execute(string[] args, AbsolutePath rootDirectory) if (EnvironmentInfo.IsOsx && existingSecrets.Count == 0 && !fromCredentialStore) { - if (generatedPassword || prompts.PromptForConfirmation($"Save password to keychain? (associated with '{rootDirectory}')")) + if (generatedPassword || + prompts.PromptForConfirmation($"Save password to keychain? (associated with '{rootDirectory}')")) + { CredentialStore.SavePassword(credentialStoreName, password); + } } else if (fromLegacyCredentialStore) { @@ -103,20 +107,27 @@ private int Execute(string[] args, AbsolutePath rootDirectory) else { if (choice == SaveAndExit) + { SaveSecrets(addedSecrets, password, parametersFile); + } if (choice == DeletePasswordAndExit) + { CredentialStore.DeletePassword(credentialStoreName); + } if (addedSecrets.Any()) + { Host.Information("Remember to clear your clipboard!"); + } return 0; } } } - private static Dictionary LoadSecrets(IReadOnlyCollection secretParameters, string password, AbsolutePath parametersFile) + private static Dictionary LoadSecrets(IReadOnlyCollection secretParameters, string password, + AbsolutePath parametersFile) { var jobject = parametersFile.ReadJsonObject(); return jobject @@ -129,7 +140,9 @@ private static void SaveSecrets(Dictionary secrets, string passw parametersFile.UpdateJsonObject(obj => { foreach (var (name, secret) in secrets) + { obj[name] = Encrypt(secret, password); + } }); } } diff --git a/src/Fallout.Cli/Commands/SetupCommand.cs b/src/Fallout.Cli/Commands/SetupCommand.cs index fed807c20..5c879a814 100644 --- a/src/Fallout.Cli/Commands/SetupCommand.cs +++ b/src/Fallout.Cli/Commands/SetupCommand.cs @@ -1,9 +1,9 @@ using System; -using System.Threading.Tasks; using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using Fallout.Cli.Prompts; @@ -49,19 +49,27 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A #region Basic - var falloutLatestReleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: false); - var falloutLatestPrereleaseVersion = NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: true); - var falloutLatestLocalVersion = NuGetPackageResolver.GetGlobalInstalledPackage(FalloutCommonPackageId, version: null, packagesConfigFile: null) + var falloutLatestReleaseVersion = + NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: false); + + var falloutLatestPrereleaseVersion = + NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: true); + + var falloutLatestLocalVersion = NuGetPackageResolver + .GetGlobalInstalledPackage(FalloutCommonPackageId, version: null, packagesConfigFile: null) ?.Version.ToString(); if (rootDirectory == null) + { rootDirectory = WorkingDirectory.FindParentOrSelf(x => x.ContainsDirectory(".git") || x.ContainsDirectory(".svn")); + } if (rootDirectory == null) { Host.Warning("Could not find root directory. Falling back to working directory ..."); rootDirectory = WorkingDirectory; } + prompts.ShowInput("deciduous_tree", "Root directory", rootDirectory); var buildProjectName = prompts.PromptForInput("How should the project be named?", "_build"); @@ -83,9 +91,10 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A .Where(x => x.Item2 != null) .Distinct(x => x.Item2) .Select(x => (x.Item2, $"{x.Item2} ({x.Item1})")).ToArray()); + prompts.ShowInput("gem_stone", "Fallout.Common version", falloutVersion); - var solutionFile = (AbsolutePath) prompts.PromptForChoice( + var solutionFile = (AbsolutePath)prompts.PromptForChoice( "Which solution should be the default?", choices: new DirectoryInfo(rootDirectory) .EnumerateFiles("*", SearchOption.AllDirectories) @@ -93,7 +102,9 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A .OrderByDescending(x => x.FullName) .Select(x => (x, rootDirectory.GetRelativePathTo(x.FullName).ToString())) .Concat((null, "None")).ToArray())?.FullName; - prompts.ShowInput("toolbox", "Default solution", solutionFile != null ? rootDirectory.GetRelativePathTo(solutionFile) : ""); + + prompts.ShowInput("toolbox", "Default solution", + solutionFile != null ? rootDirectory.GetRelativePathTo(solutionFile) : ""); #endregion @@ -119,14 +130,21 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A var solutionDocument = XDocument.Load(solutionFile); scaffolder.UpdateSolutionXmlFileContent(solutionDocument, buildProjectFileRelative); - var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true }; + var settings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true + }; + using var writer = XmlWriter.Create(solutionFile, settings); solutionDocument.Save(writer); } else { var solutionFileContent = solutionFile.ReadAllLines().ToList(); - scaffolder.UpdateSolutionFileContent(solutionFileContent, buildProjectFileRelative, buildProjectGuid, buildProjectName); + scaffolder.UpdateSolutionFileContent(solutionFileContent, buildProjectFileRelative, buildProjectGuid, + buildProjectName); + solutionFile.WriteAllLines(solutionFileContent, Encoding.UTF8); } } diff --git a/src/Fallout.Cli/Commands/TriggerCommand.cs b/src/Fallout.Cli/Commands/TriggerCommand.cs index ed2c2f595..f8e817310 100644 --- a/src/Fallout.Cli/Commands/TriggerCommand.cs +++ b/src/Fallout.Cli/Commands/TriggerCommand.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Fallout.Common; using Fallout.Common.Git; @@ -21,7 +20,7 @@ public Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, Absolut private static int Execute(string[] args, AbsolutePath rootDirectory) { var repository = GitRepository.FromLocalDirectory(rootDirectory.NotNull()).NotNull("No Git repository"); - Assert.NotNull(repository.Branch, "Git repository must not be detached"); + repository.Branch.NotNull("Git repository must not be detached"); Assert.NotEmpty(args); try diff --git a/src/Fallout.Cli/Commands/UpdateCommand.cs b/src/Fallout.Cli/Commands/UpdateCommand.cs index 488e7d025..d8e7fd728 100644 --- a/src/Fallout.Cli/Commands/UpdateCommand.cs +++ b/src/Fallout.Cli/Commands/UpdateCommand.cs @@ -1,17 +1,17 @@ -using System.IO; -using System.Threading.Tasks; using System.Linq; using System.Text.Json.Nodes; -using NuGet.Versioning; +using System.Threading.Tasks; using Fallout.Cli.Prompts; using Fallout.Common; using Fallout.Common.Execution; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using Fallout.Common.Utilities; +using Fallout.Solutions; +using NuGet.Versioning; using static Fallout.Common.Constants; +using Project = Microsoft.Build.Evaluation.Project; namespace Fallout.Cli.Commands; @@ -38,7 +38,7 @@ public async Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, A ToolBanner.Print(); Logging.Configure(); - Assert.NotNull(rootDirectory); + rootDirectory.NotNull(); if (buildScript != null) { @@ -73,29 +73,33 @@ private async Task UpdateBuildProjectAsync(AbsolutePath buildScript) var previousPackageVersion = await UpdateFalloutCommonPackageAsync(buildProject); if (previousPackageVersion.MinVersion >= NuGetVersion.Parse("0.23.5")) + { RemoveLegacyFileIncludes(buildProject); + } buildProject.Save(); } - internal static void UpdateTargetFramework(Microsoft.Build.Evaluation.Project buildProject) + internal static void UpdateTargetFramework(Project buildProject) { buildProject.SetProperty("TargetFramework", "net10.0"); } - private static async Task UpdateFalloutCommonPackageAsync(Microsoft.Build.Evaluation.Project buildProject) + private static async Task UpdateFalloutCommonPackageAsync(Project buildProject) { var packageItem = buildProject.Items.SingleOrDefault(x => x.EvaluatedInclude == FalloutCommonPackageId).NotNull(); var previousPackageVersion = FloatRange.Parse(packageItem.GetMetadataValue("Version")); var latestPackageVersion = await NuGetVersionResolver.GetLatestVersion(FalloutCommonPackageId, includePrereleases: false); if (!previousPackageVersion.Satisfies(NuGetVersion.Parse(latestPackageVersion))) + { packageItem.SetMetadataValue("Version", latestPackageVersion); + } return previousPackageVersion; } - private static void RemoveLegacyFileIncludes(Microsoft.Build.Evaluation.Project buildProject) + private static void RemoveLegacyFileIncludes(Project buildProject) { var legacyIncludes = new[] @@ -122,7 +126,9 @@ private static void RemoveLegacyFileIncludes(Microsoft.Build.Evaluation.Project var itemGroupElement = x.Parent; itemGroupElement.RemoveChild(x); if (itemGroupElement.Children.Count == 0) + { itemGroupElement.Parent.RemoveChild(itemGroupElement); + } }); } @@ -130,7 +136,9 @@ private void UpdateConfigurationFile(AbsolutePath rootDirectory) { var configurationFile = rootDirectory / FalloutDirectoryName; if (!configurationFile.Exists()) + { return; + } var solutionFile = rootDirectory / configurationFile.ReadAllLines().FirstOrDefault(x => !x.IsNullOrEmpty()); configurationFile.DeleteFile(); @@ -139,15 +147,21 @@ private void UpdateConfigurationFile(AbsolutePath rootDirectory) Host.Warning($"The previous {FalloutFileName} file was transformed to a {FalloutDirectoryName} directory."); Host.Warning($"The .tmp directory can be cleared, as it is moved to {FalloutDirectoryName}/temp as well."); if (solutionFile != null) - Host.Warning($"Verify the property referencing the solution has the same name as the member with the {nameof(SolutionAttribute)}."); + { + Host.Warning( + $"Verify the property referencing the solution has the same name as the member with the {nameof(SolutionAttribute)}."); + } } private static void UpdateGlobalJsonFile(AbsolutePath rootDirectory) { var latestInstalledSdk = DotNetTasks.DotNet("--list-sdks", logInvocation: false, logOutput: false) .LastOrDefault().Text?.Split(" ").First(); + if (latestInstalledSdk == null) + { return; + } var globalJsonFile = rootDirectory / "global.json"; var jobject = globalJsonFile.Existing()?.ReadJsonObject() ?? new JsonObject(); diff --git a/src/Fallout.Cli/PackageManager.cs b/src/Fallout.Cli/PackageManager.cs index 4c838ac2d..3fc642aa0 100644 --- a/src/Fallout.Cli/PackageManager.cs +++ b/src/Fallout.Cli/PackageManager.cs @@ -1,6 +1,5 @@ using System.Linq; using Fallout.Common; -using Fallout.Common.Utilities; using Fallout.Solutions; namespace Fallout.Cli; @@ -23,13 +22,16 @@ public void AddOrReplacePackage(string packageId, string packageVersion, string var previousPackage = buildProject.Items.SingleOrDefault(x => x.EvaluatedInclude == packageId); if (previousPackage != null) + { buildProject.RemoveItem(previousPackage); + } var packageDownloadItem = buildProject.AddItem(packageType, packageId).Single(); packageDownloadItem.Xml.AddMetadata( "Version", packageType == ReferenceType ? packageVersion : $"[{packageVersion}]", expressAsAttribute: true); + buildProject.Save(); } } diff --git a/src/Fallout.Cli/Program.cs b/src/Fallout.Cli/Program.cs index 11091ba7c..c6b344902 100644 --- a/src/Fallout.Cli/Program.cs +++ b/src/Fallout.Cli/Program.cs @@ -13,7 +13,7 @@ namespace Fallout.Cli; -internal partial class Program +internal class Program { private static async Task Main(string[] args) { @@ -81,10 +81,14 @@ private static AbsolutePath TryGetRootDirectory() // TODO: copied in FalloutBuild.GetRootDirectory AbsolutePath parameterValue = EnvironmentInfo.GetNamedArgument(Constants.RootDirectoryParameterName); if (parameterValue != null) + { return parameterValue; + } if (EnvironmentInfo.GetNamedArgument(Constants.RootDirectoryParameterName)) + { return EnvironmentInfo.WorkingDirectory; + } return Constants.TryGetRootDirectoryFrom(Directory.GetCurrentDirectory()); } diff --git a/src/Fallout.Cli/Prompts/SpectreConsolePrompts.cs b/src/Fallout.Cli/Prompts/SpectreConsolePrompts.cs index f57737562..362c68152 100644 --- a/src/Fallout.Cli/Prompts/SpectreConsolePrompts.cs +++ b/src/Fallout.Cli/Prompts/SpectreConsolePrompts.cs @@ -27,7 +27,7 @@ public void ShowCompletion(string title) public void ClearPreviousLine() { AnsiConsole.Cursor.MoveUp(); - System.Console.WriteLine(' '.Repeat(System.Console.WindowWidth)); + Console.WriteLine(' '.Repeat(Console.WindowWidth)); AnsiConsole.Cursor.MoveUp(); } @@ -62,6 +62,7 @@ public T PromptForChoice(string question, params (T Value, string Description .HighlightStyle(new Style(Color.Turquoise2)) .UseConverter(x => choices.Single(y => Equals(x, y.Value)).Description) .AddChoices(choices.Select(x => x.Value))); + return choice; } diff --git a/src/Fallout.Cli/Rewriting/Cake/AbsolutePathRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/AbsolutePathRewriter.cs index 9f1fafa8b..e600f2c1a 100644 --- a/src/Fallout.Cli/Rewriting/Cake/AbsolutePathRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/AbsolutePathRewriter.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Fallout.Cli.Rewriting.Cake; @@ -14,9 +14,11 @@ internal class AbsolutePathRewriter : SafeSyntaxRewriter { public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { - var reducedFieldDeclaration = (FieldDeclarationSyntax) base.VisitFieldDeclaration(node).NotNull(); + var reducedFieldDeclaration = (FieldDeclarationSyntax)base.VisitFieldDeclaration(node).NotNull(); if (reducedFieldDeclaration == node) + { return node; + } var variableDeclarator = reducedFieldDeclaration.GetSingleDeclarator(); return SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(nameof(AbsolutePath)), variableDeclarator.Identifier) @@ -30,23 +32,36 @@ public override SyntaxNode VisitInterpolatedStringExpression(InterpolatedStringE var textTokens = node.Contents.OfType().Select(x => x.TextToken.Text).ToList(); if (textTokens.All(x => !x.Contains("/")) || textTokens.Any(x => x.ContainsAnyOrdinalIgnoreCase("<", ">", ":", " -"))) + { return node; + } if (node.Parent is BinaryExpressionSyntax) + { return node; + } if (node.Parent?.Parent?.Parent is InvocationExpressionSyntax invocationExpression && invocationExpression.GetIdentifierName().EqualsAnyOrdinalIgnoreCase("SelectNodes", "SelectSingleNode", "Append")) + { return node; + } var str = node.ToString(); var index = str.IndexOf('*'); if (index == -1) - return CreateAbsolutePathExpression(new[] { SyntaxFactory.ParseExpression(str.Replace("/", "\" / $\"")) }); + { + return CreateAbsolutePathExpression(new[] + { + SyntaxFactory.ParseExpression(str.Replace("/", "\" / $\"")) + }); + } var lastPathSeparatorIndex = str[..index].LastIndexOf('/'); if (lastPathSeparatorIndex < 0) + { return node; + } var absolutePart = str[..lastPathSeparatorIndex] + "\""; var wildcardPart = "\"" + str[(lastPathSeparatorIndex + 1)..]; @@ -56,7 +71,10 @@ public override SyntaxNode VisitInterpolatedStringExpression(InterpolatedStringE SyntaxKind.SimpleMemberAccessExpression, node.Contents.First() is InterpolationSyntax ? SyntaxFactory.ParseExpression(absolutePart) - : SyntaxFactory.ParenthesizedExpression(CreateAbsolutePathExpression(new[] { SyntaxFactory.ParseExpression(absolutePart) })), + : SyntaxFactory.ParenthesizedExpression(CreateAbsolutePathExpression(new[] + { + SyntaxFactory.ParseExpression(absolutePart) + })), SyntaxFactory.IdentifierName(GetGlobbingMethod(node, wildcardPart.Split("/"))))) .WithArguments(wildcardPart.TrimMatchingDoubleQuotes().ToLiteralExpression()); @@ -88,24 +106,34 @@ public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node) if (node.Token.Value is not string || !node.Token.ValueText.Contains('/') || node.Token.ValueText.ContainsAnyOrdinalIgnoreCase("<", ">", ":", " -")) + { return node; + } if (node.Parent is BinaryExpressionSyntax) + { return node; + } if (node.Parent?.Parent?.Parent is InvocationExpressionSyntax invocationExpression && invocationExpression.GetIdentifierName().EqualsAnyOrdinalIgnoreCase("SelectNodes", "SelectSingleNode", "Append")) + { return node; + } var parts = node.Token.ValueText.Split('/', StringSplitOptions.RemoveEmptyEntries).Where(x => x != ".").ToList(); var nonWildcardParts = parts.TakeWhile(x => !x.Contains('*')).ToList(); var wildcardParts = parts.Skip(nonWildcardParts.Count).ToList(); var literalExpressions = - nonWildcardParts.Select(x => SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(x))); + nonWildcardParts.Select(x => + SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(x))); + var absolutePathExpression = CreateAbsolutePathExpression(literalExpressions); if (wildcardParts.Count == 0) + { return absolutePathExpression; + } return SyntaxFactory.InvocationExpression( SyntaxFactory.MemberAccessExpression( @@ -118,9 +146,9 @@ public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node) private string GetGlobbingMethod(SyntaxNode node, IEnumerable wildcardparts = null) { return (wildcardparts?.LastOrDefault()?.Contains(".") ?? false) || - node.Parent?.Parent?.Parent is InvocationExpressionSyntax invocationExpression && - (invocationExpression.GetIdentifierName() == "GetFiles" || - invocationExpression.GetIdentifierName() == "CopyFiles") + (node.Parent?.Parent?.Parent is InvocationExpressionSyntax invocationExpression && + (invocationExpression.GetIdentifierName() == "GetFiles" || + invocationExpression.GetIdentifierName() == "CopyFiles")) ? "GlobFiles" : "GlobDirectories"; } @@ -129,15 +157,19 @@ private string GetGlobbingMethod(SyntaxNode node, IEnumerable wildcardpa // File(expr) => expr public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { - node = (InvocationExpressionSyntax) base.VisitInvocationExpression(node).NotNull(); + node = (InvocationExpressionSyntax)base.VisitInvocationExpression(node).NotNull(); if (node.GetIdentifierName() == "MakeAbsolute" || node.GetIdentifierName() == "Directory" || node.GetIdentifierName() == "File") + { return node.GetSingleArgument(); + } if (node.GetIdentifierName() == "Combine") + { return CreateAbsolutePathExpression(node.Arguments()); + } if (node.GetIdentifierName() == "GetFiles") { @@ -145,6 +177,7 @@ public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax .Aggregate( node.Arguments().FirstOrDefault()?.ToFullString(), (f, s) => f + ".Concat(" + s.ToFullString() + ")"); + return SyntaxFactory.ParseExpression(expr); // return InvocationExpression( // MemberAccessExpression( @@ -173,7 +206,7 @@ public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax private ExpressionSyntax CreateAbsolutePathExpression(IEnumerable parts) { return parts.Aggregate( - (ExpressionSyntax) SyntaxFactory.IdentifierName("RootDirectory"), + (ExpressionSyntax)SyntaxFactory.IdentifierName("RootDirectory"), (left, right) => SyntaxFactory.BinaryExpression(SyntaxKind.DivideExpression, left, right)); } @@ -189,9 +222,11 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { - node = (MemberAccessExpressionSyntax) base.VisitMemberAccessExpression(node).NotNull(); + node = (MemberAccessExpressionSyntax)base.VisitMemberAccessExpression(node).NotNull(); if (node.GetIdentifierName() != "FullPath") + { return node; + } return node.Expression; } diff --git a/src/Fallout.Cli/Rewriting/Cake/ClassRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/ClassRewriter.cs index 833dd5411..59960a7dc 100644 --- a/src/Fallout.Cli/Rewriting/Cake/ClassRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/ClassRewriter.cs @@ -1,14 +1,14 @@ using System; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Tools.DotNet; using Fallout.Common.Tools.MSBuild; using Fallout.Common.Tools.NuGet; using Fallout.Common.Tools.SignTool; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -57,24 +57,42 @@ internal class ClassRewriter : SafeSyntaxRewriter public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) { - node = (CompilationUnitSyntax) base.VisitCompilationUnit(node).NotNull(); + node = (CompilationUnitSyntax)base.VisitCompilationUnit(node).NotNull(); var defaultTargetField = node.Members.OfType() .SingleOrDefault(x => x.GetSingleDeclarator().Identifier.Text == defaultTargetFieldName); + if (defaultTargetField != null) { var literalExpression = defaultTargetField.GetSingleDeclarator().Initializer?.Value as LiteralExpressionSyntax; var defaultTarget = literalExpression.GetConstantValue(); - var mainMethodDeclaration = ParseMemberDeclaration($"public static int Main() => Execute(x => x.{defaultTarget});"); - node = node.WithMembers(List(new[] { mainMethodDeclaration }.Concat(node.Members.Except(new[] { defaultTargetField })))); + var mainMethodDeclaration = + ParseMemberDeclaration($"public static int Main() => Execute(x => x.{defaultTarget});"); + + node = node.WithMembers(List(new[] + { + mainMethodDeclaration + }.Concat(node.Members.Except(new[] + { + defaultTargetField + })))); } else if (defaultTargetName != null) { - var mainMethodDeclaration = ParseMemberDeclaration($"public static int Main() => Execute(x => x.{defaultTargetName});"); - node = node.WithMembers(List(new[] { mainMethodDeclaration }.Concat(node.Members))); + var mainMethodDeclaration = + ParseMemberDeclaration($"public static int Main() => Execute(x => x.{defaultTargetName});"); + + node = node.WithMembers(List(new[] + { + mainMethodDeclaration + }.Concat(node.Members))); } - var baseTypes = SeparatedList(new BaseTypeSyntax[] { SimpleBaseType(ParseTypeName(nameof(FalloutBuild))) }); + var baseTypes = SeparatedList(new BaseTypeSyntax[] + { + SimpleBaseType(ParseTypeName(nameof(FalloutBuild))) + }); + var classDeclaration = ClassDeclaration("Build") .WithBaseList(BaseList(baseTypes)) .WithMembers(List(node.Members.Where(x => x != defaultTargetField))); @@ -93,33 +111,46 @@ public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) return CompilationUnit() .WithUsings(List(namespaceUsings.Concat(staticUsings))) - .WithMembers(List(new MemberDeclarationSyntax[] { classDeclaration })); + .WithMembers(List(new MemberDeclarationSyntax[] + { + classDeclaration + })); } public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) { - node = (GlobalStatementSyntax) base.VisitGlobalStatement(node).NotNull(); + node = (GlobalStatementSyntax)base.VisitGlobalStatement(node).NotNull(); if (node.Statement is not ExpressionStatementSyntax expressionStatement) + { return node .WithLeadingTrivia(BeginMultilineComment) .WithTrailingTrivia(EndMultilineComment); + } var invocationExpression = expressionStatement.Expression as InvocationExpressionSyntax; var identifierName = invocationExpression?.GetIdentifierName(); if (identifierName == "Setup" || identifierName == "Teardown") + { return node .WithLeadingTrivia(BeginMultilineComment) .WithTrailingTrivia(EndMultilineComment); + } if (identifierName == "RunTarget") { var expression = invocationExpression.GetSingleArgument(); if (expression is IdentifierNameSyntax targetIdentifier) + { defaultTargetFieldName = targetIdentifier.Identifier.Text; + } + if (expression is LiteralExpressionSyntax literalExpression) + { defaultTargetName = literalExpression.GetConstantValue(); + } + return null; } diff --git a/src/Fallout.Cli/Rewriting/Cake/CodeAnalysisExtensions.cs b/src/Fallout.Cli/Rewriting/Cake/CodeAnalysisExtensions.cs index 95ddd1c90..76157eae2 100644 --- a/src/Fallout.Cli/Rewriting/Cake/CodeAnalysisExtensions.cs +++ b/src/Fallout.Cli/Rewriting/Cake/CodeAnalysisExtensions.cs @@ -26,7 +26,8 @@ public static InvocationExpressionSyntax WithArguments( return node.WithArgumentList(ArgumentList(SeparatedList(expressions.Select(Argument)))); } - public static InvocationExpressionSyntax WithArguments(this InvocationExpressionSyntax node, IEnumerable expressions) + public static InvocationExpressionSyntax WithArguments(this InvocationExpressionSyntax node, + IEnumerable expressions) { return node.WithArguments(expressions.ToArray()); } @@ -65,7 +66,9 @@ public static string GetIdentifierName(this InvocationExpressionSyntax invocatio public static TypeSyntax GetSingleGenericArgumentOrNull(this InvocationExpressionSyntax invocationExpression) { - return invocationExpression.Expression is GenericNameSyntax genericName ? genericName.TypeArgumentList.Arguments.Single() : null; + return invocationExpression.Expression is GenericNameSyntax genericName + ? genericName.TypeArgumentList.Arguments.Single() + : null; } public static T GetSingleArgument(this InvocationExpressionSyntax invocationExpression) @@ -82,7 +85,7 @@ public static T GetArgumentAt(this InvocationExpressionSyntax invocationExpre public static T GetConstantValue(this LiteralExpressionSyntax literalExpression) { - return (T) literalExpression.Token.Value; + return (T)literalExpression.Token.Value; } public static VariableDeclaratorSyntax GetSingleDeclarator(this FieldDeclarationSyntax fieldDeclaration) @@ -106,13 +109,19 @@ public static TypeSyntax GetExpressionType(this ExpressionSyntax expression) if (expression is MemberAccessExpressionSyntax memberAccessExpression && memberAccessExpression.Expression is IdentifierNameSyntax identifierName) + { return ParseTypeName(identifierName.Identifier.Text); + } if (expression is ObjectCreationExpressionSyntax objectCreationExpression) + { return objectCreationExpression.Type; + } if (expression is ArrayCreationExpressionSyntax arrayCreationExpression) + { return arrayCreationExpression.Type; + } return ParseTypeName("object"); } diff --git a/src/Fallout.Cli/Rewriting/Cake/FormattingRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/FormattingRewriter.cs index 63a84b8ad..ed5030da2 100644 --- a/src/Fallout.Cli/Rewriting/Cake/FormattingRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/FormattingRewriter.cs @@ -1,20 +1,26 @@ using System; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.Utilities.Collections; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; internal class FormattingRewriter : SafeSyntaxRewriter { - private static readonly SyntaxTrivia[] Indent = { Space, Space, Space, Space }; + private static readonly SyntaxTrivia[] Indent = + { + Space, + Space, + Space, + Space + }; public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - node = (PropertyDeclarationSyntax) base.VisitPropertyDeclaration(node).NotNull(); + node = (PropertyDeclarationSyntax)base.VisitPropertyDeclaration(node).NotNull(); return (node.Type as SimpleNameSyntax)?.Identifier.Text == nameof(Target) ? node.WithLeadingTrivia(LineFeed.Concat(Indent)) @@ -24,7 +30,9 @@ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax no public override SyntaxToken VisitToken(SyntaxToken token) { if (token.Parent is not MemberAccessExpressionSyntax memberAccessExpression) + { return token; + } var identifierName = memberAccessExpression.GetIdentifierName(); if (identifierName == nameof(ITargetDefinition.Executes) || @@ -33,10 +41,14 @@ public override SyntaxToken VisitToken(SyntaxToken token) identifierName == nameof(ITargetDefinition.OnlyWhenStatic) || identifierName == nameof(ITargetDefinition.OnlyWhenDynamic) || identifierName == nameof(ITargetDefinition.ProceedAfterFailure)) + { return token.WithLeadingTrivia(LineFeed.Concat(Indent).Concat(Indent)); + } if (identifierName.StartsWith("Set")) + { return token.WithLeadingTrivia(LineFeed.Concat(Indent).Concat(Indent).Concat(Indent)); + } return token; } @@ -54,7 +66,12 @@ public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) _ => _ .WithModifiers(TokenList(node .Modifiers.Select((x, i) => x - .WithLeadingTrivia(i == 0 ? new SyntaxTrivia[0] : new[] { Space }))))); + .WithLeadingTrivia(i == 0 + ? new SyntaxTrivia[0] + : new[] + { + Space + }))))); } public override SyntaxNode VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node) diff --git a/src/Fallout.Cli/Rewriting/Cake/IdentifierNameRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/IdentifierNameRewriter.cs index 025299a64..5c4495d4e 100644 --- a/src/Fallout.Cli/Rewriting/Cake/IdentifierNameRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/IdentifierNameRewriter.cs @@ -1,12 +1,10 @@ -using System; using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.Tools.DotNet; using Fallout.Common.Tools.MSBuild; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Fallout.Cli.Rewriting.Cake; diff --git a/src/Fallout.Cli/Rewriting/Cake/InvocationRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/InvocationRewriter.cs index c30b3efcf..b93c1afcc 100644 --- a/src/Fallout.Cli/Rewriting/Cake/InvocationRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/InvocationRewriter.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Fallout.Common; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Fallout.Common; -using Fallout.Common.IO; using Serilog; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; @@ -17,7 +16,8 @@ internal class InvocationRewriter : SafeSyntaxRewriter { new(original: "Does", replacement: nameof(ITargetDefinition.Executes), convert: ConvertLambdaBodyToBlock), new(original: "IsDependentOn", replacement: nameof(ITargetDefinition.DependsOn), convert: ConvertStringToIdentifier), - new(original: "IsDependeeOf", replacement: nameof(ITargetDefinition.DependentFor), convert: ConvertStringToIdentifier), + new(original: "IsDependeeOf", replacement: nameof(ITargetDefinition.DependentFor), + convert: ConvertStringToIdentifier), new(original: "ContinueOnError", replacement: nameof(ITargetDefinition.ProceedAfterFailure)), new(original: "WithCriteria", replacement: RenameWithCriteria, convert: ConvertWithCriteria), // Logger @@ -25,16 +25,16 @@ internal class InvocationRewriter : SafeSyntaxRewriter new(original: "Verbose", replacement: $"{nameof(Log)}.{nameof(Log.Debug)}"), }; - private static Func ConvertStringToIdentifier = x => + private static readonly Func ConvertStringToIdentifier = x => { - var literalExpression = (LiteralExpressionSyntax) x; + var literalExpression = (LiteralExpressionSyntax)x; var targetName = literalExpression.GetConstantValue(); return IdentifierName(targetName); }; - private static Func ConvertLambdaBodyToBlock = x => + private static readonly Func ConvertLambdaBodyToBlock = x => { - var lambdaExpression = (LambdaExpressionSyntax) x; + var lambdaExpression = (LambdaExpressionSyntax)x; return lambdaExpression.Block != null ? lambdaExpression : lambdaExpression @@ -55,11 +55,13 @@ internal class InvocationRewriter : SafeSyntaxRewriter public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { - node = (InvocationExpressionSyntax) base.VisitInvocationExpression(node).NotNull(); + node = (InvocationExpressionSyntax)base.VisitInvocationExpression(node).NotNull(); var name = node.GetIdentifierName(); var replacement = Replacements.SingleOrDefault(x => x.Original == name); if (replacement == null) + { return node; + } return node.Expression switch { @@ -103,8 +105,11 @@ public ReplacementData( } public string Original { get; } + public Func Replacement { get; } + public Func Convert { get; } + public Func Reorder { get; } } } diff --git a/src/Fallout.Cli/Rewriting/Cake/MemberAccessRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/MemberAccessRewriter.cs index facc0245f..eecbe560c 100644 --- a/src/Fallout.Cli/Rewriting/Cake/MemberAccessRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/MemberAccessRewriter.cs @@ -1,9 +1,7 @@ -using System; using System.Collections.Generic; -using System.Linq; +using Fallout.Common; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Fallout.Common; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -18,7 +16,7 @@ internal class MemberAccessRewriter : SafeSyntaxRewriter public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { - node = (MemberAccessExpressionSyntax) base.VisitMemberAccessExpression(node).NotNull(); + node = (MemberAccessExpressionSyntax)base.VisitMemberAccessExpression(node).NotNull(); return node.Expression is not IdentifierNameSyntax identifierNameSyntax || !Replacements.TryGetValue(identifierNameSyntax.Identifier.Text, out var newName) ? node diff --git a/src/Fallout.Cli/Rewriting/Cake/ParameterRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/ParameterRewriter.cs index 4cd1e697f..6dd94d752 100644 --- a/src/Fallout.Cli/Rewriting/Cake/ParameterRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/ParameterRewriter.cs @@ -1,10 +1,9 @@ -using System; using System.Linq; +using Fallout.Common.IO; +using Fallout.Common.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Fallout.Common.IO; -using Fallout.Common.Utilities; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -14,12 +13,16 @@ internal class ParameterRewriter : SafeSyntaxRewriter public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { if (node.Parent is not CompilationUnitSyntax) + { return node; + } var variableDeclarator = node.GetSingleDeclarator(); var invocationExpression = variableDeclarator.Initializer?.Value as InvocationExpressionSyntax; if (invocationExpression?.GetIdentifierName() != "Argument") + { return node; + } var originalNode = node; var resolutionName = invocationExpression.GetArgumentAt(0).GetConstantValue(); @@ -32,10 +35,18 @@ AttributeListSyntax CreateAttributeListWith(string name, string argument = null) { var attribute = Attribute(IdentifierName(name)); if (argument != null) + { attribute = attribute.WithArgumentList(AttributeArgumentList( - SeparatedList(new[] { AttributeArgument(argument.ToLiteralExpression()) }))); + SeparatedList(new[] + { + AttributeArgument(argument.ToLiteralExpression()) + }))); + } - return AttributeList(SeparatedList(new[] { attribute })); + return AttributeList(SeparatedList(new[] + { + attribute + })); } node = node.WithoutTrivia(); @@ -46,11 +57,13 @@ AttributeListSyntax CreateAttributeListWith(string name, string argument = null) .EqualsOrdinalIgnoreCase(variableDeclarator.Identifier.Text) ? resolutionName : null)); + node = node.WithDeclaration( node.Declaration.WithType( variableDeclarator.Identifier.Text.ContainsAnyOrdinalIgnoreCase("path", "dir", "file") ? ParseTypeName(nameof(AbsolutePath)) : invocationExpression.GetSingleGenericArgumentOrNull() ?? defaultValue.GetExpressionType())); + node = node.WithDeclaration( node.Declaration .WithVariables( @@ -58,10 +71,13 @@ AttributeListSyntax CreateAttributeListWith(string name, string argument = null) .Select(x => x.WithInitializer(nonDefaultValue != null ? EqualsValueClause(nonDefaultValue) : null))))); + node = node.WithModifiers(TokenList(Token(SyntaxKind.ReadOnlyKeyword))); if (variableDeclarator.Identifier.Text.ContainsAnyOrdinalIgnoreCase("password", "key", "token", "secret", "credentials")) + { node = node.AddAttributeLists(CreateAttributeListWith("Secret")); + } return node.WithTriviaFrom(originalNode); } diff --git a/src/Fallout.Cli/Rewriting/Cake/RegularFieldRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/RegularFieldRewriter.cs index f701325c5..1a372d1c6 100644 --- a/src/Fallout.Cli/Rewriting/Cake/RegularFieldRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/RegularFieldRewriter.cs @@ -1,8 +1,6 @@ -using System; -using System.Linq; +using Fallout.Common; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Fallout.Common; namespace Fallout.Cli.Rewriting.Cake; @@ -10,11 +8,13 @@ internal class RegularFieldRewriter : SafeSyntaxRewriter { public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { - node = (FieldDeclarationSyntax) base.VisitFieldDeclaration(node).NotNull(); + node = (FieldDeclarationSyntax)base.VisitFieldDeclaration(node).NotNull(); var initializerValue = node.GetSingleDeclarator().Initializer?.Value; if (initializerValue != null) + { node = node.WithDeclaration(node.Declaration .WithType(initializerValue.GetExpressionType())); + } return node; } diff --git a/src/Fallout.Cli/Rewriting/Cake/RemoveUsingDirectivesRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/RemoveUsingDirectivesRewriter.cs index f1b806dde..ca36ebcce 100644 --- a/src/Fallout.Cli/Rewriting/Cake/RemoveUsingDirectivesRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/RemoveUsingDirectivesRewriter.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Fallout.Cli.Rewriting.Cake; diff --git a/src/Fallout.Cli/Rewriting/Cake/RenameFieldIdentifierRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/RenameFieldIdentifierRewriter.cs index e62e6fa70..e42572f98 100644 --- a/src/Fallout.Cli/Rewriting/Cake/RenameFieldIdentifierRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/RenameFieldIdentifierRewriter.cs @@ -1,9 +1,8 @@ -using System; using System.Collections.Generic; using System.Linq; +using Fallout.Common.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Fallout.Common.Utilities; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -15,7 +14,9 @@ internal class RenameFieldIdentifierRewriter : SafeSyntaxRewriter public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node) { if (node.Parent is not CompilationUnitSyntax or ClassDeclarationSyntax) + { return node; + } string CreateRename(string name) => renames[name] = name.Capitalize().ReplaceKnownWords(); diff --git a/src/Fallout.Cli/Rewriting/Cake/SafeSyntaxRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/SafeSyntaxRewriter.cs index e0614e8c6..0450be505 100644 --- a/src/Fallout.Cli/Rewriting/Cake/SafeSyntaxRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/SafeSyntaxRewriter.cs @@ -1,8 +1,7 @@ using System; -using System.Linq; +using Fallout.Common; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Fallout.Common; namespace Fallout.Cli.Rewriting.Cake; diff --git a/src/Fallout.Cli/Rewriting/Cake/TargetDefinitionRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/TargetDefinitionRewriter.cs index 0738e4eac..e69d0a65a 100644 --- a/src/Fallout.Cli/Rewriting/Cake/TargetDefinitionRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/TargetDefinitionRewriter.cs @@ -1,9 +1,8 @@ -using System; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.Utilities.Collections; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -12,7 +11,7 @@ internal class TargetDefinitionRewriter : SafeSyntaxRewriter { public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { - node = (InvocationExpressionSyntax) base.VisitInvocationExpression(node).NotNull(); + node = (InvocationExpressionSyntax)base.VisitInvocationExpression(node).NotNull(); if (node.Expression is IdentifierNameSyntax identifierName && identifierName.Identifier.Text == "Task") @@ -71,10 +70,12 @@ public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) { - var reducedNode = (GlobalStatementSyntax) base.VisitGlobalStatement(node).NotNull(); + var reducedNode = (GlobalStatementSyntax)base.VisitGlobalStatement(node).NotNull(); if (node.Statement is not ExpressionStatementSyntax expressionStatement) + { return reducedNode; + } var innerInvocationExpression = (expressionStatement.Expression as InvocationExpressionSyntax) .Descendants(x => (x.Expression as MemberAccessExpressionSyntax)?.Expression as InvocationExpressionSyntax) @@ -85,7 +86,8 @@ public override SyntaxNode VisitGlobalStatement(GlobalStatementSyntax node) var name = innerInvocationExpression.GetSingleArgument() .GetConstantValue() .Replace("-", "_"); - var expression = ((ExpressionStatementSyntax) reducedNode.Statement).Expression; + + var expression = ((ExpressionStatementSyntax)reducedNode.Statement).Expression; return PropertyDeclaration(ParseTypeName(nameof(Target)), name) .WithExpressionBody(ArrowExpressionClause(expression)) .WithSemicolonToken(expressionStatement.SemicolonToken); diff --git a/src/Fallout.Cli/Rewriting/Cake/ToolInvocationRewriter.cs b/src/Fallout.Cli/Rewriting/Cake/ToolInvocationRewriter.cs index 11801fc82..c4ede820c 100644 --- a/src/Fallout.Cli/Rewriting/Cake/ToolInvocationRewriter.cs +++ b/src/Fallout.Cli/Rewriting/Cake/ToolInvocationRewriter.cs @@ -1,15 +1,14 @@ -using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.Tools.DotNet; using Fallout.Common.Tools.MSBuild; using Fallout.Common.Tools.NuGet; using Fallout.Common.Tools.SignTool; using Fallout.Common.Utilities.Collections; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.Cli.Rewriting.Cake; @@ -19,32 +18,62 @@ internal class ToolInvocationRewriter : SafeSyntaxRewriter private static IEnumerable Replacements => new ReplacementData[] { - new(original: "DotNetCoreRestore", replacement: nameof(DotNetTasks.DotNetRestore), positionals: new[] { "ProjectFile" }), - new(original: "DotNetCoreBuild", replacement: nameof(DotNetTasks.DotNetBuild), positionals: new[] { "ProjectFile" }), - new(original: "DotNetCoreTest", replacement: nameof(DotNetTasks.DotNetTest), positionals: new[] { "ProjectFile" }), - new(original: "DotNetCorePack", replacement: nameof(DotNetTasks.DotNetPack), positionals: new[] { "ProjectFile" }), - new(original: "DotNetCorePublish", replacement: nameof(DotNetTasks.DotNetPublish), positionals: new[] { "Project" }), - new(original: "Sign", replacement: nameof(SignToolTasks.SignTool), positionals: new[] { "Files" }), - new(original: "NuGetRestore", replacement: nameof(NuGetTasks.NuGetRestore), positionals: new[] { "TargetPath" }), - new(original: "NuGetPack", replacement: nameof(NuGetTasks.NuGetPack), positionals: new[] { "TargetPath" }), - new(original: "MSBuild", replacement: nameof(MSBuildTasks.MSBuild), positionals: new[] { "TargetPath" }), + new(original: "DotNetCoreRestore", replacement: nameof(DotNetTasks.DotNetRestore), positionals: new[] + { + "ProjectFile" + }), + new(original: "DotNetCoreBuild", replacement: nameof(DotNetTasks.DotNetBuild), positionals: new[] + { + "ProjectFile" + }), + new(original: "DotNetCoreTest", replacement: nameof(DotNetTasks.DotNetTest), positionals: new[] + { + "ProjectFile" + }), + new(original: "DotNetCorePack", replacement: nameof(DotNetTasks.DotNetPack), positionals: new[] + { + "ProjectFile" + }), + new(original: "DotNetCorePublish", replacement: nameof(DotNetTasks.DotNetPublish), positionals: new[] + { + "Project" + }), + new(original: "Sign", replacement: nameof(SignToolTasks.SignTool), positionals: new[] + { + "Files" + }), + new(original: "NuGetRestore", replacement: nameof(NuGetTasks.NuGetRestore), positionals: new[] + { + "TargetPath" + }), + new(original: "NuGetPack", replacement: nameof(NuGetTasks.NuGetPack), positionals: new[] + { + "TargetPath" + }), + new(original: "MSBuild", replacement: nameof(MSBuildTasks.MSBuild), positionals: new[] + { + "TargetPath" + }), }; public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) { - node = (InvocationExpressionSyntax) base.VisitInvocationExpression(node).NotNull(); + node = (InvocationExpressionSyntax)base.VisitInvocationExpression(node).NotNull(); var identifierName = node.GetIdentifierName(); var replacement = Replacements.SingleOrDefault(x => x.Original == identifierName); if (replacement == null) + { return node; + } var arguments = new List<(string Name, ExpressionSyntax Value)>(); var settings = node.GetArgumentAt(replacement.Positionals.Length); node.Arguments().Take(replacement.Positionals.Length) .ForEach((x, i) => arguments.Add((replacement.Positionals[i], x))); + settings?.Initializer?.Expressions.Cast() - .ForEach(x => arguments.Add((((IdentifierNameSyntax) x.Left).Identifier.Text, x.Right))); + .ForEach(x => arguments.Add((((IdentifierNameSyntax)x.Left).Identifier.Text, x.Right))); ExpressionSyntax expr = IdentifierName("_"); foreach (var (name, value) in arguments) @@ -88,8 +117,11 @@ public ReplacementData( } public string Original { get; } + public string Replacement { get; } + public string[] Positionals { get; } + public Dictionary Renames { get; } } } diff --git a/src/Fallout.Common/Attributes/CheckPathEnvironmentVariableAttribute.cs b/src/Fallout.Common/Attributes/CheckPathEnvironmentVariableAttribute.cs index 26b24376b..d2cb8e727 100644 --- a/src/Fallout.Common/Attributes/CheckPathEnvironmentVariableAttribute.cs +++ b/src/Fallout.Common/Attributes/CheckPathEnvironmentVariableAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Fallout.Common.Tooling; namespace Fallout.Common.Execution; diff --git a/src/Fallout.Common/Attributes/FileSystemGlobbingAttributeBase.cs b/src/Fallout.Common/Attributes/FileSystemGlobbingAttributeBase.cs index b4d385931..20463957c 100644 --- a/src/Fallout.Common/Attributes/FileSystemGlobbingAttributeBase.cs +++ b/src/Fallout.Common/Attributes/FileSystemGlobbingAttributeBase.cs @@ -55,6 +55,7 @@ public override object GetValue(MemberInfo member, object instance) Assert.True( globbedElements.Contains(x), $"Value '{x}' for member '{member.Name}' is not contained any pattern '{patterns.JoinCommaSpace()}'")); + Assert.True(parameterValue.Length == 1 || memberType == typeof(AbsolutePath[]), $"Member '{member.Name}' can only accept a single value but got:" .Concat(parameterValue.Select(x => x.ToString())) @@ -74,7 +75,7 @@ public override object GetValue(MemberInfo member, object instance) public override IEnumerable<(string, object)> GetValueSet(MemberInfo member, object instance) { - return GetGlobbedElements(member).Select(x => (PathConstruction.GetRelativePath(Build.RootDirectory, x), (object) x)); + return GetGlobbedElements(member).Select(x => (PathConstruction.GetRelativePath(Build.RootDirectory, x), (object)x)); } private AbsolutePath[] GetGlobbedElements(MemberInfo member) diff --git a/src/Fallout.Common/Attributes/GitRepositoryAttribute.cs b/src/Fallout.Common/Attributes/GitRepositoryAttribute.cs index 9b6d80a07..599866a73 100644 --- a/src/Fallout.Common/Attributes/GitRepositoryAttribute.cs +++ b/src/Fallout.Common/Attributes/GitRepositoryAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; using Fallout.Common.ValueInjection; namespace Fallout.Common.Git; diff --git a/src/Fallout.Common/Attributes/GlobbingOptionsAttribute.cs b/src/Fallout.Common/Attributes/GlobbingOptionsAttribute.cs index 5b5a08c62..ae3bf729e 100644 --- a/src/Fallout.Common/Attributes/GlobbingOptionsAttribute.cs +++ b/src/Fallout.Common/Attributes/GlobbingOptionsAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Execution; namespace Fallout.Common.IO; diff --git a/src/Fallout.Common/Attributes/HandleSingleFileExecutionAttribute.cs b/src/Fallout.Common/Attributes/HandleSingleFileExecutionAttribute.cs index 3034762ee..41e47d39c 100644 --- a/src/Fallout.Common/Attributes/HandleSingleFileExecutionAttribute.cs +++ b/src/Fallout.Common/Attributes/HandleSingleFileExecutionAttribute.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.Json.Nodes; -using ICSharpCode.SharpZipLib.Zip; -using NuGet.Packaging; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Utilities; +using ICSharpCode.SharpZipLib.Zip; +using NuGet.Packaging; using Serilog; namespace Fallout.Common.Execution; @@ -20,7 +19,9 @@ public class HandleSingleFileExecutionAttribute : BuildExtensionAttributeBase, I public void OnBuildCreated(IReadOnlyCollection executableTargets) { if (!IsSingleFileExecution) + { return; + } InstallDotNetRuntime(); ExtractPackageFiles(); @@ -29,9 +30,13 @@ public void OnBuildCreated(IReadOnlyCollection executableTarge private static void ExtractPackageFiles() { var executingAssembly = Assembly.GetEntryAssembly().NotNull(); - var packageResourceNames = executingAssembly.GetManifestResourceNames().Where(x => x.EndsWithOrdinalIgnoreCase("nupkg")).ToList(); + var packageResourceNames = executingAssembly.GetManifestResourceNames().Where(x => x.EndsWithOrdinalIgnoreCase("nupkg")) + .ToList(); + if (!packageResourceNames.Any()) + { return; + } var globalPackagesDirectory = Constants.GlobalFalloutDirectory / "packages"; Log.Information("Extracting packages to {PackagesDirectory}", globalPackagesDirectory); @@ -43,7 +48,8 @@ private static void ExtractPackageFiles() var packageResourceStream = executingAssembly.GetManifestResourceStream(packageResourceName).NotNull(); var packageArchiveReader = new PackageArchiveReader(packageResourceStream); var nuspecReader = packageArchiveReader.NuspecReader; - var packageFile = globalPackagesDirectory / nuspecReader.GetId() / nuspecReader.GetVersion().ToString() / packageResourceName; + var packageFile = globalPackagesDirectory / nuspecReader.GetId() / nuspecReader.GetVersion().ToString() / + packageResourceName; packageResourceStream.Seek(offset: 0, SeekOrigin.Begin); packageResourceStream.CopyToFile(packageFile); @@ -97,6 +103,7 @@ private void InstallDotNetRuntime() private bool IsSingleFileExecution => Assembly.GetEntryAssembly().NotNull().Location == string.Empty; private string ScriptFileName => EnvironmentInfo.IsWin ? "dotnet-install.ps1" : "dotnet-install.sh"; + private AbsolutePath ScriptFile => Constants.GlobalFalloutDirectory / ScriptFileName; private string GetDotNetRuntimeVersion() diff --git a/src/Fallout.Common/Attributes/LatestGitHubReleaseAttribute.cs b/src/Fallout.Common/Attributes/LatestGitHubReleaseAttribute.cs index 5b2a9f6b3..e46ed80bc 100644 --- a/src/Fallout.Common/Attributes/LatestGitHubReleaseAttribute.cs +++ b/src/Fallout.Common/Attributes/LatestGitHubReleaseAttribute.cs @@ -1,12 +1,11 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using NuGet.Versioning; using Fallout.Common.Git; using Fallout.Common.Tools.GitHub; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; +using NuGet.Versioning; namespace Fallout.Common.Tooling; @@ -20,6 +19,7 @@ public LatestGitHubReleaseAttribute(string identifier) } public bool IncludePrerelease { get; set; } + public bool UseTagName { get; set; } public string Pattern { get; set; } = @"v?(?\d+\.\d+(?:\.\d+)?(?:\.\d+)?(?:-\w+)?)"; @@ -29,6 +29,7 @@ public override object GetValue(MemberInfo member, object instance) var repository = GitRepository.FromUrl($"https://github.com/{identifier}"); var releases = GitHubTasks.GitHubClient.Repository.Release .GetAll(repository.GetGitHubOwner(), repository.GetGitHubName()).GetAwaiter().GetResult(); + var versions = releases .Select(x => Regex.Match((!UseTagName ? x.Name : x.TagName).NotNullOrWhiteSpace(), Pattern)) .Select(x => x.Groups["version"].Value) @@ -36,7 +37,9 @@ public override object GetValue(MemberInfo member, object instance) .OrderByDescending(x => x); if (member.GetMemberType() == typeof(NuGetVersion[])) + { return versions.ToArray(); + } var latestVersion = versions.FirstOrDefault(x => !x.IsPrerelease || IncludePrerelease); return member.GetMemberType() == typeof(NuGetVersion) diff --git a/src/Fallout.Common/Attributes/LatestMavenVersionAttribute.cs b/src/Fallout.Common/Attributes/LatestMavenVersionAttribute.cs index 5421ac89b..1d3e75aa9 100644 --- a/src/Fallout.Common/Attributes/LatestMavenVersionAttribute.cs +++ b/src/Fallout.Common/Attributes/LatestMavenVersionAttribute.cs @@ -1,10 +1,9 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; -using NuGet.Versioning; using Fallout.Common.IO; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; +using NuGet.Versioning; namespace Fallout.Common.Tooling; @@ -33,6 +32,7 @@ public override object GetValue(MemberInfo member, object instance) .Select(NuGetVersion.Parse) .OrderByDescending(x => x) .FirstOrDefault(x => !x.IsPrerelease || IncludePrerelease); + return member.GetMemberType() == typeof(string) ? version?.ToNormalizedString() : version; diff --git a/src/Fallout.Common/Attributes/LatestMyGetVersionAttribute.cs b/src/Fallout.Common/Attributes/LatestMyGetVersionAttribute.cs index 8ed7f7ad0..0afd63603 100644 --- a/src/Fallout.Common/Attributes/LatestMyGetVersionAttribute.cs +++ b/src/Fallout.Common/Attributes/LatestMyGetVersionAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; using Fallout.Common.IO; using Fallout.Common.Utilities; diff --git a/src/Fallout.Common/Attributes/LatestNpmVersionAttribute.cs b/src/Fallout.Common/Attributes/LatestNpmVersionAttribute.cs index da4fc3ba6..6407ffc22 100644 --- a/src/Fallout.Common/Attributes/LatestNpmVersionAttribute.cs +++ b/src/Fallout.Common/Attributes/LatestNpmVersionAttribute.cs @@ -1,9 +1,7 @@ -using System; -using System.Linq; -using System.Reflection; -using NuGet.Versioning; +using System.Reflection; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; +using NuGet.Versioning; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Common/Attributes/LatestNuGetVersionAttribute.cs b/src/Fallout.Common/Attributes/LatestNuGetVersionAttribute.cs index e780b0b61..896019a9c 100644 --- a/src/Fallout.Common/Attributes/LatestNuGetVersionAttribute.cs +++ b/src/Fallout.Common/Attributes/LatestNuGetVersionAttribute.cs @@ -1,9 +1,7 @@ -using System; -using System.Linq; -using System.Reflection; -using NuGet.Versioning; +using System.Reflection; using Fallout.Common.Utilities; using Fallout.Common.ValueInjection; +using NuGet.Versioning; namespace Fallout.Common.Tooling; @@ -17,11 +15,14 @@ public LatestNuGetVersionAttribute(string packageId) } public bool IncludePrerelease { get; set; } + public bool IncludeUnlisted { get; set; } public override object GetValue(MemberInfo member, object instance) { - var version = NuGetVersionResolver.GetLatestVersion(packageId, IncludePrerelease, IncludeUnlisted).GetAwaiter().GetResult(); + var version = NuGetVersionResolver.GetLatestVersion(packageId, IncludePrerelease, IncludeUnlisted).GetAwaiter() + .GetResult(); + return member.GetMemberType() == typeof(string) ? version : NuGetVersion.Parse(version); diff --git a/src/Fallout.Common/Attributes/LocalPathAttribute.cs b/src/Fallout.Common/Attributes/LocalPathAttribute.cs index 769ffc4cb..daa2c100a 100644 --- a/src/Fallout.Common/Attributes/LocalPathAttribute.cs +++ b/src/Fallout.Common/Attributes/LocalPathAttribute.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Reflection; using Fallout.Common.IO; @@ -44,6 +43,7 @@ public override object GetValue(MemberInfo member, object instance) var toolPath = PathConstruction.HasPathRoot(absoluteOrRelativePath) ? absoluteOrRelativePath : Path.Combine(Build.RootDirectory, absoluteOrRelativePath); + return ToolResolver.GetTool(toolPath); } } diff --git a/src/Fallout.Common/Attributes/NpmPackageAttribute.cs b/src/Fallout.Common/Attributes/NpmPackageAttribute.cs index 32e5c9b12..2fa21bebe 100644 --- a/src/Fallout.Common/Attributes/NpmPackageAttribute.cs +++ b/src/Fallout.Common/Attributes/NpmPackageAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Common/Attributes/NuGetPackageAttribute.cs b/src/Fallout.Common/Attributes/NuGetPackageAttribute.cs index 29ba513bb..afc973dec 100644 --- a/src/Fallout.Common/Attributes/NuGetPackageAttribute.cs +++ b/src/Fallout.Common/Attributes/NuGetPackageAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Common/Attributes/PathVariableAttribute.cs b/src/Fallout.Common/Attributes/PathVariableAttribute.cs index 73f82f552..1dd972010 100644 --- a/src/Fallout.Common/Attributes/PathVariableAttribute.cs +++ b/src/Fallout.Common/Attributes/PathVariableAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Common/Attributes/SolutionAttribute.cs b/src/Fallout.Common/Attributes/SolutionAttribute.cs index 388d22f65..18e83a235 100644 --- a/src/Fallout.Common/Attributes/SolutionAttribute.cs +++ b/src/Fallout.Common/Attributes/SolutionAttribute.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; +using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; -using Serilog; -using Fallout.Common; namespace Fallout.Solutions; @@ -67,7 +65,9 @@ private AbsolutePath TryGetSolutionFileFromFalloutFile() { var falloutFile = Build.RootDirectory / Constants.FalloutFileName; if (!falloutFile.Exists()) + { return null; + } var solutionFileRelative = falloutFile.ReadAllLines().ElementAtOrDefault(0); Assert.True(solutionFileRelative != null && !solutionFileRelative.Contains(value: '\\'), diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs index 9bacdb915..b5ffaa1b2 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; @@ -34,7 +33,9 @@ public partial class AppVeyor : Host, IBuildServer internal static bool IsRunningAppVeyor => EnvironmentInfo.HasVariable("APPVEYOR"); - private readonly Lazy cli = Lazy.Create(() => IsRunningAppVeyor ? ToolResolver.GetEnvironmentOrPathTool("appveyor") : null); + private readonly Lazy cli = + Lazy.Create(() => IsRunningAppVeyor ? ToolResolver.GetEnvironmentOrPathTool("appveyor") : null); + private int messageCount; internal AppVeyor() @@ -42,45 +43,81 @@ internal AppVeyor() } string IBuildServer.Branch => RepositoryBranch; + string IBuildServer.Commit => RepositoryCommitSha; public Tool Cli => cli.Value; public string Url => EnvironmentInfo.GetVariable("APPVEYOR_URL"); + public string ApiUrl => EnvironmentInfo.GetVariable("APPVEYOR_API_URL"); + public string AccountName => EnvironmentInfo.GetVariable("APPVEYOR_ACCOUNT_NAME"); + public int ProjectId => EnvironmentInfo.GetVariable("APPVEYOR_PROJECT_ID"); + public string ProjectName => EnvironmentInfo.GetVariable("APPVEYOR_PROJECT_NAME"); + public string ProjectSlug => EnvironmentInfo.GetVariable("APPVEYOR_PROJECT_SLUG"); + public string BuildFolder => EnvironmentInfo.GetVariable("APPVEYOR_BUILD_FOLDER"); + public int BuildId => EnvironmentInfo.GetVariable("APPVEYOR_BUILD_ID"); + public int BuildNumber => EnvironmentInfo.GetVariable("APPVEYOR_BUILD_NUMBER"); + public string BuildVersion => EnvironmentInfo.GetVariable("APPVEYOR_BUILD_VERSION"); + public string BuildWorkerImage => EnvironmentInfo.GetVariable("APPVEYOR_BUILD_WORKER_IMAGE"); + public int? PullRequestNumber => EnvironmentInfo.GetVariable("APPVEYOR_PULL_REQUEST_NUMBER"); + public string PullRequestTitle => EnvironmentInfo.GetVariable("APPVEYOR_PULL_REQUEST_TITLE"); + public string PullRequestHeadRepositoryName => EnvironmentInfo.GetVariable("APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME"); + public string PullRequestHeadRepositoryBranch => EnvironmentInfo.GetVariable("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH"); + public string PullRequestHeadCommit => EnvironmentInfo.GetVariable("APPVEYOR_PULL_REQUEST_HEAD_COMMIT"); + public string JobId => EnvironmentInfo.GetVariable("APPVEYOR_JOB_ID"); + public string JobName => EnvironmentInfo.GetVariable("APPVEYOR_JOB_NAME"); + public int JobNumber => EnvironmentInfo.GetVariable("APPVEYOR_JOB_NUMBER"); + public string RepositoryProvider => EnvironmentInfo.GetVariable("APPVEYOR_REPO_PROVIDER"); + public string RepositoryScm => EnvironmentInfo.GetVariable("APPVEYOR_REPO_SCM"); + public string RepositoryName => EnvironmentInfo.GetVariable("APPVEYOR_REPO_NAME"); + public string RepositoryBranch => EnvironmentInfo.GetVariable("APPVEYOR_REPO_BRANCH"); + public bool RepositoryTag => EnvironmentInfo.GetVariable("APPVEYOR_REPO_TAG"); + public string RepositoryTagName => EnvironmentInfo.GetVariable("APPVEYOR_REPO_TAG_NAME"); + public string RepositoryCommitSha => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT"); + public string RepositoryCommitAuthor => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT_AUTHOR"); + public string RepositoryCommitAuthorEmail => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL"); + public DateTime RepositoryCommitTimestamp => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT_TIMESTAMP"); + public string RepositoryCommitMessage => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT_MESSAGE"); + public string RepositoryCommitMessageExtended => EnvironmentInfo.GetVariable("APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED"); + public bool ScheduledBuild => EnvironmentInfo.GetVariable("APPVEYOR_SCHEDULED_BUILD"); + public bool ForcedBuild => EnvironmentInfo.GetVariable("APPVEYOR_FORCED_BUILD"); + public bool Rebuild => EnvironmentInfo.GetVariable("APPVEYOR_RE_BUILD"); + public string Platform => EnvironmentInfo.GetVariable("PLATFORM"); + public string Configuration => EnvironmentInfo.GetVariable("CONFIGURATION"); public void UpdateBuildVersion(string version) diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyorAttribute.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyorAttribute.cs index e7b9f6fd8..7bc1ac8b2 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyorAttribute.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyorAttribute.cs @@ -15,50 +15,67 @@ namespace Fallout.Common.CI.AppVeyor; /// Interface according to the official website. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public class AppVeyorAttribute : ConfigurationAttributeBase +public class AppVeyorAttribute( + string suffix, + AppVeyorImage image, + params AppVeyorImage[] images) + : ConfigurationAttributeBase { - private readonly string suffix; - private readonly AppVeyorImage[] images; - - public AppVeyorAttribute( - AppVeyorImage image, - params AppVeyorImage[] images) - : this(suffix: null, image, images) + private readonly AppVeyorImage[] images = new[] { - } + image + }.Concat(images).ToArray(); public AppVeyorAttribute( - string suffix, AppVeyorImage image, params AppVeyorImage[] images) + : this(suffix: null, image, images) { - this.suffix = suffix; - this.images = new[] { image }.Concat(images).ToArray(); } public override string IdPostfix => suffix; public override Type HostType => typeof(AppVeyor); + public override AbsolutePath ConfigurationFile => Build.RootDirectory / ConfigurationFileName; - public override IEnumerable GeneratedFiles => new[] { ConfigurationFile }; + + public override IEnumerable GeneratedFiles => new[] + { + ConfigurationFile + }; + private string ConfigurationFileName => suffix != null ? $"appveyor.{suffix}.yml" : "appveyor.yml"; public override IEnumerable RelevantTargetNames => InvokedTargets; + public override IEnumerable IrrelevantTargetNames => new string[0]; public AppVeyorService[] Services { get; set; } = new AppVeyorService[0]; + public string[] InvokedTargets { get; set; } = new string[0]; + public string[] BranchesOnly { get; set; } = new string[0]; + public string[] BranchesExcept { get; set; } = new string[0]; + public bool SkipTags { get; set; } + public bool SkipBranchesWithPullRequest { get; set; } + public string OnlyCommitsMessage { get; set; } + public string OnlyCommitsAuthor { get; set; } + public string SkipCommitsMessage { get; set; } + public string SkipCommitsAuthor { get; set; } + public string[] Init { get; set; } = new string[0]; + public string[] Cache { get; set; } = new string[0]; + public string[] Secrets { get; set; } + public bool Submodules { get; set; } public override CustomFileWriter CreateWriter(StreamWriter streamWriter) @@ -69,24 +86,24 @@ public override CustomFileWriter CreateWriter(StreamWriter streamWriter) public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) { return new AppVeyorConfiguration - { - Images = images, - BuildCmdPath = BuildCmdPath, - Services = Services, - Branches = GetBranches(), - SkipTags = SkipTags, - SkipBranchesWithPullRequest = SkipBranchesWithPullRequest, - OnlyCommitsMessage = OnlyCommitsMessage, - OnlyCommitsAuthor = OnlyCommitsAuthor, - SkipCommitsMessage = SkipCommitsMessage, - SkipCommitsAuthor = SkipCommitsAuthor, - InvokedTargets = InvokedTargets, - Init = Init, - Cache = Cache, - Artifacts = GetArtifacts(relevantTargets).ToArray(), - Secrets = GetSecrets(), - Submodules = Submodules - }; + { + Images = images, + BuildCmdPath = BuildCmdPath, + Services = Services, + Branches = GetBranches(), + SkipTags = SkipTags, + SkipBranchesWithPullRequest = SkipBranchesWithPullRequest, + OnlyCommitsMessage = OnlyCommitsMessage, + OnlyCommitsAuthor = OnlyCommitsAuthor, + SkipCommitsMessage = SkipCommitsMessage, + SkipCommitsAuthor = SkipCommitsAuthor, + InvokedTargets = InvokedTargets, + Init = Init, + Cache = Cache, + Artifacts = GetArtifacts(relevantTargets).ToArray(), + Secrets = GetSecrets(), + Submodules = Submodules + }; } private IEnumerable GetArtifacts(IReadOnlyCollection relevantTargets) @@ -100,13 +117,15 @@ private IEnumerable GetArtifacts(IReadOnlyCollection r protected AppVeyorBranches GetBranches() { if (BranchesOnly.Length == 0 && BranchesExcept.Length == 0) + { return null; + } return new AppVeyorBranches - { - Only = BranchesOnly, - Except = BranchesExcept - }; + { + Only = BranchesOnly, + Except = BranchesExcept + }; } private Dictionary GetSecrets() diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyorImage.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyorImage.cs index b870f7c75..e8dab7f29 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyorImage.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyorImage.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.CI.AppVeyor; @@ -9,15 +7,36 @@ namespace Fallout.Common.CI.AppVeyor; /// public enum AppVeyorImage { - [EnumValue("Visual Studio 2013")] VisualStudio2013, - [EnumValue("Visual Studio 2015")] VisualStudio2015, - [EnumValue("Visual Studio 2017")] VisualStudio2017, - [EnumValue("Visual Studio 2019")] VisualStudio2019, - [EnumValue("Visual Studio 2022")] VisualStudio2022, - [EnumValue("Visual Studio 2022")] VisualStudioLatest, - [EnumValue("Ubuntu1604")] Ubuntu1604, - [EnumValue("Ubuntu1804")] Ubuntu1804, - [EnumValue("Ubuntu")] UbuntuLatest, - [EnumValue("MacOs-Mojave")] MacOsMojave, - [EnumValue("MacOs")] MacOsLatest + [EnumValue("Visual Studio 2013")] + VisualStudio2013, + + [EnumValue("Visual Studio 2015")] + VisualStudio2015, + + [EnumValue("Visual Studio 2017")] + VisualStudio2017, + + [EnumValue("Visual Studio 2019")] + VisualStudio2019, + + [EnumValue("Visual Studio 2022")] + VisualStudio2022, + + [EnumValue("Visual Studio 2022")] + VisualStudioLatest, + + [EnumValue("Ubuntu1604")] + Ubuntu1604, + + [EnumValue("Ubuntu1804")] + Ubuntu1804, + + [EnumValue("Ubuntu")] + UbuntuLatest, + + [EnumValue("MacOs-Mojave")] + MacOsMojave, + + [EnumValue("MacOs")] + MacOsLatest } diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyorSecretAttribute.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyorSecretAttribute.cs index 830fffd42..625508590 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyorSecretAttribute.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyorSecretAttribute.cs @@ -1,17 +1,11 @@ using System; -using System.Linq; namespace Fallout.Common.CI.AppVeyor; [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public class AppVeyorSecretAttribute : Attribute +public class AppVeyorSecretAttribute(string parameter, string value) : Attribute { - public AppVeyorSecretAttribute(string parameter, string value) - { - Parameter = parameter; - Value = value; - } + public string Parameter { get; } = parameter; - public string Parameter { get; } - public string Value { get; } + public string Value { get; } = value; } diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyorService.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyorService.cs index 2bfd3551f..32e27dd32 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyorService.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyorService.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.AppVeyor; /// diff --git a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorBranches.cs b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorBranches.cs index cb2f721f8..fb0a5a184 100644 --- a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorBranches.cs +++ b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorBranches.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -8,6 +6,7 @@ namespace Fallout.Common.CI.AppVeyor.Configuration; public class AppVeyorBranches : ConfigurationEntity { public string[] Only { get; set; } + public string[] Except { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorConfiguration.cs b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorConfiguration.cs index 4e76c58d6..b39e6a50d 100644 --- a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorConfiguration.cs +++ b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorConfiguration.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -10,20 +8,35 @@ namespace Fallout.Common.CI.AppVeyor.Configuration; public class AppVeyorConfiguration : ConfigurationEntity { public AppVeyorImage[] Images { get; set; } + public string BuildCmdPath { get; set; } + public AppVeyorService[] Services { get; set; } + public AppVeyorBranches Branches { get; set; } + public string[] Init { get; set; } + public string[] Cache { get; set; } + public bool Submodules { get; set; } + public string[] InvokedTargets { get; set; } + public string[] Artifacts { get; set; } + public bool SkipTags { get; set; } + public bool SkipBranchesWithPullRequest { get; set; } + public string OnlyCommitsMessage { get; set; } + public string OnlyCommitsAuthor { get; set; } + public string SkipCommitsMessage { get; set; } + public string SkipCommitsAuthor { get; set; } + public Dictionary Secrets { get; set; } // ReSharper disable once CognitiveComplexity @@ -41,6 +54,7 @@ public override void Write(CustomFileWriter writer) { Services.ForEach(x => writer.WriteLine($"- {x.GetValue().ToLowerInvariant()}")); } + writer.WriteLine(); } @@ -50,6 +64,7 @@ public override void Write(CustomFileWriter writer) { Branches.Write(writer); } + writer.WriteLine(); } @@ -61,18 +76,28 @@ public override void Write(CustomFileWriter writer) SkipCommitsAuthor != null) { if (SkipTags) + { writer.WriteLine("skip_tags: true"); + } + if (SkipBranchesWithPullRequest) + { writer.WriteLine("skip_branch_with_pr: true"); + } if (OnlyCommitsMessage != null || OnlyCommitsAuthor != null) { using (writer.WriteBlock("only_commits:")) { if (OnlyCommitsMessage != null) + { writer.WriteLine($"message: {OnlyCommitsMessage}"); + } + if (OnlyCommitsAuthor != null) + { writer.WriteLine($"author: {OnlyCommitsAuthor}"); + } } } @@ -81,9 +106,14 @@ public override void Write(CustomFileWriter writer) using (writer.WriteBlock("skip_commits:")) { if (SkipCommitsMessage != null) + { writer.WriteLine($"message: {SkipCommitsMessage}"); + } + if (SkipCommitsAuthor != null) + { writer.WriteLine($"author: {SkipCommitsAuthor}"); + } } } @@ -96,6 +126,7 @@ public override void Write(CustomFileWriter writer) { Init.ForEach(x => writer.WriteLine($"- {x}")); } + writer.WriteLine(); } @@ -105,6 +136,7 @@ public override void Write(CustomFileWriter writer) { writer.WriteLine("- git submodule update --init --recursive"); } + writer.WriteLine(); } diff --git a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorCustomWriterExtensions.cs b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorCustomWriterExtensions.cs index 5e753554a..ab12fdf0c 100644 --- a/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorCustomWriterExtensions.cs +++ b/src/Fallout.Common/CI/AppVeyor/Configuration/AppVeyorCustomWriterExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.AppVeyor.Configuration; diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.Theming.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.Theming.cs index 160de8739..3989ee6bd 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.Theming.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.Theming.cs @@ -50,7 +50,9 @@ protected internal override void ReportError(string text, string details = null) protected internal override bool FilterMessage(string message) { if (!message.ContainsOrdinalIgnoreCase("##vso[")) + { return false; + } Console.WriteLine(message); return true; diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.cs index 904923913..0b43103b1 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelines.cs @@ -31,60 +31,109 @@ internal AzurePipelines(Action messageSink) } string IBuildServer.Branch => SourceBranch; + string IBuildServer.Commit => SourceVersion; public string AgentBuildDirectory => EnvironmentInfo.GetVariable("AGENT_BUILDDIRECTORY"); + public string AgentHomeDirectory => EnvironmentInfo.GetVariable("AGENT_HOMEDIRECTORY"); + public long AgentId => EnvironmentInfo.GetVariable("AGENT_ID"); + public AzurePipelinesJobStatus AgentJobStatus => EnvironmentInfo.GetVariable("AGENT_JOBSTATUS"); + public string AgentMachineName => EnvironmentInfo.GetVariable("AGENT_MACHINENAME"); + public string AgentName => EnvironmentInfo.GetVariable("AGENT_NAME"); + public string AgentWorkFolder => EnvironmentInfo.GetVariable("AGENT_WORKFOLDER"); + public string ArtifactStagingDirectory => EnvironmentInfo.GetVariable("BUILD_ARTIFACTSTAGINGDIRECTORY"); + public long BuildId => EnvironmentInfo.GetVariable("BUILD_BUILDID"); - [NoConvert] public string BuildNumber => EnvironmentInfo.GetVariable("BUILD_BUILDNUMBER"); + + [NoConvert] + public string BuildNumber => EnvironmentInfo.GetVariable("BUILD_BUILDNUMBER"); + public string BuildUri => EnvironmentInfo.GetVariable("BUILD_BUILDURI"); + public string BinariesDirectory => EnvironmentInfo.GetVariable("BUILD_BINARIESDIRECTORY"); + public string DefinitionName => EnvironmentInfo.GetVariable("BUILD_DEFINITIONNAME"); + public long DefinitionVersion => EnvironmentInfo.GetVariable("BUILD_DEFINITIONVERSION"); + public string QueuedBy => EnvironmentInfo.GetVariable("BUILD_QUEUEDBY"); + public Guid QueuedById => EnvironmentInfo.GetVariable("BUILD_QUEUEDBYID"); + public AzurePipelinesBuildReason BuildReason => EnvironmentInfo.GetVariable("BUILD_REASON"); + public bool RepositoryClean => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_CLEAN"); + public string RepositoryLocalPath => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_LOCALPATH"); + public string RepositoryName => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_NAME"); public AzurePipelinesRepositoryType RepositoryProvider => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_PROVIDER"); public string RepositoryTfvcWorkspace => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_TFVC_WORKSPACE"); + public string RepositoryUri => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_URI"); + public string RequestedFor => EnvironmentInfo.GetVariable("BUILD_REQUESTEDFOR"); + public string RequestedForEmail => EnvironmentInfo.GetVariable("BUILD_REQUESTEDFOREMAIL"); + public Guid RequestedForId => EnvironmentInfo.GetVariable("BUILD_REQUESTEDFORID"); + public string SourceBranch => EnvironmentInfo.GetVariable("BUILD_SOURCEBRANCH"); + public string SourceBranchName => EnvironmentInfo.GetVariable("BUILD_SOURCEBRANCHNAME"); + public string SourceDirectory => EnvironmentInfo.GetVariable("BUILD_SOURCESDIRECTORY"); + public string SourceVersion => EnvironmentInfo.GetVariable("BUILD_SOURCEVERSION"); + public string StagingDirectory => EnvironmentInfo.GetVariable("BUILD_STAGINGDIRECTORY"); + public bool RepositoryGitSubmoduleCheckout => EnvironmentInfo.GetVariable("BUILD_REPOSITORY_GIT_SUBMODULECHECKOUT"); + public string SourceTfvcShelveset => EnvironmentInfo.GetVariable("BUILD_SOURCETFVCSHELVESET"); + public string TestResultsDirectory => EnvironmentInfo.GetVariable("COMMON_TESTRESULTSDIRECTORY"); + public string AccessToken => EnvironmentInfo.GetVariable("SYSTEM_ACCESSTOKEN"); + public Guid CollectionId => EnvironmentInfo.GetVariable("SYSTEM_COLLECTIONID"); + public string DefaultWorkingDirectory => EnvironmentInfo.GetVariable("SYSTEM_DEFAULTWORKINGDIRECTORY"); + public long DefinitionId => EnvironmentInfo.GetVariable("SYSTEM_DEFINITIONID"); + public long? PullRequestId => EnvironmentInfo.GetVariable("SYSTEM_PULLREQUEST_PULLREQUESTID"); + public string PullRequestSourceBranch => EnvironmentInfo.GetVariable("SYSTEM_PULLREQUEST_SOURCEBRANCH"); + public string PullRequestTargetBranch => EnvironmentInfo.GetVariable("SYSTEM_PULLREQUEST_TARGETBRANCH"); + public string StageName => EnvironmentInfo.GetVariable("SYSTEM_STAGENAME"); + public string StageDisplayName => EnvironmentInfo.GetVariable("SYSTEM_STAGEDISPLAYNAME"); + public string TeamFoundationCollectionUri => EnvironmentInfo.GetVariable("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"); + public string TeamProject => EnvironmentInfo.GetVariable("SYSTEM_TEAMPROJECT"); + public Guid TeamProjectId => EnvironmentInfo.GetVariable("SYSTEM_TEAMPROJECTID"); + public string JobDisplayName => EnvironmentInfo.GetVariable("SYSTEM_JOBDISPLAYNAME"); + public Guid JobId => EnvironmentInfo.GetVariable("SYSTEM_JOBID"); + public Guid TaskInstanceId => EnvironmentInfo.GetVariable("SYSTEM_TASKINSTANCEID"); + public string PhaseName => EnvironmentInfo.GetVariable("SYSTEM_PHASENAME"); public void Group(string group) diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesAttribute.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesAttribute.cs index bce1d11b7..d5bd553b2 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesAttribute.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesAttribute.cs @@ -38,15 +38,25 @@ public AzurePipelinesAttribute( params AzurePipelinesImage[] images) { this.suffix = suffix?.Replace(oldChar: ' ', newChar: '_'); - this.images = new[] { image }.Concat(images).ToArray(); + this.images = new[] + { + image + }.Concat(images).ToArray(); } public override string IdPostfix => suffix; public override Type HostType => typeof(AzurePipelines); + public override AbsolutePath ConfigurationFile => ConfigurationDirectory / ConfigurationFileName; - public override IEnumerable GeneratedFiles => new[] { ConfigurationFile }; + + public override IEnumerable GeneratedFiles => new[] + { + ConfigurationFile + }; + protected virtual AbsolutePath ConfigurationDirectory => Build.RootDirectory; + private string ConfigurationFileName => suffix != null ? $"azure-pipelines.{suffix}.yml" : "azure-pipelines.yml"; public override IEnumerable RelevantTargetNames => InvokedTargets; @@ -86,10 +96,15 @@ public bool TriggerBatch } public string[] TriggerBranchesInclude { get; set; } = new string[0]; + public string[] TriggerBranchesExclude { get; set; } = new string[0]; + public string[] TriggerTagsInclude { get; set; } = new string[0]; + public string[] TriggerTagsExclude { get; set; } = new string[0]; + public string[] TriggerPathsInclude { get; set; } = new string[0]; + public string[] TriggerPathsExclude { get; set; } = new string[0]; public bool PullRequestsDisabled { get; set; } @@ -101,15 +116,30 @@ public bool? PullRequestsAutoCancel } public string[] PullRequestsBranchesInclude { get; set; } = new string[0]; + public string[] PullRequestsBranchesExclude { get; set; } = new string[0]; + public string[] PullRequestsPathsInclude { get; set; } = new string[0]; + public string[] PullRequestsPathsExclude { get; set; } = new string[0]; - public string[] CacheKeyFiles { get; set; } = { "**/global.json", "**/*.csproj", "**/Directory.Packages.props" }; - public string[] CachePaths { get; set; } = { AzurePipelinesCachePaths.Nuke, AzurePipelinesCachePaths.NuGet }; + public string[] CacheKeyFiles { get; set; } = + { + "**/global.json", + "**/*.csproj", + "**/Directory.Packages.props" + }; + + public string[] CachePaths { get; set; } = + { + AzurePipelinesCachePaths.Nuke, + AzurePipelinesCachePaths.NuGet + }; public string[] ImportVariableGroups { get; set; } = new string[0]; + public string[] ImportSecrets { get; set; } = new string[0]; + public bool EnableAccessToken { get; set; } public override CustomFileWriter CreateWriter(StreamWriter streamWriter) @@ -120,12 +150,12 @@ public override CustomFileWriter CreateWriter(StreamWriter streamWriter) public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) { return new AzurePipelinesConfiguration - { - VariableGroups = ImportVariableGroups, - VcsPushTrigger = GetVcsPushTrigger(), - VcsPullRequestTrigger = GetVcsPullRequestTrigger(), - Stages = images.Select(x => GetStage(x, relevantTargets)).ToArray() - }; + { + VariableGroups = ImportVariableGroups, + VcsPushTrigger = GetVcsPushTrigger(), + VcsPullRequestTrigger = GetVcsPullRequestTrigger(), + Stages = images.Select(x => GetStage(x, relevantTargets)).ToArray() + }; } protected AzurePipelinesVcsPushTrigger GetVcsPushTrigger() @@ -138,19 +168,21 @@ protected AzurePipelinesVcsPushTrigger GetVcsPushTrigger() TriggerTagsExclude.Length == 0 && TriggerPathsInclude.Length == 0 && TriggerPathsExclude.Length == 0) + { return null; + } return new AzurePipelinesVcsPushTrigger - { - Disabled = TriggerDisabled, - Batch = triggerBatch, - BranchesInclude = TriggerBranchesInclude, - BranchesExclude = TriggerBranchesExclude, - TagsInclude = TriggerTagsInclude, - TagsExclude = TriggerTagsExclude, - PathsInclude = TriggerPathsInclude, - PathsExclude = TriggerPathsExclude, - }; + { + Disabled = TriggerDisabled, + Batch = triggerBatch, + BranchesInclude = TriggerBranchesInclude, + BranchesExclude = TriggerBranchesExclude, + TagsInclude = TriggerTagsInclude, + TagsExclude = TriggerTagsExclude, + PathsInclude = TriggerPathsInclude, + PathsExclude = TriggerPathsExclude, + }; } protected AzurePipelinesVcsPushTrigger GetVcsPullRequestTrigger() @@ -161,19 +193,21 @@ protected AzurePipelinesVcsPushTrigger GetVcsPullRequestTrigger() PullRequestsBranchesExclude.Length == 0 && PullRequestsPathsInclude.Length == 0 && PullRequestsPathsExclude.Length == 0) + { return null; + } return new AzurePipelinesVcsPushTrigger - { - Disabled = PullRequestsDisabled, - AutoCancel = pullRequestsAutoCancel, - BranchesInclude = PullRequestsBranchesInclude, - BranchesExclude = PullRequestsBranchesExclude, - TagsInclude = new string[0], - TagsExclude = new string[0], - PathsInclude = PullRequestsPathsInclude, - PathsExclude = PullRequestsPathsExclude, - }; + { + Disabled = PullRequestsDisabled, + AutoCancel = pullRequestsAutoCancel, + BranchesInclude = PullRequestsBranchesInclude, + BranchesExclude = PullRequestsBranchesExclude, + TagsInclude = new string[0], + TagsExclude = new string[0], + PathsInclude = PullRequestsPathsInclude, + PathsExclude = PullRequestsPathsExclude, + }; } protected virtual AzurePipelinesStage GetStage( @@ -187,13 +221,13 @@ protected virtual AzurePipelinesStage GetStage( .Select(x => x.Job).ToArray(); return new AzurePipelinesStage - { - Name = image.GetValue().Replace("-", "_").Replace(".", "_"), - DisplayName = image.GetValue(), - Image = image, - Dependencies = new AzurePipelinesStage[0], - Jobs = jobs - }; + { + Name = image.GetValue().Replace("-", "_").Replace(".", "_"), + DisplayName = image.GetValue(), + Image = image, + Dependencies = new AzurePipelinesStage[0], + Jobs = jobs + }; } protected virtual AzurePipelinesJob GetJob( @@ -205,13 +239,13 @@ protected virtual AzurePipelinesJob GetJob( var totalPartitions = executableTarget.PartitionSize ?? 0; var dependencies = GetTargetDependencies(executableTarget).SelectMany(x => jobs[x]).ToArray(); return new AzurePipelinesJob - { - Name = executableTarget.Name, - DisplayName = executableTarget.Name, - Dependencies = dependencies, - Parallel = totalPartitions, - Steps = GetSteps(executableTarget, relevantTargets, image).ToArray(), - }; + { + Name = executableTarget.Name, + DisplayName = executableTarget.Name, + Dependencies = dependencies, + Parallel = totalPartitions, + Steps = GetSteps(executableTarget, relevantTargets, image).ToArray(), + }; } protected virtual IEnumerable GetSteps( @@ -222,12 +256,12 @@ protected virtual IEnumerable GetSteps( if (submodules.HasValue || largeFileStorage.HasValue || fetchDepth.HasValue || clean.HasValue) { yield return new AzurePipelineCheckoutStep - { - InclueSubmodules = submodules, - IncludeLargeFileStorage = largeFileStorage, - FetchDepth = fetchDepth, - Clean = clean - }; + { + InclueSubmodules = submodules, + IncludeLargeFileStorage = largeFileStorage, + FetchDepth = fetchDepth, + Clean = clean + }; } if (CacheKeyFiles.Any()) @@ -235,11 +269,11 @@ protected virtual IEnumerable GetSteps( foreach (var cachePath in CachePaths.NotNull()) { yield return new AzurePipelinesCacheStep - { - Image = image, - KeyFiles = CacheKeyFiles, - Path = cachePath - }; + { + Image = image, + KeyFiles = CacheKeyFiles, + Path = cachePath + }; } } @@ -249,7 +283,7 @@ string GetArtifactPath(AbsolutePath path) : path; var publishedArtifacts = executableTarget.ArtifactProducts - .Select(x => (AbsolutePath) x) + .Select(x => (AbsolutePath)x) .Select(x => x.DescendantsAndSelf(y => y.Parent).FirstOrDefault(y => !y.ToString().ContainsOrdinalIgnoreCase("*"))) .Distinct() .Select(GetArtifactPath).ToArray(); @@ -269,21 +303,21 @@ string GetArtifactPath(AbsolutePath path) var chainLinkTargets = GetInvokedTargets(executableTarget, relevantTargets).ToArray(); yield return new AzurePipelinesCmdStep - { - BuildCmdPath = BuildCmdPath, - PartitionSize = executableTarget.PartitionSize, - InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), - Imports = GetImports().ToDictionary(x => x.Key, x => x.Value) - }; + { + BuildCmdPath = BuildCmdPath, + PartitionSize = executableTarget.PartitionSize, + InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), + Imports = GetImports().ToDictionary(x => x.Key, x => x.Value) + }; foreach (var publishedArtifact in publishedArtifacts) { var artifactName = publishedArtifact.Split('/').Last(); yield return new AzurePipelinesPublishStep - { - ArtifactName = artifactName, - PathToPublish = publishedArtifact - }; + { + ArtifactName = artifactName, + PathToPublish = publishedArtifact + }; } } @@ -292,19 +326,25 @@ string GetArtifactPath(AbsolutePath path) static string GetSecretValue(string secret) => $"$({secret})"; if (EnableAccessToken) + { yield return ("SYSTEM_ACCESSTOKEN", GetSecretValue("System.AccessToken")); + } foreach (var secret in ImportSecrets) + { yield return (secret, GetSecretValue(secret)); + } } protected virtual string GetArtifact(string artifact) { if (Build.RootDirectory.Contains(artifact)) + { artifact = GetRelativePath(Build.RootDirectory, artifact); + } return HasPathRoot(artifact) ? artifact - : (UnixRelativePath) artifact; + : (UnixRelativePath)artifact; } } diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesBuildReason.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesBuildReason.cs index c94bd07e4..b88982051 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesBuildReason.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesBuildReason.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesBuildReason { diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCachePaths.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCachePaths.cs index e6908b071..d56ce8a57 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCachePaths.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCachePaths.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; // https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops public static class AzurePipelinesCachePaths diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCodeCoverageToolType.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCodeCoverageToolType.cs index bc9da2406..6cc706a66 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCodeCoverageToolType.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesCodeCoverageToolType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesCodeCoverageToolType { diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesImage.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesImage.cs index f03f9b282..44d251b49 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesImage.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesImage.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.CI.AzurePipelines; @@ -9,14 +7,33 @@ namespace Fallout.Common.CI.AzurePipelines; /// public enum AzurePipelinesImage { - [EnumValue("windows-2022")] Windows2022, - [EnumValue("windows-2019")] Windows2019, - [EnumValue("ubuntu-22.04")] Ubuntu2204, - [EnumValue("ubuntu-20.04")] Ubuntu2004, - [EnumValue("macOS-13")] MacOs13, - [EnumValue("macOS-12")] MacOs12, - [EnumValue("macOS-11")] MacOs11, - [EnumValue("windows-latest")] WindowsLatest, - [EnumValue("ubuntu-latest")] UbuntuLatest, - [EnumValue("macOS-latest")] MacOsLatest + [EnumValue("windows-2022")] + Windows2022, + + [EnumValue("windows-2019")] + Windows2019, + + [EnumValue("ubuntu-22.04")] + Ubuntu2204, + + [EnumValue("ubuntu-20.04")] + Ubuntu2004, + + [EnumValue("macOS-13")] + MacOs13, + + [EnumValue("macOS-12")] + MacOs12, + + [EnumValue("macOS-11")] + MacOs11, + + [EnumValue("windows-latest")] + WindowsLatest, + + [EnumValue("ubuntu-latest")] + UbuntuLatest, + + [EnumValue("macOS-latest")] + MacOsLatest } diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesIssueType.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesIssueType.cs index 750b376a8..672ef3318 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesIssueType.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesIssueType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesIssueType { diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesJobStatus.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesJobStatus.cs index 5ce6581d9..473db7f4a 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesJobStatus.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesJobStatus.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesJobStatus { diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesRepositoryType.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesRepositoryType.cs index 498dbada3..c0032d3b0 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesRepositoryType.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesRepositoryType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines; +namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesRepositoryType { diff --git a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesTestResultsType.cs b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesTestResultsType.cs index 5c206ee22..bdc01a3d7 100644 --- a/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesTestResultsType.cs +++ b/src/Fallout.Common/CI/AzurePipelines/AzurePipelinesTestResultsType.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.AzurePipelines; public enum AzurePipelinesTestResultsType diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelineCheckoutStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelineCheckoutStep.cs index 3b1633d9b..8ec50b955 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelineCheckoutStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelineCheckoutStep.cs @@ -6,8 +6,11 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelineCheckoutStep : AzurePipelinesStep { public bool? InclueSubmodules { get; set; } + public bool? IncludeLargeFileStorage { get; set; } + public int? FetchDepth { get; set; } + public bool? Clean { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCacheStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCacheStep.cs index f2eb1aa0d..d496e1f33 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCacheStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCacheStep.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; +using Fallout.Common.Tooling; using Fallout.Common.Utilities; -using Fallout.Common.Tooling; namespace Fallout.Common.CI.AzurePipelines.Configuration; @@ -9,7 +7,9 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesCacheStep : AzurePipelinesStep { public AzurePipelinesImage Image { get; set; } + public string[] KeyFiles { get; set; } + public string Path { get; set; } private string AdjustedPath => diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCmdStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCmdStep.cs index 5b6783439..740fac9e5 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCmdStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCmdStep.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -9,8 +7,11 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesCmdStep : AzurePipelinesStep { public string[] InvokedTargets { get; set; } + public string BuildCmdPath { get; set; } + public int? PartitionSize { get; set; } + public Dictionary Imports { get; set; } public override void Write(CustomFileWriter writer) @@ -21,7 +22,9 @@ public override void Write(CustomFileWriter writer) var arguments = $"{InvokedTargets.JoinSpace()} --skip"; if (PartitionSize != null) + { arguments += $" --partition $(System.JobPositionInPhase)/{PartitionSize}"; + } using (writer.WriteBlock("inputs:")) { diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCustomWriterExtensions.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCustomWriterExtensions.cs index 0d86dbd2d..0630cf3ec 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCustomWriterExtensions.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesCustomWriterExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.AzurePipelines.Configuration; diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesDownloadStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesDownloadStep.cs index 283d7b287..46ccb4940 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesDownloadStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesDownloadStep.cs @@ -1,12 +1,11 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesDownloadStep : AzurePipelinesStep { public string ArtifactName { get; set; } + public string DownloadPath { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesJob.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesJob.cs index 6af3caeaf..155091b1a 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesJob.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesJob.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; @@ -9,10 +8,15 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesJob : ConfigurationEntity { public string Name { get; set; } + public string DisplayName { get; set; } + public AzurePipelinesImage? Image { get; set; } + public AzurePipelinesJob[] Dependencies { get; set; } + public int Parallel { get; set; } + public AzurePipelinesStep[] Steps { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesPublishStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesPublishStep.cs index 834117f78..5629e5564 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesPublishStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesPublishStep.cs @@ -1,12 +1,11 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesPublishStep : AzurePipelinesStep { public string ArtifactName { get; set; } + public string PathToPublish { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStage.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStage.cs index 792366370..926c080d1 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStage.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStage.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; @@ -9,9 +8,13 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesStage : ConfigurationEntity { public string Name { get; set; } + public string DisplayName { get; set; } + public AzurePipelinesImage? Image { get; set; } + public AzurePipelinesStage[] Dependencies { get; set; } + public AzurePipelinesJob[] Jobs { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStep.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStep.cs index c25fb30ba..4c697b3cc 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStep.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesStep.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.AzurePipelines.Configuration; +namespace Fallout.Common.CI.AzurePipelines.Configuration; public abstract class AzurePipelinesStep : ConfigurationEntity { diff --git a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesVcsPushTrigger.cs b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesVcsPushTrigger.cs index a5bc905e2..7e11d823e 100644 --- a/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesVcsPushTrigger.cs +++ b/src/Fallout.Common/CI/AzurePipelines/Configuration/AzurePipelinesVcsPushTrigger.cs @@ -7,13 +7,21 @@ namespace Fallout.Common.CI.AzurePipelines.Configuration; public class AzurePipelinesVcsPushTrigger : ConfigurationEntity { public bool Disabled { get; set; } + public bool? Batch { get; set; } + public bool? AutoCancel { get; set; } + public string[] BranchesInclude { get; set; } + public string[] BranchesExclude { get; set; } + public string[] TagsInclude { get; set; } + public string[] TagsExclude { get; set; } + public string[] PathsInclude { get; set; } + public string[] PathsExclude { get; set; } public override void Write(CustomFileWriter writer) @@ -25,10 +33,14 @@ public override void Write(CustomFileWriter writer) } if (Batch.HasValue) + { writer.WriteLine($"batch: {Batch.ToString().ToLowerInvariant()}"); + } if (AutoCancel.HasValue) + { writer.WriteLine($"autoCancel: {AutoCancel.ToString().ToLowerInvariant()}"); + } if (BranchesInclude.Length > 0 || BranchesExclude.Length > 0) { diff --git a/src/Fallout.Common/CI/Bamboo/Bamboo.cs b/src/Fallout.Common/CI/Bamboo/Bamboo.cs index a963ae45c..1e3319e1c 100644 --- a/src/Fallout.Common/CI/Bamboo/Bamboo.cs +++ b/src/Fallout.Common/CI/Bamboo/Bamboo.cs @@ -19,25 +19,44 @@ internal Bamboo() } string IBuildServer.Branch => null; + string IBuildServer.Commit => null; public long AgentId => EnvironmentInfo.GetVariable("bamboo_agentId"); + public string AgentWorkingDirectory => EnvironmentInfo.GetVariable("bamboo_agentWorkingDirectory"); + public string AgentHome => EnvironmentInfo.GetVariable("BAMBOO_AGENT_HOME"); + public string BuildKey => EnvironmentInfo.GetVariable("bamboo_buildKey"); + public long BuildNumber => EnvironmentInfo.GetVariable("bamboo_buildNumber"); + public string BuildPlanName => EnvironmentInfo.GetVariable("bamboo_buildPlanName"); + public string BuildResultsKey => EnvironmentInfo.GetVariable("bamboo_buildResultKey"); + public string BuildResultsUrl => EnvironmentInfo.GetVariable("bamboo_buildResultsUrl"); + public DateTime BuildTimeStamp => DateTime.Parse(EnvironmentInfo.GetVariable("bamboo_buildTimeStamp")); + public string BuildWorkingDirectory => EnvironmentInfo.GetVariable("bamboo_build_working_directory"); + public bool BuildFailed => EnvironmentInfo.GetVariable("bamboo_buildFailed"); + public string PlanKey => EnvironmentInfo.GetVariable("bamboo_planKey"); + public string ShortPlanKey => EnvironmentInfo.GetVariable("bamboo_shortPlanKey"); + public string PlanName => EnvironmentInfo.GetVariable("bamboo_planName"); + public string ShortPlanName => EnvironmentInfo.GetVariable("bamboo_shortPlanName"); + public string PlanStorageTag => EnvironmentInfo.GetVariable("bamboo_plan_storageTag"); + public string PlanResultsUrl => EnvironmentInfo.GetVariable("bamboo_resultsUrl"); + public string ShortJobKey => EnvironmentInfo.GetVariable("bamboo_shortJobKey"); + public string ShortJobName => EnvironmentInfo.GetVariable("bamboo_shortJobName"); } diff --git a/src/Fallout.Common/CI/Bitbucket/Bitbucket.cs b/src/Fallout.Common/CI/Bitbucket/Bitbucket.cs index cc79eabf9..24d1e36d1 100644 --- a/src/Fallout.Common/CI/Bitbucket/Bitbucket.cs +++ b/src/Fallout.Common/CI/Bitbucket/Bitbucket.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Fallout.Common.CI.Bitbucket; @@ -20,6 +18,7 @@ internal Bitbucket() } string IBuildServer.Branch => Branch; + string IBuildServer.Commit => Commit; /// diff --git a/src/Fallout.Common/CI/Bitrise/Bitrise.cs b/src/Fallout.Common/CI/Bitrise/Bitrise.cs index 3a6920d61..1179c6c32 100644 --- a/src/Fallout.Common/CI/Bitrise/Bitrise.cs +++ b/src/Fallout.Common/CI/Bitrise/Bitrise.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Fallout.Common.CI.Bitrise; @@ -26,22 +25,41 @@ internal Bitrise() } string IBuildServer.Branch => GitBranch; + string IBuildServer.Commit => GitCommit; public string BuildUrl => EnvironmentInfo.GetVariable("BITRISE_BUILD_URL"); + public long BuildNumber => EnvironmentInfo.GetVariable("BITRISE_BUILD_NUMBER"); + public string AppTitle => EnvironmentInfo.GetVariable("BITRISE_APP_TITLE"); + public string AppUrl => EnvironmentInfo.GetVariable("BITRISE_APP_URL"); - [NoConvert] public string AppSlug => EnvironmentInfo.GetVariable("BITRISE_APP_SLUG"); - [NoConvert] public string BuildSlug => EnvironmentInfo.GetVariable("BITRISE_BUILD_SLUG"); - public DateTime BuildTriggerTimestamp => ConvertUnixTimestamp(EnvironmentInfo.GetVariable("BITRISE_BUILD_TRIGGER_TIMESTAMP")); + + [NoConvert] + public string AppSlug => EnvironmentInfo.GetVariable("BITRISE_APP_SLUG"); + + [NoConvert] + public string BuildSlug => EnvironmentInfo.GetVariable("BITRISE_BUILD_SLUG"); + + public DateTime BuildTriggerTimestamp => + ConvertUnixTimestamp(EnvironmentInfo.GetVariable("BITRISE_BUILD_TRIGGER_TIMESTAMP")); + public string RepositoryUrl => EnvironmentInfo.GetVariable("GIT_REPOSITORY_URL"); + public string GitBranch => EnvironmentInfo.GetVariable("BITRISE_GIT_BRANCH"); + public string GitTag => EnvironmentInfo.GetVariable("BITRISE_GIT_TAG"); + public string GitCommit => EnvironmentInfo.GetVariable("BITRISE_GIT_COMMIT"); + public string GitMessage => EnvironmentInfo.GetVariable("BITRISE_GIT_MESSAGE"); + public long? PullRequest => EnvironmentInfo.GetVariable("BITRISE_PULL_REQUEST"); + public string ProvisionUrl => EnvironmentInfo.GetVariable("BITRISE_PROVISION_URL"); + public string CertificateUrl => EnvironmentInfo.GetVariable("BITRISE_CERTIFICATE_URL"); + public string CertificatePassphrase => EnvironmentInfo.GetVariable("BITRISE_CERTIFICATE_PASSPHRASE"); } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsActionReference.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsActionReference.cs index b8dd7f26a..502980401 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsActionReference.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsActionReference.cs @@ -31,7 +31,9 @@ internal static class GitHubActionsActionReference public static string Resolve(string defaultAction, string value, string origin) { if (value.IsNullOrWhiteSpace()) + { return defaultAction; + } Assert.True(value.All(x => !char.IsControl(x)), $"{origin}: action reference '{value}' must be a single line without control characters"); @@ -66,7 +68,9 @@ private static string ResolveMarketplaceReference(string defaultAction, string r // 'releases/v1' is a branch rather than an owner/repo. var isExplicitBareRef = reference.StartsWith("@", StringComparison.Ordinal); if (isExplicitBareRef) + { reference = reference.Substring(startIndex: 1); + } var separatorIndex = reference.IndexOf('@'); if (!isExplicitBareRef && separatorIndex >= 0) @@ -76,6 +80,7 @@ private static string ResolveMarketplaceReference(string defaultAction, string r Assert.True(name.Contains('/'), $"{origin}: action reference '{reference}' must name the action as 'owner/repo@ref'"); + Assert.True(!gitReference.IsNullOrWhiteSpace() && !gitReference.Contains('@'), $"{origin}: action reference '{reference}' must name exactly one version or commit after the '@'"); @@ -84,6 +89,7 @@ private static string ResolveMarketplaceReference(string defaultAction, string r Assert.True(!reference.IsNullOrWhiteSpace() && !reference.Contains('@'), $"{origin}: action reference '{reference}' must name exactly one version or commit"); + Assert.True(isExplicitBareRef || !reference.Contains('/'), $"{origin}: action reference '{reference}' is ambiguous — write 'owner/repo@ref' for a complete " + $"reference, or '@{reference}' to use it as a ref on '{GetActionName(defaultAction)}'"); @@ -109,7 +115,9 @@ private static string GetActionName(string defaultAction) private static (string Reference, string Comment) SplitComment(string value) { if (value.StartsWith("#", StringComparison.Ordinal)) + { return (string.Empty, value); + } var commentIndex = value.IndexOf(" #", StringComparison.Ordinal); return commentIndex < 0 diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsArtifactStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsArtifactStep.cs index a42b50279..bfc0665c1 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsArtifactStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsArtifactStep.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.GitHubActions.Configuration; @@ -19,7 +17,9 @@ public string Uses } = GitHubActionsDefaults.UploadArtifactAction; public string Name { get; set; } + public string Path { get; set; } + public string Condition { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCacheStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCacheStep.cs index 0b43aa770..7f3775326 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCacheStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCacheStep.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -21,7 +20,9 @@ public string Uses } = GitHubActionsDefaults.CacheAction; public string[] IncludePatterns { get; set; } + public string[] ExcludePatterns { get; set; } + public string[] KeyFiles { get; set; } public override void Write(CustomFileWriter writer) @@ -36,7 +37,8 @@ public override void Write(CustomFileWriter writer) writer.WriteLine("path: |"); IncludePatterns.ForEach(x => writer.WriteLine($" {x}")); ExcludePatterns.ForEach(x => writer.WriteLine($" !{x}")); - writer.WriteLine($"key: ${{{{ runner.os }}}}-${{{{ hashFiles({KeyFiles.Select(x => x.SingleQuoteYaml()).JoinCommaSpace()}) }}}}"); + writer.WriteLine( + $"key: ${{{{ runner.os }}}}-${{{{ hashFiles({KeyFiles.Select(x => x.SingleQuoteYaml()).JoinCommaSpace()}) }}}}"); } } } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs index 1ddfc22ea..7bff7eb93 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCheckoutStep.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.GitHubActions.Configuration; @@ -19,9 +17,13 @@ public string Uses } = GitHubActionsDefaults.CheckoutAction; public GitHubActionsSubmodules? Submodules { get; set; } + public bool? Lfs { get; set; } + public uint? FetchDepth { get; set; } + public bool? Progress { get; set; } + public string Filter { get; set; } /// @@ -51,15 +53,30 @@ public override void Write(CustomFileWriter writer) using (writer.Indent()) { if (Submodules.HasValue) + { writer.WriteLine($"submodules: {Submodules.ToString().ToLowerInvariant()}"); - if(Lfs.HasValue) + } + + if (Lfs.HasValue) + { writer.WriteLine($"lfs: {Lfs.ToString().ToLowerInvariant()}"); + } + if (FetchDepth.HasValue) + { writer.WriteLine($"fetch-depth: {FetchDepth}"); + } + if (Progress.HasValue) + { writer.WriteLine($"progress: {Progress.ToString().ToLowerInvariant()}"); + } + if (!Filter.IsNullOrWhiteSpace()) + { writer.WriteLine($"filter: {Filter}"); + } + if (!Ref.IsNullOrWhiteSpace()) { // Pin checkout to the source repo of the PR head (or the current repo on @@ -73,7 +90,9 @@ public override void Write(CustomFileWriter writer) } foreach (var line in CheckoutWith) + { writer.WriteLine(line); + } } } } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsConfiguration.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsConfiguration.cs index 4af60abbd..ba4694ba6 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsConfiguration.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsConfiguration.cs @@ -10,12 +10,19 @@ public class GitHubActionsConfiguration : ConfigurationEntity public string Name { get; set; } public GitHubActionsTrigger[] ShortTriggers { get; set; } + public GitHubActionsDetailedTrigger[] DetailedTriggers { get; set; } + public string[] Env { get; set; } = new string[0]; + public (GitHubActionsPermissions Type, string Permission)[] Permissions { get; set; } + public string ConcurrencyGroup { get; set; } + public bool ConcurrencyCancelInProgress { get; set; } + public string DefaultShell { get; set; } + public GitHubActionsJob[] Jobs { get; set; } public override void Write(CustomFileWriter writer) @@ -24,7 +31,9 @@ public override void Write(CustomFileWriter writer) writer.WriteLine(); if (ShortTriggers.Length > 0) + { writer.WriteLine($"on: [{ShortTriggers.Select(x => x.GetValue().ToLowerInvariant()).JoinCommaSpace()}]"); + } else { writer.WriteLine("on:"); @@ -65,7 +74,8 @@ public override void Write(CustomFileWriter writer) { // create a default value that only cancels in-progress runs of the same workflow // we don't fall back to github.ref which would disable multiple runs in main/master which is usually what is wanted - group = "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.run_id }}"; + group = + "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.run_id }}"; } writer.WriteLine($"group: {group}"); diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCustomStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCustomStep.cs index e82c1d442..dc97f7ae7 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCustomStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsCustomStep.cs @@ -16,14 +16,23 @@ namespace Fallout.Common.CI.GitHubActions.Configuration; public class GitHubActionsCustomStep : GitHubActionsStep { public string Name { get; set; } + public string Uses { get; set; } - public Dictionary With { get; set; } = new Dictionary(); - public Dictionary Env { get; set; } = new Dictionary(); + + public Dictionary With { get; set; } = new(); + + public Dictionary Env { get; set; } = new(); + public string If { get; set; } + public string Shell { get; set; } + public string[] Run { get; set; } = new string[0]; + public bool? ContinueOnError { get; set; } + public int? TimeoutMinutes { get; set; } + public string Id { get; set; } public override void Write(CustomFileWriter writer) @@ -32,15 +41,29 @@ public override void Write(CustomFileWriter writer) var written = false; if (!Name.IsNullOrWhiteSpace()) - Scalar("name", Name.SingleQuoteYaml()); // single-quoted so a ':' or apostrophe in the name stays valid YAML + { + Scalar("name", Name.SingleQuoteYaml()); // single-quoted so a ':' or apostrophe in the name stays valid YAML + } + if (!Id.IsNullOrWhiteSpace()) + { Scalar("id", Id); + } + if (!Uses.IsNullOrWhiteSpace()) + { Scalar("uses", Uses); + } + if ((With?.Count ?? 0) > 0) + { MapBlock("with", With); + } + if ((Env?.Count ?? 0) > 0) + { MapBlock("env", Env); + } var runCount = Run?.Length ?? 0; if (runCount == 1) @@ -52,17 +75,30 @@ public override void Write(CustomFileWriter writer) writer.WriteLine((written ? " " : "- ") + "run: |"); written = true; using (writer.Indent()) + { Run.ForEach(x => writer.WriteLine($" {x}")); + } } if (!Shell.IsNullOrWhiteSpace()) + { Scalar("shell", Shell); + } + if (!If.IsNullOrWhiteSpace()) + { Scalar("if", If); + } + if (ContinueOnError.HasValue) + { Scalar("continue-on-error", ContinueOnError.Value ? "true" : "false"); + } + if (TimeoutMinutes.HasValue) + { Scalar("timeout-minutes", TimeoutMinutes.Value.ToString(CultureInfo.InvariantCulture)); + } return; @@ -78,7 +114,9 @@ void MapBlock(string key, Dictionary map) written = true; // Ordinal sort so multi-entry blocks render deterministically (Dictionary order isn't guaranteed). using (writer.Indent()) + { map.OrderBy(x => x.Key, StringComparer.Ordinal).ForEach(x => writer.WriteLine($" {x.Key}: {x.Value}")); + } } } } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsDetailedTrigger.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsDetailedTrigger.cs index a79de83ca..033e4a125 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsDetailedTrigger.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsDetailedTrigger.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.GitHubActions.Configuration; public abstract class GitHubActionsDetailedTrigger : ConfigurationEntity diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs index 40969f717..d115a6946 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsJob.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -9,13 +7,21 @@ namespace Fallout.Common.CI.GitHubActions.Configuration; public class GitHubActionsJob : ConfigurationEntity { public string Name { get; set; } + public GitHubActionsImage Image { get; set; } + public string[] RunsOnLabels { get; set; } = new string[0]; + public int TimeoutMinutes { get; set; } + public string ConcurrencyGroup { get; set; } + public string EnvironmentName { get; set; } + public string EnvironmentUrl { get; set; } + public bool ConcurrencyCancelInProgress { get; set; } + public GitHubActionsStep[] Steps { get; set; } public override void Write(CustomFileWriter writer) @@ -49,7 +55,8 @@ public override void Write(CustomFileWriter writer) { // create a default value that only cancels in-progress runs of the same workflow // we don't fall back to github.ref which would disable multiple runs in main/master which is usually what is wanted - group = "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.run_id }}"; + group = + "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.run_id }}"; } writer.WriteLine($"group: {group}"); diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs index 0167df4be..28a4b48da 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs @@ -20,6 +20,7 @@ public string SetupDotNetAction } = GitHubActionsDefaults.SetupDotNetAction; public string[] InvokedTargets { get; set; } + public Dictionary Imports { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsScheduledTrigger.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsScheduledTrigger.cs index 2814cdd84..72f91c52d 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsScheduledTrigger.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsScheduledTrigger.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.GitHubActions.Configuration; diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStep.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStep.cs index 4971b527a..c462e4ab9 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStep.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStep.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.GitHubActions.Configuration; public abstract class GitHubActionsStep : ConfigurationEntity diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStepPipeline.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStepPipeline.cs index 72f41b869..3f189349b 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStepPipeline.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsStepPipeline.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Linq; -using Fallout.Common.Utilities; namespace Fallout.Common.CI.GitHubActions.Configuration; @@ -11,10 +10,10 @@ namespace Fallout.Common.CI.GitHubActions.Configuration; /// public class GitHubActionsStepPipeline { - private readonly Dictionary> inserts = - new Dictionary>(); + private readonly Dictionary> inserts = new(); - internal GitHubActionsStepPipeline(string workflowName, GitHubActionsImage image, IReadOnlyList builtInSteps) + internal GitHubActionsStepPipeline(string workflowName, GitHubActionsImage image, + IReadOnlyList builtInSteps) { WorkflowName = workflowName; Image = image; @@ -33,22 +32,27 @@ internal GitHubActionsStepPipeline(string workflowName, GitHubActionsImage image /// Insert one custom step at . Multiple inserts at one position render in call order. public void Insert(GitHubActionsStepPosition position, GitHubActionsCustomStep step) { - Assert.NotNull(step); + step.NotNull(); if (!inserts.TryGetValue(position, out var list)) + { inserts[position] = list = new List(); + } + list.Add(step); } /// Insert several custom steps at , in enumeration order. public void Insert(GitHubActionsStepPosition position, IEnumerable steps) { - Assert.NotNull(steps); + steps.NotNull(); foreach (var step in steps) + { Insert(position, step); + } } internal IReadOnlyList GetInserts(GitHubActionsStepPosition position) - => inserts.TryGetValue(position, out var list) ? list : (IReadOnlyList)new GitHubActionsCustomStep[0]; + => inserts.TryGetValue(position, out var list) ? list : new GitHubActionsCustomStep[0]; internal IEnumerable AllInserts => inserts.Values.SelectMany(x => x); } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsVcsTrigger.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsVcsTrigger.cs index 50b0ce47f..ba86eb1ca 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsVcsTrigger.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsVcsTrigger.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Utilities; @@ -9,11 +8,17 @@ namespace Fallout.Common.CI.GitHubActions.Configuration; public class GitHubActionsVcsTrigger : GitHubActionsDetailedTrigger { public GitHubActionsTrigger Kind { get; set; } + public string[] Branches { get; set; } + public string[] BranchesIgnore { get; set; } + public string[] Tags { get; set; } + public string[] TagsIgnore { get; set; } + public string[] IncludePaths { get; set; } + public string[] ExcludePaths { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchInput.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchInput.cs index 573351f6d..8f7f1ed8b 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchInput.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchInput.cs @@ -3,9 +3,14 @@ namespace Fallout.Common.CI.GitHubActions.Configuration; public class GitHubActionsWorkflowDispatchInput { public string Name { get; set; } + public GitHubActionsInputType Type { get; set; } + public bool Required { get; set; } + public string Default { get; set; } + public string[] Options { get; set; } = new string[0]; + public string Description { get; set; } } diff --git a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchTrigger.cs b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchTrigger.cs index 08d3e9b06..a3260fe5e 100644 --- a/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchTrigger.cs +++ b/src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsWorkflowDispatchTrigger.cs @@ -41,14 +41,22 @@ void WriteInput(GitHubActionsWorkflowDispatchInput input) writer.WriteLine($"description: {description.DoubleQuote()}"); writer.WriteLine($"required: {input.Required.ToString().ToLowerInvariant()}"); if (input.Type != GitHubActionsInputType.String) + { writer.WriteLine($"type: {input.Type.GetValue()}"); + } + if (input.Default != null) + { writer.WriteLine($"default: {input.Default}"); + } + if (input.Type == GitHubActionsInputType.Choice) { writer.WriteLine("options:"); using (writer.Indent()) + { input.Options.ForEach(x => writer.WriteLine($"- {x.DoubleQuote()}")); + } } } } @@ -60,12 +68,27 @@ private IEnumerable GetInputs() { #pragma warning disable FALLOUTOBS001 // deliberate bridge for the obsolete legacy arrays foreach (var input in OptionalInputs) - yield return new GitHubActionsWorkflowDispatchInput { Name = input, Required = false }; + { + yield return new GitHubActionsWorkflowDispatchInput + { + Name = input, + Required = false + }; + } + foreach (var input in RequiredInputs) - yield return new GitHubActionsWorkflowDispatchInput { Name = input, Required = true }; + { + yield return new GitHubActionsWorkflowDispatchInput + { + Name = input, + Required = true + }; + } #pragma warning restore FALLOUTOBS001 foreach (var input in Inputs) + { yield return input; + } } } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs index 08efa273a..2ae9d81dc 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs @@ -13,7 +13,10 @@ public async Task CreateComment(int issue, string text) { await httpClient.Value .CreateRequest(HttpMethod.Post, $"repos/{Repository}/issues/{issue}/comments") - .WithJsonContent(new { body = text }) + .WithJsonContent(new + { + body = text + }) .GetResponseAsync(); } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Theming.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Theming.cs index 2dcc184f8..2168fc607 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Theming.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Theming.cs @@ -28,7 +28,9 @@ protected internal override void ReportError(string text, string details = null) protected internal override bool FilterMessage(string message) { if (!message.StartsWith("::") && !message.StartsWith("##[")) + { return false; + } Console.WriteLine(message); return true; diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs index 0eb132a39..34338ad4a 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs @@ -34,6 +34,7 @@ internal GitHubActions() var content = File.ReadAllText(EventPath); return JsonNode.Parse(content)?.AsObject() ?? new JsonObject(); }); + httpClient = Lazy.Create(() => { var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{Token.NotNull()}")); @@ -44,10 +45,12 @@ internal GitHubActions() client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Auth); return client; }); + jobId = Lazy.Create(GetJobId); } string IBuildServer.Branch => Ref; + string IBuildServer.Commit => Sha; ///The path to the GitHub home directory used to store user data. For example, /github/home. @@ -96,19 +99,27 @@ internal GitHubActions() public string RefType => EnvironmentInfo.GetVariable("GITHUB_REF_TYPE"); public long RunAttempt => EnvironmentInfo.GetVariable("GITHUB_RUN_ATTEMPT"); + public long RunNumber => EnvironmentInfo.GetVariable("GITHUB_RUN_NUMBER"); + public long RunId => EnvironmentInfo.GetVariable("GITHUB_RUN_ID"); + public string ServerUrl => EnvironmentInfo.GetVariable("GITHUB_SERVER_URL"); + public string Job => EnvironmentInfo.GetVariable("GITHUB_JOB"); // https://github.com/actions/toolkit/tree/master/packages/core/src public string Token => EnvironmentInfo.GetVariable("GITHUB_TOKEN"); + public long JobId => jobId.Value; public JsonObject GitHubEvent => eventContext.Value; + public bool IsPullRequest => EventName == "pull_request"; + public int? PullRequestNumber => GitHubEvent["number"]?.GetValue(); + public string PullRequestAction => GitHubEvent["action"]?.GetValue(); public AbsolutePath StepSummaryFile => EnvironmentInfo.GetVariable("GITHUB_STEP_SUMMARY"); diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs index e3c355c34..f9ce74091 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsAttribute.cs @@ -34,36 +34,59 @@ public GitHubActionsAttribute( params GitHubActionsImage[] images) { this.name = NormalizeWorkflowName(name); - this.images = new[] { image }.Concat(images).ToArray(); + this.images = new[] + { + image + }.Concat(images).ToArray(); } public override string IdPostfix => name; + public override Type HostType => typeof(GitHubActions); + public override AbsolutePath ConfigurationFile => Build.RootDirectory / ".github" / "workflows" / $"{name}.yml"; - public override IEnumerable GeneratedFiles => new[] { ConfigurationFile }; + + public override IEnumerable GeneratedFiles => new[] + { + ConfigurationFile + }; public override IEnumerable RelevantTargetNames => InvokedTargets; + public override IEnumerable IrrelevantTargetNames => new string[0]; public GitHubActionsTrigger[] On { get; set; } = new GitHubActionsTrigger[0]; + public string[] OnPushBranches { get; set; } = new string[0]; + public string[] OnPushBranchesIgnore { get; set; } = new string[0]; + public string[] OnPushTags { get; set; } = new string[0]; + public string[] OnPushTagsIgnore { get; set; } = new string[0]; + public string[] OnPushIncludePaths { get; set; } = new string[0]; + public string[] OnPushExcludePaths { get; set; } = new string[0]; + public string[] OnPullRequestBranches { get; set; } = new string[0]; + public string[] OnPullRequestTags { get; set; } = new string[0]; + public string[] OnPullRequestIncludePaths { get; set; } = new string[0]; + public string[] OnPullRequestExcludePaths { get; set; } = new string[0]; + [Obsolete($"Use [{nameof(GitHubActionsInputAttribute)}] instead. Removed in 2027.x.x.", DiagnosticId = "FALLOUTOBS001", UrlFormat = "https://github.com/Fallout-build/Fallout/blob/main/docs/obsolete_apis.md")] public string[] OnWorkflowDispatchOptionalInputs { get; set; } = new string[0]; + [Obsolete($"Use [{nameof(GitHubActionsInputAttribute)}] instead. Removed in 2027.x.x.", DiagnosticId = "FALLOUTOBS001", UrlFormat = "https://github.com/Fallout-build/Fallout/blob/main/docs/obsolete_apis.md")] public string[] OnWorkflowDispatchRequiredInputs { get; set; } = new string[0]; + public string OnCronSchedule { get; set; } /// @@ -77,26 +100,44 @@ public GitHubActionsAttribute( public string[] Env { get; set; } = new string[0]; public string[] ImportSecrets { get; set; } = new string[0]; + public bool EnableGitHubToken { get; set; } + public GitHubActionsPermissions[] WritePermissions { get; set; } = new GitHubActionsPermissions[0]; + public GitHubActionsPermissions[] ReadPermissions { get; set; } = new GitHubActionsPermissions[0]; - public string[] CacheIncludePatterns { get; set; } = { ".fallout/temp", "~/.nuget/packages" }; + public string[] CacheIncludePatterns { get; set; } = + { + ".fallout/temp", + "~/.nuget/packages" + }; + public string[] CacheExcludePatterns { get; set; } = new string[0]; - public string[] CacheKeyFiles { get; set; } = { "**/global.json", "**/*.csproj", "**/Directory.Packages.props" }; + + public string[] CacheKeyFiles { get; set; } = + { + "**/global.json", + "**/*.csproj", + "**/Directory.Packages.props" + }; public bool PublishArtifacts { get; set; } = true; + public string PublishCondition { get; set; } public int TimeoutMinutes { get; set; } public string EnvironmentName { get; set; } + public string EnvironmentUrl { get; set; } public string ConcurrencyGroup { get; set; } + public bool ConcurrencyCancelInProgress { get; set; } public string JobConcurrencyGroup { get; set; } + public bool JobConcurrencyCancelInProgress { get; set; } /// @@ -230,8 +271,10 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection 0, $"'{nameof(Env)}' entry '{variable}' must be in 'KEY: value' form with a non-empty key"); + Assert.True(!variable.Substring(startIndex: 0, separatorIndex).Any(char.IsWhiteSpace), $"'{nameof(Env)}' entry '{variable}' has whitespace in its key; expected 'KEY: value'"); + Assert.True(separatorIndex == variable.Length - 1 || char.IsWhiteSpace(variable[separatorIndex + 1]), $"'{nameof(Env)}' entry '{variable}' must have a space after the key's colon; expected 'KEY: value'"); } @@ -240,25 +283,28 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection (x, "write")) - .Concat(ReadPermissions.Select(x => (x, "read"))).ToArray(), - ConcurrencyGroup = ConcurrencyGroup, - ConcurrencyCancelInProgress = ConcurrencyCancelInProgress, - DefaultShell = DefaultShell, - Jobs = images.Select(x => GetJobs(x, relevantTargets)).ToArray() - }; + { + Name = name, + ShortTriggers = On, + DetailedTriggers = GetTriggers().ToArray(), + Env = Env, + Permissions = WritePermissions.Select(x => (x, "write")) + .Concat(ReadPermissions.Select(x => (x, "read"))).ToArray(), + ConcurrencyGroup = ConcurrencyGroup, + ConcurrencyCancelInProgress = ConcurrencyCancelInProgress, + DefaultShell = DefaultShell, + Jobs = images.Select(x => GetJobs(x, relevantTargets)).ToArray() + }; Assert.True(configuration.ShortTriggers.Length == 0 || configuration.DetailedTriggers.Length == 0, $"Workflows can only define either shorthand '{nameof(On)}' or '{nameof(On)}*' triggers"); + Assert.True(configuration.ShortTriggers.Length > 0 || configuration.DetailedTriggers.Length > 0, $"Workflows must define either shorthand '{nameof(On)}' or '{nameof(On)}*' triggers"); + Assert.True(RunsOnLabels.Length == 0 || images.Length == 1, $"Cannot use '{nameof(RunsOnLabels)}' with multiple images; labels resolve a single job's runner"); + Assert.True(RunsOnLabels.All(x => !x.IsNullOrWhiteSpace()), $"'{nameof(RunsOnLabels)}' entries must not be null, empty, or whitespace"); @@ -268,49 +314,49 @@ public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) { return new GitHubActionsJob - { - Name = image.GetValue().Replace(".", "_"), - RunsOnLabels = RunsOnLabels, - EnvironmentName = EnvironmentName, - EnvironmentUrl = EnvironmentUrl, - Steps = GetSteps(relevantTargets, image), - Image = image, - TimeoutMinutes = TimeoutMinutes, - ConcurrencyGroup = JobConcurrencyGroup, - ConcurrencyCancelInProgress = JobConcurrencyCancelInProgress - }; + { + Name = image.GetValue().Replace(".", "_"), + RunsOnLabels = RunsOnLabels, + EnvironmentName = EnvironmentName, + EnvironmentUrl = EnvironmentUrl, + Steps = GetSteps(relevantTargets, image), + Image = image, + TimeoutMinutes = TimeoutMinutes, + ConcurrencyGroup = JobConcurrencyGroup, + ConcurrencyCancelInProgress = JobConcurrencyCancelInProgress + }; } private GitHubActionsStep[] GetSteps(IReadOnlyCollection relevantTargets, GitHubActionsImage image) { var checkout = new GitHubActionsCheckoutStep - { - Uses = CheckoutAction, - Submodules = submodules, - Lfs = lfs, - FetchDepth = fetchDepth, - Progress = progress, - Filter = filter, - Ref = reference, - CheckoutWith = CheckoutWith - }; + { + Uses = CheckoutAction, + Submodules = submodules, + Lfs = lfs, + FetchDepth = fetchDepth, + Progress = progress, + Filter = filter, + Ref = reference, + CheckoutWith = CheckoutWith + }; var cache = CacheKeyFiles.Any() ? new GitHubActionsCacheStep - { - Uses = CacheAction, - IncludePatterns = CacheIncludePatterns, - ExcludePatterns = CacheExcludePatterns, - KeyFiles = CacheKeyFiles - } + { + Uses = CacheAction, + IncludePatterns = CacheIncludePatterns, + ExcludePatterns = CacheExcludePatterns, + KeyFiles = CacheKeyFiles + } : null; var run = new GitHubActionsRunStep - { - SetupDotNetAction = SetupDotNetAction, - InvokedTargets = InvokedTargets, - Imports = GetImports().ToDictionary(x => x.Key, x => x.Value) - }; + { + SetupDotNetAction = SetupDotNetAction, + InvokedTargets = InvokedTargets, + Imports = GetImports().ToDictionary(x => x.Key, x => x.Value) + }; var artifacts = new List(); if (PublishArtifacts) @@ -319,34 +365,54 @@ private GitHubActionsStep[] GetSteps(IReadOnlyCollection relev .SelectMany(x => x.ArtifactProducts) .Select(x => (AbsolutePath)x) // TODO: https://github.com/actions/upload-artifact/issues/11 - .Select(x => x.DescendantsAndSelf(y => y.Parent).FirstOrDefault(y => !y.ToString().ContainsOrdinalIgnoreCase("*"))) + .Select(x => x.DescendantsAndSelf(y => y.Parent) + .FirstOrDefault(y => !y.ToString().ContainsOrdinalIgnoreCase("*"))) .Distinct().ToList(); foreach (var artifact in artifactPaths) + { artifacts.Add(new GitHubActionsArtifactStep - { - Uses = UploadArtifactAction, - Name = artifact.ToString().TrimStart(artifact.Parent.ToString()).TrimStart('/', '\\'), - Path = Build.RootDirectory.GetUnixRelativePathTo(artifact), - Condition = PublishCondition - }); + { + Uses = UploadArtifactAction, + Name = artifact.ToString().TrimStart(artifact.Parent.ToString()).TrimStart('/', '\\'), + Path = Build.RootDirectory.GetUnixRelativePathTo(artifact), + Condition = PublishCondition + }); + } } - var builtInSteps = new List { checkout }; + var builtInSteps = new List + { + checkout + }; + if (cache != null) + { builtInSteps.Add(cache); + } + builtInSteps.Add(run); builtInSteps.AddRange(artifacts); var pipeline = new GitHubActionsStepPipeline(name, image, builtInSteps.AsReadOnly()); if (Build is IConfigureGitHubActions configure) + { configure.ConfigureSteps(pipeline); + } + ValidateCustomSteps(pipeline); - var steps = new List { checkout }; + var steps = new List + { + checkout + }; + steps.AddRange(pipeline.GetInserts(GitHubActionsStepPosition.PostCheckout)); if (cache != null) + { steps.Add(cache); + } + steps.AddRange(pipeline.GetInserts(GitHubActionsStepPosition.PreRun)); steps.Add(run); steps.AddRange(pipeline.GetInserts(GitHubActionsStepPosition.PostRun)); @@ -383,8 +449,10 @@ private void ValidateCustomSteps(GitHubActionsStepPipeline pipeline) Assert.True(hasUses ^ hasRun, $"Custom step '{id}' in workflow '{name}' must set exactly one of '{nameof(GitHubActionsCustomStep.Uses)}' or '{nameof(GitHubActionsCustomStep.Run)}'"); + Assert.True((step.With?.Count ?? 0) == 0 || hasUses, $"Custom step '{id}' in workflow '{name}' sets '{nameof(GitHubActionsCustomStep.With)}' but no '{nameof(GitHubActionsCustomStep.Uses)}'; 'with:' is only valid on a 'uses:' step"); + Assert.True(step.Shell.IsNullOrWhiteSpace() || !hasUses, $"Custom step '{id}' in workflow '{name}' sets '{nameof(GitHubActionsCustomStep.Shell)}' on a 'uses:' step; shell applies only to run steps"); @@ -404,16 +472,22 @@ private void ValidateCustomSteps(GitHubActionsStepPipeline pipeline) protected virtual IEnumerable<(string Key, string Value)> GetImports() { foreach (var input in GetWorkflowDispatchInputs()) + { yield return (input.Name, $"${{{{ github.event.inputs.{input.Name} }}}}"); + } static string GetSecretValue(string secret) => $"${{{{ secrets.{secret.SplitCamelHumpsWithKnownWords().JoinUnderscore().ToUpperInvariant()} }}}}"; foreach (var secret in ImportSecrets) + { yield return (secret, GetSecretValue(secret)); + } if (EnableGitHubToken) + { yield return ("GITHUB_TOKEN", GetSecretValue("GITHUB_TOKEN")); + } } protected virtual IEnumerable GetTriggers() @@ -426,19 +500,20 @@ protected virtual IEnumerable GetTriggers() OnPushExcludePaths.Length > 0) { Assert.True( - OnPushBranches.Length == 0 && OnPushTags.Length == 0 || OnPushBranchesIgnore.Length == 0 && OnPushTagsIgnore.Length == 0, + (OnPushBranches.Length == 0 && OnPushTags.Length == 0) || + (OnPushBranchesIgnore.Length == 0 && OnPushTagsIgnore.Length == 0), $"Cannot use {nameof(OnPushBranches)}/{nameof(OnPushTags)} and {nameof(OnPushBranchesIgnore)}/{nameof(OnPushTagsIgnore)} in combination"); yield return new GitHubActionsVcsTrigger - { - Kind = GitHubActionsTrigger.Push, - Branches = OnPushBranches, - BranchesIgnore = OnPushBranchesIgnore, - Tags = OnPushTags, - TagsIgnore = OnPushTagsIgnore, - IncludePaths = OnPushIncludePaths, - ExcludePaths = OnPushExcludePaths - }; + { + Kind = GitHubActionsTrigger.Push, + Branches = OnPushBranches, + BranchesIgnore = OnPushBranchesIgnore, + Tags = OnPushTags, + TagsIgnore = OnPushTagsIgnore, + IncludePaths = OnPushIncludePaths, + ExcludePaths = OnPushExcludePaths + }; } if (OnPullRequestBranches.Length > 0 || @@ -447,23 +522,33 @@ protected virtual IEnumerable GetTriggers() OnPullRequestExcludePaths.Length > 0) { yield return new GitHubActionsVcsTrigger - { - Kind = GitHubActionsTrigger.PullRequest, - Branches = OnPullRequestBranches, - BranchesIgnore = new string[0], - Tags = OnPullRequestTags, - TagsIgnore = new string[0], - IncludePaths = OnPullRequestIncludePaths, - ExcludePaths = OnPullRequestExcludePaths - }; + { + Kind = GitHubActionsTrigger.PullRequest, + Branches = OnPullRequestBranches, + BranchesIgnore = new string[0], + Tags = OnPullRequestTags, + TagsIgnore = new string[0], + IncludePaths = OnPullRequestIncludePaths, + ExcludePaths = OnPullRequestExcludePaths + }; } var dispatchInputs = GetWorkflowDispatchInputs().ToArray(); if (dispatchInputs.Length > 0) - yield return new GitHubActionsWorkflowDispatchTrigger { Inputs = dispatchInputs }; + { + yield return new GitHubActionsWorkflowDispatchTrigger + { + Inputs = dispatchInputs + }; + } if (OnCronSchedule != null) - yield return new GitHubActionsScheduledTrigger { Cron = OnCronSchedule }; + { + yield return new GitHubActionsScheduledTrigger + { + Cron = OnCronSchedule + }; + } } // Workflow names are spaces-to-underscores normalized (see the ctor), so an input's Workflows scope @@ -483,22 +568,37 @@ private IEnumerable GetWorkflowDispatchInput // legacy arrays first → untyped string inputs, preserving the existing ordering and output #pragma warning disable FALLOUTOBS001 // deliberate bridge for the obsolete legacy arrays foreach (var input in OnWorkflowDispatchOptionalInputs) - yield return new GitHubActionsWorkflowDispatchInput { Name = input, Required = false }; + { + yield return new GitHubActionsWorkflowDispatchInput + { + Name = input, + Required = false + }; + } + foreach (var input in OnWorkflowDispatchRequiredInputs) - yield return new GitHubActionsWorkflowDispatchInput { Name = input, Required = true }; + { + yield return new GitHubActionsWorkflowDispatchInput + { + Name = input, + Required = true + }; + } #pragma warning restore FALLOUTOBS001 foreach (var input in DeclaredInputs.Where(x => x.Workflows.Length == 0 || - x.Workflows.Select(NormalizeWorkflowName).Contains(name))) + x.Workflows.Select(NormalizeWorkflowName).Contains(name))) + { yield return new GitHubActionsWorkflowDispatchInput - { - Name = input.Name, - Type = input.Type, - Required = input.Required, - Default = input.Default, - Options = input.Options, - Description = input.Description - }; + { + Name = input.Name, + Type = input.Type, + Required = input.Required, + Default = input.Default, + Options = input.Options, + Description = input.Description + }; + } } private void ValidateWorkflowDispatchInputs() @@ -508,34 +608,50 @@ private void ValidateWorkflowDispatchInputs() foreach (var input in DeclaredInputs) { if (input.Type == GitHubActionsInputType.Choice) + { Assert.True(input.Options.Length > 0, $"'{input.Name}' is a choice input and requires non-empty '{nameof(GitHubActionsInputAttribute.Options)}'"); + } else + { Assert.True(input.Options.Length == 0, $"'{input.Name}' sets '{nameof(GitHubActionsInputAttribute.Options)}' but its type is not '{nameof(GitHubActionsInputType.Choice)}'"); + } if (input.Default != null) { if (input.Type == GitHubActionsInputType.Choice) + { Assert.True(input.Options.Contains(input.Default), $"'{input.Name}' default '{input.Default}' is not one of its options"); + } + if (input.Type == GitHubActionsInputType.Number) + { Assert.True(double.TryParse(input.Default, NumberStyles.Any, CultureInfo.InvariantCulture, out _), $"'{input.Name}' default '{input.Default}' is not a valid number"); + } + if (input.Type == GitHubActionsInputType.Boolean) + { Assert.True(input.Default is "true" or "false", $"'{input.Name}' default '{input.Default}' must be 'true' or 'false'"); + } } foreach (var workflow in input.Workflows) + { Assert.True(declaredWorkflows.Contains(NormalizeWorkflowName(workflow)), $"'{input.Name}' targets unknown workflow '{workflow}'"); + } } var inputs = GetWorkflowDispatchInputs().ToList(); foreach (var input in inputs) + { Assert.True(!input.Name.IsNullOrWhiteSpace(), $"workflow_dispatch input names must be non-empty in workflow '{name}'"); + } var names = inputs.Select(x => x.Name).ToList(); Assert.True(names.Count == names.Distinct().Count(), diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsDefaults.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsDefaults.cs index 5c47695c1..c0f081e08 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsDefaults.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsDefaults.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.GitHubActions; /// diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsImage.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsImage.cs index f2e38554e..4d2383199 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsImage.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsImage.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; namespace Fallout.Common.CI.GitHubActions; @@ -9,18 +7,45 @@ namespace Fallout.Common.CI.GitHubActions; /// public enum GitHubActionsImage { - [EnumValue("windows-2022")] WindowsServer2022, - [EnumValue("windows-2019")] WindowsServer2019, - [EnumValue("ubuntu-24.04")] Ubuntu2404, - [EnumValue("ubuntu-22.04")] Ubuntu2204, - [EnumValue("ubuntu-20.04")] Ubuntu2004, - [EnumValue("ubuntu-18.04")] Ubuntu1804, - [EnumValue("macos-14")] MacOs14, - [EnumValue("macos-12")] MacOs12, - [EnumValue("macos-11")] MacOs11, - [EnumValue("macos-10.15")] MacOs1015, - [EnumValue("windows-latest")] WindowsLatest, - [EnumValue("ubuntu-latest")] UbuntuLatest, - [EnumValue("macos-latest")] MacOsLatest, - [EnumValue("self-hosted")] SelfHosted + [EnumValue("windows-2022")] + WindowsServer2022, + + [EnumValue("windows-2019")] + WindowsServer2019, + + [EnumValue("ubuntu-24.04")] + Ubuntu2404, + + [EnumValue("ubuntu-22.04")] + Ubuntu2204, + + [EnumValue("ubuntu-20.04")] + Ubuntu2004, + + [EnumValue("ubuntu-18.04")] + Ubuntu1804, + + [EnumValue("macos-14")] + MacOs14, + + [EnumValue("macos-12")] + MacOs12, + + [EnumValue("macos-11")] + MacOs11, + + [EnumValue("macos-10.15")] + MacOs1015, + + [EnumValue("windows-latest")] + WindowsLatest, + + [EnumValue("ubuntu-latest")] + UbuntuLatest, + + [EnumValue("macos-latest")] + MacOsLatest, + + [EnumValue("self-hosted")] + SelfHosted } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputAttribute.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputAttribute.cs index e22b46449..9178d54f4 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputAttribute.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputAttribute.cs @@ -25,10 +25,16 @@ public GitHubActionsInputAttribute(string name) } public string Name { get; } + public GitHubActionsInputType Type { get; set; } = GitHubActionsInputType.String; + public bool Required { get; set; } + public string Default { get; set; } + public string[] Options { get; set; } = new string[0]; + public string Description { get; set; } + public string[] Workflows { get; set; } = new string[0]; } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputType.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputType.cs index 7deeacb39..c562c802f 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputType.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsInputType.cs @@ -8,9 +8,18 @@ namespace Fallout.Common.CI.GitHubActions; /// public enum GitHubActionsInputType { - [EnumValue("string")] String, - [EnumValue("boolean")] Boolean, - [EnumValue("number")] Number, - [EnumValue("choice")] Choice, - [EnumValue("environment")] Environment + [EnumValue("string")] + String, + + [EnumValue("boolean")] + Boolean, + + [EnumValue("number")] + Number, + + [EnumValue("choice")] + Choice, + + [EnumValue("environment")] + Environment } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsPermissions.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsPermissions.cs index a69838d35..e278ed8e6 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsPermissions.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsPermissions.cs @@ -1,22 +1,45 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.CI.GitHubActions; public enum GitHubActionsPermissions { - [EnumValue("actions")] Actions, - [EnumValue("checks")] Checks, - [EnumValue("contents")] Contents, - [EnumValue("deployments")] Deployments, - [EnumValue("id-token")] IdToken, - [EnumValue("issues")] Issues, - [EnumValue("discussions")] Discussions, - [EnumValue("packages")] Packages, - [EnumValue("pages")] Pages, - [EnumValue("pull-requests")] PullRequests, - [EnumValue("repository-projects")] RepositoryProjects, - [EnumValue("security-events")] SecurityEvents, - [EnumValue("statuses")] Statuses, + [EnumValue("actions")] + Actions, + + [EnumValue("checks")] + Checks, + + [EnumValue("contents")] + Contents, + + [EnumValue("deployments")] + Deployments, + + [EnumValue("id-token")] + IdToken, + + [EnumValue("issues")] + Issues, + + [EnumValue("discussions")] + Discussions, + + [EnumValue("packages")] + Packages, + + [EnumValue("pages")] + Pages, + + [EnumValue("pull-requests")] + PullRequests, + + [EnumValue("repository-projects")] + RepositoryProjects, + + [EnumValue("security-events")] + SecurityEvents, + + [EnumValue("statuses")] + Statuses, } diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsSubmodules.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsSubmodules.cs index 7ce5a1594..6fb365597 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsSubmodules.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsSubmodules.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.GitHubActions; public enum GitHubActionsSubmodules diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActionsTrigger.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActionsTrigger.cs index f96098337..7b09009c6 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActionsTrigger.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActionsTrigger.cs @@ -1,12 +1,15 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.CI.GitHubActions; public enum GitHubActionsTrigger { - [EnumValue("push")] Push, - [EnumValue("pull_request")] PullRequest, - [EnumValue("workflow_dispatch")] WorkflowDispatch + [EnumValue("push")] + Push, + + [EnumValue("pull_request")] + PullRequest, + + [EnumValue("workflow_dispatch")] + WorkflowDispatch } diff --git a/src/Fallout.Common/CI/GitLab/GitLab.cs b/src/Fallout.Common/CI/GitLab/GitLab.cs index 5e4fe45c9..c2826c7d0 100644 --- a/src/Fallout.Common/CI/GitLab/GitLab.cs +++ b/src/Fallout.Common/CI/GitLab/GitLab.cs @@ -35,13 +35,15 @@ internal GitLab(Action messageSink) } string IBuildServer.Branch => CommitRefName; + string IBuildServer.Commit => CommitSha; public void BeginSection(string text, bool collapsed = true) { var sectionId = GetSectionId(text); var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - messageSink($"{SectionStartSequence}section_start:{unixTimestamp}:{sectionId}[collapsed={collapsed.ToString().ToLowerInvariant()}]{SectionResetSequence}{text}"); + messageSink( + $"{SectionStartSequence}section_start:{unixTimestamp}:{sectionId}[collapsed={collapsed.ToString().ToLowerInvariant()}]{SectionResetSequence}{text}"); } public void EndSection(string text) @@ -204,7 +206,8 @@ private string GetSectionId(string text) /// The project visibility. /// Can be internal, private, or public. /// - public GitLabProjectVisibility ProjectVisibility => EnvironmentInfo.GetVariable("CI_PROJECT_VISIBILITY"); + public GitLabProjectVisibility ProjectVisibility => + EnvironmentInfo.GetVariable("CI_PROJECT_VISIBILITY"); /// /// Address of the container registry server, formatted as <host>[:<port>]. @@ -390,7 +393,8 @@ private string GetSectionId(string text) /// /// The direct group image prefix for pulling images through the Dependency Proxy. /// - public string DependencyProxyDirectGroupImagePrefix => EnvironmentInfo.GetVariable("CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX"); + public string DependencyProxyDirectGroupImagePrefix => + EnvironmentInfo.GetVariable("CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX"); /// /// The top-level group image prefix for pulling images through the Dependency Proxy. @@ -490,14 +494,16 @@ private string GetSectionId(string text) /// Deprecated in GitLab 15.9 and scheduled to be removed in GitLab 17.0. /// Use ID tokens instead. /// - [Obsolete] public string JobJwt => EnvironmentInfo.GetVariable("CI_JOB_JWT"); + [Obsolete] + public string JobJwt => EnvironmentInfo.GetVariable("CI_JOB_JWT"); /// /// The same value as $CI_JOB_JWT. /// Deprecated in GitLab 15.9 and scheduled to be removed in GitLab 17.0. /// Use ID tokens instead. /// - [Obsolete] public string JobJwtV1 => EnvironmentInfo.GetVariable("CI_JOB_JWT_V1"); + [Obsolete] + public string JobJwtV1 => EnvironmentInfo.GetVariable("CI_JOB_JWT_V1"); /// /// A newly formatted RS256 JSON web token to increase compatibility. @@ -507,7 +513,8 @@ private string GetSectionId(string text) /// Deprecated in GitLab 15.9 and scheduled to be removed in GitLab 17.0. /// Use ID tokens instead. /// - [Obsolete] public string JobJwtV2 => EnvironmentInfo.GetVariable("CI_JOB_JWT_V2"); + [Obsolete] + public string JobJwtV2 => EnvironmentInfo.GetVariable("CI_JOB_JWT_V2"); /// /// $CI_JOB_NAME in lowercase, shortened to 63 bytes, and with everything except 0-9 and a-z replaced with -. @@ -806,7 +813,8 @@ private string GetSectionId(string text) /// /// true if $CI_MERGE_REQUEST_DESCRIPTION is truncated down to 2700 characters because the description of the merge request is too long. /// - public bool MergeRequestDescriptionIsTruncated => EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED"); + public bool MergeRequestDescriptionIsTruncated => + EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_DESCRIPTION_IS_TRUNCATED"); /// /// The instance-level ID of the merge request. @@ -861,7 +869,8 @@ private string GetSectionId(string text) /// /// true when the source branch of the merge request is protected. /// - public bool MergeRequestSourceBranchProtected => EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_PROTECTED"); + public bool MergeRequestSourceBranchProtected => + EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_PROTECTED"); /// /// The HEAD SHA of the source branch of the merge request. @@ -898,7 +907,8 @@ private string GetSectionId(string text) /// /// true when the target branch of the merge request is protected. /// - public bool MergeRequestTargetBranchProtected => EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED"); + public bool MergeRequestTargetBranchProtected => + EnvironmentInfo.GetVariable("CI_MERGE_REQUEST_TARGET_BRANCH_PROTECTED"); /// /// The HEAD SHA of the target branch of the merge request. @@ -920,17 +930,20 @@ private string GetSectionId(string text) /// /// The source repository name of the pull request. /// - public string ExternalPullRequestSourceRepository => EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_SOURCE_REPOSITORY"); + public string ExternalPullRequestSourceRepository => + EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_SOURCE_REPOSITORY"); /// /// The target repository name of the pull request. /// - public string ExternalPullRequestTargetRepository => EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_TARGET_REPOSITORY"); + public string ExternalPullRequestTargetRepository => + EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_TARGET_REPOSITORY"); /// /// The source branch name of the pull request. /// - public string ExternalPullRequestSourceBranchName => EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME"); + public string ExternalPullRequestSourceBranchName => + EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME"); /// /// The HEAD SHA of the source branch of the pull request. @@ -940,7 +953,8 @@ private string GetSectionId(string text) /// /// The target branch name of the pull request. /// - public string ExternalPullRequestTargetBranchName => EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME"); + public string ExternalPullRequestTargetBranchName => + EnvironmentInfo.GetVariable("CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME"); /// /// The HEAD SHA of the target branch of the pull request. diff --git a/src/Fallout.Common/CI/GitLab/GitLabProjectVisibility.cs b/src/Fallout.Common/CI/GitLab/GitLabProjectVisibility.cs index 3d7ff8827..c8adb9317 100644 --- a/src/Fallout.Common/CI/GitLab/GitLabProjectVisibility.cs +++ b/src/Fallout.Common/CI/GitLab/GitLabProjectVisibility.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.GitLab; +namespace Fallout.Common.CI.GitLab; public enum GitLabProjectVisibility { diff --git a/src/Fallout.Common/CI/Jenkins/Jenkins.cs b/src/Fallout.Common/CI/Jenkins/Jenkins.cs index 7707c14b1..ccb9d6e2c 100644 --- a/src/Fallout.Common/CI/Jenkins/Jenkins.cs +++ b/src/Fallout.Common/CI/Jenkins/Jenkins.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Fallout.Common.CI.Jenkins; @@ -20,6 +18,7 @@ internal Jenkins() } string IBuildServer.Branch => GitBranch ?? BranchName; + string IBuildServer.Commit => GitCommit; /// diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationConfiguration.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationConfiguration.cs index 7e26eb7ec..9bd366981 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationConfiguration.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationConfiguration.cs @@ -7,10 +7,15 @@ namespace Fallout.Common.CI.SpaceAutomation.Configuration; public class SpaceAutomationConfiguration : ConfigurationEntity { public string Name { get; set; } + public string VolumeSize { get; set; } + public string[] RefSpec { get; set; } + public SpaceAutomationContainer Container { get; set; } + public SpaceAutomationTrigger[] Triggers { get; set; } + public int? TimeoutInMinutes { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationContainer.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationContainer.cs index 4e950362f..279ed40d1 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationContainer.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationContainer.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -9,10 +7,15 @@ namespace Fallout.Common.CI.SpaceAutomation.Configuration; public class SpaceAutomationContainer : ConfigurationEntity { public string Image { get; set; } + public SpaceAutomationResources Resources { get; set; } + public Dictionary Imports { get; set; } + public string BuildScript { get; set; } + public string[] InvokedTargets { get; set; } + public bool Submodules { get; set; } public override void Write(CustomFileWriter writer) @@ -26,7 +29,9 @@ public override void Write(CustomFileWriter writer) { var scriptContent = new List(); if (Submodules) + { scriptContent.Add("git submodule update --init --recursive"); + } scriptContent.Add($"./{BuildScript} {InvokedTargets.JoinSpace()}"); diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCronScheduleTrigger.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCronScheduleTrigger.cs index 92a0dd6f2..18b8cd54d 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCronScheduleTrigger.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCronScheduleTrigger.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.SpaceAutomation.Configuration; diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCustomWriterExtensions.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCustomWriterExtensions.cs index 79e1d096f..1f9029d75 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCustomWriterExtensions.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationCustomWriterExtensions.cs @@ -20,7 +20,9 @@ public static IDisposable WriteBlock(this CustomFileWriter writer, string text) public static void WriteArray(this CustomFileWriter writer, string property, string[] values) { if (!values?.Any() ?? true) + { return; + } if (values.Length <= 1) { @@ -32,7 +34,9 @@ public static void WriteArray(this CustomFileWriter writer, string property, str using (writer.Indent()) { foreach (var value in values) + { writer.WriteLine(value); + } } writer.WriteLine("\"\"\""); diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationPushTrigger.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationPushTrigger.cs index 25bdb3782..c556c78f1 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationPushTrigger.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationPushTrigger.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -8,11 +7,17 @@ namespace Fallout.Common.CI.SpaceAutomation.Configuration; public class SpaceAutomationPushTrigger : SpaceAutomationTrigger { public bool? OnPush { get; set; } + public string[] OnPushBranchIncludes { get; set; } + public string[] OnPushBranchExcludes { get; set; } + public string[] OnPushBranchRegexIncludes { get; set; } + public string[] OnPushBranchRegexExcludes { get; set; } + public string[] OnPushPathIncludes { get; set; } + public string[] OnPushPathExcludes { get; set; } public override void Write(CustomFileWriter writer) @@ -36,7 +41,9 @@ bool HasPathFilter() using (writer.WriteBlock("gitPush")) { if (OnPush != null) + { writer.WriteLine($"enabled = {OnPush.ToString().ToLowerInvariant()}"); + } if (HasBranchFilter()) { diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationResources.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationResources.cs index 07784c3d5..e85ced48f 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationResources.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationResources.cs @@ -1,24 +1,28 @@ -using System; -using System.Linq; -using Fallout.Common.Utilities; +using Fallout.Common.Utilities; namespace Fallout.Common.CI.SpaceAutomation.Configuration; public class SpaceAutomationResources : ConfigurationEntity { public string Cpu { get; set; } + public string Memory { get; set; } public override void Write(CustomFileWriter writer) { if (Cpu != null || Memory != null) { - using (writer.WriteBlock($"resources")) + using (writer.WriteBlock("resources")) { if (Cpu != null) + { writer.WriteLine($"cpu = {Cpu}"); + } + if (Memory != null) + { writer.WriteLine($"memory = {Memory}"); + } } } } diff --git a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationTrigger.cs b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationTrigger.cs index 1a691d8a8..f87fb9e42 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationTrigger.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/Configuration/SpaceAutomationTrigger.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.SpaceAutomation.Configuration; +namespace Fallout.Common.CI.SpaceAutomation.Configuration; public abstract class SpaceAutomationTrigger : ConfigurationEntity { diff --git a/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomation.cs b/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomation.cs index 54efe276e..1bbc2e897 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomation.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomation.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Fallout.Common.CI.SpaceAutomation; @@ -16,21 +14,36 @@ public partial class SpaceAutomation : Host, IBuildServer internal static bool IsRunningSpaceAutomation => EnvironmentInfo.HasVariable("JB_SPACE_PROJECT_KEY"); string IBuildServer.Branch => GitBranch; + string IBuildServer.Commit => GitRevision; public string ProjectKey => EnvironmentInfo.GetVariable("JB_SPACE_PROJECT_KEY"); + public string ProjectId => EnvironmentInfo.GetVariable("JB_SPACE_PROJECT_ID"); + public string ApiUrl => EnvironmentInfo.GetVariable("JB_SPACE_API_URL"); + public string ClientId => EnvironmentInfo.GetVariable("JB_SPACE_CLIENT_ID"); + public string ClientSecret => EnvironmentInfo.GetVariable("JB_SPACE_CLIENT_SECRET"); + public string ClientToken => EnvironmentInfo.GetVariable("JB_SPACE_CLIENT_TOKEN"); + public string ExecutionNumber => EnvironmentInfo.GetVariable("JB_SPACE_EXECUTION_NUMBER"); + public string ExecutionId => EnvironmentInfo.GetVariable("JB_SPACE_EXECUTION_ID"); + public string ExecutionUrl => EnvironmentInfo.GetVariable("JB_SPACE_EXECUTION_URL"); + public string RepositoryName => EnvironmentInfo.GetVariable("JB_SPACE_GIT_REPOSITORY_NAME"); + public string GitRevision => EnvironmentInfo.GetVariable("JB_SPACE_GIT_REVISION"); + public string GitBranch => EnvironmentInfo.GetVariable("JB_SPACE_GIT_BRANCH"); + public string StepDataPath => EnvironmentInfo.GetVariable("JB_SPACE_STEP_DATA_PATH"); + public string WorkDirPath => EnvironmentInfo.GetVariable("JB_SPACE_WORK_DIR_PATH"); + public string FileSharePath => EnvironmentInfo.GetVariable("JB_SPACE_FILE_SHARE_PATH"); } diff --git a/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomationAttribute.cs b/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomationAttribute.cs index e11408d49..c87f5ac3a 100644 --- a/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomationAttribute.cs +++ b/src/Fallout.Common/CI/SpaceAutomation/SpaceAutomationAttribute.cs @@ -26,16 +26,28 @@ public SpaceAutomationAttribute(string name, string image) } public override Type HostType => typeof(SpaceAutomation); + public override AbsolutePath ConfigurationFile => Build.RootDirectory / ".space.kts"; - public override IEnumerable GeneratedFiles => new[] { ConfigurationFile }; + + public override IEnumerable GeneratedFiles => new[] + { + ConfigurationFile + }; public override IEnumerable RelevantTargetNames => InvokedTargets; + public override IEnumerable IrrelevantTargetNames => new string[0]; public string VolumeSize { get; set; } + public string ResourcesCpu { get; set; } + public string ResourcesMemory { get; set; } - public string[] RefSpec { get; set; } = { "refs/heads/*:refs/heads/*" }; + + public string[] RefSpec { get; set; } = + { + "refs/heads/*:refs/heads/*" + }; public bool Submodules { @@ -52,13 +64,19 @@ public bool OnPush } public string[] OnPushBranchIncludes { get; set; } + public string[] OnPushBranchExcludes { get; set; } + public string[] OnPushBranchRegexIncludes { get; set; } + public string[] OnPushBranchRegexExcludes { get; set; } + public string[] OnPushPathIncludes { get; set; } + public string[] OnPushPathExcludes { get; set; } public string OnCronSchedule { get; set; } + public string[] ImportSecrets { get; set; } = new string[0]; public int TimeoutInMinutes @@ -75,41 +93,42 @@ public override CustomFileWriter CreateWriter(StreamWriter streamWriter) public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) { return new SpaceAutomationConfiguration - { - Name = name, - VolumeSize = VolumeSize, - RefSpec = RefSpec, - Container = GetContainer(), - Triggers = GetTriggers().ToArray(), - TimeoutInMinutes = timeoutInMinutes - }; + { + Name = name, + VolumeSize = VolumeSize, + RefSpec = RefSpec, + Container = GetContainer(), + Triggers = GetTriggers().ToArray(), + TimeoutInMinutes = timeoutInMinutes + }; } protected virtual SpaceAutomationContainer GetContainer() { return new SpaceAutomationContainer - { - Image = image, - Resources = GetResources(), - Imports = GetImports().ToDictionary(x => x.Key, x => x.Value), - Submodules = submodules, - BuildScript = BuildCmdPath.Replace(".cmd", ".sh"), - InvokedTargets = InvokedTargets - }; + { + Image = image, + Resources = GetResources(), + Imports = GetImports().ToDictionary(x => x.Key, x => x.Value), + Submodules = submodules, + BuildScript = BuildCmdPath.Replace(".cmd", ".sh"), + InvokedTargets = InvokedTargets + }; } protected virtual SpaceAutomationResources GetResources() { return new SpaceAutomationResources - { - Cpu = ResourcesCpu, - Memory = ResourcesMemory - }; + { + Cpu = ResourcesCpu, + Memory = ResourcesMemory + }; } private IEnumerable<(string Key, string Value)> GetImports() { - return ImportSecrets.Select(x => ($"env[{x.DoubleQuote()}]", $"Secrets({x.SplitCamelHumpsWithKnownWords().JoinDash().ToLowerInvariant().DoubleQuote()})")); + return ImportSecrets.Select(x => ($"env[{x.DoubleQuote()}]", + $"Secrets({x.SplitCamelHumpsWithKnownWords().JoinDash().ToLowerInvariant().DoubleQuote()})")); } protected virtual IEnumerable GetTriggers() @@ -123,18 +142,23 @@ protected virtual IEnumerable GetTriggers() OnPushPathExcludes != null) { yield return new SpaceAutomationPushTrigger - { - OnPush = onPush, - OnPushBranchIncludes = OnPushBranchIncludes, - OnPushBranchExcludes = OnPushBranchExcludes, - OnPushBranchRegexIncludes = OnPushBranchRegexIncludes, - OnPushBranchRegexExcludes = OnPushBranchRegexExcludes, - OnPushPathIncludes = OnPushPathIncludes, - OnPushPathExcludes = OnPushPathExcludes - }; + { + OnPush = onPush, + OnPushBranchIncludes = OnPushBranchIncludes, + OnPushBranchExcludes = OnPushBranchExcludes, + OnPushBranchRegexIncludes = OnPushBranchRegexIncludes, + OnPushBranchRegexExcludes = OnPushBranchRegexExcludes, + OnPushPathIncludes = OnPushPathIncludes, + OnPushPathExcludes = OnPushPathExcludes + }; } if (OnCronSchedule != null) - yield return new SpaceAutomationCronScheduleTrigger { CronExpression = OnCronSchedule }; + { + yield return new SpaceAutomationCronScheduleTrigger + { + CronExpression = OnCronSchedule + }; + } } } diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityArtifactDependency.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityArtifactDependency.cs index bf9080c54..488299f20 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityArtifactDependency.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityArtifactDependency.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -7,6 +5,7 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityArtifactDependency : TeamCityDependency { public TeamCityBuildType BuildType { get; set; } + public string[] ArtifactRules { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildType.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildType.cs index 30bf70cc7..9529bb9a9 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildType.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildType.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.Common.Utilities; @@ -7,18 +6,29 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityBuildType : ConfigurationEntity { public string Id { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public TeamCityBuildTypeVcsRoot VcsRoot { get; set; } + public bool IsComposite { get; set; } + public bool IsDeployment { get; set; } + public string BuildCmdPath { get; set; } + public string[] InvokedTargets { get; set; } + public Partition Partition { get; set; } + public TeamCityParameter[] Parameters { get; set; } + public string[] ArtifactRules { get; set; } public TeamCityTrigger[] Triggers { get; set; } + public TeamCityDependency[] Dependencies { get; set; } public override void Write(CustomFileWriter writer) @@ -29,18 +39,27 @@ public override void Write(CustomFileWriter writer) writer.WriteLine($"name = {Name.DoubleQuote()}"); if (Description != null) + { writer.WriteLine($"description = {Description.DoubleQuote()}"); + } if (IsComposite) + { writer.WriteLine("type = Type.COMPOSITE"); + } + if (IsDeployment) + { writer.WriteLine("type = Type.DEPLOYMENT"); + } VcsRoot.Write(writer); WriteArtifacts(writer); if (!IsComposite) + { WriteSteps(writer); + } WriteParameters(writer); WriteTriggers(writer); @@ -53,24 +72,32 @@ public override void Write(CustomFileWriter writer) public virtual void WriteDependencies(CustomFileWriter writer) { if (!Dependencies?.Any() ?? true) + { return; + } using (writer.WriteBlock("dependencies")) { foreach (var dependency in Dependencies) + { dependency.Write(writer); + } } } public virtual void WriteParameters(CustomFileWriter writer) { if (!Parameters?.Any() ?? true) + { return; + } using (writer.WriteBlock("params")) { foreach (var parameter in Parameters) + { parameter.Write(writer); + } } } @@ -82,12 +109,16 @@ public virtual void WriteArtifacts(CustomFileWriter writer) private void WriteTriggers(CustomFileWriter writer) { if (!Triggers?.Any() ?? true) + { return; + } using (writer.WriteBlock("triggers")) { foreach (var trigger in Triggers) + { trigger.Write(writer); + } } } @@ -97,7 +128,9 @@ public virtual void WriteSteps(CustomFileWriter writer) { var arguments = $"{InvokedTargets.JoinSpace()} --skip"; if (Partition != null) + { arguments += $" --partition {Partition}"; + } void WriteConditionalExec(string path, string condition, string platform) { diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildTypeVcsRoot.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildTypeVcsRoot.cs index 530945720..093fcb672 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildTypeVcsRoot.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityBuildTypeVcsRoot.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -7,7 +5,9 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityBuildTypeVcsRoot : ConfigurationEntity { public TeamCityVcsRoot Root { get; set; } + public bool ShowDependenciesChanges { get; set; } + public bool CleanCheckoutDirectory { get; set; } public override void Write(CustomFileWriter writer) @@ -16,9 +16,14 @@ public override void Write(CustomFileWriter writer) { writer.WriteLine($"root({Root.Id})"); if (CleanCheckoutDirectory) + { writer.WriteLine("cleanCheckout = true"); + } + if (ShowDependenciesChanges) + { writer.WriteLine("showDependenciesChanges = true"); + } } } } diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfiguration.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfiguration.cs index 49fd4ce09..d17b1364e 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfiguration.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfiguration.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; @@ -21,6 +19,7 @@ public class TeamCityConfiguration : ConfigurationEntity }; public string Version { get; set; } + public TeamCityProject Project { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfigurationParameter.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfigurationParameter.cs index 3bbe95cff..064999b10 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfigurationParameter.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityConfigurationParameter.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.Common.Utilities; @@ -8,12 +7,19 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityConfigurationParameter : TeamCityParameter { public TeamCityParameterType Type { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public TeamCityParameterDisplay Display { get; set; } + public string DefaultValue { get; set; } + public IReadOnlyDictionary Options { get; set; } + public bool AllowMultiple { get; set; } + public string ValueSeparator { get; set; } public override void Write(CustomFileWriter writer) @@ -25,7 +31,9 @@ public override void Write(CustomFileWriter writer) writer.WriteLine($"label = {Name.DoubleQuote()},"); if (Description != null) + { writer.WriteLine($"description = {Description.DoubleQuote()},"); + } writer.WriteLine($"value = {DefaultValue.DoubleQuote()},"); @@ -48,7 +56,9 @@ public override void Write(CustomFileWriter writer) } if (Options == null && Type != TeamCityParameterType.Checkbox && Type != TeamCityParameterType.Password) + { writer.WriteLine($"allowEmpty = {(Display != TeamCityParameterDisplay.Prompt).ToString().ToLowerInvariant()},"); + } writer.WriteLine($"display = ParameterDisplay.{Display.ToString().ToUpperInvariant()})"); } diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityCustomWriterExtensions.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityCustomWriterExtensions.cs index 75b9a1546..a24d1bffe 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityCustomWriterExtensions.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityCustomWriterExtensions.cs @@ -20,7 +20,9 @@ public static IDisposable WriteBlock(this CustomFileWriter writer, string text) public static void WriteArray(this CustomFileWriter writer, string property, string[] values) { if (!values?.Any() ?? true) + { return; + } if (values.Length <= 1) { @@ -32,7 +34,9 @@ public static void WriteArray(this CustomFileWriter writer, string property, str using (writer.Indent()) { foreach (var value in values) + { writer.WriteLine(value); + } } writer.WriteLine("\"\"\".trimIndent()"); diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependency.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependency.cs index 995ee019a..fb1c45417 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependency.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependency.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public abstract class TeamCityDependency : ConfigurationEntity diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependencyFailureAction.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependencyFailureAction.cs index dfe57f1f2..a2dbde3ce 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependencyFailureAction.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityDependencyFailureAction.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public enum TeamCityDependencyFailureAction diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityFinishBuildTrigger.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityFinishBuildTrigger.cs index 65d3bdbc1..96d82fb0b 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityFinishBuildTrigger.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityFinishBuildTrigger.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityKeyValueParameter.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityKeyValueParameter.cs index 54c28ba41..449296158 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityKeyValueParameter.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityKeyValueParameter.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -13,6 +11,7 @@ public TeamCityKeyValueParameter(string key, string value) } public string Key { get; set; } + public string Value { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameter.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameter.cs index 647d3d2ae..6a1bc9b01 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameter.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameter.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public abstract class TeamCityParameter : ConfigurationEntity diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterDisplay.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterDisplay.cs index 24929f333..ecfba73e0 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterDisplay.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterDisplay.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public enum TeamCityParameterDisplay diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterType.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterType.cs index 940f2c943..6fb9fce00 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterType.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityParameterType.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public enum TeamCityParameterType diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityProject.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityProject.cs index 74bd74de6..e4d7d5a52 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityProject.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityProject.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.Common.Utilities; @@ -7,8 +6,11 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityProject : ConfigurationEntity { public string Description { get; set; } + public TeamCityParameter[] Parameters { get; set; } + public TeamCityVcsRoot VcsRoot { get; set; } + public TeamCityBuildType[] BuildTypes { get; set; } public override void Write(CustomFileWriter writer) @@ -16,10 +18,15 @@ public override void Write(CustomFileWriter writer) using (writer.WriteBlock("project")) { if (Description != null) + { writer.WriteLine($"description = {Description}"); + } foreach (var buildType in BuildTypes) + { writer.WriteLine($"buildType({buildType.Id})"); + } + writer.WriteLine(); writer.WriteLine($"buildTypesOrder = arrayListOf({BuildTypes.Select(x => x.Id).JoinCommaSpace()})"); @@ -30,7 +37,9 @@ public override void Write(CustomFileWriter writer) using (writer.WriteBlock("params")) { foreach (var parameter in Parameters) + { parameter.Write(writer); + } } } } diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityScheduledTrigger.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityScheduledTrigger.cs index 82a283544..72423cef8 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityScheduledTrigger.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityScheduledTrigger.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -7,9 +5,13 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityScheduledTrigger : TeamCityTrigger { public string[] BranchFilters { get; set; } + public string[] TriggerRules { get; set; } + public bool TriggerBuildAlways { get; set; } + public bool WithPendingChangesOnly { get; set; } + public bool EnableQueueOptimization { get; set; } public override void Write(CustomFileWriter writer) @@ -25,7 +27,9 @@ public override void Write(CustomFileWriter writer) writer.WriteArray("triggerRules", TriggerRules); if (TriggerBuildAlways) + { writer.WriteLine("triggerBuild = always()"); + } writer.WriteLine("withPendingChangesOnly = false"); writer.WriteLine($"enableQueueOptimization = {EnableQueueOptimization.ToString().ToLowerInvariant()}"); diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCitySnapshotDependency.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCitySnapshotDependency.cs index d8358967e..b741cba22 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCitySnapshotDependency.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCitySnapshotDependency.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -7,7 +5,9 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCitySnapshotDependency : TeamCityDependency { public TeamCityBuildType BuildType { get; set; } + public TeamCityDependencyFailureAction FailureAction { get; set; } + public TeamCityDependencyFailureAction CancelAction { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityTrigger.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityTrigger.cs index fa302d64a..05ffc5639 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityTrigger.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityTrigger.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity.Configuration; public abstract class TeamCityTrigger : ConfigurationEntity diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsRoot.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsRoot.cs index 0435792b9..eb58cd1f0 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsRoot.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsRoot.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; diff --git a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsTrigger.cs b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsTrigger.cs index af6010d08..cbc641c25 100644 --- a/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsTrigger.cs +++ b/src/Fallout.Common/CI/TeamCity/Configuration/TeamCityVcsTrigger.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Utilities; namespace Fallout.Common.CI.TeamCity.Configuration; @@ -7,6 +5,7 @@ namespace Fallout.Common.CI.TeamCity.Configuration; public class TeamCityVcsTrigger : TeamCityTrigger { public string[] BranchFilters { get; set; } + public string[] TriggerRules { get; set; } public override void Write(CustomFileWriter writer) diff --git a/src/Fallout.Common/CI/TeamCity/DotNetBuildSettingsExtensions.cs b/src/Fallout.Common/CI/TeamCity/DotNetBuildSettingsExtensions.cs index 1298e4cca..08ae272c2 100644 --- a/src/Fallout.Common/CI/TeamCity/DotNetBuildSettingsExtensions.cs +++ b/src/Fallout.Common/CI/TeamCity/DotNetBuildSettingsExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; @@ -13,6 +11,7 @@ public static DotNetBuildSettings AddTeamCityLogger(this DotNetBuildSettings too var teamcityPackage = NuGetPackageResolver .GetLocalInstalledPackage("TeamCity.Dotnet.Integration", NuGetToolPathResolver.NuGetPackagesConfigFile) .NotNull("teamcityPackage != null"); + var loggerAssembly = teamcityPackage.Directory / "build" / "_common" / "msbuild15" / "TeamCity.MSBuild.Logger.dll"; Assert.FileExists(loggerAssembly); return toolSettings diff --git a/src/Fallout.Common/CI/TeamCity/DotNetTestSettingsExtensions.cs b/src/Fallout.Common/CI/TeamCity/DotNetTestSettingsExtensions.cs index ec8653064..d4880b204 100644 --- a/src/Fallout.Common/CI/TeamCity/DotNetTestSettingsExtensions.cs +++ b/src/Fallout.Common/CI/TeamCity/DotNetTestSettingsExtensions.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; namespace Fallout.Common.CI.TeamCity; @@ -13,6 +11,7 @@ public static DotNetTestSettings AddTeamCityLogger(this DotNetTestSettings toolS var teamcityPackage = NuGetPackageResolver .GetLocalInstalledPackage("TeamCity.Dotnet.Integration", NuGetToolPathResolver.NuGetPackagesConfigFile) .NotNull("teamcityPackage != null"); + var loggerPath = teamcityPackage.Directory / "build" / "_common" / "vstest15"; Assert.DirectoryExists(loggerPath); return toolSettings diff --git a/src/Fallout.Common/CI/TeamCity/MSBuildSettingsExtensions.cs b/src/Fallout.Common/CI/TeamCity/MSBuildSettingsExtensions.cs index 0dcd951b0..dd016f64d 100644 --- a/src/Fallout.Common/CI/TeamCity/MSBuildSettingsExtensions.cs +++ b/src/Fallout.Common/CI/TeamCity/MSBuildSettingsExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tools.MSBuild; namespace Fallout.Common.CI.TeamCity; diff --git a/src/Fallout.Common/CI/TeamCity/TeamCity.Theming.cs b/src/Fallout.Common/CI/TeamCity/TeamCity.Theming.cs index a670029cb..1dd9110ba 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCity.Theming.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCity.Theming.cs @@ -8,6 +8,7 @@ namespace Fallout.Common.CI.TeamCity; public partial class TeamCity { internal override IHostTheme Theme => AnsiConsoleHostTheme.Default256AnsiColorTheme; + internal override string OutputTemplate => Logging.StandardOutputTemplate; protected internal override IDisposable WriteBlock(string text) @@ -30,7 +31,9 @@ protected internal override void ReportError(string text, string details = null) protected internal override bool FilterMessage(string message) { if (!message.ContainsOrdinalIgnoreCase("##teamcity[")) + { return false; + } Console.WriteLine(message); return true; diff --git a/src/Fallout.Common/CI/TeamCity/TeamCity.cs b/src/Fallout.Common/CI/TeamCity/TeamCity.cs index a0575e130..c8847bbc2 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCity.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCity.cs @@ -25,7 +25,9 @@ public partial class TeamCity : Host, IBuildServer private static IReadOnlyDictionary ParseDictionary(AbsolutePath file) { if (file == null) + { return null; + } var lines = file.ReadAllLines(); var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -38,8 +40,11 @@ private static IReadOnlyDictionary ParseDictionary(AbsolutePath .Replace("\\:", ":") .Replace("\\=", "=") .Replace("\\\\", "\\"); + if (string.IsNullOrWhiteSpace(line) || line[index: 0] == '#') + { continue; + } var index = line.IndexOfRegex(@"[^\.]=") + 1; var key = line[..index].Replace("secure:", string.Empty); @@ -78,7 +83,7 @@ internal TeamCity(Action messageSink) runnerProperties = Lazy.Create(() => ParseDictionary(SystemProperties?["teamcity.runner.properties.file"])); recentlyFailedTests = Lazy.Create(() => { - var file = (AbsolutePath) SystemProperties?["teamcity.tests.recentlyFailedTests.file"]; + var file = (AbsolutePath)SystemProperties?["teamcity.tests.recentlyFailedTests.file"]; return file.FileExists() ? file.ReadAllLines().ToImmutableList() as IReadOnlyCollection : new string[0]; @@ -86,32 +91,54 @@ internal TeamCity(Action messageSink) } string IBuildServer.Branch => BranchName; + string IBuildServer.Commit => BuildVcsNumber; public IReadOnlyDictionary ConfigurationProperties => configurationProperties.Value; + public IReadOnlyDictionary SystemProperties => systemProperties.Value; + public IReadOnlyDictionary RunnerProperties => runnerProperties.Value; + public IReadOnlyCollection RecentlyFailedTests => recentlyFailedTests.Value; public string BuildConfiguration => SystemProperties?["teamcity.buildConfName"]; + public string BuildTypeId => SystemProperties?["teamcity.buildType.id"]; - [NoConvert] public string BuildNumber => EnvironmentInfo.GetVariable("BUILD_NUMBER"); + + [NoConvert] + public string BuildNumber => EnvironmentInfo.GetVariable("BUILD_NUMBER"); + public string BuildVcsNumber => SystemProperties?["build.vcs.number"]; + public string Version => SystemProperties?["teamcity.version"]; + public string ProjectName => SystemProperties?["teamcity.projectName"]; + public string ServerUrl => ConfigurationProperties?["teamcity.serverUrl"]; + public string AuthUserId => SystemProperties["teamcity.auth.userId"]; + public string AuthPassword => SystemProperties["teamcity.auth.password"]; + public string ProjectId => ConfigurationProperties?["teamcity.project.id"]; + public long BuildId => long.Parse(ConfigurationProperties?["teamcity.build.id"] ?? 0.ToString()); + public bool IsBuildPersonal => bool.Parse(SystemProperties?.GetValueOrDefault("build.is.personal") ?? bool.FalseString); + public bool IsPullRequest => ConfigurationProperties?.GetValueOrDefault("teamcity.pullRequest.number") != null; + public long? PullRequestNumber => IsPullRequest ? long.Parse(ConfigurationProperties["teamcity.pullRequest.number"]) : null; + public string PullRequestSourceBranch => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.source.branch"] : null; + public string PullRequestTargetBranch => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.target.branch"] : null; + public string PullRequestTitle => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.title"] : null; - [NoConvert] public string BranchName => ConfigurationProperties?.GetValueOrDefault("teamcity.build.branch") + [NoConvert] + public string BranchName => ConfigurationProperties?.GetValueOrDefault("teamcity.build.branch") .NotNull("Configuration property 'teamcity.build.branch' is null. See https://youtrack.jetbrains.com/issue/TW-62888."); public void DisableServiceMessages() @@ -135,6 +162,7 @@ public void ImportData( Assert.True( type != TeamCityImportType.dotNetCoverage || tool != null, $"Importing data of type '{type}' requires to specify the tool"); + if (tool == TeamCityImportTool.dotcover && ConfigurationProperties["teamcity.dotCover.home"].EndsWithOrdinalIgnoreCase("bundled")) { @@ -162,9 +190,15 @@ public void AddBuildProblem(string description) public void SetBuildStatus(string text, bool prepend = false, bool append = false) { if (prepend) + { text = $"{text} {{build.status.text}}"; + } + if (append) + { text = $"{{build.status.text}} {text}"; + } + Write("buildStatus", x => x.AddPair("text", text)); } @@ -275,14 +309,20 @@ public void WriteError(string text, string errorDetails = null) public void Write(string command, Func, Dictionary> dictionaryConfigurator) { - Write(new[] { command }.Concat(dictionaryConfigurator(new Dictionary()) + Write(new[] + { + command + }.Concat(dictionaryConfigurator(new Dictionary()) .Select(x => $"{x.Key}='{Escape(x.Value.ToString())}'") .ToArray())); } public void Write(string command, params string[] escapedArguments) { - Write(new[] { command }.Concat(escapedArguments.Select(x => x.SingleQuote()))); + Write(new[] + { + command + }.Concat(escapedArguments.Select(x => x.SingleQuote()))); } public void Write(IEnumerable escapedTokens) diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs b/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs index 04453628b..d4e159e9e 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs @@ -8,7 +8,6 @@ using Fallout.Common.CI.TeamCity.Configuration; using Fallout.Common.Execution; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; using Fallout.Common.ValueInjection; @@ -19,9 +18,17 @@ namespace Fallout.Common.CI.TeamCity; public class TeamCityAttribute : ChainedConfigurationAttributeBase { public override Type HostType => typeof(TeamCity); + public override AbsolutePath ConfigurationFile => TeamcityDirectory / "settings.kts"; - public override IEnumerable GeneratedFiles => new[] { PomFile, ConfigurationFile }; + + public override IEnumerable GeneratedFiles => new[] + { + PomFile, + ConfigurationFile + }; + private AbsolutePath TeamcityDirectory => Build.RootDirectory / ".teamcity"; + private AbsolutePath PomFile => TeamcityDirectory / "pom.xml"; public override IEnumerable RelevantTargetNames => new string[0] @@ -32,15 +39,27 @@ public class TeamCityAttribute : ChainedConfigurationAttributeBase public string Version { get; set; } = "2021.2"; public string Description { get; set; } + public bool CleanCheckoutDirectory { get; set; } = true; public string[] VcsTriggerBranchFilters { get; set; } = new string[0]; - public string[] VcsTriggerRules { get; set; } = { "+:**" }; + + public string[] VcsTriggerRules { get; set; } = + { + "+:**" + }; + public string[] VcsTriggeredTargets { get; set; } = new string[0]; public bool NightlyBuildAlways { get; set; } = true; + public string[] NightlyTriggerBranchFilters { get; set; } = new string[0]; - public string[] NightlyTriggerRules { get; set; } = { "+:**" }; + + public string[] NightlyTriggerRules { get; set; } = + { + "+:**" + }; + public string[] NightlyTriggeredTargets { get; set; } = new string[0]; public string[] ManuallyTriggeredTargets { get; set; } = new string[0]; @@ -62,10 +81,10 @@ public override CustomFileWriter CreateWriter(StreamWriter streamWriter) public override ConfigurationEntity GetConfiguration(IReadOnlyCollection relevantTargets) { return new TeamCityConfiguration - { - Version = Version, - Project = GetProject(relevantTargets) - }; + { + Version = Version, + Project = GetProject(relevantTargets) + }; } public override void SerializeState() @@ -83,18 +102,19 @@ protected virtual TeamCityProject GetProject(IReadOnlyCollection(); var buildTypes = relevantTargets - .SelectMany(x => GetBuildTypes(x, vcsRoot, lookupTable, relevantTargets), (x, y) => (ExecutableTarget: x, BuildType: y)) + .SelectMany(x => GetBuildTypes(x, vcsRoot, lookupTable, relevantTargets), + (x, y) => (ExecutableTarget: x, BuildType: y)) .ForEachLazy(x => lookupTable.Add(x.ExecutableTarget, x.BuildType)) .Select(x => x.BuildType).ToArray(); var parameters = GetGlobalParameters(relevantTargets); return new TeamCityProject - { - VcsRoot = vcsRoot, - BuildTypes = buildTypes, - Parameters = parameters.ToArray() - }; + { + VcsRoot = vcsRoot, + BuildTypes = buildTypes, + Parameters = parameters.ToArray() + }; } protected virtual IEnumerable GetBuildTypes( @@ -111,55 +131,72 @@ protected virtual IEnumerable GetBuildTypes( from dependency in x.ArtifactDependencies let rules = dependency.Select(GetArtifactRule).ToArray() select new TeamCityArtifactDependency - { - BuildType = buildTypes[dependency.Key].Single(y => y.Partition == null), - ArtifactRules = rules - }).ToArray(); + { + BuildType = buildTypes[dependency.Key].Single(y => y.Partition == null), + ArtifactRules = rules + }).ToArray(); var snapshotDependencies = GetTargetDependencies(executableTarget) .SelectMany(x => buildTypes[x]) .Where(x => x.Partition == null) .Select(x => new TeamCitySnapshotDependency - { - BuildType = x, - FailureAction = TeamCityDependencyFailureAction.FailToStart, - CancelAction = TeamCityDependencyFailureAction.Cancel - }).ToArray(); + { + BuildType = x, + FailureAction = TeamCityDependencyFailureAction.FailToStart, + CancelAction = TeamCityDependencyFailureAction.Cancel + }).ToArray(); if (isPartitioned) { var totalPartitions = executableTarget.PartitionSize.Value; for (var i = 0; i < totalPartitions; i++) { - var partition = new Partition { Part = i + 1, Total = totalPartitions }; + var partition = new Partition + { + Part = i + 1, + Total = totalPartitions + }; + yield return new TeamCityBuildType - { - Id = $"{executableTarget.Name}_P{partition.Part}T{partition.Total}", - Name = $"{executableTarget.Name} {partition}", - Description = executableTarget.Description, - BuildCmdPath = BuildCmdPath, - ArtifactRules = artifactRules, - Partition = partition, - InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), - VcsRoot = new TeamCityBuildTypeVcsRoot { Root = vcsRoot, CleanCheckoutDirectory = CleanCheckoutDirectory }, - Dependencies = snapshotDependencies.Concat(artifactDependencies).ToArray() - }; + { + Id = $"{executableTarget.Name}_P{partition.Part}T{partition.Total}", + Name = $"{executableTarget.Name} {partition}", + Description = executableTarget.Description, + BuildCmdPath = BuildCmdPath, + ArtifactRules = artifactRules, + Partition = partition, + InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), + VcsRoot = new TeamCityBuildTypeVcsRoot + { + Root = vcsRoot, + CleanCheckoutDirectory = CleanCheckoutDirectory + }, + Dependencies = snapshotDependencies.Concat(artifactDependencies).ToArray() + }; } - artifactRules = new[] { "**/*" }; + artifactRules = new[] + { + "**/*" + }; + snapshotDependencies = buildTypes[executableTarget] .Select(x => new TeamCitySnapshotDependency - { - BuildType = x, - FailureAction = TeamCityDependencyFailureAction.AddProblem, - CancelAction = TeamCityDependencyFailureAction.Cancel - }).ToArray(); + { + BuildType = x, + FailureAction = TeamCityDependencyFailureAction.AddProblem, + CancelAction = TeamCityDependencyFailureAction.Cancel + }).ToArray(); + artifactDependencies = buildTypes[executableTarget] .Select(x => new TeamCityArtifactDependency - { - BuildType = x, - ArtifactRules = new[] { "**/*" } - }).ToArray(); + { + BuildType = x, + ArtifactRules = new[] + { + "**/*" + } + }).ToArray(); } var parameters = executableTarget.DelegateRequirements @@ -168,28 +205,29 @@ from dependency in x.ArtifactDependencies .Concat(new TeamCityKeyValueParameter( "teamcity.ui.runButton.caption", executableTarget.Name.SplitCamelHumpsWithKnownWords().JoinSpace())).ToArray(); + var triggers = GetTriggers(executableTarget, buildTypes).ToArray(); yield return new TeamCityBuildType - { - Id = executableTarget.Name, - Name = executableTarget.Name, - Description = executableTarget.Description, - BuildCmdPath = BuildCmdPath, - VcsRoot = new TeamCityBuildTypeVcsRoot - { - Root = vcsRoot, - ShowDependenciesChanges = isPartitioned, - CleanCheckoutDirectory = CleanCheckoutDirectory - }, - IsComposite = isPartitioned, - IsDeployment = ManuallyTriggeredTargets.Contains(executableTarget.Name), - InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), - ArtifactRules = artifactRules, - Dependencies = snapshotDependencies.Concat(artifactDependencies).ToArray(), - Parameters = parameters, - Triggers = triggers - }; + { + Id = executableTarget.Name, + Name = executableTarget.Name, + Description = executableTarget.Description, + BuildCmdPath = BuildCmdPath, + VcsRoot = new TeamCityBuildTypeVcsRoot + { + Root = vcsRoot, + ShowDependenciesChanges = isPartitioned, + CleanCheckoutDirectory = CleanCheckoutDirectory + }, + IsComposite = isPartitioned, + IsDeployment = ManuallyTriggeredTargets.Contains(executableTarget.Name), + InvokedTargets = chainLinkTargets.Select(x => x.Name).ToArray(), + ArtifactRules = artifactRules, + Dependencies = snapshotDependencies.Concat(artifactDependencies).ToArray(), + Parameters = parameters, + Triggers = triggers + }; } protected virtual IEnumerable GetTriggers( @@ -199,30 +237,30 @@ protected virtual IEnumerable GetTriggers( if (VcsTriggeredTargets.Contains(executableTarget.Name)) { yield return new TeamCityVcsTrigger - { - BranchFilters = VcsTriggerBranchFilters, - TriggerRules = VcsTriggerRules - }; + { + BranchFilters = VcsTriggerBranchFilters, + TriggerRules = VcsTriggerRules + }; } if (NightlyTriggeredTargets.Contains(executableTarget.Name)) { yield return new TeamCityScheduledTrigger - { - BranchFilters = NightlyTriggerBranchFilters, - TriggerRules = NightlyTriggerRules, - EnableQueueOptimization = true, - WithPendingChangesOnly = false, - TriggerBuildAlways = NightlyBuildAlways - }; + { + BranchFilters = NightlyTriggerBranchFilters, + TriggerRules = NightlyTriggerRules, + EnableQueueOptimization = true, + WithPendingChangesOnly = false, + TriggerBuildAlways = NightlyBuildAlways + }; } foreach (var triggerDependency in executableTarget.TriggerDependencies) { yield return new TeamCityFinishBuildTrigger - { - BuildType = buildTypes[triggerDependency].Single(x => x.Partition == null) - }; + { + BuildType = buildTypes[triggerDependency].Single(x => x.Partition == null) + }; } } @@ -249,6 +287,7 @@ protected virtual IEnumerable GetSecretParameters() { var guids = Build.GetType().GetCustomAttributes() .ToDictionary(x => x.Name, x => $"credentialsJSON:{Guid.Parse(x.Guid):D}"); + return ImportSecrets.Select(x => new TeamCityConfigurationParameter { @@ -275,42 +314,55 @@ protected virtual TeamCityParameter GetParameter(MemberInfo member, bool require var defaultValue = !member.HasCustomAttribute() ? member.GetValue(Build) : default(string); // TODO: enumerables of ... if (defaultValue != null && - (memberType.IsAssignableTo(typeof(IAbsolutePathHolder)))) + memberType.IsAssignableTo(typeof(IAbsolutePathHolder))) + { defaultValue = Build.RootDirectory.GetUnixRelativePathTo(defaultValue.ToString()); + } TeamCityParameterType GetParameterType() { if (member.HasCustomAttribute()) + { return TeamCityParameterType.Password; + } + if (memberType == typeof(bool)) + { return TeamCityParameterType.Checkbox; + } + if (valueSet != null) + { return TeamCityParameterType.Select; + } + return TeamCityParameterType.Text; } return new TeamCityConfigurationParameter - { - // TODO: #555 - Should this use ParameterService.GetParameterMemberName(member) ? - Name = ParameterService.GetParameterMemberName(member), - Description = attribute.Description, - Options = valueSet?.ToDictionary(x => x.Item1, x => x.Item2), - Type = GetParameterType(), - DefaultValue = memberType.IsArray && defaultValue is IEnumerable enumerable - ? enumerable.Cast().Select(x => x.ToString()).Join(valueSeparator) - : defaultValue?.ToString(), - Display = required ? TeamCityParameterDisplay.Prompt : TeamCityParameterDisplay.Normal, - AllowMultiple = memberType.IsArray && valueSet is not null, - ValueSeparator = valueSeparator - }; + { + // TODO: #555 - Should this use ParameterService.GetParameterMemberName(member) ? + Name = ParameterService.GetParameterMemberName(member), + Description = attribute.Description, + Options = valueSet?.ToDictionary(x => x.Item1, x => x.Item2), + Type = GetParameterType(), + DefaultValue = memberType.IsArray && defaultValue is IEnumerable enumerable + ? enumerable.Cast().Select(x => x.ToString()).Join(valueSeparator) + : defaultValue?.ToString(), + Display = required ? TeamCityParameterDisplay.Prompt : TeamCityParameterDisplay.Normal, + AllowMultiple = memberType.IsArray && valueSet is not null, + ValueSeparator = valueSeparator + }; } protected virtual string GetArtifactRule(string source) { if (!Build.RootDirectory.Contains(source)) + { return source; + } - var relativeSource = (string) GetUnixRelativePath(Build.RootDirectory, source); + var relativeSource = (string)GetUnixRelativePath(Build.RootDirectory, source); var target = relativeSource.TakeWhile(x => x != '*').Reverse().SkipWhile(x => x != '/').Reverse(); return $"{relativeSource} => {new string(target.ToArray()).TrimEnd('/')}"; } diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityImportTool.cs b/src/Fallout.Common/CI/TeamCity/TeamCityImportTool.cs index 967f97e28..7df619424 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityImportTool.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityImportTool.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.TeamCity; +namespace Fallout.Common.CI.TeamCity; public enum TeamCityImportTool { diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityImportType.cs b/src/Fallout.Common/CI/TeamCity/TeamCityImportType.cs index b7d119c28..d5c95ff57 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityImportType.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityImportType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.CI.TeamCity; +namespace Fallout.Common.CI.TeamCity; public enum TeamCityImportType { diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityNoDataPublishedAction.cs b/src/Fallout.Common/CI/TeamCity/TeamCityNoDataPublishedAction.cs index 1d193fa68..2ee42e51a 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityNoDataPublishedAction.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityNoDataPublishedAction.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.CI.TeamCity; public enum TeamCityNoDataPublishedAction diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityStatus.cs b/src/Fallout.Common/CI/TeamCity/TeamCityStatus.cs index 69155e424..9b57dd88e 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityStatus.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityStatus.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Fallout.Common.CI.TeamCity; diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityTokenAttribute.cs b/src/Fallout.Common/CI/TeamCity/TeamCityTokenAttribute.cs index 397569868..65a2ae59f 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityTokenAttribute.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityTokenAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.Common.CI.TeamCity; @@ -13,5 +12,6 @@ public TeamCityTokenAttribute(string name, string guid) } public string Name { get; } + public string Guid { get; } } diff --git a/src/Fallout.Common/CI/TravisCI/TravisCI.cs b/src/Fallout.Common/CI/TravisCI/TravisCI.cs index 3065a9148..d4e7fb5ca 100644 --- a/src/Fallout.Common/CI/TravisCI/TravisCI.cs +++ b/src/Fallout.Common/CI/TravisCI/TravisCI.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Fallout.Common.CI.TravisCI; @@ -19,9 +17,11 @@ internal TravisCI() } string IBuildServer.Branch => Branch; + string IBuildServer.Commit => Commit; public bool Ci => EnvironmentInfo.GetVariable("CI"); + public bool ContinousIntegration => EnvironmentInfo.GetVariable("CONTINUOUS_INTEGRATION"); /// @@ -79,7 +79,8 @@ internal TravisCI() /// /// The number of the current job (for example, “4.1”). /// - [NoConvert] public string JobNumber => EnvironmentInfo.GetVariable("TRAVIS_JOB_NUMBER"); + [NoConvert] + public string JobNumber => EnvironmentInfo.GetVariable("TRAVIS_JOB_NUMBER"); /// /// On multi-OS builds, this value indicates the platform the job is running on. Values are linux and osx currently, to be extended in the future. @@ -89,7 +90,8 @@ internal TravisCI() /// /// TRAVIS_PULL_REQUEST is set to the pull request number if the current job is a pull request build, or false if it’s not. /// - [NoConvert] public string PullRequest => EnvironmentInfo.GetVariable("TRAVIS_PULL_REQUEST"); + [NoConvert] + public string PullRequest => EnvironmentInfo.GetVariable("TRAVIS_PULL_REQUEST"); /// /// If the current job is a pull request, the name of the branch from which the PR originated. @@ -130,21 +132,38 @@ internal TravisCI() public string Tag => EnvironmentInfo.GetVariable("TRAVIS_TAG"); public string DartVersion => EnvironmentInfo.GetVariable("TRAVIS_DARTVersion"); + public string GoVersion => EnvironmentInfo.GetVariable("TRAVIS_GOVersion"); + public string HaxeVersion => EnvironmentInfo.GetVariable("TRAVIS_HAXEVersion"); + public string JdkVersion => EnvironmentInfo.GetVariable("TRAVIS_JDKVersion"); + public string JuliaVersion => EnvironmentInfo.GetVariable("TRAVIS_JULIAVersion"); + public string NodeVersion => EnvironmentInfo.GetVariable("TRAVIS_NODEVersion"); + public string OtpRelease => EnvironmentInfo.GetVariable("TRAVIS_OTP_RELEASE"); + public string PerlVersion => EnvironmentInfo.GetVariable("TRAVIS_PERLVersion"); + public string PhpVersion => EnvironmentInfo.GetVariable("TRAVIS_PHPVersion"); + public string PythonVersion => EnvironmentInfo.GetVariable("TRAVIS_PYTHONVersion"); + public string RVersion => EnvironmentInfo.GetVariable("TRAVIS_RVersion"); + public string RubyVersion => EnvironmentInfo.GetVariable("TRAVIS_RUBYVersion"); + public string RustVersion => EnvironmentInfo.GetVariable("TRAVIS_RUSTVersion"); + public string ScalaVersion => EnvironmentInfo.GetVariable("TRAVIS_SCALAVersion"); + public string XCodeSdk => EnvironmentInfo.GetVariable("TRAVIS_XCODE_SDK"); + public string XCodeScheme => EnvironmentInfo.GetVariable("TRAVIS_XCODE_SCHEME"); + public string XCodeProject => EnvironmentInfo.GetVariable("TRAVIS_XCODE_PROJECT"); + public string XCodeWorkspace => EnvironmentInfo.GetVariable("TRAVIS_XCODE_WORKSPACE"); } diff --git a/src/Fallout.Common/CI/TravisCI/TravisCIEventType.cs b/src/Fallout.Common/CI/TravisCI/TravisCIEventType.cs index e5cb6e562..958775102 100644 --- a/src/Fallout.Common/CI/TravisCI/TravisCIEventType.cs +++ b/src/Fallout.Common/CI/TravisCI/TravisCIEventType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace Fallout.Common.CI.TravisCI; diff --git a/src/Fallout.Common/ChangeLog/ChangeLog.cs b/src/Fallout.Common/ChangeLog/ChangeLog.cs index 71bd815e9..fa7958b08 100644 --- a/src/Fallout.Common/ChangeLog/ChangeLog.cs +++ b/src/Fallout.Common/ChangeLog/ChangeLog.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -using NuGet.Versioning; using Fallout.Common.IO; +using NuGet.Versioning; namespace Fallout.Common.ChangeLog; diff --git a/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs b/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs index c354b0293..c9dbdcac1 100644 --- a/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs +++ b/src/Fallout.Common/ChangeLog/ChangeLogTasks.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using NuGet.Versioning; using Fallout.Common.Git; using Fallout.Common.IO; using Fallout.Common.Tools.GitHub; using Fallout.Common.Utilities; +using NuGet.Versioning; using Serilog; // ReSharper disable ArgumentsStyleLiteral @@ -37,7 +37,8 @@ public static string GetNuGetReleaseNotes(string changelogFile, GitRepository re if (repository.IsGitHubRepository() && changelogPath.FileExists()) { changelogSectionNotes.Add(string.Empty); - changelogSectionNotes.Add($"Full changelog at {repository.GetGitHubBrowseUrl(changelogPath, itemType: GitHubItemType.File)}"); + changelogSectionNotes.Add( + $"Full changelog at {repository.GetGitHubBrowseUrl(changelogPath, itemType: GitHubItemType.File)}"); } return changelogSectionNotes.JoinNewLine(); @@ -100,7 +101,8 @@ public static ChangeLog ReadChangelog(string changelogFile) /// public static void FinalizeChangelog(ChangeLog changelogFile, NuGetVersion tag, GitRepository repository = null) { - Log.Information("Finalizing {File} for {Tag} ...", PathConstruction.GetRelativePath(FalloutBuild.RootDirectory, changelogFile.Path), tag); + Log.Information("Finalizing {File} for {Tag} ...", + PathConstruction.GetRelativePath(FalloutBuild.RootDirectory, changelogFile.Path), tag); var unreleasedNotes = changelogFile.Unreleased; var releaseNotes = changelogFile.ReleaseNotes; @@ -133,7 +135,8 @@ public static void FinalizeChangelog(ChangeLog changelogFile, NuGetVersion tag, /// public static void FinalizeChangelog(AbsolutePath changelogFile, string tag, GitRepository repository = null) { - Log.Information("Finalizing {File} for {Tag} ...", PathConstruction.GetRelativePath(FalloutBuild.RootDirectory, changelogFile), tag); + Log.Information("Finalizing {File} for {Tag} ...", + PathConstruction.GetRelativePath(FalloutBuild.RootDirectory, changelogFile), tag); var content = changelogFile.ReadAllLines().ToList(); var sections = GetReleaseSections(content).ToList(); @@ -142,7 +145,9 @@ public static void FinalizeChangelog(AbsolutePath changelogFile, string tag, Git Assert.True(firstSection.Caption.All(char.IsLetter), "Cannot find a draft section"); Assert.True(sections.All(x => !x.Caption.EqualsOrdinalIgnoreCase(tag)), $"Tag '{tag}' already exists"); - Assert.True(firstSection.EndIndex > firstSection.StartIndex, $"Draft section '{firstSection.Caption}' does not contain any information"); + Assert.True(firstSection.EndIndex > firstSection.StartIndex, + $"Draft section '{firstSection.Caption}' does not contain any information"); + Assert.True(secondSection == null || NuGetVersion.Parse(tag).CompareTo(NuGetVersion.Parse(secondSection.Caption)) > 0, $"Tag '{tag}' is not greater compared to last tag '{secondSection?.Caption}'"); @@ -224,7 +229,9 @@ static string GetCaption(string str) var index = content.FindIndex(IsReleaseHead); if (index < 0) + { yield break; + } while (index < content.Count) { @@ -236,7 +243,7 @@ static string GetCaption(string str) } var caption = GetCaption(line); - var nextReleaseHeadIndex = content.FindIndex(index + 1, x => IsReleaseHead(x) || !IsReleaseContent(x)); + var nextReleaseHeadIndex = content.FindIndex(index + 1, x => IsReleaseHead(x) || !IsReleaseContent(x)); var releaseData = new ReleaseSection @@ -268,7 +275,11 @@ private static void UpdateVersionSummary(string tag, List content, GitRe content.Add(string.Empty); content.Add($"[{firstSection.Caption}]: {repository.GetGitHubCompareTagToHeadUrl(tag)}"); for (var i = 1; i + 1 < sections.Count; i++) - content.Add($"[{sections[i].Caption}]: {repository.GetGitHubCompareTagsUrl(sections[i].Caption, sections[i + 1].Caption)}"); + { + content.Add( + $"[{sections[i].Caption}]: {repository.GetGitHubCompareTagsUrl(sections[i].Caption, sections[i + 1].Caption)}"); + } + content.Add($"[{lastSection.Caption}]: {repository.GetGitHubBrowseUrl(branch: lastSection.Caption)}"); } } diff --git a/src/Fallout.Common/ChangeLog/ReleaseNotes.cs b/src/Fallout.Common/ChangeLog/ReleaseNotes.cs index 6d9cb0f48..642a1911f 100644 --- a/src/Fallout.Common/ChangeLog/ReleaseNotes.cs +++ b/src/Fallout.Common/ChangeLog/ReleaseNotes.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using NuGet.Versioning; diff --git a/src/Fallout.Common/Gitter/GitterTasks.cs b/src/Fallout.Common/Gitter/GitterTasks.cs index 305b557a2..5aa3763b9 100644 --- a/src/Fallout.Common/Gitter/GitterTasks.cs +++ b/src/Fallout.Common/Gitter/GitterTasks.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; using Fallout.Common.Utilities.Net; @@ -15,7 +13,7 @@ namespace Fallout.Common.Gitter; public static class GitterTasks { - private static HttpClient client = new(); + private static readonly HttpClient client = new(); public static void SendGitterMessage(string message, string roomId, string token) { diff --git a/src/Fallout.Common/IO/FtpTasks.cs b/src/Fallout.Common/IO/FtpTasks.cs index ad1bd6840..3e483f580 100644 --- a/src/Fallout.Common/IO/FtpTasks.cs +++ b/src/Fallout.Common/IO/FtpTasks.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net; using Serilog; + #pragma warning disable SYSLIB0014 namespace Fallout.Common.IO; @@ -60,7 +61,9 @@ public static void FtpMakeDirectory(string path) { var parentPath = GetParentPath(path); if (parentPath != path) + { FtpMakeDirectory(parentPath); + } var request = WebRequest.Create(path); request.Method = WebRequestMethods.Ftp.MakeDirectory; diff --git a/src/Fallout.Common/IO/HttpTasks.cs b/src/Fallout.Common/IO/HttpTasks.cs index 009204f01..d2f81f09d 100644 --- a/src/Fallout.Common/IO/HttpTasks.cs +++ b/src/Fallout.Common/IO/HttpTasks.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; @@ -59,7 +58,11 @@ private static HttpClient CreateHttpClient( Configure clientConfigurator = null, Action headerConfigurator = null) { - var httpClient = new HttpClient { Timeout = DefaultTimeout }; + var httpClient = new HttpClient + { + Timeout = DefaultTimeout + }; + clientConfigurator?.Invoke(httpClient); headerConfigurator?.Invoke(httpClient.DefaultRequestHeaders); return httpClient; diff --git a/src/Fallout.Common/IO/TextTasks.cs b/src/Fallout.Common/IO/TextTasks.cs index b941d1db6..97f905121 100644 --- a/src/Fallout.Common/IO/TextTasks.cs +++ b/src/Fallout.Common/IO/TextTasks.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; namespace Fallout.Common.IO; diff --git a/src/Fallout.Common/IO/XmlElementBuilder.cs b/src/Fallout.Common/IO/XmlElementBuilder.cs index 56e460c03..be80aec6d 100644 --- a/src/Fallout.Common/IO/XmlElementBuilder.cs +++ b/src/Fallout.Common/IO/XmlElementBuilder.cs @@ -16,15 +16,22 @@ public XmlElementBuilder SetAttribute(string name, object value) if (attribute == null) { if (value != null) + { element.Add(new XAttribute(name, value)); + } } else { if (value == null) + { attribute.Remove(); + } else + { attribute.SetValue(value); + } } + return this; } diff --git a/src/Fallout.Common/IO/XmlTasks.cs b/src/Fallout.Common/IO/XmlTasks.cs index 97373781f..3957992e4 100644 --- a/src/Fallout.Common/IO/XmlTasks.cs +++ b/src/Fallout.Common/IO/XmlTasks.cs @@ -155,7 +155,9 @@ private static (IReadOnlyCollection Elements, IReadOnlyCollectionfallout-migrate for a complete rewrite. /// public class Solution(SolutionModel model, AbsolutePath path = null) - : global::Fallout.Solutions.Solution(model, path) + : Solutions.Solution(model, path) { // C# does not inherit user-defined conversion operators onto a subclass, so the // canonical Solution's string / AbsolutePath coercions would silently disappear // on the shim. Re-expose them so `string s = Solution;` and `AbsolutePath p = Solution;` // keep working. The parameter type (shim Solution) is more specific than the // canonical's, so these win over the inherited operators without ambiguity. - public static implicit operator string(Solution solution) => (global::Fallout.Solutions.Solution)solution; - public static implicit operator AbsolutePath(Solution solution) => (global::Fallout.Solutions.Solution)solution; + public static implicit operator string(Solution solution) => (Solutions.Solution)solution; + public static implicit operator AbsolutePath(Solution solution) => (Solutions.Solution)solution; } /// @@ -45,7 +45,7 @@ public class Solution(SolutionModel model, AbsolutePath path = null) /// the shim above resolves correctly. /// public class SolutionAttribute(string relativePath) - : global::Fallout.Solutions.SolutionAttribute(relativePath) + : Solutions.SolutionAttribute(relativePath) { public SolutionAttribute() : this(relativePath: null) diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVault.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVault.cs index 7914871e8..0e7e61faa 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVault.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVault.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using Azure.Identity; using Azure.Security.KeyVault.Certificates; @@ -7,72 +6,77 @@ using Azure.Security.KeyVault.Secrets; using Fallout.Common.Utilities; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +public class AzureKeyVaultConfiguration { - public partial class AzureKeyVaultConfiguration - { - public virtual string TenantId { get; internal set; } - public virtual string ClientId { get; internal set; } - public virtual string ClientSecret { get; internal set; } - public virtual string BaseUrl { get; internal set; } - } + public virtual string TenantId { get; internal set; } - public class AzureKeyVault + public virtual string ClientId { get; internal set; } + + public virtual string ClientSecret { get; internal set; } + + public virtual string BaseUrl { get; internal set; } +} + +public class AzureKeyVault +{ + private readonly Lazy certificateClient; + private readonly Lazy keyClient; + private readonly Lazy secretClient; + + internal AzureKeyVault(string tenantId, string clientId, string clientSecret, string baseUrl) { - private readonly Lazy certificateClient; - private readonly Lazy keyClient; - private readonly Lazy secretClient; + var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + var uri = new Uri(baseUrl); - internal AzureKeyVault(string tenantId, string clientId, string clientSecret, string baseUrl) - { - var credential = new ClientSecretCredential(tenantId, clientId, clientSecret); - var uri = new Uri(baseUrl); + keyClient = new Lazy(() => new KeyClient(uri, credential)); + secretClient = new Lazy(() => new SecretClient(uri, credential)); + certificateClient = new Lazy(() => new CertificateClient(uri, credential)); + } - keyClient = new Lazy(() => new KeyClient(uri, credential)); - secretClient = new Lazy(() => new SecretClient(uri, credential)); - certificateClient = new Lazy(() => new CertificateClient(uri, credential)); - } + public CertificateClient CertificateClient => certificateClient.Value; + + public KeyClient KeyClient => keyClient.Value; - public CertificateClient CertificateClient => certificateClient.Value; - public KeyClient KeyClient => keyClient.Value; - public SecretClient SecretClient => secretClient.Value; + public SecretClient SecretClient => secretClient.Value; - public async Task GetKey(string keyName, string secretName = null) + public async Task GetKey(string keyName, string secretName = null) + { + return new AzureKeyVaultKey { - return new AzureKeyVaultKey - { - Key = (await KeyClient.GetKeyAsync(keyName)).Value.Key, - Secret = secretName != null ? await GetSecret(secretName) : null - }; - } + Key = (await KeyClient.GetKeyAsync(keyName)).Value.Key, + Secret = secretName != null ? await GetSecret(secretName) : null + }; + } - public async Task GetSecret(string secretName) + public async Task GetSecret(string secretName) + { + return (await SecretClient.GetSecretAsync(secretName)).Value.Value; + } + + public async Task GetCertificate(string certificateName, bool includeKey = true, + bool includeSecret = true) + { + var certificateBundleResponse = await CertificateClient.GetCertificateAsync(certificateName); + var certificateBundle = certificateBundleResponse.Value; + + string Parse(Uri id) { - return (await SecretClient.GetSecretAsync(secretName)).Value.Value; + Assert.True(id.Segments.Length < 3, $"The key/secret id {id.ToString().DoubleQuote()} does not contain a valid name"); + return id.Segments[2].TrimEnd('/'); } - public async Task GetCertificate(string certificateName, bool includeKey = true, bool includeSecret = true) + return new AzureKeyVaultCertificate { - var certificateBundleResponse = await CertificateClient.GetCertificateAsync(certificateName); - var certificateBundle = certificateBundleResponse.Value; - - string Parse(Uri id) - { - Assert.True(id.Segments.Length < 3, $"The key/secret id {id.ToString().DoubleQuote()} does not contain a valid name"); - return id.Segments[2].TrimEnd('/'); - } - - return new AzureKeyVaultCertificate - { - Cer = certificateBundle.Cer, - X509Thumbprint = certificateBundle.Properties.X509Thumbprint, - Key = includeKey && certificateBundle.KeyId != null - ? await GetKey(Parse(certificateBundle.KeyId)) - : null, - Secret = includeSecret && certificateBundle.SecretId != null - ? await GetSecret(Parse(certificateBundle.SecretId)) - : null - }; - } + Cer = certificateBundle.Cer, + X509Thumbprint = certificateBundle.Properties.X509Thumbprint, + Key = includeKey && certificateBundle.KeyId != null + ? await GetKey(Parse(certificateBundle.KeyId)) + : null, + Secret = includeSecret && certificateBundle.SecretId != null + ? await GetSecret(Parse(certificateBundle.SecretId)) + : null + }; } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttribute.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttribute.cs index 13ad2ab0a..1b56ac937 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttribute.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttribute.cs @@ -1,15 +1,12 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +/// Attribute to obtain the KeyVault defined by to retrieve multiple items. +public class AzureKeyVaultAttribute : AzureKeyVaultAttributeBase { - /// Attribute to obtain the KeyVault defined by to retrieve multiple items. - public class AzureKeyVaultAttribute : AzureKeyVaultAttributeBase + protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) { - protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) - { - return AzureKeyVaultTasks.LoadVault(configuration); - } + return AzureKeyVaultTasks.LoadVault(configuration); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttributeBase.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttributeBase.cs index c77a8f02e..85050033e 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttributeBase.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultAttributeBase.cs @@ -1,46 +1,50 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; using Fallout.Common.ValueInjection; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +public abstract class AzureKeyVaultAttributeBase : ParameterAttribute { - public abstract class AzureKeyVaultAttributeBase : ParameterAttribute + public string ConfigurationMemberName { get; set; } + + public override object GetValue(MemberInfo member, object instance) { - public string ConfigurationMemberName { get; set; } + var value = base.GetValue(member, instance); + if (value != null) + { + return value; + } - public override object GetValue(MemberInfo member, object instance) + var configuration = GetConfiguration(instance); + if (configuration == null) { - var value = base.GetValue(member, instance); - if (value != null) - return value; + return null; + } - var configuration = GetConfiguration(instance); - if (configuration == null) - return null; + return GetValue(configuration, member); + } - return GetValue(configuration, member); - } + protected abstract object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member); - protected abstract object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member); + private AzureKeyVaultConfiguration GetConfiguration(object instance) + { + var membersWithAttributes = ValueInjectionUtility.GetInjectionMembers(instance.GetType()) + .Where(x => x.Attribute is AzureKeyVaultConfigurationAttribute) + .ToArray(); - private AzureKeyVaultConfiguration GetConfiguration(object instance) - { - var membersWithAttributes = ValueInjectionUtility.GetInjectionMembers(instance.GetType()) - .Where(x => x.Attribute is AzureKeyVaultConfigurationAttribute) - .ToArray(); + Assert.NotEmpty(membersWithAttributes, + $"Member of the type {nameof(AzureKeyVaultConfiguration)} with {nameof(AzureKeyVaultConfigurationAttribute)} must be defined when using {GetType().Name}"); - Assert.NotEmpty(membersWithAttributes, - $"Member of the type {nameof(AzureKeyVaultConfiguration)} with {nameof(AzureKeyVaultConfigurationAttribute)} must be defined when using {GetType().Name}"); - Assert.True(membersWithAttributes.Length == 1 || ConfigurationMemberName != null, - $"Property {nameof(ConfigurationMemberName)} must be defined when providing more than one {nameof(AzureKeyVaultConfiguration)} member"); + Assert.True(membersWithAttributes.Length == 1 || ConfigurationMemberName != null, + $"Property {nameof(ConfigurationMemberName)} must be defined when providing more than one {nameof(AzureKeyVaultConfiguration)} member"); - var memberWithAttribute = membersWithAttributes.Length == 1 - ? membersWithAttributes.Single() - : membersWithAttributes.SingleOrDefault(x => x.Member.Name == ConfigurationMemberName); - Assert.True(memberWithAttribute is not (null, null), $"No member with the name '{ConfigurationMemberName}' exists"); + var memberWithAttribute = membersWithAttributes.Length == 1 + ? membersWithAttributes.Single() + : membersWithAttributes.SingleOrDefault(x => x.Member.Name == ConfigurationMemberName); - return (AzureKeyVaultConfiguration) memberWithAttribute.Attribute.GetValue(memberWithAttribute.Member, instance); - } + Assert.True(memberWithAttribute is not (null, null), $"No member with the name '{ConfigurationMemberName}' exists"); + + return (AzureKeyVaultConfiguration)memberWithAttribute.Attribute.GetValue(memberWithAttribute.Member, instance); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificate.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificate.cs index 1e99a96ed..b68c3cb65 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificate.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificate.cs @@ -1,13 +1,12 @@ -using System; -using System.Linq; +namespace Fallout.Common.Tools.AzureKeyVault; -namespace Fallout.Common.Tools.AzureKeyVault +public class AzureKeyVaultCertificate { - public class AzureKeyVaultCertificate - { - public byte[] Cer { get; internal set; } - public byte[] X509Thumbprint { get; internal set; } - public AzureKeyVaultKey Key { get; internal set; } - public string Secret { get; internal set; } - } + public byte[] Cer { get; internal set; } + + public byte[] X509Thumbprint { get; internal set; } + + public AzureKeyVaultKey Key { get; internal set; } + + public string Secret { get; internal set; } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificateAttribute.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificateAttribute.cs index 13a29ce46..9403ba96b 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificateAttribute.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultCertificateAttribute.cs @@ -1,28 +1,18 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; -namespace Fallout.Common.Tools.AzureKeyVault -{ - /// Attribute to obtain a certificates from from the Azure KeyVault defined by . - public class AzureKeyVaultCertificateAttribute : AzureKeyVaultAttributeBase - { - private readonly string certificateName; - - public AzureKeyVaultCertificateAttribute(string certificateName = null) - { - this.certificateName = certificateName; - } +namespace Fallout.Common.Tools.AzureKeyVault; - /// If set to true, the key of the certificate is also obtained. - public bool IncludeKey { get; set; } = true; +/// Attribute to obtain a certificates from from the Azure KeyVault defined by . +public class AzureKeyVaultCertificateAttribute(string certificateName = null) : AzureKeyVaultAttributeBase +{ + /// If set to true, the key of the certificate is also obtained. + public bool IncludeKey { get; set; } = true; - /// If set to true, the secret of the certificate is also obtained. - public bool IncludeSecret { get; set; } = true; + /// If set to true, the secret of the certificate is also obtained. + public bool IncludeSecret { get; set; } = true; - protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) - { - return AzureKeyVaultTasks.GetCertificateBundle(configuration, certificateName, IncludeKey, IncludeSecret); - } + protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) + { + return AzureKeyVaultTasks.GetCertificateBundle(configuration, certificateName, IncludeKey, IncludeSecret); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultConfigurationAttribute.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultConfigurationAttribute.cs index 1736b355a..fe45a7a60 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultConfigurationAttribute.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultConfigurationAttribute.cs @@ -1,72 +1,69 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; using Fallout.Common.ValueInjection; using Serilog; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +/// Defines where the KeyVault login details can be found. +public class AzureKeyVaultConfigurationAttribute : ValueInjectionAttributeBase { - /// Defines where the KeyVault login details can be found. - public class AzureKeyVaultConfigurationAttribute : ValueInjectionAttributeBase - { - ///

The base url of the Azure Key Vault. Either or must be set.

- public string BaseUrl { get; set; } + ///

The base url of the Azure Key Vault. Either or must be set.

+ public string BaseUrl { get; set; } - ///

Azure Tenant id.

- public string TenantId { get; set; } + ///

Azure Tenant id.

+ public string TenantId { get; set; } - ///

The client id of an AzureAd application with permissions for the required operations. Either or must be set.

- public string ClientId { get; set; } + ///

The client id of an AzureAd application with permissions for the required operations. Either or must be set.

+ public string ClientId { get; set; } - ///

The name of the parameter or environment variable which contains the base url to the Azure Key Vault. Either or must be set.

- public string BaseUrlParameterName { get; set; } + ///

The name of the parameter or environment variable which contains the base url to the Azure Key Vault. Either or must be set.

+ public string BaseUrlParameterName { get; set; } - ///

The name of the parameter or environment variable which contains the Azure Tenant id.

- public string TenantIdParameterName { get; set; } + ///

The name of the parameter or environment variable which contains the Azure Tenant id.

+ public string TenantIdParameterName { get; set; } - ///

The name of the parameter or environment variable which contains the id of an AzureAd application with permissions for the required operations. Either or must be set.

- public string ClientIdParameterName { get; set; } + ///

The name of the parameter or environment variable which contains the id of an AzureAd application with permissions for the required operations. Either or must be set.

+ public string ClientIdParameterName { get; set; } - ///

The name of the parameter or environment variable which contains the secret of the AzureAd application.

- public string ClientSecretParameterName { get; set; } + ///

The name of the parameter or environment variable which contains the secret of the AzureAd application.

+ public string ClientSecretParameterName { get; set; } - public override object GetValue(MemberInfo member, object instance) + public override object GetValue(MemberInfo member, object instance) + { + var configuration = new AzureKeyVaultConfiguration { - var configuration = new AzureKeyVaultConfiguration - { - BaseUrl = BaseUrl ?? GetMemberValueOrNull(BaseUrlParameterName, instance), - TenantId = TenantId ?? GetMemberValueOrNull(TenantIdParameterName, instance), - ClientId = ClientId ?? GetMemberValueOrNull(ClientIdParameterName, instance), - ClientSecret = GetMemberValueOrNull(ClientSecretParameterName, instance) - }; + BaseUrl = BaseUrl ?? GetMemberValueOrNull(BaseUrlParameterName, instance), + TenantId = TenantId ?? GetMemberValueOrNull(TenantIdParameterName, instance), + ClientId = ClientId ?? GetMemberValueOrNull(ClientIdParameterName, instance), + ClientSecret = GetMemberValueOrNull(ClientSecretParameterName, instance) + }; - if (configuration.BaseUrl == null || - configuration.TenantId == null || - configuration.ClientId == null || - configuration.ClientSecret == null) - { - string IsProvided(string value) => value != null ? "" : ""; - - Log.Warning("Could not initialize {Member} with Azure KeyVault (" + - "BaseUrl={BaseUrl}, " + - "TenantId={TenantId}, " + - "ClientId={ClientId}, " + - "ClientSecret={ClientSecret})", - member.Name, - IsProvided(configuration.BaseUrl), - IsProvided(configuration.TenantId), - IsProvided(configuration.ClientId), - IsProvided(configuration.ClientSecret)); + if (configuration.BaseUrl == null || + configuration.TenantId == null || + configuration.ClientId == null || + configuration.ClientSecret == null) + { + string IsProvided(string value) => value != null ? "" : ""; - return null; - } + Log.Warning("Could not initialize {Member} with Azure KeyVault (" + + "BaseUrl={BaseUrl}, " + + "TenantId={TenantId}, " + + "ClientId={ClientId}, " + + "ClientSecret={ClientSecret})", + member.Name, + IsProvided(configuration.BaseUrl), + IsProvided(configuration.TenantId), + IsProvided(configuration.ClientId), + IsProvided(configuration.ClientSecret)); - return configuration; + return null; } - public AzureKeyVaultConfiguration GetValue(object instance) - { - return (AzureKeyVaultConfiguration) GetValue(member: null, instance); - } + return configuration; + } + + public AzureKeyVaultConfiguration GetValue(object instance) + { + return (AzureKeyVaultConfiguration)GetValue(member: null, instance); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKey.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKey.cs index d0da5e314..2fb4adb77 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKey.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKey.cs @@ -1,12 +1,10 @@ -using System; -using System.Linq; -using Azure.Security.KeyVault.Keys; +using Azure.Security.KeyVault.Keys; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +public class AzureKeyVaultKey { - public class AzureKeyVaultKey - { - public JsonWebKey Key { get; internal set; } - public string Secret { get; internal set; } - } + public JsonWebKey Key { get; internal set; } + + public string Secret { get; internal set; } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKeyAttribute.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKeyAttribute.cs index d4a4e7464..8b79c9608 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKeyAttribute.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultKeyAttribute.cs @@ -1,22 +1,12 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +/// Attribute to obtain a key from from the Azure KeyVault defined by . +public class AzureKeyVaultKeyAttribute(string keyName = null) : AzureKeyVaultAttributeBase { - /// Attribute to obtain a key from from the Azure KeyVault defined by . - public class AzureKeyVaultKeyAttribute : AzureKeyVaultAttributeBase + protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) { - private readonly string keyName; - - public AzureKeyVaultKeyAttribute(string keyName = null) - { - this.keyName = keyName; - } - - protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) - { - return AzureKeyVaultTasks.GetKeyBundle(configuration, keyName ?? member.Name); - } + return AzureKeyVaultTasks.GetKeyBundle(configuration, keyName ?? member.Name); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultSecretAttribute.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultSecretAttribute.cs index d2bc8362b..e411484b7 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultSecretAttribute.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultSecretAttribute.cs @@ -1,23 +1,13 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; -namespace Fallout.Common.Tools.AzureKeyVault +namespace Fallout.Common.Tools.AzureKeyVault; + +/// Attribute to obtain a secret from the Azure KeyVault defined by . +public class AzureKeyVaultSecretAttribute(string secretName = null) : AzureKeyVaultAttributeBase { - /// Attribute to obtain a secret from the Azure KeyVault defined by . - public class AzureKeyVaultSecretAttribute : AzureKeyVaultAttributeBase + protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) { - private readonly string secretName; - - public AzureKeyVaultSecretAttribute(string secretName = null) - { - this.secretName = secretName; - } - - protected override object GetValue(AzureKeyVaultConfiguration configuration, MemberInfo member) - { - return ParameterService.GetParameter(member.Name) ?? - AzureKeyVaultTasks.GetSecret(configuration, secretName ?? member.Name); - } + return ParameterService.GetParameter(member.Name) ?? + AzureKeyVaultTasks.GetSecret(configuration, secretName ?? member.Name); } } diff --git a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultTasks.cs b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultTasks.cs index b4a6bfb35..7a42a1a4a 100644 --- a/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultTasks.cs +++ b/src/Fallout.Common/Tools/AzureKeyVault/AzureKeyVaultTasks.cs @@ -1,45 +1,41 @@ -using System; -using System.Linq; +namespace Fallout.Common.Tools.AzureKeyVault; -namespace Fallout.Common.Tools.AzureKeyVault +public static class AzureKeyVaultTasks { - public static class AzureKeyVaultTasks + ///

Load an Azure Key Vault to obtain secrets.

+ public static AzureKeyVault LoadVault(AzureKeyVaultConfiguration configuration) { - ///

Load an Azure Key Vault to obtain secrets.

- public static AzureKeyVault LoadVault(AzureKeyVaultConfiguration configuration) - { - return CreateVault(configuration); - } + return CreateVault(configuration); + } - ///

>Get a key bundle.

- public static AzureKeyVaultKey GetKeyBundle(AzureKeyVaultConfiguration configuration, string keyName) - { - return CreateVault(configuration).GetKey(keyName).Result; - } + ///

>Get a key bundle.

+ public static AzureKeyVaultKey GetKeyBundle(AzureKeyVaultConfiguration configuration, string keyName) + { + return CreateVault(configuration).GetKey(keyName).Result; + } - ///

Get a secret.

- public static string GetSecret(AzureKeyVaultConfiguration configuration, string secretName) - { - return CreateVault(configuration).GetSecret(secretName).Result; - } + ///

Get a secret.

+ public static string GetSecret(AzureKeyVaultConfiguration configuration, string secretName) + { + return CreateVault(configuration).GetSecret(secretName).Result; + } - ///

Get a certificate bundle.

- public static AzureKeyVaultCertificate GetCertificateBundle( - AzureKeyVaultConfiguration configuration, - string certificateName, - bool includeKey = true, - bool includeSecret = true) - { - return CreateVault(configuration).GetCertificate(certificateName, includeKey, includeSecret).Result; - } + ///

Get a certificate bundle.

+ public static AzureKeyVaultCertificate GetCertificateBundle( + AzureKeyVaultConfiguration configuration, + string certificateName, + bool includeKey = true, + bool includeSecret = true) + { + return CreateVault(configuration).GetCertificate(certificateName, includeKey, includeSecret).Result; + } - private static AzureKeyVault CreateVault(AzureKeyVaultConfiguration configuration) - { - return new AzureKeyVault( - configuration.TenantId.NotNull("configuration.TenantId != null"), - configuration.ClientId.NotNull("configuration.ClientId != null"), - configuration.ClientSecret.NotNull("configuration.ClientSecret != null"), - configuration.BaseUrl.NotNull("configuration.BaseUrl != null")); - } + private static AzureKeyVault CreateVault(AzureKeyVaultConfiguration configuration) + { + return new AzureKeyVault( + configuration.TenantId.NotNull("configuration.TenantId != null"), + configuration.ClientId.NotNull("configuration.ClientId != null"), + configuration.ClientSecret.NotNull("configuration.ClientSecret != null"), + configuration.BaseUrl.NotNull("configuration.BaseUrl != null")); } } diff --git a/src/Fallout.Common/Tools/CorFlags/CorFlagsSettings.cs b/src/Fallout.Common/Tools/CorFlags/CorFlagsSettings.cs index f24733583..75fff8de6 100644 --- a/src/Fallout.Common/Tools/CorFlags/CorFlagsSettings.cs +++ b/src/Fallout.Common/Tools/CorFlags/CorFlagsSettings.cs @@ -1,6 +1,4 @@ -using System; - -namespace Fallout.Common.Tools.CorFlags; +namespace Fallout.Common.Tools.CorFlags; partial class CorFlagsSettings { diff --git a/src/Fallout.Common/Tools/Coverlet/CoverletSettingsExtensions.cs b/src/Fallout.Common/Tools/Coverlet/CoverletSettingsExtensions.cs index d37e3dfb3..072b28df7 100644 --- a/src/Fallout.Common/Tools/Coverlet/CoverletSettingsExtensions.cs +++ b/src/Fallout.Common/Tools/Coverlet/CoverletSettingsExtensions.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; namespace Fallout.Common.Tools.Coverlet; diff --git a/src/Fallout.Common/Tools/Docker/DockerTargetDefinitionExtensions.cs b/src/Fallout.Common/Tools/Docker/DockerTargetDefinitionExtensions.cs index 0016dac68..b5324bd70 100644 --- a/src/Fallout.Common/Tools/Docker/DockerTargetDefinitionExtensions.cs +++ b/src/Fallout.Common/Tools/Docker/DockerTargetDefinitionExtensions.cs @@ -26,7 +26,8 @@ public static class DockerTargetDefinitionExtensions /// /// Execute this target within a Docker container /// - public static ITargetDefinition DockerRun(this ITargetDefinition targetDefinition, Configure configurator) + public static ITargetDefinition DockerRun(this ITargetDefinition targetDefinition, + Configure configurator) { var definition = (TargetDefinition)targetDefinition; var build = definition.Build; @@ -34,7 +35,9 @@ public static ITargetDefinition DockerRun(this ITargetDefinition targetDefinitio definition.Intercept = () => { if (build.IsInterceptorExecution) + { return false; + } var settings = configurator.InvokeSafe(new DockerRunTargetSettings()); settings.DotNetRuntime.NotNull(); @@ -46,7 +49,8 @@ public static ITargetDefinition DockerRun(this ITargetDefinition targetDefinitio bool IsUpToDate() => build.BuildAssemblyDirectory.GlobFiles("*.dll") .Select(x => build.BuildAssemblyDirectory.GetRelativePathTo(x)) .All(x => File.Exists(buildAssemblyDirectory / x) && - File.GetLastWriteTime(buildAssemblyDirectory / x) >= File.GetLastWriteTime(build.BuildAssemblyDirectory / x)); + File.GetLastWriteTime(buildAssemblyDirectory / x) >= + File.GetLastWriteTime(build.BuildAssemblyDirectory / x)); if ((!settings.BuildCaching ?? true) || !IsUpToDate()) { @@ -73,6 +77,7 @@ bool IsUpToDate() => build.BuildAssemblyDirectory.GlobFiles("*.dll") var (rootDirectory, nugetDirectory) = settings.Platform.StartsWithOrdinalIgnoreCase("win") ? (WindowsRootDirectory, WindowsNuGetDirectory) : (UnixRootDirectory, UnixNuGetDirectory); + var localTempDirectory = build.TemporaryDirectory / "docker" / definition.Name; var tempDirectory = rootDirectory / build.RootDirectory.GetRelativePathTo(localTempDirectory); var envFile = buildAssemblyDirectory / $".env.{definition.Name}"; @@ -80,6 +85,7 @@ bool IsUpToDate() => build.BuildAssemblyDirectory.GlobFiles("*.dll") envFile.WriteAllLines(environmentVariables.Where(x => x.Value != null).Select(x => $"{x.Key}={x.Value}") .Concat(environmentVariables.Where(x => x.Value == null).Select(x => x.Key))); + localTempDirectory.CreateOrCleanDirectory(); if (!settings.Username.IsNullOrEmpty()) @@ -100,16 +106,17 @@ bool IsUpToDate() => build.BuildAssemblyDirectory.GlobFiles("*.dll") .When(!settings.Rm.HasValue, _ => _ .EnableRm()) .AddVolume($"{build.RootDirectory}:{rootDirectory}") - .AddVolume($"{NuGetPackageResolver.GetPackagesDirectory(NuGetToolPathResolver.NuGetPackagesConfigFile)}:{nugetDirectory}") + .AddVolume( + $"{NuGetPackageResolver.GetPackagesDirectory(NuGetToolPathResolver.NuGetPackagesConfigFile)}:{nugetDirectory}") .SetPlatform(settings.Platform) .SetWorkdir(rootDirectory) .SetEnvFile(envFile) .SetEntrypoint(rootDirectory / build.RootDirectory.GetRelativePathTo(buildAssembly)) .SetArgs(new[] - { - definition.Target.Name, - $"--{ParameterService.GetParameterDashedName(Constants.SkippedTargetsParameterName)}" - }.Concat(settings.Args)) + { + definition.Target.Name, + $"--{ParameterService.GetParameterDashedName(Constants.SkippedTargetsParameterName)}" + }.Concat(settings.Args)) .DisableProcessInvocationLogging() .SetProcessLogger((_, message) => { @@ -126,7 +133,9 @@ bool IsUpToDate() => build.BuildAssemblyDirectory.GlobFiles("*.dll") finally { if (!settings.KeepEnvFile ?? true) + { File.Delete(envFile); + } } return true; diff --git a/src/Fallout.Common/Tools/Docker/DockerTasks.cs b/src/Fallout.Common/Tools/Docker/DockerTasks.cs index c5d3752e2..a36040e22 100644 --- a/src/Fallout.Common/Tools/Docker/DockerTasks.cs +++ b/src/Fallout.Common/Tools/Docker/DockerTasks.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; using Serilog.Events; namespace Fallout.Common.Tools.Docker; diff --git a/src/Fallout.Common/Tools/DotCover/TeamCitySetDotCoverHomePathAttribute.cs b/src/Fallout.Common/Tools/DotCover/TeamCitySetDotCoverHomePathAttribute.cs index ca999c4e9..a32342645 100644 --- a/src/Fallout.Common/Tools/DotCover/TeamCitySetDotCoverHomePathAttribute.cs +++ b/src/Fallout.Common/Tools/DotCover/TeamCitySetDotCoverHomePathAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.CI.TeamCity; using Fallout.Common.Execution; diff --git a/src/Fallout.Common/Tools/DotNet/DotNetRuntimeIdentifier.cs b/src/Fallout.Common/Tools/DotNet/DotNetRuntimeIdentifier.cs index 227c365ab..b520cbc2a 100644 --- a/src/Fallout.Common/Tools/DotNet/DotNetRuntimeIdentifier.cs +++ b/src/Fallout.Common/Tools/DotNet/DotNetRuntimeIdentifier.cs @@ -1,6 +1,4 @@ -using System; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using Fallout.Common.Tooling; namespace Fallout.Common.Tools.DotNet; @@ -8,37 +6,168 @@ namespace Fallout.Common.Tools.DotNet; [TypeConverter(typeof(TypeConverter))] public class DotNetRuntimeIdentifier : Enumeration { - public static DotNetRuntimeIdentifier win_x64 = new() { Value = "win-x64" }; - public static DotNetRuntimeIdentifier win_x86 = new() { Value = "win-x86" }; - public static DotNetRuntimeIdentifier win_arm = new() { Value = "win-arm" }; - public static DotNetRuntimeIdentifier win_arm64 = new() { Value = "win-arm64" }; - public static DotNetRuntimeIdentifier win7_x64 = new() { Value = "win7-x64" }; - public static DotNetRuntimeIdentifier win7_x86 = new() { Value = "win7-x86" }; - public static DotNetRuntimeIdentifier win81_x64 = new() { Value = "win81-x64" }; - public static DotNetRuntimeIdentifier win81_x86 = new() { Value = "win81-x86" }; - public static DotNetRuntimeIdentifier win81_arm = new() { Value = "win81-arm" }; - public static DotNetRuntimeIdentifier win10_x64 = new() { Value = "win10-x64" }; - public static DotNetRuntimeIdentifier win10_x86 = new() { Value = "win10-x86" }; - public static DotNetRuntimeIdentifier win10_arm = new() { Value = "win10-arm" }; - public static DotNetRuntimeIdentifier win10_arm64 = new() { Value = "win10-arm64" }; - public static DotNetRuntimeIdentifier linux_x64 = new() { Value = "linux-x64" }; - public static DotNetRuntimeIdentifier linux_musl_x64 = new() { Value = "linux-musl-x64" }; - public static DotNetRuntimeIdentifier linux_arm = new() { Value = "linux-arm" }; - public static DotNetRuntimeIdentifier linux_arm64 = new() { Value = "linux-arm64" }; - public static DotNetRuntimeIdentifier rhel_x64 = new() { Value = "rhel-x64" }; - public static DotNetRuntimeIdentifier rhel_6_x64 = new() { Value = "rhel.6-x64" }; - public static DotNetRuntimeIdentifier tizen = new() { Value = "tizen" }; - public static DotNetRuntimeIdentifier tizen_4_0_0 = new() { Value = "tizen.4.0.0" }; - public static DotNetRuntimeIdentifier tizen_5_0_0 = new() { Value = "tizen.5.0.0" }; - public static DotNetRuntimeIdentifier osx_x64 = new() { Value = "osx-x64" }; - public static DotNetRuntimeIdentifier osx_10_10_x64 = new() { Value = "osx.10.10-x64" }; - public static DotNetRuntimeIdentifier osx_10_11_x64 = new() { Value = "osx.10.11-x64" }; - public static DotNetRuntimeIdentifier osx_10_12_x64 = new() { Value = "osx.10.12-x64" }; - public static DotNetRuntimeIdentifier osx_10_13_x64 = new() { Value = "osx.10.13-x64" }; - public static DotNetRuntimeIdentifier osx_10_14_x64 = new() { Value = "osx.10.14-x64" }; - public static DotNetRuntimeIdentifier osx_10_15_x64 = new() { Value = "osx.10.15-x64" }; - public static DotNetRuntimeIdentifier osx_11_0_x64 = new() { Value = "osx.11.0-x64" }; - public static DotNetRuntimeIdentifier osx_11_0_arm64 = new() { Value = "osx.11.0-arm64" }; - public static DotNetRuntimeIdentifier osx_12_x64 = new() { Value = "osx.12-x64" }; - public static DotNetRuntimeIdentifier osx_12_arm64 = new() { Value = "osx.12-arm64" }; + public static DotNetRuntimeIdentifier win_x64 = new() + { + Value = "win-x64" + }; + + public static DotNetRuntimeIdentifier win_x86 = new() + { + Value = "win-x86" + }; + + public static DotNetRuntimeIdentifier win_arm = new() + { + Value = "win-arm" + }; + + public static DotNetRuntimeIdentifier win_arm64 = new() + { + Value = "win-arm64" + }; + + public static DotNetRuntimeIdentifier win7_x64 = new() + { + Value = "win7-x64" + }; + + public static DotNetRuntimeIdentifier win7_x86 = new() + { + Value = "win7-x86" + }; + + public static DotNetRuntimeIdentifier win81_x64 = new() + { + Value = "win81-x64" + }; + + public static DotNetRuntimeIdentifier win81_x86 = new() + { + Value = "win81-x86" + }; + + public static DotNetRuntimeIdentifier win81_arm = new() + { + Value = "win81-arm" + }; + + public static DotNetRuntimeIdentifier win10_x64 = new() + { + Value = "win10-x64" + }; + + public static DotNetRuntimeIdentifier win10_x86 = new() + { + Value = "win10-x86" + }; + + public static DotNetRuntimeIdentifier win10_arm = new() + { + Value = "win10-arm" + }; + + public static DotNetRuntimeIdentifier win10_arm64 = new() + { + Value = "win10-arm64" + }; + + public static DotNetRuntimeIdentifier linux_x64 = new() + { + Value = "linux-x64" + }; + + public static DotNetRuntimeIdentifier linux_musl_x64 = new() + { + Value = "linux-musl-x64" + }; + + public static DotNetRuntimeIdentifier linux_arm = new() + { + Value = "linux-arm" + }; + + public static DotNetRuntimeIdentifier linux_arm64 = new() + { + Value = "linux-arm64" + }; + + public static DotNetRuntimeIdentifier rhel_x64 = new() + { + Value = "rhel-x64" + }; + + public static DotNetRuntimeIdentifier rhel_6_x64 = new() + { + Value = "rhel.6-x64" + }; + + public static DotNetRuntimeIdentifier tizen = new() + { + Value = "tizen" + }; + + public static DotNetRuntimeIdentifier tizen_4_0_0 = new() + { + Value = "tizen.4.0.0" + }; + + public static DotNetRuntimeIdentifier tizen_5_0_0 = new() + { + Value = "tizen.5.0.0" + }; + + public static DotNetRuntimeIdentifier osx_x64 = new() + { + Value = "osx-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_10_x64 = new() + { + Value = "osx.10.10-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_11_x64 = new() + { + Value = "osx.10.11-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_12_x64 = new() + { + Value = "osx.10.12-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_13_x64 = new() + { + Value = "osx.10.13-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_14_x64 = new() + { + Value = "osx.10.14-x64" + }; + + public static DotNetRuntimeIdentifier osx_10_15_x64 = new() + { + Value = "osx.10.15-x64" + }; + + public static DotNetRuntimeIdentifier osx_11_0_x64 = new() + { + Value = "osx.11.0-x64" + }; + + public static DotNetRuntimeIdentifier osx_11_0_arm64 = new() + { + Value = "osx.11.0-arm64" + }; + + public static DotNetRuntimeIdentifier osx_12_x64 = new() + { + Value = "osx.12-x64" + }; + + public static DotNetRuntimeIdentifier osx_12_arm64 = new() + { + Value = "osx.12-arm64" + }; } diff --git a/src/Fallout.Common/Tools/DotNet/DotNetTasks.cs b/src/Fallout.Common/Tools/DotNet/DotNetTasks.cs index caaccc91f..1e996a190 100644 --- a/src/Fallout.Common/Tools/DotNet/DotNetTasks.cs +++ b/src/Fallout.Common/Tools/DotNet/DotNetTasks.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.Common.Tooling; using Serilog.Events; @@ -27,12 +25,12 @@ public static string EscapeMSBuild(string str) { // https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-special-characters return str - .Replace("%", "%25") // Referencing metadata - .Replace("$", "%24") // Referencing properties - .Replace("@", "%40") // Referencing item lists - .Replace("'", "%27") // Conditions and other expressions - .Replace(";", "%3B") // List separator - .Replace("?", "%3F") // Wildcard character for file names in Include and Exclude attributes + .Replace("%", "%25") // Referencing metadata + .Replace("$", "%24") // Referencing properties + .Replace("@", "%40") // Referencing item lists + .Replace("'", "%27") // Conditions and other expressions + .Replace(";", "%3B") // List separator + .Replace("?", "%3F") // Wildcard character for file names in Include and Exclude attributes .Replace("*", "%2A"); // Wildcard character for use in file names in Include and Exclude attributes } } diff --git a/src/Fallout.Common/Tools/Git/GitTasks.cs b/src/Fallout.Common/Tools/Git/GitTasks.cs index c2fb51ab9..0f7ab6b82 100644 --- a/src/Fallout.Common/Tools/Git/GitTasks.cs +++ b/src/Fallout.Common/Tools/Git/GitTasks.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.Tools.Git; diff --git a/src/Fallout.Common/Tools/GitHub/GitHubTasks.cs b/src/Fallout.Common/Tools/GitHub/GitHubTasks.cs index 1d1abc282..d72f5d04e 100644 --- a/src/Fallout.Common/Tools/GitHub/GitHubTasks.cs +++ b/src/Fallout.Common/Tools/GitHub/GitHubTasks.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -24,7 +23,9 @@ public static class GitHubTasks static GitHubTasks() { if (EnvironmentInfo.GetVariable("GITHUB_TOKEN") is { } token) + { GitHubClient.Credentials = new Credentials(token); + } } public static async Task> GetGitHubDownloadUrls( @@ -38,6 +39,7 @@ static GitHubTasks() var relativeDirectory = HasPathRoot(directory) ? GetRelativePath(repository.LocalDirectory, directory) : directory; + relativeDirectory = (relativeDirectory + "/").TrimStart("/"); branch ??= await repository.GetDefaultBranch(); @@ -60,7 +62,8 @@ public static async Task GetDefaultBranch(this GitRepository repository) return repo.DefaultBranch; } - public static async Task GetLatestRelease(this GitRepository repository, bool includePrerelease = false, bool trimPrefix = false) + public static async Task GetLatestRelease(this GitRepository repository, bool includePrerelease = false, + bool trimPrefix = false) { Assert.True(repository.IsGitHubRepository()); var releases = await GitHubClient.Repository.Release.GetAll(repository.GetGitHubOwner(), repository.GetGitHubName()); @@ -73,7 +76,11 @@ public static async Task GetGitHubMilestone(this GitRepository reposi var milestones = await GitHubClient.Issue.Milestone.GetAllForRepository( repository.GetGitHubOwner(), repository.GetGitHubName(), - new MilestoneRequest { State = ItemStateFilter.All }); + new MilestoneRequest + { + State = ItemStateFilter.All + }); + return milestones.FirstOrDefault(x => x.Title == name); } @@ -84,7 +91,11 @@ public static async Task> GetGitHubMilestoneIssues(this Git return await GitHubClient.Issue.GetAllForRepository( repository.GetGitHubOwner(), repository.GetGitHubName(), - new RepositoryIssueRequest { State = ItemStateFilter.All, Milestone = milestone.Number.ToString() }); + new RepositoryIssueRequest + { + State = ItemStateFilter.All, + Milestone = milestone.Number.ToString() + }); } public static async Task TryCreateGitHubMilestone(this GitRepository repository, string title) @@ -123,7 +134,10 @@ await GitHubClient.Issue.Milestone.Update( repository.GetGitHubOwner(), repository.GetGitHubName(), milestone.Number, - new MilestoneUpdate { State = ItemState.Closed }); + new MilestoneUpdate + { + State = ItemState.Closed + }); } public static bool IsGitHubRepository(this GitRepository repository) @@ -204,10 +218,14 @@ private static string GetMethod(string relativePath, GitHubItemType itemType, Gi : null; if (itemType == GitHubItemType.Directory || Directory.Exists(absolutePath) || relativePath == null) + { return "tree"; + } if (itemType == GitHubItemType.File || File.Exists(absolutePath)) + { return "blob"; + } return null; } @@ -215,13 +233,19 @@ private static string GetMethod(string relativePath, GitHubItemType itemType, Gi private static string GetRepositoryRelativePath(string path, GitRepository repository) { if (path == null) + { return null; + } if (!Path.IsPathRooted(path)) + { return path; + } var localDirectory = repository.LocalDirectory.NotNull(); - Assert.True(IsDescendantPath(localDirectory, path), $"Path {path.SingleQuote()} must be descendant of {localDirectory:s}"); + Assert.True(IsDescendantPath(localDirectory, path), + $"Path {path.SingleQuote()} must be descendant of {localDirectory:s}"); + return GetRelativePath(localDirectory, path).Replace(oldChar: '\\', newChar: '/'); } } diff --git a/src/Fallout.Common/Tools/GitVersion/GitVersionAttribute.cs b/src/Fallout.Common/Tools/GitVersion/GitVersionAttribute.cs index 894f06e10..f9c3e2fd4 100644 --- a/src/Fallout.Common/Tools/GitVersion/GitVersionAttribute.cs +++ b/src/Fallout.Common/Tools/GitVersion/GitVersionAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Reflection; using Fallout.Common.CI.AppVeyor; using Fallout.Common.CI.AzurePipelines; @@ -19,10 +18,15 @@ namespace Fallout.Common.Tools.GitVersion; public class GitVersionAttribute : ValueInjectionAttributeBase { public string Framework { get; set; } + public bool DisableOnUnix { get; set; } + public bool UpdateAssemblyInfo { get; set; } + public bool UpdateBuildNumber { get; set; } = true; + public bool NoFetch { get; set; } + public bool NoCache { get; set; } = true; public override object GetValue(MemberInfo member, object instance) @@ -36,7 +40,10 @@ public override object GetValue(MemberInfo member, object instance) var repository = SuppressErrors(() => GitRepository.FromLocalDirectory(Build.RootDirectory)); if (repository is { Protocol: GitProtocol.Ssh } && !NoFetch) - Log.Warning($"{nameof(GitVersion)} does not support fetching SSH endpoints, enable {nameof(NoFetch)} to skip fetching"); + { + Log.Warning( + $"{nameof(GitVersion)} does not support fetching SSH endpoints, enable {nameof(NoFetch)} to skip fetching"); + } var gitVersion = GitVersionTasks.GitVersion(s => s .SetFramework(Framework) @@ -47,7 +54,8 @@ public override object GetValue(MemberInfo member, object instance) .When(TeamCity.Instance is { IsPullRequest: true } && !EnvironmentInfo.Variables.ContainsKey("Git_Branch"), _ => _ .AddProcessEnvironmentVariable( "Git_Branch", - TeamCity.Instance.ConfigurationProperties.Single(x => x.Key.StartsWith("teamcity.build.vcs.branch")).Value))) + TeamCity.Instance.ConfigurationProperties.Single(x => x.Key.StartsWith("teamcity.build.vcs.branch")) + .Value))) .Result; if (UpdateBuildNumber) diff --git a/src/Fallout.Common/Tools/GitVersion/GitVersionTasks.cs b/src/Fallout.Common/Tools/GitVersion/GitVersionTasks.cs index d59c8e643..e2ac62be1 100644 --- a/src/Fallout.Common/Tools/GitVersion/GitVersionTasks.cs +++ b/src/Fallout.Common/Tools/GitVersion/GitVersionTasks.cs @@ -18,7 +18,8 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< } catch (Exception exception) { - throw new Exception($"Cannot parse {nameof(GitVersion)} output:".Concat(output.Select(x => x.Text)).JoinNewLine(), exception); + throw new Exception($"Cannot parse {nameof(GitVersion)} output:".Concat(output.Select(x => x.Text)).JoinNewLine(), + exception); } } } @@ -31,11 +32,11 @@ public record GitVersion( string PreReleaseTagWithDash, string PreReleaseLabel, string PreReleaseLabelWithDash, - [property: JsonConverter(typeof(NumberToStringJsonConverter))] + [property: JsonConverter(typeof(NumberToStringJsonConverter))] string PreReleaseNumber, - [property: JsonConverter(typeof(NumberToStringJsonConverter))] + [property: JsonConverter(typeof(NumberToStringJsonConverter))] string WeightedPreReleaseNumber, - [property: JsonConverter(typeof(NumberToStringJsonConverter))] + [property: JsonConverter(typeof(NumberToStringJsonConverter))] string BuildMetaData, string BuildMetaDataPadded, string FullBuildMetaData, @@ -56,7 +57,7 @@ public record GitVersion( string NuGetPreReleaseTagV2, string NuGetPreReleaseTag, string VersionSourceSha, - [property: JsonConverter(typeof(NumberToStringJsonConverter))] + [property: JsonConverter(typeof(NumberToStringJsonConverter))] string CommitsSinceVersionSource, string CommitsSinceVersionSourcePadded, int? UncommittedChanges, diff --git a/src/Fallout.Common/Tools/GitVersion/NumberToStringJsonConverter.cs b/src/Fallout.Common/Tools/GitVersion/NumberToStringJsonConverter.cs index 78f3af1aa..177bbbfee 100644 --- a/src/Fallout.Common/Tools/GitVersion/NumberToStringJsonConverter.cs +++ b/src/Fallout.Common/Tools/GitVersion/NumberToStringJsonConverter.cs @@ -22,7 +22,9 @@ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS return reader.TokenType switch { JsonTokenType.String => reader.GetString(), - JsonTokenType.Number => reader.TryGetInt64(out var l) ? l.ToString() : reader.GetDouble().ToString(CultureInfo.InvariantCulture), + JsonTokenType.Number => reader.TryGetInt64(out var l) + ? l.ToString() + : reader.GetDouble().ToString(CultureInfo.InvariantCulture), JsonTokenType.Null => null, _ => throw new JsonException($"Unexpected token type {reader.TokenType} when deserializing string.") }; diff --git a/src/Fallout.Common/Tools/InnoSetup/InnoSetupSettings.cs b/src/Fallout.Common/Tools/InnoSetup/InnoSetupSettings.cs index 9e55bf1be..64d9da569 100644 --- a/src/Fallout.Common/Tools/InnoSetup/InnoSetupSettings.cs +++ b/src/Fallout.Common/Tools/InnoSetup/InnoSetupSettings.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tools.InnoSetup; +namespace Fallout.Common.Tools.InnoSetup; public partial class InnoSetupSettings { diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildPlatform.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildPlatform.cs index 05f0b0e01..d59a2ac66 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildPlatform.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildPlatform.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Tools.MSBuild; /// diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildProject.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildProject.cs index 049307af1..95cde7e16 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildProject.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildProject.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Dynamic; using System.Linq; @@ -18,7 +17,10 @@ internal MSBuildProject( } public bool IsSdkProject { get; } + public bool IsLegacyProject => !IsSdkProject; + public IReadOnlyDictionary Properties { get; } + public ILookup ItemGroups { get; } } diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs index 4daa75529..c52c7b8dd 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildSettingsExtensions.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Tools.MSBuild; public static partial class MSBuildSettingsExtensions diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildTasks.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildTasks.cs index 92d7890b8..1a4fe8669 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildTasks.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildTasks.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; -using System.Reflection; using Fallout.Common.Tooling; using Fallout.Common.Utilities; @@ -30,12 +27,12 @@ public static string EscapeMSBuild(string str) { // https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-special-characters return str - .Replace("%", "%25") // Referencing metadata - .Replace("$", "%24") // Referencing properties - .Replace("@", "%40") // Referencing item lists - .Replace("'", "%27") // Conditions and other expressions - .Replace(";", "%3B") // List separator - .Replace("?", "%3F") // Wildcard character for file names in Include and Exclude attributes + .Replace("%", "%25") // Referencing metadata + .Replace("$", "%24") // Referencing properties + .Replace("@", "%40") // Referencing item lists + .Replace("'", "%27") // Conditions and other expressions + .Replace(";", "%3B") // List separator + .Replace("?", "%3F") // Wildcard character for file names in Include and Exclude attributes .Replace("*", "%2A"); // Wildcard character for use in file names in Include and Exclude attributes } } @@ -45,7 +42,9 @@ partial class MSBuildSettings private string FormatPlatform(MSBuildTargetPlatform value) { if (value == null) + { return null; + } if (Equals(value, MSBuildTargetPlatform.MSIL)) { diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildToolPathResolver.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildToolPathResolver.cs index b99e201b4..9fa15fdad 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildToolPathResolver.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildToolPathResolver.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -9,7 +8,11 @@ namespace Fallout.Common.Tools.MSBuild; public static class MSBuildToolPathResolver { - private static readonly MSBuildPlatform[] platforms = { MSBuildPlatform.x86, MSBuildPlatform.x64 }; + private static readonly MSBuildPlatform[] platforms = + { + MSBuildPlatform.x86, + MSBuildPlatform.x64 + }; public static string Resolve(MSBuildVersion? msBuildVersion = null, MSBuildPlatform? msBuildPlatform = null) { @@ -17,7 +20,8 @@ public static string Resolve(MSBuildVersion? msBuildVersion = null, MSBuildPlatf .NotNull("Could not find a suitable MSBuild instance."); } - private static IEnumerable ResolveInternal(MSBuildVersion? msBuildVersion = null, MSBuildPlatform? msBuildPlatform = null) + private static IEnumerable ResolveInternal(MSBuildVersion? msBuildVersion = null, + MSBuildPlatform? msBuildPlatform = null) { Assert.True(!EnvironmentInfo.IsUnix || msBuildVersion == null); Assert.True(!EnvironmentInfo.IsUnix || msBuildPlatform == null); @@ -25,24 +29,34 @@ private static IEnumerable ResolveInternal(MSBuildVersion? msBuildVersio if (EnvironmentInfo.IsUnix) { return new[] - { - "/usr/bin/msbuild", - "/usr/local/bin/msbuild", - "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild" - }.Where(File.Exists); + { + "/usr/bin/msbuild", + "/usr/local/bin/msbuild", + "/Library/Frameworks/Mono.framework/Versions/Current/Commands/msbuild" + }.Where(File.Exists); } var instances = new List(); instances.AddRange( - from version in new[] { MSBuildVersion.VS2026, MSBuildVersion.VS2022, MSBuildVersion.VS2019, MSBuildVersion.VS2017 } + from version in new[] + { + MSBuildVersion.VS2026, + MSBuildVersion.VS2022, + MSBuildVersion.VS2019, + MSBuildVersion.VS2017 + } from platform in platforms from edition in typeof(VisualStudioEdition).GetEnumValues() let folder = GetProgramFilesFolder(version, edition) select GetFromVs2017Instance(version, platform, edition, folder)); instances.AddRange( - from version in new[] { MSBuildVersion.VS2015, MSBuildVersion.VS2013 } + from version in new[] + { + MSBuildVersion.VS2015, + MSBuildVersion.VS2013 + } from platform in platforms select GetVs2013To2015Instance(platform, version)); @@ -117,7 +131,6 @@ private static string GetVisualStudioFolder(MSBuildVersion version) }; } - private static string GetVersionFolder(MSBuildVersion version) { return version switch @@ -140,7 +153,9 @@ public Instance(MSBuildVersion version, MSBuildPlatform platform, string directo } public MSBuildPlatform Platform { get; } + public MSBuildVersion Version { get; } + public string ToolPath { get; } } diff --git a/src/Fallout.Common/Tools/MSBuild/MSBuildVersion.cs b/src/Fallout.Common/Tools/MSBuild/MSBuildVersion.cs index 503991ead..0c885525d 100644 --- a/src/Fallout.Common/Tools/MSBuild/MSBuildVersion.cs +++ b/src/Fallout.Common/Tools/MSBuild/MSBuildVersion.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Tools.MSBuild; /// diff --git a/src/Fallout.Common/Tools/MSpec/MSpecTasks.cs b/src/Fallout.Common/Tools/MSpec/MSpecTasks.cs index 6f4b05689..eb48a88f3 100644 --- a/src/Fallout.Common/Tools/MSpec/MSpecTasks.cs +++ b/src/Fallout.Common/Tools/MSpec/MSpecTasks.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; namespace Fallout.Common.Tools.MSpec; diff --git a/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs b/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs index 310027f1f..c2fe942a5 100644 --- a/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs +++ b/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs @@ -50,6 +50,7 @@ async Task PostMediaFile(string file) media_ids = mediaIds.ToArray() }) .GetResponseAsync(); + response.AssertSuccessfulStatusCode(); } } diff --git a/src/Fallout.Common/Tools/MinVer/MinVerAttribute.cs b/src/Fallout.Common/Tools/MinVer/MinVerAttribute.cs index 00a9d34b9..2e9d95197 100644 --- a/src/Fallout.Common/Tools/MinVer/MinVerAttribute.cs +++ b/src/Fallout.Common/Tools/MinVer/MinVerAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; +using System.Reflection; using Fallout.Common.CI.AppVeyor; using Fallout.Common.CI.AzurePipelines; using Fallout.Common.CI.TeamCity; @@ -15,6 +13,7 @@ namespace Fallout.Common.Tools.MinVer; public class MinVerAttribute : ValueInjectionAttributeBase { public string Framework { get; set; } + public bool UpdateBuildNumber { get; set; } public override object GetValue(MemberInfo member, object instance) diff --git a/src/Fallout.Common/Tools/MinVer/MinVerTasks.cs b/src/Fallout.Common/Tools/MinVer/MinVerTasks.cs index 8db835a42..a134a0817 100644 --- a/src/Fallout.Common/Tools/MinVer/MinVerTasks.cs +++ b/src/Fallout.Common/Tools/MinVer/MinVerTasks.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Fallout.Common.Tooling; @@ -17,12 +16,22 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< public record MinVer(string MinVerVersion) { public string MinVerMajor => MinVerVersion.Split('.')[0]; + public string MinVerMinor => MinVerVersion.Split('.')[1]; + public string MinVerPatch => MinVerVersion.Split('.')[2].Split('-')[0].Split('+')[0]; - public string MinVerPreRelease => MinVerVersion.Split('+')[0].Contains('-') ? MinVerVersion.Split('+')[0].Split(['-',], count: 2)[1] : null; + + public string MinVerPreRelease => MinVerVersion.Split('+')[0].Contains('-') + ? MinVerVersion.Split('+')[0].Split(['-',], count: 2)[1] + : null; + public string MinVerBuildMetadata => MinVerVersion.Contains('+') ? MinVerVersion.Split(['+',], count: 2)[1] : null; + public string AssemblyVersion => $"{MinVerMajor}.0.0.0"; + public string FileVersion => $"{MinVerMajor}.{MinVerMinor}.{MinVerPatch}.0"; + public string PackageVersion => MinVerVersion; + public string Version => MinVerVersion; } diff --git a/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningAttribute.cs b/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningAttribute.cs index ff235424e..a0bf4d77a 100644 --- a/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningAttribute.cs +++ b/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningAttribute.cs @@ -1,4 +1,3 @@ -using System; using System.Reflection; using Fallout.Common.CI.AppVeyor; using Fallout.Common.CI.AzurePipelines; diff --git a/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningTasks.cs b/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningTasks.cs index d9a560516..a619a3e12 100644 --- a/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningTasks.cs +++ b/src/Fallout.Common/Tools/NerdbankGitVersioning/NerdbankGitVersioningTasks.cs @@ -18,7 +18,9 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< } catch (Exception exception) { - throw new Exception($"Cannot parse {nameof(NerdbankGitVersioning)} output:".Concat(output.Select(x => x.Text)).JoinNewLine(), exception); + throw new Exception( + $"Cannot parse {nameof(NerdbankGitVersioning)} output:".Concat(output.Select(x => x.Text)).JoinNewLine(), + exception); } } } diff --git a/src/Fallout.Common/Tools/Netlify/NetlifyTasks.cs b/src/Fallout.Common/Tools/Netlify/NetlifyTasks.cs index 0c2ee173c..956dbe2fe 100644 --- a/src/Fallout.Common/Tools/Netlify/NetlifyTasks.cs +++ b/src/Fallout.Common/Tools/Netlify/NetlifyTasks.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.Common.Tooling; diff --git a/src/Fallout.Common/Tools/Npm/NpmTasks.cs b/src/Fallout.Common/Tools/Npm/NpmTasks.cs index 942cba901..e607f833a 100644 --- a/src/Fallout.Common/Tools/Npm/NpmTasks.cs +++ b/src/Fallout.Common/Tools/Npm/NpmTasks.cs @@ -1,5 +1,4 @@ -using System; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; using Serilog.Events; namespace Fallout.Common.Tools.Npm; diff --git a/src/Fallout.Common/Tools/OctoVersion/OctoVersionAttribute.cs b/src/Fallout.Common/Tools/OctoVersion/OctoVersionAttribute.cs index 76ea94a44..6fe72ba82 100644 --- a/src/Fallout.Common/Tools/OctoVersion/OctoVersionAttribute.cs +++ b/src/Fallout.Common/Tools/OctoVersion/OctoVersionAttribute.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Reflection; using Fallout.Common.CI.AppVeyor; using Fallout.Common.CI.AzurePipelines; @@ -126,8 +125,10 @@ public override object GetValue(MemberInfo member, object instance) Assert.False(autoDetectBranch.HasValue && autoDetectBranch.Value && !branch.IsNullOrEmpty(), $"Branch cannot be specified via {nameof(Branch)} or {nameof(BranchMember)} properties when {nameof(AutoDetectBranch)} is enabled"); - Assert.True(autoDetectBranch.HasValue && autoDetectBranch.Value || !branch.IsNullOrEmpty(), + + Assert.True((autoDetectBranch.HasValue && autoDetectBranch.Value) || !branch.IsNullOrEmpty(), $"Branch must either be provided via {nameof(Branch)} or {nameof(BranchMember)} properties, or {nameof(AutoDetectBranch)} must be enabled"); + branch ??= GitRepository.FromLocalDirectory(Build.RootDirectory).Branch; var outputFile = FalloutBuild.TemporaryDirectory / $"octoversion.{Guid.NewGuid()}.json"; @@ -144,6 +145,7 @@ public override object GetValue(MemberInfo member, object instance) .SetMinor(minorVersion) .SetPatch(patchVersion)) .Result; + outputFile.DeleteFile(); if (UpdateBuildNumber) diff --git a/src/Fallout.Common/Tools/OctoVersion/OctoVersionTasks.cs b/src/Fallout.Common/Tools/OctoVersion/OctoVersionTasks.cs index e448d44d8..af4214464 100644 --- a/src/Fallout.Common/Tools/OctoVersion/OctoVersionTasks.cs +++ b/src/Fallout.Common/Tools/OctoVersion/OctoVersionTasks.cs @@ -15,7 +15,7 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< Assert.FileExists(getVersion.OutputJsonFile); try { - var file = (AbsolutePath) getVersion.OutputJsonFile; + var file = (AbsolutePath)getVersion.OutputJsonFile; // STJ deserializes records natively via constructor binding, so // the AllWritableContractResolver workaround Newtonsoft needed // is no longer required. @@ -23,7 +23,8 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< } catch (Exception exception) { - throw new Exception($"Cannot parse {nameof(OctoVersion)} output from {getVersion.OutputJsonFile.SingleQuote()}.", exception); + throw new Exception($"Cannot parse {nameof(OctoVersion)} output from {getVersion.OutputJsonFile.SingleQuote()}.", + exception); } } diff --git a/src/Fallout.Common/Tools/PowerShell/PowerShellTasks.cs b/src/Fallout.Common/Tools/PowerShell/PowerShellTasks.cs index b5ef996a1..530a53a30 100644 --- a/src/Fallout.Common/Tools/PowerShell/PowerShellTasks.cs +++ b/src/Fallout.Common/Tools/PowerShell/PowerShellTasks.cs @@ -1,5 +1,4 @@ -using System; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; namespace Fallout.Common.Tools.PowerShell; diff --git a/src/Fallout.Common/Tools/Pulumi/PulumiTasks.cs b/src/Fallout.Common/Tools/Pulumi/PulumiTasks.cs index 20978d81d..78ff784d1 100644 --- a/src/Fallout.Common/Tools/Pulumi/PulumiTasks.cs +++ b/src/Fallout.Common/Tools/Pulumi/PulumiTasks.cs @@ -1,5 +1,4 @@ -using System; -using Fallout.Common.Tooling; +using Fallout.Common.Tooling; using Serilog.Events; namespace Fallout.Common.Tools.Pulumi; diff --git a/src/Fallout.Common/Tools/SignPath/SignPathTasks.cs b/src/Fallout.Common/Tools/SignPath/SignPathTasks.cs index 477f96441..60bcbe6d2 100644 --- a/src/Fallout.Common/Tools/SignPath/SignPathTasks.cs +++ b/src/Fallout.Common/Tools/SignPath/SignPathTasks.cs @@ -29,6 +29,7 @@ private class SigningRequestStatus } public static string SignPathApiUrl => "https://app.signpath.io/api/v1"; + public static TimeSpan ServiceUnavailableRetryTimeoutInSeconds = TimeSpan.FromSeconds(3); public static TimeSpan DefaultHttpClientTimeout = TimeSpan.FromSeconds(15); public static TimeSpan UploadAndDownloadRequestTimeout = TimeSpan.FromSeconds(15); @@ -42,7 +43,8 @@ public static string GetSigningRequestUrl(string organizationId, string signingR private static string GetSignPathAppVeyorIntegrationUrl(string organizationId, string projectSlug, string signingPolicySlug) { - return $"{SignPathApiUrl}/{organizationId}/Integrations/AppVeyor?ProjectSlug={projectSlug}&SigningPolicySlug={signingPolicySlug}"; + return + $"{SignPathApiUrl}/{organizationId}/Integrations/AppVeyor?ProjectSlug={projectSlug}&SigningPolicySlug={signingPolicySlug}"; } public static async Task GetSigningRequestUrlViaAppVeyor( @@ -55,18 +57,19 @@ public static async Task GetSigningRequestUrlViaAppVeyor( { var contentType = "application/json"; var content = new - { - AppVeyor.Instance.AccountName, - AppVeyor.Instance.ProjectSlug, - AppVeyor.Instance.BuildVersion, - AppVeyor.Instance.BuildId, - AppVeyor.Instance.JobId - }; + { + AppVeyor.Instance.AccountName, + AppVeyor.Instance.ProjectSlug, + AppVeyor.Instance.BuildVersion, + AppVeyor.Instance.BuildId, + AppVeyor.Instance.JobId + }; using var httpClient = CreateAuthorizedHttpClient(authToken, DefaultHttpClientTimeout); var response = await httpClient.PostAsync( GetSignPathAppVeyorIntegrationUrl(organizationId, projectSlug, signingPolicySlug), new StringContent(content.ToJson(JsonExtensions.DefaultSerializerOptions), Encoding.UTF8, contentType)); + response.AssertStatusCode(HttpStatusCode.Created); Log.Information("Signing request created: {Url}", response.Headers.Location.AbsoluteUri.Replace("api/v1", "Web")); @@ -170,13 +173,13 @@ private static IDisposable SwitchSecurityProtocol() private static HttpClient CreateAuthorizedHttpClient(string apiToken, TimeSpan timeout) { return new HttpClient - { - Timeout = timeout, - DefaultRequestHeaders = - { - Authorization = new AuthenticationHeaderValue("Bearer", apiToken) - } - }; + { + Timeout = timeout, + DefaultRequestHeaders = + { + Authorization = new AuthenticationHeaderValue("Bearer", apiToken) + } + }; } private static HttpResponseMessage SendRequestWithRetry( @@ -193,12 +196,14 @@ private static HttpResponseMessage SendRequestWithRetry( }, delay: ServiceUnavailableRetryTimeoutInSeconds, logAction: Log.Debug); + return response; } private static HttpResponseMessage SendGetRequestWithRetry(HttpClient httpClient, string url) { - return SendRequestWithRetry(httpClient, () => new HttpRequestMessage(HttpMethod.Get, url), expectedStatusCode: HttpStatusCode.OK); + return SendRequestWithRetry(httpClient, () => new HttpRequestMessage(HttpMethod.Get, url), + expectedStatusCode: HttpStatusCode.OK); } private static string SubmitVia( @@ -226,19 +231,23 @@ HttpRequestMessage CreateHttpRequest() { var content = new MultipartFormDataContent(); var data = new[] - { - (nameof(artifactConfigurationId), artifactConfigurationId), - (nameof(signingPolicyId), signingPolicyId), - (nameof(projectSlug), projectSlug), - (nameof(artifactConfigurationSlug), artifactConfigurationSlug), - (nameof(signingPolicySlug), signingPolicySlug), - (nameof(description), description) - } + { + (nameof(artifactConfigurationId), artifactConfigurationId), + (nameof(signingPolicyId), signingPolicyId), + (nameof(projectSlug), projectSlug), + (nameof(artifactConfigurationSlug), artifactConfigurationSlug), + (nameof(signingPolicySlug), signingPolicySlug), + (nameof(description), description) + } .Where(x => x.Item2 != null).ToList(); + data.ForEach(x => content.Add(new StringContent(x.Item2), x.Item1)); content.Add(GetStreamContent(), "Artifact", artifactFile); - return new HttpRequestMessage(HttpMethod.Post, url) { Content = content }; + return new HttpRequestMessage(HttpMethod.Post, url) + { + Content = content + }; } var response = SendRequestWithRetry(httpClient, CreateHttpRequest, HttpStatusCode.Created); @@ -251,7 +260,8 @@ private static HttpResponseMessage AssertStatusCode(this HttpResponseMessage res { var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); var jobject = content.GetJsonObject(); - Assert.Fail($"[{response.StatusCode}] {jobject.GetChildren("").Select(x => x.GetValue()).JoinNewLine()}"); + Assert.Fail( + $"[{response.StatusCode}] {jobject.GetChildren("").Select(x => x.GetValue()).JoinNewLine()}"); } return response; diff --git a/src/Fallout.Common/Tools/Slack/SlackTasks.cs b/src/Fallout.Common/Tools/Slack/SlackTasks.cs index b47ae4b09..927a53e0f 100644 --- a/src/Fallout.Common/Tools/Slack/SlackTasks.cs +++ b/src/Fallout.Common/Tools/Slack/SlackTasks.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Net.Http; using System.Text.Json; using System.Text.Json.Serialization; @@ -14,7 +13,7 @@ namespace Fallout.Common.Tools.Slack; public static class SlackTasks { - private static HttpClient client = new(); + private static readonly HttpClient client = new(); public static void SendSlackMessage(Configure configurator, string webhook) { @@ -27,7 +26,10 @@ public static async Task SendSlackMessageAsync(Configure configura var payload = JsonSerializer.Serialize(message); var response = await client.CreateRequest(HttpMethod.Post, webhook) - .WithFormUrlEncodedContent(new Dictionary { ["payload"] = payload }) + .WithFormUrlEncodedContent(new Dictionary + { + ["payload"] = payload + }) .GetResponseAsync(); var responseText = await response.GetBodyAsync(); diff --git a/src/Fallout.Common/Tools/Teams/Teams.Extensions.cs b/src/Fallout.Common/Tools/Teams/Teams.Extensions.cs index ed17340f2..2068c50ff 100644 --- a/src/Fallout.Common/Tools/Teams/Teams.Extensions.cs +++ b/src/Fallout.Common/Tools/Teams/Teams.Extensions.cs @@ -6,6 +6,7 @@ public partial class TeamsMessage { [JsonPropertyName("@type")] internal string Type => "MessageCard"; + [JsonPropertyName("@context")] internal string Context => "http://schema.org/extensions"; } diff --git a/src/Fallout.Common/Tools/Teams/TeamsTasks.cs b/src/Fallout.Common/Tools/Teams/TeamsTasks.cs index 15869da35..5dc836a34 100644 --- a/src/Fallout.Common/Tools/Teams/TeamsTasks.cs +++ b/src/Fallout.Common/Tools/Teams/TeamsTasks.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; using Fallout.Common.Tooling; using Fallout.Common.Utilities.Net; diff --git a/src/Fallout.Common/Tools/Twitter/TwitterTasks.cs b/src/Fallout.Common/Tools/Twitter/TwitterTasks.cs index 9203a7dd8..a6c5451d4 100644 --- a/src/Fallout.Common/Tools/Twitter/TwitterTasks.cs +++ b/src/Fallout.Common/Tools/Twitter/TwitterTasks.cs @@ -51,6 +51,7 @@ public static async Task SendTweetAsync( { "oauth_token", accessToken }, { "oauth_version", "1.0" } }; + data.AddPair("oauth_signature", GetOAuthSignature(data, Url, consumerSecret, accessTokenSecret)); var authorization = GetOAuthHeader(data); @@ -65,15 +66,26 @@ public static async Task SendTweetAsync( Assert.True(response.StatusCode == HttpStatusCode.OK, $"StatusCode != 200 - '{GetErrorFromBody(responseBody)}'"); } - private static string GetOAuthSignature(Dictionary data, string url, string consumerSecret, string tokenSecret) + private static string GetOAuthSignature(Dictionary data, string url, string consumerSecret, + string tokenSecret) { - var signature = new[] { "POST", Uri.EscapeDataString(url) } + var signature = new[] + { + "POST", + Uri.EscapeDataString(url) + } .Concat(Uri.EscapeDataString(data .Select(x => $"{Uri.EscapeDataString(x.Key)}={Uri.EscapeDataString(x.Value)}") .OrderBy(x => x) .JoinAmpersand())) .JoinAmpersand(); - var cryptoKey = Encoding.ASCII.GetBytes(new[] { consumerSecret, tokenSecret }.JoinAmpersand()); + + var cryptoKey = Encoding.ASCII.GetBytes(new[] + { + consumerSecret, + tokenSecret + }.JoinAmpersand()); + var cryptoTransform = new HMACSHA1(cryptoKey); return Convert.ToBase64String(cryptoTransform.ComputeHash(Encoding.ASCII.GetBytes(signature))); } @@ -93,7 +105,9 @@ private static string GetErrorFromBody(string response) var jResponse = JsonNode.Parse(response).NotNull().AsObject(); var message = jResponse["errors"].NotNull().AsArray()[0].NotNull()["message"]?.GetValue(); if (!string.IsNullOrEmpty(message)) + { return message; + } } catch { diff --git a/src/Fallout.Common/Tools/Unity/Logging/BlockMatcher.cs b/src/Fallout.Common/Tools/Unity/Logging/BlockMatcher.cs index e7d6ab747..c551812f5 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/BlockMatcher.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/BlockMatcher.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace Fallout.Common.Tools.Unity.Logging; @@ -21,9 +19,13 @@ public BlockMatcher( } public string Name { get; } + public string Beginning { get; } + public string End { get; } + public MatchType BeginMatchType { get; } + public MatchType EndMatchType { get; } public MatchType MatchesEnd(string message) @@ -42,7 +44,10 @@ protected MatchedBlock NewMatchedBlock(Match match) { var name = Name; if (match.Groups.Count > 0) + { name += ": " + match.Groups[groupnum: 0]; + } + return new MatchedBlock(this, name, BeginMatchType); } } diff --git a/src/Fallout.Common/Tools/Unity/Logging/FileWatcher.cs b/src/Fallout.Common/Tools/Unity/Logging/FileWatcher.cs index 3daf8662e..7d93765c5 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/FileWatcher.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/FileWatcher.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Text; using System.Threading; @@ -27,11 +26,11 @@ public void Start() { logResetEvent = new AutoResetEvent(initialState: false); fileSystemWatcher = new FileSystemWatcher(Path.GetPathRoot(file).NotNull()) - { - Filter = Path.GetFileName(file), - EnableRaisingEvents = true, - NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite - }; + { + Filter = Path.GetFileName(file), + EnableRaisingEvents = true, + NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite + }; fileSystemWatcher.Changed += (_, _) => logResetEvent.Set(); @@ -49,9 +48,13 @@ public void AssertStopped() while (logReaderThread != null) { if (!logReaderThread.IsAlive) + { logReaderThread = null; + } else + { Thread.Sleep(millisecondsTimeout: 100); + } } } @@ -61,7 +64,10 @@ private void ReadLogFile() while (!File.Exists(file)) { if (cancellationTokenSource.IsCancellationRequested) + { return; + } + logResetEvent.WaitOne(millisecondsTimeout: 100); } @@ -81,11 +87,16 @@ private void ReadLogFile() currentLine = ""; } else + { currentLine += currentChar; + } } if (cancellationTokenSource.IsCancellationRequested) + { break; + } + logResetEvent.WaitOne(millisecondsTimeout: 100); } } diff --git a/src/Fallout.Common/Tools/Unity/Logging/LineMatcher.cs b/src/Fallout.Common/Tools/Unity/Logging/LineMatcher.cs index 7edfb586d..ed52d9535 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/LineMatcher.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/LineMatcher.cs @@ -1,12 +1,11 @@ -using System; -using System.Linq; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace Fallout.Common.Tools.Unity.Logging; internal class LineMatcher { public string RegexPattern { get; } + public LogLevel LogLevel { get; } public LineMatcher(string regexPattern, LogLevel logLevel) diff --git a/src/Fallout.Common/Tools/Unity/Logging/LogParser.cs b/src/Fallout.Common/Tools/Unity/Logging/LogParser.cs index b7b2981f8..17c86842c 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/LogParser.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/LogParser.cs @@ -14,7 +14,8 @@ internal class LogParser private readonly IReadOnlyList blockMatchers = new[] { - new BlockMatcher("Player statistics", "\\*\\*\\*Player size statistics\\*\\*\\*", "Unloading.*", endMatchType: MatchType.Exclusive), + new BlockMatcher("Player statistics", "\\*\\*\\*Player size statistics\\*\\*\\*", "Unloading.*", + endMatchType: MatchType.Exclusive), new BlockMatcher("Lightmap", "---- Lightmapping Start for (.*) ----", "---- Lightmapping End for (.*) ----"), new BlockMatcher("Compile", "-----Compiler Commandline Arguments:", "-----EndCompilerOutput---------------"), new BlockMatcher("Update", "Updating (.+) - GUID: .*", "\\s*done: hash - .+"), @@ -61,6 +62,7 @@ public void Log(string message) LogLine(message); LogBlockEnd(); return; + case MatchType.Exclusive: LogBlockEnd(); break; @@ -76,6 +78,7 @@ public void Log(string message) case MatchType.Inclusive: LogBlockStart(block); break; + case MatchType.Exclusive: LogLine(message); LogBlockStart(block); diff --git a/src/Fallout.Common/Tools/Unity/Logging/MatchType.cs b/src/Fallout.Common/Tools/Unity/Logging/MatchType.cs index 4997189a8..f785801ca 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/MatchType.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/MatchType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tools.Unity.Logging; +namespace Fallout.Common.Tools.Unity.Logging; internal enum MatchType { diff --git a/src/Fallout.Common/Tools/Unity/Logging/MatchedBlock.cs b/src/Fallout.Common/Tools/Unity/Logging/MatchedBlock.cs index 5b3c3a261..ed652bbfb 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/MatchedBlock.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/MatchedBlock.cs @@ -1,12 +1,11 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tools.Unity.Logging; +namespace Fallout.Common.Tools.Unity.Logging; internal class MatchedBlock { public BlockMatcher BlockMatcher { get; } + public string Name { get; } + public MatchType MatchType { get; } public MatchedBlock(BlockMatcher blockMatcher, string name, MatchType matchType) diff --git a/src/Fallout.Common/Tools/Unity/Logging/UnityLogLevel.cs b/src/Fallout.Common/Tools/Unity/Logging/UnityLogLevel.cs index cc470ada5..28006a110 100644 --- a/src/Fallout.Common/Tools/Unity/Logging/UnityLogLevel.cs +++ b/src/Fallout.Common/Tools/Unity/Logging/UnityLogLevel.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tools.Unity.Logging; +namespace Fallout.Common.Tools.Unity.Logging; public enum LogLevel { diff --git a/src/Fallout.Common/Tools/Unity/UnityTasks.cs b/src/Fallout.Common/Tools/Unity/UnityTasks.cs index f240b7008..970cbd241 100644 --- a/src/Fallout.Common/Tools/Unity/UnityTasks.cs +++ b/src/Fallout.Common/Tools/Unity/UnityTasks.cs @@ -27,8 +27,10 @@ protected override string GetToolPath(ToolOptions options = null) { var unityOptions = options as UnityOptionsBase; var programFiles = EnvironmentInfo.IsWin - ? EnvironmentInfo.SpecialFolder(EnvironmentInfo.Is32Bit ? SpecialFolders.ProgramFilesX86 : SpecialFolders.ProgramFiles) + ? EnvironmentInfo.SpecialFolder( + EnvironmentInfo.Is32Bit ? SpecialFolders.ProgramFilesX86 : SpecialFolders.ProgramFiles) : null; + return unityOptions?.HubVersion ?? GetEditorVersionFromProject() switch { { } version => EnvironmentInfo.Platform switch @@ -48,7 +50,9 @@ protected override string GetToolPath(ToolOptions options = null) string GetEditorVersionFromProject() { if (options is not UnitySettings projectOptions) + { return null; + } var projectVersionFile = (AbsolutePath)projectOptions.ProjectPath / "ProjectSettings" / "ProjectVersion.txt"; var properties = projectVersionFile.ReadYaml>(); @@ -74,7 +78,7 @@ protected override IReadOnlyCollection Run(T options = null) { try { - return base.Run(options); + return base.Run(options); } catch (Exception) { @@ -90,7 +94,9 @@ protected override Func GetExitHandler(ToolOption { AssertWatcherStopped(); if (p.ExitCode == 0) + { return null; + } var message = new StringBuilder() .AppendLine($"Process '{Path.GetFileName(p.FileName)}' exited with code {p.ExitCode}. Verify the invocation.") @@ -98,9 +104,13 @@ protected override Func GetExitHandler(ToolOption .ToString(); if (unityOptions?.StableExitCodes.Any(x => x == p.ExitCode) ?? false) + { Log.Warning(message); + } else + { Assert.Fail(message); + } return null; }; diff --git a/src/Fallout.Common/Tools/VSWhere/VSWhereTasks.cs b/src/Fallout.Common/Tools/VSWhere/VSWhereTasks.cs index 81dbc84ae..f4bb4121b 100644 --- a/src/Fallout.Common/Tools/VSWhere/VSWhereTasks.cs +++ b/src/Fallout.Common/Tools/VSWhere/VSWhereTasks.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.Common.Tooling; @@ -15,7 +14,9 @@ protected override object GetResult(ToolOptions options, IReadOnlyCollection< { if (options is not VSWhereSettings { UTF8: true, Property: null } vswhereOptions || !vswhereOptions.Format.Equals(VSWhereFormat.json)) + { return null; + } return output.EnsureOnlyStd().StdToJson().ToList(); } diff --git a/src/Fallout.Common/Tools/Xunit/Xunit2SettingsExtensions.cs b/src/Fallout.Common/Tools/Xunit/Xunit2SettingsExtensions.cs index f2adb7fa1..d363ac977 100644 --- a/src/Fallout.Common/Tools/Xunit/Xunit2SettingsExtensions.cs +++ b/src/Fallout.Common/Tools/Xunit/Xunit2SettingsExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Fallout.Common/Tools/Xunit/XunitTasks.cs b/src/Fallout.Common/Tools/Xunit/XunitTasks.cs index d04c738f9..9e0bca624 100644 --- a/src/Fallout.Common/Tools/Xunit/XunitTasks.cs +++ b/src/Fallout.Common/Tools/Xunit/XunitTasks.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.Tooling; namespace Fallout.Common.Tools.Xunit; @@ -20,9 +19,11 @@ protected override Func GetExitHandler(ToolOption { 0 => default, 1 => throw new Exception("One or more of the tests failed"), - 2 => throw new Exception("The help page was shown, either because it was requested, or because the user did not provide any command line arguments"), + 2 => throw new Exception( + "The help page was shown, either because it was requested, or because the user did not provide any command line arguments"), 3 => throw new Exception("There was a problem with one of the command line options passed to the runner"), - 4 => throw new Exception("There was a problem loading one or more of the test assemblies (for example, if a 64-bit only assembly is run with the 32-bit test runner)"), + 4 => throw new Exception( + "There was a problem loading one or more of the test assemblies (for example, if a 64-bit only assembly is run with the 32-bit test runner)"), _ => throw new NotSupportedException() }; } diff --git a/src/Fallout.Common/Utilities/TemplateUtility.cs b/src/Fallout.Common/Utilities/TemplateUtility.cs index 61af42723..c33bf54b0 100644 --- a/src/Fallout.Common/Utilities/TemplateUtility.cs +++ b/src/Fallout.Common/Utilities/TemplateUtility.cs @@ -13,7 +13,9 @@ public static void AddRegion(List content, string section, IEnumerable x.TrimStart().StartsWith(section)) + 1; foreach (var line in lines) + { content.Insert(index++, line); + } } public static ILookup ExtractAndRemoveRegions(List content, string beginPrefix, string endPrefix) @@ -23,7 +25,9 @@ public static ILookup ExtractAndRemoveRegions(List conte for (var i = 0; i < content.Count; i++) { if (!content[i].TrimStart().StartsWith(beginPrefix)) + { continue; + } var regionName = content[i].TrimStart().TrimStart(beginPrefix).TrimStart(); i++; @@ -70,26 +74,34 @@ private static void FillTemplateDirectoryRecursivelyInternal( Assert.DirectoryExists(directory); if (excludeDirectory != null && excludeDirectory(directory)) + { return; + } bool ShouldMove(AbsolutePath file) => tokens?.Keys.Any(x => file.Name.Contains(x)) ?? false; foreach (var file in directory.GetFiles()) { if (excludeFile != null && excludeFile(file)) + { continue; + } FillTemplateFile(file, tokens); if (ShouldMove(file)) + { file.Rename(file.Name.Replace(tokens), ExistsPolicy.FileOverwriteIfNewer); + } } directory.GetDirectories() .ForEach(x => FillTemplateDirectoryRecursivelyInternal(x, tokens, excludeDirectory, excludeFile)); if (ShouldMove(directory)) + { directory.Rename(directory.Name.Replace(tokens), ExistsPolicy.MergeAndOverwriteIfNewer); + } } public static void FillTemplateFile( @@ -128,7 +140,9 @@ private static string HandleLine( { var commentIndex = line.LastIndexOf(" // ", StringComparison.OrdinalIgnoreCase); if (!ShouldIncludeLine(line, commentIndex, tokens)) + { return null; + } return (commentIndex == -1 ? line @@ -139,16 +153,23 @@ private static string HandleLine( private static bool ShouldIncludeLine(string line, int commentIndex, IReadOnlyDictionary tokens) { if (commentIndex == -1) + { return true; + } var requiredTokensText = line[(commentIndex + 4)..].Replace(" ", string.Empty); - var requiredTokens = requiredTokensText.Split(new[] { "||", "&&" }, StringSplitOptions.RemoveEmptyEntries); + var requiredTokens = requiredTokensText.Split(new[] + { + "||", + "&&" + }, StringSplitOptions.RemoveEmptyEntries); + var orConjunction = requiredTokensText.Contains("||"); var andConjunction = requiredTokensText.Contains("&&"); Assert.False(orConjunction && andConjunction, "Conjunctions AND and OR can only be used mutually exclusively"); - return andConjunction && requiredTokens.All(tokens.ContainsKey) || - !andConjunction && requiredTokens.Any(tokens.ContainsKey); + return (andConjunction && requiredTokens.All(tokens.ContainsKey)) || + (!andConjunction && requiredTokens.Any(tokens.ContainsKey)); } private static void RemoveDoubleEmptyLines(IList lines) diff --git a/src/Fallout.Components/Configuration.cs b/src/Fallout.Components/Configuration.cs index f55201c79..c9434721f 100644 --- a/src/Fallout.Components/Configuration.cs +++ b/src/Fallout.Components/Configuration.cs @@ -1,6 +1,4 @@ -using System; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using Fallout.Common.Tooling; namespace Fallout.Components; @@ -8,8 +6,15 @@ namespace Fallout.Components; [TypeConverter(typeof(TypeConverter))] public class Configuration : Enumeration { - public static Configuration Debug = new() { Value = nameof(Debug) }; - public static Configuration Release = new() { Value = nameof(Release) }; + public static Configuration Debug = new() + { + Value = nameof(Debug) + }; + + public static Configuration Release = new() + { + Value = nameof(Release) + }; public static implicit operator string(Configuration configuration) { diff --git a/src/Fallout.Components/ICompile.cs b/src/Fallout.Components/ICompile.cs index 6bec3c673..d459d3549 100644 --- a/src/Fallout.Components/ICompile.cs +++ b/src/Fallout.Components/ICompile.cs @@ -1,12 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Fallout.Common; -using Fallout.Solutions; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Solutions; using static Fallout.Common.Tools.DotNet.DotNetTasks; namespace Fallout.Components; @@ -72,6 +70,7 @@ public interface ICompile : IRestore, IHasConfiguration .SetInformationalVersion(o.Versioning.AssemblyInformationalVersion)); Configure CompileSettings => _ => _; + Configure PublishSettings => _ => _; IEnumerable<(Project Project, string Framework)> PublishConfigurations diff --git a/src/Fallout.Components/ICreateGitHubRelease.cs b/src/Fallout.Components/ICreateGitHubRelease.cs index f730d0854..084660fa6 100644 --- a/src/Fallout.Components/ICreateGitHubRelease.cs +++ b/src/Fallout.Components/ICreateGitHubRelease.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -18,10 +17,14 @@ public interface ICreateGitHubRelease : IHasGitRepository, IHasChangelog { const string GitHubRelease = nameof(GitHubRelease); - [Parameter] [Secret] string GitHubToken => TryGetValue(() => GitHubToken) ?? GitHubActions.Instance?.Token; + [Parameter] + [Secret] + string GitHubToken => TryGetValue(() => GitHubToken) ?? GitHubActions.Instance?.Token; string Name { get; } + bool Prerelease => false; + bool Draft => false; IEnumerable AssetFiles { get; } @@ -44,7 +47,6 @@ async Task GetOrCreateRelease() Draft = Draft, Body = ChangelogTasks.ExtractChangelogSectionNotes(ChangelogFile).JoinNewLine() }); - } catch { @@ -63,11 +65,12 @@ async Task GetOrCreateRelease() { await using var assetFile = File.OpenRead(x); var asset = new ReleaseAssetUpload - { - FileName = x.Name, - ContentType = "application/octet-stream", - RawData = assetFile - }; + { + FileName = x.Name, + ContentType = "application/octet-stream", + RawData = assetFile + }; + await GitHubTasks.GitHubClient.Repository.Release.UploadAsset(release, asset); }).ToArray(); diff --git a/src/Fallout.Components/IGlobalTool.cs b/src/Fallout.Components/IGlobalTool.cs index 69447ffbd..a72472097 100644 --- a/src/Fallout.Components/IGlobalTool.cs +++ b/src/Fallout.Components/IGlobalTool.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common; using Fallout.Common.Tools.DotNet; using static Fallout.Common.Tools.DotNet.DotNetTasks; @@ -10,6 +8,7 @@ namespace Fallout.Components; public interface IGlobalTool : IFalloutBuild { string GlobalToolPackageName => Path.GetFileNameWithoutExtension(BuildProjectFile); + string GlobalToolVersion => "1.0.0"; Target PackGlobalTool => _ => _ diff --git a/src/Fallout.Components/IHasArtifacts.cs b/src/Fallout.Components/IHasArtifacts.cs index 2e76d86d7..ebcec6139 100644 --- a/src/Fallout.Components/IHasArtifacts.cs +++ b/src/Fallout.Components/IHasArtifacts.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.IO; namespace Fallout.Components; diff --git a/src/Fallout.Components/IHasChangelog.cs b/src/Fallout.Components/IHasChangelog.cs index 1aeff4d5b..a9c324d21 100644 --- a/src/Fallout.Components/IHasChangelog.cs +++ b/src/Fallout.Components/IHasChangelog.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using static Fallout.Common.ChangeLog.ChangelogTasks; namespace Fallout.Components; @@ -9,5 +7,6 @@ public interface IHasChangelog : IFalloutBuild { // TODO: assert file exists string ChangelogFile => RootDirectory / "CHANGELOG.md"; + string NuGetReleaseNotes => GetNuGetReleaseNotes(ChangelogFile, (this as IHasGitRepository)?.GitRepository); -} \ No newline at end of file +} diff --git a/src/Fallout.Components/IHasConfiguration.cs b/src/Fallout.Components/IHasConfiguration.cs index c39fcd3cf..2f095c5a8 100644 --- a/src/Fallout.Components/IHasConfiguration.cs +++ b/src/Fallout.Components/IHasConfiguration.cs @@ -1,11 +1,10 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; namespace Fallout.Components; public interface IHasConfiguration : IFalloutBuild { - [Parameter] Configuration Configuration => TryGetValue(() => Configuration) ?? - (IsLocalBuild ? Configuration.Debug : Configuration.Release); + [Parameter] + Configuration Configuration => TryGetValue(() => Configuration) ?? + (IsLocalBuild ? Configuration.Debug : Configuration.Release); } diff --git a/src/Fallout.Components/IHasGitRepository.cs b/src/Fallout.Components/IHasGitRepository.cs index f88dc751f..d2dac17a4 100644 --- a/src/Fallout.Components/IHasGitRepository.cs +++ b/src/Fallout.Components/IHasGitRepository.cs @@ -1,11 +1,11 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.Git; namespace Fallout.Components; public interface IHasGitRepository : IFalloutBuild { - [GitRepository] [Required] GitRepository GitRepository => TryGetValue(() => GitRepository); + [GitRepository] + [Required] + GitRepository GitRepository => TryGetValue(() => GitRepository); } diff --git a/src/Fallout.Components/IHasGitVersion.cs b/src/Fallout.Components/IHasGitVersion.cs index 7ae0e2e9e..e5850a02b 100644 --- a/src/Fallout.Components/IHasGitVersion.cs +++ b/src/Fallout.Components/IHasGitVersion.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.Tools.GitVersion; namespace Fallout.Components; diff --git a/src/Fallout.Components/IHasNerdbankGitVersioning.cs b/src/Fallout.Components/IHasNerdbankGitVersioning.cs index d3edcebfc..7fbac5bf5 100644 --- a/src/Fallout.Components/IHasNerdbankGitVersioning.cs +++ b/src/Fallout.Components/IHasNerdbankGitVersioning.cs @@ -1,11 +1,11 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.Tools.NerdbankGitVersioning; namespace Fallout.Components; public interface IHasNerdbankGitVersioning : IFalloutBuild { - [NerdbankGitVersioning] [Required] NerdbankGitVersioning Versioning => TryGetValue(() => Versioning); + [NerdbankGitVersioning] + [Required] + NerdbankGitVersioning Versioning => TryGetValue(() => Versioning); } diff --git a/src/Fallout.Components/IHasReports.cs b/src/Fallout.Components/IHasReports.cs index fe27dffc9..8002ace43 100644 --- a/src/Fallout.Components/IHasReports.cs +++ b/src/Fallout.Components/IHasReports.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.IO; +using Fallout.Common.IO; namespace Fallout.Components; diff --git a/src/Fallout.Components/IHasSolution.cs b/src/Fallout.Components/IHasSolution.cs index 8d5637319..bfd8fc4b2 100644 --- a/src/Fallout.Components/IHasSolution.cs +++ b/src/Fallout.Components/IHasSolution.cs @@ -1,11 +1,11 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Solutions; namespace Fallout.Components; public interface IHasSolution : IFalloutBuild { - [Solution] [Required] Solution Solution => TryGetValue(() => Solution); + [Solution] + [Required] + Solution Solution => TryGetValue(() => Solution); } diff --git a/src/Fallout.Components/IHasTwitterCredentials.cs b/src/Fallout.Components/IHasTwitterCredentials.cs index dd7f23f17..0990bb80d 100644 --- a/src/Fallout.Components/IHasTwitterCredentials.cs +++ b/src/Fallout.Components/IHasTwitterCredentials.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; namespace Fallout.Components; @@ -9,8 +7,19 @@ public interface IHasTwitterCredentials : IFalloutBuild { const string Twitter = nameof(Twitter); - [Parameter] [Secret] string ConsumerKey => TryGetValue(() => ConsumerKey); - [Parameter] [Secret] string ConsumerSecret => TryGetValue(() => ConsumerSecret); - [Parameter] [Secret] string AccessToken => TryGetValue(() => AccessToken); - [Parameter] [Secret] string AccessTokenSecret => TryGetValue(() => AccessTokenSecret); -} \ No newline at end of file + [Parameter] + [Secret] + string ConsumerKey => TryGetValue(() => ConsumerKey); + + [Parameter] + [Secret] + string ConsumerSecret => TryGetValue(() => ConsumerSecret); + + [Parameter] + [Secret] + string AccessToken => TryGetValue(() => AccessToken); + + [Parameter] + [Secret] + string AccessTokenSecret => TryGetValue(() => AccessTokenSecret); +} diff --git a/src/Fallout.Components/IPack.cs b/src/Fallout.Components/IPack.cs index 9b3a66a4b..fd0859e51 100644 --- a/src/Fallout.Components/IPack.cs +++ b/src/Fallout.Components/IPack.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; diff --git a/src/Fallout.Components/IPublish.cs b/src/Fallout.Components/IPublish.cs index 6984164bc..26e236b89 100644 --- a/src/Fallout.Components/IPublish.cs +++ b/src/Fallout.Components/IPublish.cs @@ -8,15 +8,19 @@ using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using Fallout.Common.Utilities; -using Fallout.Common.Utilities.Collections; +using Serilog; using static Fallout.Common.Tools.DotNet.DotNetTasks; namespace Fallout.Components; public interface IPublish : IPack, ITest { - [Parameter] string NuGetSource => TryGetValue(() => NuGetSource) ?? "https://api.nuget.org/v3/index.json"; - [Parameter] [Secret] string NuGetApiKey => TryGetValue(() => NuGetApiKey); + [Parameter] + string NuGetSource => TryGetValue(() => NuGetSource) ?? "https://api.nuget.org/v3/index.json"; + + [Parameter] + [Secret] + string NuGetApiKey => TryGetValue(() => NuGetApiKey); /// /// The channels this build publishes to (FALLOUT001). Override to fan a single @@ -26,7 +30,15 @@ public interface IPublish : IPack, ITest /// [Experimental("FALLOUT001")] IEnumerable PublishTargets => - new[] { new PublishTarget { Name = "default", Source = NuGetSource, ApiKey = NuGetApiKey } }; + new[] + { + new PublishTarget + { + Name = "default", + Source = NuGetSource, + ApiKey = NuGetApiKey + } + }; /// /// Names of the configured to push to this run (FALLOUT001). @@ -55,6 +67,7 @@ public interface IPublish : IPack, ITest IEnumerable PushPackageFiles => PackagesDirectory.GlobFiles("*.nupkg"); bool PushCompleteOnFailure => true; + int PushDegreeOfParallelism => 5; Target Publish => _ => _ @@ -90,11 +103,13 @@ public interface IPublish : IPack, ITest var routed = candidates.Where(x => target.Accepts(x.NameWithoutExtension)).ToList(); if (routed.Count == 0) { - Serilog.Log.Warning("Publish target {Target}: no packaged files matched its routing rules — skipping.", target.Name); + Log.Warning("Publish target {Target}: no packaged files matched its routing rules — skipping.", target.Name); continue; } - Serilog.Log.Information("Publish target {Target}: pushing {Count} package(s) → {Source}.", target.Name, routed.Count, target.Source); + Log.Information("Publish target {Target}: pushing {Count} package(s) → {Source}.", target.Name, routed.Count, + target.Source); + DotNetNuGetPush(_ => _ .SetSource(target.Source) .SetApiKey(target.ApiKey!) diff --git a/src/Fallout.Components/IReportCoverage.cs b/src/Fallout.Components/IReportCoverage.cs index 5023fc79c..c34a9a329 100644 --- a/src/Fallout.Components/IReportCoverage.cs +++ b/src/Fallout.Components/IReportCoverage.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; using System.Linq; using Fallout.Common; using Fallout.Common.CI.AzurePipelines; @@ -17,10 +16,15 @@ namespace Fallout.Components; public interface IReportCoverage : ITest, IHasReports, IHasGitRepository { bool CreateCoverageHtmlReport { get; } + bool ReportToCodecov { get; } - [Parameter] [Secret] string CodecovToken => TryGetValue(() => CodecovToken); + + [Parameter] + [Secret] + string CodecovToken => TryGetValue(() => CodecovToken); AbsolutePath CoverageReportDirectory => ReportDirectory / "coverage-report"; + AbsolutePath CoverageReportArchive => CoverageReportDirectory.WithExtension("zip"); Target ReportCoverage => _ => _ diff --git a/src/Fallout.Components/IRestore.cs b/src/Fallout.Components/IRestore.cs index 5819fe98a..a39352497 100644 --- a/src/Fallout.Components/IRestore.cs +++ b/src/Fallout.Components/IRestore.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common; +using Fallout.Common; using Fallout.Common.Tooling; using Fallout.Common.Tools.DotNet; using static Fallout.Common.Tools.DotNet.DotNetTasks; diff --git a/src/Fallout.Components/ISignPackages.cs b/src/Fallout.Components/ISignPackages.cs index 89e886546..86fb3bb91 100644 --- a/src/Fallout.Components/ISignPackages.cs +++ b/src/Fallout.Components/ISignPackages.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -50,16 +49,25 @@ record SignPathSettings( string ProjectSlug, string PolicySlug); - [Parameter] SignPathSettings Settings => TryGetValue(() => Settings); - [Parameter] [Secret] string ApiToken => TryGetValue(() => ApiToken); + [Parameter] + SignPathSettings Settings => TryGetValue(() => Settings); + + [Parameter] + [Secret] + string ApiToken => TryGetValue(() => ApiToken); AbsolutePath SignPathTemporaryDirectory => TemporaryDirectory / "signpath"; + AbsolutePath SignPathRequestDirectory => SignPathTemporaryDirectory / "signing-request"; + AbsolutePath SignPathResponseDirectory => SignPathTemporaryDirectory / "signing-response"; + AbsolutePath SignPathRequestArchive => SignPathRequestDirectory.WithExtension(".zip"); + AbsolutePath SignPathResponseArchive => SignPathResponseDirectory.WithExtension(".zip"); IEnumerable SignPathPackages { get; } + bool SignPathReplacePackages => true; private AppVeyor AppVeyor => AppVeyor.Instance; diff --git a/src/Fallout.Components/ITest.cs b/src/Fallout.Components/ITest.cs index 537beb206..2bfd07f9e 100644 --- a/src/Fallout.Components/ITest.cs +++ b/src/Fallout.Components/ITest.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using Fallout.Common; @@ -7,12 +6,12 @@ using Fallout.Common.CI.GitHubActions; using Fallout.Common.CI.TeamCity; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Tooling; using Fallout.Common.Tools.Coverlet; using Fallout.Common.Tools.DotNet; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Solutions; using static Fallout.Common.Tools.DotNet.DotNetTasks; namespace Fallout.Components; @@ -53,7 +52,10 @@ void ReportTestResults() AzurePipelines.Instance?.PublishTestResults( type: AzurePipelinesTestResultsType.VSTest, title: $"{Path.GetFileNameWithoutExtension(x)} ({AzurePipelines.Instance.StageDisplayName})", - files: new string[] { x })); + files: new string[] + { + x + })); } void ReportTestCount() @@ -107,6 +109,7 @@ IEnumerable GetOutcomes(AbsolutePath file) .SetCoverletOutput(TestResultDirectory / $"{v.Name}.xml")); Configure TestSettings => _ => _; + Configure TestProjectSettings => (_, v) => _; IEnumerable TestProjects { get; } diff --git a/src/Fallout.Components/PublishTarget.cs b/src/Fallout.Components/PublishTarget.cs index 77332a5f6..321a1856e 100644 --- a/src/Fallout.Components/PublishTarget.cs +++ b/src/Fallout.Components/PublishTarget.cs @@ -29,7 +29,10 @@ public sealed class PublishTarget public string? ApiKey { get; init; } /// Package-name globs (*, ?) this target accepts. Default: everything. - public IReadOnlyList IncludePackages { get; init; } = new[] { "*" }; + public IReadOnlyList IncludePackages { get; init; } = new[] + { + "*" + }; /// Package-name globs this target rejects. Exclusion wins over inclusion. public IReadOnlyList ExcludePackages { get; init; } = Array.Empty(); diff --git a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs b/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs index c250ccf71..882b7f974 100644 --- a/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs +++ b/src/Fallout.Core/Planning/Graph/StronglyConnectedComponentFinder.cs @@ -22,7 +22,9 @@ public StronglyConnectedComponentList DetectCycle(IEnumerable> grap foreach (var v in graph) { if (v.Index < 0) + { StrongConnect(v); + } } return stronglyConnectedComponents; @@ -49,7 +51,9 @@ private void StrongConnect(Vertex v) } if (v.LowLink != v.Index) + { return; + } var scc = new StronglyConnectedComponent(); Vertex w2; @@ -57,7 +61,8 @@ private void StrongConnect(Vertex v) { w2 = stack.Pop(); scc.Add(w2); - } while (v != w2); + } + while (v != w2); stronglyConnectedComponents.Add(scc); } diff --git a/src/Fallout.Core/Planning/TopoSort.cs b/src/Fallout.Core/Planning/TopoSort.cs index eb0299f2f..b06a3986b 100644 --- a/src/Fallout.Core/Planning/TopoSort.cs +++ b/src/Fallout.Core/Planning/TopoSort.cs @@ -78,7 +78,9 @@ public static PlanResult Order( foreach (var dependency in dependencies(node)) { if (vertexByNode.TryGetValue(dependency, out var dependencyVertex)) + { vertex.Dependencies.Add(dependencyVertex); + } } } @@ -87,8 +89,11 @@ public static PlanResult Order( .Cycles() .Select(scc => (IReadOnlyList)scc.Select(v => v.Value).ToList()) .ToList(); + if (cycles.Count > 0) + { return new PlanResult(Array.Empty(), cycles, Array.Empty()); + } // Peel roots — vertices nothing remaining depends on — one at a time. On an acyclic graph // this always terminates with at least one root per step. @@ -99,7 +104,9 @@ public static PlanResult Order( { var independents = graph.Where(x => !graph.Any(y => y.Dependencies.Contains(x))).ToList(); if (strict && ambiguousStep.Count == 0 && independents.Count > 1) + { ambiguousStep = independents.Select(x => x.Value).ToList(); + } var independent = independents.First(); graph.Remove(independent); diff --git a/src/Fallout.MSBuildTasks/CodeGenerationTask.cs b/src/Fallout.MSBuildTasks/CodeGenerationTask.cs index 042b73cda..1d6783c47 100644 --- a/src/Fallout.MSBuildTasks/CodeGenerationTask.cs +++ b/src/Fallout.MSBuildTasks/CodeGenerationTask.cs @@ -1,10 +1,9 @@ -using System; using System.Linq; -using Microsoft.Build.Framework; using Fallout.CodeGeneration; using Fallout.CodeGeneration.Model; using Fallout.Common.IO; using Fallout.Common.Utilities.Collections; +using Microsoft.Build.Framework; namespace Fallout.MSBuildTasks; @@ -27,7 +26,7 @@ protected override bool ExecuteInner() var specificationFiles = SpecificationFiles.Select(x => x.GetMetadata("Fullpath")).ToList(); string GetFilePath(Tool tool) - => (AbsolutePath) BaseDirectory + => (AbsolutePath)BaseDirectory / (UseNestedNamespaces ? tool.Name : ".") / tool.DefaultOutputFileName; @@ -43,7 +42,9 @@ string GetNamespace(Tool tool) .ForEach(x => CodeGenerator.GenerateCode(x, GetFilePath, GetNamespace)); if (UpdateReferences) + { ReferenceUpdater.UpdateReferences(specificationFiles); + } return true; } diff --git a/src/Fallout.MSBuildTasks/ContextAwareTask.cs b/src/Fallout.MSBuildTasks/ContextAwareTask.cs index a3dfb9f4d..aa9221a65 100644 --- a/src/Fallout.MSBuildTasks/ContextAwareTask.cs +++ b/src/Fallout.MSBuildTasks/ContextAwareTask.cs @@ -2,9 +2,9 @@ using System.IO; using System.Linq; using System.Reflection; +using Fallout.Common; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using Fallout.Common; #if NETCOREAPP using System.Runtime.Loader; using Fallout.Common.Utilities; @@ -18,7 +18,8 @@ public abstract class ContextAwareTask : Task private const string Subcategory = "Build"; - protected virtual string ManagedDllDirectory => Path.GetDirectoryName(new Uri(GetType().GetTypeInfo().Assembly.Location).LocalPath); + protected virtual string ManagedDllDirectory => + Path.GetDirectoryName(new Uri(GetType().GetTypeInfo().Assembly.Location).LocalPath); protected virtual string UnmanagedDllDirectory => null; @@ -37,10 +38,15 @@ public sealed override bool Execute() from outerProperty in outerProperties.Values where outerProperty.SetMethod != null && outerProperty.GetMethod != null let innerProperty = innerProperties[outerProperty.Name] - select new { outerProperty, innerProperty }; + select new + { + outerProperty, + innerProperty + }; var propertiesMap = propertiesDiscovery.ToArray(); - var outputPropertiesMap = propertiesMap.Where(pair => pair.outerProperty.GetCustomAttribute() != null).ToArray(); + var outputPropertiesMap = propertiesMap.Where(pair => pair.outerProperty.GetCustomAttribute() != null) + .ToArray(); foreach (var propertyPair in propertiesMap) { @@ -51,7 +57,9 @@ from outerProperty in outerProperties.Values var result = innerTaskType.InvokeMember(nameof(ExecuteInner), innerTask); foreach (var propertyPair in outputPropertiesMap) + { propertyPair.outerProperty.SetValue(this, propertyPair.innerProperty.GetValue(innerTask)); + } return result; #else diff --git a/src/Fallout.MSBuildTasks/EmbedPackagesForSelfContainedTask.cs b/src/Fallout.MSBuildTasks/EmbedPackagesForSelfContainedTask.cs index 6304e11e5..4f9736ef6 100644 --- a/src/Fallout.MSBuildTasks/EmbedPackagesForSelfContainedTask.cs +++ b/src/Fallout.MSBuildTasks/EmbedPackagesForSelfContainedTask.cs @@ -1,10 +1,9 @@ -using System; -using System.IO; +using System.IO; using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; using Fallout.Common.Tooling; using Fallout.Common.Utilities; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; namespace Fallout.MSBuildTasks; @@ -26,6 +25,7 @@ protected override bool ExecuteInner() .Where(x => !x.Id.StartsWithOrdinalIgnoreCase("microsoft.netcore.app.runtime")) .Where(x => Directory.GetDirectories(x.Directory, "tools").Any()) .Select(x => new TaskItem(x.File)).ToArray(); + return true; } } diff --git a/src/Fallout.MSBuildTasks/PackPackageToolsTask.cs b/src/Fallout.MSBuildTasks/PackPackageToolsTask.cs index 4de298bed..ea9e4a541 100644 --- a/src/Fallout.MSBuildTasks/PackPackageToolsTask.cs +++ b/src/Fallout.MSBuildTasks/PackPackageToolsTask.cs @@ -1,10 +1,9 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Fallout.Common.Tooling; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -using Fallout.Common.Tooling; using static Fallout.Common.IO.PathConstruction; namespace Fallout.MSBuildTasks; @@ -32,9 +31,13 @@ protected override bool ExecuteInner() private IEnumerable GetFiles(string packageId, string packageVersion) { - var packageToolsDirectory = Path.Combine(NuGetPackageRoot, packageId.ToLowerInvariant(), packageVersion.ToLowerInvariant(), "tools"); + var packageToolsDirectory = Path.Combine(NuGetPackageRoot, packageId.ToLowerInvariant(), + packageVersion.ToLowerInvariant(), "tools"); + if (!Directory.Exists(packageToolsDirectory)) + { yield break; + } foreach (var file in Directory.GetFiles(packageToolsDirectory, "*", SearchOption.AllDirectories)) { @@ -47,6 +50,7 @@ private IEnumerable GetFiles(string packageId, string packageVersion) "any", packageId, GetRelativePath(packageToolsDirectory, file))); + yield return taskItem; } } diff --git a/src/Fallout.MSBuildTasks/TaskItemExtensions.cs b/src/Fallout.MSBuildTasks/TaskItemExtensions.cs index 9ec6b435f..2d792a7b2 100644 --- a/src/Fallout.MSBuildTasks/TaskItemExtensions.cs +++ b/src/Fallout.MSBuildTasks/TaskItemExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Microsoft.Build.Framework; diff --git a/src/Fallout.Migrate.Analyzers/NukeMigrationAnalyzer.cs b/src/Fallout.Migrate.Analyzers/NukeMigrationAnalyzer.cs index e90066b51..0708458c8 100644 --- a/src/Fallout.Migrate.Analyzers/NukeMigrationAnalyzer.cs +++ b/src/Fallout.Migrate.Analyzers/NukeMigrationAnalyzer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -16,7 +17,8 @@ public sealed class NukeMigrationAnalyzer : DiagnosticAnalyzer category: "Migration", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "The Nuke.* namespaces and bare types NukeBuild/INukeBuild are provided by the transition shim only. Long-term consumers should reference Fallout.* directly.", + description: + "The Nuke.* namespaces and bare types NukeBuild/INukeBuild are provided by the transition shim only. Long-term consumers should reference Fallout.* directly.", helpLinkUri: "https://github.com/Fallout-build/Fallout/blob/main/docs/Migration/from-nuke.md"); public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); @@ -33,7 +35,7 @@ public override void Initialize(AnalysisContext context) var referencesFallout = false; foreach (var assemblyName in startContext.Compilation.ReferencedAssemblyNames) { - if (assemblyName.Name.StartsWith("Fallout.", System.StringComparison.Ordinal)) + if (assemblyName.Name.StartsWith("Fallout.", StringComparison.Ordinal)) { referencesFallout = true; break; @@ -41,7 +43,9 @@ public override void Initialize(AnalysisContext context) } if (!referencesFallout) + { return; + } startContext.RegisterSyntaxNodeAction(AnalyzeUsing, SyntaxKind.UsingDirective); startContext.RegisterSyntaxNodeAction(AnalyzeIdentifier, SyntaxKind.IdentifierName); @@ -52,11 +56,15 @@ private static void AnalyzeUsing(SyntaxNodeAnalysisContext context) { var usingDirective = (UsingDirectiveSyntax)context.Node; if (usingDirective.Name is null) + { return; + } var nameText = usingDirective.Name.ToString(); - if (!nameText.StartsWith("Nuke.", System.StringComparison.Ordinal) && nameText != "Nuke") + if (!nameText.StartsWith("Nuke.", StringComparison.Ordinal) && nameText != "Nuke") + { return; + } context.ReportDiagnostic(Diagnostic.Create(Rule, usingDirective.Name.GetLocation(), nameText)); } @@ -69,16 +77,25 @@ private static void AnalyzeIdentifier(SyntaxNodeAnalysisContext context) // Skip identifiers that aren't the leftmost in a qualified chain — we want // one diagnostic per offending chain, not one per identifier in it. if (identifier.Parent is QualifiedNameSyntax q && q.Right == identifier) + { return; + } + if (identifier.Parent is MemberAccessExpressionSyntax m && m.Name == identifier) + { return; + } // Skip the Name of a using directive — AnalyzeUsing already handled it. if (FindAncestorUsingDirective(identifier) is { } parentUsing && parentUsing.Name?.Span.Contains(identifier.Span) == true) + { return; + } if (name != "Nuke" && name != "NukeBuild" && name != "INukeBuild") + { return; + } var outermost = WalkUpQualified(identifier); context.ReportDiagnostic(Diagnostic.Create(Rule, outermost.GetLocation(), outermost.ToString())); @@ -89,11 +106,17 @@ private static SyntaxNode WalkUpQualified(SyntaxNode node) while (true) { if (node.Parent is QualifiedNameSyntax q && q.Left == node) + { node = q; + } else if (node.Parent is MemberAccessExpressionSyntax m && m.Expression == node) + { node = m; + } else + { return node; + } } } @@ -102,11 +125,17 @@ private static SyntaxNode WalkUpQualified(SyntaxNode node) for (var current = node.Parent; current is not null; current = current.Parent) { if (current is UsingDirectiveSyntax u) + { return u; + } + // Stop at any statement / member boundary — usings are top-level only. if (current is MemberDeclarationSyntax or StatementSyntax) + { return null; + } } + return null; } } diff --git a/src/Fallout.Migrate.Analyzers/NukeMigrationCodeFix.cs b/src/Fallout.Migrate.Analyzers/NukeMigrationCodeFix.cs index d3a0758e7..b62f7d855 100644 --- a/src/Fallout.Migrate.Analyzers/NukeMigrationCodeFix.cs +++ b/src/Fallout.Migrate.Analyzers/NukeMigrationCodeFix.cs @@ -23,13 +23,17 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); if (root is null) + { return; + } foreach (var diagnostic in context.Diagnostics) { var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); if (node is null) + { continue; + } context.RegisterCodeFix( CodeAction.Create( @@ -40,11 +44,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } } - private static async Task ApplyFixAsync(Document document, SyntaxNode offendingNode, CancellationToken cancellationToken) + private static async Task ApplyFixAsync(Document document, SyntaxNode offendingNode, + CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (root is null) + { return document; + } var rewriter = new NukeToFalloutRewriter(); var rewritten = rewriter.Visit(offendingNode); @@ -65,7 +72,9 @@ private sealed class NukeToFalloutRewriter : CSharpSyntaxRewriter }; if (replacement is null) + { return base.VisitIdentifierName(node); + } return node.WithIdentifier(SyntaxFactory.Identifier( node.Identifier.LeadingTrivia, diff --git a/src/Fallout.Migrate/Steps/CleanupBootstrapScriptsStep.cs b/src/Fallout.Migrate/Steps/CleanupBootstrapScriptsStep.cs index d29216c8b..a3fc79bdc 100644 --- a/src/Fallout.Migrate/Steps/CleanupBootstrapScriptsStep.cs +++ b/src/Fallout.Migrate/Steps/CleanupBootstrapScriptsStep.cs @@ -36,7 +36,7 @@ private static RewriteResult Cleanup(string content) var envVarToCheck = "NUKE_ENTERPRISE_TOKEN"; if (!content.Contains(envVarToCheck)) { - return new(content, 0); + return new RewriteResult(content, 0); } // This is just to preserve the current line ending the user has for this files @@ -49,7 +49,7 @@ private static RewriteResult Cleanup(string content) // here, we get the index of the line that contains the environment variable check var indexOfEnterpriseEnvVarCheck = lines.FindIndex(line => line.Contains(envVarToCheck)); - + // here, we get the index of the line that ends the if block // which is fi on bash and a simple "}" in powershell var endOfIfBlock = lines.FindIndex(indexOfEnterpriseEnvVarCheck, @@ -63,7 +63,7 @@ private static RewriteResult Cleanup(string content) lines.RemoveRange(indexOfEnterpriseEnvVarCheck, endOfIfBlock - indexOfEnterpriseEnvVarCheck + 1); - return new(string.Join(newline, lines), 1); + return new RewriteResult(string.Join(newline, lines), 1); } [GeneratedRegex(@"\r\n|\n|\r")] diff --git a/src/Fallout.Migrate/Steps/ResolveFalloutVersionStep.cs b/src/Fallout.Migrate/Steps/ResolveFalloutVersionStep.cs index 51d5c2c3d..65248c234 100644 --- a/src/Fallout.Migrate/Steps/ResolveFalloutVersionStep.cs +++ b/src/Fallout.Migrate/Steps/ResolveFalloutVersionStep.cs @@ -171,7 +171,11 @@ private static async Task ResolveFromNuGetAsync(int major) /// A configured . private static HttpClient CreateHttpClient() { - HttpClient client = new() { Timeout = TimeSpan.FromSeconds(3) }; + HttpClient client = new() + { + Timeout = TimeSpan.FromSeconds(3) + }; + client.DefaultRequestHeaders.UserAgent.ParseAdd("fallout-migrate"); return client; diff --git a/src/Fallout.Migrate/Steps/RewriteCsprojsStep.cs b/src/Fallout.Migrate/Steps/RewriteCsprojsStep.cs index bda0d3f54..c69d15e77 100644 --- a/src/Fallout.Migrate/Steps/RewriteCsprojsStep.cs +++ b/src/Fallout.Migrate/Steps/RewriteCsprojsStep.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -152,10 +153,13 @@ private static RewriteResult HandleMsBuildVariable(string falloutVersion, string var (variablesToBump, ambiguousVariables) = ClassifyPackageReferenceVariables(content, falloutVersionVariable); - (content, int redirectEdits) = RedirectAmbiguousVariablesToFalloutVersion(content, ambiguousVariables, falloutVersionVariable); + (content, int redirectEdits) = + RedirectAmbiguousVariablesToFalloutVersion(content, ambiguousVariables, falloutVersionVariable); + edits += redirectEdits; - content = EnsureFalloutVersionPropertyExists(content, ambiguousVariables, falloutVersionVariable, falloutVersion, ref edits); + content = EnsureFalloutVersionPropertyExists(content, ambiguousVariables, falloutVersionVariable, falloutVersion, + ref edits); (content, int bumpEdits) = BumpVariableProperties(content, variablesToBump, falloutVersion); edits += bumpEdits; @@ -173,7 +177,11 @@ private static (HashSet variablesToBump, HashSet ambiguousVariab .Select(m => m.Groups["variable"].Value) .ToHashSet(); - var variablesToBump = new HashSet { falloutVersionVariable }; + var variablesToBump = new HashSet + { + falloutVersionVariable + }; + var ambiguousVariables = new HashSet(); foreach (var variable in falloutPackageReferenceVariablePattern.Matches(content) @@ -197,6 +205,7 @@ private static (string content, int edits) RedirectAmbiguousVariablesToFalloutVe // unchanged while just swapping the variable reference. var redirectPattern = new Regex( $@"(]*?Version="")\$\({Regex.Escape(variable)}\)"); + content = redirectPattern.Replace(content, m => { edits++; @@ -215,7 +224,7 @@ private static string EnsureFalloutVersionPropertyExists( return content; } - var propertyGroupIndex = content.IndexOf("", System.StringComparison.Ordinal); + var propertyGroupIndex = content.IndexOf("", StringComparison.Ordinal); if (propertyGroupIndex >= 0) { edits++; @@ -227,10 +236,11 @@ private static string EnsureFalloutVersionPropertyExists( // No PropertyGroup exists at all (e.g. a project relying solely on Directory.Build.props // for properties) — synthesize one right after the opening tag so the newly // introduced $(FalloutVersion) reference has somewhere to resolve from. - var projectTagStart = content.IndexOf("= 0 ? content.IndexOf('>', projectTagStart) : -1; + if (projectTagEnd < 0) { return content; @@ -242,7 +252,8 @@ private static string EnsureFalloutVersionPropertyExists( $"\n \n <{falloutVersionVariable}>{falloutVersion}\n "); } - private static (string content, int edits) BumpVariableProperties(string content, HashSet variablesToBump, string falloutVersion) + private static (string content, int edits) BumpVariableProperties(string content, HashSet variablesToBump, + string falloutVersion) { var edits = 0; diff --git a/src/Fallout.Migrate/Steps/VerifyBuildTargetFrameworkStep.cs b/src/Fallout.Migrate/Steps/VerifyBuildTargetFrameworkStep.cs index 961d5eca1..3a0dc75a9 100644 --- a/src/Fallout.Migrate/Steps/VerifyBuildTargetFrameworkStep.cs +++ b/src/Fallout.Migrate/Steps/VerifyBuildTargetFrameworkStep.cs @@ -40,8 +40,10 @@ public Task ExecuteAsync(MigrationContext context, Summary summary) .ToArray(); string[] outdated = monikers - .Where(moniker => TargetFrameworkMonikers.IsOlderThanMinimumSupported(moniker, BumpDotNetVersionStep.MinimumSupportedMajor)) + .Where(moniker => + TargetFrameworkMonikers.IsOlderThanMinimumSupported(moniker, BumpDotNetVersionStep.MinimumSupportedMajor)) .ToArray(); + if (outdated.Length == 0) { continue; diff --git a/src/Fallout.ProjectModel/Project.GetMSBuildProject.cs b/src/Fallout.ProjectModel/Project.GetMSBuildProject.cs index f37b8b3a4..a66c550be 100644 --- a/src/Fallout.ProjectModel/Project.GetMSBuildProject.cs +++ b/src/Fallout.ProjectModel/Project.GetMSBuildProject.cs @@ -1,5 +1,3 @@ -using Fallout.Common; - namespace Fallout.Solutions; public static partial class ProjectExtensions diff --git a/src/Fallout.ProjectModel/Project.Items.cs b/src/Fallout.ProjectModel/Project.Items.cs index faf415cb5..736f52d0b 100644 --- a/src/Fallout.ProjectModel/Project.Items.cs +++ b/src/Fallout.ProjectModel/Project.Items.cs @@ -2,8 +2,6 @@ using System.Linq; using Fallout.Common.Utilities; -using Fallout.Common; - namespace Fallout.Solutions; public static partial class ProjectExtensions @@ -50,13 +48,15 @@ public static IEnumerable GetItemMetadata(this Project project, string ite return project.GetItemMetadata(itemGroupName, metadataName).Select(ReflectionUtility.Convert); } - public static string GetItemMetadataSingleOrDefault(this Project project, string itemGroupName, string includeName, string metadataName) + public static string GetItemMetadataSingleOrDefault(this Project project, string itemGroupName, string includeName, + string metadataName) { var items = project.GetMSBuildProject().GetItems(itemGroupName); return items.SingleOrDefault(x => x.EvaluatedInclude == includeName)?.GetMetadataValue(metadataName); } - public static T GetItemMetadataSingleOrDefault(this Project project, string itemGroupName, string includeName, string metadataName) + public static T GetItemMetadataSingleOrDefault(this Project project, string itemGroupName, string includeName, + string metadataName) { return ReflectionUtility.Convert(project.GetItemMetadataSingleOrDefault(itemGroupName, includeName, metadataName)); } diff --git a/src/Fallout.ProjectModel/Project.Misc.cs b/src/Fallout.ProjectModel/Project.Misc.cs index 8a834a3ed..6c0d98bfa 100644 --- a/src/Fallout.ProjectModel/Project.Misc.cs +++ b/src/Fallout.ProjectModel/Project.Misc.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Fallout.Common; - namespace Fallout.Solutions; public static partial class ProjectExtensions @@ -67,7 +64,9 @@ private static IReadOnlyCollection GetSplittedPropertyValue( { var property = msbuildProject.GetProperty(name); if (property != null) + { return property.EvaluatedValue.Split(';'); + } } return null; diff --git a/src/Fallout.ProjectModel/Project.Properties.cs b/src/Fallout.ProjectModel/Project.Properties.cs index 9132e5d61..a82222178 100644 --- a/src/Fallout.ProjectModel/Project.Properties.cs +++ b/src/Fallout.ProjectModel/Project.Properties.cs @@ -1,7 +1,5 @@ using Fallout.Common.Utilities; -using Fallout.Common; - namespace Fallout.Solutions; public static partial class ProjectExtensions @@ -26,4 +24,4 @@ public static T GetProperty(this Project project, string propertyName) { return ReflectionUtility.Convert(project.GetProperty(propertyName)); } -} \ No newline at end of file +} diff --git a/src/Fallout.ProjectModel/ProjectModelTasks.cs b/src/Fallout.ProjectModel/ProjectModelTasks.cs index 65e604ae3..8cb68290e 100644 --- a/src/Fallout.ProjectModel/ProjectModelTasks.cs +++ b/src/Fallout.ProjectModel/ProjectModelTasks.cs @@ -2,15 +2,16 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using Microsoft.Build.Evaluation; -using Microsoft.Build.Locator; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Utilities; +using Microsoft.Build.Construction; +using Microsoft.Build.Definition; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Locator; using Serilog; -#pragma warning disable CA2255 -using Fallout.Common; +#pragma warning disable CA2255 namespace Fallout.Solutions; @@ -22,7 +23,9 @@ public static class ProjectModelTasks public static void Initialize() { if (!MSBuildLocator.CanRegister) + { return; + } var msbuildExtensionPath = Environment.GetEnvironmentVariable("MSBuildExtensionsPath"); var msbuildExePath = Environment.GetEnvironmentVariable("MSBUILD_EXE_PATH"); @@ -65,7 +68,9 @@ static void TriggerAssemblyResolution() } catch (Exception) { - Log.Warning($"Attempting second-chance registration of MSBuild after {nameof(MSBuildLocator.RegisterDefaults)} failed"); + Log.Warning( + $"Attempting second-chance registration of MSBuild after {nameof(MSBuildLocator.RegisterDefaults)} failed"); + RegisterMSBuildFromDotNet(); } @@ -89,9 +94,9 @@ public static Microsoft.Build.Evaluation.Project ParseProject( string targetFramework = null) { var projectCollection = new ProjectCollection(); - var projectRoot = Microsoft.Build.Construction.ProjectRootElement.Open(projectFile, projectCollection, preserveFormatting: true); + var projectRoot = ProjectRootElement.Open(projectFile, projectCollection, preserveFormatting: true); var msbuildProject = Microsoft.Build.Evaluation.Project.FromProjectRootElement(projectRoot, - new Microsoft.Build.Definition.ProjectOptions + new ProjectOptions { GlobalProperties = GetProperties(configuration, targetFramework), ToolsVersion = projectCollection.DefaultToolsVersion, @@ -108,7 +113,9 @@ public static Microsoft.Build.Evaluation.Project ParseProject( projectCollection.UnloadProject(msbuildProject); targetFramework = targetFrameworks.First(); - Log.Warning("Project {Project} has multiple target frameworks {TargetFrameworks}", projectFile, targetFrameworks.JoinCommaSpace()); + Log.Warning("Project {Project} has multiple target frameworks {TargetFrameworks}", projectFile, + targetFrameworks.JoinCommaSpace()); + Log.Warning("Evaluating using {TargetFramework} ...", targetFramework); msbuildProject = new Microsoft.Build.Evaluation.Project( @@ -125,9 +132,15 @@ private static Dictionary GetProperties(string configuration, st { var properties = new Dictionary(); if (configuration != null) + { properties.Add("Configuration", configuration); + } + if (targetFramework != null) + { properties.Add("TargetFramework", targetFramework); + } + return properties; } } diff --git a/src/Fallout.SourceGenerators/CodeAnalysisExtensions.cs b/src/Fallout.SourceGenerators/CodeAnalysisExtensions.cs index a00c38fff..40ec884bf 100644 --- a/src/Fallout.SourceGenerators/CodeAnalysisExtensions.cs +++ b/src/Fallout.SourceGenerators/CodeAnalysisExtensions.cs @@ -1,8 +1,7 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis; using Fallout.Common.Utilities.Collections; +using Microsoft.CodeAnalysis; namespace Fallout.SourceGenerators; diff --git a/src/Fallout.SourceGenerators/Shims/FalloutBuild.cs b/src/Fallout.SourceGenerators/Shims/FalloutBuild.cs index a556028f4..264b8d2ff 100644 --- a/src/Fallout.SourceGenerators/Shims/FalloutBuild.cs +++ b/src/Fallout.SourceGenerators/Shims/FalloutBuild.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common; +namespace Fallout.Common; internal static class FalloutBuild { diff --git a/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs b/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs index 38ee627cf..3e4137797 100644 --- a/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs +++ b/src/Fallout.SourceGenerators/StronglyTypedSolutionGenerator.cs @@ -1,19 +1,15 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json.Nodes; using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Fallout.Common; using Fallout.Common.IO; -using Fallout.Solutions; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using Fallout.Solutions; +using Microsoft.CodeAnalysis; using Scriban; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Fallout.SourceGenerators; @@ -38,7 +34,9 @@ public void Execute(GeneratorExecutionContext context) .ToList(); if (members.Count == 0) + { return; + } var rootDirectory = GetRootDirectoryFrom(context.Compilation); @@ -137,7 +135,9 @@ string GetEscapedName(string name) _ => $"{GetDelimiterChar()}"); if (Regex.IsMatch(name, @"^[0-9]")) + { return $"{GetDelimiterChar()}{name}"; + } return name; } diff --git a/src/Fallout.SourceGenerators/TransitionShimGenerator.cs b/src/Fallout.SourceGenerators/TransitionShimGenerator.cs index 0736dac58..ccaf02d94 100644 --- a/src/Fallout.SourceGenerators/TransitionShimGenerator.cs +++ b/src/Fallout.SourceGenerators/TransitionShimGenerator.cs @@ -33,11 +33,13 @@ public sealed class TransitionShimGenerator : IIncrementalGenerator private static readonly DiagnosticDescriptor ActionableSkipRule = new( id: "SHIM001", title: "Transition-shim type skipped (actionable)", - messageFormat: "Type '{0}' was skipped by the transition-shim generator (kind: {1}). Consumers relying on this via the Nuke.* shim need a hand-written bridge, a canonical-side fix (un-seal / widen ctor visibility), or to migrate via 'fallout-migrate'.", + messageFormat: + "Type '{0}' was skipped by the transition-shim generator (kind: {1}). Consumers relying on this via the Nuke.* shim need a hand-written bridge, a canonical-side fix (un-seal / widen ctor visibility), or to migrate via 'fallout-migrate'.", category: "Migration", defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Covers skip kinds where an action is available: sealed-class (un-seal canonical or hand-bridge), no-accessible-ctor (widen ctor visibility, hand-bridge static surface, or fallout-migrate), and ambiguous-across-assemblies (dedup at canonical or fallout-migrate)."); + description: + "Covers skip kinds where an action is available: sealed-class (un-seal canonical or hand-bridge), no-accessible-ctor (widen ctor visibility, hand-bridge static surface, or fallout-migrate), and ambiguous-across-assemblies (dedup at canonical or fallout-migrate)."); // SHIM002: language-limit skips. C# can't subclass these kinds — they're // intentionally not bridged and are documented fallout-migrate territory. @@ -46,11 +48,13 @@ public sealed class TransitionShimGenerator : IIncrementalGenerator private static readonly DiagnosticDescriptor LanguageLimitSkipRule = new( id: "SHIM002", title: "Transition-shim type skipped (language limit; fallout-migrate territory)", - messageFormat: "Type '{0}' was skipped by the transition-shim generator (kind: {1}). This kind cannot be subclassed in C# — use 'fallout-migrate' to rewrite the consumer's reference to the canonical Fallout.* name.", + messageFormat: + "Type '{0}' was skipped by the transition-shim generator (kind: {1}). This kind cannot be subclassed in C# — use 'fallout-migrate' to rewrite the consumer's reference to the canonical Fallout.* name.", category: "Migration", defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: false, - description: "Covers skip kinds bounded by C# language limits: enum, delegate, and struct cannot be subclassed cross-assembly, so a transparent shim is impossible. The consumer-side fix is always 'fallout-migrate'. Off by default to keep the build console quiet; opt in via dotnet_diagnostic.SHIM002.severity if you want the full inventory."); + description: + "Covers skip kinds bounded by C# language limits: enum, delegate, and struct cannot be subclassed cross-assembly, so a transparent shim is impossible. The consumer-side fix is always 'fallout-migrate'. Off by default to keep the build console quiet; opt in via dotnet_diagnostic.SHIM002.severity if you want the full inventory."); public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -75,7 +79,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { var (markers, compilation) = input; if (markers.IsDefaultOrEmpty) + { return; + } foreach (var marker in markers) { @@ -96,7 +102,9 @@ public ShimMarker(string fromPrefix, string toPrefix, ImmutableArray exc ToPrefix = toPrefix; ExceptPrefixes = exceptPrefixes; } + public string FromPrefix { get; } + public string ToPrefix { get; } // Sub-namespaces under FromPrefix to leave unshimmed — e.g. a namespace @@ -113,19 +121,30 @@ private static ImmutableArray ExtractMarkers(ISymbol target) foreach (var attr in target.GetAttributes()) { if (attr.AttributeClass?.ToDisplayString() != AttributeFullName) + { continue; + } + if (attr.ConstructorArguments.Length < 2) + { continue; + } + var from = attr.ConstructorArguments[0].Value as string; var to = attr.ConstructorArguments[1].Value as string; if (string.IsNullOrEmpty(from) || string.IsNullOrEmpty(to)) + { continue; + } var except = ImmutableArray.Empty; foreach (var named in attr.NamedArguments) { if (named.Key != "ExceptNamespacePrefixes" || named.Value.IsNull) + { continue; + } + except = named.Value.Values .Select(v => v.Value as string) .Where(s => !string.IsNullOrEmpty(s)) @@ -135,6 +154,7 @@ private static ImmutableArray ExtractMarkers(ISymbol target) results.Add(new ShimMarker(from!, to!, except)); } + return results.ToImmutable(); } @@ -152,9 +172,13 @@ private static void EmitShimsForMarker(SourceProductionContext ctx, Compilation foreach (var assemblyRef in compilation.SourceModule.ReferencedAssemblySymbols) { if (!assemblyRef.Name.StartsWith("Fallout.", StringComparison.Ordinal)) + { continue; + } + CountFqnsInNamespace(assemblyRef.GlobalNamespace, marker, fqnCounts); } + var ambiguous = new HashSet( fqnCounts.Where(kv => kv.Value > 1).Select(kv => kv.Key), StringComparer.Ordinal); @@ -176,7 +200,9 @@ private static void EmitShimsForMarker(SourceProductionContext ctx, Compilation // Cheap filter: only walk Fallout.* assemblies. Avoids touching the // BCL and other irrelevant deps. if (!assemblyRef.Name.StartsWith("Fallout.", StringComparison.Ordinal)) + { continue; + } VisitNamespace(ctx, assemblyRef.GlobalNamespace, marker, emittedHints, ambiguous, handBridged); } @@ -186,66 +212,107 @@ private static void CollectHandBridgedFqns(INamespaceSymbol ns, string toPrefix, { var fullNs = ns.ToDisplayString(); var inScope = !string.IsNullOrEmpty(fullNs) - && (fullNs == toPrefix || fullNs.StartsWith(toPrefix + ".", StringComparison.Ordinal)); + && (fullNs == toPrefix || fullNs.StartsWith(toPrefix + ".", StringComparison.Ordinal)); + if (inScope) { foreach (var type in ns.GetTypeMembers()) { - if (type.DeclaredAccessibility != Accessibility.Public) continue; + if (type.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + // Compose the metadata-style FQN: namespace + "." + MetadataName. // MetadataName encodes arity (e.g. `Foo`1` for `Foo`), so this // matches arity correctly when we look it up later. sink.Add(fullNs + "." + type.MetadataName); } } + foreach (var child in ns.GetNamespaceMembers()) + { CollectHandBridgedFqns(child, toPrefix, sink); + } } private static void CountFqnsInNamespace(INamespaceSymbol ns, ShimMarker marker, Dictionary counts) { foreach (var type in ns.GetTypeMembers()) { - if (type.DeclaredAccessibility != Accessibility.Public) continue; + if (type.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + var fullNamespace = type.ContainingNamespace?.ToDisplayString() ?? string.Empty; - if (string.IsNullOrEmpty(fullNamespace)) continue; + if (string.IsNullOrEmpty(fullNamespace)) + { + continue; + } + var matches = fullNamespace == marker.FromPrefix - || fullNamespace.StartsWith(marker.FromPrefix + ".", StringComparison.Ordinal); - if (!matches || IsUnderExcludedPrefix(fullNamespace, marker)) continue; + || fullNamespace.StartsWith(marker.FromPrefix + ".", StringComparison.Ordinal); + + if (!matches || IsUnderExcludedPrefix(fullNamespace, marker)) + { + continue; + } + var fqn = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); counts[fqn] = counts.TryGetValue(fqn, out var existing) ? existing + 1 : 1; } + foreach (var child in ns.GetNamespaceMembers()) + { CountFqnsInNamespace(child, marker, counts); + } } - private static void VisitNamespace(SourceProductionContext ctx, INamespaceSymbol ns, ShimMarker marker, HashSet emittedHints, HashSet ambiguousFqns, HashSet handBridgedFqns) + private static void VisitNamespace(SourceProductionContext ctx, INamespaceSymbol ns, ShimMarker marker, + HashSet emittedHints, HashSet ambiguousFqns, HashSet handBridgedFqns) { foreach (var type in ns.GetTypeMembers()) { if (type.DeclaredAccessibility != Accessibility.Public) + { continue; + } var fullNamespace = type.ContainingNamespace?.ToDisplayString() ?? string.Empty; // Match `Fallout.Common` exactly OR any sub-namespace `Fallout.Common.X.Y`. // The marker stores the prefix without trailing dot. - if (string.IsNullOrEmpty(fullNamespace)) continue; + if (string.IsNullOrEmpty(fullNamespace)) + { + continue; + } + var matches = fullNamespace == marker.FromPrefix - || fullNamespace.StartsWith(marker.FromPrefix + ".", StringComparison.Ordinal); - if (!matches || IsUnderExcludedPrefix(fullNamespace, marker)) continue; + || fullNamespace.StartsWith(marker.FromPrefix + ".", StringComparison.Ordinal); + + if (!matches || IsUnderExcludedPrefix(fullNamespace, marker)) + { + continue; + } EmitOrSkipType(ctx, type, marker, emittedHints, ambiguousFqns, handBridgedFqns); } + foreach (var child in ns.GetNamespaceMembers()) + { VisitNamespace(ctx, child, marker, emittedHints, ambiguousFqns, handBridgedFqns); + } } - private static void EmitOrSkipType(SourceProductionContext ctx, INamedTypeSymbol type, ShimMarker marker, HashSet emittedHints, HashSet ambiguousFqns, HashSet handBridgedFqns) + private static void EmitOrSkipType(SourceProductionContext ctx, INamedTypeSymbol type, ShimMarker marker, + HashSet emittedHints, HashSet ambiguousFqns, HashSet handBridgedFqns) { // Skip nested types at top level — they get emitted inside their // declaring shim's source. if (type.ContainingType is not null) + { return; + } // If the consumer hand-wrote a shim at the target FQN, treat that as the // authoritative bridge. Skip both emission and the SHIM001 diagnostic — @@ -253,7 +320,9 @@ private static void EmitOrSkipType(SourceProductionContext ctx, INamedTypeSymbol var targetNs = SwapNamespace(type.ContainingNamespace.ToDisplayString(), marker); var targetMetadataFqn = targetNs + "." + type.MetadataName; if (handBridgedFqns.Contains(targetMetadataFqn)) + { return; + } var fqn = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); if (ambiguousFqns.Contains(fqn)) @@ -263,6 +332,7 @@ private static void EmitOrSkipType(SourceProductionContext ctx, INamedTypeSymbol location: Location.None, type.ToDisplayString(), "ambiguous-across-assemblies")); + return; } @@ -275,12 +345,15 @@ private static void EmitOrSkipType(SourceProductionContext ctx, INamedTypeSymbol location: Location.None, type.ToDisplayString(), skipReason)); + return; } var hint = $"{HintName(type)}.g.cs"; if (!emittedHints.Add(hint)) - return; // Already emitted via another referenced assembly. + { + return; // Already emitted via another referenced assembly. + } var source = EmitTopLevelShim(type, marker); ctx.AddSource(hint, source); @@ -294,18 +367,37 @@ private static bool IsLanguageLimitKind(string skipReason) /// private static string? ClassifyForSkip(INamedTypeSymbol type) { - if (type.TypeKind == TypeKind.Enum) return "enum"; - if (type.TypeKind == TypeKind.Delegate) return "delegate"; + if (type.TypeKind == TypeKind.Enum) + { + return "enum"; + } + + if (type.TypeKind == TypeKind.Delegate) + { + return "delegate"; + } + // Class record falls through as Easy tier — handled in EmitTypeBody. - if (type.TypeKind == TypeKind.Struct) return "struct"; + if (type.TypeKind == TypeKind.Struct) + { + return "struct"; + } + // Static classes are now session-2 supported via method-by-method // delegation — no longer skipped. - if (type.IsSealed && type.TypeKind == TypeKind.Class && !type.IsRecord) return "sealed-class"; + if (type.IsSealed && type.TypeKind == TypeKind.Class && !type.IsRecord) + { + return "sealed-class"; + } + // For classes (interfaces don't have constructors), require at least one // public or protected instance ctor — otherwise we can't subclass cross-assembly. // Static classes don't apply here (they don't get instantiated). if (type.TypeKind == TypeKind.Class && !type.IsStatic && !HasAnyAccessibleInstanceCtor(type)) + { return "no-accessible-ctor"; + } + return null; } @@ -320,8 +412,11 @@ private static bool HasAnyAccessibleInstanceCtor(INamedTypeSymbol type) if (ctor.DeclaredAccessibility == Accessibility.Public || ctor.DeclaredAccessibility == Accessibility.Protected || ctor.DeclaredAccessibility == Accessibility.ProtectedOrInternal) + { return true; + } } + return false; } @@ -352,28 +447,44 @@ private static void EmitTypeBody(StringBuilder sb, INamedTypeSymbol type, int in // Modifiers. We also add `new` when this is a nested type whose name // shadows a same-named nested type on the base (otherwise CS0108). // C# convention is `public new` order, not `new public`. - var newKeyword = (type.ContainingType is not null && BaseHasNestedTypeNamed(type)) ? "new " : string.Empty; + var newKeyword = type.ContainingType is not null && BaseHasNestedTypeNamed(type) ? "new " : string.Empty; string modifiers; if (type.IsStatic) + { modifiers = $"public {newKeyword}static partial"; + } else if (type.TypeKind == TypeKind.Interface) + { modifiers = $"public {newKeyword}partial"; + } else if (type.IsAbstract) + { modifiers = $"public {newKeyword}abstract partial"; + } else + { modifiers = $"public {newKeyword}partial"; + } // Records keep their record-ness so the inheritance rules line up. // "Only records may inherit from records" (CS8865). string kind; if (type.IsStatic) - kind = "class"; // static class + { + kind = "class"; // static class + } else if (type.TypeKind == TypeKind.Interface) + { kind = "interface"; + } else if (type.IsRecord) + { kind = "record class"; + } else + { kind = "class"; + } var genericParams = FormatGenericParameters(type); var canonicalFqn = FormatCanonicalReference(type); @@ -382,7 +493,9 @@ private static void EmitTypeBody(StringBuilder sb, INamedTypeSymbol type, int in // Static classes don't have a base type. Everything else inherits from the canonical. if (!type.IsStatic) + { sb.Append(" : ").Append(canonicalFqn); + } // Generic constraints var constraints = FormatGenericConstraints(type); @@ -397,20 +510,32 @@ private static void EmitTypeBody(StringBuilder sb, INamedTypeSymbol type, int in // Constructors (only for non-static classes — interfaces / static classes don't have them) if (type.TypeKind == TypeKind.Class && !type.IsStatic) + { EmitConstructors(sb, type, indentLevel + 1); + } // Static method delegation (only for static classes). // Non-static types inherit their instance methods from the canonical // base. Static methods on regular classes are inherited too, so we // don't redeclare those either. if (type.IsStatic) + { EmitStaticMethodDelegates(sb, type, indentLevel + 1); + } // Nested public types foreach (var nested in type.GetTypeMembers()) { - if (nested.DeclaredAccessibility != Accessibility.Public) continue; - if (ClassifyForSkip(nested) is not null) continue; // skip Hard-tier nesteds silently for now + if (nested.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + if (ClassifyForSkip(nested) is not null) + { + continue; // skip Hard-tier nesteds silently for now + } + sb.AppendLine(); EmitTypeBody(sb, nested, indentLevel + 1); } @@ -426,17 +551,41 @@ private static void EmitStaticMethodDelegates(StringBuilder sb, INamedTypeSymbol foreach (var member in type.GetMembers()) { - if (member is not IMethodSymbol method) continue; - if (method.DeclaredAccessibility != Accessibility.Public) continue; - if (!method.IsStatic) continue; - if (method.MethodKind != MethodKind.Ordinary) continue; // skip operators, accessors, etc. - if (method.IsImplicitlyDeclared) continue; + if (member is not IMethodSymbol method) + { + continue; + } + + if (method.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + if (!method.IsStatic) + { + continue; + } + + if (method.MethodKind != MethodKind.Ordinary) + { + continue; // skip operators, accessors, etc. + } + + if (method.IsImplicitlyDeclared) + { + continue; + } // Dedupe by signature so we don't emit two identical method // delegations if the canonical has the same shape twice (shouldn't // happen but defensive). - var sigKey = method.Name + "(" + string.Join(",", method.Parameters.Select(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ")"; - if (!emittedSig.Add(sigKey)) continue; + var sigKey = method.Name + "(" + string.Join(",", + method.Parameters.Select(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) + ")"; + + if (!emittedSig.Add(sigKey)) + { + continue; + } EmitOneStaticMethodDelegate(sb, method, canonicalFqn, indent); } @@ -454,7 +603,10 @@ private static void EmitOneStaticMethodDelegate(StringBuilder sb, IMethodSymbol var paramStrings = method.Parameters.Select(FormatParameter).ToList(); if (method.IsExtensionMethod && paramStrings.Count > 0) + { paramStrings[0] = "this " + paramStrings[0]; + } + var parameterList = string.Join(", ", paramStrings); var argumentList = string.Join(", ", method.Parameters.Select(p => @@ -466,6 +618,7 @@ private static void EmitOneStaticMethodDelegate(StringBuilder sb, IMethodSymbol RefKind.In => "in ", _ => string.Empty, }; + return prefix + SafeIdentifier(p.Name); })); @@ -473,8 +626,12 @@ private static void EmitOneStaticMethodDelegate(StringBuilder sb, IMethodSymbol sb.Append(indent).Append("public static ").Append(returnType).Append(' ').Append(method.Name) .Append(methodGenericParams).Append('(').Append(parameterList).Append(')'); + if (!string.IsNullOrEmpty(constraints)) + { sb.Append(' ').Append(constraints); + } + sb.Append(" => ").Append(canonicalFqn).Append('.').Append(method.Name).Append(methodGenericParams) .Append('(').Append(argumentList).Append(");").AppendLine(); } @@ -492,10 +649,15 @@ private static void EmitConstructors(StringBuilder sb, INamedTypeSymbol type, in { Accessibility.Public => "public", Accessibility.Protected => "protected", - Accessibility.ProtectedOrInternal => "protected", // collapse internal half + Accessibility.ProtectedOrInternal => "protected", // collapse internal half _ => null, }; - if (accessor is null) continue; + + if (accessor is null) + { + continue; + } + if (ctor.IsImplicitlyDeclared && ctor.Parameters.Length == 0) { // Implicit default — subclass's implicit default chains to it @@ -507,17 +669,25 @@ private static void EmitConstructors(StringBuilder sb, INamedTypeSymbol type, in var parameterList = string.Join(", ", parameters.Select(FormatParameter)); var argumentList = string.Join(", ", parameters.Select(p => SafeIdentifier(p.Name))); - var sigKey = string.Join(",", parameters.Select(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + var sigKey = string.Join(",", + parameters.Select(p => p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); + if (!emittedSig.Add(sigKey)) + { continue; + } sb.Append(indent).Append(accessor).Append(' ').Append(type.Name).Append('(').Append(parameterList).Append(')'); if (parameters.Length > 0) + { sb.Append(" : base(").Append(argumentList).Append(')'); + } + sb.AppendLine(" { }"); emittedAny = true; } - _ = emittedAny; // reserved for future diagnostic + + _ = emittedAny; // reserved for future diagnostic } // ─────────────────────────────────────────────────────────────────────── @@ -531,23 +701,33 @@ private static bool BaseHasNestedTypeNamed(INamedTypeSymbol type) // canonical containing type, so walking ContainingType.BaseType chain // works. var outer = type.ContainingType; - if (outer is null) return false; - var canonicalBase = outer; // the canonical type the outer shim inherits from + if (outer is null) + { + return false; + } + + var canonicalBase = outer; // the canonical type the outer shim inherits from for (var b = canonicalBase; b is not null; b = b.BaseType) { foreach (var nested in b.GetTypeMembers()) { if (nested.Name == type.Name && nested.DeclaredAccessibility == Accessibility.Public) + { return true; + } } } + return false; } private static string SwapNamespace(string original, ShimMarker marker) { if (!original.StartsWith(marker.FromPrefix, StringComparison.Ordinal)) + { return original; + } + return marker.ToPrefix + original.Substring(marker.FromPrefix.Length); } @@ -557,20 +737,29 @@ private static string SwapNamespace(string original, ShimMarker marker) private static bool IsUnderExcludedPrefix(string fullNamespace, ShimMarker marker) { if (marker.ExceptPrefixes.IsDefaultOrEmpty) + { return false; + } + foreach (var except in marker.ExceptPrefixes) { if (fullNamespace == except || fullNamespace.StartsWith(except + ".", StringComparison.Ordinal)) + { return true; + } } + return false; } private static string FormatGenericParameters(INamedTypeSymbol type) { if (type.TypeParameters.IsDefaultOrEmpty || type.TypeParameters.Length == 0) + { return string.Empty; + } + return "<" + string.Join(", ", type.TypeParameters.Select(p => p.Name)) + ">"; } @@ -580,22 +769,50 @@ private static string FormatGenericConstraints(INamedTypeSymbol type) private static string FormatTypeParameterConstraints(ImmutableArray typeParameters) { if (typeParameters.IsDefaultOrEmpty || typeParameters.Length == 0) + { return string.Empty; + } var clauses = new List(); foreach (var tp in typeParameters) { var parts = new List(); - if (tp.HasReferenceTypeConstraint) parts.Add("class"); - if (tp.HasValueTypeConstraint) parts.Add("struct"); - if (tp.HasNotNullConstraint) parts.Add("notnull"); - if (tp.HasUnmanagedTypeConstraint) parts.Add("unmanaged"); + if (tp.HasReferenceTypeConstraint) + { + parts.Add("class"); + } + + if (tp.HasValueTypeConstraint) + { + parts.Add("struct"); + } + + if (tp.HasNotNullConstraint) + { + parts.Add("notnull"); + } + + if (tp.HasUnmanagedTypeConstraint) + { + parts.Add("unmanaged"); + } + foreach (var ct in tp.ConstraintTypes) + { parts.Add(ct.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); - if (tp.HasConstructorConstraint) parts.Add("new()"); + } + + if (tp.HasConstructorConstraint) + { + parts.Add("new()"); + } + if (parts.Count > 0) + { clauses.Add($"where {tp.Name} : {string.Join(", ", parts)}"); + } } + return string.Join(" ", clauses); } @@ -609,9 +826,14 @@ private static string FormatCanonicalReference(INamedTypeSymbol type) while (t is not null) { segments.Insert(0, FormatTypeSegmentWithGenericArgs(t)); - if (t.ContainingType is null) break; + if (t.ContainingType is null) + { + break; + } + t = t.ContainingType; } + var ns = t!.ContainingNamespace.ToDisplayString(); return $"global::{ns}." + string.Join(".", segments); } @@ -619,7 +841,10 @@ private static string FormatCanonicalReference(INamedTypeSymbol type) private static string FormatTypeSegmentWithGenericArgs(INamedTypeSymbol t) { if (t.TypeParameters.IsDefaultOrEmpty || t.TypeParameters.Length == 0) + { return t.Name; + } + return $"{t.Name}<{string.Join(", ", t.TypeParameters.Select(p => p.Name))}>"; } @@ -632,11 +857,13 @@ private static string FormatParameter(IParameterSymbol p) RefKind.In => "in ", _ => string.Empty, }; + var paramsKeyword = p.IsParams ? "params " : string.Empty; var typeName = p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var defaultClause = p.HasExplicitDefaultValue ? " = " + FormatDefaultValue(p.ExplicitDefaultValue, p.Type) : string.Empty; + return $"{refKind}{paramsKeyword}{typeName} {SafeIdentifier(p.Name)}{defaultClause}"; } @@ -644,12 +871,16 @@ private static string FormatDefaultValue(object? value, ITypeSymbol type) { // Type parameters (T) can't accept literal null; use `default`. if (type.TypeKind == TypeKind.TypeParameter) + { return "default"; + } if (value is null) + { return type.IsValueType && type.NullableAnnotation != NullableAnnotation.Annotated ? "default" : "null"; + } // Enums: Roslyn surfaces the value as the underlying integer. Without // a cast or member-name lookup, the literal isn't convertible to the @@ -661,11 +892,20 @@ private static string FormatDefaultValue(object? value, ITypeSymbol type) } if (value is string s) + { return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + } + if (value is bool b) + { return b ? "true" : "false"; + } + if (value is char c) + { return "'" + c + "'"; + } + return value.ToString() ?? "default"; } @@ -690,6 +930,7 @@ private static string HintName(INamedTypeSymbol type) _ => ch, }); } + return sb.ToString(); } @@ -698,35 +939,35 @@ private static string HintName(INamedTypeSymbol type) // ─────────────────────────────────────────────────────────────────────── private const string ShimAttributeSource = """ - // - #nullable enable - namespace Fallout.Migrate.Shims; - - /// - /// Marks the consuming assembly as a transition-shim project. The - /// TransitionShimGenerator walks referenced Fallout.* assemblies and emits - /// shim types under toNamespacePrefix mirroring the - /// public types whose namespace begins with fromNamespacePrefix. - /// - [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] - internal sealed class ShimAllPublicTypesUnderAttribute : System.Attribute - { - public ShimAllPublicTypesUnderAttribute(string fromNamespacePrefix, string toNamespacePrefix) - { - FromNamespacePrefix = fromNamespacePrefix; - ToNamespacePrefix = toNamespacePrefix; - } - - public string FromNamespacePrefix { get; } - public string ToNamespacePrefix { get; } - - /// - /// Sub-namespaces under fromNamespacePrefix to leave unshimmed, - /// e.g. one whose types relocated to another namespace that a separate - /// marker already shims. Prevents two markers emitting the same target - /// shim type. - /// - public string[]? ExceptNamespacePrefixes { get; set; } - } - """; + // + #nullable enable + namespace Fallout.Migrate.Shims; + + /// + /// Marks the consuming assembly as a transition-shim project. The + /// TransitionShimGenerator walks referenced Fallout.* assemblies and emits + /// shim types under toNamespacePrefix mirroring the + /// public types whose namespace begins with fromNamespacePrefix. + /// + [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class ShimAllPublicTypesUnderAttribute : System.Attribute + { + public ShimAllPublicTypesUnderAttribute(string fromNamespacePrefix, string toNamespacePrefix) + { + FromNamespacePrefix = fromNamespacePrefix; + ToNamespacePrefix = toNamespacePrefix; + } + + public string FromNamespacePrefix { get; } + public string ToNamespacePrefix { get; } + + /// + /// Sub-namespaces under fromNamespacePrefix to leave unshimmed, + /// e.g. one whose types relocated to another namespace that a separate + /// marker already shims. Prevents two markers emitting the same target + /// shim type. + /// + public string[]? ExceptNamespacePrefixes { get; set; } + } + """; } diff --git a/src/Fallout.Tooling.Generator/CodeGenerator.cs b/src/Fallout.Tooling.Generator/CodeGenerator.cs index 182c7ed87..bd9fdf703 100644 --- a/src/Fallout.Tooling.Generator/CodeGenerator.cs +++ b/src/Fallout.Tooling.Generator/CodeGenerator.cs @@ -53,7 +53,8 @@ public static void GenerateCode(Tool tool, string outputFile) ToolGenerator.Run(tool, streamWriter); } - Log.Information("Generated code for {ToolName} from {File}", tool.Name, Path.GetFileName(tool.SpecificationFile) ?? ""); + Log.Information("Generated code for {ToolName} from {File}", tool.Name, + Path.GetFileName(tool.SpecificationFile) ?? ""); } // ReSharper disable once CognitiveComplexity @@ -70,7 +71,11 @@ bool NotExistent(Property property) { var nonExistent = task.SettingsClass.Properties.All(x => x.Name != property.Name); if (!nonExistent) - Log.Warning("Property {PropertyName} for task {TaskName} already exists", property.Name, task.GetTaskMethodName()); + { + Log.Warning("Property {PropertyName} for task {TaskName} already exists", property.Name, + task.GetTaskMethodName()); + } + return nonExistent; } @@ -78,17 +83,22 @@ bool NotExistent(Property property) { Assert.True(tool.CommonTaskPropertySets.TryGetValue(commonPropertySet, out var properties), $"No common property set {commonPropertySet}"); + properties.Where(NotExistent).ForEach(x => task.SettingsClass.Properties.Add(x.Clone())); } if (!task.OmitCommonProperties) + { tool.CommonTaskProperties.Where(NotExistent).ForEach(x => task.SettingsClass.Properties.Add(x.Clone())); + } foreach (var property in task.SettingsClass.Properties) { property.DataClass = task.SettingsClass; foreach (var delegateProperty in property.Delegates) + { delegateProperty.DataClass = task.SettingsClass; + } } task.SettingsClass.Properties = task.SettingsClass.Properties.OrderBy(x => x.IsTailArgument).ToList(); @@ -101,11 +111,15 @@ bool NotExistent(Property property) { property.DataClass = dataClass; foreach (var delegateProperty in property.Delegates) + { delegateProperty.DataClass = dataClass; + } } } foreach (var enumeration in tool.Enumerations) + { enumeration.Tool = tool; + } } } diff --git a/src/Fallout.Tooling.Generator/Generators/DataClassExtensionGenerator.cs b/src/Fallout.Tooling.Generator/Generators/DataClassExtensionGenerator.cs index 86bd6cb56..5bf71633e 100644 --- a/src/Fallout.Tooling.Generator/Generators/DataClassExtensionGenerator.cs +++ b/src/Fallout.Tooling.Generator/Generators/DataClassExtensionGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using Fallout.CodeGeneration.Model; using Fallout.CodeGeneration.Writers; using Fallout.Common.Utilities; @@ -27,7 +26,9 @@ public static void Run(DataClass dataClass, ToolWriter toolWriter) private static void WriteMethods(DataClassWriter writer, Property property) { if (property.CustomImpl) + { return; + } writer.WriteLine($"#region {property.Name}"); var access = $"o.{property.Name}"; @@ -145,7 +146,8 @@ private static void WriteMethods(DataClassWriter writer, Property property) property, name: $"Set{property.Name}", additionalParameters: [$"IDictionary<{keyType}, {valueType}> v"], - modification: $"Set(() => {access}, v.ToDictionary(x => x.Key, x => x.Value, {property.GetKeyComparer()}))") + modification: + $"Set(() => {access}, v.ToDictionary(x => x.Key, x => x.Value, {property.GetKeyComparer()}))") // Set item .WriteMethod( property, @@ -323,13 +325,15 @@ void WriteDelegateProperty(Property delegateProperty) property, name: $"Remove{propertyPlural}", additionalParameters: [$"params {valueType}[] v"], - modification: $"Set(() => {access}, DelegateHelper.RemoveCollection({access}, {keyValue}, v, {separator}))", + modification: + $"Set(() => {access}, DelegateHelper.RemoveCollection({access}, {keyValue}, v, {separator}))", delegateProperty.Help) .WriteMethod( property, name: $"Remove{propertyPlural}", additionalParameters: [$"IEnumerable<{valueType}> v"], - modification: $"Set(() => {access}, DelegateHelper.RemoveCollection({access}, {keyValue}, v, {separator}))", + modification: + $"Set(() => {access}, DelegateHelper.RemoveCollection({access}, {keyValue}, v, {separator}))", delegateProperty.Help) // Clear .WriteMethod( @@ -352,8 +356,14 @@ private static DataClassWriter WriteMethod( string modification, string help = null) { - var builder = $"[Builder(Type = typeof({writer.DataClass.Name}), Property = nameof({writer.DataClass.Name}.{property.Name}))]"; - var parameters = new[] { "this T o" }.Concat(additionalParameters); + var builder = + $"[Builder(Type = typeof({writer.DataClass.Name}), Property = nameof({writer.DataClass.Name}.{property.Name}))]"; + + var parameters = new[] + { + "this T o" + }.Concat(additionalParameters); + var signature = $"public static T {name}({parameters.JoinCommaSpace()}) where T : {writer.DataClass.Name}"; var implementation = $"o.Modify(b => b.{modification})"; return writer @@ -367,6 +377,8 @@ private static DataClassWriter WriteMethod( private static void CheckPlural(Property property) { if (property.Name.ToPlural() != property.Name) + { Log.Warning("Property {Class}.{Property} should be pluralized", property.DataClass.Name, property.Name); + } } } diff --git a/src/Fallout.Tooling.Generator/Generators/DataClassGenerator.cs b/src/Fallout.Tooling.Generator/Generators/DataClassGenerator.cs index cf53c541c..74dad1b3a 100644 --- a/src/Fallout.Tooling.Generator/Generators/DataClassGenerator.cs +++ b/src/Fallout.Tooling.Generator/Generators/DataClassGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Fallout.CodeGeneration.Model; using Fallout.CodeGeneration.Writers; @@ -16,7 +15,9 @@ public static class DataClassGenerator public static void Run(DataClass dataClass, ToolWriter toolWriter) { if (dataClass.OmitDataClass) + { return; + } if (dataClass.IsToolSettingsClass) { @@ -25,47 +26,72 @@ public static void Run(DataClass dataClass, ToolWriter toolWriter) CheckMissingValue(); CheckMissingSecret(); if (dataClass.Properties.Count > 1) + { CheckMissingPosition(); + } void CheckMissingValue() { if (property.NoArgument) + { return; + } if (property.Format != null && property.Format.Contains("{value}")) + { return; - - if (new[] { "int", "bool" }.Contains(property.Type)) + } + + if (new[] + { + "int", + "bool" + }.Contains(property.Type)) + { return; + } - Log.Warning("Property {ClassName}.{PropertyName} doesn't contain '{{value}}'", property.DataClass.Name, property.Name); + Log.Warning("Property {ClassName}.{PropertyName} doesn't contain '{{value}}'", property.DataClass.Name, + property.Name); } void CheckMissingSecret() { if (property.Secret.HasValue) + { return; + } if (!property.Name.ContainsOrdinalIgnoreCase("key") && !property.Name.ContainsOrdinalIgnoreCase("password") && !property.Name.ContainsOrdinalIgnoreCase("token")) + { return; + } - Log.Warning("Property {ClassName}.{PropertyName} should have explicit secret definition", property.DataClass.Name, property.Name); + Log.Warning("Property {ClassName}.{PropertyName} should have explicit secret definition", + property.DataClass.Name, property.Name); } void CheckMissingPosition() { if (property.NoArgument) + { return; + } if (!property.Format.StartsWith("{")) + { return; + } if (property.Position != null) + { return; + } - Log.Warning("Property {ClassName}.{PropertyName} does not have a position", property.DataClass.Name, property.Name); + Log.Warning("Property {ClassName}.{PropertyName} does not have a position", property.DataClass.Name, + property.Name); } } } @@ -91,15 +117,19 @@ void CheckMissingPosition() string GetCommandAttribute() { if (dataClass is not SettingsClass settingsClass) + { return null; + } var commandArguments = new (string Name, string Value)[] { (nameof(CommandAttribute.Type), $"typeof({dataClass.Tool.GetClassName()})"), - (nameof(CommandAttribute.Command), $"nameof({dataClass.Tool.GetClassName()}.{settingsClass.Task.GetTaskMethodName()})"), + (nameof(CommandAttribute.Command), + $"nameof({dataClass.Tool.GetClassName()}.{settingsClass.Task.GetTaskMethodName()})"), (nameof(CommandAttribute.Arguments), settingsClass.Task.DefiniteArgument?.DoubleQuote()), }.Where(x => x.Item2 != null) .Select(x => $"{x.Name} = {x.Value}").JoinCommaSpace(); + return $"[Command({commandArguments})]"; } } @@ -107,19 +137,28 @@ string GetCommandAttribute() private static void WritePropertyDeclaration(DataClassWriter writer, Property property) { if (property.CustomImpl) + { return; + } var type = GetPropertyType(property); - var attributes = new[] { GetArgumentAttribute(), GetJsonPropertyAttribute() }.WhereNotNull(); + var attributes = new[] + { + GetArgumentAttribute(), + GetJsonPropertyAttribute() + }.WhereNotNull(); writer .WriteLine($"/// {property.Help}") - .WriteLine($"{attributes.JoinSpace()} public {type.External} {property.Name} => Get<{type.Internal}>(() => {property.Name});"); + .WriteLine( + $"{attributes.JoinSpace()} public {type.External} {property.Name} => Get<{type.Internal}>(() => {property.Name});"); string GetArgumentAttribute() { if (property.Format.IsNullOrWhiteSpace()) + { return null; + } var arguments = new (string Name, string Value)[] { @@ -128,7 +167,7 @@ string GetArgumentAttribute() (nameof(ArgumentAttribute.Secret), property.Secret?.ToString().ToLowerInvariant()), (nameof(ArgumentAttribute.Separator), property.Separator?.DoubleQuote()), (nameof(ArgumentAttribute.QuoteMultiple), property.QuoteMultiple ? bool.TrueString.ToLowerInvariant() : null), - (nameof(ArgumentAttribute.FormatterMethod), property.Formatter?.Apply(x => $"nameof({x})")), + (nameof(ArgumentAttribute.FormatterMethod), property.Formatter?.Apply(x => $"nameof({x})")), }.Where(x => x.Item2 != null) .Select(x => $"{x.Name} = {x.Value}").JoinCommaSpace(); @@ -138,7 +177,9 @@ string GetArgumentAttribute() string GetJsonPropertyAttribute() { if (property.Json.IsNullOrWhiteSpace()) + { return null; + } return $"[JsonPropertyName({property.Json.DoubleQuote()})]"; } @@ -147,7 +188,9 @@ string GetJsonPropertyAttribute() private static (string External, string Internal) GetPropertyType(Property property) { if (property.IsList()) + { return ($"IReadOnlyList<{property.GetListValueType()}>", $"List<{property.GetListValueType()}>"); + } if (property.IsDictionary()) { diff --git a/src/Fallout.Tooling.Generator/Generators/EnumerationGenerator.cs b/src/Fallout.Tooling.Generator/Generators/EnumerationGenerator.cs index 0dc9112ac..3d4954934 100644 --- a/src/Fallout.Tooling.Generator/Generators/EnumerationGenerator.cs +++ b/src/Fallout.Tooling.Generator/Generators/EnumerationGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Text; using Fallout.CodeGeneration.Model; @@ -12,7 +11,9 @@ public static void Run(Enumeration enumeration, ToolWriter toolWriter) { var values = enumeration.Values.ToArray(); for (var i = 0; i + 1 < values.Length; i++) + { values[i] += ","; + } string GetIdentifier(string value) => value.Aggregate( diff --git a/src/Fallout.Tooling.Generator/Generators/ModelExtensions.cs b/src/Fallout.Tooling.Generator/Generators/ModelExtensions.cs index 9968d1933..e28e567a5 100644 --- a/src/Fallout.Tooling.Generator/Generators/ModelExtensions.cs +++ b/src/Fallout.Tooling.Generator/Generators/ModelExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Text.RegularExpressions; using Fallout.CodeGeneration.Model; diff --git a/src/Fallout.Tooling.Generator/Generators/StringExtensions.cs b/src/Fallout.Tooling.Generator/Generators/StringExtensions.cs index a77ee75f6..3aafbe84c 100644 --- a/src/Fallout.Tooling.Generator/Generators/StringExtensions.cs +++ b/src/Fallout.Tooling.Generator/Generators/StringExtensions.cs @@ -1,9 +1,8 @@ -using System; using System.Diagnostics; using System.Globalization; using System.Linq; -using Humanizer; using Fallout.Common; +using Humanizer; namespace Fallout.CodeGeneration.Generators; @@ -109,7 +108,7 @@ public static string EscapeProperty(this string propertyName) public static string Paragraph(this string text) { - return text == null || text.StartsWith("

") && text.EndsWith("

") + return text == null || (text.StartsWith("

") && text.EndsWith("

")) ? text : $"

{text}

"; } diff --git a/src/Fallout.Tooling.Generator/Generators/TaskGenerator.cs b/src/Fallout.Tooling.Generator/Generators/TaskGenerator.cs index 1c3ea9158..8b3c45433 100644 --- a/src/Fallout.Tooling.Generator/Generators/TaskGenerator.cs +++ b/src/Fallout.Tooling.Generator/Generators/TaskGenerator.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.CodeGeneration.Model; using Fallout.CodeGeneration.Writers; using Fallout.Common.Utilities; @@ -14,7 +12,9 @@ public static class TaskGenerator public static void Run(Tool tool, ToolWriter toolWriter) { if (tool.Tasks.Count == 0 && !tool.CustomExecutable && tool.PathExecutable == null && tool.NuGetPackageId == null) + { return; + } var baseClasses = new[] { @@ -29,20 +29,28 @@ public static void Run(Tool tool, ToolWriter toolWriter) .WriteSummary(tool) .WriteLine("[ExcludeFromCodeCoverage]") .WriteLineIfTrue(tool.NuGetPackageId != null && tool.PackageExecutable == null, "[NuGetTool(Id = PackageId)]") - .WriteLineIfTrue(tool.NuGetPackageId != null && tool.PackageExecutable != null, "[NuGetTool(Id = PackageId, Executable = PackageExecutable)]") - .WriteLineIfTrue(tool.NpmPackageId != null && tool.PackageExecutable != null, "[NpmTool(Id = PackageId, Executable = PackageExecutable)]") + .WriteLineIfTrue(tool.NuGetPackageId != null && tool.PackageExecutable != null, + "[NuGetTool(Id = PackageId, Executable = PackageExecutable)]") + .WriteLineIfTrue(tool.NpmPackageId != null && tool.PackageExecutable != null, + "[NpmTool(Id = PackageId, Executable = PackageExecutable)]") .WriteLineIfTrue(tool.AptGetPackageId != null, "[AptGetTool(Id = PackageId)]") .WriteLineIfTrue(tool.PathExecutable != null, "[PathTool(Executable = PathExecutable)]") .WriteLine($"public partial class {tool.GetClassName()} : {baseClasses.JoinCommaSpace()}") .WriteBlock(w => { w - .WriteLine($"public static string {tool.Name}Path {{ get => new {tool.GetClassName()}().GetToolPathInternal(); set => new {tool.GetClassName()}().SetToolPath(value); }}") - .WriteLineIfTrue(tool.NuGetPackageId != null, $"public const string PackageId = {tool.NuGetPackageId.DoubleQuote()};") - .WriteLineIfTrue(tool.PackageExecutable != null, $"public const string PackageExecutable = {tool.PackageExecutable.DoubleQuote()};") - .WriteLineIfTrue(tool.NpmPackageId != null, $"public const string PackageId = {tool.NpmPackageId.DoubleQuote()};") - .WriteLineIfTrue(tool.AptGetPackageId != null, $"public const string PackageId = {tool.AptGetPackageId.DoubleQuote()};") - .WriteLineIfTrue(tool.PathExecutable != null, $"public const string PathExecutable = {tool.PathExecutable.DoubleQuote()};") + .WriteLine( + $"public static string {tool.Name}Path {{ get => new {tool.GetClassName()}().GetToolPathInternal(); set => new {tool.GetClassName()}().SetToolPath(value); }}") + .WriteLineIfTrue(tool.NuGetPackageId != null, + $"public const string PackageId = {tool.NuGetPackageId.DoubleQuote()};") + .WriteLineIfTrue(tool.PackageExecutable != null, + $"public const string PackageExecutable = {tool.PackageExecutable.DoubleQuote()};") + .WriteLineIfTrue(tool.NpmPackageId != null, + $"public const string PackageId = {tool.NpmPackageId.DoubleQuote()};") + .WriteLineIfTrue(tool.AptGetPackageId != null, + $"public const string PackageId = {tool.AptGetPackageId.DoubleQuote()};") + .WriteLineIfTrue(tool.PathExecutable != null, + $"public const string PathExecutable = {tool.PathExecutable.DoubleQuote()};") .WriteGenericTask(); tool.Tasks.ForEach(x => new TaskWriter(x, toolWriter) @@ -56,27 +64,29 @@ private static void WriteGenericTask(this ToolWriter writer) { var tool = writer.Tool; var parameters = new[] - { - "ArgumentStringHandler arguments", - "string workingDirectory = null", - "IReadOnlyDictionary environmentVariables = null", - "int? timeout = null", - "bool? logOutput = null", - "bool? logInvocation = null", - "Action logger = null", - "Func exitHandler = null" - }; + { + "ArgumentStringHandler arguments", + "string workingDirectory = null", + "IReadOnlyDictionary environmentVariables = null", + "int? timeout = null", + "bool? logOutput = null", + "bool? logInvocation = null", + "Action logger = null", + "Func exitHandler = null" + }; + var arguments = new[] - { - "arguments", - "workingDirectory", - "environmentVariables", - "timeout", - "logOutput", - "logInvocation", - "logger", - "exitHandler" - }; + { + "arguments", + "workingDirectory", + "environmentVariables", + "timeout", + "logOutput", + "logInvocation", + "logger", + "exitHandler" + }; + var signature = $"IReadOnlyCollection {tool.Name}({parameters.JoinCommaSpace()})"; var invocation = $"new {tool.GetClassName()}().Run({arguments.JoinCommaSpace()})"; writer @@ -91,6 +101,7 @@ private static TaskWriter WriteToolSettingsTask(this TaskWriter writer) var returnType = !task.HasReturnValue() ? "IReadOnlyCollection" : $"({task.ReturnType} Result, IReadOnlyCollection Output)"; + var signature = $"{returnType} {task.GetTaskMethodName()}({task.SettingsClass.Name} options = null)"; var invocation = !task.HasReturnValue() ? $"new {task.Tool.GetClassName()}().Run<{task.SettingsClass.Name}>(options)" @@ -109,6 +120,7 @@ private static TaskWriter WriteConfiguratorTask(this TaskWriter writer) var returnType = !task.HasReturnValue() ? "IReadOnlyCollection" : $"({task.ReturnType} Result, IReadOnlyCollection Output)"; + var signature = $"{returnType} {task.GetTaskMethodName()}(Configure<{task.SettingsClass.Name}> configurator)"; var invocation = !task.HasReturnValue() ? $"new {task.Tool.GetClassName()}().Run<{task.SettingsClass.Name}>(configurator.Invoke(new {task.SettingsClass.Name}()))" @@ -129,11 +141,12 @@ private static TaskWriter WriteCombinatorialConfiguratorTask(this TaskWriter wri : $"IEnumerable<({task.SettingsClass.Name} Settings, {task.ReturnType} Result, IReadOnlyCollection Output)>"; var parameters = new[] - { - $"CombinatorialConfigure<{task.SettingsClass.Name}> configurator", - "int degreeOfParallelism = 1", - "bool completeOnFailure = false" - }.JoinCommaSpace(); + { + $"CombinatorialConfigure<{task.SettingsClass.Name}> configurator", + "int degreeOfParallelism = 1", + "bool completeOnFailure = false" + }.JoinCommaSpace(); + var signature = $"{returnType} {task.GetTaskMethodName()}({parameters})"; var invocation = $"configurator.Invoke({task.GetTaskMethodName()}, degreeOfParallelism, completeOnFailure)"; diff --git a/src/Fallout.Tooling.Generator/Generators/ToolGenerator.cs b/src/Fallout.Tooling.Generator/Generators/ToolGenerator.cs index 2e6c95168..38c7c53a7 100644 --- a/src/Fallout.Tooling.Generator/Generators/ToolGenerator.cs +++ b/src/Fallout.Tooling.Generator/Generators/ToolGenerator.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -57,21 +56,21 @@ private static ToolWriter WriteEnumerations(this ToolWriter writer) private static IEnumerable GetNamespaceImports(Tool tool) { return new[] - { - "Fallout.Common", - "Fallout.Common.Tooling", - "Fallout.Common.Tools", - "Fallout.Common.Utilities.Collections", - "System", - "System.Collections.Generic", - "System.Collections.ObjectModel", - "System.ComponentModel", - "System.Diagnostics.CodeAnalysis", - "System.IO", - "System.Linq", - "System.Text", - "System.Text.Json.Serialization" - } + { + "Fallout.Common", + "Fallout.Common.Tooling", + "Fallout.Common.Tools", + "Fallout.Common.Utilities.Collections", + "System", + "System.Collections.Generic", + "System.Collections.ObjectModel", + "System.ComponentModel", + "System.Diagnostics.CodeAnalysis", + "System.IO", + "System.Linq", + "System.Text", + "System.Text.Json.Serialization" + } .Concat(tool.Imports ?? new List()) .OrderBy(x => x); } diff --git a/src/Fallout.Tooling.Generator/Generators/WriterExtensions.cs b/src/Fallout.Tooling.Generator/Generators/WriterExtensions.cs index 942056f59..2883bfa78 100644 --- a/src/Fallout.Tooling.Generator/Generators/WriterExtensions.cs +++ b/src/Fallout.Tooling.Generator/Generators/WriterExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Fallout.CodeGeneration.Model; @@ -15,7 +14,9 @@ public static T WriteObsoleteAttributeWhenObsolete(this T writerWrapper, IDep where T : IWriterWrapper { if (!deprecatable.IsDeprecated()) + { return writerWrapper; + } var message = deprecatable.GetDeprecationMessage(); var obsoleteText = string.IsNullOrEmpty(message) ? string.Empty : $"(\"{message}\")"; @@ -47,6 +48,7 @@ public static T WriteRemarks(this T writerWrapper, Task task) // TODO: point at the Fallout docs site once #41 lands; until then, the README is the authoritative source. lines.Add(("This is a " + "CLI wrapper with fluent API that allows to modify the following arguments:").Paragraph()); + lines.AddRange(GetArgumentsList(task.SettingsClass)); return writerWrapper @@ -56,16 +58,21 @@ public static T WriteRemarks(this T writerWrapper, Task task) public static T WriteInherit(this T writerWrapper, Task task) where T : IWriterWrapper { - return writerWrapper.WriteLine($"/// "); + return writerWrapper.WriteLine( + $"/// "); } private static IEnumerable GetArgumentsList(DataClass dataClass) { var allDataClasses = dataClass.Tool.Tasks.Select(x => x.SettingsClass).Concat(dataClass.Tool.DataClasses).ToList(); var typeHierarchy = dataClass.DescendantsAndSelf(x => allDataClasses.FirstOrDefault(y => y.Name == x.BaseClass)); - var properties = typeHierarchy.SelectMany(x => x.Properties, (x, y) => (Class: x, Property: y)).Where(x => !string.IsNullOrEmpty(x.Property.Format)).ToList(); + var properties = typeHierarchy.SelectMany(x => x.Properties, (x, y) => (Class: x, Property: y)) + .Where(x => !string.IsNullOrEmpty(x.Property.Format)).ToList(); + if (properties.Count == 0) + { yield break; + } string GetArgument(Property property) { @@ -75,19 +82,31 @@ string GetArgument(Property property) : valueIndex != 0 ? property.Format.Substring(startIndex: 0, valueIndex).TrimEnd(':', '=', ' ') : $"<{property.Name.ToInstance()}>"; + if (!argument.Any(char.IsLetter)) - Log.Warning("Format for property {ClassName}.{PropertyName} is all non-letters", property.DataClass.Tool.Name, property.Name); + { + Log.Warning("Format for property {ClassName}.{PropertyName} is all non-letters", property.DataClass.Tool.Name, + property.Name); + } + return argument; } var propertiesWithArgument = properties - .Select(x => new { Property = x.Class.Name + "." + x.Property.Name, Argument = GetArgument(x.Property) }) + .Select(x => new + { + Property = x.Class.Name + "." + x.Property.Name, + Argument = GetArgument(x.Property) + }) .OrderBy(x => !x.Argument.StartsWith("<")) .ThenBy(x => x.Argument); yield return "
    "; foreach (var pair in propertiesWithArgument) + { yield return $"
  • {pair.Argument} via {pair.Property.ToSeeCref()}
  • "; + } + yield return "
"; } @@ -105,7 +124,8 @@ public static T WriteSummary(this T writerWrapper, Enumeration enumeration) return writerWrapper.WriteSummary(GetUsedWithinText(enumeration.Tool)); } - public static T WriteSummaryExtension(this T writerWrapper, string actionText, Property property, Property alternativeProperty = null) + public static T WriteSummaryExtension(this T writerWrapper, string actionText, Property property, + Property alternativeProperty = null) where T : IWriterWrapper { return writerWrapper.WriteSummary( @@ -129,7 +149,9 @@ public static T WriteSummary(this T writerWrapper, params string[] lines) lines = lines.Where(x => !string.IsNullOrEmpty(x)).ToArray(); if (lines.Length == 0) + { return writerWrapper; + } writerWrapper.WriteLine($"/// {lines.Join(string.Empty)}"); diff --git a/src/Fallout.Tooling.Generator/Model/DataClass.cs b/src/Fallout.Tooling.Generator/Model/DataClass.cs index 78c9594e5..d2af212ee 100644 --- a/src/Fallout.Tooling.Generator/Model/DataClass.cs +++ b/src/Fallout.Tooling.Generator/Model/DataClass.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text.Json.Serialization; namespace Fallout.CodeGeneration.Model; diff --git a/src/Fallout.Tooling.Generator/Model/Enumeration.cs b/src/Fallout.Tooling.Generator/Model/Enumeration.cs index 8581dbc81..d6d36805b 100644 --- a/src/Fallout.Tooling.Generator/Model/Enumeration.cs +++ b/src/Fallout.Tooling.Generator/Model/Enumeration.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text.Json.Serialization; namespace Fallout.CodeGeneration.Model; diff --git a/src/Fallout.Tooling.Generator/Model/IDeprecatable.cs b/src/Fallout.Tooling.Generator/Model/IDeprecatable.cs index 4704f1ad0..a66f55c08 100644 --- a/src/Fallout.Tooling.Generator/Model/IDeprecatable.cs +++ b/src/Fallout.Tooling.Generator/Model/IDeprecatable.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.CodeGeneration.Model; public interface IDeprecatable @@ -14,14 +11,17 @@ public static class DeprecatableExtensions { public static bool IsDeprecated(this IDeprecatable deprecatable) { - return deprecatable.DeprecationMessage != null || deprecatable.Parent != null && deprecatable.Parent.IsDeprecated(); + return deprecatable.DeprecationMessage != null || (deprecatable.Parent != null && deprecatable.Parent.IsDeprecated()); } public static string GetDeprecationMessage(this IDeprecatable deprecatable) { var message = deprecatable.DeprecationMessage; if (!string.IsNullOrEmpty(message)) + { return message; + } + return deprecatable.Parent?.GetDeprecationMessage(); } } diff --git a/src/Fallout.Tooling.Generator/Model/Property.cs b/src/Fallout.Tooling.Generator/Model/Property.cs index b85990769..4dc57e03e 100644 --- a/src/Fallout.Tooling.Generator/Model/Property.cs +++ b/src/Fallout.Tooling.Generator/Model/Property.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text.Json.Serialization; namespace Fallout.CodeGeneration.Model; @@ -10,7 +9,8 @@ namespace Fallout.CodeGeneration.Model; [Serializable] public class Property : IDeprecatable { - [NonSerialized] private DataClass dataClass; + [NonSerialized] + private DataClass dataClass; [JsonIgnore] public DataClass DataClass @@ -62,6 +62,7 @@ public DataClass DataClass public bool? Secret { get; set; } public string Formatter { get; set; } + public int? Position { get; set; } [Description("Custom implementation of value presentation.")] diff --git a/src/Fallout.Tooling.Generator/Model/RegexPatterns.cs b/src/Fallout.Tooling.Generator/Model/RegexPatterns.cs index 0a779ec06..d42bfd865 100644 --- a/src/Fallout.Tooling.Generator/Model/RegexPatterns.cs +++ b/src/Fallout.Tooling.Generator/Model/RegexPatterns.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.CodeGeneration.Model; +namespace Fallout.CodeGeneration.Model; internal static class RegexPatterns { diff --git a/src/Fallout.Tooling.Generator/Model/Task.cs b/src/Fallout.Tooling.Generator/Model/Task.cs index 66d7f53db..5f1e87950 100644 --- a/src/Fallout.Tooling.Generator/Model/Task.cs +++ b/src/Fallout.Tooling.Generator/Model/Task.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text.Json.Serialization; namespace Fallout.CodeGeneration.Model; diff --git a/src/Fallout.Tooling.Generator/Model/Tool.cs b/src/Fallout.Tooling.Generator/Model/Tool.cs index 664cd43ed..f83949279 100644 --- a/src/Fallout.Tooling.Generator/Model/Tool.cs +++ b/src/Fallout.Tooling.Generator/Model/Tool.cs @@ -1,10 +1,8 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.IO; -using System.Linq; using System.Text.Json.Serialization; namespace Fallout.CodeGeneration.Model; @@ -13,20 +11,30 @@ namespace Fallout.CodeGeneration.Model; public class Tool : IDeprecatable { [JsonPropertyName("$schema")] - public string Schema => "https://raw.githubusercontent.com/Fallout-build/Fallout/main/src/Fallout.Tooling.Generator/schema.json"; + public string Schema => + "https://raw.githubusercontent.com/Fallout-build/Fallout/main/src/Fallout.Tooling.Generator/schema.json"; - [JsonIgnore] public string SpecificationFile { get; set; } - [JsonIgnore] public string DefaultOutputFile => Path.ChangeExtension(SpecificationFile, "Generated.cs"); - [JsonIgnore] public string DefaultOutputFileName => Path.GetFileName(DefaultOutputFile); + [JsonIgnore] + public string SpecificationFile { get; set; } - [JsonIgnore] public string SourceFile { get; set; } + [JsonIgnore] + public string DefaultOutputFile => Path.ChangeExtension(SpecificationFile, "Generated.cs"); - [JsonIgnore] public string Namespace { get; set; } + [JsonIgnore] + public string DefaultOutputFileName => Path.GetFileName(DefaultOutputFile); - [JsonIgnore] public IDeprecatable Parent => null; + [JsonIgnore] + public string SourceFile { get; set; } + + [JsonIgnore] + public string Namespace { get; set; } + + [JsonIgnore] + public IDeprecatable Parent => null; [Description("Contains all references on which this definition is based on. Allows checking for updates.")] public List References { get; set; } = new(); + public List Imports { get; set; } = new(); [JsonRequired] diff --git a/src/Fallout.Tooling.Generator/ReferenceUpdater.cs b/src/Fallout.Tooling.Generator/ReferenceUpdater.cs index a5d24237e..b32513329 100644 --- a/src/Fallout.Tooling.Generator/ReferenceUpdater.cs +++ b/src/Fallout.Tooling.Generator/ReferenceUpdater.cs @@ -5,31 +5,33 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; -using HtmlAgilityPack; using Fallout.CodeGeneration.Model; using Fallout.Common; using Fallout.Common.Utilities.Net; +using HtmlAgilityPack; using Serilog; +using Task = System.Threading.Tasks.Task; namespace Fallout.CodeGeneration; public static class ReferenceUpdater { - private static HttpClient client = new(); + private static readonly HttpClient client = new(); public static void UpdateReferences(string specificationsDirectory, string referencesDirectory = null) { - UpdateReferences(Directory.GetFiles(specificationsDirectory, "*.json", SearchOption.TopDirectoryOnly), referencesDirectory); + UpdateReferences(Directory.GetFiles(specificationsDirectory, "*.json", SearchOption.TopDirectoryOnly), + referencesDirectory); } public static void UpdateReferences(IEnumerable specificationFiles, string referencesDirectory = null) { var tools = specificationFiles.Select(ToolSerializer.Load); var updateTasks = tools.SelectMany(x => x.References.Select(y => Update(y, x, referencesDirectory))); - System.Threading.Tasks.Task.WaitAll(updateTasks.ToArray()); + Task.WaitAll(updateTasks.ToArray()); } - private static async System.Threading.Tasks.Task Update(string reference, Tool tool, string referencesDirectory) + private static async Task Update(string reference, Tool tool, string referencesDirectory) { var index = tool.References.IndexOf(reference); try @@ -39,6 +41,7 @@ private static async System.Threading.Tasks.Task Update(string reference, Tool t var referenceFile = Path.Combine( referencesDirectory, $"{Path.GetFileNameWithoutExtension(tool.SpecificationFile)}.ref.{referenceId}.txt"); + var referenceContent = await GetReferenceContent(reference); File.WriteAllText(referenceFile, referenceContent); @@ -58,10 +61,13 @@ private static async Task GetReferenceContent(string reference) var response = await client.CreateRequest(HttpMethod.Get, referenceValues[0]) .GetResponseAsync(); + await response.WriteToFile(tempFile); if (referenceValues.Length == 1) + { return File.ReadAllText(tempFile, Encoding.UTF8); + } var document = new HtmlDocument(); document.Load(tempFile, Encoding.UTF8); diff --git a/src/Fallout.Tooling.Generator/ToolSerializer.cs b/src/Fallout.Tooling.Generator/ToolSerializer.cs index 8dd30ba74..267214a32 100644 --- a/src/Fallout.Tooling.Generator/ToolSerializer.cs +++ b/src/Fallout.Tooling.Generator/ToolSerializer.cs @@ -1,7 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Text.Encodings.Web; using System.Text.Json; @@ -9,7 +9,6 @@ using System.Text.Json.Serialization.Metadata; using Fallout.CodeGeneration.Model; using Fallout.Common.IO; -using Fallout.Common.Utilities; using Serilog; namespace Fallout.CodeGeneration; @@ -31,7 +30,10 @@ public static class ToolSerializer Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, TypeInfoResolver = new DefaultJsonTypeInfoResolver { - Modifiers = { ApplySerializationFilters } + Modifiers = + { + ApplySerializationFilters + } } }; @@ -64,15 +66,19 @@ public static void Save(Tool tool, AbsolutePath file) // Value-type defaults and null reference values are handled by DefaultIgnoreCondition.WhenWritingDefault. private static void ApplySerializationFilters(JsonTypeInfo typeInfo) { - var readOnlyProperties = new System.Collections.Generic.List(); + var readOnlyProperties = new List(); foreach (var jsonProperty in typeInfo.Properties) { if (jsonProperty.AttributeProvider is not PropertyInfo declared) + { continue; + } // Tool.Schema is intentionally read-only but must serialize. if (typeInfo.Type == typeof(Tool) && declared.Name == nameof(Tool.Schema)) + { continue; + } var getter = declared.GetGetMethod(nonPublic: true); var setter = declared.GetSetMethod(nonPublic: true); @@ -88,13 +94,18 @@ private static void ApplySerializationFilters(JsonTypeInfo typeInfo) readOnlyProperties.Add(jsonProperty); } } + foreach (var p in readOnlyProperties) + { typeInfo.Properties.Remove(p); + } foreach (var jsonProperty in typeInfo.Properties) { if (jsonProperty.AttributeProvider is not PropertyInfo declared) + { continue; + } var propertyName = declared.Name; var propertyType = declared.PropertyType; @@ -113,7 +124,11 @@ private static void ApplySerializationFilters(JsonTypeInfo typeInfo) { jsonProperty.ShouldSerialize = (_, value) => { - if (value is not IEnumerable enumerable) return false; + if (value is not IEnumerable enumerable) + { + return false; + } + var enumerator = enumerable.GetEnumerator(); try { return enumerator.MoveNext(); } finally { (enumerator as IDisposable)?.Dispose(); } diff --git a/src/Fallout.Tooling.Generator/Writers/DataClassWriter.cs b/src/Fallout.Tooling.Generator/Writers/DataClassWriter.cs index c0933babc..59b4f00ad 100644 --- a/src/Fallout.Tooling.Generator/Writers/DataClassWriter.cs +++ b/src/Fallout.Tooling.Generator/Writers/DataClassWriter.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.CodeGeneration.Model; +using Fallout.CodeGeneration.Model; namespace Fallout.CodeGeneration.Writers; @@ -13,5 +11,6 @@ public DataClassWriter(DataClass dataClass, ToolWriter writer) } public DataClass DataClass { get; } + public IWriter Writer { get; } } diff --git a/src/Fallout.Tooling.Generator/Writers/IWriter.cs b/src/Fallout.Tooling.Generator/Writers/IWriter.cs index 5c7126748..905688217 100644 --- a/src/Fallout.Tooling.Generator/Writers/IWriter.cs +++ b/src/Fallout.Tooling.Generator/Writers/IWriter.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.CodeGeneration.Writers; diff --git a/src/Fallout.Tooling.Generator/Writers/IWriterWrapper.cs b/src/Fallout.Tooling.Generator/Writers/IWriterWrapper.cs index 440b1c544..49ceaf8b7 100644 --- a/src/Fallout.Tooling.Generator/Writers/IWriterWrapper.cs +++ b/src/Fallout.Tooling.Generator/Writers/IWriterWrapper.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.CodeGeneration.Writers; public interface IWriterWrapper diff --git a/src/Fallout.Tooling.Generator/Writers/TaskWriter.cs b/src/Fallout.Tooling.Generator/Writers/TaskWriter.cs index a77cfa7ff..f0beae400 100644 --- a/src/Fallout.Tooling.Generator/Writers/TaskWriter.cs +++ b/src/Fallout.Tooling.Generator/Writers/TaskWriter.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using Fallout.CodeGeneration.Model; namespace Fallout.CodeGeneration.Writers; @@ -13,5 +11,6 @@ public TaskWriter(Task task, ToolWriter toolWriter) } public Task Task { get; } + public IWriter Writer { get; } } diff --git a/src/Fallout.Tooling.Generator/Writers/ToolWriter.cs b/src/Fallout.Tooling.Generator/Writers/ToolWriter.cs index 9df963238..8ad6b4daa 100644 --- a/src/Fallout.Tooling.Generator/Writers/ToolWriter.cs +++ b/src/Fallout.Tooling.Generator/Writers/ToolWriter.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using Fallout.CodeGeneration.Model; using Fallout.Common.Utilities; @@ -18,6 +17,7 @@ public ToolWriter(Tool tool, StreamWriter streamWriter) } public Tool Tool { get; } + public IWriter Writer => this; public void Dispose() diff --git a/src/Fallout.Tooling.Generator/Writers/WriterExtensions.cs b/src/Fallout.Tooling.Generator/Writers/WriterExtensions.cs index dd52218e7..2a7165792 100644 --- a/src/Fallout.Tooling.Generator/Writers/WriterExtensions.cs +++ b/src/Fallout.Tooling.Generator/Writers/WriterExtensions.cs @@ -13,7 +13,10 @@ public static TWrapper ForEach( where TWrapper : IWriterWrapper { foreach (var item in enumerable) + { action(writerWrapper, item); + } + return writerWrapper; } @@ -24,7 +27,10 @@ public static TWrapper ForEach( where TWrapper : IWriterWrapper { foreach (var item in enumerable) + { action(item); + } + return writerWrapper; } @@ -36,7 +42,10 @@ public static TWrapper ForEach( { var list = enumerable.ToList(); foreach (var item in list) + { action(item, item.Equals(list.Last())); + } + return writerWrapper; } @@ -44,7 +53,10 @@ public static T WriteLine(this T writerWrapper, string text) where T : IWriterWrapper { if (text != null) + { writerWrapper.Writer.WriteLine(text.Trim()); + } + return writerWrapper; } @@ -58,7 +70,10 @@ public static T WriteLineIfTrue(this T writerWrapper, bool condition, string where T : IWriterWrapper { if (condition) + { writerWrapper.WriteLine(text); + } + return writerWrapper; } @@ -73,7 +88,10 @@ public static T When(this T writerWrapper, bool condition, Action action) where T : IWriterWrapper { if (condition) + { action(writerWrapper); + } + return writerWrapper; } } diff --git a/src/Fallout.Tooling/ArgumentStringHandler.cs b/src/Fallout.Tooling/ArgumentStringHandler.cs index 18af0ee1b..94ee2047f 100644 --- a/src/Fallout.Tooling/ArgumentStringHandler.cs +++ b/src/Fallout.Tooling/ArgumentStringHandler.cs @@ -1,5 +1,4 @@ #if NET6_0_OR_GREATER - using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Fallout.Tooling/Configure.cs b/src/Fallout.Tooling/Configure.cs index 15014593d..86dfb6a8f 100644 --- a/src/Fallout.Tooling/Configure.cs +++ b/src/Fallout.Tooling/Configure.cs @@ -36,7 +36,8 @@ public static T InvokeSafe(this Configure configurator, T obj) completeOnFailure); } - public static IReadOnlyCollection<(TOptions Options, TResult Result, IReadOnlyCollection Output)> Invoke( + public static IReadOnlyCollection<(TOptions Options, TResult Result, IReadOnlyCollection Output)> Invoke( this CombinatorialConfigure configurator, Func Output)> executor, int degreeOfParallelism, @@ -60,7 +61,9 @@ private static IReadOnlyCollection Invoke( bool completeOnFailure) where TOptions : ToolOptions, new() { - var invocations = new ConcurrentBag<(TOptions Options, Action Logger, TResult Result, Exception Exception)>(); + var invocations = + new ConcurrentBag<(TOptions Options, Action Logger, TResult Result, Exception Exception)>(); + try { configurator(new TOptions()) @@ -78,12 +81,16 @@ private static IReadOnlyCollection Invoke( invocations.Add((x, x.GetLogger(), default, exception)); if (!completeOnFailure) + { throw; + } } }); if (invocations.Any(x => x.Exception != null)) + { throw new AggregateException(invocations.Select(x => x.Exception).WhereNotNull()); + } return invocations.Select(x => x.Result).ToList(); } diff --git a/src/Fallout.Tooling/DelegateHelper.cs b/src/Fallout.Tooling/DelegateHelper.cs index 5325f38b7..e38b7a6b8 100644 --- a/src/Fallout.Tooling/DelegateHelper.cs +++ b/src/Fallout.Tooling/DelegateHelper.cs @@ -24,7 +24,10 @@ public static IDictionary SetCollection( var newDictionary = dictionary?.ToDictionary(x => x.Key, x => x.Value) ?? new Dictionary(); var collectionAsString = CollectionToString(values, separator); if (!string.IsNullOrWhiteSpace(collectionAsString)) + { newDictionary[key] = collectionAsString; + } + return newDictionary; } @@ -55,10 +58,14 @@ public static IDictionary RemoveCollection( return newDictionary; } - private static List ParseCollection(IReadOnlyDictionary dictionary, string key, string separator) + private static List ParseCollection(IReadOnlyDictionary dictionary, string key, + string separator) { if (dictionary?.TryGetValue(key, out var value) != true) + { return []; + } + // STJ round-tripping IReadOnlyDictionary deserializes values as JsonElement, // not as primitive strings like Newtonsoft did. Coerce both shapes back to string here. var stringValue = value switch @@ -68,6 +75,7 @@ private static List ParseCollection(IReadOnlyDictionary null, _ => value.ToString() }; + return (stringValue?.Split([separator], StringSplitOptions.RemoveEmptyEntries) ?? []) .Select(ReflectionUtility.Convert).ToList(); } diff --git a/src/Fallout.Tooling/Enumeration.cs b/src/Fallout.Tooling/Enumeration.cs index 1bc9711bb..5e10a9c30 100644 --- a/src/Fallout.Tooling/Enumeration.cs +++ b/src/Fallout.Tooling/Enumeration.cs @@ -61,12 +61,21 @@ protected bool Equals(Enumeration other) public override bool Equals(object obj) { if (ReferenceEquals(objA: null, objB: obj)) + { return false; + } + if (ReferenceEquals(this, obj)) + { return true; + } + if (obj.GetType() != GetType()) + { return false; - return Equals((Enumeration) obj); + } + + return Equals((Enumeration)obj); } public override int GetHashCode() @@ -94,6 +103,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { var matchingFields = typeof(T).GetFields(ReflectionUtility.Static) .Where(x => x.Name.EqualsOrdinalIgnoreCase(stringValue)).ToList(); + Assert.HasSingleItem(matchingFields); return matchingFields.Single().GetValue(obj: null); } diff --git a/src/Fallout.Tooling/EnumerationJsonConverter.cs b/src/Fallout.Tooling/EnumerationJsonConverter.cs index 01ec7301a..4b2064e19 100644 --- a/src/Fallout.Tooling/EnumerationJsonConverter.cs +++ b/src/Fallout.Tooling/EnumerationJsonConverter.cs @@ -1,9 +1,7 @@ using System; using System.ComponentModel; -using System.Globalization; using System.Text.Json; using System.Text.Json.Serialization; -using Fallout.Common.Utilities; namespace Fallout.Common.Tooling; @@ -31,7 +29,10 @@ private class TypedConverter : JsonConverter public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) + { return null; + } + var raw = reader.GetString(); var converter = TypeDescriptor.GetConverter(typeToConvert); return (T)converter.ConvertFromInvariantString(raw); @@ -40,9 +41,13 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (value is null) + { writer.WriteNullValue(); + } else + { writer.WriteStringValue((string)value); + } } public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) diff --git a/src/Fallout.Tooling/IProcess.cs b/src/Fallout.Tooling/IProcess.cs index 09e7230c6..f45c22a05 100644 --- a/src/Fallout.Tooling/IProcess.cs +++ b/src/Fallout.Tooling/IProcess.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Tooling/NpmToolPathResolver.cs b/src/Fallout.Tooling/NpmToolPathResolver.cs index 1ec405e29..e24e6d654 100644 --- a/src/Fallout.Tooling/NpmToolPathResolver.cs +++ b/src/Fallout.Tooling/NpmToolPathResolver.cs @@ -1,6 +1,4 @@ -using System; -using System.Linq; -using Fallout.Common.IO; +using Fallout.Common.IO; namespace Fallout.Common.Tooling; @@ -27,4 +25,4 @@ public static string GetNpmExecutable(string npmExecutable) .AssertZeroExitCode() .Output.StdToText(); } -} \ No newline at end of file +} diff --git a/src/Fallout.Tooling/NuGetPackageResolver.cs b/src/Fallout.Tooling/NuGetPackageResolver.cs index 8735dc0f6..0b2ecba14 100644 --- a/src/Fallout.Tooling/NuGetPackageResolver.cs +++ b/src/Fallout.Tooling/NuGetPackageResolver.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Text.Json.Nodes; using System.Xml.Linq; -using NuGet.Packaging; -using NuGet.Versioning; using Fallout.Common.IO; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using NuGet.Packaging; +using NuGet.Versioning; namespace Fallout.Common.Tooling; @@ -48,10 +48,11 @@ public static IEnumerable GetLocalInstalledPackages( var assetsObject = JsonNode.Parse(assetsContent).NotNull().AsObject(); // ReSharper disable HeapView.BoxingAllocation - var firstFramework = assetsObject["project"].NotNull()["frameworks"].NotNull().AsObject().First().Value.NotNull().AsObject(); + var firstFramework = assetsObject["project"].NotNull()["frameworks"].NotNull().AsObject().First().Value.NotNull() + .AsObject(); var directPackageReferences = - (firstFramework["dependencies"]?.AsObject().Select(x => x.Key).ToList()) + firstFramework["dependencies"]?.AsObject().Select(x => x.Key).ToList() ?? new List(); var packageReferences = @@ -67,12 +68,12 @@ public static IEnumerable GetLocalInstalledPackages( .ToList(); var packageDownloads = - (firstFramework["downloadDependencies"]?.AsArray() + firstFramework["downloadDependencies"]?.AsArray() .Select(x => x.NotNull().AsObject()) .Select(x => ( PackageId: x["name"].NotNull().GetValue(), Version: x["version"].NotNull().GetValue().Trim('[', ']').Split(',').First().Trim() - )).ToList()) + )).ToList() ?? new List<(string, string)>(); // ReSharper restore HeapView.BoxingAllocation @@ -117,7 +118,9 @@ private static IEnumerable GetLocalInstalledPackagesFromConfig { var package = GetGlobalInstalledPackage(packageId, version, packagesConfigFile); if (package == null) + { continue; + } installedPackages.Add(package); yield return package; @@ -134,7 +137,9 @@ private static IEnumerable GetLocalInstalledPackagesFromConfig foreach (var dependentPackage in GetDependentPackages(packageToCheck, packagesConfigFile)) { if (installedPackages.Contains(dependentPackage)) + { continue; + } installedPackages.Add(dependentPackage); packagesToCheck.Enqueue(dependentPackage); @@ -145,13 +150,18 @@ private static IEnumerable GetLocalInstalledPackagesFromConfig } } - private static IEnumerable GetDependentPackages(InstalledPackage packageToCheck, AbsolutePath packagesConfigFile) + private static IEnumerable GetDependentPackages(InstalledPackage packageToCheck, + AbsolutePath packagesConfigFile) { return packageToCheck.Metadata.GetDependencyGroups() .SelectMany(x => x.Packages) .Select(x => GetGlobalInstalledPackage(x.Id, x.VersionRange, packagesConfigFile)) .WhereNotNull() - .Distinct(x => new { x.Id, x.Version }); + .Distinct(x => new + { + x.Id, + x.Version + }); } public static InstalledPackage GetGlobalInstalledPackage( @@ -163,7 +173,9 @@ public static InstalledPackage GetGlobalInstalledPackage( !version.Contains("*") && !version.StartsWith("[") && !version.EndsWith("]")) + { version = $"[{version}]"; + } VersionRange.TryParse(version, out var versionRange); return GetGlobalInstalledPackage(packageId, versionRange, packagesConfigFile); @@ -180,7 +192,9 @@ public static InstalledPackage GetGlobalInstalledPackage( packageId = packageId.ToLowerInvariant(); var packagesDirectory = GetPackagesDirectory(packagesConfigFile); if (packagesDirectory == null) + { return null; + } var packagesDirectoryInfo = new DirectoryInfo(packagesDirectory); var packages = packagesDirectoryInfo @@ -203,7 +217,8 @@ public static InstalledPackage GetGlobalInstalledPackage( return versionRange == null ? candidatePackages.FirstOrDefault() - : candidatePackages.SingleOrDefault(x => x.Version == versionRange.FindBestMatch(candidatePackages.Select(y => y.Version))); + : candidatePackages.SingleOrDefault(x => + x.Version == versionRange.FindBestMatch(candidatePackages.Select(y => y.Version))); } public static string GetPackagesConfigFile(string projectDirectory) @@ -212,6 +227,7 @@ public static string GetPackagesConfigFile(string projectDirectory) var packagesConfigFile = projectDirectoryInfo.GetFiles("packages.config").SingleOrDefault() ?? projectDirectoryInfo.GetFiles("*.csproj") .SingleOrDefaultOrError($"Directory '{projectDirectory}' contains multiple project files."); + return packagesConfigFile?.FullName; } @@ -224,10 +240,11 @@ string TryGetFromEnvironmentVariable() string TryGetGlobalDirectoryFromConfig() => GetConfigFiles(packagesConfigFile) .Select(x => new - { - File = x, - Setting = XDocument.Load(x).XPathSelectAttributeValues(".//add[@key='globalPackagesFolder']/@value").SingleOrDefault() - }) + { + File = x, + Setting = XDocument.Load(x).XPathSelectAttributeValues(".//add[@key='globalPackagesFolder']/@value") + .SingleOrDefault() + }) .Where(x => x.Setting != null) .Select(x => Path.IsPathRooted(x.Setting) ? x.Setting @@ -255,6 +272,7 @@ string TryGetLocalDirectory() TryGetGlobalDirectoryFromConfig() ?? TryGetDefaultGlobalDirectory() ?? TryGetLocalDirectory(); + return packagesDirectory != null && Directory.Exists(packagesDirectory) ? packagesDirectory : null; @@ -275,7 +293,9 @@ private static IEnumerable GetConfigFiles(AbsolutePath packagesConfigFil var directories = new List(); if (packagesConfigFile != null) + { directories.AddRange(packagesConfigFile.Descendants(x => x.Parent)); + } if (EnvironmentInfo.IsWin) { @@ -290,15 +310,23 @@ private static IEnumerable GetConfigFiles(AbsolutePath packagesConfigFil var dataHomeDirectoy = EnvironmentInfo.GetVariable("XDG_DATA_HOME"); if (!string.IsNullOrEmpty(dataHomeDirectoy)) + { directories.Add(dataHomeDirectoy); + } else // TODO: /usr/local/share + { directories.Add(EnvironmentInfo.SpecialFolder(SpecialFolders.UserProfile).NotNull() / ".local" / "share"); + } } return directories .WhereDirectoryExists() - .SelectMany(x => new[] { x / "nuget.config", x / "NuGet.config" }) + .SelectMany(x => new[] + { + x / "nuget.config", + x / "NuGet.config" + }) // TODO: Add AbsolutePathComparer .Select(x => x.ToString()) .Distinct(EnvironmentInfo.IsLinux ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase) @@ -315,13 +343,25 @@ public sealed class Comparer : IEqualityComparer public bool Equals(InstalledPackage x, InstalledPackage y) { if (ReferenceEquals(x, y)) + { return true; + } + if (ReferenceEquals(x, objB: null)) + { return false; + } + if (ReferenceEquals(y, objB: null)) + { return false; + } + if (x.GetType() != y.GetType()) + { return false; + } + return Equals(x.Id, y.Id) && Equals(x.Version, y.Version); } @@ -346,9 +386,13 @@ public InstalledPackage(AbsolutePath file) } public AbsolutePath File { get; } + public AbsolutePath Directory => File.Parent.NotNull(); + public NuspecReader Metadata => metadata.Value; + public string Id => Metadata.GetIdentity().Id; + public NuGetVersion Version => Metadata.GetIdentity().Version; public override string ToString() diff --git a/src/Fallout.Tooling/NuGetToolPathResolver.cs b/src/Fallout.Tooling/NuGetToolPathResolver.cs index 6df96e92a..00c1831a5 100644 --- a/src/Fallout.Tooling/NuGetToolPathResolver.cs +++ b/src/Fallout.Tooling/NuGetToolPathResolver.cs @@ -25,14 +25,23 @@ public static void Reset() PaketPackagesConfigFile = null; } - public static string GetPackageExecutable(string packageId, string packageExecutable, string version = null, string framework = null) + public static string GetPackageExecutable(string packageId, string packageExecutable, string version = null, + string framework = null) { Assert.True(packageId != null && packageExecutable != null); - var packageDirectory = GetPackageDirectory(packageId.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries), version); - var packageExecutables = packageExecutable.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + var packageDirectory = GetPackageDirectory(packageId.Split(new[] + { + '|' + }, StringSplitOptions.RemoveEmptyEntries), version); + + var packageExecutables = packageExecutable.Split(new[] + { + '|' + }, StringSplitOptions.RemoveEmptyEntries); #if !NETSTANDARD2_0 - var enumerationOptions = new EnumerationOptions { RecurseSubdirectories = true, MatchCasing = MatchCasing.CaseInsensitive }; + var enumerationOptions = new EnumerationOptions { RecurseSubdirectories = true, MatchCasing = + MatchCasing.CaseInsensitive }; #endif var packageExecutablePaths = packageExecutables #if !NETSTANDARD2_0 @@ -45,8 +54,11 @@ public static string GetPackageExecutable(string packageId, string packageExecut Assert.NotEmpty(packageExecutablePaths, $"Could not find {packageExecutables.Select(x => x.SingleQuote()).JoinCommaOr()} inside '{packageDirectory}'"); + if (packageExecutablePaths.Count == 1 && framework == null) + { return packageExecutablePaths.Single(); + } static string GetFramework(string file) { @@ -68,16 +80,20 @@ static string GetPackageExecutable(IEnumerable executables) Assert.True(frameworks.Count > 0); if (frameworks.Count == 1) + { return GetPackageExecutable(frameworks.Single()); + } framework ??= (Assembly.GetEntryAssembly()?.GetCustomAttribute() ?.FrameworkDisplayName?.Replace(".NET ", "net")).NotNull(); + var sortedFrameworks = frameworks.Select(x => x.Key) .OrderBy(x => x == framework) .ThenBy(x => x.Contains(".")) .ThenBy(x => !x.StartsWith("netcore")) .ThenBy(x => x) .Reverse(); + return GetPackageExecutable(frameworks[sortedFrameworks.First()]); } @@ -131,7 +147,9 @@ private static string GetPackageDirectory(string[] packageIds, string version) catch (Exception exception) { if (NuGetPackagesConfigFile != null && !NuGetPackagesConfigFile.EndsWithOrdinalIgnoreCase(".csproj")) + { throw; + } var packageCombinations = from packageId in packageIds @@ -140,7 +158,8 @@ from packageVersion in { NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false).GetAwaiter().GetResult(), NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: true).GetAwaiter().GetResult(), - NuGetPackageResolver.GetGlobalInstalledPackage(packageId, version: null, packagesConfigFile: null)?.Version.ToString() + NuGetPackageResolver.GetGlobalInstalledPackage(packageId, version: null, packagesConfigFile: null) + ?.Version.ToString() } where packageVersion != null select (Id: packageId, Version: packageVersion); @@ -153,6 +172,7 @@ from packageVersion in }.Concat(packageCombinations.Distinct().Select(x => $" - fallout :add-package {x.Id} --version {x.Version}")) .JoinNewLine(), exception); + throw new Exception("Not reachable"); } } diff --git a/src/Fallout.Tooling/NuGetVersionResolver.cs b/src/Fallout.Tooling/NuGetVersionResolver.cs index cbd4dff6d..b9d7750fd 100644 --- a/src/Fallout.Tooling/NuGetVersionResolver.cs +++ b/src/Fallout.Tooling/NuGetVersionResolver.cs @@ -2,7 +2,6 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Threading.Tasks; -using Fallout.Common.Utilities; namespace Fallout.Common.Tooling; @@ -17,6 +16,7 @@ public static async Task GetLatestVersion(string packageId, bool include var url = includeUnlisted ? $"https://api.nuget.org/v3/flatcontainer/{packageId.ToLowerInvariant()}/index.json" : $"https://api-v2v3search-0.nuget.org/query?q=packageid:{packageId}&prerelease={includePrereleases}"; + var jsonString = await client.GetStringAsync(url); var jsonObject = JsonNode.Parse(jsonString)?.AsObject().NotNull(); @@ -24,6 +24,7 @@ public static async Task GetLatestVersion(string packageId, bool include { var versions = jsonObject!.First().Value.NotNull().AsArray() .Select(x => x!.GetValue()); + return versions.Last(x => includePrereleases || !x.Contains("-")); } diff --git a/src/Fallout.Tooling/Options.Combinatorial.cs b/src/Fallout.Tooling/Options.Combinatorial.cs index c432334c9..58c87f68b 100644 --- a/src/Fallout.Tooling/Options.Combinatorial.cs +++ b/src/Fallout.Tooling/Options.Combinatorial.cs @@ -36,13 +36,15 @@ public static T[] CombineWith(this T options, IEnumerable val return values.Select(x => configurator(options, x)).ToArray(); } - public static T[] CombineWith(this IEnumerable options, IEnumerable values, Func configurator) + public static T[] CombineWith(this IEnumerable options, IEnumerable values, + Func configurator) where T : Options { return options.SelectMany(x => values.Select(y => configurator(x, y))).ToArray(); } - public static T[] CombineWith(this T options, IEnumerable values, Func> configurator) + public static T[] CombineWith(this T options, IEnumerable values, + Func> configurator) where T : Options { return values.SelectMany(x => configurator(options, x)).ToArray(); diff --git a/src/Fallout.Tooling/Options.Modify.cs b/src/Fallout.Tooling/Options.Modify.cs index f07079db2..a3d0a3dbf 100644 --- a/src/Fallout.Tooling/Options.Modify.cs +++ b/src/Fallout.Tooling/Options.Modify.cs @@ -8,8 +8,8 @@ public static partial class OptionsExtensions public static T Modify(this T options, Action modification = null) where T : IOptions { - var json = JsonSerializer.Serialize(options, options.GetType(), Tooling.Options.SerializerOptions); - var copy = (T)JsonSerializer.Deserialize(json, options.GetType(), Tooling.Options.SerializerOptions); + var json = JsonSerializer.Serialize(options, options.GetType(), Options.SerializerOptions); + var copy = (T)JsonSerializer.Deserialize(json, options.GetType(), Options.SerializerOptions); // TODO OPTIONS: HACK if (options is ToolOptions originalOptions && copy is ToolOptions copiedOptions) diff --git a/src/Fallout.Tooling/Options.cs b/src/Fallout.Tooling/Options.cs index b93361245..0ada19b72 100644 --- a/src/Fallout.Tooling/Options.cs +++ b/src/Fallout.Tooling/Options.cs @@ -21,8 +21,13 @@ public interface IOptions void SetDictionary(Expression>> provider, TKey key, TValue value); void AddDictionary(Expression>> provider, TKey key, TValue value); - void AddDictionary(Expression>> provider, Dictionary value); - void AddDictionary(Expression>> provider, ReadOnlyDictionary value); + + void AddDictionary(Expression>> provider, + Dictionary value); + + void AddDictionary(Expression>> provider, + ReadOnlyDictionary value); + void RemoveDictionary(Expression>> provider, TKey key); void ClearDictionary(Expression>> provider); @@ -88,6 +93,7 @@ private static JsonSerializerOptions CreateSerializerOptions() DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, WriteIndented = true, }; + options.Converters.Add(new TypeConverter()); options.Converters.Add(LookupTableConverter); options.Converters.Add(new EnumerationJsonConverterFactory()); @@ -147,29 +153,34 @@ private T Get(LambdaExpression provider) #region Dictionary - private void UsingDictionary(Expression>> provider, Action> action) + private void UsingDictionary(Expression>> provider, + Action> action) { var dictionary = Get>(provider) ?? new Dictionary(); action.Invoke(dictionary); Set(provider, dictionary); } - void IOptions.SetDictionary(Expression>> provider, TKey key, TValue value) + void IOptions.SetDictionary(Expression>> provider, TKey key, + TValue value) { UsingDictionary(provider, dictionary => dictionary[key] = value); } - void IOptions.AddDictionary(Expression>> provider, TKey key, TValue value) + void IOptions.AddDictionary(Expression>> provider, TKey key, + TValue value) { UsingDictionary(provider, dictionary => dictionary.Add(key, value)); } - void IOptions.AddDictionary(Expression>> provider, Dictionary value) + void IOptions.AddDictionary(Expression>> provider, + Dictionary value) { UsingDictionary(provider, dictionary => dictionary.AddDictionary(value)); } - void IOptions.AddDictionary(Expression>> provider, ReadOnlyDictionary value) + void IOptions.AddDictionary(Expression>> provider, + ReadOnlyDictionary value) { UsingDictionary(provider, dictionary => dictionary.AddDictionary(value)); } @@ -188,7 +199,8 @@ void IOptions.ClearDictionary(Expression(Expression>> provider, Action> action) + private void UsingLookup(Expression>> provider, + Action> action) { var lookup = Get>(provider) ?? new LookupTable(); action.Invoke(lookup); diff --git a/src/Fallout.Tooling/Output.cs b/src/Fallout.Tooling/Output.cs index 08d165a05..0a0947804 100644 --- a/src/Fallout.Tooling/Output.cs +++ b/src/Fallout.Tooling/Output.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tooling; +namespace Fallout.Common.Tooling; public struct Output { diff --git a/src/Fallout.Tooling/OutputType.cs b/src/Fallout.Tooling/OutputType.cs index 034f48fde..ba64f65de 100644 --- a/src/Fallout.Tooling/OutputType.cs +++ b/src/Fallout.Tooling/OutputType.cs @@ -1,7 +1,4 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tooling; +namespace Fallout.Common.Tooling; public enum OutputType { diff --git a/src/Fallout.Tooling/PaketPackageResolver.cs b/src/Fallout.Tooling/PaketPackageResolver.cs index b7647bbfa..005bea690 100644 --- a/src/Fallout.Tooling/PaketPackageResolver.cs +++ b/src/Fallout.Tooling/PaketPackageResolver.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Tooling/Process2.cs b/src/Fallout.Tooling/Process2.cs index 6b76aecd8..a2b847209 100644 --- a/src/Fallout.Tooling/Process2.cs +++ b/src/Fallout.Tooling/Process2.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace Fallout.Common.Tooling; @@ -25,7 +24,7 @@ public Process2(Process process, Func outputFilter, int? timeout public string WorkingDirectory => process.StartInfo.WorkingDirectory; - public IReadOnlyCollection Output { get; private set; } + public IReadOnlyCollection Output { get; } public int ExitCode => process.ExitCode; @@ -49,7 +48,10 @@ public bool WaitForExit() // use process.StartTime var hasExited = process.WaitForExit(timeout ?? -1); if (!hasExited) + { process.Kill(); + } + return hasExited; } } diff --git a/src/Fallout.Tooling/ProcessExtensions.cs b/src/Fallout.Tooling/ProcessExtensions.cs index 40be83ec9..8ba46c233 100644 --- a/src/Fallout.Tooling/ProcessExtensions.cs +++ b/src/Fallout.Tooling/ProcessExtensions.cs @@ -25,7 +25,9 @@ public static IProcess AssertZeroExitCode( process.AssertWaitForExit(); if (process.ExitCode != 0) + { throw new ProcessException(process); + } return process; } @@ -33,7 +35,9 @@ public static IProcess AssertZeroExitCode( public static IReadOnlyCollection EnsureOnlyStd(this IReadOnlyCollection output) { foreach (var o in output) + { Assert.True(o.Type == OutputType.Std); + } return output; } diff --git a/src/Fallout.Tooling/ProcessTasks.cs b/src/Fallout.Tooling/ProcessTasks.cs index 87fd97d4f..c7f21acfd 100644 --- a/src/Fallout.Tooling/ProcessTasks.cs +++ b/src/Fallout.Tooling/ProcessTasks.cs @@ -19,7 +19,11 @@ public static class ProcessTasks public static bool LogWorkingDirectory = true; public static string DefaultWorkingDirectory = EnvironmentInfo.WorkingDirectory; - private static readonly char[] pathSeparators = { EnvironmentInfo.IsWin ? ';' : ':' }; + private static readonly char[] pathSeparators = + { + EnvironmentInfo.IsWin ? ';' : ':' + }; + private static readonly object syncLock = new(); public static IProcess StartShell( @@ -46,7 +50,6 @@ public static IProcess StartShell( } #if NET6_0_OR_GREATER - public static IProcess StartProcess( string toolPath, ArgumentStringHandler arguments, @@ -82,9 +85,11 @@ public static IProcess StartProcess( Action logger = null, Func outputFilter = null) { - Assert.NotNull(toolPath); + toolPath.NotNull(); if (!Path.IsPathRooted(toolPath) && !toolPath.Contains(Path.DirectorySeparatorChar)) + { toolPath = ToolPathResolver.GetPathExecutable(toolPath); + } var toolPathOverride = GetToolPathOverride(toolPath); if (!string.IsNullOrEmpty(toolPathOverride)) @@ -117,7 +122,9 @@ private static string GetToolPathOverride(string toolPath) if (EnvironmentInfo.IsUnix && toolPath.EndsWithOrdinalIgnoreCase(".exe") && !EnvironmentInfo.IsWsl) + { return ToolPathResolver.GetPathExecutable("mono"); + } return null; } @@ -137,30 +144,36 @@ private static IProcess StartProcessInternal( Assert.DirectoryExists(workingDirectory); var startInfo = new ProcessStartInfo - { - FileName = toolPath, - Arguments = arguments , - WorkingDirectory = workingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - StandardErrorEncoding = Encoding.UTF8, - StandardOutputEncoding = Encoding.UTF8 - }; + { + FileName = toolPath, + Arguments = arguments, + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + StandardErrorEncoding = Encoding.UTF8, + StandardOutputEncoding = Encoding.UTF8 + }; if (environmentVariables != null) { startInfo.Environment.Clear(); foreach (var (key, value) in environmentVariables) + { startInfo.Environment[key] = value; + } } if (logInvocation) + { LogInvocation(startInfo, outputFilter); + } var process = Process.Start(startInfo); if (process == null) + { return null; + } var output = GetOutputCollection(process, logger, outputFilter); return new Process2(process, outputFilter, timeout, output); @@ -171,7 +184,8 @@ private static void LogInvocation(ProcessStartInfo startInfo, Func {ToolPath} {Arguments}", startInfo.FileName.DoubleQuoteIfNeeded(), outputFilter(startInfo.Arguments)); + Log.Information("> {ToolPath} {Arguments}", startInfo.FileName.DoubleQuoteIfNeeded(), + outputFilter(startInfo.Arguments)); if (LogWorkingDirectory) { @@ -198,19 +212,34 @@ private static BlockingCollection GetOutputCollection( process.OutputDataReceived += (_, e) => { if (e.Data == null) + { return; + } var filteredOutput = outputFilter(e.Data); - output.Add(new Output { Text = filteredOutput, Type = OutputType.Std }); + output.Add(new Output + { + Text = filteredOutput, + Type = OutputType.Std + }); + logger?.Invoke(OutputType.Std, filteredOutput); }; + process.ErrorDataReceived += (_, e) => { if (e.Data == null) + { return; + } var filteredOutput = outputFilter(e.Data); - output.Add(new Output { Text = filteredOutput, Type = OutputType.Err }); + output.Add(new Output + { + Text = filteredOutput, + Type = OutputType.Err + }); + logger?.Invoke(OutputType.Err, filteredOutput); }; @@ -223,9 +252,13 @@ private static BlockingCollection GetOutputCollection( public static void DefaultLogger(OutputType type, string output) { if (type == OutputType.Std) + { Log.Debug(output); + } else + { Log.Error(output); + } } public static void PrintEnvironmentVariables() @@ -241,7 +274,10 @@ private static void PrintEnvironmentVariables(IDictionary enviro var padding = values.Length.ToString().Length; return values.Length == 1 - ? new[] { (pair.Key, values.Single()) } + ? new[] + { + (pair.Key, values.Single()) + } : values.Select((x, i) => ($"{pair.Key}[{i.ToString().PadLeft(padding, paddingChar: '0')}]", x)); } diff --git a/src/Fallout.Tooling/Tool.cs b/src/Fallout.Tooling/Tool.cs index 83d70f870..c2a807b76 100644 --- a/src/Fallout.Tooling/Tool.cs +++ b/src/Fallout.Tooling/Tool.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Tooling/ToolExecutor.cs b/src/Fallout.Tooling/ToolExecutor.cs index 9be1603a9..86b38f196 100644 --- a/src/Fallout.Tooling/ToolExecutor.cs +++ b/src/Fallout.Tooling/ToolExecutor.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Tooling; diff --git a/src/Fallout.Tooling/ToolOptions.Arguments.cs b/src/Fallout.Tooling/ToolOptions.Arguments.cs index b15362e21..efe96208d 100644 --- a/src/Fallout.Tooling/ToolOptions.Arguments.cs +++ b/src/Fallout.Tooling/ToolOptions.Arguments.cs @@ -12,34 +12,44 @@ namespace Fallout.Common.Tooling; public class CommandAttribute : Attribute { public Type Type { get; set; } + public string Command { get; set; } + public string Arguments { get; set; } } public class ArgumentEscapeAttribute : Attribute { public Type Type { get; set; } + public string Method { get; set; } } public class ArgumentAttribute : Attribute { public int Position { get; set; } + public string Format { get; set; } + public string AlternativeFormat { get; set; } + public bool Secret { get; set; } public Type FormatterType { get; set; } + public string FormatterMethod { get; set; } public string Separator { get; set; } + public string InnerSeparator { get; set; } + public bool QuoteMultiple { get; set; } } public class BuilderAttribute : Attribute { public Type Type { get; set; } + public string Property { get; set; } } @@ -52,7 +62,9 @@ internal partial IEnumerable GetArguments() { var commandAttribute = GetType().GetCustomAttribute(); if (commandAttribute?.Arguments != null) + { yield return commandAttribute.Arguments; + } var escapeMethod = CreateEscape(); var arguments = InternalOptions @@ -66,14 +78,18 @@ internal partial IEnumerable GetArguments() .Concat(ProcessAdditionalArguments ?? []); foreach (var argument in arguments) + { yield return argument; + } Func CreateEscape() { var toolType = commandAttribute?.Type; var escapeAttribute = toolType?.GetCustomAttribute(); if (escapeAttribute == null) + { return (x, _) => x.DoubleQuoteIfNeeded(); + } var escapeType = escapeAttribute.Type ?? GetType(); var formatterMethod = escapeType.GetMethod(escapeAttribute.Method, ReflectionUtility.All); @@ -91,16 +107,24 @@ private IEnumerable GetArgument( var formatParts = format.SplitSpace(); if (!property.PropertyType.IsGenericType || property.PropertyType.IsNullableType()) + { return GetScalarArguments(); + } if (property.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyList<>)) + { return GetListArguments(); + } if (property.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)) + { return GetDictionaryArguments(); + } if (property.PropertyType.GetGenericTypeDefinition() == typeof(ILookup<,>)) + { return GetLookupArguments(); + } return []; @@ -118,14 +142,25 @@ string Parse(JsonNode token, Type type) object[] formatterArgs = formatterMethod.GetParameters().Length == 1 ? [objValue] : [objValue, property]; + value = formatterMethod.GetValue(obj: this, args: formatterArgs); } else { value = NodeToString(token); - if (new[] { typeof(bool), typeof(bool?) }.Contains(type) || - (type == typeof(object) && new[] { bool.TrueString, bool.FalseString }.Contains(value))) + if (new[] + { + typeof(bool), + typeof(bool?) + }.Contains(type) || + (type == typeof(object) && new[] + { + bool.TrueString, + bool.FalseString + }.Contains(value))) + { value = value?.ToLowerInvariant(); + } } return escape.Invoke(value, property); @@ -136,14 +171,20 @@ IEnumerable GetScalarArguments() if (property.PropertyType == typeof(bool?) && !format.ContainsOrdinalIgnoreCase(ValuePlaceholder) && !node.GetValue()) + { yield break; + } var value = Parse(node, property.PropertyType); if (value.IsNullOrWhiteSpace()) + { yield break; + } foreach (var part in formatParts) + { yield return part.Replace(ValuePlaceholder, value); + } } IEnumerable GetListArguments() @@ -157,22 +198,28 @@ IEnumerable GetListArguments() { Assert.False(attribute.QuoteMultiple); return from value in values - from part in formatParts - select part.Replace(ValuePlaceholder, value); + from part in formatParts + select part.Replace(ValuePlaceholder, value); } return formatParts.SelectMany(part => { var valueStart = part.IndexOf(ValuePlaceholder, StringComparison.OrdinalIgnoreCase); if (valueStart == -1) + { return [part]; + } if (attribute.Separator.IsNullOrWhiteSpace() && !attribute.QuoteMultiple) + { return values.Select(x => part.Replace(ValuePlaceholder, x)); + } var joinedValues = values.Join(attribute.Separator); if (attribute.QuoteMultiple) + { joinedValues = joinedValues.DoubleQuote(); + } return [part.Replace(ValuePlaceholder, joinedValues)]; }); @@ -181,26 +228,31 @@ from part in formatParts IEnumerable GetDictionaryArguments() { var valueType = property.PropertyType.GetGenericArguments().Last(); - var pairs = node.AsObject().Select(kv => (Key: kv.Key, Value: Parse(kv.Value, valueType))) + var pairs = node.AsObject().Select(kv => (kv.Key, Value: Parse(kv.Value, valueType))) .Where(x => !x.Value.IsNullOrWhiteSpace()); if (attribute.Separator == null) { return from pair in pairs - from part in formatParts - select part.Replace(KeyPlaceholder, pair.Key).Replace(ValuePlaceholder, pair.Value); + from part in formatParts + select part.Replace(KeyPlaceholder, pair.Key).Replace(ValuePlaceholder, pair.Value); } return formatParts.SelectMany(part => { var pairStart = part.IndexOf(KeyPlaceholder, StringComparison.OrdinalIgnoreCase); - var pairLength = part.IndexOf(ValuePlaceholder, StringComparison.OrdinalIgnoreCase) - pairStart + ValuePlaceholder.Length; + var pairLength = part.IndexOf(ValuePlaceholder, StringComparison.OrdinalIgnoreCase) - pairStart + + ValuePlaceholder.Length; if (pairStart == -1) + { return [part]; + } if (attribute.Separator.IsNullOrWhiteSpace()) + { return pairs.Select(x => part.Replace(KeyPlaceholder, x.Key).Replace(ValuePlaceholder, x.Value)); + } var pairPart = part.Substring(pairStart, pairLength); var replacedPairs = pairs.Select(x => pairPart.Replace(KeyPlaceholder, x.Key).Replace(ValuePlaceholder, x.Value)); @@ -212,40 +264,51 @@ IEnumerable GetLookupArguments() { var valueType = property.PropertyType.GetGenericArguments().Last(); var pairs = node.AsObject() - .Select(kv => (Key: kv.Key, Values: kv.Value.AsArray().Select(x => Parse(x, valueType)))); + .Select(kv => (kv.Key, Values: kv.Value.AsArray().Select(x => Parse(x, valueType)))); if (attribute.Separator == null) { if (attribute.InnerSeparator == null) { return from pair in pairs - from value in pair.Values - from part in formatParts - select part.Replace(KeyPlaceholder, pair.Key).Replace(ValuePlaceholder, value); + from value in pair.Values + from part in formatParts + select part.Replace(KeyPlaceholder, pair.Key).Replace(ValuePlaceholder, value); } else { return from pair in pairs - from part in formatParts - select part.Replace(KeyPlaceholder, pair.Key).Replace(ValuePlaceholder, pair.Values.Join(attribute.InnerSeparator)); + from part in formatParts + select part.Replace(KeyPlaceholder, pair.Key) + .Replace(ValuePlaceholder, pair.Values.Join(attribute.InnerSeparator)); } } - Assert.NotNull(attribute.InnerSeparator); + attribute.InnerSeparator.NotNull(); return formatParts.SelectMany(part => { var pairStart = part.IndexOf(KeyPlaceholder, StringComparison.OrdinalIgnoreCase); - var pairLength = part.IndexOf(ValuePlaceholder, StringComparison.OrdinalIgnoreCase) - pairStart + ValuePlaceholder.Length; + var pairLength = part.IndexOf(ValuePlaceholder, StringComparison.OrdinalIgnoreCase) - pairStart + + ValuePlaceholder.Length; if (pairStart == -1) + { return [part]; + } if (attribute.Separator.IsNullOrWhiteSpace()) - return pairs.Select(x => part.Replace(KeyPlaceholder, x.Key).Replace(ValuePlaceholder, x.Values.Join(attribute.InnerSeparator))).ToArray(); + { + return pairs.Select(x => + part.Replace(KeyPlaceholder, x.Key) + .Replace(ValuePlaceholder, x.Values.Join(attribute.InnerSeparator))) + .ToArray(); + } var pairPart = part.Substring(pairStart, pairLength); - var replacedPairs = pairs.Select(x => pairPart.Replace(KeyPlaceholder, x.Key).Replace(ValuePlaceholder, x.Values.Join(attribute.InnerSeparator))); + var replacedPairs = pairs.Select(x => + pairPart.Replace(KeyPlaceholder, x.Key).Replace(ValuePlaceholder, x.Values.Join(attribute.InnerSeparator))); + return [part.Substring(startIndex: 0, length: pairStart) + replacedPairs.Join(attribute.Separator)]; }); } @@ -255,7 +318,11 @@ from part in formatParts // string regardless of underlying kind. STJ's GetValue() throws on non-string nodes. private static string NodeToString(JsonNode node) { - if (node is null) return null; + if (node is null) + { + return null; + } + return node.GetValueKind() switch { JsonValueKind.String => node.GetValue(), @@ -276,8 +343,11 @@ private static object DeserializeWithCoercion(JsonNode token, Type type) { var target = Nullable.GetUnderlyingType(type) ?? type; if (target == typeof(bool) && bool.TryParse(jv.GetValue(), out var coerced)) + { return coerced; + } } - return token.Deserialize(type, Options.SerializerOptions); + + return token.Deserialize(type, SerializerOptions); } } diff --git a/src/Fallout.Tooling/ToolOptions.Properties.cs b/src/Fallout.Tooling/ToolOptions.Properties.cs index f9e89f492..046847fa7 100644 --- a/src/Fallout.Tooling/ToolOptions.Properties.cs +++ b/src/Fallout.Tooling/ToolOptions.Properties.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using Serilog.Events; + // ReSharper disable ArrangeMethodOrOperatorBody namespace Fallout.Common.Tooling; @@ -15,7 +15,8 @@ partial class ToolOptions public string ProcessWorkingDirectory => Get(() => ProcessWorkingDirectory); /// Collection of environment variables to be passed to the process. By default, the environment variables of the current process are used. - public IReadOnlyDictionary ProcessEnvironmentVariables => Get>(() => ProcessEnvironmentVariables); + public IReadOnlyDictionary ProcessEnvironmentVariables => + Get>(() => ProcessEnvironmentVariables); /// Defines the execution timeout of the invoked process. public int? ProcessExecutionTimeout => Get(() => ProcessExecutionTimeout); @@ -42,7 +43,8 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessToolPath))] - public static T SetProcessToolPath(this T o, string value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessToolPath, value)); + public static T SetProcessToolPath(this T o, string value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessToolPath, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessToolPath))] @@ -54,11 +56,13 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessWorkingDirectory))] - public static T SetProcessWorkingDirectory(this T o, string value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessWorkingDirectory, value)); + public static T SetProcessWorkingDirectory(this T o, string value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessWorkingDirectory, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessWorkingDirectory))] - public static T ResetProcessWorkingDirectory(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessWorkingDirectory)); + public static T ResetProcessWorkingDirectory(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessWorkingDirectory)); #endregion @@ -66,43 +70,53 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T SetProcessEnvironmentVariables(this T o, ReadOnlyDictionary values) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessEnvironmentVariables, values)); + public static T SetProcessEnvironmentVariables(this T o, ReadOnlyDictionary values) + where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessEnvironmentVariables, values)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T SetProcessEnvironmentVariables(this T o, Dictionary values) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessEnvironmentVariables, values)); + public static T SetProcessEnvironmentVariables(this T o, Dictionary values) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessEnvironmentVariables, values)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T AddProcessEnvironmentVariables(this T o, ReadOnlyDictionary values) where T : ToolOptions => o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, values)); + public static T AddProcessEnvironmentVariables(this T o, ReadOnlyDictionary values) + where T : ToolOptions => o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, values)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T AddProcessEnvironmentVariables(this T o, Dictionary values) where T : ToolOptions => o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, values)); + public static T AddProcessEnvironmentVariables(this T o, Dictionary values) where T : ToolOptions => + o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, values)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T AddProcessEnvironmentVariable(this T o, string key, string value) where T : ToolOptions => o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, key, value)); + public static T AddProcessEnvironmentVariable(this T o, string key, string value) where T : ToolOptions => + o.Modify(b => b.AddDictionary(() => o.ProcessEnvironmentVariables, key, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T SetProcessEnvironmentVariable(this T o, string key, string value) where T : ToolOptions => o.Modify(b => b.SetDictionary(() => o.ProcessEnvironmentVariables, key, value)); + public static T SetProcessEnvironmentVariable(this T o, string key, string value) where T : ToolOptions => + o.Modify(b => b.SetDictionary(() => o.ProcessEnvironmentVariables, key, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T SetProcessEnvironmentVariable(this T o, string key, object value) where T : ToolOptions => o.Modify(b => b.SetDictionary(() => o.ProcessEnvironmentVariables, key, value.ToString())); + public static T SetProcessEnvironmentVariable(this T o, string key, object value) where T : ToolOptions => + o.Modify(b => b.SetDictionary(() => o.ProcessEnvironmentVariables, key, value.ToString())); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T RemoveProcessEnvironmentVariable(this T o, string key) where T : ToolOptions => o.Modify(b => b.RemoveDictionary(() => o.ProcessEnvironmentVariables, key)); + public static T RemoveProcessEnvironmentVariable(this T o, string key) where T : ToolOptions => + o.Modify(b => b.RemoveDictionary(() => o.ProcessEnvironmentVariables, key)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T ClearProcessEnvironmentVariables(this T o) where T : ToolOptions => o.Modify(b => b.ClearDictionary(() => o.ProcessEnvironmentVariables)); + public static T ClearProcessEnvironmentVariables(this T o) where T : ToolOptions => + o.Modify(b => b.ClearDictionary(() => o.ProcessEnvironmentVariables)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessEnvironmentVariables))] - public static T ResetProcessEnvironmentVariables(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessEnvironmentVariables)); + public static T ResetProcessEnvironmentVariables(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessEnvironmentVariables)); #endregion @@ -110,15 +124,18 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExecutionTimeout))] - public static T SetProcessExecutionTimeout(this T o, int? value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExecutionTimeout, value)); + public static T SetProcessExecutionTimeout(this T o, int? value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExecutionTimeout, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExecutionTimeout))] - public static T SetProcessExecutionTimeout(this T o, TimeSpan value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExecutionTimeout, value.TotalMilliseconds)); + public static T SetProcessExecutionTimeout(this T o, TimeSpan value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExecutionTimeout, value.TotalMilliseconds)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExecutionTimeout))] - public static T ResetProcessExecutionTimeout(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessExecutionTimeout)); + public static T ResetProcessExecutionTimeout(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessExecutionTimeout)); #endregion @@ -126,19 +143,23 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessOutputLogging))] - public static T SetProcessOutputLogging(this T o, bool? value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessOutputLogging, value)); + public static T SetProcessOutputLogging(this T o, bool? value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessOutputLogging, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessOutputLogging))] - public static T EnableProcessOutputLogging(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessOutputLogging, value: true)); + public static T EnableProcessOutputLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessOutputLogging, value: true)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessOutputLogging))] - public static T DisableProcessOutputLogging(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessOutputLogging, value: false)); + public static T DisableProcessOutputLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessOutputLogging, value: false)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessOutputLogging))] - public static T ResetProcessOutputLogging(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessOutputLogging)); + public static T ResetProcessOutputLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessOutputLogging)); #endregion @@ -146,19 +167,23 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessInvocationLogging))] - public static T SetProcessInvocationLogging(this T o, bool? value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value)); + public static T SetProcessInvocationLogging(this T o, bool? value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessInvocationLogging))] - public static T EnableProcessInvocationLogging(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value: true)); + public static T EnableProcessInvocationLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value: true)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessInvocationLogging))] - public static T DisableProcessInvocationLogging(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value: false)); + public static T DisableProcessInvocationLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessInvocationLogging, value: false)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessInvocationLogging))] - public static T ResetProcessInvocationLogging(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessInvocationLogging)); + public static T ResetProcessInvocationLogging(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessInvocationLogging)); #endregion @@ -166,23 +191,28 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExitHandling))] - public static T SetProcessExitHandling(this T o, bool? value) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExitHandling, value)); + public static T SetProcessExitHandling(this T o, bool? value) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExitHandling, value)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExitHandling))] - public static T EnableProcessExitHandling(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExitHandling, value: true)); + public static T EnableProcessExitHandling(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExitHandling, value: true)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExitHandling))] - public static T DisableProcessExitHandling(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExitHandling, value: false)); + public static T DisableProcessExitHandling(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExitHandling, value: false)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExitHandling))] - public static T ToggleProcessExitHandling(this T o) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessExitHandling, !o.ProcessExitHandling)); + public static T ToggleProcessExitHandling(this T o) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessExitHandling, !o.ProcessExitHandling)); /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessExitHandling))] - public static T ResetProcessExitHandling(this T o) where T : ToolOptions => o.Modify(b => b.Remove(() => o.ProcessExitHandling)); + public static T ResetProcessExitHandling(this T o) where T : ToolOptions => + o.Modify(b => b.Remove(() => o.ProcessExitHandling)); #endregion @@ -190,25 +220,38 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T SetProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessRedactedSecrets, v)); + public static T SetProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T SetProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessRedactedSecrets, v)); + public static T SetProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T AddProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.AddCollection(() => o.ProcessRedactedSecrets, v)); + public static T AddProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.AddCollection(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T AddProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.AddCollection(() => o.ProcessRedactedSecrets, v)); + public static T AddProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.AddCollection(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T RemoveProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.RemoveCollection(() => o.ProcessRedactedSecrets, v)); + public static T RemoveProcessRedactedSecrets(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.RemoveCollection(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T RemoveProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.RemoveCollection(() => o.ProcessRedactedSecrets, v)); + public static T RemoveProcessRedactedSecrets(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.RemoveCollection(() => o.ProcessRedactedSecrets, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessRedactedSecrets))] - public static T ClearProcessRedactedSecrets(this T o) where T : ToolOptions => o.Modify(b => b.ClearCollection(() => o.ProcessRedactedSecrets)); + public static T ClearProcessRedactedSecrets(this T o) where T : ToolOptions => + o.Modify(b => b.ClearCollection(() => o.ProcessRedactedSecrets)); #endregion @@ -216,25 +259,38 @@ public static partial class ToolOptionsExtensions /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T SetProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessAdditionalArguments, v)); + public static T SetProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T SetProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.Set(() => o.ProcessAdditionalArguments, v)); + public static T SetProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.Set(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T AddProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.AddCollection(() => o.ProcessAdditionalArguments, v)); + public static T AddProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.AddCollection(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T AddProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.AddCollection(() => o.ProcessAdditionalArguments, v)); + public static T AddProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.AddCollection(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T RemoveProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => o.Modify(b => b.RemoveCollection(() => o.ProcessAdditionalArguments, v)); + public static T RemoveProcessAdditionalArguments(this T o, params string[] v) where T : ToolOptions => + o.Modify(b => b.RemoveCollection(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T RemoveProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => o.Modify(b => b.RemoveCollection(() => o.ProcessAdditionalArguments, v)); + public static T RemoveProcessAdditionalArguments(this T o, IEnumerable v) where T : ToolOptions => + o.Modify(b => b.RemoveCollection(() => o.ProcessAdditionalArguments, v)); + /// [Builder(Type = typeof(ToolOptions), Property = nameof(ToolOptions.ProcessAdditionalArguments))] - public static T ClearProcessAdditionalArguments(this T o) where T : ToolOptions => o.Modify(b => b.ClearCollection(() => o.ProcessAdditionalArguments)); + public static T ClearProcessAdditionalArguments(this T o) where T : ToolOptions => + o.Modify(b => b.ClearCollection(() => o.ProcessAdditionalArguments)); #endregion } diff --git a/src/Fallout.Tooling/ToolPathResolver.cs b/src/Fallout.Tooling/ToolPathResolver.cs index 956253c4d..898ac4ed9 100644 --- a/src/Fallout.Tooling/ToolPathResolver.cs +++ b/src/Fallout.Tooling/ToolPathResolver.cs @@ -11,17 +11,22 @@ public static string TryGetEnvironmentExecutable(string environmentExecutable) { var environmentExecutablePath = EnvironmentInfo.GetVariable(environmentExecutable); if (environmentExecutablePath == null) + { return null; + } Assert.FileExists(environmentExecutablePath, $"Path '{environmentExecutablePath}' from environment variable '{environmentExecutable}' does not exist"); + return environmentExecutablePath; } public static string GetPathExecutable(string pathExecutable) { if (File.Exists(pathExecutable)) + { return Path.GetFullPath(pathExecutable); + } var locateExecutable = EnvironmentInfo.IsWin ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "where.exe") @@ -36,8 +41,13 @@ string GetExecutableFullPath(string path) : pathExecutable); var environmentVariable = Environment.GetEnvironmentVariable("PATH").NotNullOrEmpty("PATH variable not available"); - var paths = environmentVariable.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); - return paths.Select(GetExecutableFullPath).FirstOrDefault(File.Exists).NotNull($"Could not find '{pathExecutable}' on the PATH."); + var paths = environmentVariable.Split(new[] + { + Path.PathSeparator + }, StringSplitOptions.RemoveEmptyEntries); + + return paths.Select(GetExecutableFullPath).FirstOrDefault(File.Exists) + .NotNull($"Could not find '{pathExecutable}' on the PATH."); } var locateProcess = ProcessTasks.StartProcess( @@ -45,11 +55,12 @@ string GetExecutableFullPath(string path) pathExecutable, logOutput: false, logInvocation: false); + locateProcess.AssertWaitForExit(); return locateProcess.Output .Select(x => x.Text) - .Where(x => EnvironmentInfo.IsWin && Path.HasExtension(x) || EnvironmentInfo.IsUnix) + .Where(x => (EnvironmentInfo.IsWin && Path.HasExtension(x)) || EnvironmentInfo.IsUnix) .FirstOrDefault(File.Exists) .NotNull($"Could not find '{pathExecutable}' via '{locateExecutable}'."); } diff --git a/src/Fallout.Tooling/ToolRequirement.cs b/src/Fallout.Tooling/ToolRequirement.cs index 21e591c3e..a372e39a9 100644 --- a/src/Fallout.Tooling/ToolRequirement.cs +++ b/src/Fallout.Tooling/ToolRequirement.cs @@ -1,11 +1,8 @@ -using System; -using System.Linq; - -namespace Fallout.Common.Tooling; +namespace Fallout.Common.Tooling; public interface IRequireTool; -public interface IRequireToolWithVersion; +public interface IRequireToolWithVersion; public interface IRequirePathTool : IRequireTool; @@ -15,7 +12,6 @@ public interface IRequireNpmPackage : IRequireTool, IRequireToolWithVersion; public interface IRequireAptGetPackage : IRequireTool; - public class ToolRequirement; public class PathToolRequirement(string pathExecutable) : ToolRequirement @@ -26,12 +22,16 @@ public class PathToolRequirement(string pathExecutable) : ToolRequirement public class NuGetPackageRequirement(string packageId, string version = null) : ToolRequirement { public string PackageId { get; init; } = packageId; - public string Version { get; init; } = version ?? NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false).GetAwaiter().GetResult(); + + public string Version { get; init; } = version ?? + NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases: false) + .GetAwaiter().GetResult(); } public class NpmPackageRequirement(string packageId, string version = null) : ToolRequirement { public string PackageId { get; init; } = packageId; + public string Version { get; init; } = version ?? NpmVersionResolver.GetLatestVersion(packageId).GetAwaiter().GetResult(); } diff --git a/src/Fallout.Tooling/ToolResolver.cs b/src/Fallout.Tooling/ToolResolver.cs index c3b0c406f..88923229f 100644 --- a/src/Fallout.Tooling/ToolResolver.cs +++ b/src/Fallout.Tooling/ToolResolver.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Tooling; public static class ToolResolver @@ -27,7 +24,9 @@ public static Tool TryGetEnvironmentTool(string name) { var toolPath = ToolPathResolver.TryGetEnvironmentExecutable($"{name.ToUpperInvariant()}_EXE"); if (toolPath == null) + { return null; + } return GetTool(toolPath); } diff --git a/src/Fallout.Tooling/ToolTasks.ExitHandler.cs b/src/Fallout.Tooling/ToolTasks.ExitHandler.cs index f2dc6d02f..3b041c0ca 100644 --- a/src/Fallout.Tooling/ToolTasks.ExitHandler.cs +++ b/src/Fallout.Tooling/ToolTasks.ExitHandler.cs @@ -1,17 +1,21 @@ using System; -using System.Linq; namespace Fallout.Common.Tooling; public abstract partial class ToolTasks { - private Action GetExitHandlerInternal(ToolOptions options = null, Func exitHandler = null) + private Action GetExitHandlerInternal(ToolOptions options = null, + Func exitHandler = null) { if (options is { ProcessExitHandling: false }) + { return (_, _) => { }; + } if (exitHandler != null) + { return (_, p) => exitHandler.Invoke(p); + } return options?.ProcessExitHandler != null ? (o, p) => options.ProcessExitHandler.Invoke(o, p) diff --git a/src/Fallout.Tooling/ToolTasks.Logger.cs b/src/Fallout.Tooling/ToolTasks.Logger.cs index c15691ae2..8eb40b4ef 100644 --- a/src/Fallout.Tooling/ToolTasks.Logger.cs +++ b/src/Fallout.Tooling/ToolTasks.Logger.cs @@ -14,6 +14,7 @@ public class LogErrorAsStandard : Attribute; public class LogLevelPattern(LogEventLevel level, string pattern) : Attribute { public LogEventLevel Level { get; } = level; + public string Pattern { get; } = pattern; } @@ -28,7 +29,9 @@ public abstract partial class ToolTasks protected internal virtual partial Action GetLogger(ToolOptions options) { if (options?.ProcessLogger != null) + { return options.ProcessLogger; + } var toolType = GetType(); var levelProvider = toolType.GetCustomAttributes() @@ -45,11 +48,17 @@ protected internal virtual partial Action GetLogger(ToolOpti { var patternLevel = levelProvider.Select(x => x.Invoke(text)).FirstOrDefault(x => x != null); if (patternLevel != null) + { Log.Write(patternLevel.Value, text); + } else if (type == OutputType.Err && !errorAsStandard) + { Log.Error(text); + } else + { Log.Write(defaultLevel, text); + } }; } } diff --git a/src/Fallout.Tooling/ToolTasks.Run.cs b/src/Fallout.Tooling/ToolTasks.Run.cs index 1baad0f74..19363455a 100644 --- a/src/Fallout.Tooling/ToolTasks.Run.cs +++ b/src/Fallout.Tooling/ToolTasks.Run.cs @@ -46,12 +46,12 @@ protected virtual partial (TResult Result, IReadOnlyCollection Output) R } catch (Exception exception) { - throw new Exception($"Cannot parse {typeof(TResult).Name} from output:".Concat(output.Select(x => x.Text)).JoinNewLine(), exception); + throw new Exception( + $"Cannot parse {typeof(TResult).Name} from output:".Concat(output.Select(x => x.Text)).JoinNewLine(), exception); } } #if NET6_0_OR_GREATER - protected IReadOnlyCollection Run( ArgumentStringHandler arguments, string workingDirectory = null, @@ -107,6 +107,7 @@ public static T Resolve() var applicableTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()) .Where(x => x.IsAssignableTo(typeof(T))) .OrderByDescending(x => x.Descendants(x => x.BaseType).Count()); + var mostDerivedType = applicableTypes.First(); return mostDerivedType.CreateInstance(); } diff --git a/src/Fallout.Tooling/ToolTasks.ToolPath.cs b/src/Fallout.Tooling/ToolTasks.ToolPath.cs index 667d77fb3..c3f0959b0 100644 --- a/src/Fallout.Tooling/ToolTasks.ToolPath.cs +++ b/src/Fallout.Tooling/ToolTasks.ToolPath.cs @@ -17,10 +17,14 @@ public abstract partial class ToolTasks protected internal string GetToolPathInternal(ToolOptions options = null) { if (options?.ProcessToolPath != null) + { return options.ProcessToolPath; + } if (ToolPathResolver.TryGetEnvironmentExecutable(ToolPathOverrideVariableName) is { } environmentExecutable) + { return environmentExecutable; + } return GetToolPath(options); } @@ -30,9 +34,13 @@ protected virtual partial string GetToolPath(ToolOptions options) var toolType = GetType(); var attribute = toolType.GetCustomAttribute(); if (attribute != null) + { return attribute.GetToolPath(options); + } + + Assert.Fail( + $"Unable to resolve tool path for {toolType.Name}. Set via {nameof(ToolOptionsExtensions.SetProcessToolPath)}."); - Assert.Fail($"Unable to resolve tool path for {toolType.Name}. Set via {nameof(ToolOptionsExtensions.SetProcessToolPath)}."); return null; } @@ -71,6 +79,7 @@ internal override ToolRequirement GetRequirement(string version = null) public class NpmToolAttribute : ToolAttribute { public string Id { get; set; } + public string Executable { get; set; } internal override string GetToolPath(ToolOptions options) @@ -102,6 +111,7 @@ internal override ToolRequirement GetRequirement(string version = null) public class NuGetToolAttribute : ToolAttribute { public string Id { get; set; } + public string Executable { get; set; } internal override string GetToolPath(ToolOptions options) @@ -129,5 +139,6 @@ public interface IToolOptionsWithFramework public static class ToolOptionsWithFrameworkExtensions { [Builder(Type = typeof(IToolOptionsWithFramework), Property = nameof(IToolOptionsWithFramework.Framework))] - public static T SetFramework(this T o, string v) where T : Options, IToolOptionsWithFramework => o.Modify(b => b.Set(() => o.Framework, v)); + public static T SetFramework(this T o, string v) where T : Options, IToolOptionsWithFramework => + o.Modify(b => b.Set(() => o.Framework, v)); } diff --git a/src/Fallout.Tooling/ToolTasks.cs b/src/Fallout.Tooling/ToolTasks.cs index 3bd6fb0bc..1dbafd8c4 100644 --- a/src/Fallout.Tooling/ToolTasks.cs +++ b/src/Fallout.Tooling/ToolTasks.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Tooling; @@ -10,7 +9,10 @@ public abstract partial class ToolTasks protected virtual partial string GetToolPath(ToolOptions options = null); protected virtual partial IReadOnlyCollection Run(T options = null) where T : ToolOptions, new(); - protected virtual partial (TResult Result, IReadOnlyCollection Output) Run(TOptions options = null) where TOptions : ToolOptions, new(); + + protected virtual partial (TResult Result, IReadOnlyCollection Output) Run(TOptions options = null) + where TOptions : ToolOptions, new(); + protected virtual partial Func GetExitHandler(ToolOptions options = null); protected virtual T PreProcess(T options) where T : ToolOptions => options; diff --git a/src/Fallout.Tooling/ToolingExtensions.cs b/src/Fallout.Tooling/ToolingExtensions.cs index 640bada62..6895b59b3 100644 --- a/src/Fallout.Tooling/ToolingExtensions.cs +++ b/src/Fallout.Tooling/ToolingExtensions.cs @@ -15,13 +15,19 @@ public static AbsolutePath SetExecutable(this AbsolutePath path, bool updateVcsI if (updateVcsIndex) { if (path.Descendants(x => x.Parent).Any(x => x.ContainsDirectory(".git"))) + { ProcessTasks.StartProcess("git", $"update-index --add --chmod=+x {path}", logInvocation: false, logOutput: false); + } else if (path.Descendants(x => x.Parent).Any(x => x.ContainsDirectory(".svn"))) + { ProcessTasks.StartProcess("svn", $"propset svn:executable on {path}", logInvocation: false, logOutput: false); + } } if (EnvironmentInfo.IsUnix) + { ProcessTasks.StartProcess("chmod", $"+x {path}", logInvocation: false, logOutput: false); + } return path; } @@ -29,7 +35,9 @@ public static AbsolutePath SetExecutable(this AbsolutePath path, bool updateVcsI public static AbsolutePath AddUnixSymlink(this AbsolutePath path, AbsolutePath linkPath, bool force = false) { if (EnvironmentInfo.IsUnix) + { ProcessTasks.StartProcess("ln", $"-s{(force ? "f" : "")} {path} {linkPath}", logInvocation: false, logOutput: false); + } return path; } @@ -37,7 +45,9 @@ public static AbsolutePath AddUnixSymlink(this AbsolutePath path, AbsolutePath l public static AbsolutePath SetUnixPermissions(this AbsolutePath path, string permissions) { if (EnvironmentInfo.IsUnix) + { ProcessTasks.StartProcess("chmod", $"{permissions} {path}", logInvocation: false, logOutput: false); + } return path; } diff --git a/src/Fallout.Utilities.IO.Compression/CompressionExtensions.cs b/src/Fallout.Utilities.IO.Compression/CompressionExtensions.cs index 85c69b524..f6d8df56b 100644 --- a/src/Fallout.Utilities.IO.Compression/CompressionExtensions.cs +++ b/src/Fallout.Utilities.IO.Compression/CompressionExtensions.cs @@ -3,11 +3,12 @@ using System.IO; using System.IO.Compression; using System.Linq; +using Fallout.Common.Utilities.Collections; using ICSharpCode.SharpZipLib.BZip2; using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; -using Fallout.Common.Utilities.Collections; +using ZipFile = ICSharpCode.SharpZipLib.Zip.ZipFile; namespace Fallout.Common.IO; @@ -16,25 +17,41 @@ public static class CompressionExtensions public static void CompressTo(this AbsolutePath directory, AbsolutePath archiveFile, Func filter = null) { if (archiveFile.HasExtension(".zip")) + { directory.ZipTo(archiveFile, filter); + } else if (archiveFile.HasExtension(".tar.gz", ".tgz")) + { directory.TarGZipTo(archiveFile, filter); + } else if (archiveFile.HasExtension(".tar.bz2", ".tbz2", ".tbz")) + { directory.TarBZip2To(archiveFile, filter); + } else + { Assert.Fail($"Unknown archive extension for archive '{Path.GetFileName(archiveFile)}'"); + } } public static void UncompressTo(this AbsolutePath archiveFile, AbsolutePath directory) { if (archiveFile.HasExtension(".zip")) + { archiveFile.UnZipTo(directory); + } else if (archiveFile.HasExtension(".tar.gz", ".tgz")) + { archiveFile.UnTarGZipTo(directory); + } else if (archiveFile.HasExtension(".tar.bz2", ".tbz2", ".tbz")) + { archiveFile.UnTarBZip2To(directory); + } else + { Assert.Fail($"Unknown archive extension for archive '{Path.GetFileName(archiveFile)}'"); + } } public static void ZipTo( @@ -65,7 +82,7 @@ void AddFile(AbsolutePath file) public static void UnZipTo(this AbsolutePath archiveFile, AbsolutePath directory) { using var fileStream = File.OpenRead(archiveFile); - using var zipFile = new ICSharpCode.SharpZipLib.Zip.ZipFile(fileStream); + using var zipFile = new ZipFile(fileStream); var entries = zipFile.Cast().Where(x => !x.IsDirectory); diff --git a/src/Fallout.Utilities.IO.Globbing/Globbing.cs b/src/Fallout.Utilities.IO.Globbing/Globbing.cs index c80710f10..0b920523b 100644 --- a/src/Fallout.Utilities.IO.Globbing/Globbing.cs +++ b/src/Fallout.Utilities.IO.Globbing/Globbing.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using GlobExpressions; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using GlobExpressions; namespace Fallout.Common.IO; @@ -23,7 +22,9 @@ private static GlobOptions GlobOptions public static IReadOnlyCollection GlobFiles(string directory, params string[] patterns) { if (patterns.Length == 0) + { (directory, patterns) = GetGlobFromSingleDefinition(directory); + } var directoryInfo = new DirectoryInfo(directory); return patterns.SelectMany(x => Glob.Files(directoryInfo, x, GlobOptions)).Select(x => x.FullName).ToList(); @@ -31,13 +32,15 @@ public static IReadOnlyCollection GlobFiles(string directory, params str public static IReadOnlyCollection GlobFiles(this AbsolutePath directory, params string[] patterns) { - return GlobFiles((string) directory, patterns).Select(x => (AbsolutePath) x).ToList(); + return GlobFiles((string)directory, patterns).Select(x => (AbsolutePath)x).ToList(); } public static IReadOnlyCollection GlobDirectories(string directory, params string[] patterns) { if (patterns.Length == 0) + { (directory, patterns) = GetGlobFromSingleDefinition(directory); + } var directoryInfo = new DirectoryInfo(directory); return patterns.SelectMany(x => Glob.Directories(directoryInfo, x, GlobOptions)).Select(x => x.FullName).ToList(); @@ -45,13 +48,18 @@ public static IReadOnlyCollection GlobDirectories(string directory, para public static IReadOnlyCollection GlobDirectories(this AbsolutePath directory, params string[] patterns) { - return GlobDirectories((string) directory, patterns).Select(x => (AbsolutePath) x).ToList(); + return GlobDirectories((string)directory, patterns).Select(x => (AbsolutePath)x).ToList(); } private static (string Directory, string[] Patterns) GetGlobFromSingleDefinition(AbsolutePath definition) { - var directory = definition.DescendantsAndSelf(x => x.Parent).FirstOrDefault(x => !x.ToString().ContainsOrdinalIgnoreCase("*")); + var directory = definition.DescendantsAndSelf(x => x.Parent) + .FirstOrDefault(x => !x.ToString().ContainsOrdinalIgnoreCase("*")); + var pattern = definition.ToString().TrimStart(directory).TrimStart(PathConstruction.AllSeparators); - return (directory, new[] { pattern }); + return (directory, new[] + { + pattern + }); } } diff --git a/src/Fallout.Utilities.IO.Globbing/GlobbingCaseSensitivity.cs b/src/Fallout.Utilities.IO.Globbing/GlobbingCaseSensitivity.cs index fe8829bc4..ef2110d15 100644 --- a/src/Fallout.Utilities.IO.Globbing/GlobbingCaseSensitivity.cs +++ b/src/Fallout.Utilities.IO.Globbing/GlobbingCaseSensitivity.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.IO; /// diff --git a/src/Fallout.Utilities.Net/HttpClient.Request.cs b/src/Fallout.Utilities.Net/HttpClient.Request.cs index 62e75fd9d..f3f9ee90a 100644 --- a/src/Fallout.Utilities.Net/HttpClient.Request.cs +++ b/src/Fallout.Utilities.Net/HttpClient.Request.cs @@ -1,10 +1,9 @@ using System; -using System.Linq; using System.Net.Http; namespace Fallout.Common.Utilities.Net; -public static partial class HttpClientExtensions +public static class HttpClientExtensions { /// /// Creates an HTTP request. @@ -17,7 +16,8 @@ public static HttpRequestBuilder CreateRequest(this HttpClient client, HttpMetho /// /// Creates an HTTP request. /// - public static HttpRequestBuilder CreateRequest(this HttpClient client, HttpMethod method, string baseAddress, string relativeUri) + public static HttpRequestBuilder CreateRequest(this HttpClient client, HttpMethod method, string baseAddress, + string relativeUri) { return new HttpRequestBuilder(client, new HttpRequestMessage(method, new Uri(new Uri(baseAddress), relativeUri))); } @@ -32,5 +32,6 @@ public HttpRequestBuilder(HttpClient client, HttpRequestMessage request) } public HttpClient Client { get; } + public HttpRequestMessage Request { get; } } diff --git a/src/Fallout.Utilities.Net/HttpRequest.Authentication.cs b/src/Fallout.Utilities.Net/HttpRequest.Authentication.cs index 9d000161a..93ff387f4 100644 --- a/src/Fallout.Utilities.Net/HttpRequest.Authentication.cs +++ b/src/Fallout.Utilities.Net/HttpRequest.Authentication.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Net.Http.Headers; using System.Text; @@ -31,4 +30,4 @@ public static HttpRequestBuilder WithAuthentication(this HttpRequestBuilder buil builder.Request.Headers.Authorization = new AuthenticationHeaderValue(scheme, parameter); return builder; } -} \ No newline at end of file +} diff --git a/src/Fallout.Utilities.Net/HttpRequest.Content.cs b/src/Fallout.Utilities.Net/HttpRequest.Content.cs index d666ff822..1588b3092 100644 --- a/src/Fallout.Utilities.Net/HttpRequest.Content.cs +++ b/src/Fallout.Utilities.Net/HttpRequest.Content.cs @@ -40,7 +40,8 @@ public static HttpRequestBuilder WithStringContent(this HttpRequestBuilder build /// /// Sets the dictionary as content via . /// - public static HttpRequestBuilder WithFormUrlEncodedContent(this HttpRequestBuilder builder, IDictionary dictionary) + public static HttpRequestBuilder WithFormUrlEncodedContent(this HttpRequestBuilder builder, + IDictionary dictionary) { builder.Request.Content = new FormUrlEncodedContent(dictionary); return builder; @@ -78,7 +79,9 @@ public static MultipartFormDataContent AddStreamContent( { var streamContent = new StreamContent(content); if (mediaType != null) + { streamContent.Headers.ContentType = new MediaTypeHeaderValue(mediaType); + } data.Add(streamContent, name, filename); return data; diff --git a/src/Fallout.Utilities.Net/HttpResponse.Assert.cs b/src/Fallout.Utilities.Net/HttpResponse.Assert.cs index 4c6f51ece..247355e71 100644 --- a/src/Fallout.Utilities.Net/HttpResponse.Assert.cs +++ b/src/Fallout.Utilities.Net/HttpResponse.Assert.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Net; using System.Net.Http; using System.Runtime.Serialization; @@ -31,11 +30,14 @@ public static HttpResponseInspector AssertStatusCode( /// /// Asserts the status code of an HTTP response. /// - public static HttpResponseInspector AssertStatusCode(this HttpResponseInspector inspector, Func errorSelector) + public static HttpResponseInspector AssertStatusCode(this HttpResponseInspector inspector, + Func errorSelector) { var response = inspector.Response; if (errorSelector.Invoke(response.StatusCode) is { } error) + { throw new HttpResponseException(error); + } return inspector; } @@ -52,11 +54,14 @@ public static HttpResponseInspector AssertSuccessfulStatusCode(this HttpResponse /// /// Asserts an HTTP response. /// - public static HttpResponseInspector AssertResponse(this HttpResponseInspector inspector, Func errorSelector) + public static HttpResponseInspector AssertResponse(this HttpResponseInspector inspector, + Func errorSelector) { var response = inspector.Response; if (errorSelector.Invoke(response) is { } error) + { throw new HttpResponseException(error); + } return inspector; } diff --git a/src/Fallout.Utilities.Net/HttpResponse.Body.cs b/src/Fallout.Utilities.Net/HttpResponse.Body.cs index cc5a7f2a1..01f9d8869 100644 --- a/src/Fallout.Utilities.Net/HttpResponse.Body.cs +++ b/src/Fallout.Utilities.Net/HttpResponse.Body.cs @@ -40,7 +40,8 @@ public static async Task GetBodyAsJsonObject(this HttpResponseInspec return JsonNode.Parse(await inspector.GetBodyAsync(), nodeOptions: options)?.AsObject(); } - public static async Task WriteToFile(this HttpResponseInspector inspector, AbsolutePath path, FileMode mode = FileMode.CreateNew) + public static async Task WriteToFile(this HttpResponseInspector inspector, AbsolutePath path, + FileMode mode = FileMode.CreateNew) { using var fileStream = File.Open(path, mode); await inspector.Response.Content.CopyToAsync(fileStream); diff --git a/src/Fallout.Utilities/ArgumentParser.cs b/src/Fallout.Utilities/ArgumentParser.cs index 52be603be..5fa8fd0e3 100644 --- a/src/Fallout.Utilities/ArgumentParser.cs +++ b/src/Fallout.Utilities/ArgumentParser.cs @@ -6,7 +6,7 @@ namespace Fallout.Common; -internal class ArgumentParser +internal class ArgumentParser(IEnumerable arguments) { public static bool IsArgument(string value) { @@ -31,33 +31,33 @@ private static string[] Parse(string arguments) return arguments.Split((c, _) => { if (c == '\"' && !inSingleQuotes && !escaped) + { inDoubleQuotes = !inDoubleQuotes; + } if (c == '\'' && !inDoubleQuotes && !escaped) + { inSingleQuotes = !inSingleQuotes; + } escaped = c == '\\' && !escaped; return c == ' ' && !(inDoubleQuotes || inSingleQuotes); }, includeSplitCharacter: true) - .Select(x => x.Trim().TrimMatchingDoubleQuotes().TrimMatchingSingleQuotes().Replace("\\\"", "\"").Replace("\\\'", "'")) + .Select(x => x.Trim().TrimMatchingDoubleQuotes().TrimMatchingSingleQuotes().Replace("\\\"", "\"") + .Replace("\\\'", "'")) .Where(x => !string.IsNullOrEmpty(x)) .ToArray(); } - private readonly string[] arguments; + private readonly string[] arguments = arguments.ToArray(); public ArgumentParser(string arguments) : this(Parse(arguments)) { } - public ArgumentParser(IEnumerable arguments) - { - this.arguments = arguments.ToArray(); - } - public IReadOnlyList Arguments => arguments; public bool HasArgument(string argumentName) @@ -69,7 +69,9 @@ public object GetNamedArgument(string argumentName, Type destinationType, char? { var index = GetArgumentIndex(argumentName); if (index == -1) + { return destinationType.GetDefaultValue(); + } var values = arguments.Skip(index + 1).TakeUntil(IsArgument).ToArray(); return ConvertArgument(argumentName, values, destinationType, separator); @@ -79,14 +81,21 @@ public object GetPositionalArgument(int position, Type destinationType, char? se { var positionalArgumentsCount = arguments.TakeUntil(IsArgument).Count(); if (position < 0) + { position = positionalArgumentsCount + position % positionalArgumentsCount; + } if (positionalArgumentsCount <= position) + { return null; + } return ConvertArgument( $"$positional[{position}]", - new[] { arguments[position] }, + new[] + { + arguments[position] + }, destinationType, separator); } @@ -95,7 +104,9 @@ public object GetAllPositionalArguments(Type destinationType, char? separator = { var positionalArguments = Arguments.TakeUntil(IsArgument).ToArray(); if (positionalArguments.Length == 0) + { return destinationType.GetDefaultValue(); + } return ConvertArgument( "$all-positional", @@ -107,7 +118,8 @@ public object GetAllPositionalArguments(Type destinationType, char? separator = private int GetArgumentIndex(string argumentName) { var argumentMemberName = GetArgumentMemberName(argumentName); - return Array.FindLastIndex(arguments, x => IsArgument(x) && GetArgumentMemberName(x).EqualsOrdinalIgnoreCase(argumentMemberName)); + return Array.FindLastIndex(arguments, + x => IsArgument(x) && GetArgumentMemberName(x).EqualsOrdinalIgnoreCase(argumentMemberName)); } private object ConvertArgument( @@ -118,6 +130,7 @@ private object ConvertArgument( { Assert.True(values.Length == 1 || !separator.HasValue || values.All(x => !x.Contains(separator.Value)), $"Argumenet '{argumentName}' with value [ {values.JoinCommaSpace()} ] cannot be split with separator '{separator}'"); + values = separator.HasValue && values.Any(x => x.Contains(separator.Value)) ? values.SingleOrDefault()?.Split(separator.Value) ?? new string[0] : values; @@ -129,9 +142,14 @@ private object ConvertArgument( catch (Exception ex) { Assert.Fail( - new[] { ex.Message, "Arguments were:" } + new[] + { + ex.Message, + "Arguments were:" + } .Concat(arguments.Select((x, i) => $" [{i}] = {x}")) .JoinNewLine()); + // ReSharper disable once HeuristicUnreachableCode return null; } @@ -145,7 +163,12 @@ private object ConvertValues(string argumentName, IReadOnlyCollection va } catch (Exception ex) { - Assert.Fail(new[] { $"Resolving argument '{argumentName}' failed.", ex.Message }.JoinNewLine()); + Assert.Fail(new[] + { + $"Resolving argument '{argumentName}' failed.", + ex.Message + }.JoinNewLine()); + // ReSharper disable once HeuristicUnreachableCode return null; } diff --git a/src/Fallout.Utilities/Assert.cs b/src/Fallout.Utilities/Assert.cs index d8ce8016b..adf1ef7e3 100644 --- a/src/Fallout.Utilities/Assert.cs +++ b/src/Fallout.Utilities/Assert.cs @@ -31,7 +31,9 @@ public static void True( string argumentExpression = null) { if (!condition) + { throw new ArgumentException(message ?? "Expected condition to be true", message == null ? argumentExpression : null); + } } /// @@ -44,7 +46,9 @@ public static void False( string argumentExpression = null) { if (condition) + { throw new ArgumentException(message ?? "Expected condition to be false", message == null ? argumentExpression : null); + } } /// @@ -53,8 +57,7 @@ public static void False( public static T NotNull( this T obj, string message = null, - [CallerArgumentExpression("obj")] - string argumentExpression = null) + [CallerArgumentExpression("obj")] string argumentExpression = null) where T : class { if (obj == null) @@ -73,8 +76,7 @@ public static T NotNull( public static T? NotNull( this T? obj, string message = null, - [CallerArgumentExpression("obj")] - string argumentExpression = null) + [CallerArgumentExpression("obj")] string argumentExpression = null) where T : struct { if (obj == null) @@ -93,11 +95,14 @@ public static T NotNull( public static string NotNullOrEmpty( this string str, string message = null, - [CallerArgumentExpression("str")] - string argumentExpression = null) + [CallerArgumentExpression("str")] string argumentExpression = null) { if (string.IsNullOrEmpty(str)) - throw new ArgumentException(message ?? "Expected string to be not null or empty", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? "Expected string to be not null or empty", + message == null ? argumentExpression : null); + } + return str; } @@ -107,11 +112,14 @@ public static string NotNullOrEmpty( public static string NotNullOrWhiteSpace( this string str, string message = null, - [CallerArgumentExpression("str")] - string argumentExpression = null) + [CallerArgumentExpression("str")] string argumentExpression = null) { if (string.IsNullOrWhiteSpace(str)) - throw new ArgumentException(message ?? "Expected string to be not null or whitespace", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? "Expected string to be not null or whitespace", + message == null ? argumentExpression : null); + } + return str; } @@ -125,7 +133,10 @@ public static void NotEmpty( string argumentExpression = null) { if (collection.NotNull(argumentExpression: argumentExpression).Count == 0) - throw new ArgumentException(message ?? "Expected collection to be not empty", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? "Expected collection to be not empty", + message == null ? argumentExpression : null); + } } /// @@ -138,7 +149,10 @@ public static void Empty( string argumentExpression = null) { if (collection.NotNull(argumentExpression: argumentExpression).Count > 0) - throw new ArgumentException(message ?? "Expected collection to be empty", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? "Expected collection to be empty", + message == null ? argumentExpression : null); + } } /// @@ -152,7 +166,10 @@ public static void Count( string argumentExpression = null) { if (collection.NotNull(argumentExpression: argumentExpression).Count != length) - throw new ArgumentException(message ?? $"Expected collection to have length of {length}", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? $"Expected collection to have length of {length}", + message == null ? argumentExpression : null); + } } /// @@ -170,18 +187,26 @@ public static void HasSingleItem( /// /// Asserts that the file exists with an optional exception message. If no message is provided, the argument expression is used. /// - public static void FileExists(string path, string message = null, [CallerArgumentExpression("path")] string argumentExpression = null) + public static void FileExists(string path, string message = null, + [CallerArgumentExpression("path")] string argumentExpression = null) { if (!File.Exists(path.NotNull(argumentExpression))) - throw new ArgumentException(message ?? $"Expected file to exist: {path}", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? $"Expected file to exist: {path}", + message == null ? argumentExpression : null); + } } /// /// Asserts that the directory exists with an optional exception message. If no message is provided, the argument expression is used. /// - public static void DirectoryExists(string path, string message = null, [CallerArgumentExpression("path")] string argumentExpression = null) + public static void DirectoryExists(string path, string message = null, + [CallerArgumentExpression("path")] string argumentExpression = null) { if (!Directory.Exists(path.NotNull(argumentExpression))) - throw new ArgumentException(message ?? $"Expected directory to exist: {path}", message == null ? argumentExpression : null); + { + throw new ArgumentException(message ?? $"Expected directory to exist: {path}", + message == null ? argumentExpression : null); + } } } diff --git a/src/Fallout.Utilities/AsyncHelper.cs b/src/Fallout.Utilities/AsyncHelper.cs index 9e04e2c56..2b2030366 100644 --- a/src/Fallout.Utilities/AsyncHelper.cs +++ b/src/Fallout.Utilities/AsyncHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; diff --git a/src/Fallout.Utilities/Collections/Array.Deconstruct.cs b/src/Fallout.Utilities/Collections/Array.Deconstruct.cs index 17252c509..087854d7c 100644 --- a/src/Fallout.Utilities/Collections/Array.Deconstruct.cs +++ b/src/Fallout.Utilities/Collections/Array.Deconstruct.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; namespace Fallout.Common.Utilities.Collections; diff --git a/src/Fallout.Utilities/Collections/Dictionary.AddDictionary.cs b/src/Fallout.Utilities/Collections/Dictionary.AddDictionary.cs index cd3a62757..c641bc4b6 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.AddDictionary.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.AddDictionary.cs @@ -10,7 +10,10 @@ public static Dictionary AddDictionary( Dictionary otherDictionary) { foreach (var (key, value) in otherDictionary) + { dictionary.AddPair(key, value); + } + return dictionary; } @@ -19,7 +22,10 @@ public static Dictionary AddDictionary( ReadOnlyDictionary otherDictionary) { foreach (var (key, value) in otherDictionary) + { dictionary.AddPair(key, value); + } + return dictionary; } } diff --git a/src/Fallout.Utilities/Collections/Dictionary.AddKeyValue.cs b/src/Fallout.Utilities/Collections/Dictionary.AddKeyValue.cs index 5b47676b4..bb76a3b9d 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.AddKeyValue.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.AddKeyValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace Fallout.Common.Utilities.Collections; diff --git a/src/Fallout.Utilities/Collections/Dictionary.AsReadOnly.cs b/src/Fallout.Utilities/Collections/Dictionary.AsReadOnly.cs index df64d800c..369a29de2 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.AsReadOnly.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.AsReadOnly.cs @@ -1,5 +1,4 @@ #if NETSTANDARD2_0 - using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs b/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs index 82d545718..65333bcd5 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs @@ -1,5 +1,4 @@ #if NETSTANDARD2_0 - using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Fallout.Utilities/Collections/Dictionary.SetKeyValue.cs b/src/Fallout.Utilities/Collections/Dictionary.SetKeyValue.cs index ee539df87..33144b82a 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.SetKeyValue.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.SetKeyValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace Fallout.Common.Utilities.Collections; diff --git a/src/Fallout.Utilities/Collections/Dictionary.ToGeneric.cs b/src/Fallout.Utilities/Collections/Dictionary.ToGeneric.cs index ddb67bd03..2b0c3219f 100644 --- a/src/Fallout.Utilities/Collections/Dictionary.ToGeneric.cs +++ b/src/Fallout.Utilities/Collections/Dictionary.ToGeneric.cs @@ -1,20 +1,19 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Utilities.Collections; public static partial class DictionaryExtensions { - public static Dictionary ToGeneric(this IDictionary dictionary, IEqualityComparer equalityComparer = null) + public static Dictionary ToGeneric(this IDictionary dictionary, + IEqualityComparer equalityComparer = null) { var genericDictionary = new Dictionary(equalityComparer); var enumerator = dictionary.NotNull().GetEnumerator(); while (enumerator.MoveNext()) { - genericDictionary.Add((TKey) enumerator.Key, (TValue) enumerator.Value); + genericDictionary.Add((TKey)enumerator.Key, (TValue)enumerator.Value); } return genericDictionary; diff --git a/src/Fallout.Utilities/Collections/Enumerable.AsEnumerable.cs b/src/Fallout.Utilities/Collections/Enumerable.AsEnumerable.cs index eec6fb0c2..a0720a3ed 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.AsEnumerable.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.AsEnumerable.cs @@ -11,6 +11,11 @@ public static partial class EnumerableExtensions /// public static IEnumerable AsEnumerable(this object obj) { - return obj is IEnumerable enumerable ? enumerable.Cast() : new[] { (T) obj }; + return obj is IEnumerable enumerable + ? enumerable.Cast() + : new[] + { + (T)obj + }; } } diff --git a/src/Fallout.Utilities/Collections/Enumerable.Concat.cs b/src/Fallout.Utilities/Collections/Enumerable.Concat.cs index 947b78494..3aa2172a3 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.Concat.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.Concat.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Fallout.Common.Utilities.Collections; @@ -16,15 +14,21 @@ public static IEnumerable Concat(this T obj, IEnumerable enumerable) yield return obj; foreach (var element in enumerable) + { yield return element; + } } public static IEnumerable Concat(this IEnumerable enumerable, params T[] others) { foreach (var element in enumerable) + { yield return element; + } foreach (var element in others) + { yield return element; + } } } diff --git a/src/Fallout.Utilities/Collections/Enumerable.Distinct.cs b/src/Fallout.Utilities/Collections/Enumerable.Distinct.cs index c2c20caed..0e7443548 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.Distinct.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.Distinct.cs @@ -9,7 +9,8 @@ public static partial class EnumerableExtensions /// /// Filters a collection to distinct/unique elements. /// - public static IEnumerable Distinct(this IEnumerable enumerable, Func selector) + public static IEnumerable Distinct(this IEnumerable enumerable, + Func selector) { return enumerable.Distinct(new DelegateEqualityComparer(selector)); } @@ -26,13 +27,25 @@ public DelegateEqualityComparer(Func selector) public bool Equals(TSource x, TSource y) { if (ReferenceEquals(x, y)) + { return true; + } + if (ReferenceEquals(x, objB: null)) + { return false; + } + if (ReferenceEquals(y, objB: null)) + { return false; + } + if (x.GetType() != y.GetType()) + { return false; + } + return Equals(selector(x), selector(y)); } diff --git a/src/Fallout.Utilities/Collections/Enumerable.ForEach.cs b/src/Fallout.Utilities/Collections/Enumerable.ForEach.cs index a83dac1fe..8244a9780 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.ForEach.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.ForEach.cs @@ -12,7 +12,9 @@ public static partial class EnumerableExtensions public static void ForEach(this IEnumerable enumerable, Action action) { foreach (var item in enumerable) + { action(item); + } } /// @@ -20,7 +22,11 @@ public static void ForEach(this IEnumerable enumerable, Action action) /// public static void ForEach(this IEnumerable enumerable, Action action) { - enumerable.Select((x, i) => new { x, i }).ForEach(x => action(x.x, x.i)); + enumerable.Select((x, i) => new + { + x, + i + }).ForEach(x => action(x.x, x.i)); } /// diff --git a/src/Fallout.Utilities/Collections/Enumerable.Random.cs b/src/Fallout.Utilities/Collections/Enumerable.Random.cs index b829b6a77..361eb6167 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.Random.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.Random.cs @@ -6,7 +6,7 @@ namespace Fallout.Common.Utilities.Collections; partial class EnumerableExtensions { - private static readonly Random randomNumberGenerator = new Random(); + private static readonly Random randomNumberGenerator = new(); public static T Random(this IEnumerable collection) { @@ -18,7 +18,8 @@ public static ICollection Randomize(this ICollection collection) { var list = collection.ToList(); var count = list.Count; - while (count > 1) { + while (count > 1) + { count--; var k = randomNumberGenerator.Next(count + 1); (list[k], list[count]) = (list[count], list[k]); diff --git a/src/Fallout.Utilities/Collections/Enumerable.Sequence.cs b/src/Fallout.Utilities/Collections/Enumerable.Sequence.cs index 718418fb7..a3953a9f0 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.Sequence.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.Sequence.cs @@ -5,7 +5,8 @@ namespace Fallout.Common.Utilities.Collections; public static partial class EnumerableExtensions { - public static bool SequenceStartsWith(this IEnumerable enumerable, IEnumerable other, IEqualityComparer comparer = null) + public static bool SequenceStartsWith(this IEnumerable enumerable, IEnumerable other, + IEqualityComparer comparer = null) { var enumerableList = enumerable as List ?? enumerable.ToList(); var otherList = other as List ?? other.ToList(); diff --git a/src/Fallout.Utilities/Collections/Enumerable.ToDictionary.cs b/src/Fallout.Utilities/Collections/Enumerable.ToDictionary.cs index 0ff9935dd..b5623c4ea 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.ToDictionary.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.ToDictionary.cs @@ -41,9 +41,13 @@ public static Dictionary ToDictionarySafe( var groups = enumerable.ToLookup(keySelector.Invoke, valueSelector.Invoke); Assert.True( groups.All(x => x.Count() == 1), - new[] { $"{duplicationMessage.TrimEnd(":")}:" } + new[] + { + $"{duplicationMessage.TrimEnd(":")}:" + } .Concat(groups.Where(x => x.Count() > 1).Select(x => $" - {x.Key}")) .JoinNewLine()); + return groups.ToDictionary(x => x.Key, x => x.Single()); } } diff --git a/src/Fallout.Utilities/Collections/Enumerable.TopologicalSort.cs b/src/Fallout.Utilities/Collections/Enumerable.TopologicalSort.cs index b4bc26522..26b4a7151 100644 --- a/src/Fallout.Utilities/Collections/Enumerable.TopologicalSort.cs +++ b/src/Fallout.Utilities/Collections/Enumerable.TopologicalSort.cs @@ -1,28 +1,33 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Fallout.Common.Utilities.Collections; public static partial class EnumerableExtensions { - public static IEnumerable TSort(this IEnumerable source, Func> dependencies, bool throwOnCycle = false) + public static IEnumerable TSort(this IEnumerable source, Func> dependencies, + bool throwOnCycle = false) { var sorted = new List(); var visited = new HashSet(); foreach (var item in source) + { Visit(item, visited, sorted, dependencies, throwOnCycle); + } return sorted; } - private static void Visit(T item, HashSet visited, List sorted, Func> dependencies, bool throwOnCycle) + private static void Visit(T item, HashSet visited, List sorted, Func> dependencies, + bool throwOnCycle) { if (visited.Contains(item)) { if (throwOnCycle && !sorted.Contains(item)) + { throw new Exception($"Cyclic dependency found involving {item}."); + } return; } @@ -30,7 +35,9 @@ private static void Visit(T item, HashSet visited, List sorted, Func ToLookupTable(this ILookup lookup, IEqualityComparer comparer) + public static LookupTable ToLookupTable(this ILookup lookup, + IEqualityComparer comparer) { return new LookupTable(lookup, comparer); } diff --git a/src/Fallout.Utilities/Collections/LookupTable.cs b/src/Fallout.Utilities/Collections/LookupTable.cs index 636e37291..eaf8d8661 100644 --- a/src/Fallout.Utilities/Collections/LookupTable.cs +++ b/src/Fallout.Utilities/Collections/LookupTable.cs @@ -47,7 +47,9 @@ public void AddRange(TKey key, IEnumerable values) { var list = (lookupDictionary[key] = lookupDictionary.GetValueOrDefault(key, new List())).NotNull(); foreach (var value in values) + { list.Add(value); + } } public void Remove(TKey key) diff --git a/src/Fallout.Utilities/Collections/Object.DescendantsAndSelf.cs b/src/Fallout.Utilities/Collections/Object.DescendantsAndSelf.cs index 95b70cb79..8752bf9f3 100644 --- a/src/Fallout.Utilities/Collections/Object.DescendantsAndSelf.cs +++ b/src/Fallout.Utilities/Collections/Object.DescendantsAndSelf.cs @@ -17,7 +17,9 @@ public static IEnumerable DescendantsAndSelf( yield return obj; foreach (var p in obj.Descendants(selector, traverse)) + { yield return p; + } } /// @@ -29,14 +31,20 @@ public static IEnumerable Descendants( Func traverse = null) { if (traverse != null && !traverse(obj)) + { yield break; + } var next = selector(obj); if (traverse == null && Equals(next, default(T))) + { yield break; + } foreach (var nextOrDescendant in next.DescendantsAndSelf(selector, traverse)) + { yield return nextOrDescendant; + } } /// @@ -49,8 +57,10 @@ public static IEnumerable DescendantsAndSelf( { yield return obj; - foreach (var p in Descendants(obj, selector, traverse)) + foreach (var p in obj.Descendants(selector, traverse)) + { yield return p; + } } /// @@ -63,6 +73,8 @@ public static IEnumerable Descendants( { foreach (var child in selector(obj).Where(x => traverse == null || traverse(x))) foreach (var childOrDescendant in child.DescendantsAndSelf(selector, traverse)) + { yield return childOrDescendant; + } } } diff --git a/src/Fallout.Utilities/Converters/AbsolutePathJsonConverter.cs b/src/Fallout.Utilities/Converters/AbsolutePathJsonConverter.cs index 6143f4dfc..ebc3c3463 100644 --- a/src/Fallout.Utilities/Converters/AbsolutePathJsonConverter.cs +++ b/src/Fallout.Utilities/Converters/AbsolutePathJsonConverter.cs @@ -17,9 +17,11 @@ public override AbsolutePath Read( { var value = reader.GetString(); if (value != null) + { return HasPathRoot(value) ? AbsolutePath.Create(value) : EnvironmentInfo.WorkingDirectory / value; + } return null; } diff --git a/src/Fallout.Utilities/DelegateDisposable.cs b/src/Fallout.Utilities/DelegateDisposable.cs index 4fe70cc82..d15bbe566 100644 --- a/src/Fallout.Utilities/DelegateDisposable.cs +++ b/src/Fallout.Utilities/DelegateDisposable.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Linq.Expressions; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/Disposable.CombineWith.cs b/src/Fallout.Utilities/Disposable.CombineWith.cs index 196c80754..0d73a1a63 100644 --- a/src/Fallout.Utilities/Disposable.CombineWith.cs +++ b/src/Fallout.Utilities/Disposable.CombineWith.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/EnvironmentInfo.Platform.cs b/src/Fallout.Utilities/EnvironmentInfo.Platform.cs index 639b26ee1..1555ce503 100644 --- a/src/Fallout.Utilities/EnvironmentInfo.Platform.cs +++ b/src/Fallout.Utilities/EnvironmentInfo.Platform.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using System.Linq; +using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -63,7 +61,9 @@ public static bool IsWsl get { if (!IsLinux) + { return false; + } try { @@ -91,13 +91,19 @@ public static PlatformFamily Platform get { if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { return PlatformFamily.OSX; + } if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { return PlatformFamily.Linux; + } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { return PlatformFamily.Windows; + } return PlatformFamily.Unknown; } diff --git a/src/Fallout.Utilities/EnvironmentInfo.SpecialFolder.cs b/src/Fallout.Utilities/EnvironmentInfo.SpecialFolder.cs index e2d0a3ecc..54261332c 100644 --- a/src/Fallout.Utilities/EnvironmentInfo.SpecialFolder.cs +++ b/src/Fallout.Utilities/EnvironmentInfo.SpecialFolder.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.IO; using Fallout.Common.Utilities; @@ -25,7 +24,9 @@ public static AbsolutePath SpecialFolder(SpecialFolders folder) // https://github.com/nuke-build/nuke/pull/825#discussion_r848954724 if (path.IsNullOrEmpty() && folder == SpecialFolders.UserProfile) + { path = Environment.GetEnvironmentVariable("USERPROFILE"); + } return path; } diff --git a/src/Fallout.Utilities/EnvironmentInfo.Variables.cs b/src/Fallout.Utilities/EnvironmentInfo.Variables.cs index 677cf6187..84a4a7b36 100644 --- a/src/Fallout.Utilities/EnvironmentInfo.Variables.cs +++ b/src/Fallout.Utilities/EnvironmentInfo.Variables.cs @@ -53,7 +53,9 @@ public static T GetVariable(string name, char? separator = null) { var value = GetVariable(name); if (value == null) + { return default; + } return (T)ReflectionUtility.Convert(value, typeof(T), separator, booleanDefault: false); } @@ -90,7 +92,8 @@ public static string ExpandVariables(string value) string ExpandUnixEnvironmentVariables() => value .ReplaceRegex("^~", _ => Environment.GetEnvironmentVariable("HOME")) - .ReplaceRegex(@"\$([a-z_][a-z0-9_]*)", x => Environment.GetEnvironmentVariable(x.Groups[1].Value), RegexOptions.IgnoreCase); + .ReplaceRegex(@"\$([a-z_][a-z0-9_]*)", x => Environment.GetEnvironmentVariable(x.Groups[1].Value), + RegexOptions.IgnoreCase); return IsWin ? Environment.ExpandEnvironmentVariables(value) diff --git a/src/Fallout.Utilities/EnvironmentInfo.cs b/src/Fallout.Utilities/EnvironmentInfo.cs index 3a6d5eb0b..e1bd5d4a4 100644 --- a/src/Fallout.Utilities/EnvironmentInfo.cs +++ b/src/Fallout.Utilities/EnvironmentInfo.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Fallout.Common.IO; using Fallout.Common.Utilities; @@ -11,6 +10,7 @@ namespace Fallout.Common; public static partial class EnvironmentInfo { public static string NewLine => Environment.NewLine; + public static string MachineName => Environment.MachineName; /// diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Children.cs b/src/Fallout.Utilities/IO/AbsolutePath.Children.cs index ef2316aea..b9b66dc2f 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Children.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Children.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using Fallout.Common.Utilities.Collections; @@ -20,7 +19,9 @@ public static IEnumerable GetFiles( Assert.True(depth >= 0); if (depth == 0) + { return Enumerable.Empty(); + } var files = Directory.EnumerateFiles(path, pattern, SearchOption.TopDirectoryOnly) .Where(x => (File.GetAttributes(x) & attributes) == attributes) @@ -41,7 +42,11 @@ public static IEnumerable GetDirectories( { Assert.True(depth >= 0); - var paths = new string[] { path }; + var paths = new string[] + { + path + }; + while (!paths.IsEmpty() && depth > 0) { var matchingDirectories = paths @@ -51,7 +56,9 @@ public static IEnumerable GetDirectories( .Select(AbsolutePath.Create).ToList(); foreach (var matchingDirectory in matchingDirectories) + { yield return matchingDirectory; + } depth--; paths = paths.SelectMany(x => Directory.GetDirectories(x, "*", SearchOption.TopDirectoryOnly)).ToArray(); diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Create.cs b/src/Fallout.Utilities/IO/AbsolutePath.Create.cs index 35e2fa466..e90e0a47f 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Create.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Create.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; namespace Fallout.Common.IO; @@ -33,10 +32,14 @@ public static AbsolutePath CreateOrCleanDirectory(this AbsolutePath path) public static AbsolutePath TouchFile(this AbsolutePath path, DateTime? time = null, bool createDirectories = true) { if (createDirectories) + { path.Parent.CreateDirectory(); + } if (!File.Exists(path)) + { File.WriteAllBytes(path, new byte[0]); + } File.SetLastWriteTime(path, time ?? DateTime.Now); diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Delete.cs b/src/Fallout.Utilities/IO/AbsolutePath.Delete.cs index 5f69bbb6c..ec13c59ee 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Delete.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Delete.cs @@ -12,7 +12,9 @@ partial class AbsolutePathExtensions public static void DeleteFile(this AbsolutePath path) { if (!path.FileExists()) + { return; + } File.SetAttributes(path, FileAttributes.Normal); File.Delete(path); @@ -24,7 +26,9 @@ public static void DeleteFile(this AbsolutePath path) public static void DeleteDirectory(this AbsolutePath path) { if (!path.DirectoryExists()) + { return; + } Directory.GetFiles(path, "*", SearchOption.AllDirectories).ForEach(x => File.SetAttributes(x, FileAttributes.Normal)); Directory.Delete(path, recursive: true); diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Exists.cs b/src/Fallout.Utilities/IO/AbsolutePath.Exists.cs index b6b5abf31..67bd287bb 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Exists.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Exists.cs @@ -16,9 +16,14 @@ public static partial class AbsolutePathExtensions public static bool Exists(this AbsolutePath path, [CallerArgumentExpression("path")] string expression = null) { if (expression.EndsWithAnyOrdinalIgnoreCase("file", "executable", "exe", "script", "archive")) + { return path.FileExists(); + } + if (expression.EndsWithAnyOrdinalIgnoreCase("directory", "dir", "folder")) + { return path.DirectoryExists(); + } throw new ArgumentException($"Cannot infer from argument '{expression}' if either file or directory must exist"); } @@ -51,7 +56,8 @@ public static bool ContainsFile(this AbsolutePath path, string pattern, SearchOp /// /// Indicates whether the directory contains a directory (* as wildcard) using . /// - public static bool ContainsDirectory(this AbsolutePath path, string pattern, SearchOption options = SearchOption.TopDirectoryOnly) + public static bool ContainsDirectory(this AbsolutePath path, string pattern, + SearchOption options = SearchOption.TopDirectoryOnly) { Assert.DirectoryExists(path); return path.ToDirectoryInfo().GetDirectories(pattern, options).Any(); diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Find.cs b/src/Fallout.Utilities/IO/AbsolutePath.Find.cs index 9664c0c98..dcc74a655 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Find.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Find.cs @@ -15,7 +15,6 @@ public static AbsolutePath FindParent(this AbsolutePath path, Func x.Parent).FirstOrDefault(predicate); } - /// /// Finds the first parent (starting with self) that fulfills the condition. /// diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Hash.cs b/src/Fallout.Utilities/IO/AbsolutePath.Hash.cs index aebe9c4da..db04c1340 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Hash.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Hash.cs @@ -53,7 +53,8 @@ public static string GetFileSetHash(this IEnumerable paths, string md5.TransformBlock(pathBytes, inputOffset: 0, inputCount: pathBytes.Length, outputBuffer: pathBytes, outputOffset: 0); var contentBytes = File.ReadAllBytes(path); - md5.TransformBlock(contentBytes, inputOffset: 0, inputCount: contentBytes.Length, outputBuffer: contentBytes, outputOffset: 0); + md5.TransformBlock(contentBytes, inputOffset: 0, inputCount: contentBytes.Length, outputBuffer: contentBytes, + outputOffset: 0); } md5.TransformFinalBlock(new byte[0], inputOffset: 0, inputCount: 0); diff --git a/src/Fallout.Utilities/IO/AbsolutePath.Misc.cs b/src/Fallout.Utilities/IO/AbsolutePath.Misc.cs index c68ffdf36..4cb82beed 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.Misc.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.Misc.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; -using System.Linq; - -namespace Fallout.Common.IO; +namespace Fallout.Common.IO; partial class AbsolutePathExtensions { diff --git a/src/Fallout.Utilities/IO/AbsolutePath.MoveCopy.cs b/src/Fallout.Utilities/IO/AbsolutePath.MoveCopy.cs index 829c7e524..14164f7d6 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.MoveCopy.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.MoveCopy.cs @@ -118,10 +118,14 @@ public static AbsolutePath Move( Assert.True(source.DirectoryExists() || source.FileExists()); if (source.DirectoryExists()) - return MoveDirectory(source, target, policy, createDirectories, deleteRemainingFiles); + { + return source.MoveDirectory(target, policy, createDirectories, deleteRemainingFiles); + } if (source.FileExists()) - return MoveFile(source, target, policy, createDirectories); + { + return source.MoveFile(target, policy, createDirectories); + } throw new Exception("Unreachable"); } @@ -141,10 +145,14 @@ public static AbsolutePath Copy( Assert.True(source.DirectoryExists() || (excludeDirectory == null && excludeFile == null)); if (source.DirectoryExists()) - return CopyDirectory(source, target, policy, excludeDirectory, excludeFile, createDirectories); + { + return source.CopyDirectory(target, policy, excludeDirectory, excludeFile, createDirectories); + } if (source.FileExists()) - return CopyFile(source, target, policy, createDirectories); + { + return source.CopyFile(target, policy, createDirectories); + } throw new Exception("Unreachable"); } @@ -182,17 +190,23 @@ private static AbsolutePath HandleFile( Action action) { if (File.Exists(target) && !Permitted()) + { return source; + } if (createDirectories) + { target.Parent.CreateDirectory(); + } action.Invoke(); return target; bool Permitted() { - var filePolicies = ExistsPolicy.FileFail | ExistsPolicy.FileSkip | ExistsPolicy.FileOverwrite | ExistsPolicy.FileOverwriteIfNewer; + var filePolicies = ExistsPolicy.FileFail | ExistsPolicy.FileSkip | ExistsPolicy.FileOverwrite | + ExistsPolicy.FileOverwriteIfNewer; + return (policy & filePolicies) switch { ExistsPolicy.FileFail => throw new Exception($"File '{target}' already exists"), @@ -217,7 +231,9 @@ private static AbsolutePath MoveDirectory( source.GetFiles().ForEach(x => x.MoveFile(target / source.GetRelativePathTo(x), policy)); if (!source.ToDirectoryInfo().EnumerateFileSystemInfos().Any() || deleteRemainingFiles) + { source.DeleteDirectory(); + } }); } @@ -231,7 +247,9 @@ private static AbsolutePath CopyDirectory( { return HandleDirectory(source, target, policy, createDirectories, () => { - source.GetDirectories().WhereNot(excludeDirectory).ForEach(x => x.CopyDirectory(target / source.GetRelativePathTo(x), policy, excludeDirectory, excludeFile)); + source.GetDirectories().WhereNot(excludeDirectory).ForEach(x => + x.CopyDirectory(target / source.GetRelativePathTo(x), policy, excludeDirectory, excludeFile)); + source.GetFiles().WhereNot(excludeFile).ForEach(x => x.CopyFile(target / source.GetRelativePathTo(x), policy)); }); } @@ -245,11 +263,15 @@ private static AbsolutePath HandleDirectory( { Assert.DirectoryExists(source); Assert.False(source.Contains(target), $"Target directory '{target}' must not be in source directory '{source}'"); - Assert.True(!Directory.Exists(target) || (policy.HasFlag(ExistsPolicy.DirectoryMerge) && !policy.HasFlag(ExistsPolicy.DirectoryFail)), + Assert.True( + !Directory.Exists(target) || + (policy.HasFlag(ExistsPolicy.DirectoryMerge) && !policy.HasFlag(ExistsPolicy.DirectoryFail)), "Policy disallows merging directories"); if (createDirectories) + { target.CreateDirectory(); + } action.Invoke(); return target; diff --git a/src/Fallout.Utilities/IO/AbsolutePath.ReadWrite.cs b/src/Fallout.Utilities/IO/AbsolutePath.ReadWrite.cs index d23b71f68..c8f277dae 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.ReadWrite.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.ReadWrite.cs @@ -65,7 +65,12 @@ public static AbsolutePath WriteAllLines( bool? eofLineBreak = null) { if (eofLineBreak ?? DefaultEofLineBreak) - lines = lines.Concat(new[] { string.Empty }).ToArray(); + { + lines = lines.Concat(new[] + { + string.Empty + }).ToArray(); + } return path.WriteAllText(lines.JoinNewLine(platformFamily ?? DefaultLineBreakType), encoding); } diff --git a/src/Fallout.Utilities/IO/AbsolutePath.cs b/src/Fallout.Utilities/IO/AbsolutePath.cs index 0007beb6c..e02c6af01 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.cs @@ -1,12 +1,12 @@ +#if NET6_0_OR_GREATER +using System.Text.Json.Serialization; +using Fallout.Utilities.Converters; +#endif using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; -#if NET6_0_OR_GREATER -using System.Text.Json.Serialization; -using Fallout.Utilities.Converters; -#endif using Fallout.Common.Utilities; using static Fallout.Common.IO.PathConstruction; using TypeConverter = Fallout.Utilities.Converters.TypeConverter; @@ -58,7 +58,9 @@ private AbsolutePath(string path) public static implicit operator AbsolutePath(string path) { if (path is null) + { return null; + } Assert.True(HasPathRoot(path), $"Path '{path}' must be rooted"); return new AbsolutePath(path); @@ -131,13 +133,19 @@ protected bool Equals(AbsolutePath other) public override bool Equals(object obj) { if (ReferenceEquals(objA: null, obj)) + { return false; + } if (ReferenceEquals(this, obj)) + { return true; + } if (obj.GetType() != GetType()) + { return false; + } return Equals((AbsolutePath)obj); } diff --git a/src/Fallout.Utilities/IO/IAbsolutePathHolder.cs b/src/Fallout.Utilities/IO/IAbsolutePathHolder.cs index 3a92d9f1f..057088a6c 100644 --- a/src/Fallout.Utilities/IO/IAbsolutePathHolder.cs +++ b/src/Fallout.Utilities/IO/IAbsolutePathHolder.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.IO; public interface IAbsolutePathHolder diff --git a/src/Fallout.Utilities/IO/PathConstruction.cs b/src/Fallout.Utilities/IO/PathConstruction.cs index 6761ff189..8d3b303c3 100644 --- a/src/Fallout.Utilities/IO/PathConstruction.cs +++ b/src/Fallout.Utilities/IO/PathConstruction.cs @@ -37,17 +37,17 @@ public static class PathConstruction { public static WinRelativePath GetWinRelativePath(string basePath, string destinationPath) { - return (WinRelativePath) GetRelativePath(basePath, destinationPath); + return (WinRelativePath)GetRelativePath(basePath, destinationPath); } public static UnixRelativePath GetUnixRelativePath(string basePath, string destinationPath) { - return (UnixRelativePath) GetRelativePath(basePath, destinationPath); + return (UnixRelativePath)GetRelativePath(basePath, destinationPath); } public static RelativePath GetRelativePathTo(this AbsolutePath basePath, string destinationPath) { - return (RelativePath) GetRelativePath(basePath, destinationPath); + return (RelativePath)GetRelativePath(basePath, destinationPath); } public static WinRelativePath GetWinRelativePathTo(this AbsolutePath basePath, string destinationPath) @@ -62,12 +62,12 @@ public static UnixRelativePath GetUnixRelativePathTo(this AbsolutePath basePath, public static WinRelativePath ToWinRelativePath(this RelativePath path) { - return (WinRelativePath) (string) path; + return (WinRelativePath)(string)path; } public static UnixRelativePath ToUnixRelativePath(this RelativePath path) { - return (UnixRelativePath) (string) path; + return (UnixRelativePath)(string)path; } public static bool Contains(this AbsolutePath basePath, string destinationPath) @@ -86,11 +86,23 @@ public static string GetRelativePath(string basePath, string destinationPath) Assert.True(separator == GetSeparator(destinationPath), "Separators do not match"); Assert.True(!IsWinRoot(basePath) || Path.GetPathRoot(basePath) == Path.GetPathRoot(destinationPath), "Root must be same"); - var baseParts = basePath.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); - var destinationParts = destinationPath.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); + var baseParts = basePath.Split(new[] + { + separator + }, StringSplitOptions.RemoveEmptyEntries); + + var destinationParts = destinationPath.Split(new[] + { + separator + }, StringSplitOptions.RemoveEmptyEntries); - var sameParts = baseParts.Zip(destinationParts, (a, b) => new { Base = a, Destination = b }) + var sameParts = baseParts.Zip(destinationParts, (a, b) => new + { + Base = a, + Destination = b + }) .TakeWhile(x => x.Base.EqualsOrdinalIgnoreCase(x.Destination)).Count(); + return Enumerable.Repeat("..", baseParts.Length - sameParts).ToList() .Concat(destinationParts.Skip(sameParts).ToList()).Join(separator); } @@ -107,7 +119,12 @@ public static bool IsDescendantPath(string basePath, string destinationPath) internal const char UncSeparator = '\\'; internal const char UnixSeparator = '/'; - internal static readonly char[] AllSeparators = { WinSeparator, UncSeparator, UnixSeparator }; + internal static readonly char[] AllSeparators = + { + WinSeparator, + UncSeparator, + UnixSeparator + }; private static bool IsSameDirectory(string pathPart) => pathPart?.Length == 1 && @@ -144,13 +161,19 @@ internal static bool IsUncRoot(string root) public static string GetPathRoot(string path) { if (path == null) + { return null; + } if (HasUnixRoot(path)) + { return GetHeadPart(path, count: 1); + } if (HasWinRoot(path)) + { return GetHeadPart(path, count: 2); + } if (HasUncRoot(path)) { @@ -170,19 +193,32 @@ public static string Combine(string left, string right, char? separator = null) Assert.False(HasPathRoot(right), "Second path must not be rooted"); if (string.IsNullOrWhiteSpace(left)) + { return right; + } + if (string.IsNullOrWhiteSpace(right)) + { return !IsWinRoot(left) ? left : $@"{left}\"; + } AssertSeparatorChoice(left, separator); separator ??= GetSeparator(left); if (IsWinRoot(left)) + { return $@"{left}\{right}"; + } + if (IsUnixRoot(left)) + { return $"{left}{right}"; + } + if (IsUncRoot(left)) + { return $@"{left}\{right}"; + } return $"{left}{separator}{right}"; } @@ -234,13 +270,19 @@ private static char GetSeparator(string path) if (root != null) { if (IsWinRoot(root)) + { return WinSeparator; + } if (IsUncRoot(root)) + { return UncSeparator; + } if (IsUnixRoot(root)) + { return UnixSeparator; + } } return Path.DirectorySeparatorChar; @@ -249,21 +291,32 @@ private static char GetSeparator(string path) private static void AssertSeparatorChoice(string path, char? separator) { if (separator == null) + { return; + } var root = GetPathRoot(path); if (root == null) + { return; + } - Assert.True(!IsWinRoot(root) || separator == WinSeparator, $"For Windows-rooted paths the separator must be '{WinSeparator}'"); - Assert.True(!IsUncRoot(root) || separator == UncSeparator, $"For UNC-rooted paths the separator must be '{UncSeparator}'"); - Assert.True(!IsUnixRoot(root) || separator == UnixSeparator, $"For Unix-rooted paths the separator must be '{UnixSeparator}'"); + Assert.True(!IsWinRoot(root) || separator == WinSeparator, + $"For Windows-rooted paths the separator must be '{WinSeparator}'"); + + Assert.True(!IsUncRoot(root) || separator == UncSeparator, + $"For UNC-rooted paths the separator must be '{UncSeparator}'"); + + Assert.True(!IsUnixRoot(root) || separator == UnixSeparator, + $"For Unix-rooted paths the separator must be '{UnixSeparator}'"); } private static string Trim(string path) { if (path == null) + { return null; + } return IsUnixRoot(path) // TODO: "//" ? ? path diff --git a/src/Fallout.Utilities/IO/RelativePath.cs b/src/Fallout.Utilities/IO/RelativePath.cs index 817d7e545..726dd6f1e 100644 --- a/src/Fallout.Utilities/IO/RelativePath.cs +++ b/src/Fallout.Utilities/IO/RelativePath.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; using static Fallout.Common.IO.PathConstruction; namespace Fallout.Common.IO; @@ -24,7 +23,9 @@ protected RelativePath(string path, char? separator = null) public static explicit operator RelativePath(string path) { if (path is null) + { return null; + } return new RelativePath(NormalizePath(path)); } @@ -47,7 +48,7 @@ public static implicit operator string(RelativePath path) public static RelativePath operator /(RelativePath left, string right) { var separator = left.NotNull().separator; - return new RelativePath(NormalizePath(Combine(left, (RelativePath) right, separator), separator), separator); + return new RelativePath(NormalizePath(Combine(left, (RelativePath)right, separator), separator), separator); } public static RelativePath operator +(RelativePath left, string right) diff --git a/src/Fallout.Utilities/IO/UnixRelativePath.cs b/src/Fallout.Utilities/IO/UnixRelativePath.cs index b1561a0a0..69335e369 100644 --- a/src/Fallout.Utilities/IO/UnixRelativePath.cs +++ b/src/Fallout.Utilities/IO/UnixRelativePath.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using static Fallout.Common.IO.PathConstruction; namespace Fallout.Common.IO; diff --git a/src/Fallout.Utilities/IO/WinRelativePath.cs b/src/Fallout.Utilities/IO/WinRelativePath.cs index 45330a634..164060d7b 100644 --- a/src/Fallout.Utilities/IO/WinRelativePath.cs +++ b/src/Fallout.Utilities/IO/WinRelativePath.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using static Fallout.Common.IO.PathConstruction; namespace Fallout.Common.IO; diff --git a/src/Fallout.Utilities/Lazy.cs b/src/Fallout.Utilities/Lazy.cs index f5f69da89..ab9f17da6 100644 --- a/src/Fallout.Utilities/Lazy.cs +++ b/src/Fallout.Utilities/Lazy.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/Object.Apply.cs b/src/Fallout.Utilities/Object.Apply.cs index 18ef3f025..c6529a31d 100644 --- a/src/Fallout.Utilities/Object.Apply.cs +++ b/src/Fallout.Utilities/Object.Apply.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/Object.Clone.cs b/src/Fallout.Utilities/Object.Clone.cs index 5b42508cf..4ef5fa9e6 100644 --- a/src/Fallout.Utilities/Object.Clone.cs +++ b/src/Fallout.Utilities/Object.Clone.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.Serialization; namespace Fallout.Common.Utilities; @@ -19,6 +17,6 @@ public static T Clone(this T obj) using var memoryStream = new MemoryStream(); serializer.WriteObject(memoryStream, obj); memoryStream.Seek(offset: 0, loc: SeekOrigin.Begin); - return (T) serializer.ReadObject(memoryStream); + return (T)serializer.ReadObject(memoryStream); } } diff --git a/src/Fallout.Utilities/Object.When.cs b/src/Fallout.Utilities/Object.When.cs index 7f9c0df29..4d7619b20 100644 --- a/src/Fallout.Utilities/Object.When.cs +++ b/src/Fallout.Utilities/Object.When.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/Reflection/Assembly.Versioning.cs b/src/Fallout.Utilities/Reflection/Assembly.Versioning.cs index dca95e18e..7e31a93af 100644 --- a/src/Fallout.Utilities/Reflection/Assembly.Versioning.cs +++ b/src/Fallout.Utilities/Reflection/Assembly.Versioning.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Reflection; namespace Fallout.Common.Utilities; diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.Conversion.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.Conversion.cs index 0e689e788..37487b0ad 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.Conversion.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.Conversion.cs @@ -16,10 +16,14 @@ public static T Convert(string value) public static object Convert(object value, Type destinationType) { if (destinationType.IsInstanceOfType(value)) + { return value; + } if (destinationType == typeof(string) && value == null) + { return null; + } try { @@ -37,8 +41,14 @@ public static object Convert(object value, Type destinationType) // TODO: rename overloads? public static object Convert(string value, Type destinationType, char? separator, bool booleanDefault) { - var values = (separator.HasValue ? value.Split(separator.Value) : new[] { value }) + var values = (separator.HasValue + ? value.Split(separator.Value) + : new[] + { + value + }) .Where(x => !x.IsNullOrWhiteSpace()).ToArray(); + return Convert(values, destinationType, booleanDefault); } @@ -51,10 +61,14 @@ public static object Convert(IReadOnlyCollection values, Type destinatio if (values.Count == 0) { if (destinationType.IsArray) + { return Array.CreateInstance(elementType, length: 0); + } if (destinationType == typeof(bool) || destinationType == typeof(bool?)) + { return booleanDefault; + } return null; } @@ -64,6 +78,7 @@ public static object Convert(IReadOnlyCollection values, Type destinatio { Assert.HasSingleItem(convertedValues, $"Value [ {values.JoinCommaSpace()} ] cannot be assigned to '{destinationType.GetDisplayShortName()}'"); + return convertedValues.Single(); } diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.DisplayText.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.DisplayText.cs index 1bc409fd5..ce3732d89 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.DisplayText.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.DisplayText.cs @@ -32,31 +32,35 @@ public static string GetDisplayShortName(this MemberInfo member) internal static string GetDisplayShortName(this Type type, IList tupleNames) { var aliases = new Dictionary - { - { typeof(byte), "byte" }, - { typeof(sbyte), "sbyte" }, - { typeof(short), "short" }, - { typeof(ushort), "ushort" }, - { typeof(int), "int" }, - { typeof(uint), "uint" }, - { typeof(long), "long" }, - { typeof(ulong), "ulong" }, - { typeof(float), "float" }, - { typeof(double), "double" }, - { typeof(decimal), "decimal" }, - { typeof(object), "object" }, - { typeof(bool), "bool" }, - { typeof(char), "char" }, - { typeof(string), "string" }, - { typeof(void), "void" } - }; + { + { typeof(byte), "byte" }, + { typeof(sbyte), "sbyte" }, + { typeof(short), "short" }, + { typeof(ushort), "ushort" }, + { typeof(int), "int" }, + { typeof(uint), "uint" }, + { typeof(long), "long" }, + { typeof(ulong), "ulong" }, + { typeof(float), "float" }, + { typeof(double), "double" }, + { typeof(decimal), "decimal" }, + { typeof(object), "object" }, + { typeof(bool), "bool" }, + { typeof(char), "char" }, + { typeof(string), "string" }, + { typeof(void), "void" } + }; if (aliases.TryGetValue(type, out var alias)) + { return alias; + } var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) + { return $"{underlyingType.GetDisplayShortName(tupleNames)}?"; + } if (type.IsGenericType) { @@ -80,7 +84,9 @@ string PopName() } if (type.IsArray) + { return $"{type.GetElementType().GetDisplayShortName(tupleNames)}[{','.Repeat(type.GetArrayRank() - 1)}]"; + } return type.Name; } @@ -101,6 +107,7 @@ public static string GetDisplayText(this ParameterInfo parameter) : parameter.ParameterType.IsByRef ? "ref " : string.Empty; + var defaultValue = parameter.HasDefaultValue ? $" = {parameter.DefaultValue ?? "null"}" : string.Empty; return $"{modifier}{parameterType} {parameter.Name}{defaultValue}"; @@ -109,7 +116,9 @@ public static string GetDisplayText(this ParameterInfo parameter) public static string GetDisplayText(this MemberInfo member) { if (member is Type type) + { return type.GetDisplayShortName(); + } var parameterList = member is MethodBase methodBase ? $"({methodBase.GetParameters().Select(x => x.GetDisplayText()).JoinCommaSpace()})" @@ -121,6 +130,7 @@ public static string GetDisplayText(this MemberInfo member) .Cast() .FirstOrDefault() : member.GetCustomAttribute(); + var memberType = member is not ConstructorInfo ? $" : {member.GetMemberType().GetDisplayShortName(tupleNamesAttribute?.TransformNames.ToList())}" : string.Empty; diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.Expressions.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.Expressions.cs index 102e6ed0e..facc40fc1 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.Expressions.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.Expressions.cs @@ -21,11 +21,14 @@ public static MemberInfo GetMemberInfo(Expression> expression) public static MemberInfo GetMemberInfo(this LambdaExpression expression) { if (expression.Body is MethodCallExpression methodCallExpression) + { return methodCallExpression.Method; + } var memberExpression = expression.Body is not UnaryExpression unaryExpression - ? (MemberExpression) expression.Body - : (MemberExpression) unaryExpression.Operand; + ? (MemberExpression)expression.Body + : (MemberExpression)unaryExpression.Operand; + return memberExpression.Member; } diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.Invocation.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.Invocation.cs index c8c8c59ce..d4a98d536 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.Invocation.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.Invocation.cs @@ -10,7 +10,7 @@ public static partial class ReflectionUtility { public static T CreateInstance(this Type type, params object[] args) { - return (T) type.CreateInstance(args); + return (T)type.CreateInstance(args); } public static object CreateInstance(this Type type, params object[] args) @@ -20,7 +20,7 @@ public static object CreateInstance(this Type type, params object[] args) public static T GetValue(this MemberInfo member, object obj = null, object[] args = null) { - return (T) GetValue(member, obj, args); + return (T)member.GetValue(obj, args); } public static object GetValue(this MemberInfo member, object obj = null, object[] args = null) @@ -39,15 +39,16 @@ public static TResult GetValueNonVirtual(this MemberInfo member, object Assert.True(member is PropertyInfo or MethodInfo); var method = member is PropertyInfo property ? property.GetMethod.NotNull() - : (MethodInfo) member; + : (MethodInfo)member; var funcType = Expression.GetFuncType(method.GetParameters().Select(x => x.ParameterType) .Concat(method.ReturnType).ToArray()); + var functionPointer = method.NotNull("method != null").MethodHandle.GetFunctionPointer(); - var nonVirtualDelegate = (Delegate) Activator.CreateInstance(funcType, obj, functionPointer) + var nonVirtualDelegate = (Delegate)Activator.CreateInstance(funcType, obj, functionPointer) .NotNull("nonVirtualDelegate != null"); - return (TResult) nonVirtualDelegate.DynamicInvoke(arguments); + return (TResult)nonVirtualDelegate.DynamicInvoke(arguments); // var method = (MethodInfo) func.GetMemberInfo(); // @@ -115,7 +116,7 @@ public static T InvokeMember( BindingFlags? bindingFlags = null, params object[] args) { - return (T) type.InvokeMember(memberName, target, bindingFlags, args); + return (T)type.InvokeMember(memberName, target, bindingFlags, args); } public static void DynamicInvokeUnwrap(this Delegate @delegate, params object[] args) diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.cs index 46ddb1b55..180b2a3d7 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.cs @@ -40,7 +40,10 @@ public static Type GetNullableType(this Type type) public static bool IsCollectionLike(this Type type) { return type != typeof(string) && - new[] { type }.Concat(type.GetInterfaces()) + new[] + { + type + }.Concat(type.GetInterfaces()) .Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)); } @@ -169,10 +172,12 @@ public static IEnumerable GetAllMembers( .SelectMany(x => x.GetMembers(bindingFlags)) .Where(filter) .Where(x => type.GetMember(x.Name).SingleOrDefault() == null).ToLookup(x => x.GetDisplayShortName()); + var classMembers = type .GetMembers(bindingFlags) .Where(filter) .Where(x => !x.IsExplicit()).ToDictionary(x => x.Name); + var removeMembers = new List(); foreach (var interfaceMembers in interfaceMembersByName) @@ -184,7 +189,8 @@ public static IEnumerable GetAllMembers( if (filterQuasiOverridden && classMember == null && interfaceMembers.Count() > 1) { var orderedProperties = interfaceMembers - .TSort(x => interfaceMembers.Where(y => y.DeclaringType.NotNull().IsAssignableFrom(x.DeclaringType))).ToList(); + .TSort(x => interfaceMembers.Where(y => y.DeclaringType.NotNull().IsAssignableFrom(x.DeclaringType))) + .ToList(); var mostBaseType = orderedProperties.First().DeclaringType.NotNull(); var derivedTypes = orderedProperties.Skip(1).Select(x => x.DeclaringType); @@ -202,12 +208,18 @@ public static IEnumerable GetAllMembers( // } Assert.True(allowAmbiguity || interfaceMembers.Count() == 1 || classMember != null, - new[] { $"{memberType} '{memberName}' must be implemented explicitly because it is inherited from multiple interfaces:" } + new[] + { + $"{memberType} '{memberName}' must be implemented explicitly because it is inherited from multiple interfaces:" + } .Concat(interfaceMembers.Select(x => $" - {x.DeclaringType.NotNull().Name}")) .JoinNewLine()); Assert.True(allowAmbiguity || classMember == null || classMember.IsPublic(), - new[] { $"{memberType} '{memberName}' must be marked public to override inherited member from:" } + new[] + { + $"{memberType} '{memberName}' must be marked public to override inherited member from:" + } .Concat(interfaceMembers.Select(x => $" - {x.DeclaringType.NotNull().Name}")) .JoinNewLine()); } diff --git a/src/Fallout.Utilities/Security/EncryptionUtility.cs b/src/Fallout.Utilities/Security/EncryptionUtility.cs index 14bbf5213..f285efdf7 100644 --- a/src/Fallout.Utilities/Security/EncryptionUtility.cs +++ b/src/Fallout.Utilities/Security/EncryptionUtility.cs @@ -44,16 +44,38 @@ internal static class EncryptionUtility private const int V1KeyLength = 32; private const int V1IvLength = 16; private const int V1Iterations = 10_000; - private static readonly byte[] v1StaticSalt = { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }; + + private static readonly byte[] v1StaticSalt = + { + 0x49, + 0x76, + 0x61, + 0x6e, + 0x20, + 0x4d, + 0x65, + 0x64, + 0x76, + 0x65, + 0x64, + 0x65, + 0x76 + }; public static string Decrypt(string cipherText, string password, string name) { try { if (cipherText.StartsWith(V2Prefix, StringComparison.Ordinal)) + { return DecryptV2(cipherText[V2Prefix.Length..], password); + } + if (cipherText.StartsWith(V1Prefix, StringComparison.Ordinal)) + { return DecryptV1(cipherText[V1Prefix.Length..], password); + } + // Unprefixed ciphertext from very old setups — treat as v1. return DecryptV1(cipherText, password); } @@ -79,7 +101,9 @@ public static string Encrypt(string clearText, string password) var ciphertext = new byte[plaintext.Length]; var tag = new byte[V2TagLength]; using (var aes = new AesGcm(key, V2TagLength)) + { aes.Encrypt(nonce, plaintext, ciphertext, tag); + } var blob = new byte[V2SaltLength + V2NonceLength + V2TagLength + ciphertext.Length]; Buffer.BlockCopy(salt, 0, blob, 0, V2SaltLength); @@ -117,7 +141,9 @@ private static string DecryptV2(string base64Blob, string password) var plaintext = new byte[ciphertextLength]; using (var aes = new AesGcm(key, V2TagLength)) + { aes.Decrypt(nonce, ciphertext, tag, plaintext); + } return Encoding.UTF8.GetString(plaintext); } @@ -127,7 +153,9 @@ private static string DecryptV1(string base64Ciphertext, string password) var ciphertext = Convert.FromBase64String(base64Ciphertext); var passwordBytes = Encoding.UTF8.GetBytes(password); - var keyAndIv = Rfc2898DeriveBytes.Pbkdf2(passwordBytes, v1StaticSalt, V1Iterations, HashAlgorithmName.SHA256, V1KeyLength + V1IvLength); + var keyAndIv = Rfc2898DeriveBytes.Pbkdf2(passwordBytes, v1StaticSalt, V1Iterations, HashAlgorithmName.SHA256, + V1KeyLength + V1IvLength); + var key = new byte[V1KeyLength]; var iv = new byte[V1IvLength]; Buffer.BlockCopy(keyAndIv, 0, key, 0, V1KeyLength); @@ -138,7 +166,10 @@ private static string DecryptV1(string base64Ciphertext, string password) aes.IV = iv; using var memoryStream = new MemoryStream(); using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write)) + { cryptoStream.Write(ciphertext, 0, ciphertext.Length); + } + return Encoding.UTF8.GetString(memoryStream.ToArray()); } @@ -153,7 +184,9 @@ internal static string EncryptV1Legacy(string clearText, string password) var plaintext = Encoding.UTF8.GetBytes(clearText); var passwordBytes = Encoding.UTF8.GetBytes(password); - var keyAndIv = Rfc2898DeriveBytes.Pbkdf2(passwordBytes, v1StaticSalt, V1Iterations, HashAlgorithmName.SHA256, V1KeyLength + V1IvLength); + var keyAndIv = Rfc2898DeriveBytes.Pbkdf2(passwordBytes, v1StaticSalt, V1Iterations, HashAlgorithmName.SHA256, + V1KeyLength + V1IvLength); + var key = new byte[V1KeyLength]; var iv = new byte[V1IvLength]; Buffer.BlockCopy(keyAndIv, 0, key, 0, V1KeyLength); @@ -164,7 +197,9 @@ internal static string EncryptV1Legacy(string clearText, string password) aes.IV = iv; using var memoryStream = new MemoryStream(); using (var cryptoStream = new CryptoStream(memoryStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) + { cryptoStream.Write(plaintext, 0, plaintext.Length); + } return V1Prefix + Convert.ToBase64String(memoryStream.ToArray()); } diff --git a/src/Fallout.Utilities/Shims/ExcludeAssemblyFromCodeCoverageAttribute.cs b/src/Fallout.Utilities/Shims/ExcludeAssemblyFromCodeCoverageAttribute.cs index 4abf74282..f389ddbae 100644 --- a/src/Fallout.Utilities/Shims/ExcludeAssemblyFromCodeCoverageAttribute.cs +++ b/src/Fallout.Utilities/Shims/ExcludeAssemblyFromCodeCoverageAttribute.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; +// ReSharper disable once CheckNamespace -// ReSharper disable once CheckNamespace namespace System.Diagnostics.CodeAnalysis; [AttributeUsage(AttributeTargets.Assembly)] diff --git a/src/Fallout.Utilities/Shims/IsExternalInit.cs b/src/Fallout.Utilities/Shims/IsExternalInit.cs index f72d845e3..20535a588 100644 --- a/src/Fallout.Utilities/Shims/IsExternalInit.cs +++ b/src/Fallout.Utilities/Shims/IsExternalInit.cs @@ -1,4 +1,5 @@ // ReSharper disable once CheckNamespace + namespace System.Runtime.CompilerServices; internal static class IsExternalInit; diff --git a/src/Fallout.Utilities/Task.WaitAll.cs b/src/Fallout.Utilities/Task.WaitAll.cs index 3c88a60ca..b8028c2a7 100644 --- a/src/Fallout.Utilities/Task.WaitAll.cs +++ b/src/Fallout.Utilities/Task.WaitAll.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -8,7 +7,7 @@ namespace Fallout.Common.Utilities; [DebuggerNonUserCode] [DebuggerStepThrough] -public static partial class TaskExtensions +public static class TaskExtensions { public static void WaitAll(this IEnumerable tasks) { diff --git a/src/Fallout.Utilities/Text/String.Emptiness.cs b/src/Fallout.Utilities/Text/String.Emptiness.cs index af6abbb26..335c99b38 100644 --- a/src/Fallout.Utilities/Text/String.Emptiness.cs +++ b/src/Fallout.Utilities/Text/String.Emptiness.cs @@ -1,4 +1,3 @@ - namespace Fallout.Common.Utilities; public static partial class StringExtensions diff --git a/src/Fallout.Utilities/Text/String.Escape.cs b/src/Fallout.Utilities/Text/String.Escape.cs index 84da36d33..eedbd1741 100644 --- a/src/Fallout.Utilities/Text/String.Escape.cs +++ b/src/Fallout.Utilities/Text/String.Escape.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Utilities; public static partial class StringExtensions @@ -8,7 +5,9 @@ public static partial class StringExtensions public static string EscapeBraces(this string str) { if (string.IsNullOrWhiteSpace(str)) + { return string.Empty; + } return str.NotNull().Replace("{", "{{").Replace("}", "}}"); } diff --git a/src/Fallout.Utilities/Text/String.GetHash.cs b/src/Fallout.Utilities/Text/String.GetHash.cs index 477a7e0ea..922693a93 100644 --- a/src/Fallout.Utilities/Text/String.GetHash.cs +++ b/src/Fallout.Utilities/Text/String.GetHash.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Security.Cryptography; using System.Text; diff --git a/src/Fallout.Utilities/Text/String.Indent.cs b/src/Fallout.Utilities/Text/String.Indent.cs index 4fd5c7da1..603e12d65 100644 --- a/src/Fallout.Utilities/Text/String.Indent.cs +++ b/src/Fallout.Utilities/Text/String.Indent.cs @@ -1,4 +1,3 @@ - namespace Fallout.Common.Utilities; public static partial class StringExtensions diff --git a/src/Fallout.Utilities/Text/String.Join.cs b/src/Fallout.Utilities/Text/String.Join.cs index 2e5c9b17d..35fc75c10 100644 --- a/src/Fallout.Utilities/Text/String.Join.cs +++ b/src/Fallout.Utilities/Text/String.Join.cs @@ -109,7 +109,8 @@ public static string JoinCommaOr(this IEnumerable values) { ICollection valuesList = values.ToList(); return valuesList.Count >= 2 - ? valuesList.Reverse().Skip(1).Reverse().JoinCommaSpace() + $"{(valuesList.Count > 2 ? "," : string.Empty)} or " + valuesList.Last() + ? valuesList.Reverse().Skip(1).Reverse().JoinCommaSpace() + $"{(valuesList.Count > 2 ? "," : string.Empty)} or " + + valuesList.Last() : valuesList.JoinCommaSpace(); } @@ -120,7 +121,8 @@ public static string JoinCommaAnd(this IEnumerable values) { ICollection valuesList = values.ToList(); return valuesList.Count >= 2 - ? valuesList.Reverse().Skip(1).Reverse().JoinCommaSpace() + $"{(valuesList.Count > 2 ? "," : string.Empty)} and " + valuesList.Last() + ? valuesList.Reverse().Skip(1).Reverse().JoinCommaSpace() + $"{(valuesList.Count > 2 ? "," : string.Empty)} and " + + valuesList.Last() : valuesList.JoinCommaSpace(); } @@ -134,6 +136,7 @@ public static string JoinNewLine(this IEnumerable values, PlatformFamily : platformFamily.Value == PlatformFamily.Windows ? "\r\n" : "\n"; + return values.Join(newLine); } @@ -147,6 +150,7 @@ public static string JoinParagraph(this IEnumerable values, PlatformFami : platformFamily.Value == PlatformFamily.Windows ? "\r\n" : "\n"; + return values.Join(newLine + newLine); } } diff --git a/src/Fallout.Utilities/Text/String.KnownWords.cs b/src/Fallout.Utilities/Text/String.KnownWords.cs index 62bff0920..79174a037 100644 --- a/src/Fallout.Utilities/Text/String.KnownWords.cs +++ b/src/Fallout.Utilities/Text/String.KnownWords.cs @@ -16,4 +16,4 @@ public static partial class StringExtensions "SignPath", "JetBrains" }; -} \ No newline at end of file +} diff --git a/src/Fallout.Utilities/Text/String.PrependAppend.cs b/src/Fallout.Utilities/Text/String.PrependAppend.cs index 7c4c44304..323d7b2c4 100644 --- a/src/Fallout.Utilities/Text/String.PrependAppend.cs +++ b/src/Fallout.Utilities/Text/String.PrependAppend.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Utilities; public static partial class StringExtensions diff --git a/src/Fallout.Utilities/Text/String.Quoting.cs b/src/Fallout.Utilities/Text/String.Quoting.cs index 0f4b788a6..df8d2e1a4 100644 --- a/src/Fallout.Utilities/Text/String.Quoting.cs +++ b/src/Fallout.Utilities/Text/String.Quoting.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; namespace Fallout.Common.Utilities; @@ -19,13 +18,19 @@ public static string DoubleQuoteIfNeeded(this string str) public static string DoubleQuoteIfNeeded(this string str, params char?[] disallowed) { if (string.IsNullOrWhiteSpace(str)) + { return string.Empty; + } if (str.IsDoubleQuoted()) + { return str; + } if (!str.Contains(disallowed)) + { return str; + } return str.DoubleQuote(); } @@ -52,13 +57,19 @@ public static string SingleQuoteIfNeeded(this string str) public static string SingleQuoteIfNeeded(this string str, params char?[] disallowed) { if (string.IsNullOrWhiteSpace(str)) + { return string.Empty; + } if (str.IsSingleQuoted()) + { return str; + } if (!str.Contains(disallowed)) + { return str; + } return str.SingleQuote(); } @@ -101,4 +112,4 @@ private static bool Contains(this string str, char?[] chars) { return chars.Any(x => x.HasValue && str.IndexOf(x.Value) != -1); } -} \ No newline at end of file +} diff --git a/src/Fallout.Utilities/Text/String.Repeat.cs b/src/Fallout.Utilities/Text/String.Repeat.cs index 31c5b60a5..74ff3389f 100644 --- a/src/Fallout.Utilities/Text/String.Repeat.cs +++ b/src/Fallout.Utilities/Text/String.Repeat.cs @@ -1,5 +1,4 @@ - -namespace Fallout.Common.Utilities; +namespace Fallout.Common.Utilities; public static partial class StringExtensions { diff --git a/src/Fallout.Utilities/Text/String.Replace.cs b/src/Fallout.Utilities/Text/String.Replace.cs index f17c9b195..11b31818e 100644 --- a/src/Fallout.Utilities/Text/String.Replace.cs +++ b/src/Fallout.Utilities/Text/String.Replace.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -23,7 +22,7 @@ public static string ReplaceRegex( public static string ReplaceUnicode(this string str) { - return unicodeRegex.Replace(str, m => ((char) int.Parse(m.Groups["Value"].Value, NumberStyles.HexNumber)).ToString()); + return unicodeRegex.Replace(str, m => ((char)int.Parse(m.Groups["Value"].Value, NumberStyles.HexNumber)).ToString()); } public static string ReplaceKnownWords(this string str) diff --git a/src/Fallout.Utilities/Text/String.Split.cs b/src/Fallout.Utilities/Text/String.Split.cs index 8b7e5877f..940495bf6 100644 --- a/src/Fallout.Utilities/Text/String.Split.cs +++ b/src/Fallout.Utilities/Text/String.Split.cs @@ -12,12 +12,16 @@ public static IEnumerable Split(this string str, Func p for (var i = 0; i < str.Length; i++) { if (!predicate(str[i], i)) + { continue; + } yield return str.Substring(next, i - next); next = i; if (!includeSplitCharacter) + { next++; + } } yield return str.Substring(next); @@ -38,7 +42,8 @@ public static IEnumerable SplitCamelHumps(this string str, params string if (exclusions.Length > 0 && i >= exclusionIndex) { var currentExclusions = exclusions - .Select(x => (Exclusion: x, Index: str.IndexOf(x, exclusionIndex, StringComparison.InvariantCultureIgnoreCase))) + .Select(x => (Exclusion: x, + Index: str.IndexOf(x, exclusionIndex, StringComparison.InvariantCultureIgnoreCase))) .Where(x => x.Index == i).ToList(); if (currentExclusions.Any()) @@ -58,7 +63,11 @@ public static IEnumerable SplitCamelHumps(this string str, params string /// public static string[] SplitLineBreaks(this string str, StringSplitOptions options = StringSplitOptions.None) { - return str.Split(new[] { "\r\n", "\n" }, options); + return str.Split(new[] + { + "\r\n", + "\n" + }, options); } /// @@ -66,7 +75,11 @@ public static string[] SplitLineBreaks(this string str, StringSplitOptions optio /// public static string[] SplitParagraphs(this string str, StringSplitOptions options = StringSplitOptions.None) { - return str.Split(new[] { "\r\n\r\n", "\n\n" }, options); + return str.Split(new[] + { + "\r\n\r\n", + "\n\n" + }, options); } /// @@ -74,7 +87,10 @@ public static string[] SplitParagraphs(this string str, StringSplitOptions optio /// public static string[] SplitSpace(this string str) { - return str.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + return str.Split(new[] + { + ' ' + }, StringSplitOptions.RemoveEmptyEntries); } /// diff --git a/src/Fallout.Utilities/Text/String.Trim.cs b/src/Fallout.Utilities/Text/String.Trim.cs index b72b53c13..ee81fba34 100644 --- a/src/Fallout.Utilities/Text/String.Trim.cs +++ b/src/Fallout.Utilities/Text/String.Trim.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Utilities; public static partial class StringExtensions @@ -49,7 +46,7 @@ public static string TrimStart(this string str, string trim) /// public static string TrimMatchingDoubleQuotes(this string str) { - return TrimMatchingQuotes(str, quote: '"'); + return str.TrimMatchingQuotes(quote: '"'); } /// @@ -57,16 +54,20 @@ public static string TrimMatchingDoubleQuotes(this string str) /// public static string TrimMatchingSingleQuotes(this string str) { - return TrimMatchingQuotes(str, quote: '\''); + return str.TrimMatchingQuotes(quote: '\''); } internal static string TrimMatchingQuotes(this string str, char quote) { if (str.Length < 2) + { return str; + } if (str[index: 0] != quote || str[str.Length - 1] != quote) + { return str; + } return str.Substring(startIndex: 1, str.Length - 2); } diff --git a/src/Fallout.Utilities/Text/String.Truncate.cs b/src/Fallout.Utilities/Text/String.Truncate.cs index 7270b5acc..9333d0123 100644 --- a/src/Fallout.Utilities/Text/String.Truncate.cs +++ b/src/Fallout.Utilities/Text/String.Truncate.cs @@ -1,6 +1,3 @@ -using System; -using System.Linq; - namespace Fallout.Common.Utilities; partial class StringExtensions diff --git a/src/Fallout.Utilities/Text/Xml/XElement.GetAttributeValue.cs b/src/Fallout.Utilities/Text/Xml/XElement.GetAttributeValue.cs index 947306a76..cd4f18b32 100644 --- a/src/Fallout.Utilities/Text/Xml/XElement.GetAttributeValue.cs +++ b/src/Fallout.Utilities/Text/Xml/XElement.GetAttributeValue.cs @@ -1,10 +1,8 @@ -using System; -using System.Linq; -using System.Xml.Linq; +using System.Xml.Linq; namespace Fallout.Common.Utilities; -public static partial class XElementExtensions +public static class XElementExtensions { public static string GetAttributeValue(this XElement element, string name) { diff --git a/src/Fallout.Utilities/Text/Xml/XNode.XPathEvaluateValue.cs b/src/Fallout.Utilities/Text/Xml/XNode.XPathEvaluateValue.cs index 38b68371d..647a001a8 100644 --- a/src/Fallout.Utilities/Text/Xml/XNode.XPathEvaluateValue.cs +++ b/src/Fallout.Utilities/Text/Xml/XNode.XPathEvaluateValue.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Xml; @@ -8,24 +7,28 @@ namespace Fallout.Common.Utilities; -public static partial class XNodeExtensions +public static class XNodeExtensions { - public static IEnumerable XPathSelectElementValues(this XNode node, string query, params (string prefix, string uri)[] namespaces) + public static IEnumerable XPathSelectElementValues(this XNode node, string query, + params (string prefix, string uri)[] namespaces) { return node.XPathSelect(query, namespaces).Select(x => x.Value); } - public static IEnumerable XPathSelectAttributeValues(this XNode node, string query, params (string prefix, string uri)[] namespaces) + public static IEnumerable XPathSelectAttributeValues(this XNode node, string query, + params (string prefix, string uri)[] namespaces) { return node.XPathSelect(query, namespaces).Select(x => x.Value); } - public static IEnumerable XPathSelectElements(this XNode node, string query, params (string prefix, string uri)[] namespaces) + public static IEnumerable XPathSelectElements(this XNode node, string query, + params (string prefix, string uri)[] namespaces) { return node.XPathSelect(query, namespaces); } - public static IEnumerable XPathSelectAttributes(this XNode node, string query, params (string prefix, string uri)[] namespaces) + public static IEnumerable XPathSelectAttributes(this XNode node, string query, + params (string prefix, string uri)[] namespaces) { return node.XPathSelect(query, namespaces); } @@ -41,7 +44,9 @@ private static IEnumerable XPathSelect(this XNode node, string query, para { xmlNamespaceManager = new XmlNamespaceManager(reader.NameTable); foreach (var (prefix, uri) in namespaces) + { xmlNamespaceManager.AddNamespace(prefix, uri); + } } } diff --git a/src/Fallout.Utilities/Text/Xml/XmlExtensions.cs b/src/Fallout.Utilities/Text/Xml/XmlExtensions.cs index a99ba8a9d..988876a22 100644 --- a/src/Fallout.Utilities/Text/Xml/XmlExtensions.cs +++ b/src/Fallout.Utilities/Text/Xml/XmlExtensions.cs @@ -32,7 +32,7 @@ public static T GetXml(this string content, LoadOptions options = LoadOptions var document = XDocument.Parse(content, options); using var reader = document.Root.NotNull().CreateReader(); - return (T) serializer.Deserialize(reader); + return (T)serializer.Deserialize(reader); } /// diff --git a/src/Fallout.Utilities/Url.WithUtmValues.cs b/src/Fallout.Utilities/Url.WithUtmValues.cs index 937d55da5..7e8669bb2 100644 --- a/src/Fallout.Utilities/Url.WithUtmValues.cs +++ b/src/Fallout.Utilities/Url.WithUtmValues.cs @@ -14,12 +14,12 @@ public static Uri WithUtmValues(this Uri uri, string medium, string source, stri var lastSegment = uri.Segments.Last().Trim('/').Apply(x => x.IsNullOrWhiteSpace() ? null : x); var dictionary = new Dictionary - { - ["utm_medium"] = medium.NotNullOrWhiteSpace(), - ["utm_source"] = source.NotNullOrWhiteSpace(), - ["utm_campaign"] = campaign ?? lastSegment, - ["utm_content"] = content ?? (campaign != null ? lastSegment : null), - }; + { + ["utm_medium"] = medium.NotNullOrWhiteSpace(), + ["utm_source"] = source.NotNullOrWhiteSpace(), + ["utm_campaign"] = campaign ?? lastSegment, + ["utm_content"] = content ?? (campaign != null ? lastSegment : null), + }; var query = dictionary.Where(x => x.Value != null).Select(x => $"{x.Key}={x.Value}").Join("&"); return new Uri(uri.AbsoluteUri + "?" + query); diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/BuildTypeNames.cs b/src/Persistence/Fallout.Persistence.Solution/Model/BuildTypeNames.cs index 4f3a8b63b..4ef3ce23c 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/BuildTypeNames.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/BuildTypeNames.cs @@ -26,6 +26,7 @@ internal static bool TryGetKnown(StringSpan buildType, [NotNullWhen(true)] out s Release => Release, _ => null, }; + return value is not null; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/ConfigurationRuleFollower.cs b/src/Persistence/Fallout.Persistence.Solution/Model/ConfigurationRuleFollower.cs index 367899568..7927c6cfa 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/ConfigurationRuleFollower.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/ConfigurationRuleFollower.cs @@ -10,29 +10,29 @@ internal readonly ref struct ConfigurationRuleFollower(IReadOnlyList? configurationRules = configurationRules; - internal bool HasRules => !this.configurationRules.IsNullOrEmpty(); + internal bool HasRules => !configurationRules.IsNullOrEmpty(); internal readonly bool? GetIsBuildable(string? solutionBuildType = null, string? solutionPlatform = null) { - string value = this.GetDimensionValue(BuildDimension.Build, solutionBuildType, solutionPlatform); + string value = GetDimensionValue(BuildDimension.Build, solutionBuildType, solutionPlatform); return string.IsNullOrEmpty(value) ? null : bool.Parse(value); } internal readonly bool? GetIsDeployable(string solutionBuildType, string solutionPlatform) { - string value = this.GetDimensionValue(BuildDimension.Deploy, solutionBuildType, solutionPlatform); + string value = GetDimensionValue(BuildDimension.Deploy, solutionBuildType, solutionPlatform); return string.IsNullOrEmpty(value) ? null : bool.Parse(value); } internal readonly string? GetProjectBuildType(string solutionBuildType, string solutionPlatform) { - string value = this.GetDimensionValue(BuildDimension.BuildType, solutionBuildType, solutionPlatform); + string value = GetDimensionValue(BuildDimension.BuildType, solutionBuildType, solutionPlatform); return value == "*" ? solutionBuildType : value.NullIfEmpty(); } internal readonly string? GetProjectPlatform(string? solutionBuildType = null, string? solutionPlatform = null) { - string value = this.GetDimensionValue(BuildDimension.Platform, solutionBuildType, solutionPlatform); + string value = GetDimensionValue(BuildDimension.Platform, solutionBuildType, solutionPlatform); return value == "*" ? solutionPlatform : value.NullIfEmpty(); } @@ -41,7 +41,8 @@ internal readonly ref struct ConfigurationRuleFollower(IReadOnlyList - private static bool RuleAppliesTo(ConfigurationRule rule, BuildDimension dimension, string? solutionBuildType, string? solutionPlatform) + private static bool RuleAppliesTo(ConfigurationRule rule, BuildDimension dimension, string? solutionBuildType, + string? solutionPlatform) { if (rule.Dimension != dimension) { @@ -55,8 +56,8 @@ private static bool RuleAppliesTo(ConfigurationRule rule, BuildDimension dimensi // so this needs to validate that the solution value is specified. // Otherwise this always returns true. return rule.ProjectValue != "*" || - ((dimension != BuildDimension.BuildType || IsSpecified(solutionBuildType)) && - (dimension != BuildDimension.Platform || IsSpecified(solutionPlatform))); + ((dimension != BuildDimension.BuildType || IsSpecified(solutionBuildType)) && + (dimension != BuildDimension.Platform || IsSpecified(solutionPlatform))); } // If the rule applies to all Platforms, the BuildType must be specified and match. @@ -72,17 +73,21 @@ private static bool RuleAppliesTo(ConfigurationRule rule, BuildDimension dimensi static bool AppliesToAllPlatforms(ConfigurationRule rule) => rule.SolutionPlatform.IsNullOrEmpty(); static bool IsSpecified([NotNullWhen(true)] string? dimensionValue) => !dimensionValue.IsNullOrEmpty(); + static bool IsSame(ConfigurationRule rule, string solutionBuildType, string solutionPlatform) => IsSameBuildType(rule, solutionBuildType) && IsSamePlatform(rule, solutionPlatform); + static bool IsSameBuildType(ConfigurationRule rule, string solutionBuildType) => StringComparer.OrdinalIgnoreCase.Equals(rule.SolutionBuildType, solutionBuildType); + static bool IsSamePlatform(ConfigurationRule rule, string solutionPlatform) => - StringComparer.OrdinalIgnoreCase.Equals(PlatformNames.Canonical(rule.SolutionPlatform), PlatformNames.Canonical(solutionPlatform)); + StringComparer.OrdinalIgnoreCase.Equals(PlatformNames.Canonical(rule.SolutionPlatform), + PlatformNames.Canonical(solutionPlatform)); } private readonly string GetDimensionValue(BuildDimension dimension, string? solutionBuildType, string? solutionPlatform) { - foreach (ConfigurationRule rule in this.configurationRules.GetStructReverseEnumerable()) + foreach (ConfigurationRule rule in configurationRules.GetStructReverseEnumerable()) { if (RuleAppliesTo(rule, dimension, solutionBuildType, solutionPlatform)) { diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/ModelHelper.cs b/src/Persistence/Fallout.Persistence.Solution/Model/ModelHelper.cs index 51353e4f5..0bc8c0c28 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/ModelHelper.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/ModelHelper.cs @@ -55,9 +55,9 @@ internal static string GetDisplayName(this ProjectType projectType) internal static string GetSolutionConfiguration(this ConfigurationRule rule) { - return rule.SolutionBuildType.IsNullOrEmpty() && rule.SolutionPlatform.IsNullOrEmpty() ? - string.Empty : - $"{rule.SolutionBuildType.NullIfEmpty() ?? BuildTypeNames.All}|{rule.SolutionPlatform.NullIfEmpty() ?? PlatformNames.All}"; + return rule.SolutionBuildType.IsNullOrEmpty() && rule.SolutionPlatform.IsNullOrEmpty() + ? string.Empty + : $"{rule.SolutionBuildType.NullIfEmpty() ?? BuildTypeNames.All}|{rule.SolutionPlatform.NullIfEmpty() ?? PlatformNames.All}"; } // Splits the configuration into build type and platform. @@ -120,11 +120,13 @@ internal static ConfigurationRule CreatePlatformRule(string solutionPlatform, st internal static ConfigurationRule CreateNoBuildRule() { - return new ConfigurationRule(BuildDimension.Build, solutionBuildType: string.Empty, solutionPlatform: string.Empty, projectValue: bool.FalseString); + return new ConfigurationRule(BuildDimension.Build, solutionBuildType: string.Empty, solutionPlatform: string.Empty, + projectValue: bool.FalseString); } internal static ConfigurationRule CreateNoPlatformsRule() { - return new ConfigurationRule(BuildDimension.Platform, solutionBuildType: string.Empty, solutionPlatform: string.Empty, projectValue: PlatformNames.Missing); + return new ConfigurationRule(BuildDimension.Platform, solutionBuildType: string.Empty, solutionPlatform: string.Empty, + projectValue: PlatformNames.Missing); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/PlatformNames.cs b/src/Persistence/Fallout.Persistence.Solution/Model/PlatformNames.cs index 0bf2de306..0aecea4df 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/PlatformNames.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/PlatformNames.cs @@ -28,7 +28,8 @@ internal static class PlatformNames internal const string ARM = nameof(ARM); internal const string ARM64 = nameof(ARM64); - internal static string Canonical(string platform) => string.Equals(platform, AnySpaceCPU, StringComparison.OrdinalIgnoreCase) ? AnyCPU : platform; + internal static string Canonical(string platform) => + string.Equals(platform, AnySpaceCPU, StringComparison.OrdinalIgnoreCase) ? AnyCPU : platform; internal static string ToStringKnown(string platform) { @@ -53,6 +54,7 @@ internal static bool TryGetKnown(StringSpan platform, [NotNullWhen(true)] out st ARM64 => ARM64, _ => null, }; + return value is not null; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectConfigMapping.cs b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectConfigMapping.cs index 9aa6d6e37..449228c6f 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectConfigMapping.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectConfigMapping.cs @@ -18,15 +18,16 @@ internal readonly struct ProjectConfigMapping(string buildType, string platform, internal bool Deploy { get; init; } = deploy; - internal readonly bool IsValidBuildType => !string.IsNullOrEmpty(this.BuildType) && this.BuildType != BuildTypeNames.All; + internal readonly bool IsValidBuildType => !string.IsNullOrEmpty(BuildType) && BuildType != BuildTypeNames.All; - internal readonly bool IsValidPlatform => !string.IsNullOrEmpty(this.Platform) && this.Platform != PlatformNames.All; + internal readonly bool IsValidPlatform => !string.IsNullOrEmpty(Platform) && Platform != PlatformNames.All; internal readonly bool IsSame(in ProjectConfigMapping other) { - return other.Build == this.Build && - other.Deploy == this.Deploy && - StringComparer.Ordinal.Equals(this.BuildType, other.BuildType) && - (this.Platform == other.Platform || StringComparer.Ordinal.Equals(PlatformNames.Canonical(this.Platform), PlatformNames.Canonical(other.Platform))); + return other.Build == Build && + other.Deploy == Deploy && + StringComparer.Ordinal.Equals(BuildType, other.BuildType) && + (Platform == other.Platform || StringComparer.Ordinal.Equals(PlatformNames.Canonical(Platform), + PlatformNames.Canonical(other.Platform))); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.BuiltInTypes.cs b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.BuiltInTypes.cs index 041e7bafc..57571a57a 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.BuiltInTypes.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.BuiltInTypes.cs @@ -10,23 +10,25 @@ internal sealed partial class ProjectTypeTable internal static readonly ConfigurationRule NoPlatformsRule = ModelHelper.CreateNoPlatformsRule(); - internal static readonly Guid VCXProj = new Guid("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); - internal static readonly Guid SolutionFolder = new Guid("2150E333-8FDC-42A3-9474-1A3956D46DE8"); + internal static readonly Guid VCXProj = new("8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"); + internal static readonly Guid SolutionFolder = new("2150E333-8FDC-42A3-9474-1A3956D46DE8"); - private static readonly ConfigurationRule[] ClrBuildRules = [ModelHelper.CreatePlatformRule(string.Empty, PlatformNames.AnyCPU)]; + private static readonly ConfigurationRule[] ClrBuildRules = + [ModelHelper.CreatePlatformRule(string.Empty, PlatformNames.AnyCPU)]; private static ProjectTypeTable? implicitProjectTypes; - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", Justification = "Creating multi-item table.")] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:Parameter should not span multiple lines", + Justification = "Creating multi-item table.")] internal static ProjectTypeTable BuiltInTypes => implicitProjectTypes ??= new ProjectTypeTable( isBuiltIn: true, - projectTypes: [ - + projectTypes: + [ // Base rules that apply to all project types. new ProjectType( Guid.Empty, - rules: [ - + rules: + [ // Sets the project build type to be the same as the solution build type. new ConfigurationRule(BuildDimension.BuildType, string.Empty, string.Empty, BuildTypeNames.All), @@ -39,61 +41,152 @@ internal sealed partial class ProjectTypeTable ]), // Common Project System CLR projects. - new ProjectType(new Guid("9A19103F-16F7-4668-BE54-9A1E7A4F7556"), ClrBuildRules) { Name = "Common C#" }, - new ProjectType(new Guid("778DAE3C-4631-46EA-AA77-85C1314464D9"), ClrBuildRules) { Name = "Common VB" }, - new ProjectType(new Guid("6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705"), ClrBuildRules) { Name = "Common F#" }, + new ProjectType(new Guid("9A19103F-16F7-4668-BE54-9A1E7A4F7556"), ClrBuildRules) + { + Name = "Common C#" + }, + new ProjectType(new Guid("778DAE3C-4631-46EA-AA77-85C1314464D9"), ClrBuildRules) + { + Name = "Common VB" + }, + new ProjectType(new Guid("6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705"), ClrBuildRules) + { + Name = "Common F#" + }, // Default CLR projects. - new ProjectType(new Guid("FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"), ClrBuildRules) { Name = "C#", Extension = ".csproj" }, - new ProjectType(new Guid("F184B08F-C81C-45F6-A57F-5ABD9991F28F"), ClrBuildRules) { Name = "VB", Extension = ".vbproj" }, - new ProjectType(new Guid("F2A71F9B-5D33-465A-A702-920D77279786"), ClrBuildRules) { Name = "F#", Extension = ".fsproj" }, + new ProjectType(new Guid("FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"), ClrBuildRules) + { + Name = "C#", + Extension = ".csproj" + }, + new ProjectType(new Guid("F184B08F-C81C-45F6-A57F-5ABD9991F28F"), ClrBuildRules) + { + Name = "VB", + Extension = ".vbproj" + }, + new ProjectType(new Guid("F2A71F9B-5D33-465A-A702-920D77279786"), ClrBuildRules) + { + Name = "F#", + Extension = ".fsproj" + }, // CLR shared code project - new ProjectType(new Guid("D954291E-2A0B-460D-934E-DC6B0785DB48"), NoBuildRules) { Name = "Shared", Extension = ".shproj" }, + new ProjectType(new Guid("D954291E-2A0B-460D-934E-DC6B0785DB48"), NoBuildRules) + { + Name = "Shared", + Extension = ".shproj" + }, // Website project - new ProjectType(new Guid("E24C65DC-7377-472B-9ABA-BC803B73C61A"), ClrBuildRules) { Name = "Website", Extension = ".webproj" }, + new ProjectType(new Guid("E24C65DC-7377-472B-9ABA-BC803B73C61A"), ClrBuildRules) + { + Name = "Website", + Extension = ".webproj" + }, // Visual C++ project new ProjectType( VCXProj, - rules: [ + rules: + [ ModelHelper.CreatePlatformRule(PlatformNames.AnyCPU, PlatformNames.x64), ModelHelper.CreatePlatformRule(PlatformNames.x86, PlatformNames.Win32), ]) - { Name = "VC", Extension = ".vcxproj" }, + { + Name = "VC", + Extension = ".vcxproj" + }, // Visual C++ shared code project. // This is a special project type that is used to represent shared items in C++ projects. // It uses the same project type id as vcxproj, but doesn't have configurations. // It does not specify a name since it shares a project type id with vcxproj, // that way 'VC' is always used as the friendly name for the VCXProj guid. - new ProjectType(VCXProj, NoBuildRules) { Extension = ".vcxitems" }, + new ProjectType(VCXProj, NoBuildRules) + { + Extension = ".vcxitems" + }, // Exe project type - new ProjectType(new Guid("911E67C6-3D85-4FCE-B560-20A9C3E3FF48"), NoBuildRules) { Name = "Exe", Extension = ".exe" }, + new ProjectType(new Guid("911E67C6-3D85-4FCE-B560-20A9C3E3FF48"), NoBuildRules) + { + Name = "Exe", + Extension = ".exe" + }, // This probably won't get used, but adding to make sure it doesn't see configurations. - new ProjectType(SolutionFolder, NoBuildRules) { Name = "Folder" }, + new ProjectType(SolutionFolder, NoBuildRules) + { + Name = "Folder" + }, // JavaScript project types - new ProjectType(new Guid("54A90642-561A-4BB1-A94E-469ADEE60C69"), NoBuildRules) { Name = "Javascript", Extension = ".esproj" }, - new ProjectType(new Guid("9092AA53-FB77-4645-B42D-1CCCA6BD08BD"), NoBuildRules) { Name = "Node.js", Extension = ".njsproj" }, + new ProjectType(new Guid("54A90642-561A-4BB1-A94E-469ADEE60C69"), NoBuildRules) + { + Name = "Javascript", + Extension = ".esproj" + }, + new ProjectType(new Guid("9092AA53-FB77-4645-B42D-1CCCA6BD08BD"), NoBuildRules) + { + Name = "Node.js", + Extension = ".njsproj" + }, // Setup project types - new ProjectType(new Guid("151D2E53-A2C4-4D7D-83FE-D05416EBD58E"), NoBuildRules) { Name = "Deploy", Extension = ".deployproj" }, - new ProjectType(new Guid("54435603-DBB4-11D2-8724-00A0C9A8B90C"), NoBuildRules) { Name = "Installer", Extension = ".vsproj" }, - new ProjectType(new Guid("930C7802-8A8C-48F9-8165-68863BCCD9DD"), NoBuildRules) { Name = "Wix", Extension = ".wixproj" }, + new ProjectType(new Guid("151D2E53-A2C4-4D7D-83FE-D05416EBD58E"), NoBuildRules) + { + Name = "Deploy", + Extension = ".deployproj" + }, + new ProjectType(new Guid("54435603-DBB4-11D2-8724-00A0C9A8B90C"), NoBuildRules) + { + Name = "Installer", + Extension = ".vsproj" + }, + new ProjectType(new Guid("930C7802-8A8C-48F9-8165-68863BCCD9DD"), NoBuildRules) + { + Name = "Wix", + Extension = ".wixproj" + }, // SQL project types - new ProjectType(new Guid("00D1A9C2-B5F0-4AF3-8072-F6C62B433612"), NoBuildRules) { Name = "SQL", Extension = ".sqlproj" }, - new ProjectType(new Guid("0C603C2C-620A-423B-A800-4F3E2F6281F1"), NoBuildRules) { Name = "U-SQL-DB", Extension = ".usqldbproj" }, - new ProjectType(new Guid("182E2583-ECAD-465B-BB50-91101D7C24CE"), NoBuildRules) { Name = "U-SQL", Extension = ".usqlproj" }, - new ProjectType(new Guid("F14B399A-7131-4C87-9E4B-1186C45EF12D"), [NoPlatformsRule]) { Name = "SSRS", Extension = ".rptproj" }, + new ProjectType(new Guid("00D1A9C2-B5F0-4AF3-8072-F6C62B433612"), NoBuildRules) + { + Name = "SQL", + Extension = ".sqlproj" + }, + new ProjectType(new Guid("0C603C2C-620A-423B-A800-4F3E2F6281F1"), NoBuildRules) + { + Name = "U-SQL-DB", + Extension = ".usqldbproj" + }, + new ProjectType(new Guid("182E2583-ECAD-465B-BB50-91101D7C24CE"), NoBuildRules) + { + Name = "U-SQL", + Extension = ".usqlproj" + }, + new ProjectType(new Guid("F14B399A-7131-4C87-9E4B-1186C45EF12D"), [NoPlatformsRule]) + { + Name = "SSRS", + Extension = ".rptproj" + }, // Azure project types - new ProjectType(new Guid("A07B5EB6-E848-4116-A8D0-A826331D98C6"), NoBuildRules) { Name = "Fabric", Extension = ".sfproj" }, - new ProjectType(new Guid("CC5FD16D-436D-48AD-A40C-5A424C6E3E79"), NoBuildRules) { Name = "Cloud Computing", Extension = ".ccproj" }, - new ProjectType(new Guid("E53339B2-1760-4266-BCC7-CA923CBCF16C"), NoBuildRules) { Name = "Docker", Extension = ".dcproj" }, + new ProjectType(new Guid("A07B5EB6-E848-4116-A8D0-A826331D98C6"), NoBuildRules) + { + Name = "Fabric", + Extension = ".sfproj" + }, + new ProjectType(new Guid("CC5FD16D-436D-48AD-A40C-5A424C6E3E79"), NoBuildRules) + { + Name = "Cloud Computing", + Extension = ".ccproj" + }, + new ProjectType(new Guid("E53339B2-1760-4266-BCC7-CA923CBCF16C"), NoBuildRules) + { + Name = "Docker", + Extension = ".dcproj" + }, ]); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.cs b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.cs index 6c95c72e3..1aa84f346 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/ProjectTypeTable.cs @@ -30,44 +30,49 @@ internal ProjectTypeTable(List projectTypes) private ProjectTypeTable(bool isBuiltIn, List projectTypes) { this.isBuiltIn = isBuiltIn; - this.projectTypesList = projectTypes; - this.fromExtension = new(this.ProjectTypes.Count, StringComparer.OrdinalIgnoreCase); - this.fromName = new(this.ProjectTypes.Count, StringComparer.OrdinalIgnoreCase); - this.fromProjectTypeId = new(this.ProjectTypes.Count); + projectTypesList = projectTypes; + fromExtension = new Dictionary(ProjectTypes.Count, StringComparer.OrdinalIgnoreCase); + fromName = new Dictionary(ProjectTypes.Count, StringComparer.OrdinalIgnoreCase); + fromProjectTypeId = new Dictionary(ProjectTypes.Count); foreach (ProjectType type in projectTypes.GetStructEnumerable()) { if (!type.Extension.IsNullOrEmpty() && - !this.fromExtension.TryAdd(GetExtension(type.Extension), type)) + !fromExtension.TryAdd(GetExtension(type.Extension), type)) { string projectType = type.GetDisplayName(); - throw new SolutionException(string.Format(Errors.DuplicateExtension_Args2, type.Extension, projectType), SolutionErrorType.DuplicateExtension); + throw new SolutionException(string.Format(Errors.DuplicateExtension_Args2, type.Extension, projectType), + SolutionErrorType.DuplicateExtension); } if (!type.Name.IsNullOrEmpty()) { - if (!this.fromName.TryAdd(type.Name, type)) + if (!fromName.TryAdd(type.Name, type)) { string projectType = type.GetDisplayName(); - throw new SolutionException(string.Format(Errors.DuplicateName_Args2, type.Name, projectType), SolutionErrorType.DuplicateName); + throw new SolutionException(string.Format(Errors.DuplicateName_Args2, type.Name, projectType), + SolutionErrorType.DuplicateName); } // If a name isn't provided, it is just to map an extension to a project type. - if (type.ProjectTypeId != Guid.Empty && !this.fromProjectTypeId.TryAdd(type.ProjectTypeId, type)) + if (type.ProjectTypeId != Guid.Empty && !fromProjectTypeId.TryAdd(type.ProjectTypeId, type)) { string projectType = type.GetDisplayName(); - throw new SolutionException(string.Format(Errors.DuplicateProjectTypeId_Args2, type.ProjectTypeId, projectType), SolutionErrorType.DuplicateProjectTypeId); + throw new SolutionException( + string.Format(Errors.DuplicateProjectTypeId_Args2, type.ProjectTypeId, projectType), + SolutionErrorType.DuplicateProjectTypeId); } } if (string.IsNullOrEmpty(type.Name) && string.IsNullOrEmpty(type.Extension) && type.ProjectTypeId == Guid.Empty) { - if (this.defaultRules is not null) + if (defaultRules is not null) { - throw new SolutionException(Errors.DuplicateDefaultProjectType, SolutionErrorType.DuplicateDefaultProjectType); + throw new SolutionException(Errors.DuplicateDefaultProjectType, + SolutionErrorType.DuplicateDefaultProjectType); } - this.defaultRules ??= type.ConfigurationRules; + defaultRules ??= type.ConfigurationRules; } } @@ -75,46 +80,48 @@ private ProjectTypeTable(bool isBuiltIn, List projectTypes) { if (!type.BasedOn.IsNullOrEmpty()) { - if (this.GetBasedOnType(type) is null) + if (GetBasedOnType(type) is null) { - throw new SolutionException(string.Format(Errors.InvalidProjectTypeReference_Args1, type.BasedOn), SolutionErrorType.InvalidProjectTypeReference); + throw new SolutionException(string.Format(Errors.InvalidProjectTypeReference_Args1, type.BasedOn), + SolutionErrorType.InvalidProjectTypeReference); } // Check for loops in the BasedOn chain using Floyd's cycle-finding algorithm. ProjectType? currentSlow = type; - ProjectType? currentFast = this.GetBasedOnType(type); + ProjectType? currentFast = GetBasedOnType(type); while (currentSlow is not null && currentFast is not null) { - if (object.ReferenceEquals(currentSlow, currentFast)) + if (ReferenceEquals(currentSlow, currentFast)) { string projectType = type.GetDisplayName(); - throw new SolutionException(string.Format(Errors.InvalidLoop_Args1, projectType), SolutionErrorType.InvalidLoop); + throw new SolutionException(string.Format(Errors.InvalidLoop_Args1, projectType), + SolutionErrorType.InvalidLoop); } - currentSlow = this.GetBasedOnType(currentSlow); - currentFast = this.GetBasedOnType(this.GetBasedOnType(currentFast)); + currentSlow = GetBasedOnType(currentSlow); + currentFast = GetBasedOnType(GetBasedOnType(currentFast)); } } } - this.defaultRules ??= []; + defaultRules ??= []; static string GetExtension(string extension) => extension.StartsWith('.') ? extension : $".{extension}"; } - internal IReadOnlyList ProjectTypes => this.projectTypesList; + internal IReadOnlyList ProjectTypes => projectTypesList; internal Guid? GetProjectTypeId(string? alias, StringSpan extension) { return - this.GetProjectTypeId(this.GetForName(alias) ?? this.GetForExtension(extension.ToString())) ?? - (this.isBuiltIn ? null : BuiltInTypes.GetProjectTypeId(alias, extension)); + GetProjectTypeId(GetForName(alias) ?? GetForExtension(extension.ToString())) ?? + (isBuiltIn ? null : BuiltInTypes.GetProjectTypeId(alias, extension)); } // Figures out what the most concise friendly type name of the project type is, if it fails use the project type id. internal string GetConciseType(SolutionProjectModel projectModel) { - return this.GetConciseType(projectModel.TypeId, projectModel.Type, projectModel.Extension); + return GetConciseType(projectModel.TypeId, projectModel.Type, projectModel.Extension); } // Figures out what the most concise friendly type name of the project type is, if it fails use the project type id. @@ -122,27 +129,30 @@ internal string GetConciseType(Guid typeId, string type, string extension) { // Get TypeId to add to the Project element. return - !this.TryGetProjectType(typeId, type, extension, out ProjectType? projectType, out bool impliedFromExtension) ? GetTypeFromModel(typeId, type) : - !impliedFromExtension ? GetTypeFromProjectType(projectType) : - string.Empty; + !TryGetProjectType(typeId, type, extension, out ProjectType? projectType, out bool impliedFromExtension) + ? GetTypeFromModel(typeId, type) + : !impliedFromExtension + ? GetTypeFromProjectType(projectType) + : string.Empty; string GetTypeFromProjectType(ProjectType projectType) => - projectType.Name.NullIfEmpty() ?? this.GetProjectTypeId(projectType)?.ToString() ?? Guid.Empty.ToString(); + projectType.Name.NullIfEmpty() ?? GetProjectTypeId(projectType)?.ToString() ?? Guid.Empty.ToString(); static string GetTypeFromModel(Guid typeId, string type) => typeId == Guid.Empty ? type : typeId.ToString(); } // Gets all of the configuration rules that apply to the project. - internal ConfigurationRuleFollower GetProjectConfigurationRules(SolutionProjectModel projectModel, bool excludeProjectSpecificRules = false) + internal ConfigurationRuleFollower GetProjectConfigurationRules(SolutionProjectModel projectModel, + bool excludeProjectSpecificRules = false) { // Rules are ordered most general to most specific. - if (this.TryGetProjectType(projectModel, out ProjectType? type, out _)) + if (TryGetProjectType(projectModel, out ProjectType? type, out _)) { - List rules = new List(32); + List rules = new(32); // Get the default built-in rules. - if (!this.isBuiltIn) + if (!isBuiltIn) { rules.AddRange(BuiltInTypes.defaultRules); } @@ -151,7 +161,7 @@ internal ConfigurationRuleFollower GetProjectConfigurationRules(SolutionProjectM GetProjectTypeConfigurationRules(type, rules); // Get the default rules in the solution. These intentionally are higher priority than type rules. - rules.AddRange(this.defaultRules); + rules.AddRange(defaultRules); // Gets the rules defined on this project model. if (projectModel.ProjectConfigurationRules is not null && !excludeProjectSpecificRules) @@ -177,7 +187,7 @@ void GetProjectTypeConfigurationRules(ProjectType? type, List return; } - GetProjectTypeConfigurationRules(this.GetBasedOnType(type), rules); + GetProjectTypeConfigurationRules(GetBasedOnType(type), rules); rules.AddRange(type.ConfigurationRules); } } @@ -187,7 +197,7 @@ void GetProjectTypeConfigurationRules(ProjectType? type, List // If the type doesn't have a project type id, keep searching on the BasedOn type. while (type is not null && type.ProjectTypeId == Guid.Empty) { - type = this.GetBasedOnType(type); + type = GetBasedOnType(type); } return type?.ProjectTypeId; @@ -196,10 +206,10 @@ void GetProjectTypeConfigurationRules(ProjectType? type, List private ProjectType? GetBasedOnType(ProjectType? type) { return type is not null && !type.BasedOn.IsNullOrEmpty() && - (this.TryGetProjectType(Guid.Empty, type.BasedOn, null, out ProjectType? basedOnType, out _) || - BuiltInTypes.TryGetProjectType(Guid.Empty, null, type.BasedOn, out basedOnType, out _)) ? - basedOnType : - null; + (TryGetProjectType(Guid.Empty, type.BasedOn, null, out ProjectType? basedOnType, out _) || + BuiltInTypes.TryGetProjectType(Guid.Empty, null, type.BasedOn, out basedOnType, out _)) + ? basedOnType + : null; } private bool TryGetProjectType( @@ -207,7 +217,7 @@ private bool TryGetProjectType( [NotNullWhen(true)] out ProjectType? type, out bool impliedFromExtension) { - return this.TryGetProjectType( + return TryGetProjectType( projectModel.TypeId, projectModel.Type, projectModel.Extension, @@ -230,10 +240,10 @@ private bool TryGetProjectType( } // Only pick the implied type from the extension if it matches the typeName. - type = this.GetForExtension(extension); + type = GetForExtension(extension); if (type is not null) { - Guid typeProjectTypeId = this.GetProjectTypeId(type) ?? Guid.Empty; + Guid typeProjectTypeId = GetProjectTypeId(type) ?? Guid.Empty; if ((projectTypeId == Guid.Empty || typeProjectTypeId == projectTypeId) && (typeName.IsNullOrEmpty() || StringComparer.OrdinalIgnoreCase.Equals(typeName, type.Name))) { @@ -242,21 +252,21 @@ private bool TryGetProjectType( } } - type = this.GetForName(typeName); + type = GetForName(typeName); if (type is not null) { impliedFromExtension = false; return true; } - if (this.fromProjectTypeId.TryGetValue(projectTypeId, out type)) + if (fromProjectTypeId.TryGetValue(projectTypeId, out type)) { impliedFromExtension = false; return true; } // If not found in solution scope, try implicit types. - if (!this.isBuiltIn) + if (!isBuiltIn) { if (BuiltInTypes.TryGetProjectType(projectTypeId, typeName, extension, out type, out impliedFromExtension)) { @@ -271,11 +281,11 @@ private bool TryGetProjectType( private ProjectType? GetForExtension(string? extension) { - return !extension.IsNullOrEmpty() && this.fromExtension.TryGetValue(extension, out ProjectType? type) ? type : null; + return !extension.IsNullOrEmpty() && fromExtension.TryGetValue(extension, out ProjectType? type) ? type : null; } private ProjectType? GetForName(string? name) { - return !name.IsNullOrEmpty() && this.fromName.TryGetValue(name, out ProjectType? type) ? type : null; + return !name.IsNullOrEmpty() && fromName.TryGetValue(name, out ProjectType? type) ? type : null; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/PropertyContainerModel.cs b/src/Persistence/Fallout.Persistence.Solution/Model/PropertyContainerModel.cs index 47bb00514..82434a7aa 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/PropertyContainerModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/PropertyContainerModel.cs @@ -23,10 +23,10 @@ private protected PropertyContainerModel(PropertyContainerModel propertyContaine { if (!propertyContainer.properties.IsNullOrEmpty()) { - this.properties = new List(propertyContainer.properties.Count); + properties = new List(propertyContainer.properties.Count); foreach (SolutionPropertyBag property in propertyContainer.properties) { - this.properties.Add(new SolutionPropertyBag(property)); + properties.Add(new SolutionPropertyBag(property)); } } } @@ -34,7 +34,7 @@ private protected PropertyContainerModel(PropertyContainerModel propertyContaine /// /// Gets properties associated with this model. /// - public IReadOnlyList Properties => this.properties.IReadOnlyList() ?? []; + public IReadOnlyList Properties => properties.IReadOnlyList() ?? []; /// /// Gets a property bag by its id. @@ -43,7 +43,7 @@ private protected PropertyContainerModel(PropertyContainerModel propertyContaine /// The property bag if found. public SolutionPropertyBag? FindProperties(string id) { - return ModelHelper.FindByItemRef(this.properties, id); + return properties.FindByItemRef(id); } /// @@ -54,8 +54,8 @@ private protected PropertyContainerModel(PropertyContainerModel propertyContaine /// The property bag. public SolutionPropertyBag AddProperties(string id, PropertiesScope scope = PropertiesScope.PreLoad) { - this.properties ??= []; - return this.FindProperties(id) ?? this.properties.AddAndReturn(new SolutionPropertyBag(id, scope)); + properties ??= []; + return FindProperties(id) ?? properties.AddAndReturn(new SolutionPropertyBag(id, scope)); } /// @@ -67,7 +67,7 @@ public bool RemoveProperties(string id) { return this.properties is not null && - this.FindProperties(id) is SolutionPropertyBag properties && + FindProperties(id) is SolutionPropertyBag properties && this.properties.Remove(properties); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionArgumentException.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionArgumentException.cs index 8ea30d32d..e1fd20253 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionArgumentException.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionArgumentException.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; - namespace Fallout.Persistence.Solution.Model; /// @@ -18,7 +16,7 @@ public class SolutionArgumentException : ArgumentException public SolutionArgumentException(string? message, SolutionErrorType type) : base(message) { - this.Type = type; + Type = type; } /// @@ -30,7 +28,7 @@ public SolutionArgumentException(string? message, SolutionErrorType type) public SolutionArgumentException(string? message, Exception? innerException, SolutionErrorType type) : base(message, innerException) { - this.Type = type; + Type = type; } /// @@ -42,7 +40,7 @@ public SolutionArgumentException(string? message, Exception? innerException, Sol public SolutionArgumentException(string? message, string? paramName, SolutionErrorType type) : base(message, paramName) { - this.Type = type; + Type = type; } /// @@ -55,7 +53,7 @@ public SolutionArgumentException(string? message, string? paramName, SolutionErr public SolutionArgumentException(string? message, string? paramName, Exception? innerException, SolutionErrorType type) : base(message, paramName, innerException) { - this.Type = type; + Type = type; } /// diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.DimensionDiffTracker.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.DimensionDiffTracker.cs index cb1d71759..1f961bde1 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.DimensionDiffTracker.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.DimensionDiffTracker.cs @@ -15,22 +15,22 @@ private struct DimensionDiffTracker private bool anyDifferent; // There was at least one item that was different than the expected value. - internal readonly bool HasDifferences => this.differences > 0; + internal readonly bool HasDifferences => differences > 0; // All items are different than expected, but they are the same as each other. - internal readonly bool SameDifference => !this.anyDifferent && this.itemsChecked == this.differences && this.itemsChecked > 0; + internal readonly bool SameDifference => !anyDifferent && itemsChecked == differences && itemsChecked > 0; internal void ObserveDifferentValue(T current) { - this.itemsChecked++; - this.differences++; - if (this.differences == 1) + itemsChecked++; + differences++; + if (differences == 1) { - this.firstDifferent = current; + firstDifferent = current; } else { - this.anyDifferent = this.anyDifferent || !EqualityComparer.Default.Equals(this.firstDifferent, current); + anyDifferent = anyDifferent || !EqualityComparer.Default.Equals(firstDifferent, current); } } @@ -38,36 +38,36 @@ internal void ObserveValue(T expected, T current) { if (!EqualityComparer.Default.Equals(expected, current)) { - this.ObserveDifferentValue(current); + ObserveDifferentValue(current); } else { - this.itemsChecked++; + itemsChecked++; } } internal void ClearDifferences() { - this.differences = 0; - this.itemsChecked = 0; - this.anyDifferent = false; - this.firstDifferent = default!; + differences = 0; + itemsChecked = 0; + anyDifferent = false; + firstDifferent = default!; } internal readonly bool TryGetSame(out T sameChanged) { - sameChanged = this.firstDifferent; - return this.SameDifference; + sameChanged = firstDifferent; + return SameDifference; } internal readonly bool TryGetSame(DimensionDiffTracker alternate, out T sameChanged) { - if (this.TryGetSame(out sameChanged)) + if (TryGetSame(out sameChanged)) { return true; } - if (this.HasDifferences) + if (HasDifferences) { return alternate.TryGetSame(out sameChanged); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.ProjectDiffTracker.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.ProjectDiffTracker.cs index 785e4f1fd..9e10ee44c 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.ProjectDiffTracker.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.ProjectDiffTracker.cs @@ -14,9 +14,11 @@ private struct ProjectDiffTracker internal DimensionDiffTracker BuildTracker; internal DimensionDiffTracker DeployTracker; - internal readonly bool HasDifferences => this.BuildTypeTracker.HasDifferences || this.PlatformTracker.HasDifferences || this.BuildTracker.HasDifferences || this.DeployTracker.HasDifferences; + internal readonly bool HasDifferences => BuildTypeTracker.HasDifferences || PlatformTracker.HasDifferences || + BuildTracker.HasDifferences || DeployTracker.HasDifferences; - internal readonly bool HasSame => this.BuildTypeTracker.SameDifference || this.PlatformTracker.SameDifference || this.BuildTracker.SameDifference || this.DeployTracker.SameDifference; + internal readonly bool HasSame => BuildTypeTracker.SameDifference || PlatformTracker.SameDifference || + BuildTracker.SameDifference || DeployTracker.SameDifference; // The ProjectDiffTracker is a struct, so this passes the array to // make sure this actually clears the diffs and a boxed copy. @@ -42,36 +44,41 @@ internal static void ClearDiffs(ProjectDiffTracker[] trackers) internal void ObserveDifferentValue(in ProjectConfigMapping currentMapping) { - this.BuildTypeTracker.ObserveDifferentValue(currentMapping.BuildType); - this.PlatformTracker.ObserveDifferentValue(PlatformNames.Canonical(currentMapping.Platform)); - this.BuildTracker.ObserveDifferentValue(currentMapping.Build); - this.DeployTracker.ObserveDifferentValue(currentMapping.Deploy); + BuildTypeTracker.ObserveDifferentValue(currentMapping.BuildType); + PlatformTracker.ObserveDifferentValue(PlatformNames.Canonical(currentMapping.Platform)); + BuildTracker.ObserveDifferentValue(currentMapping.Build); + DeployTracker.ObserveDifferentValue(currentMapping.Deploy); } internal void ObserveValue(in ProjectConfigMapping expectedMapping, in ProjectConfigMapping currentMapping) { - this.BuildTypeTracker.ObserveValue(expectedMapping.BuildType, currentMapping.BuildType); - this.PlatformTracker.ObserveValue(PlatformNames.Canonical(expectedMapping.Platform), PlatformNames.Canonical(currentMapping.Platform)); - this.BuildTracker.ObserveValue(expectedMapping.Build, currentMapping.Build); - this.DeployTracker.ObserveValue(expectedMapping.Deploy, currentMapping.Deploy); + BuildTypeTracker.ObserveValue(expectedMapping.BuildType, currentMapping.BuildType); + PlatformTracker.ObserveValue(PlatformNames.Canonical(expectedMapping.Platform), + PlatformNames.Canonical(currentMapping.Platform)); + + BuildTracker.ObserveValue(expectedMapping.Build, currentMapping.Build); + DeployTracker.ObserveValue(expectedMapping.Deploy, currentMapping.Deploy); } internal void ClearDiffs() { - this.BuildTypeTracker.ClearDifferences(); - this.PlatformTracker.ClearDifferences(); - this.BuildTracker.ClearDifferences(); - this.DeployTracker.ClearDifferences(); + BuildTypeTracker.ClearDifferences(); + PlatformTracker.ClearDifferences(); + BuildTracker.ClearDifferences(); + DeployTracker.ClearDifferences(); } internal void ClearDiffs(BuildDimension dimension) { switch (dimension) { - case BuildDimension.BuildType: this.BuildTypeTracker.ClearDifferences(); break; - case BuildDimension.Platform: this.PlatformTracker.ClearDifferences(); break; - case BuildDimension.Build: this.BuildTracker.ClearDifferences(); break; - case BuildDimension.Deploy: this.DeployTracker.ClearDifferences(); break; + case BuildDimension.BuildType: BuildTypeTracker.ClearDifferences(); break; + + case BuildDimension.Platform: PlatformTracker.ClearDifferences(); break; + + case BuildDimension.Build: BuildTracker.ClearDifferences(); break; + + case BuildDimension.Deploy: DeployTracker.ClearDifferences(); break; } } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.Rules.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.Rules.cs index f5ef04d28..e6ea56da5 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.Rules.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.Rules.cs @@ -14,15 +14,15 @@ internal sealed partial class SolutionConfigurationMap internal List? CreateProjectRules(SolutionProjectModel projectModel) { // If this project doesn't have any mappings, then we can't create any rules for it. - if (!this.perProjectCurrent.TryGetValue(projectModel, out SolutionToProjectMappings currentMatrix)) + if (!perProjectCurrent.TryGetValue(projectModel, out SolutionToProjectMappings currentMatrix)) { return null; } // What a project would look like with the default configuration rules. - SolutionToProjectMappings expectedMatrix = new SolutionToProjectMappings(this, projectModel, out bool _); + SolutionToProjectMappings expectedMatrix = new(this, projectModel, out bool _); - return this.CreateRules(in expectedMatrix, in currentMatrix); + return CreateRules(in expectedMatrix, in currentMatrix); } /// @@ -34,10 +34,10 @@ internal sealed partial class SolutionConfigurationMap private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in ScopedRules scopedRules) { int iBuildTypeBegin = scopedRules.BuildTypeIndex == ScopedRules.All ? 0 : scopedRules.BuildTypeIndex; - int iBuildTypeEnd = scopedRules.BuildTypeIndex == ScopedRules.All ? this.BuildTypesCount : scopedRules.BuildTypeIndex + 1; + int iBuildTypeEnd = scopedRules.BuildTypeIndex == ScopedRules.All ? BuildTypesCount : scopedRules.BuildTypeIndex + 1; int iPlatformBegin = scopedRules.PlatformIndex == ScopedRules.All ? 0 : scopedRules.PlatformIndex; - int iPlatformEnd = scopedRules.PlatformIndex == ScopedRules.All ? this.PlatformsCount : scopedRules.PlatformIndex + 1; + int iPlatformEnd = scopedRules.PlatformIndex == ScopedRules.All ? PlatformsCount : scopedRules.PlatformIndex + 1; for (int iBuildType = iBuildTypeBegin; iBuildType < iBuildTypeEnd; iBuildType++) { @@ -48,13 +48,17 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in continue; } - SolutionConfigIndex idx = this.ToIndex(iBuildType, iPlatform); + SolutionConfigIndex idx = ToIndex(iBuildType, iPlatform); ProjectConfigMapping mapping = projectMappings[idx]; string solutionBuildType = idx.BuildType(this); string solutionPlatform = idx.Platform(this); - string projectBuildType = scopedRules.Rules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? mapping.BuildType; - string projectPlatform = scopedRules.Rules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? mapping.Platform; + string projectBuildType = scopedRules.Rules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? + mapping.BuildType; + + string projectPlatform = scopedRules.Rules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? + mapping.Platform; + bool build = scopedRules.Rules.GetIsBuildable(solutionBuildType, solutionPlatform) ?? mapping.Build; bool deploy = scopedRules.Rules.GetIsDeployable(solutionBuildType, solutionPlatform) ?? mapping.Deploy; @@ -77,7 +81,7 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in // build type difference // Create rules when mappings are the same for all dimensions in the project. (e.g. Build => false) - bool hasRemainingDiffs = this.CreateProjectGlobalRules( + bool hasRemainingDiffs = CreateProjectGlobalRules( in expectedMatrix, in currentMatrix, out ProjectDiffTracker[] perPlatform, @@ -91,7 +95,7 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in // Create rules when mappings are the same for all build types. (e.g. AnyCPU => arm64) bool addedRules = false; - for (int iPlatform = 0; iPlatform < this.PlatformsCount; iPlatform++) + for (int iPlatform = 0; iPlatform < PlatformsCount; iPlatform++) { // emit all *|plat = xxx|yyy|.... ref ProjectDiffTracker projectDiffTracker = ref perPlatform[iPlatform]; @@ -99,7 +103,9 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in { addedRules = true; - ConfigurationRule[] platformRules = this.CreateDimensionRules(in expectedMatrix, ref projectDiffTracker, ScopedRules.All, iPlatform); + ConfigurationRule[] platformRules = + CreateDimensionRules(in expectedMatrix, ref projectDiffTracker, ScopedRules.All, iPlatform); + allRules ??= []; allRules.AddRange(platformRules); } @@ -109,11 +115,11 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in { ProjectDiffTracker.ClearDiffs(perBuildType); - for (int iBuildType = 0; iBuildType < this.BuildTypesCount; iBuildType++) + for (int iBuildType = 0; iBuildType < BuildTypesCount; iBuildType++) { - for (int iPlatform = 0; iPlatform < this.PlatformsCount; iPlatform++) + for (int iPlatform = 0; iPlatform < PlatformsCount; iPlatform++) { - SolutionConfigIndex index = this.ToIndex(iBuildType, iPlatform); + SolutionConfigIndex index = ToIndex(iBuildType, iPlatform); ProjectConfigMapping expectedMapping = expectedMatrix[index]; ProjectConfigMapping currentMapping = currentMatrix[index]; perBuildType[iBuildType].ObserveValue(in expectedMapping, in currentMapping); @@ -123,13 +129,15 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in // Create rules when mappings are the same for all platforms. bool hasSingleChanges = false; - for (int iBuildType = 0; iBuildType < this.BuildTypesCount; iBuildType++) + for (int iBuildType = 0; iBuildType < BuildTypesCount; iBuildType++) { // emit all cfg|* = xxx|yyy|.... ref ProjectDiffTracker projectDiffTracker = ref perBuildType[iBuildType]; if (projectDiffTracker.HasSame) { - ConfigurationRule[] buildTypeRules = this.CreateDimensionRules(in expectedMatrix, ref projectDiffTracker, iBuildType, ScopedRules.All); + ConfigurationRule[] buildTypeRules = + CreateDimensionRules(in expectedMatrix, ref projectDiffTracker, iBuildType, ScopedRules.All); + allRules ??= []; allRules.AddRange(buildTypeRules); } @@ -145,11 +153,11 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in // collapse to "*|foo = *|majority" + "specifig|foo = specific|different" instead of expand all. // OPINION: This may add complexity and remove the predictability of the rules, we would then // need to keep track of rules instead of being able to always regenerate them. - for (int iBuildType = 0; iBuildType < this.BuildTypesCount; iBuildType++) + for (int iBuildType = 0; iBuildType < BuildTypesCount; iBuildType++) { - for (int iPlatform = 0; iPlatform < this.PlatformsCount; iPlatform++) + for (int iPlatform = 0; iPlatform < PlatformsCount; iPlatform++) { - SolutionConfigIndex index = this.ToIndex(iBuildType, iPlatform); + SolutionConfigIndex index = ToIndex(iBuildType, iPlatform); ProjectConfigMapping expectedMapping = expectedMatrix[index]; ProjectConfigMapping currentMapping = currentMatrix[index]; if (expectedMapping.IsSame(in currentMapping)) @@ -157,29 +165,34 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in continue; } - ListBuilderStruct newRules = new ListBuilderStruct(); + ListBuilderStruct newRules = new(); string solutionBuildType = index.BuildType(this); string solutionPlatform = index.Platform(this); if (!StringComparer.Ordinal.Equals(currentMapping.BuildType, expectedMapping.BuildType)) { - newRules.Add(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, currentMapping.BuildType)); + newRules.Add(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, + currentMapping.BuildType)); } - if (!StringComparer.Ordinal.Equals(PlatformNames.Canonical(currentMapping.Platform), PlatformNames.Canonical(expectedMapping.Platform))) + if (!StringComparer.Ordinal.Equals(PlatformNames.Canonical(currentMapping.Platform), + PlatformNames.Canonical(expectedMapping.Platform))) { - newRules.Add(new ConfigurationRule(BuildDimension.Platform, solutionBuildType, solutionPlatform, currentMapping.Platform)); + newRules.Add(new ConfigurationRule(BuildDimension.Platform, solutionBuildType, solutionPlatform, + currentMapping.Platform)); } if (currentMapping.Build != expectedMapping.Build) { - newRules.Add(new ConfigurationRule(BuildDimension.Build, solutionBuildType, solutionPlatform, currentMapping.Build.ToString())); + newRules.Add(new ConfigurationRule(BuildDimension.Build, solutionBuildType, solutionPlatform, + currentMapping.Build.ToString())); } if (currentMapping.Deploy != expectedMapping.Deploy) { - newRules.Add(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, solutionPlatform, currentMapping.Deploy.ToString())); + newRules.Add(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, solutionPlatform, + currentMapping.Deploy.ToString())); } if (newRules.Count != 0) @@ -191,7 +204,7 @@ private void ApplyRules(in SolutionToProjectMappings projectMappings, scoped in } // no need to update expected, but it is easier for debuging purposes. - this.ApplyRules(in expectedMatrix, new ScopedRules(iBuildType, iPlatform, newRules.ToArray())); + ApplyRules(in expectedMatrix, new ScopedRules(iBuildType, iPlatform, newRules.ToArray())); } } } @@ -209,21 +222,21 @@ private bool CreateProjectGlobalRules( out List? rules) { rules = null; - perPlatform = new ProjectDiffTracker[this.PlatformsCount]; - perBuildType = new ProjectDiffTracker[this.BuildTypesCount]; + perPlatform = new ProjectDiffTracker[PlatformsCount]; + perBuildType = new ProjectDiffTracker[BuildTypesCount]; // Looks for any mappings that always differer the same way in this project. - ProjectDiffTracker global = new ProjectDiffTracker(); + ProjectDiffTracker global = new(); // Looks for any mappings that are always the same in this project. - ProjectDiffTracker unique = new ProjectDiffTracker(); + ProjectDiffTracker unique = new(); // Try to create a rule that applies to all build types and platforms. - for (int iBuildType = 0; iBuildType < this.BuildTypesCount; iBuildType++) + for (int iBuildType = 0; iBuildType < BuildTypesCount; iBuildType++) { - for (int iPlatform = 0; iPlatform < this.PlatformsCount; iPlatform++) + for (int iPlatform = 0; iPlatform < PlatformsCount; iPlatform++) { - SolutionConfigIndex index = this.ToIndex(iBuildType, iPlatform); + SolutionConfigIndex index = ToIndex(iBuildType, iPlatform); ProjectConfigMapping expectedMapping = expectedMatrix[index]; ProjectConfigMapping currentMapping = currentMatrix[index]; perPlatform[iPlatform].ObserveValue(in expectedMapping, in currentMapping); @@ -275,7 +288,7 @@ private bool CreateProjectGlobalRules( if (!rules.IsNullOrEmpty()) { - this.ApplyRules(in expectedMatrix, new ScopedRules(ScopedRules.All, ScopedRules.All, rules)); + ApplyRules(in expectedMatrix, new ScopedRules(ScopedRules.All, ScopedRules.All, rules)); } // easy case = all is the same as expected; @@ -289,22 +302,26 @@ private ConfigurationRule[] CreateDimensionRules( int iBuildType, int iPlatform) { - string solutionBuildType = iBuildType == ScopedRules.All ? string.Empty : this.solutionModel.BuildTypes[iBuildType]; - string solutionPlatform = iPlatform == ScopedRules.All ? string.Empty : this.solutionModel.Platforms[iPlatform]; + string solutionBuildType = iBuildType == ScopedRules.All ? string.Empty : solutionModel.BuildTypes[iBuildType]; + string solutionPlatform = iPlatform == ScopedRules.All ? string.Empty : solutionModel.Platforms[iPlatform]; - ListBuilderStruct rulesBuilder = new ListBuilderStruct(); + ListBuilderStruct rulesBuilder = new(); // Create Build rule. if (projectDiffTracker.BuildTracker.TryGetSame(out bool buildable)) { - rulesBuilder.Add(new ConfigurationRule(BuildDimension.Build, solutionBuildType, solutionPlatform, buildable.ToString())); + rulesBuilder.Add(new ConfigurationRule(BuildDimension.Build, solutionBuildType, solutionPlatform, + buildable.ToString())); + projectDiffTracker.BuildTracker.ClearDifferences(); } // Create Deploy rule. if (projectDiffTracker.DeployTracker.TryGetSame(out bool deployable)) { - rulesBuilder.Add(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, solutionPlatform, deployable.ToString())); + rulesBuilder.Add(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, solutionPlatform, + deployable.ToString())); + projectDiffTracker.DeployTracker.ClearDifferences(); } @@ -325,7 +342,7 @@ private ConfigurationRule[] CreateDimensionRules( ConfigurationRule[] rules = rulesBuilder.ToArray(); if (rules.Length != 0) { - this.ApplyRules(in expectedMatrix, new ScopedRules(iBuildType, iPlatform, rules)); + ApplyRules(in expectedMatrix, new ScopedRules(iBuildType, iPlatform, rules)); } return rules; @@ -340,6 +357,6 @@ private readonly ref struct ScopedRules(int buildTypeIndex, int platformIndex, I internal readonly int BuildTypeIndex = buildTypeIndex; internal readonly int PlatformIndex = platformIndex; - internal readonly ConfigurationRuleFollower Rules = new ConfigurationRuleFollower(rules); + internal readonly ConfigurationRuleFollower Rules = new(rules); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.cs index ff79dd8fc..f372f606a 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionConfigurationMap.cs @@ -19,33 +19,37 @@ internal sealed partial class SolutionConfigurationMap internal SolutionConfigurationMap(SolutionModel solutionModel) { this.solutionModel = solutionModel; - this.buildTypesIndex = new Dictionary(solutionModel.BuildTypes.Count); + buildTypesIndex = new Dictionary(solutionModel.BuildTypes.Count); for (int i = 0; i < solutionModel.BuildTypes.Count; i++) { - this.buildTypesIndex.Add(solutionModel.BuildTypes[i], i); + buildTypesIndex.Add(solutionModel.BuildTypes[i], i); } - this.platformsIndex = new Dictionary(solutionModel.Platforms.Count); + platformsIndex = new Dictionary(solutionModel.Platforms.Count); for (int i = 0; i < solutionModel.Platforms.Count; i++) { - this.platformsIndex.Add(PlatformNames.Canonical(solutionModel.Platforms[i]), i); + platformsIndex.Add(PlatformNames.Canonical(solutionModel.Platforms[i]), i); } - this.matrixSize = this.BuildTypesCount * this.PlatformsCount; + matrixSize = BuildTypesCount * PlatformsCount; } - internal int BuildTypesCount => this.buildTypesIndex.Count; + internal int BuildTypesCount => buildTypesIndex.Count; - internal int PlatformsCount => this.platformsIndex.Count; + internal int PlatformsCount => platformsIndex.Count; internal int GetBuildTypeIndex(string buildType) { - return !string.IsNullOrEmpty(buildType) && this.buildTypesIndex.TryGetValue(buildType, out int index) ? index : ScopedRules.All; + return !string.IsNullOrEmpty(buildType) && buildTypesIndex.TryGetValue(buildType, out int index) + ? index + : ScopedRules.All; } internal int GetPlatformIndex(string platform) { - return !string.IsNullOrEmpty(platform) && this.platformsIndex.TryGetValue(PlatformNames.Canonical(platform), out int index) ? index : ScopedRules.All; + return !string.IsNullOrEmpty(platform) && platformsIndex.TryGetValue(PlatformNames.Canonical(platform), out int index) + ? index + : ScopedRules.All; } /// @@ -61,8 +65,8 @@ internal void GetProjectConfigMap( foreach (ConfigurationRule rule in projectModel.ProjectConfigurationRules.GetStructEnumerable()) { - int buildTypeIndex = this.GetBuildTypeIndex(rule.SolutionBuildType); - int platformIndex = this.GetPlatformIndex(rule.SolutionPlatform); + int buildTypeIndex = GetBuildTypeIndex(rule.SolutionBuildType); + int platformIndex = GetPlatformIndex(rule.SolutionPlatform); if ((!string.IsNullOrEmpty(rule.SolutionBuildType) && buildTypeIndex < 0) || (!string.IsNullOrEmpty(rule.SolutionPlatform) && platformIndex < 0)) @@ -70,7 +74,7 @@ internal void GetProjectConfigMap( continue; } - this.ApplyRules(in projectMappings, new ScopedRules(buildTypeIndex, platformIndex, [rule])); + ApplyRules(in projectMappings, new ScopedRules(buildTypeIndex, platformIndex, [rule])); } } @@ -80,16 +84,16 @@ internal void GetProjectConfigMap( /// internal (string SlnKey, SolutionConfigIndex Index)[] CreateMatrixAnnotation() { - (string SlnKey, SolutionConfigIndex Index)[] ret = new (string SlnKey, SolutionConfigIndex Index)[this.matrixSize]; - for (int buildTypeIndex = 0; buildTypeIndex < this.solutionModel.BuildTypes.Count; buildTypeIndex++) + (string SlnKey, SolutionConfigIndex Index)[] ret = new (string SlnKey, SolutionConfigIndex Index)[matrixSize]; + for (int buildTypeIndex = 0; buildTypeIndex < solutionModel.BuildTypes.Count; buildTypeIndex++) { - string buildType = this.solutionModel.BuildTypes[buildTypeIndex]; + string buildType = solutionModel.BuildTypes[buildTypeIndex]; - for (int platformIndex = 0; platformIndex < this.solutionModel.Platforms.Count; platformIndex++) + for (int platformIndex = 0; platformIndex < solutionModel.Platforms.Count; platformIndex++) { - string platform = this.solutionModel.Platforms[platformIndex]; + string platform = solutionModel.Platforms[platformIndex]; - SolutionConfigIndex idx = new SolutionConfigIndex(this, buildTypeIndex, platformIndex); + SolutionConfigIndex idx = new(this, buildTypeIndex, platformIndex); ret[idx.MatrixIndex] = ($"{buildType}|{platform}", idx); } } @@ -103,44 +107,44 @@ internal void GetProjectConfigMap( /// internal void DistillProjectConfigurations() { - foreach (SolutionProjectModel projectModel in this.solutionModel.SolutionProjects) + foreach (SolutionProjectModel projectModel in solutionModel.SolutionProjects) { // Cache list of all project configurations for all solution configuration mappings. - this.GetProjectConfigMap(projectModel, out SolutionToProjectMappings mappings, out bool supportsConfigs); + GetProjectConfigMap(projectModel, out SolutionToProjectMappings mappings, out bool supportsConfigs); if (supportsConfigs) { - this.perProjectCurrent.Add(projectModel, mappings); + perProjectCurrent.Add(projectModel, mappings); } // Converts cached mappings into simpler rules. - projectModel.ProjectConfigurationRules = this.CreateProjectRules(projectModel); + projectModel.ProjectConfigurationRules = CreateProjectRules(projectModel); } } - private SolutionConfigIndex ToIndex(int iBuildType, int iPlatform) => new SolutionConfigIndex(this, iBuildType, iPlatform); + private SolutionConfigIndex ToIndex(int iBuildType, int iPlatform) => new(this, iBuildType, iPlatform); // This should only be called from ConfigIndex private string BuildTypeFromIndex(SolutionConfigIndex index) { - if (index.MatrixIndex < 0 || index.MatrixIndex >= this.matrixSize) + if (index.MatrixIndex < 0 || index.MatrixIndex >= matrixSize) { throw new ArgumentOutOfRangeException(nameof(index)); } - int config = index.MatrixIndex / this.PlatformsCount; - return this.solutionModel.BuildTypes[config]; + int config = index.MatrixIndex / PlatformsCount; + return solutionModel.BuildTypes[config]; } // This should only be called from ConfigIndex private string PlatformFromIndex(SolutionConfigIndex index) { - if (index.MatrixIndex < 0 || index.MatrixIndex >= this.matrixSize) + if (index.MatrixIndex < 0 || index.MatrixIndex >= matrixSize) { throw new ArgumentOutOfRangeException(nameof(index)); } - int plat = index.MatrixIndex % this.PlatformsCount; - return this.solutionModel.Platforms[plat]; + int plat = index.MatrixIndex % PlatformsCount; + return solutionModel.Platforms[plat]; } /// @@ -164,9 +168,12 @@ internal SolutionToProjectMappings( #if DEBUG this.projectModel = projectModel; #endif - this.mappings = new ProjectConfigMapping[configMap.matrixSize]; + mappings = new ProjectConfigMapping[configMap.matrixSize]; + + ConfigurationRuleFollower projectTypeRules = + configMap.solutionModel.ProjectTypeTable.GetProjectConfigurationRules(projectModel, + excludeProjectSpecificRules: true); - ConfigurationRuleFollower projectTypeRules = configMap.solutionModel.ProjectTypeTable.GetProjectConfigurationRules(projectModel, excludeProjectSpecificRules: true); isConfigurable = projectTypeRules.GetIsBuildable() ?? true; for (int iPlatform = 0; iPlatform < configMap.PlatformsCount; iPlatform++) @@ -179,26 +186,30 @@ internal SolutionToProjectMappings( bool build = projectTypeRules.GetIsBuildable(solutionBuildType, solutionPlatform) ?? true; bool deploy = projectTypeRules.GetIsDeployable(solutionBuildType, solutionPlatform) ?? false; - string projectBuildType = projectTypeRules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? solutionBuildType; - string projectPlatform = projectTypeRules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? solutionPlatform; + string projectBuildType = projectTypeRules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? + solutionBuildType; + + string projectPlatform = projectTypeRules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? + solutionPlatform; this[configMap.ToIndex(iBuildType, iPlatform)] = - new ProjectConfigMapping(projectBuildType, projectPlatform, !forceExclude && build, !forceExclude && deploy); + new ProjectConfigMapping(projectBuildType, projectPlatform, !forceExclude && build, + !forceExclude && deploy); } } } internal ProjectConfigMapping this[SolutionConfigIndex index] { - get => this.mappings[index.MatrixIndex]; - set => this.mappings[index.MatrixIndex] = value; + get => mappings[index.MatrixIndex]; + set => mappings[index.MatrixIndex] = value; } #if DEBUG /// public override string ToString() { - return this.projectModel.DisplayName ?? string.Empty; + return projectModel.DisplayName ?? string.Empty; } #endif } @@ -208,9 +219,7 @@ public override string ToString() /// internal readonly struct SolutionConfigIndex { - private readonly int index; - - public SolutionConfigIndex() => this.index = -1; + public SolutionConfigIndex() => MatrixIndex = -1; internal SolutionConfigIndex(SolutionConfigurationMap map, string buildType, string platform) : this(map, map.GetBuildTypeIndex(buildType), map.GetPlatformIndex(platform)) @@ -220,10 +229,10 @@ internal SolutionConfigIndex(SolutionConfigurationMap map, string buildType, str internal SolutionConfigIndex(SolutionConfigurationMap map, int buildType, int platForm) { bool unknown = buildType < 0 || buildType >= map.BuildTypesCount || platForm < 0 || platForm >= map.PlatformsCount; - this.index = unknown ? -1 : (buildType * map.PlatformsCount) + platForm; + MatrixIndex = unknown ? -1 : buildType * map.PlatformsCount + platForm; } - internal int MatrixIndex => this.index; + internal int MatrixIndex { get; } internal string BuildType(SolutionConfigurationMap map) => map.BuildTypeFromIndex(this); diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionException.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionException.cs index 4d7c06958..19c0d6970 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionException.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionException.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Runtime.Serialization; using System.Xml; using Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; @@ -26,7 +27,7 @@ public SolutionException() public SolutionException(string message) : base(message) { - this.ErrorType = SolutionErrorType.Undefined; + ErrorType = SolutionErrorType.Undefined; } /// @@ -37,7 +38,7 @@ public SolutionException(string message) public SolutionException(string message, Exception inner) : base(message, inner) { - this.ErrorType = SolutionErrorType.Undefined; + ErrorType = SolutionErrorType.Undefined; } /// @@ -48,7 +49,7 @@ public SolutionException(string message, Exception inner) public SolutionException(string message, SolutionErrorType errorType) : base(message) { - this.ErrorType = errorType; + ErrorType = errorType; } /// @@ -60,7 +61,7 @@ public SolutionException(string message, SolutionErrorType errorType) public SolutionException(string message, Exception inner, SolutionErrorType errorType) : base(message, inner) { - this.ErrorType = errorType; + ErrorType = errorType; } #if NETFRAMEWORK || NETSTANDARD @@ -70,15 +71,16 @@ public SolutionException(string message, Exception inner, SolutionErrorType erro /// /// Serialization info. /// Contextual info. - [SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Only in .NET Framework.")] - protected SolutionException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + [SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", + Justification = "Only in .NET Framework.")] + protected SolutionException(SerializationInfo info, StreamingContext context) : base(info, context) { - this.File = info.GetString("File"); + File = info.GetString("File"); int line = info.GetInt32("Line"); int column = info.GetInt32("Column"); - this.Line = line < 0 ? null : line; - this.Column = column < 0 ? null : column; + Line = line < 0 ? null : line; + Column = column < 0 ? null : column; } #endif @@ -104,29 +106,48 @@ protected SolutionException(System.Runtime.Serialization.SerializationInfo info, #if NETFRAMEWORK || NETSTANDARD /// - [SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Only in .NET Framework.")] - public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + [SuppressMessage("ApiDesign", "RS0016:Add public types and members to the declared API", + Justification = "Only in .NET Framework.")] + public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); - info.AddValue("File", this.File); - info.AddValue("Line", this.Line ?? -1); - info.AddValue("Column", this.Column ?? -1); + info.AddValue("File", File); + info.AddValue("Line", Line ?? -1); + info.AddValue("Column", Column ?? -1); } #endif - internal static SolutionException Create(string message, XmlDecorator location, SolutionErrorType errorType = SolutionErrorType.Undefined) + internal static SolutionException Create(string message, XmlDecorator location, + SolutionErrorType errorType = SolutionErrorType.Undefined) { - return location?.XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo() ? - new SolutionException(message, errorType) { Line = lineInfo.LineNumber, Column = lineInfo.LinePosition, File = location.Root.FullPath } : - new SolutionException(message, errorType) { File = location?.Root.FullPath }; + return location?.XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo() + ? new SolutionException(message, errorType) + { + Line = lineInfo.LineNumber, + Column = lineInfo.LinePosition, + File = location.Root.FullPath + } + : new SolutionException(message, errorType) + { + File = location?.Root.FullPath + }; } - internal static SolutionException Create(Exception innerException, XmlDecorator location, string? message = null, SolutionErrorType errorType = SolutionErrorType.Undefined) + internal static SolutionException Create(Exception innerException, XmlDecorator location, string? message = null, + SolutionErrorType errorType = SolutionErrorType.Undefined) { message ??= innerException.Message; - return location?.XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo() ? - new SolutionException(message, innerException, errorType) { Line = lineInfo.LineNumber, Column = lineInfo.LinePosition, File = location.Root.FullPath } : - new SolutionException(message, innerException, errorType) { File = location?.Root.FullPath }; + return location?.XmlElement is IXmlLineInfo lineInfo && lineInfo.HasLineInfo() + ? new SolutionException(message, innerException, errorType) + { + Line = lineInfo.LineNumber, + Column = lineInfo.LinePosition, + File = location.Root.FullPath + } + : new SolutionException(message, innerException, errorType) + { + File = location?.Root.FullPath + }; } // Checks if an exception caught during serialization should be wrapped by a SolutionException to add position information. diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionFolderModel.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionFolderModel.cs index 09c899e75..15da7d73a 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionFolderModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionFolderModel.cs @@ -31,50 +31,51 @@ internal SolutionFolderModel(SolutionModel solutionModel, string name, SolutionF internal SolutionFolderModel(SolutionModel solutionModel, SolutionFolderModel folderModel) : base(solutionModel, folderModel.BeSolutionItemModel) { - this.name = folderModel.name; + name = folderModel.name; if (folderModel.Files is not null) { - this.files = [.. folderModel.Files]; + files = [.. folderModel.Files]; } } /// /// Gets the files in this solution folder. /// - public IReadOnlyList? Files => this.files; + public IReadOnlyList? Files => files; /// /// Gets or sets the name of the solution folder. /// public string Name { - get => this.name; + get => name; set { Argument.ThrowIfNullOrEmpty(value, nameof(value)); SolutionModel.ValidateName(value.AsSpan()); - if (this.name == value) + if (name == value) { return; } - string testName = $"{this.Parent?.ItemRef ?? "/"}{value}/"; - if (this.Solution.FindFolder(testName) is not null) + string testName = $"{Parent?.ItemRef ?? "/"}{value}/"; + if (Solution.FindFolder(testName) is not null) { - throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, testName, "Folder"), nameof(value), SolutionErrorType.DuplicateItemRef); + throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, testName, "Folder"), + nameof(value), SolutionErrorType.DuplicateItemRef); } - string oldName = this.name; + string oldName = name; try { - this.name = value; - this.OnItemRefChanged(); + name = value; + OnItemRefChanged(); } catch (Exception) { // On error revert the name. - this.name = oldName; + name = oldName; throw; } } @@ -83,10 +84,10 @@ public string Name /// /// Gets a unique reference to this folder in the solution. /// - public string Path => this.ItemRef; + public string Path => ItemRef; /// - public override string ActualDisplayName => this.Name; + public override string ActualDisplayName => Name; /// public override Guid TypeId => ProjectTypeTable.SolutionFolder; @@ -96,26 +97,26 @@ internal override string ItemRef { get { - if (this.itemRef is not null) + if (itemRef is not null) { - return this.itemRef; + return itemRef; } - if (this.Parent is not null) + if (Parent is not null) { - this.itemRef = CycleBreaker; - string parentRef = this.Parent.ItemRef; - if (!object.ReferenceEquals(parentRef, CycleBreaker)) + itemRef = CycleBreaker; + string parentRef = Parent.ItemRef; + if (!ReferenceEquals(parentRef, CycleBreaker)) { - this.itemRef = $"{parentRef}{this.Name}/"; - return this.itemRef; + itemRef = $"{parentRef}{Name}/"; + return itemRef; } } // no parent, or part of cycle move it on top. // potential duplicates in this case will be ignored/merged on save. - this.itemRef = $"/{this.Name}/"; - return this.itemRef; + itemRef = $"/{Name}/"; + return itemRef; } } @@ -125,11 +126,11 @@ internal override string ItemRef /// The file to add. public void AddFile(string file) { - this.files ??= []; + files ??= []; - if (!this.files.Contains(file)) + if (!files.Contains(file)) { - this.files.Add(file); + files.Add(file); } } @@ -140,16 +141,16 @@ public void AddFile(string file) /// if the item was found and removed. public bool RemoveFile(string file) { - return this.files is not null && this.files.Remove(file); + return files is not null && files.Remove(file); } internal override void OnItemRefChanged() { base.OnItemRefChanged(); - this.itemRef = null; + itemRef = null; // Recursively update all children. - foreach (SolutionItemModel item in this.Solution.SolutionItems) + foreach (SolutionItemModel item in Solution.SolutionItems) { if (ReferenceEquals(item.Parent, this)) { @@ -160,13 +161,13 @@ internal override void OnItemRefChanged() private protected override Guid GetDefaultId() { - Guid parentId = this.Parent is null ? Guid.Empty : this.Parent.Id; - return DefaultIdGenerator.CreateIdFrom(parentId, this.Name); + Guid parentId = Parent is null ? Guid.Empty : Parent.Id; + return DefaultIdGenerator.CreateIdFrom(parentId, Name); } private protected override void OnParentChanged() { base.OnParentChanged(); - this.OnItemRefChanged(); + OnItemRefChanged(); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionItemModel.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionItemModel.cs index d8ae245b3..3bc399557 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionItemModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionItemModel.cs @@ -14,8 +14,8 @@ public abstract class SolutionItemModel : PropertyContainerModel private protected SolutionItemModel(SolutionModel solutionModel, SolutionFolderModel? parent) { Argument.ThrowIfNull(solutionModel, nameof(solutionModel)); - this.Solution = solutionModel; - this.Parent = parent; + Solution = solutionModel; + Parent = parent; } /// @@ -27,13 +27,13 @@ private protected SolutionItemModel(SolutionModel solutionModel, SolutionFolderM private protected SolutionItemModel(SolutionModel solutionModel, SolutionItemModel itemModel) : base(itemModel) { - this.Solution = solutionModel; - this.id = itemModel.id; - this.defaultId = itemModel.defaultId; + Solution = solutionModel; + id = itemModel.id; + defaultId = itemModel.defaultId; // This is a shallow copy of the parent, it needs to be swapped out to finish the deep copy. // But we can't find the new parent until all copy constructors have been called. - this.Parent = itemModel.Parent; + Parent = itemModel.Parent; } /// @@ -54,20 +54,21 @@ private protected SolutionItemModel(SolutionModel solutionModel, SolutionItemMod /// public Guid Id { - get => this.id ?? this.DefaultId; + get => id ?? DefaultId; set { - if (value != (this.id ?? this.defaultId)) + if (value != (id ?? defaultId)) { - if (this.Solution.FindItemById(value) is not null) + if (Solution.FindItemById(value) is not null) { - throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, value, this.GetType().Name), nameof(value), SolutionErrorType.DuplicateItemRef); + throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, value, GetType().Name), + nameof(value), SolutionErrorType.DuplicateItemRef); } - Guid? oldId = this.id ?? this.defaultId; - this.id = value == this.DefaultId ? null : value.NullIfEmpty(); - this.Solution.OnUpdateId(this, oldId); + Guid? oldId = id ?? defaultId; + id = value == DefaultId ? null : value.NullIfEmpty(); + Solution.OnUpdateId(this, oldId); } } } @@ -75,7 +76,7 @@ public Guid Id /// /// Gets a value indicating whether the Id is the default Id generated from the ItemRef. /// - public bool IsDefaultId => this.id is null; + public bool IsDefaultId => id is null; /// /// Gets the display name of the item. If there is a filename it will be used, otherwise the actual display name. @@ -95,7 +96,7 @@ public Guid Id /// internal abstract string ItemRef { get; } - private Guid DefaultId => this.defaultId ??= this.GetDefaultId(); + private Guid DefaultId => defaultId ??= GetDefaultId(); /// /// Moves the item to a new folder. @@ -103,8 +104,8 @@ public Guid Id /// The folder to move to. public void MoveToFolder(SolutionFolderModel? folder) { - this.Solution.ValidateInModel(folder); - if (ReferenceEquals(this.Parent, folder)) + Solution.ValidateInModel(folder); + if (ReferenceEquals(Parent, folder)) { return; } @@ -114,42 +115,43 @@ public void MoveToFolder(SolutionFolderModel? folder) { if (ReferenceEquals(parents, this)) { - throw new SolutionArgumentException(Errors.CannotMoveFolderToChildFolder, nameof(folder), SolutionErrorType.CannotMoveFolderToChildFolder); + throw new SolutionArgumentException(Errors.CannotMoveFolderToChildFolder, nameof(folder), + SolutionErrorType.CannotMoveFolderToChildFolder); } } - SolutionFolderModel? oldParent = this.Parent; + SolutionFolderModel? oldParent = Parent; try { - this.Parent = folder; + Parent = folder; // Reevaulate the id. - if (this.id == this.DefaultId) + if (id == DefaultId) { - this.id = null; + id = null; } if (this is SolutionProjectModel thisProject) { - this.Solution.ValidateProjectName(thisProject); + Solution.ValidateProjectName(thisProject); } } catch { // Revert the change if it fails validation. - this.Parent = oldParent; + Parent = oldParent; throw; } - this.OnParentChanged(); + OnParentChanged(); } internal virtual void OnItemRefChanged() { - this.defaultId = null; - if (this.id is null) + defaultId = null; + if (id is null) { - this.Id = Guid.Empty; + Id = Guid.Empty; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionModel.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionModel.cs index 5f8855229..a2262e591 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Linq; +using Fallout.Persistence.Solution.Serializer.SlnV12; using Fallout.Persistence.Solution.Serializer.Xml; namespace Fallout.Persistence.Solution.Model; @@ -36,15 +37,15 @@ public sealed class SolutionModel : PropertyContainerModel /// public SolutionModel() { - this.visualStudioProperties = new VisualStudioProperties(this); - this.StringTable = new StringTable().WithSolutionConstants(); - this.solutionItemsById = []; - this.solutionItems = []; - this.solutionProjects = []; - this.solutionFolders = []; - this.solutionBuildTypes = []; - this.solutionPlatforms = []; - this.projectTypes = []; + visualStudioProperties = new VisualStudioProperties(this); + StringTable = new StringTable().WithSolutionConstants(); + solutionItemsById = []; + solutionItems = []; + solutionProjects = []; + solutionFolders = []; + solutionBuildTypes = []; + solutionPlatforms = []; + projectTypes = []; } /// @@ -55,14 +56,14 @@ public SolutionModel() public SolutionModel(SolutionModel solutionModel) : base(solutionModel ?? throw new ArgumentNullException(nameof(solutionModel))) { - this.visualStudioProperties = new VisualStudioProperties(this); - this.StringTable = solutionModel.StringTable; + visualStudioProperties = new VisualStudioProperties(this); + StringTable = solutionModel.StringTable; int itemCount = solutionModel.solutionItems.Count; int folderCount = solutionModel.solutionItems.Count(x => x is SolutionFolderModel); - this.solutionItems = new List(itemCount); - this.solutionItemsById = new Dictionary(itemCount); - this.solutionFolders = new List(folderCount); - this.solutionProjects = new List(itemCount - folderCount); + solutionItems = new List(itemCount); + solutionItemsById = new Dictionary(itemCount); + solutionFolders = new List(folderCount); + solutionProjects = new List(itemCount - folderCount); foreach (SolutionItemModel item in solutionModel.solutionItems) { SolutionItemModel newItem = item switch @@ -72,25 +73,25 @@ public SolutionModel(SolutionModel solutionModel) _ => throw new InvalidOperationException(), }; - this.solutionItems.Add(newItem); - this.solutionFolders.AddIfNotNull(newItem as SolutionFolderModel); - this.solutionProjects.AddIfNotNull(newItem as SolutionProjectModel); - this.solutionItemsById[newItem.Id] = newItem; + solutionItems.Add(newItem); + solutionFolders.AddIfNotNull(newItem as SolutionFolderModel); + solutionProjects.AddIfNotNull(newItem as SolutionProjectModel); + solutionItemsById[newItem.Id] = newItem; } // Replace the shallow-parent models with the new folders. - foreach (SolutionItemModel item in this.solutionItems) + foreach (SolutionItemModel item in solutionItems) { if (item.Parent is not null) { - item.MoveToFolder(this.FindFolder(item.Parent.ItemRef) ?? throw new InvalidOperationException()); + item.MoveToFolder(FindFolder(item.Parent.ItemRef) ?? throw new InvalidOperationException()); } } - this.Description = solutionModel.Description; - this.solutionBuildTypes = [.. solutionModel.solutionBuildTypes]; - this.solutionPlatforms = [.. solutionModel.solutionPlatforms]; - this.projectTypes = [.. solutionModel.projectTypes]; + Description = solutionModel.Description; + solutionBuildTypes = [.. solutionModel.solutionBuildTypes]; + solutionPlatforms = [.. solutionModel.solutionPlatforms]; + projectTypes = [.. solutionModel.projectTypes]; } /// @@ -115,27 +116,27 @@ public SolutionModel(SolutionModel solutionModel) /// Gets the list of solution items in the solution. /// This is all of the solution folders and projects in the solution. /// - public IReadOnlyList SolutionItems => this.solutionItems; + public IReadOnlyList SolutionItems => solutionItems; /// /// Gets the list of projects in the solution. /// - public IReadOnlyList SolutionProjects => this.solutionProjects; + public IReadOnlyList SolutionProjects => solutionProjects; /// /// Gets the list of solution folders in the solution. /// - public IReadOnlyList SolutionFolders => this.solutionFolders; + public IReadOnlyList SolutionFolders => solutionFolders; /// /// Gets the list of build types in the solution. (e.g Debug/Release). /// - public IReadOnlyList BuildTypes => this.solutionBuildTypes; + public IReadOnlyList BuildTypes => solutionBuildTypes; /// /// Gets the list of platforms in the solution. (e.g. x64/Any CPU). /// - public IReadOnlyList Platforms => this.solutionPlatforms; + public IReadOnlyList Platforms => solutionPlatforms; /// /// Gets or sets the list of project types in the solution. @@ -147,12 +148,12 @@ public SolutionModel(SolutionModel solutionModel) /// public IReadOnlyList ProjectTypes { - get => this.projectTypes; + get => projectTypes; set { - this.projectTypes.Clear(); - this.projectTypes.AddRange(value); - this.projectTypeTable = null; + projectTypes.Clear(); + projectTypes.AddRange(value); + projectTypeTable = null; } } @@ -160,9 +161,9 @@ public IReadOnlyList ProjectTypes /// Gets a helper to get and set Visual Studio specific properties. /// /// A helper to get and set Visual Studio properties. - public ref readonly VisualStudioProperties VisualStudioProperties => ref this.visualStudioProperties; + public ref readonly VisualStudioProperties VisualStudioProperties => ref visualStudioProperties; - internal ProjectTypeTable ProjectTypeTable => this.projectTypeTable ??= new ProjectTypeTable(this.projectTypes); + internal ProjectTypeTable ProjectTypeTable => projectTypeTable ??= new ProjectTypeTable(projectTypes); /// /// Gets or adds a solution folder to the solution. @@ -177,10 +178,11 @@ public SolutionFolderModel AddFolder(string path) Argument.ThrowIfNullOrEmpty(path, nameof(path)); if (!path.StartsWith('/') || !path.EndsWith('/')) { - throw new SolutionArgumentException(string.Format(Errors.InvalidFolderPath_Args1, path), nameof(path), SolutionErrorType.InvalidFolderPath); + throw new SolutionArgumentException(string.Format(Errors.InvalidFolderPath_Args1, path), nameof(path), + SolutionErrorType.InvalidFolderPath); } - SolutionFolderModel? existingFolder = this.FindFolder(path); + SolutionFolderModel? existingFolder = FindFolder(path); if (existingFolder is not null) { return existingFolder; @@ -193,10 +195,10 @@ public SolutionFolderModel AddFolder(string path) string? parentItemRef = lastSlash > 0 ? folderPath.Slice(0, lastSlash + 1).ToString() : null; StringSpan newName = lastSlash > 0 ? folderPath.Slice(lastSlash + 1) : folderPath.Slice(1); - SolutionFolderModel folder = this.AddFolder(newName, parentItemRef); + SolutionFolderModel folder = AddFolder(newName, parentItemRef); // Ensure the project type is in the project type table, if it is not already. - this.solutionItemsById[folder.Id] = folder; + solutionItemsById[folder.Id] = folder; return folder; } @@ -213,14 +215,16 @@ public SolutionFolderModel AddFolder(string path) public SolutionProjectModel AddProject(string filePath, string? projectTypeName = null, SolutionFolderModel? folder = null) { Argument.ThrowIfNullOrEmpty(filePath, nameof(filePath)); - this.ValidateInModel(folder); + ValidateInModel(folder); Guid projectTypeId = - Guid.TryParse(projectTypeName, out Guid projectTypeGuid) ? projectTypeGuid : - this.ProjectTypeTable.GetProjectTypeId(projectTypeName, Path.GetExtension(filePath.AsSpan())) ?? - throw new SolutionArgumentException(string.Format(Errors.InvalidProjectTypeReference_Args1, projectTypeName), nameof(projectTypeName), SolutionErrorType.InvalidProjectTypeReference); + Guid.TryParse(projectTypeName, out Guid projectTypeGuid) + ? projectTypeGuid + : ProjectTypeTable.GetProjectTypeId(projectTypeName, Path.GetExtension(filePath.AsSpan())) ?? + throw new SolutionArgumentException(string.Format(Errors.InvalidProjectTypeReference_Args1, projectTypeName), + nameof(projectTypeName), SolutionErrorType.InvalidProjectTypeReference); - return this.AddProject(filePath, projectTypeName ?? string.Empty, projectTypeId, folder); + return AddProject(filePath, projectTypeName ?? string.Empty, projectTypeId, folder); } /// @@ -231,9 +235,9 @@ public SolutionProjectModel AddProject(string filePath, string? projectTypeName public bool RemoveFolder(SolutionFolderModel folder) { Argument.ThrowIfNull(folder, nameof(folder)); - this.ValidateInModel(folder); + ValidateInModel(folder); - return this.RemoveFolder(folder, this.SolutionItems.ToArray()); + return RemoveFolder(folder, SolutionItems.ToArray()); } /// @@ -244,16 +248,16 @@ public bool RemoveFolder(SolutionFolderModel folder) public bool RemoveProject(SolutionProjectModel project) { Argument.ThrowIfNull(project, nameof(project)); - this.ValidateInModel(project); - _ = this.solutionProjects.Remove(project); + ValidateInModel(project); + _ = solutionProjects.Remove(project); // Remove any dependencies to this project. - foreach (SolutionProjectModel existingProject in this.SolutionProjects) + foreach (SolutionProjectModel existingProject in SolutionProjects) { _ = existingProject.RemoveDependency(project); } - return this.RemoveItem(project); + return RemoveItem(project); } /// @@ -266,10 +270,10 @@ public void AddBuildType(string buildType) ValidateName(buildType.AsSpan()); - if (!this.solutionBuildTypes.Contains(buildType, StringComparer.OrdinalIgnoreCase)) + if (!solutionBuildTypes.Contains(buildType, StringComparer.OrdinalIgnoreCase)) { - buildType = this.StringTable.GetString(buildType); - this.solutionBuildTypes.Add(buildType); + buildType = StringTable.GetString(buildType); + solutionBuildTypes.Add(buildType); } } @@ -281,7 +285,7 @@ public void AddBuildType(string buildType) public bool RemoveBuildType(string buildType) { Argument.ThrowIfNullOrEmpty(buildType, nameof(buildType)); - return this.solutionBuildTypes.Remove(buildType); + return solutionBuildTypes.Remove(buildType); } /// @@ -294,10 +298,10 @@ public void AddPlatform(string platform) ValidateName(platform.AsSpan()); - if (!this.solutionPlatforms.Contains(platform, StringComparer.OrdinalIgnoreCase)) + if (!solutionPlatforms.Contains(platform, StringComparer.OrdinalIgnoreCase)) { - platform = this.StringTable.GetString(platform); - this.solutionPlatforms.Add(platform); + platform = StringTable.GetString(platform); + solutionPlatforms.Add(platform); } } @@ -309,7 +313,7 @@ public void AddPlatform(string platform) public bool RemovePlatform(string platform) { Argument.ThrowIfNullOrEmpty(platform, nameof(platform)); - return this.solutionPlatforms.Remove(platform); + return solutionPlatforms.Remove(platform); } /// @@ -319,7 +323,7 @@ public bool RemovePlatform(string platform) /// The item if found. public SolutionItemModel? FindItemById(Guid id) { - return this.solutionItemsById.TryGetValue(id, out SolutionItemModel? item) ? item : null; + return solutionItemsById.TryGetValue(id, out SolutionItemModel? item) ? item : null; } /// @@ -332,10 +336,11 @@ public bool RemovePlatform(string platform) Argument.ThrowIfNullOrEmpty(path, nameof(path)); if (!path.StartsWith('/') || !path.EndsWith('/')) { - throw new SolutionArgumentException(string.Format(Errors.InvalidFolderPath_Args1, path), nameof(path), SolutionErrorType.InvalidFolderPath); + throw new SolutionArgumentException(string.Format(Errors.InvalidFolderPath_Args1, path), nameof(path), + SolutionErrorType.InvalidFolderPath); } - return ModelHelper.FindByItemRef(this.solutionFolders, path); + return solutionFolders.FindByItemRef(path); } /// @@ -347,7 +352,7 @@ public bool RemovePlatform(string platform) { Argument.ThrowIfNullOrEmpty(path, nameof(path)); - return ModelHelper.FindByItemRef(this.solutionProjects, path); + return solutionProjects.FindByItemRef(path); } /// @@ -357,7 +362,7 @@ public bool RemovePlatform(string platform) /// public void DistillProjectConfigurations() { - SolutionConfigurationMap cfgMap = new SolutionConfigurationMap(this); + SolutionConfigurationMap cfgMap = new(this); // Load all of the current rules for the project and recalculate a new // set of configuration rules. @@ -407,12 +412,15 @@ static bool IsDosWord(scoped StringSpan name) name.EqualsOrdinalIgnoreCase("con") || name.EqualsOrdinalIgnoreCase("aux") || name.EqualsOrdinalIgnoreCase("prn"); + case 4: // disallow com? and lpt? where ? can be any number from 1 to 9 name = name.TrimEnd("123456789".AsSpan()); return name.EqualsOrdinalIgnoreCase("com") || name.EqualsOrdinalIgnoreCase("lpt"); + case 6: return name.EqualsOrdinalIgnoreCase("clock$"); + default: return false; } @@ -426,15 +434,15 @@ static bool IsDosWord(scoped StringSpan name) internal void TrimVisualStudioProperties() { // Set project id to default. - foreach (SolutionItemModel item in this.SolutionItems) + foreach (SolutionItemModel item in SolutionItems) { item.Id = Guid.Empty; } - this.VisualStudioProperties.SolutionId = null; - this.visualStudioProperties.OpenWith = null; + VisualStudioProperties.SolutionId = null; + visualStudioProperties.OpenWith = null; - this.RemoveObsoleteProperties(); + RemoveObsoleteProperties(); } /// @@ -447,7 +455,7 @@ internal void RemoveObsoleteProperties() // Remove CPS project type ids. // This explicitly checks for the built-in CPS type names, so a slnx file can still // use the CPS project ids by creating a custom ProjectType. - foreach (SolutionProjectModel project in this.SolutionProjects) + foreach (SolutionProjectModel project in SolutionProjects) { // Remove CPS project type that were used by .sln for many years due to a bug. if (StringComparer.OrdinalIgnoreCase.Equals(project.Type, "Common C#")) @@ -464,9 +472,9 @@ internal void RemoveObsoleteProperties() } } - _ = this.RemoveProperties(Serializer.SlnV12.SectionName.SharedMSBuildProjectFiles); + _ = RemoveProperties(SectionName.SharedMSBuildProjectFiles); - VisualStudioProperties vsProperties = this.VisualStudioProperties; + VisualStudioProperties vsProperties = VisualStudioProperties; vsProperties.Version = null; #pragma warning disable CS0618 // Type or member is obsolete vsProperties.HideSolutionNode = null; @@ -479,23 +487,25 @@ internal void RemoveObsoleteProperties() } } - internal SolutionProjectModel AddProject(string filePath, string projectTypeName, Guid projectTypeId, SolutionFolderModel? folder) + internal SolutionProjectModel AddProject(string filePath, string projectTypeName, Guid projectTypeId, + SolutionFolderModel? folder) { - SolutionProjectModel project = new SolutionProjectModel(this, filePath, projectTypeId, projectTypeName, folder); + SolutionProjectModel project = new(this, filePath, projectTypeId, projectTypeName, folder); // Project is already in the solution. - if (this.FindProject(project.FilePath) is not null) + if (FindProject(project.FilePath) is not null) { - throw new SolutionArgumentException(string.Format(Errors.DuplicateProjectPath_Arg1, project.ItemRef), nameof(filePath), SolutionErrorType.DuplicateProjectPath); + throw new SolutionArgumentException(string.Format(Errors.DuplicateProjectPath_Arg1, project.ItemRef), + nameof(filePath), SolutionErrorType.DuplicateProjectPath); } - this.ValidateProjectName(project); + ValidateProjectName(project); - this.solutionProjects.Add(project); - this.solutionItems.Add(project); + solutionProjects.Add(project); + solutionItems.Add(project); // Ensure the project type is in the project type table, if it is not already. - this.solutionItemsById[project.Id] = project; + solutionItemsById[project.Id] = project; return project; } @@ -512,7 +522,7 @@ internal SolutionFolderModel CreateFolder(string name) // Validate the name. ValidateName(name.AsSpan()); - return this.AddFolder(name.AsSpan(), parentItemRef: null); + return AddFolder(name.AsSpan(), parentItemRef: null); } /// @@ -523,22 +533,22 @@ internal SolutionFolderModel CreateFolder(string name) /// Use to scope suspension, call to reenable validation. internal IDisposable SuspendProjectValidation() { - this.suspendProjectValidation = true; + suspendProjectValidation = true; return new ValidationScope(this); } internal void ResumeProjectValidation() { - this.suspendProjectValidation = false; - foreach (SolutionProjectModel project in this.solutionProjects) + suspendProjectValidation = false; + foreach (SolutionProjectModel project in solutionProjects) { - this.ValidateProjectName(project); + ValidateProjectName(project); } } internal void ThrowIfProjectValidationSuspended() { - if (this.suspendProjectValidation) + if (suspendProjectValidation) { throw new InvalidOperationException(); } @@ -547,42 +557,42 @@ internal void ThrowIfProjectValidationSuspended() internal bool IsConfigurationImplicit() { return - this.IsBuildTypeImplicit() && - this.IsPlatformImplicit() && - this.ProjectTypeTable.ProjectTypes.Count == 0; + IsBuildTypeImplicit() && + IsPlatformImplicit() && + ProjectTypeTable.ProjectTypes.Count == 0; } internal bool IsBuildTypeImplicit() { // Has 0 build types, or just Debug/Release. return - this.BuildTypes.Count == 0 || - (this.BuildTypes.Count == 2 && - this.BuildTypes.Contains(BuildTypeNames.Debug) && - this.BuildTypes.Contains(BuildTypeNames.Release)); + BuildTypes.Count == 0 || + (BuildTypes.Count == 2 && + BuildTypes.Contains(BuildTypeNames.Debug) && + BuildTypes.Contains(BuildTypeNames.Release)); } internal bool IsPlatformImplicit() { return - this.Platforms.Count == 0 || - (this.Platforms.Count == 1 && - this.Platforms[0] == PlatformNames.AnySpaceCPU); + Platforms.Count == 0 || + (Platforms.Count == 1 && + Platforms[0] == PlatformNames.AnySpaceCPU); } internal void OnUpdateId(SolutionItemModel solutionItemModel, Guid? oldId) { if (oldId is not null) { - _ = this.solutionItemsById.Remove(oldId.Value); + _ = solutionItemsById.Remove(oldId.Value); } - this.solutionItemsById[solutionItemModel.Id] = solutionItemModel; + solutionItemsById[solutionItemModel.Id] = solutionItemModel; } internal void ValidateProjectName(SolutionProjectModel project) { - if (this.suspendProjectValidation) + if (suspendProjectValidation) { return; } @@ -590,7 +600,7 @@ internal void ValidateProjectName(SolutionProjectModel project) string displayName = project.ActualDisplayName; string folderPath = project.Parent?.Path ?? "Root"; - foreach (SolutionProjectModel existingProject in this.SolutionProjects) + foreach (SolutionProjectModel existingProject in SolutionProjects) { if (!ReferenceEquals(existingProject.Parent, project.Parent) || ReferenceEquals(existingProject, project)) { @@ -599,7 +609,8 @@ internal void ValidateProjectName(SolutionProjectModel project) if (existingProject.ActualDisplayName.Equals(displayName, StringComparison.OrdinalIgnoreCase)) { - throw new SolutionArgumentException(string.Format(Errors.DuplicateProjectName_Arg2, displayName, folderPath), SolutionErrorType.DuplicateProjectName); + throw new SolutionArgumentException(string.Format(Errors.DuplicateProjectName_Arg2, displayName, folderPath), + SolutionErrorType.DuplicateProjectName); } } } @@ -615,16 +626,16 @@ internal void ValidateInModel(SolutionItemModel? item) // Moves the project to the first position in the solution so that it is used as the default startup project. internal void MoveProjectFirst(SolutionProjectModel projectModel) { - int projectIndex = this.solutionProjects.IndexOf(projectModel); + int projectIndex = solutionProjects.IndexOf(projectModel); if (projectIndex > 0) { - (this.solutionProjects[projectIndex], this.solutionProjects[0]) = (this.solutionProjects[0], this.solutionProjects[projectIndex]); + (solutionProjects[projectIndex], solutionProjects[0]) = (solutionProjects[0], solutionProjects[projectIndex]); } - int itemIndex = this.solutionItems.IndexOf(projectModel); + int itemIndex = solutionItems.IndexOf(projectModel); if (itemIndex > 0) { - (this.solutionItems[itemIndex], this.solutionItems[0]) = (this.solutionItems[0], this.solutionItems[itemIndex]); + (solutionItems[itemIndex], solutionItems[0]) = (solutionItems[0], solutionItems[itemIndex]); } } @@ -635,12 +646,12 @@ private SolutionFolderModel AddFolder(StringSpan name, string? parentItemRef) ValidateName(name); SolutionFolderModel? parentFolder = - parentItemRef is null ? null : this.FindFolder(parentItemRef) ?? this.AddFolder(parentItemRef); + parentItemRef is null ? null : FindFolder(parentItemRef) ?? AddFolder(parentItemRef); - SolutionFolderModel folder = new SolutionFolderModel(this, this.StringTable.GetString(name), parentFolder); + SolutionFolderModel folder = new(this, StringTable.GetString(name), parentFolder); - this.solutionFolders.Add(folder); - this.solutionItems.Add(folder); + solutionFolders.Add(folder); + solutionItems.Add(folder); return folder; } @@ -649,7 +660,7 @@ private SolutionFolderModel AddFolder(StringSpan name, string? parentItemRef) // Recursive call reuses the solutionItems array to avoid creating a new array for each recursive call. private bool RemoveFolder(SolutionFolderModel folder, SolutionItemModel[] solutionItems) { - _ = this.solutionFolders.Remove(folder); + _ = solutionFolders.Remove(folder); // Remove any children of this folder. foreach (SolutionItemModel existingItem in solutionItems) @@ -658,20 +669,20 @@ private bool RemoveFolder(SolutionFolderModel folder, SolutionItemModel[] soluti { _ = existingItem switch { - SolutionFolderModel childFolder => this.RemoveFolder(childFolder, solutionItems), - SolutionProjectModel childProject => this.RemoveProject(childProject), + SolutionFolderModel childFolder => RemoveFolder(childFolder, solutionItems), + SolutionProjectModel childProject => RemoveProject(childProject), _ => throw new InvalidOperationException(), }; } } - return this.RemoveItem(folder); + return RemoveItem(folder); } private bool RemoveItem(SolutionItemModel item) { - _ = this.solutionItemsById.Remove(item.Id); - return this.solutionItems.Remove(item); + _ = solutionItemsById.Remove(item.Id); + return solutionItems.Remove(item); } private sealed class ValidationScope(SolutionModel model) : IDisposable diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionProjectModel.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionProjectModel.cs index 08f615dd5..d32bee033 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionProjectModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionProjectModel.cs @@ -16,13 +16,13 @@ public sealed class SolutionProjectModel : SolutionItemModel private List? dependencies; private List? projectConfigurationRules; - [SetsRequiredMembers] - internal SolutionProjectModel(SolutionModel solutionModel, string filePath, Guid typeId, string type, SolutionFolderModel? parent) + internal SolutionProjectModel(SolutionModel solutionModel, string filePath, Guid typeId, string type, + SolutionFolderModel? parent) : base(solutionModel, parent) { this.typeId = typeId; this.type = type; - this.FilePath = filePath; + FilePath = filePath; } /// @@ -34,23 +34,23 @@ internal SolutionProjectModel(SolutionModel solutionModel, string filePath, Guid internal SolutionProjectModel(SolutionModel solutionModel, SolutionProjectModel projectModel) : base(solutionModel, projectModel) { - this.typeId = projectModel.TypeId; - this.type = projectModel.Type; - this.FilePath = projectModel.FilePath; - this.DisplayName = projectModel.DisplayName; + typeId = projectModel.TypeId; + type = projectModel.Type; + FilePath = projectModel.FilePath; + DisplayName = projectModel.DisplayName; if (projectModel.dependencies is not null) { - this.dependencies = [.. projectModel.dependencies]; + dependencies = [.. projectModel.dependencies]; } if (projectModel.projectConfigurationRules is not null) { - this.projectConfigurationRules = [.. projectModel.projectConfigurationRules]; + projectConfigurationRules = [.. projectModel.projectConfigurationRules]; } } /// - public override Guid TypeId => this.typeId; + public override Guid TypeId => typeId; /// /// Gets or sets the project type. @@ -60,7 +60,7 @@ internal SolutionProjectModel(SolutionModel solutionModel, SolutionProjectModel /// public string Type { - get => this.type; + get => type; set { @@ -74,13 +74,13 @@ public string Type // Type looks like a project type id and try to lookup the type name. this.typeId = typeId; - this.type = this.Solution.ProjectTypeTable.GetConciseType(this.typeId, string.Empty, this.Extension); + type = Solution.ProjectTypeTable.GetConciseType(this.typeId, string.Empty, Extension); } else { // Type looks like a name, lookup the project type id and simplify name if possible. - this.typeId = this.Solution.ProjectTypeTable.GetProjectTypeId(value, this.Extension.AsSpan()) ?? Guid.Empty; - this.type = this.Solution.ProjectTypeTable.GetConciseType(this.typeId, value, this.Extension); + this.typeId = Solution.ProjectTypeTable.GetProjectTypeId(value, Extension.AsSpan()) ?? Guid.Empty; + type = Solution.ProjectTypeTable.GetConciseType(this.typeId, value, Extension); } } } @@ -90,32 +90,33 @@ public string Type /// public string FilePath { - get => this.filePath; + get => filePath; [MemberNotNull(nameof(filePath), nameof(Extension))] set { - if (!StringComparer.OrdinalIgnoreCase.Equals(this.filePath, value) || this.Extension is null) + if (!StringComparer.OrdinalIgnoreCase.Equals(filePath, value) || Extension is null) { - if (this.Solution.FindProject(value) is not null) + if (Solution.FindProject(value) is not null) { - throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, value, "Project"), nameof(value), SolutionErrorType.DuplicateItemRef); + throw new SolutionArgumentException(string.Format(Errors.DuplicateItemRef_Args2, value, "Project"), + nameof(value), SolutionErrorType.DuplicateItemRef); } - string oldPath = this.filePath!; - string oldExtension = this.Extension!; + string oldPath = filePath!; + string oldExtension = Extension!; try { - this.filePath = value; - this.Extension = this.Solution.StringTable.GetString(PathExtensions.GetExtension(value)); - this.OnItemRefChanged(); + filePath = value; + Extension = Solution.StringTable.GetString(PathExtensions.GetExtension(value)); + OnItemRefChanged(); - this.Solution.ValidateProjectName(this); + Solution.ValidateProjectName(this); } catch (Exception) { - this.filePath = oldPath; - this.Extension = oldExtension; + filePath = oldPath; + Extension = oldExtension; throw; } } @@ -145,13 +146,13 @@ public override string ActualDisplayName { // If the project has a file name, use that as the display name. // This historically takes precedence over the DisplayName property. - StringSpan fileName = PathExtensions.GetStandardDisplayName(this.FilePath.AsSpan()); + StringSpan fileName = PathExtensions.GetStandardDisplayName(FilePath.AsSpan()); if (fileName.IsEmpty) { - return this.DisplayName ?? string.Empty; + return DisplayName ?? string.Empty; } - return this.Solution.StringTable.GetString(fileName); + return Solution.StringTable.GetString(fileName); } } @@ -162,7 +163,7 @@ public override string ActualDisplayName /// Project to project dependencies are normally stored in the project file itself, /// this is used for solution level dependencies. /// - public IReadOnlyList? Dependencies => this.dependencies; + public IReadOnlyList? Dependencies => dependencies; /// /// Gets or sets a list of configuration rules for this project. @@ -170,12 +171,12 @@ public override string ActualDisplayName /// public IReadOnlyList? ProjectConfigurationRules { - get => this.projectConfigurationRules; - set => this.projectConfigurationRules = value is null ? null : [.. value]; + get => projectConfigurationRules; + set => projectConfigurationRules = value is null ? null : [.. value]; } /// - internal override string ItemRef => this.FilePath; + internal override string ItemRef => FilePath; /// /// Gets the project configuration for the given solution configuration. @@ -186,12 +187,17 @@ public IReadOnlyList? ProjectConfigurationRules /// The project configuration for the given solution configuration. /// BuildType and Platform will be null if the configuration information is missing. /// - public (string? BuildType, string? Platform, bool Build, bool Deploy) GetProjectConfiguration(string solutionBuildType, string solutionPlatform) + public (string? BuildType, string? Platform, bool Build, bool Deploy) GetProjectConfiguration(string solutionBuildType, + string solutionPlatform) { - ConfigurationRuleFollower projectTypeRules = this.Solution.ProjectTypeTable.GetProjectConfigurationRules(this); + ConfigurationRuleFollower projectTypeRules = Solution.ProjectTypeTable.GetProjectConfigurationRules(this); + + string? buildType = + MissingToNull(projectTypeRules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? solutionBuildType); + + string? platform = + MissingToNull(projectTypeRules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? solutionPlatform); - string? buildType = MissingToNull(projectTypeRules.GetProjectBuildType(solutionBuildType, solutionPlatform) ?? solutionBuildType); - string? platform = MissingToNull(projectTypeRules.GetProjectPlatform(solutionBuildType, solutionPlatform) ?? solutionPlatform); bool build = projectTypeRules.GetIsBuildable(solutionBuildType, solutionPlatform) ?? true; bool deploy = projectTypeRules.GetIsDeployable(solutionBuildType, solutionPlatform) ?? false; @@ -207,18 +213,19 @@ public IReadOnlyList? ProjectConfigurationRules public void AddDependency(SolutionProjectModel dependency) { Argument.ThrowIfNull(dependency, nameof(dependency)); - this.Solution.ValidateInModel(dependency); + Solution.ValidateInModel(dependency); if (ReferenceEquals(dependency, this)) { - throw new SolutionArgumentException(string.Format(Errors.InvalidLoop_Args1, dependency.ItemRef), nameof(dependency), SolutionErrorType.InvalidLoop); + throw new SolutionArgumentException(string.Format(Errors.InvalidLoop_Args1, dependency.ItemRef), nameof(dependency), + SolutionErrorType.InvalidLoop); } - this.dependencies ??= []; + dependencies ??= []; - if (!this.dependencies.Contains(dependency)) + if (!dependencies.Contains(dependency)) { - this.dependencies.Add(dependency); + dependencies.Add(dependency); } } @@ -230,11 +237,11 @@ public void AddDependency(SolutionProjectModel dependency) public bool RemoveDependency(SolutionProjectModel dependency) { Argument.ThrowIfNull(dependency, nameof(dependency)); - this.Solution.ValidateInModel(dependency); + Solution.ValidateInModel(dependency); return - this.dependencies is not null && - this.dependencies.Remove(dependency); + dependencies is not null && + dependencies.Remove(dependency); } /// @@ -244,12 +251,12 @@ this.dependencies is not null && public void AddProjectConfigurationRule(ConfigurationRule rule) { Argument.ThrowIfNull(rule, nameof(rule)); - this.projectConfigurationRules ??= []; - this.projectConfigurationRules.Add(rule); + projectConfigurationRules ??= []; + projectConfigurationRules.Add(rule); } private protected override Guid GetDefaultId() { - return DefaultIdGenerator.CreateIdFrom(this.FilePath); + return DefaultIdGenerator.CreateIdFrom(FilePath); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionPropertyBag.cs b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionPropertyBag.cs index 7306ee190..699f58275 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/SolutionPropertyBag.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/SolutionPropertyBag.cs @@ -44,20 +44,20 @@ public SolutionPropertyBag(string id, PropertiesScope scope = PropertiesScope.Pr internal SolutionPropertyBag(string id, PropertiesScope scope, int capacity) { - this.Id = id; - this.Scope = scope; - this.propertyNamesInOrder = new List(capacity); - this.properties = new PropertyBag(capacity); + Id = id; + Scope = scope; + propertyNamesInOrder = new List(capacity); + properties = new PropertyBag(capacity); } // Copy constructor. internal SolutionPropertyBag(SolutionPropertyBag propertyBag) { Argument.ThrowIfNull(propertyBag, nameof(propertyBag)); - this.Id = propertyBag.Id; - this.Scope = propertyBag.Scope; - this.propertyNamesInOrder = new List(propertyBag.propertyNamesInOrder); - this.properties = new PropertyBag(propertyBag.properties); + Id = propertyBag.Id; + Scope = propertyBag.Scope; + propertyNamesInOrder = new List(propertyBag.propertyNamesInOrder); + properties = new PropertyBag(propertyBag.properties); } /// @@ -71,21 +71,21 @@ internal SolutionPropertyBag(SolutionPropertyBag propertyBag) public PropertiesScope Scope { get; } /// - public int Count => this.propertyNamesInOrder.Count; + public int Count => propertyNamesInOrder.Count; /// /// Gets a list of property names in the order they were declared. /// - public IReadOnlyList PropertyNames => this.propertyNamesInOrder; + public IReadOnlyList PropertyNames => propertyNamesInOrder; /// - public IEnumerable Keys => this.PropertyNames; + public IEnumerable Keys => PropertyNames; /// - public IEnumerable Values => this.PropertyNames.Select(x => this[x]); + public IEnumerable Values => PropertyNames.Select(x => this[x]); /// - public string this[string key] => this.properties[key]; + public string this[string key] => properties[key]; /// #if NETFRAMEWORK || NETSTANDARD @@ -95,27 +95,27 @@ public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) => #if NETFRAMEWORK || NETSTANDARD #nullable restore #endif - this.properties.TryGetValue(key, out value); + properties.TryGetValue(key, out value); /// public bool ContainsKey(string key) { - return this.properties.ContainsKey(key); + return properties.ContainsKey(key); } /// public IEnumerator> GetEnumerator() { - foreach (string key in this.propertyNamesInOrder) + foreach (string key in propertyNamesInOrder) { - yield return new KeyValuePair(key, this.properties[key]); + yield return new KeyValuePair(key, properties[key]); } } /// IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); + return GetEnumerator(); } /// @@ -128,13 +128,13 @@ public void Add(string name, string value) Argument.ThrowIfNullOrEmpty(name, nameof(name)); Argument.ThrowIfNull(value, nameof(value)); - if (this.properties.TryAdd(name, value)) + if (properties.TryAdd(name, value)) { - this.propertyNamesInOrder.Add(name); + propertyNamesInOrder.Add(name); } else { - this.properties[name] = value; + properties[name] = value; } } @@ -149,15 +149,14 @@ public void AddRange(IReadOnlyCollection> propertie if (this.properties.Count == 0) { this.properties = new PropertyBag(properties); - this.propertyNamesInOrder = properties.ToList(x => x.Key); - return; + propertyNamesInOrder = properties.ToList(x => x.Key); } else { this.properties.EnsureCapacity(this.properties.Count + properties.Count); foreach ((string key, string value) in properties) { - this.Add(key, value); + Add(key, value); } } } @@ -169,9 +168,9 @@ public void AddRange(IReadOnlyCollection> propertie /// if the property was found and removed. public bool Remove(string name) { - if (this.properties.Remove(name)) + if (properties.Remove(name)) { - _ = this.propertyNamesInOrder.Remove(name); + _ = propertyNamesInOrder.Remove(name); return true; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/StringTable.cs b/src/Persistence/Fallout.Persistence.Solution/Model/StringTable.cs index 1cc87a130..e53a097ef 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/StringTable.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/StringTable.cs @@ -10,7 +10,7 @@ namespace Fallout.Persistence.Solution.Model; public sealed class StringTable { // string deduplication facility (we can expect a lot of similar strings in solution files we want to compact while building the model). - private readonly HashSet strings = new HashSet(StringComparer.Ordinal); + private readonly HashSet strings = new(StringComparer.Ordinal); /// /// Initializes a new instance of the class. @@ -34,7 +34,7 @@ public string GetString(StringSpan str) } // CONSIDER: We could use hashcodes to try to find strings without allocating. - return this.GetString(str.ToString()); + return GetString(str.ToString()); } /// @@ -56,23 +56,23 @@ public string GetString(StringSpan str) return string.Empty; } - if (this.strings.TryGetValue(str, out string? result)) + if (strings.TryGetValue(str, out string? result)) { return result; } - _ = this.strings.Add(str); + _ = strings.Add(str); return str; } internal void AddString(string str) { - _ = this.GetString(str); + _ = GetString(str); } // Used to test the string table. internal bool Contains(string str) { - return this.strings.Contains(str); + return strings.Contains(str); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Model/VisualStudioProperties.cs b/src/Persistence/Fallout.Persistence.Solution/Model/VisualStudioProperties.cs index 569ba4a15..7ed5456d7 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Model/VisualStudioProperties.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Model/VisualStudioProperties.cs @@ -28,8 +28,8 @@ internal VisualStudioProperties(SolutionModel solution) /// public string? OpenWith { - get => this.GetProperty(nameof(this.OpenWith)); - set => this.SetProperty(nameof(this.OpenWith), value, string.Empty); + get => GetProperty(nameof(OpenWith)); + set => SetProperty(nameof(OpenWith), value, string.Empty); } /// @@ -39,8 +39,8 @@ public string? OpenWith [Obsolete("This setting is not supported.")] public bool? HideSolutionNode { - get => bool.TryParse(this.GetProperty(nameof(this.HideSolutionNode)), out bool hideSolutionNode) ? hideSolutionNode : null; - set => this.SetProperty(nameof(this.HideSolutionNode), value == true ? Keywords.XmlTrue : null, Keywords.XmlFalse); + get => bool.TryParse(GetProperty(nameof(HideSolutionNode)), out bool hideSolutionNode) ? hideSolutionNode : null; + set => SetProperty(nameof(HideSolutionNode), value == true ? Keywords.XmlTrue : null, Keywords.XmlFalse); } /// @@ -48,8 +48,8 @@ public bool? HideSolutionNode /// public Version? MinimumVersion { - get => SlnV12Extensions.TryParseVSVersion(this.GetProperty(nameof(this.MinimumVersion))); - set => this.SetProperty(nameof(this.MinimumVersion), value?.ToString(), string.Empty); + get => SlnV12Extensions.TryParseVSVersion(GetProperty(nameof(MinimumVersion))); + set => SetProperty(nameof(MinimumVersion), value?.ToString(), string.Empty); } /// @@ -60,8 +60,8 @@ public Version? MinimumVersion /// public Version? Version { - get => SlnV12Extensions.TryParseVSVersion(this.GetProperty(nameof(this.Version))); - set => this.SetProperty(nameof(this.Version), value?.ToString(), string.Empty); + get => SlnV12Extensions.TryParseVSVersion(GetProperty(nameof(Version))); + set => SetProperty(nameof(Version), value?.ToString(), string.Empty); } /// @@ -69,23 +69,23 @@ public Version? Version /// public Guid? SolutionId { - get => Guid.TryParse(this.GetProperty(nameof(this.SolutionId)), out Guid solutionId) ? solutionId : null; - set => this.SetProperty(nameof(this.SolutionId), value == Guid.Empty ? null : value.ToString(), string.Empty); + get => Guid.TryParse(GetProperty(nameof(SolutionId)), out Guid solutionId) ? solutionId : null; + set => SetProperty(nameof(SolutionId), value == Guid.Empty ? null : value.ToString(), string.Empty); } private string? GetProperty(string propertyName) { - Argument.ThrowIfNull(this.solution, nameof(this.solution)); - SolutionPropertyBag? vsProperties = this.solution.FindProperties(SectionName.VisualStudio); + Argument.ThrowIfNull(solution, nameof(solution)); + SolutionPropertyBag? vsProperties = solution.FindProperties(SectionName.VisualStudio); return vsProperties is null ? null : vsProperties.TryGetValue(propertyName, out string? value) ? value : null; } private void SetProperty(string propertyName, string? value, string defaultValue) { - Argument.ThrowIfNull(this.solution, nameof(this.solution)); + Argument.ThrowIfNull(solution, nameof(solution)); if (value is null || StringComparer.OrdinalIgnoreCase.Equals(value, defaultValue)) { - SolutionPropertyBag? vsProperties = this.solution.FindProperties(SectionName.VisualStudio); + SolutionPropertyBag? vsProperties = solution.FindProperties(SectionName.VisualStudio); if (vsProperties is null) { return; @@ -94,12 +94,12 @@ private void SetProperty(string propertyName, string? value, string defaultValue _ = vsProperties.Remove(propertyName); if (vsProperties.Count == 0) { - _ = this.solution.RemoveProperties(SectionName.VisualStudio); + _ = solution.RemoveProperties(SectionName.VisualStudio); } } else { - this.solution.AddProperties(SectionName.VisualStudio).Add(propertyName, value); + solution.AddProperties(SectionName.VisualStudio).Add(propertyName, value); } } } diff --git a/src/Persistence/Fallout.Persistence.Solution/RequiredNetFramework.cs b/src/Persistence/Fallout.Persistence.Solution/RequiredNetFramework.cs index 55c3d12d4..af561c81b 100644 --- a/src/Persistence/Fallout.Persistence.Solution/RequiredNetFramework.cs +++ b/src/Persistence/Fallout.Persistence.Solution/RequiredNetFramework.cs @@ -5,8 +5,9 @@ namespace System.Diagnostics.CodeAnalysis { - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Combine .NET Framework adapters")] - [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", + Justification = "Combine .NET Framework adapters")] + [AttributeUsage(AttributeTargets.Constructor)] internal sealed class SetsRequiredMembersAttribute : Attribute { } @@ -16,14 +17,17 @@ internal sealed class SetsRequiredMembersAttribute : Attribute namespace System.Runtime.CompilerServices #pragma warning restore SA1403 // File may only contain a single namespace { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Combine .NET Framework adapters")] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, + Inherited = false)] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", + Justification = "Combine .NET Framework adapters")] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Combine .NET Framework adapters")] + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", + Justification = "Combine .NET Framework adapters")] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = nameof(RefStructs); @@ -31,7 +35,7 @@ internal sealed class CompilerFeatureRequiredAttribute : Attribute public CompilerFeatureRequiredAttribute(string featureName) { - this.FeatureName = featureName; + FeatureName = featureName; } public string FeatureName { get; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SingleFileSerializerBase.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SingleFileSerializerBase.cs index 0a818cf75..2b8865964 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SingleFileSerializerBase.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SingleFileSerializerBase.cs @@ -9,7 +9,7 @@ internal abstract class SingleFileSerializerBase : ISolutionSingleFil { public abstract string Name { get; } - public string DefaultFileExtension => this.FileExtension; + public string DefaultFileExtension => FileExtension; private protected abstract string FileExtension { get; } @@ -20,18 +20,19 @@ internal abstract class SingleFileSerializerBase : ISolutionSingleFil Task ISolutionSingleFileSerializer.OpenAsync(Stream reader, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return this.ReadModelAsync(fullPath: null, reader, cancellationToken); + return ReadModelAsync(fullPath: null, reader, cancellationToken); } - Task ISolutionSingleFileSerializer.SaveAsync(Stream writer, SolutionModel model, CancellationToken cancellationToken) + Task ISolutionSingleFileSerializer.SaveAsync(Stream writer, SolutionModel model, + CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return this.WriteModelAsync(fullPath: null, model, writer, cancellationToken); + return WriteModelAsync(fullPath: null, model, writer, cancellationToken); } bool ISolutionSerializer.IsSupported(string fullPath) { - return Path.GetExtension(fullPath.AsSpan()).EqualsOrdinalIgnoreCase(this.FileExtension); + return Path.GetExtension(fullPath.AsSpan()).EqualsOrdinalIgnoreCase(FileExtension); } async Task ISolutionSerializer.OpenAsync(string moniker, CancellationToken cancellationToken) @@ -40,7 +41,7 @@ async Task ISolutionSerializer.OpenAsync(string moniker, Cancella // netstandard2.0, so `await using` fails to compile there (CS8417). Synchronous disposal // of a local read stream is fine across all target frameworks. using FileStream reader = File.OpenRead(moniker); - return await this.ReadModelAsync(moniker, reader, cancellationToken); + return await ReadModelAsync(moniker, reader, cancellationToken); } async Task ISolutionSerializer.SaveAsync(string moniker, SolutionModel model, CancellationToken cancellationToken) @@ -54,10 +55,12 @@ async Task ISolutionSerializer.SaveAsync(string moniker, SolutionModel model, Ca // Plain `using` (not `await using`): see OpenAsync — FileStream is not IAsyncDisposable // on netstandard2.0 (CS8417). Synchronous disposal of a local write stream is fine here. using FileStream writer = File.OpenWrite(moniker); - await this.WriteModelAsync(moniker, model, writer, cancellationToken); + await WriteModelAsync(moniker, model, writer, cancellationToken); } - private protected abstract Task ReadModelAsync(string? fullPath, Stream reader, CancellationToken cancellationToken); + private protected abstract Task ReadModelAsync(string? fullPath, Stream reader, + CancellationToken cancellationToken); - private protected abstract Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, CancellationToken cancellationToken); + private protected abstract Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, + CancellationToken cancellationToken); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SectionName.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SectionName.cs index baaae5082..188fa0391 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SectionName.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SectionName.cs @@ -26,14 +26,22 @@ internal static class SectionName internal static string InternKnownSectionName(string sectionName) { return - StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionProperties) ? SolutionProperties : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, ExtensibilityGlobals) ? ExtensibilityGlobals : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, NestedProjects) ? NestedProjects : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionConfigurationPlatforms) ? SolutionConfigurationPlatforms : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectConfigurationPlatforms) ? ProjectConfigurationPlatforms : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, SharedMSBuildProjectFiles) ? SharedMSBuildProjectFiles : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectDependencies) ? ProjectDependencies : - StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionItems) ? SolutionItems : - sectionName; + StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionProperties) + ? SolutionProperties + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, ExtensibilityGlobals) + ? ExtensibilityGlobals + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, NestedProjects) + ? NestedProjects + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionConfigurationPlatforms) + ? SolutionConfigurationPlatforms + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectConfigurationPlatforms) + ? ProjectConfigurationPlatforms + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, SharedMSBuildProjectFiles) + ? SharedMSBuildProjectFiles + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, ProjectDependencies) + ? ProjectDependencies + : StringComparer.OrdinalIgnoreCase.Equals(sectionName, SolutionItems) + ? SolutionItems + : sectionName; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Reader.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Reader.cs index e28fb90e1..58a40cdec 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Reader.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Reader.cs @@ -43,7 +43,8 @@ private enum LineType Property, } - internal ValueTask ParseAsync(ISolutionSerializer serializer, string? fullPath, CancellationToken cancellationToken) + internal ValueTask ParseAsync(ISolutionSerializer serializer, string? fullPath, + CancellationToken cancellationToken) { Version? vsVersion = null; Version? minVsVersion = null; @@ -58,12 +59,16 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str bool inGlobal = false; bool inGlobalSection = false; - SolutionModel solutionModel = new SolutionModel(); + SolutionModel solutionModel = new(); - this.lineNumber = 0; - if (!this.TryParseFormatLine()) + lineNumber = 0; + if (!TryParseFormatLine()) { - throw new SolutionException(Errors.NotSolution, SolutionErrorType.NotSolution) { File = fullPath, Line = this.lineNumber }; + throw new SolutionException(Errors.NotSolution, SolutionErrorType.NotSolution) + { + File = fullPath, + Line = lineNumber + }; } // Some property bags need to be loaded after all projects have been resolved. @@ -78,7 +83,7 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str { using (solutionModel.SuspendProjectValidation()) { - while (this.ReadLine(out StringTokenizer tokenizer)) + while (ReadLine(out StringTokenizer tokenizer)) { cancellationToken.ThrowIfCancellationRequested(); @@ -90,64 +95,68 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str switch (lineType) { case LineType.Project: - this.TarnishIf(inProject); + TarnishIf(inProject); inProject = true; - currentProject = this.ReadProjectInfo(solutionModel, ref tokenizer, fixedProjectIds); + currentProject = ReadProjectInfo(solutionModel, ref tokenizer, fixedProjectIds); break; case LineType.EndProject: - this.TarnishIf(!inProject); + TarnishIf(!inProject); inProject = false; - this.TarnishIf(!AddProjectProperties(currentProject, currentPropertyBag, delayLoadProperties)); + TarnishIf(!AddProjectProperties(currentProject, currentPropertyBag, delayLoadProperties)); currentPropertyBag = null; currentProject = null; break; case LineType.Global: - this.TarnishIf(inProject); + TarnishIf(inProject); inGlobal = true; break; case LineType.EndGlobal: - this.TarnishIf(!inGlobal); + TarnishIf(!inGlobal); inGlobal = false; - this.TarnishIf(!solutionModel.AddSlnProperties(currentPropertyBag)); + TarnishIf(!solutionModel.AddSlnProperties(currentPropertyBag)); currentPropertyBag = null; break; case LineType.ProjectSection: - this.TarnishIf(!inProject); + TarnishIf(!inProject); inProjectSection = true; bool checkOnly = currentProject is null; - currentPropertyBag = this.ReadPropertyBag(ref tokenizer, isSolution: false, checkOnly); + currentPropertyBag = ReadPropertyBag(ref tokenizer, isSolution: false, checkOnly); break; case LineType.EndProjectSection: - this.TarnishIf(!inProject || !inProjectSection); + TarnishIf(!inProject || !inProjectSection); inProjectSection = false; - this.TarnishIf(!AddProjectProperties(currentProject, currentPropertyBag, delayLoadProperties)); + TarnishIf(!AddProjectProperties(currentProject, currentPropertyBag, delayLoadProperties)); currentPropertyBag = null; break; case LineType.GlobalSection: - this.TarnishIf(!inGlobal); + TarnishIf(!inGlobal); inGlobalSection = true; - currentPropertyBag = this.ReadPropertyBag(ref tokenizer, isSolution: true, checkOnly: false); + currentPropertyBag = ReadPropertyBag(ref tokenizer, isSolution: true, checkOnly: false); break; case LineType.EndGlobalSection: - this.TarnishIf(!inGlobal || !inGlobalSection); + TarnishIf(!inGlobal || !inGlobalSection); inGlobalSection = false; - this.TarnishIf(!solutionModel.AddSlnProperties(currentPropertyBag)); + TarnishIf(!solutionModel.AddSlnProperties(currentPropertyBag)); currentPropertyBag = null; break; case LineType.VisualStudioVersion: - vsVersion = SlnV12Extensions.TryParseVSVersion(tokenizer.NextToken(SlnConstants.VersionSeparators)); + vsVersion = SlnV12Extensions.TryParseVSVersion( + tokenizer.NextToken(SlnConstants.VersionSeparators)); + break; case LineType.MinimumVisualStudioVersion: - minVsVersion = SlnV12Extensions.TryParseVSVersion(tokenizer.NextToken(SlnConstants.VersionSeparators)); + minVsVersion = + SlnV12Extensions.TryParseVSVersion(tokenizer.NextToken(SlnConstants.VersionSeparators)); + break; case LineType.CommentLine: @@ -162,7 +171,7 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str case LineType.Property: if (currentPropertyBag is null) { - this.TarnishIf(true); + TarnishIf(true); break; } @@ -190,19 +199,19 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str break; default: - this.TarnishIf(true); + TarnishIf(true); break; } } // After this point the line number doesn't reflect where an error originated from. - this.lineNumber = null; + lineNumber = null; // The project dependencies properties require the projects to all be loaded, // so they are processed after the model has added all of the projects. foreach ((SolutionItemModel item, SolutionPropertyBag properties) in delayLoadProperties) { - this.TarnishIf(!item.AddSlnProperties(properties)); + TarnishIf(!item.AddSlnProperties(properties)); } foreach ((Guid newId, SolutionProjectModel duplicateProject) in fixedProjectIds) @@ -222,14 +231,23 @@ internal ValueTask ParseAsync(ISolutionSerializer serializer, str vsProperties.Version = vsVersion; solutionModel.SerializerExtension = new SlnV12ModelExtension( serializer, - new SlnV12SerializerSettings() { Encoding = GetSlnFileEncoding(reader) }, + new SlnV12SerializerSettings + { + Encoding = GetSlnFileEncoding(reader) + }, fullPath) - { Tarnished = this.tarnished }; + { + Tarnished = tarnished + }; } } catch (Exception ex) when (SolutionException.ShouldWrap(ex)) { - throw new SolutionException(ex.Message, ex, SolutionErrorType.Undefined) { File = fullPath, Line = this.lineNumber }; + throw new SolutionException(ex.Message, ex, SolutionErrorType.Undefined) + { + File = fullPath, + Line = lineNumber + }; } return new ValueTask(solutionModel); @@ -285,9 +303,11 @@ private static Encoding GetSlnFileEncoding(StreamReader reader) { firstComment = firstComment.Trim(); return - firstComment.IsEmpty ? null : - firstComment.StartsWith(SlnConstants.OpenWithPrefix) ? firstComment.Slice(SlnConstants.OpenWithPrefix.Length).ToString().NullIfEmpty() : - null; + firstComment.IsEmpty + ? null + : firstComment.StartsWith(SlnConstants.OpenWithPrefix) + ? firstComment.Slice(SlnConstants.OpenWithPrefix.Length).ToString().NullIfEmpty() + : null; } // determine the line time and advance the scan position. @@ -384,7 +404,8 @@ private static LineType GetLineType(ref StringTokenizer tokenizer, bool allowSol case 'M': // MinimumVisualStudioVersion - if (allowSolutionProperties && first == 0 && tokenizer.SliceIfStartsWith(SlnConstants.TagMinimumVisualStudioVersion)) + if (allowSolutionProperties && first == 0 && + tokenizer.SliceIfStartsWith(SlnConstants.TagMinimumVisualStudioVersion)) { return LineType.MinimumVisualStudioVersion; } @@ -422,7 +443,7 @@ private static bool TryParseScope(scoped StringSpan s, bool isSolution, out Prop private bool TryParseFormatLine() { - if (!this.ReadLine(out StringTokenizer tokenizer)) + if (!ReadLine(out StringTokenizer tokenizer)) { return false; } @@ -430,7 +451,7 @@ private bool TryParseFormatLine() // skips first line if empty. (happen if UTF8 bom is used by writer) if (tokenizer.IsEmpty) { - if (!this.ReadLine(out tokenizer)) + if (!ReadLine(out tokenizer)) { return false; } @@ -439,7 +460,7 @@ private bool TryParseFormatLine() if (tokenizer.Current.IndexOf(SlnConstants.SLNFileHeaderNoVersion) < 0) { // first line may contain file format signature, sp parsers will try the second line as well. - if (!this.ReadLine(out tokenizer) || tokenizer.Current.IndexOf(SlnConstants.SLNFileHeaderNoVersion) < 0) + if (!ReadLine(out tokenizer) || tokenizer.Current.IndexOf(SlnConstants.SLNFileHeaderNoVersion) < 0) { return false; } @@ -476,9 +497,15 @@ private bool TryParseFormatLine() fileVersionMaj = versionPath.Slice(0, dotIndex).ToString(); } - if (string.IsNullOrEmpty(fileVersionMaj) || !int.TryParse(fileVersionMaj, out int fileVer) || fileVer > MaxFileVersion) + if (string.IsNullOrEmpty(fileVersionMaj) || !int.TryParse(fileVersionMaj, out int fileVer) || + fileVer > MaxFileVersion) { - throw new SolutionException(string.Format(Errors.UnsupportedVersion_Args1, fileVersionMaj), SolutionErrorType.UnsupportedVersion) { File = fullPath, Line = this.lineNumber }; + throw new SolutionException(string.Format(Errors.UnsupportedVersion_Args1, fileVersionMaj), + SolutionErrorType.UnsupportedVersion) + { + File = fullPath, + Line = lineNumber + }; } return true; @@ -490,7 +517,7 @@ private bool ReadLine(out StringTokenizer lineScanner) do { string? line = reader.ReadLine(); - this.lineNumber++; + lineNumber++; lineScanner = new StringTokenizer(line ?? string.Empty); if (line is null) { @@ -511,13 +538,14 @@ private bool ReadLine(out StringTokenizer lineScanner) // We have to keep that behaviour, only slight difference will allow space in adition to tab at the end of name // With all wierd syntaxes old will accepet, it will not accept ProjectSection( Foo ) (but will do ) ProjectSection( Foo) ... StringSpan sectionName = tokenizer.NextToken(SlnConstants.SectionSeparators2).Trim(); - this.SolutionAssert(!sectionName.IsEmpty, Errors.MissingSectionName); + SolutionAssert(!sectionName.IsEmpty, Errors.MissingSectionName); StringSpan sectionScopeStr = tokenizer.NextToken(SlnConstants.SectionSeparators).Trim(); - this.SolutionAssert(TryParseScope(sectionScopeStr, isSolution, out PropertiesScope scope), Errors.InvalidScope); + SolutionAssert(TryParseScope(sectionScopeStr, isSolution, out PropertiesScope scope), Errors.InvalidScope); return checkOnly ? null : new SolutionPropertyBag(sectionName.ToString(), scope); } - private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringTokenizer tokenizer, List<(Guid NewId, SolutionProjectModel DuplicateProject)> fixedProjectIds) + private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringTokenizer tokenizer, + List<(Guid NewId, SolutionProjectModel DuplicateProject)> fixedProjectIds) { // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App1", "App1\App1.csproj", "{B0D4AB54-EB86-4C88-A2A4-C55D0C200244}" // ^ <- this is tokenizer pos. @@ -528,7 +556,7 @@ private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringToke if (!Guid.TryParse(projectType.ToString(), out Guid projectTypeId)) { projectTypeId = Guid.Empty; - this.tarnished = true; + tarnished = true; } // this just skips up to Display's name "App1" first quote, position at 'A". The TrimStart is extension to allow spaces before ')'; @@ -536,19 +564,19 @@ private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringToke StringSpan skip = tokenizer.NextToken(SlnConstants.DoubleQuote).TrimStart(); // and do the check for factory guid. ends with '). - this.SolutionAssert(!skip.IsEmpty && skip[0] == ')', Errors.SyntaxError); + SolutionAssert(!skip.IsEmpty && skip[0] == ')', Errors.SyntaxError); StringSpan displayName = tokenizer.NextToken(SlnConstants.DoubleQuote); - this.SolutionAssert(!displayName.IsEmpty, Errors.MissingDisplayName); + SolutionAssert(!displayName.IsEmpty, Errors.MissingDisplayName); skip = tokenizer.NextToken(SlnConstants.DoubleQuote).TrimStart(); - this.SolutionAssert(!skip.IsEmpty && skip[0] == ',', Errors.SyntaxError); + SolutionAssert(!skip.IsEmpty && skip[0] == ',', Errors.SyntaxError); StringSpan relativePath = tokenizer.NextToken(SlnConstants.DoubleQuote); - this.SolutionAssert(!relativePath.IsEmpty, Errors.MissingPath); + SolutionAssert(!relativePath.IsEmpty, Errors.MissingPath); // no comma check errata for this so any text between "relPath"{*}"uniqueiId" is valid. StringSpan projectUniqueId = tokenizer.NextToken(SlnConstants.ProjectSeparators); - this.SolutionAssert(!projectUniqueId.IsEmpty, Errors.MissingProjectId); - this.TarnishIf(!Guid.TryParse(projectUniqueId.ToString(), out Guid projectId)); + SolutionAssert(!projectUniqueId.IsEmpty, Errors.MissingProjectId); + TarnishIf(!Guid.TryParse(projectUniqueId.ToString(), out Guid projectId)); SolutionItemModel? duplicateItem = solution.FindItemById(projectId); @@ -562,7 +590,7 @@ private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringToke if (duplicateItem is not null) { projectId = Guid.NewGuid(); - this.tarnished = true; + tarnished = true; } folder.Id = projectId; @@ -577,12 +605,12 @@ private SolutionItemModel ReadProjectInfo(SolutionModel solution, ref StringToke if (duplicateItem is SolutionFolderModel duplicateFolder) { duplicateFolder.Id = Guid.NewGuid(); - this.tarnished = true; + tarnished = true; } else if (duplicateItem is SolutionProjectModel duplicateProject) { projectId = CreateNewProjectId(solution, path); - this.tarnished = true; + tarnished = true; // Record the new project id so it's configuration can be duplicated. fixedProjectIds.Add((projectId, duplicateProject)); @@ -611,7 +639,7 @@ static Guid CreateNewProjectId(SolutionModel solution, string path) // In these scenarios old parser would ignore the line (potentially throw aways some data) and move on. private void TarnishIf(bool tarnish) { - this.tarnished |= tarnish; + tarnished |= tarnish; } // Validate condition, that if false would make so the old parser will give up and report failure and reject the solution file. @@ -622,7 +650,11 @@ private readonly void SolutionAssert([DoesNotReturnIf(false)] bool condition, st return; } - throw new SolutionException(message, SolutionErrorType.Undefined) { File = fullPath, Line = this.lineNumber }; + throw new SolutionException(message, SolutionErrorType.Undefined) + { + File = fullPath, + Line = lineNumber + }; } } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Writer.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Writer.cs index e3f2186be..c6da5c8c2 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Writer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.Writer.cs @@ -25,9 +25,10 @@ internal static async Task SaveAsync( // Only support unicode based encodings. Note ASCII is a subset of UTF8, if ASCII fails switch to UTF8 with BOM. // This should hopefully stop propagating solutions that rely on Windows language. - Encoding encoding = formatEncoding ?? Encoding.GetEncoding(Encoding.ASCII.CodePage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); + Encoding encoding = formatEncoding ?? Encoding.GetEncoding(Encoding.ASCII.CodePage, new EncoderExceptionFallback(), + new DecoderExceptionFallback()); - using (MemoryStream memoryStream = new MemoryStream()) + using (MemoryStream memoryStream = new()) using (TextWriter memoryWriter = new StreamWriter(memoryStream, encoding)) { // First copy the model to memory, if any exceptions occur this @@ -52,8 +53,8 @@ internal void WriteSolution(bool writeExtraLine) } // emits "Microsoft Visual Studio Solution File, Format Version 12.00"; - writer.Write(SlnConstants.SLNFileHeaderNoVersion); // Microsoft Visual Studio Solution File, Format Version - writer.WriteLine(SlnConstants.SLNFileHeaderVersion); // Microsoft Visual Studio Solution File, Format Version 12.00 + writer.Write(SlnConstants.SLNFileHeaderNoVersion); // Microsoft Visual Studio Solution File, Format Version + writer.WriteLine(SlnConstants.SLNFileHeaderVersion); // Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioProperties vsProperties = model.VisualStudioProperties; string? openWithVS = vsProperties.OpenWith; @@ -66,32 +67,32 @@ internal void WriteSolution(bool writeExtraLine) string? vsVersion = vsProperties.Version?.ToString(); if (!string.IsNullOrEmpty(vsVersion)) { - writer.Write(SlnConstants.TagVisualStudioVersion); // VisualStudioVersion - writer.Write(SlnConstants.TagAssignValue); // VisualStudioVersion = - writer.WriteLine(vsVersion); // VisualStudioVersion = [ver] + writer.Write(SlnConstants.TagVisualStudioVersion); // VisualStudioVersion + writer.Write(SlnConstants.TagAssignValue); // VisualStudioVersion = + writer.WriteLine(vsVersion); // VisualStudioVersion = [ver] } string? minVsVersion = vsProperties.MinimumVersion?.ToString(); if (!string.IsNullOrEmpty(minVsVersion)) { - writer.Write(SlnConstants.TagMinimumVisualStudioVersion); // MinimumVisualStudioVersion - writer.Write(SlnConstants.TagAssignValue); // MinimumVisualStudioVersion = - writer.WriteLine(minVsVersion); // MinimumVisualStudioVersion = [ver] + writer.Write(SlnConstants.TagMinimumVisualStudioVersion); // MinimumVisualStudioVersion + writer.Write(SlnConstants.TagAssignValue); // MinimumVisualStudioVersion = + writer.WriteLine(minVsVersion); // MinimumVisualStudioVersion = [ver] } foreach (SolutionItemModel item in model.SolutionItems) { - this.WriteProject(item); + WriteProject(item); } - writer.WriteLine(SlnConstants.TagGlobal); // Global + writer.WriteLine(SlnConstants.TagGlobal); // Global foreach (SolutionPropertyBag section in model.GetSlnProperties()) { - this.WritePropertyMap(isSolution: true, section); + WritePropertyMap(isSolution: true, section); } - writer.WriteLine(SlnConstants.TagEndGlobal); // EndGlobal + writer.WriteLine(SlnConstants.TagEndGlobal); // EndGlobal } private static bool ShouldWriteExtraHeaderLine(Encoding encoding) @@ -102,10 +103,11 @@ private static bool ShouldWriteExtraHeaderLine(Encoding encoding) private void WritePropertyMap(bool isSolution, SolutionPropertyBag map) { - this.WritePropertyMap(map.Id, isSolution, map.Scope, map); + WritePropertyMap(map.Id, isSolution, map.Scope, map); } - private void WritePropertyMap(string id, bool isSolution, PropertiesScope scope, IReadOnlyDictionary properties) + private void WritePropertyMap(string id, bool isSolution, PropertiesScope scope, + IReadOnlyDictionary properties) { if (string.IsNullOrEmpty(id)) { @@ -118,11 +120,11 @@ private void WritePropertyMap(string id, bool isSolution, PropertiesScope scope, return; } - using (this.WriteSectionHeader(isSolution, id, scope)) + using (WriteSectionHeader(isSolution, id, scope)) { foreach ((string propName, string propValue) in properties) { - this.WriteProperty(propName, propValue); + WriteProperty(propName, propValue); } } } @@ -130,7 +132,9 @@ private void WritePropertyMap(string id, bool isSolution, PropertiesScope scope, private void WriteProject(SolutionItemModel item) { // For solution folders, path is just the display name again. - string path = item is SolutionProjectModel project ? PathExtensions.ConvertModelToBackslashPath(project.FilePath) : item.ActualDisplayName; + string path = item is SolutionProjectModel project + ? PathExtensions.ConvertModelToBackslashPath(project.FilePath) + : item.ActualDisplayName; if (item.TypeId == Guid.Empty) { @@ -149,20 +153,20 @@ private void WriteProject(SolutionItemModel item) throw new InvalidOperationException("Missing essential property Id on project"); } - writer.Write(SlnConstants.TagProject); // Project - writer.Write(@"("""); // Project(" - writer.Write(item.TypeId.ToSlnString()); // Project("[type] - writer.Write(@""") = """); // Project("[type]") = ") - writer.Write(item.ActualDisplayName); // Project("[type]") = "[dispName]) - writer.Write(SlnConstants.TagQuoteCommaQuote); // Project("[type]") = "[dispName]", " - writer.Write(path); // Project("[type]") = "[dispName]", "[relpath] - writer.Write(SlnConstants.TagQuoteCommaQuote); // Project("[type]") = "[dispName]", "[relpath]", " - writer.Write(item.Id.ToSlnString()); // Project("[type]") = "[dispName]", "[relpath]", "[guid] - writer.WriteLine('\"'); // Project("[type]") = "[dispName]", "[relpath]", "[guid]" + writer.Write(SlnConstants.TagProject); // Project + writer.Write(@"("""); // Project(" + writer.Write(item.TypeId.ToSlnString()); // Project("[type] + writer.Write(@""") = """); // Project("[type]") = ") + writer.Write(item.ActualDisplayName); // Project("[type]") = "[dispName]) + writer.Write(SlnConstants.TagQuoteCommaQuote); // Project("[type]") = "[dispName]", " + writer.Write(path); // Project("[type]") = "[dispName]", "[relpath] + writer.Write(SlnConstants.TagQuoteCommaQuote); // Project("[type]") = "[dispName]", "[relpath]", " + writer.Write(item.Id.ToSlnString()); // Project("[type]") = "[dispName]", "[relpath]", "[guid] + writer.WriteLine('\"'); // Project("[type]") = "[dispName]", "[relpath]", "[guid]" foreach (SolutionPropertyBag map in item.GetSlnProperties()) { - this.WritePropertyMap(isSolution: false, map); + WritePropertyMap(isSolution: false, map); } writer.WriteLine(SlnConstants.TagEndProject); // EndProject @@ -170,17 +174,16 @@ private void WriteProject(SolutionItemModel item) private void WriteProperty(string name, string value) { - writer.Write("\t\t"); // - writer.Write(name); // [propName] - writer.Write(" = "); // [propName] = - writer.WriteLine(value); // [propName] = [propValue] + writer.Write("\t\t"); // + writer.Write(name); // [propName] + writer.Write(" = "); // [propName] = + writer.WriteLine(value); // [propName] = [propValue] } private WriteSectionScope WriteSectionHeader(bool isSolution, string id, PropertiesScope scope) { string sectionTag = isSolution ? "GlobalSection" : "ProjectSection"; - string sectionScope = scope == PropertiesScope.PostLoad ? - isSolution ? "postSolution" : "postProject" : + string sectionScope = scope == PropertiesScope.PostLoad ? isSolution ? "postSolution" : "postProject" : isSolution ? "preSolution" : "preProject"; writer.Write('\t'); diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.cs index 48067a407..9aabaea83 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnFileV12Serializer.cs @@ -33,7 +33,10 @@ public SlnFileV12Serializer() /// public override ISerializerModelExtension CreateModelExtension() { - return new SlnV12ModelExtension(this, new SlnV12SerializerSettings() { Encoding = null }); + return new SlnV12ModelExtension(this, new SlnV12SerializerSettings + { + Encoding = null + }); } /// @@ -53,7 +56,7 @@ public override ISerializerModelExtension CreateModelExtension(SlnV12SerializerS // Make sure ASCII encoding always has exception fallback. if (encoding.CodePage == Encoding.ASCII.CodePage && encoding.EncoderFallback is not EncoderExceptionFallback) { - settings = new SlnV12SerializerSettings() + settings = new SlnV12SerializerSettings { Encoding = null, }; @@ -63,14 +66,16 @@ public override ISerializerModelExtension CreateModelExtension(SlnV12SerializerS return new SlnV12ModelExtension(this, settings); } - private protected override async Task ReadModelAsync(string? fullPath, Stream reader, CancellationToken cancellationToken) + private protected override async Task ReadModelAsync(string? fullPath, Stream reader, + CancellationToken cancellationToken) { // NOTE: Encoding.Default is the Windows ANSI code page in .NET Framework, but UTF-8 in .NET Core. - using StreamReader streamReader = new StreamReader(reader, Encoding.Default, detectEncodingFromByteOrderMarks: true); + using StreamReader streamReader = new(reader, Encoding.Default, detectEncodingFromByteOrderMarks: true); return await new Reader(streamReader, fullPath).ParseAsync(this, fullPath, cancellationToken); } - private protected override async Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, CancellationToken cancellationToken) + private protected override async Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, + CancellationToken cancellationToken) { try { @@ -79,7 +84,10 @@ private protected override async Task WriteModelAsync(string? fullPath, Solution catch (EncoderFallbackException) { // Change the model to save it in UTF-8 and retry. - model.SerializerExtension = new SlnV12ModelExtension(this, new SlnV12SerializerSettings() { Encoding = Encoding.UTF8 }, fullPath); + model.SerializerExtension = new SlnV12ModelExtension(this, new SlnV12SerializerSettings + { + Encoding = Encoding.UTF8 + }, fullPath); await SlnFileV12Writer.SaveAsync(model, writerStream); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12Extensions.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12Extensions.cs index 1a9a4fafa..429c14ada 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12Extensions.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12Extensions.cs @@ -49,13 +49,14 @@ public static bool AddSlnProperties(this SolutionItemModel solutionItem, Solutio } return true; + case SectionName.ProjectDependencies when solutionItem is SolutionProjectModel project: bool readAllDependencies = true; foreach (string dependencyProjectId in properties.PropertyNames) { if (Guid.TryParse(dependencyProjectId, out Guid dependencyProjectGuid) && - (project.Solution.FindItemById(dependencyProjectGuid) is SolutionProjectModel dependency)) + project.Solution.FindItemById(dependencyProjectGuid) is SolutionProjectModel dependency) { project.AddDependency(dependency); } @@ -66,6 +67,7 @@ public static bool AddSlnProperties(this SolutionItemModel solutionItem, Solutio } return readAllDependencies; + default: solutionItem.AddProperties(properties.Id, properties.Scope).AddRange(properties); return true; @@ -84,12 +86,12 @@ public static IEnumerable GetSlnProperties(this SolutionIte { Argument.ThrowIfNull(solutionItem, nameof(solutionItem)); - ListBuilderStruct slnProperties = new ListBuilderStruct((solutionItem.Properties?.Count ?? 0) + 1); + ListBuilderStruct slnProperties = new((solutionItem.Properties?.Count ?? 0) + 1); IReadOnlyList? dependencies = (solutionItem as SolutionProjectModel)?.Dependencies; if (!dependencies.IsNullOrEmpty()) { - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.ProjectDependencies, PropertiesScope.PostLoad, dependencies.Count); + SolutionPropertyBag propertyBag = new(SectionName.ProjectDependencies, PropertiesScope.PostLoad, dependencies.Count); foreach (SolutionProjectModel dependency in dependencies) { string dependencyProjectId = dependency.Id.ToSlnString(); @@ -102,7 +104,7 @@ public static IEnumerable GetSlnProperties(this SolutionIte IReadOnlyList? files = (solutionItem as SolutionFolderModel)?.Files; if (!files.IsNullOrEmpty()) { - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.SolutionItems, PropertiesScope.PreLoad, files.Count); + SolutionPropertyBag propertyBag = new(SectionName.SolutionItems, PropertiesScope.PreLoad, files.Count); foreach (string file in files) { string persistenceFile = PathExtensions.ConvertModelToBackslashPath(file); @@ -153,7 +155,8 @@ public static bool AddSlnProperties(this SolutionModel solution, SolutionPropert continue; } - if (ModelHelper.TrySplitFullConfiguration(solution.StringTable, slnConfiguration, out string? buildType, out string? platform)) + if (ModelHelper.TrySplitFullConfiguration(solution.StringTable, slnConfiguration, out string? buildType, + out string? platform)) { solution.AddBuildType(buildType); solution.AddPlatform(platform); @@ -191,6 +194,7 @@ public static bool AddSlnProperties(this SolutionModel solution, SolutionPropert } return readAllValues; + case SectionName.SolutionProperties: if (properties.TryGetValue(SlnConstants.HideSolutionNode, out string? hideSolutionNodeStr) && bool.TryParse(hideSolutionNodeStr, out bool hideSolutionNode)) @@ -202,12 +206,15 @@ public static bool AddSlnProperties(this SolutionModel solution, SolutionPropert if (properties.Count != 1) { properties.Remove(SlnConstants.HideSolutionNode); - SolutionPropertyBag solutionProperties = solution.AddProperties(SectionName.SolutionProperties, properties.Scope); + SolutionPropertyBag solutionProperties = + solution.AddProperties(SectionName.SolutionProperties, properties.Scope); + solutionProperties.AddRange(properties); } } return true; + case SectionName.ExtensibilityGlobals: if (properties.TryGetValue(SlnConstants.SolutionGuid, out string? solutionGuidStr) && Guid.TryParse(solutionGuidStr, out Guid solutionId)) @@ -222,6 +229,7 @@ public static bool AddSlnProperties(this SolutionModel solution, SolutionPropert } return true; + default: solution.AddProperties(properties.Id, properties.Scope).AddRange(properties); return true; @@ -238,7 +246,9 @@ static void SetProjectConfigurationPlatforms(SolutionModel solution, SolutionPro // Set the default configurations for a .sln file. foreach (SolutionProjectModel project in solution.SolutionProjects) { - ConfigurationRuleFollower projectTypeRules = solution.ProjectTypeTable.GetProjectConfigurationRules(project, excludeProjectSpecificRules: true); + ConfigurationRuleFollower projectTypeRules = + solution.ProjectTypeTable.GetProjectConfigurationRules(project, excludeProjectSpecificRules: true); + if (!(projectTypeRules.GetIsBuildable() ?? true)) { continue; @@ -249,13 +259,19 @@ static void SetProjectConfigurationPlatforms(SolutionModel solution, SolutionPro foreach (string platform in solution.Platforms) { // Add missing entries for each configuration, so we can detect if any were missing from the .sln file. - project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, buildType, platform, BuildTypeNames.Missing)); - project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, buildType, platform, PlatformNames.Missing)); + project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, buildType, + platform, BuildTypeNames.Missing)); + + project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, buildType, + platform, PlatformNames.Missing)); // In the old .sln file the default configuration is not to build/deploy unless there is a build/deploy line. // This rule will get overwritten by the build/deploy line if it exists. - project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Build, buildType, platform, bool.FalseString)); - project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Deploy, buildType, platform, bool.FalseString)); + project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Build, buildType, platform, + bool.FalseString)); + + project.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Deploy, buildType, platform, + bool.FalseString)); } } } @@ -277,7 +293,7 @@ void ParseProjectConfigLine(SolutionModel solutionModel, string name, string val * {ProjectId}.SolutionBuildType|SolutionPlatform.ConfigLineType = ProjectBuildType|ProjectPlatform * {190CE348-596E-435A-9E5B-12A689F9FC29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU * {190CE348-596E-435A-9E5B-12A689F9FC29}.Debug|Any CPU.Build.0 = Debug|Any CPU - */ + */ int firstDot = name.IndexOf('.'); if (firstDot < 0) @@ -323,7 +339,8 @@ void ParseProjectConfigLine(SolutionModel solutionModel, string name, string val string slnCfg = name.Substring(firstDot, slnCfgEnd - firstDot); - if (!ModelHelper.TrySplitFullConfiguration(stringTable, slnCfg, out string? solutionBuildType, out string? solutionPlatform)) + if (!ModelHelper.TrySplitFullConfiguration(stringTable, slnCfg, out string? solutionBuildType, + out string? solutionPlatform)) { return; } @@ -331,23 +348,34 @@ void ParseProjectConfigLine(SolutionModel solutionModel, string name, string val switch (lineType) { case ConfigLineType.ActiveCfg: - if (ModelHelper.TrySplitFullConfiguration(stringTable, value, out string? projectBuildType, out string? projectPlatform)) + if (ModelHelper.TrySplitFullConfiguration(stringTable, value, out string? projectBuildType, + out string? projectPlatform)) { - projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, projectBuildType)); - projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, solutionBuildType, solutionPlatform, projectPlatform)); + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, + solutionBuildType, solutionPlatform, projectBuildType)); + + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Platform, + solutionBuildType, solutionPlatform, projectPlatform)); } else if (!value.IsNullOrEmpty()) { // If the project configuration does not have a platform, just set the build type. - projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, solutionBuildType, solutionPlatform, value)); + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.BuildType, + solutionBuildType, solutionPlatform, value)); } break; + case ConfigLineType.Build: - projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Build, solutionBuildType, solutionPlatform, bool.TrueString)); + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Build, solutionBuildType, + solutionPlatform, bool.TrueString)); + break; + case ConfigLineType.Deploy: - projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, solutionPlatform, bool.TrueString)); + projectModel.AddProjectConfigurationRule(new ConfigurationRule(BuildDimension.Deploy, solutionBuildType, + solutionPlatform, bool.TrueString)); + break; } } @@ -365,7 +393,7 @@ void ParseProjectConfigLine(SolutionModel solutionModel, string name, string val public static IEnumerable GetSlnProperties(this SolutionModel solution) { Argument.ThrowIfNull(solution, nameof(solution)); - List slnProperties = new List((solution.Properties?.Count ?? 0) + 1); + List slnProperties = new((solution.Properties?.Count ?? 0) + 1); slnProperties.AddIfNotNull(GetSolutionConfigurationPlatforms(solution)); slnProperties.AddIfNotNull(GetProjectConfigurationPlatforms(solution)); @@ -403,7 +431,7 @@ not SectionName.ExtensibilityGlobals and size++; } - SolutionPropertyBag propertyBag = new SolutionPropertyBag( + SolutionPropertyBag propertyBag = new( SectionName.SolutionConfigurationPlatforms, PropertiesScope.PreLoad, capacity: size); @@ -433,16 +461,18 @@ not SectionName.ExtensibilityGlobals and return null; } - SolutionConfigurationMap cfgMap = new SolutionConfigurationMap(model); + SolutionConfigurationMap cfgMap = new(model); (string SlnKey, SolutionConfigurationMap.SolutionConfigIndex Index)[] indexer = cfgMap.CreateMatrixAnnotation(); int size = indexer.Length * model.SolutionProjects.Count * 3; - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.ProjectConfigurationPlatforms, PropertiesScope.PostLoad, size); + SolutionPropertyBag propertyBag = new(SectionName.ProjectConfigurationPlatforms, PropertiesScope.PostLoad, size); foreach (SolutionProjectModel projectModel in model.SolutionProjects) { // Gets the mapping of solution to project configurations - cfgMap.GetProjectConfigMap(projectModel, out SolutionConfigurationMap.SolutionToProjectMappings prjSlnCfgInfo, out bool writeConfigurations); + cfgMap.GetProjectConfigMap(projectModel, out SolutionConfigurationMap.SolutionToProjectMappings prjSlnCfgInfo, + out bool writeConfigurations); + if (!writeConfigurations) { continue; @@ -461,11 +491,12 @@ not SectionName.ExtensibilityGlobals and // Default project mapping in SLN was to use "Any CPU" string platform = - mapping.Platform == PlatformNames.AnyCPU ? PlatformNames.AnySpaceCPU : - mapping.Platform; + mapping.Platform == PlatformNames.AnyCPU ? PlatformNames.AnySpaceCPU : mapping.Platform; // If just the platform is missing, the project doesn't support platforms and only the build type should be written. - string prjCfgPlatString = platform == PlatformNames.Missing ? mapping.BuildType : $"{mapping.BuildType}|{platform}"; + string prjCfgPlatString = platform == PlatformNames.Missing + ? mapping.BuildType + : $"{mapping.BuildType}|{platform}"; if (mapping.BuildType != BuildTypeNames.Missing) { @@ -486,20 +517,25 @@ not SectionName.ExtensibilityGlobals and return propertyBag; - static void WriteProperty(SolutionPropertyBag propertyBag, string projectId, string slnCfg, string name, string value) => + static void WriteProperty(SolutionPropertyBag propertyBag, string projectId, string slnCfg, string name, + string value) => propertyBag.Add(projectId + '.' + slnCfg + name, value); } // HideSolutionNode property static SolutionPropertyBag GetSolutionProperties(SolutionModel solution) { - SolutionPropertyBag? additionalProperties = ModelHelper.FindByItemRef(solution.Properties, SectionName.SolutionProperties); - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.SolutionProperties, PropertiesScope.PreLoad, 1 + additionalProperties?.Count ?? 0) - { + SolutionPropertyBag? additionalProperties = solution.Properties.FindByItemRef(SectionName.SolutionProperties); + SolutionPropertyBag propertyBag = + new(SectionName.SolutionProperties, PropertiesScope.PreLoad, 1 + additionalProperties?.Count ?? 0) + { #pragma warning disable CS0618 // Type or member is obsolete - { SlnConstants.HideSolutionNode, solution.VisualStudioProperties.HideSolutionNode.GetValueOrDefault(false) ? "TRUE" : "FALSE" }, + { + SlnConstants.HideSolutionNode, + solution.VisualStudioProperties.HideSolutionNode.GetValueOrDefault(false) ? "TRUE" : "FALSE" + }, #pragma warning restore CS0618 // Type or member is obsolete - }; + }; if (additionalProperties is not null) { @@ -522,7 +558,7 @@ static SolutionPropertyBag GetSolutionProperties(SolutionModel solution) int count = solution.SolutionItems.Count(static x => x.Parent is not null); - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.NestedProjects, PropertiesScope.PreLoad, count); + SolutionPropertyBag propertyBag = new(SectionName.NestedProjects, PropertiesScope.PreLoad, count); foreach (SolutionItemModel item in solution.SolutionItems) { if (item.Parent is not null) @@ -539,17 +575,18 @@ static bool AnyNestedProjects(SolutionModel model) => static SolutionPropertyBag? GetExtensibilityGlobals(SolutionModel model) { - SolutionPropertyBag? additionalProperties = ModelHelper.FindByItemRef(model.Properties, SectionName.ExtensibilityGlobals); + SolutionPropertyBag? additionalProperties = model.Properties.FindByItemRef(SectionName.ExtensibilityGlobals); if (model.VisualStudioProperties.SolutionId is null) { return additionalProperties; } - SolutionPropertyBag propertyBag = new SolutionPropertyBag(SectionName.ExtensibilityGlobals, PropertiesScope.PostLoad, 1 + additionalProperties?.Count ?? 0) - { - { SlnConstants.SolutionGuid, (model.VisualStudioProperties.SolutionId ?? Guid.NewGuid()).ToSlnString() }, - }; + SolutionPropertyBag propertyBag = + new(SectionName.ExtensibilityGlobals, PropertiesScope.PostLoad, 1 + additionalProperties?.Count ?? 0) + { + { SlnConstants.SolutionGuid, (model.VisualStudioProperties.SolutionId ?? Guid.NewGuid()).ToSlnString() }, + }; if (additionalProperties is not null) { @@ -588,7 +625,8 @@ public static SolutionFolderModel CreateSlnFolder(this SolutionModel solution, s /// The parent solution folder to add the project to. /// The model for the new project. [Obsolete("This method is used for internal purposes, use SolutionModel.AddProject() instead.")] - public static SolutionProjectModel AddSlnProject(this SolutionModel solution, string filePath, Guid projectTypeId, SolutionFolderModel? folder) + public static SolutionProjectModel AddSlnProject(this SolutionModel solution, string filePath, Guid projectTypeId, + SolutionFolderModel? folder) { Argument.ThrowIfNull(solution, nameof(solution)); solution.ValidateInModel(folder); diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12ModelExtension.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12ModelExtension.cs index 564281346..a33fae406 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12ModelExtension.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/SlnV12/SlnV12ModelExtension.cs @@ -16,7 +16,7 @@ internal sealed class SlnV12ModelExtension(ISolutionSerializer serializer, SlnV1 public SlnV12ModelExtension(ISolutionSerializer serializer, SlnV12SerializerSettings settings, string? fullPath) : this(serializer, settings) { - this.SolutionFileFullPath = fullPath; + SolutionFileFullPath = fullPath; } /// diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/Keywords.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/Keywords.cs index 937cc9bdf..20e8b7aef 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/Keywords.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/Keywords.cs @@ -71,38 +71,38 @@ static Keywords() { StringToKeyword = new Lictionary( [ - new(nameof(Keyword.Solution), Keyword.Solution), - new(nameof(Keyword.Description), Keyword.Description), - new(nameof(Keyword.Version), Keyword.Version), - new(nameof(Keyword.Configurations), Keyword.Configurations), - new(nameof(Keyword.Folder), Keyword.Folder), - new(nameof(Keyword.Project), Keyword.Project), - new(nameof(Keyword.Id), Keyword.Id), - new(nameof(Keyword.Name), Keyword.Name), - new(nameof(Keyword.Path), Keyword.Path), - new(nameof(Keyword.Type), Keyword.Type), - new(nameof(Keyword.DefaultStartup), Keyword.DefaultStartup), - new(nameof(Keyword.DisplayName), Keyword.DisplayName), - new(nameof(Keyword.File), Keyword.File), - new(nameof(Keyword.BuildDependency), Keyword.BuildDependency), - new(nameof(Keyword.ProjectType), Keyword.ProjectType), - new(nameof(Keyword.TypeId), Keyword.TypeId), - new(nameof(Keyword.Extension), Keyword.Extension), - new(nameof(Keyword.BasedOn), Keyword.BasedOn), - new(nameof(Keyword.IsBuildable), Keyword.IsBuildable), - new(nameof(Keyword.SupportsPlatform), Keyword.SupportsPlatform), - new(nameof(Keyword.Configuration), Keyword.Configuration), - new(nameof(Keyword.Dimension), Keyword.Dimension), - new(nameof(Keyword.BuildType), Keyword.BuildType), - new(nameof(Keyword.Platform), Keyword.Platform), - new(nameof(Keyword.Build), Keyword.Build), - new(nameof(Keyword.Deploy), Keyword.Deploy), - new(nameof(Keyword.Property), Keyword.Property), - new(nameof(Keyword.Properties), Keyword.Properties), - new(nameof(Keyword.Scope), Keyword.Scope), - new(nameof(Keyword.PostLoad), Keyword.PostLoad), - new(nameof(Keyword.PreLoad), Keyword.PreLoad), - new(nameof(Keyword.Value), Keyword.Value), + new KeyValuePair(nameof(Keyword.Solution), Keyword.Solution), + new KeyValuePair(nameof(Keyword.Description), Keyword.Description), + new KeyValuePair(nameof(Keyword.Version), Keyword.Version), + new KeyValuePair(nameof(Keyword.Configurations), Keyword.Configurations), + new KeyValuePair(nameof(Keyword.Folder), Keyword.Folder), + new KeyValuePair(nameof(Keyword.Project), Keyword.Project), + new KeyValuePair(nameof(Keyword.Id), Keyword.Id), + new KeyValuePair(nameof(Keyword.Name), Keyword.Name), + new KeyValuePair(nameof(Keyword.Path), Keyword.Path), + new KeyValuePair(nameof(Keyword.Type), Keyword.Type), + new KeyValuePair(nameof(Keyword.DefaultStartup), Keyword.DefaultStartup), + new KeyValuePair(nameof(Keyword.DisplayName), Keyword.DisplayName), + new KeyValuePair(nameof(Keyword.File), Keyword.File), + new KeyValuePair(nameof(Keyword.BuildDependency), Keyword.BuildDependency), + new KeyValuePair(nameof(Keyword.ProjectType), Keyword.ProjectType), + new KeyValuePair(nameof(Keyword.TypeId), Keyword.TypeId), + new KeyValuePair(nameof(Keyword.Extension), Keyword.Extension), + new KeyValuePair(nameof(Keyword.BasedOn), Keyword.BasedOn), + new KeyValuePair(nameof(Keyword.IsBuildable), Keyword.IsBuildable), + new KeyValuePair(nameof(Keyword.SupportsPlatform), Keyword.SupportsPlatform), + new KeyValuePair(nameof(Keyword.Configuration), Keyword.Configuration), + new KeyValuePair(nameof(Keyword.Dimension), Keyword.Dimension), + new KeyValuePair(nameof(Keyword.BuildType), Keyword.BuildType), + new KeyValuePair(nameof(Keyword.Platform), Keyword.Platform), + new KeyValuePair(nameof(Keyword.Build), Keyword.Build), + new KeyValuePair(nameof(Keyword.Deploy), Keyword.Deploy), + new KeyValuePair(nameof(Keyword.Property), Keyword.Property), + new KeyValuePair(nameof(Keyword.Properties), Keyword.Properties), + new KeyValuePair(nameof(Keyword.Scope), Keyword.Scope), + new KeyValuePair(nameof(Keyword.PostLoad), Keyword.PostLoad), + new KeyValuePair(nameof(Keyword.PreLoad), Keyword.PreLoad), + new KeyValuePair(nameof(Keyword.Value), Keyword.Value), ], StringComparer.OrdinalIgnoreCase); @@ -113,7 +113,7 @@ static Keywords() } } - internal static string ToXmlString(this Keyword keyword) => KeywordToString[(int)keyword]; // let it throw + internal static string ToXmlString(this Keyword keyword) => KeywordToString[(int)keyword]; // let it throw internal static string ToXmlBool(this bool value) => value ? XmlTrue : XmlFalse; diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/LineInfoXmlDocument.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/LineInfoXmlDocument.cs index 381581493..629fb951c 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/LineInfoXmlDocument.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/LineInfoXmlDocument.cs @@ -15,21 +15,21 @@ internal sealed class LineInfoXmlDocument : XmlDocument public override XmlElement CreateElement(string? prefix, string localName, string? namespaceURI) { - return this.xmlLineInfo is not null && this.xmlLineInfo.HasLineInfo() ? - new LineInfoXmlElement(prefix, localName, namespaceURI, this, this.xmlLineInfo.LineNumber, this.xmlLineInfo.LinePosition) : - new LineInfoXmlElement(prefix, localName, namespaceURI, this); + return xmlLineInfo is not null && xmlLineInfo.HasLineInfo() + ? new LineInfoXmlElement(prefix, localName, namespaceURI, this, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition) + : new LineInfoXmlElement(prefix, localName, namespaceURI, this); } public override void Load(XmlReader reader) { - this.xmlLineInfo = reader as IXmlLineInfo; + xmlLineInfo = reader as IXmlLineInfo; try { base.Load(reader); } finally { - this.xmlLineInfo = null; + xmlLineInfo = null; } } @@ -46,9 +46,9 @@ internal LineInfoXmlElement(string? prefix, string localName, string? namespaceU internal LineInfoXmlElement(string? prefix, string localName, string? namespaceURI, XmlDocument doc, int line, int column) : base(prefix, localName, namespaceURI, doc) { - this.hasLineInfo = true; - this.LineNumber = line; - this.LinePosition = column; + hasLineInfo = true; + LineNumber = line; + LinePosition = column; } /// @@ -58,6 +58,6 @@ internal LineInfoXmlElement(string? prefix, string localName, string? namespaceU public int LinePosition { get; } /// - public bool HasLineInfo() => this.hasLineInfo; + public bool HasLineInfo() => hasLineInfo; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Reader.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Reader.cs index 37bb1744b..c3dda743f 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Reader.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Reader.cs @@ -9,7 +9,7 @@ namespace Fallout.Persistence.Solution.Serializer.Xml; internal sealed partial class SlnXmlSerializer { - private sealed partial class Reader + private sealed class Reader { private readonly string? fullPath; private readonly XmlDocument xmlDocument; @@ -21,13 +21,17 @@ internal Reader(string? fullPath, Stream readerStream) // We ideally want to preserver whitespace, but if this is on // we need to manually handle preserving all indenting and new lines // when elements are added or removed. - this.xmlDocument = new LineInfoXmlDocument() { PreserveWhitespace = true }; - this.xmlDocument.Load(readerStream); + xmlDocument = new LineInfoXmlDocument + { + PreserveWhitespace = true + }; + + xmlDocument.Load(readerStream); } internal SolutionModel Parse() { - SlnxFile slnxFile = new SlnxFile(this.xmlDocument, new SlnxSerializerSettings(), null, this.fullPath); + SlnxFile slnxFile = new(xmlDocument, new SlnxSerializerSettings(), null, fullPath); return slnxFile.ToModel(); } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Writer.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Writer.cs index 556fecc99..bb1eda7b6 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Writer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.Writer.cs @@ -25,14 +25,14 @@ internal static async Task SaveAsync( bool convertingFromSln = model.SerializerExtension is SlnV12ModelExtension; SlnxSerializerSettings xmlSerializerSettings = modelExtension?.Settings ?? - new SlnxSerializerSettings() - { - // For new documents want to do standard indentation. - PreserveWhitespace = false, - IndentChars = " ", - NewLine = Environment.NewLine, - TrimVisualStudioProperties = convertingFromSln, - }; + new SlnxSerializerSettings + { + // For new documents want to do standard indentation. + PreserveWhitespace = false, + IndentChars = " ", + NewLine = Environment.NewLine, + TrimVisualStudioProperties = convertingFromSln, + }; if (xmlSerializerSettings.TrimVisualStudioProperties == true) { @@ -52,9 +52,9 @@ internal static async Task SaveAsync( _ = root.ApplyModel(model); // Always use UTF-8 without BOM - UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + UTF8Encoding encoding = new(encoderShouldEmitUTF8Identifier: false); - using (MemoryStream memoryStream = new MemoryStream(10 * 1024)) + using (MemoryStream memoryStream = new(10 * 1024)) using (TextWriter textWriter = new StreamWriter(memoryStream, encoding)) using (XmlWriter xmlWriter = CreateXmlWriter(xmlSerializerSettings, textWriter)) { @@ -77,7 +77,7 @@ internal static async Task SaveAsync( static XmlWriter CreateXmlWriter(SlnxSerializerSettings settings, TextWriter writer) { - XmlWriterSettings xmlWriterSettings = new XmlWriterSettings() + XmlWriterSettings xmlWriterSettings = new() { Async = true, OmitXmlDeclaration = true, @@ -109,9 +109,13 @@ static XmlWriter CreateXmlWriter(SlnxSerializerSettings settings, TextWriter wri } } - static SlnxFile CreateNewSlnFile(string? fullPath, SlnxSerializerSettings xmlSerializerSettings, StringTable stringTable) + static SlnxFile CreateNewSlnFile(string? fullPath, SlnxSerializerSettings xmlSerializerSettings, + StringTable stringTable) { - XmlDocument xmlDocument = new LineInfoXmlDocument() { PreserveWhitespace = xmlSerializerSettings.PreserveWhitespace ?? false, }; + XmlDocument xmlDocument = new LineInfoXmlDocument + { + PreserveWhitespace = xmlSerializerSettings.PreserveWhitespace ?? false, + }; XmlElement slnElement = xmlDocument.CreateElement(Keyword.Solution.ToXmlString()); _ = xmlDocument.AppendChild(slnElement); diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.cs index 171cdfa50..94b5d532f 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXMLSerializer.cs @@ -27,7 +27,7 @@ public SlnXmlSerializer() /// public override ISerializerModelExtension CreateModelExtension() { - return this.CreateModelExtension(new SlnxSerializerSettings() + return CreateModelExtension(new SlnxSerializerSettings { // For new documents want to do standard indentation. PreserveWhitespace = false, @@ -42,13 +42,15 @@ public override ISerializerModelExtension CreateModelExtension(SlnxSerializerSet return new SlnXmlModelExtension(this, settings); } - private protected override Task ReadModelAsync(string? fullPath, Stream reader, CancellationToken cancellationToken) + private protected override Task ReadModelAsync(string? fullPath, Stream reader, + CancellationToken cancellationToken) { - Reader parser = new Reader(fullPath, reader); + Reader parser = new(fullPath, reader); return Task.FromResult(parser.Parse()); } - private protected override Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, CancellationToken cancellationToken) + private protected override Task WriteModelAsync(string? fullPath, SolutionModel model, Stream writerStream, + CancellationToken cancellationToken) { return Writer.SaveAsync(fullPath, model, writerStream); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXmlModelExtension.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXmlModelExtension.cs index fae3d402f..71b46c2c2 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXmlModelExtension.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/SlnXmlModelExtension.cs @@ -17,7 +17,7 @@ internal sealed class SlnXmlModelExtension(ISolutionSerializer serializer, SlnxS internal SlnXmlModelExtension(ISolutionSerializer serializer, SlnxSerializerSettings settings, SlnxFile root) : this(serializer, settings) { - this.Root = root; + Root = root; } /// @@ -27,11 +27,11 @@ internal SlnXmlModelExtension(ISolutionSerializer serializer, SlnxSerializerSett public required SlnxSerializerSettings Settings { get; init; } = settings; /// - public bool Tarnished => this.Root?.Tarnished ?? false; + public bool Tarnished => Root?.Tarnished ?? false; internal SlnxFile? Root { get; init; } - internal string? SolutionFileFullPath => this.Root?.FullPath; + internal string? SolutionFileFullPath => Root?.FullPath; - internal Version? Version => this.Root?.FileVersion; + internal Version? Version => Root?.FileVersion; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemConfigurationRulesList.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemConfigurationRulesList.cs index 6339d6826..ce983cd0d 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemConfigurationRulesList.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemConfigurationRulesList.cs @@ -25,17 +25,21 @@ internal readonly void Add(XmlConfiguration configuration) switch (configuration) { case XmlConfigurationBuildType buildType: - this.buildTypeRules.Add(buildType); + buildTypeRules.Add(buildType); break; + case XmlConfigurationPlatform platform: - this.platformRules.Add(platform); + platformRules.Add(platform); break; + case XmlConfigurationBuild build: - this.buildRules.Add(build); + buildRules.Add(build); break; + case XmlConfigurationDeploy deploy: - this.deployRules.Add(deploy); + deployRules.Add(deploy); break; + default: throw new InvalidOperationException(); } @@ -45,9 +49,10 @@ internal readonly void Add(XmlConfiguration configuration) { return typeof(TDecorator).Name switch { - nameof(XmlConfigurationBuildType) or nameof(XmlConfiguration) => this.platformRules.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlConfigurationPlatform) => this.buildRules.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlConfigurationBuild) => this.deployRules.FirstOrDefault(), + nameof(XmlConfigurationBuildType) or nameof(XmlConfiguration) => platformRules.FirstOrDefault() ?? + FindNextDecorator(), + nameof(XmlConfigurationPlatform) => buildRules.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlConfigurationBuild) => deployRules.FirstOrDefault(), nameof(XmlConfigurationDeploy) => null, _ => null, }; @@ -55,7 +60,8 @@ internal readonly void Add(XmlConfiguration configuration) internal readonly XmlDecorator? FirstOrDefault() { - return this.buildTypeRules.FirstOrDefault() ?? this.platformRules.FirstOrDefault() ?? this.buildRules.FirstOrDefault() ?? (XmlDecorator?)this.deployRules.FirstOrDefault(); + return buildTypeRules.FirstOrDefault() ?? platformRules.FirstOrDefault() ?? + buildRules.FirstOrDefault() ?? (XmlDecorator?)deployRules.FirstOrDefault(); } internal bool ApplyModelToXml(XmlContainer xmlContainer, IReadOnlyList? configurationRules) @@ -63,13 +69,18 @@ internal bool ApplyModelToXml(XmlContainer xmlContainer, IReadOnlyList(XmlContainer xmlContainer, IReadOnlyList configurationRules, BuildDimension dimension, Keyword dimensionElementName, ref ItemRefList configurations) + static bool ApplyModelToXml(XmlContainer xmlContainer, IReadOnlyList configurationRules, + BuildDimension dimension, Keyword dimensionElementName, ref ItemRefList configurations) where T : XmlConfiguration { List<(string ItemRef, ConfigurationRule Item)> dimensionRules = configurationRules.WhereToList( @@ -81,34 +92,35 @@ static bool ApplyModelToXml(XmlContainer xmlContainer, IReadOnlyList newConfiguration.ApplyModelToXml(modelConfiguration)); + applyModelToXml: static (newConfiguration, modelConfiguration) => + newConfiguration.ApplyModelToXml(modelConfiguration)); } } internal readonly List ToModel() { - List rules = new List( - this.buildTypeRules.ItemsCount + - this.platformRules.ItemsCount + - this.buildRules.ItemsCount + - this.deployRules.ItemsCount); + List rules = new( + buildTypeRules.ItemsCount + + platformRules.ItemsCount + + buildRules.ItemsCount + + deployRules.ItemsCount); - foreach (XmlConfiguration configuration in this.buildTypeRules.GetItems()) + foreach (XmlConfiguration configuration in buildTypeRules.GetItems()) { AddRule(rules, configuration); } - foreach (XmlConfiguration configuration in this.platformRules.GetItems()) + foreach (XmlConfiguration configuration in platformRules.GetItems()) { AddRule(rules, configuration); } - foreach (XmlConfiguration configuration in this.buildRules.GetItems()) + foreach (XmlConfiguration configuration in buildRules.GetItems()) { AddRule(rules, configuration); } - foreach (XmlConfiguration configuration in this.deployRules.GetItems()) + foreach (XmlConfiguration configuration in deployRules.GetItems()) { AddRule(rules, configuration); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemRefList`1.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemRefList`1.cs index 303797062..468ac55ca 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemRefList`1.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/ItemRefList`1.cs @@ -18,7 +18,7 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; internal readonly struct ItemRefList(bool ignoreCase) where T : XmlDecorator, IItemRefDecorator { - private readonly Lictionary items = new Lictionary(0, ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + private readonly Lictionary items = new(0, ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); public ItemRefList() : this(ignoreCase: false) @@ -27,36 +27,38 @@ public ItemRefList() internal readonly bool IgnoreCase { get; } = ignoreCase; - internal readonly int ItemsCount => this.items.Count; + internal readonly int ItemsCount => items.Count; internal readonly void Add(T item) { // Missing Name attribute. if (!item.IsValid() || item.ItemRef is null) { - throw SolutionException.Create(string.Format(Errors.InvalidItemRef_Args2, item.ItemRefAttribute, item.ElementName), item, SolutionErrorType.InvalidItemRef); + throw SolutionException.Create(string.Format(Errors.InvalidItemRef_Args2, item.ItemRefAttribute, item.ElementName), + item, SolutionErrorType.InvalidItemRef); } else { - if (!this.items.TryAdd(item.ItemRef, item)) + if (!items.TryAdd(item.ItemRef, item)) { // Duplicate Name attribute. - throw SolutionException.Create(string.Format(Errors.DuplicateItemRef_Args2, item.ItemRef, item.ElementName), item, SolutionErrorType.DuplicateItemRef); + throw SolutionException.Create(string.Format(Errors.DuplicateItemRef_Args2, item.ItemRef, item.ElementName), item, + SolutionErrorType.DuplicateItemRef); } } } - internal readonly T? FirstOrDefault() => this.items.Count > 0 ? this.items[0] : null; + internal readonly T? FirstOrDefault() => items.Count > 0 ? items[0] : null; // Finds the item that would be immediately after the given item ref. internal readonly bool TryFindNext(string itemRef, out T? item) { - return this.items.TryFindNext(itemRef, out item); + return items.TryFindNext(itemRef, out item); } internal readonly void Remove(T item) { - _ = this.items.Remove(item.ItemRef); + _ = items.Remove(item.ItemRef); } internal readonly EnumForwarder GetItems() @@ -66,7 +68,7 @@ internal readonly EnumForwarder GetItems() internal ref struct EnumForwarder(ItemRefList me) { - public readonly ItemsEnumerator GetEnumerator() => new ItemsEnumerator(me.items.GetEnumerator()); + public readonly ItemsEnumerator GetEnumerator() => new(me.items.GetEnumerator()); } internal ref struct ItemsEnumerator(List>.Enumerator enumerator) @@ -78,14 +80,14 @@ internal ref struct ItemsEnumerator(List>.Enumerator enu private sealed class OrdinalComparer : IComparer { - internal static readonly OrdinalComparer Instance = new OrdinalComparer(); + internal static readonly OrdinalComparer Instance = new(); public int Compare(T? x, T? y) => StringComparer.Ordinal.Compare(x?.ItemRef, y?.ItemRef); } private sealed class OrdinalIgnoreCaseComparer : IComparer { - internal static readonly OrdinalIgnoreCaseComparer Instance = new OrdinalIgnoreCaseComparer(); + internal static readonly OrdinalIgnoreCaseComparer Instance = new(); public int Compare(T? x, T? y) => StringComparer.OrdinalIgnoreCase.Compare(x?.ItemRef, y?.ItemRef); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/SlnxFile.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/SlnxFile.cs index a02d850a5..edcf4e24a 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/SlnxFile.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/SlnxFile.cs @@ -22,26 +22,29 @@ internal SlnxFile( StringTable? stringTable, string? fullPath) { - this.Document = xmlDocument; - this.FullPath = fullPath; - this.StringTable = stringTable ?? new StringTable().WithSolutionConstants(); + Document = xmlDocument; + FullPath = fullPath; + StringTable = stringTable ?? new StringTable().WithSolutionConstants(); - XmlElement? xmlSolution = this.Document.DocumentElement; + XmlElement? xmlSolution = Document.DocumentElement; if (xmlSolution is not null && Keywords.ToKeyword(xmlSolution.Name) == Keyword.Solution) { - this.Solution = new XmlSolution(this, xmlSolution); - this.Solution.UpdateFromXml(); + Solution = new XmlSolution(this, xmlSolution); + Solution.UpdateFromXml(); // This is a model part, but needs to be calculated before it can properly turn into a model. // These are used to calculate the actual project types from a project's Type attribute. - this.ProjectTypes = this.Solution.GetProjectTypeTable(); + ProjectTypes = Solution.GetProjectTypeTable(); } else { - throw new SolutionException(Errors.NotSolution, SolutionErrorType.NotSolution) { File = this.FullPath }; + throw new SolutionException(Errors.NotSolution, SolutionErrorType.NotSolution) + { + File = FullPath + }; } - this.SerializationSettings = this.GetDefaultSerializationSettings(serializationSettings); + SerializationSettings = GetDefaultSerializationSettings(serializationSettings); } internal string? FullPath { get; } @@ -60,15 +63,19 @@ internal SlnxFile( internal ProjectTypeTable ProjectTypes { get; private set; } // Keep track of user project and file paths to preserve the user's path separators. - internal Dictionary UserPaths { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal Dictionary UserPaths { get; } = new(StringComparer.OrdinalIgnoreCase); internal bool Tarnished { get; private set; } internal SolutionModel ToModel() { - this.UserPaths.Clear(); - SolutionModel model = this.Solution?.ToModel() ?? new SolutionModel() { StringTable = this.StringTable }; - model.SerializerExtension = new SlnXmlModelExtension(SolutionSerializers.SlnXml, this.SerializationSettings, root: this); + UserPaths.Clear(); + SolutionModel model = Solution?.ToModel() ?? new SolutionModel + { + StringTable = StringTable + }; + + model.SerializerExtension = new SlnXmlModelExtension(SolutionSerializers.SlnXml, SerializationSettings, root: this); return model; } @@ -77,9 +84,9 @@ internal SolutionModel ToModel() /// internal string ConvertToUserPath(string projectPath) { - return this.UserPaths.TryGetValue(projectPath, out string? userProjectPath) ? - userProjectPath : - PathExtensions.ConvertModelToForwardSlashPath(projectPath); + return UserPaths.TryGetValue(projectPath, out string? userProjectPath) + ? userProjectPath + : PathExtensions.ConvertModelToForwardSlashPath(projectPath); } /// @@ -90,26 +97,26 @@ internal string ConvertToUserPath(string projectPath) /// internal bool ApplyModel(SolutionModel model) { - this.ProjectTypes = model.ProjectTypeTable; + ProjectTypes = model.ProjectTypeTable; bool modified = false; - if (this.Solution is null) + if (Solution is null) { // Make the solution element the root element of the document. - XmlElement xmlSolution = this.Document.CreateElement(Keyword.Solution.ToXmlString()); - _ = this.Document.AppendChild(xmlSolution); - this.Solution = new XmlSolution(this, xmlSolution); - this.Solution.UpdateFromXml(); + XmlElement xmlSolution = Document.CreateElement(Keyword.Solution.ToXmlString()); + _ = Document.AppendChild(xmlSolution); + Solution = new XmlSolution(this, xmlSolution); + Solution.UpdateFromXml(); modified = true; } - modified |= this.Solution.ApplyModelToXml(model); + modified |= Solution.ApplyModelToXml(model); return modified; } internal string ToXmlString() { - return this.Document.OuterXml; + return Document.OuterXml; } // Fill out default values. @@ -118,8 +125,8 @@ private SlnxSerializerSettings GetDefaultSerializationSettings(SlnxSerializerSet string newLineChars = Environment.NewLine; string newIndentChars = " "; if ((inputSettings.IndentChars is null || inputSettings.NewLine is null) && - this.Solution is not null && - this.Solution.TryGetFormatting(out StringSpan newLine, out StringSpan indent)) + Solution is not null && + Solution.TryGetFormatting(out StringSpan newLine, out StringSpan indent)) { newLineChars = newLine.ToString(); newIndentChars = indent.ToString(); @@ -127,7 +134,7 @@ this.Solution is not null && return inputSettings with { - PreserveWhitespace = inputSettings.PreserveWhitespace ?? this.Document.PreserveWhitespace, + PreserveWhitespace = inputSettings.PreserveWhitespace ?? Document.PreserveWhitespace, IndentChars = inputSettings.IndentChars ?? newIndentChars, NewLine = inputSettings.NewLine ?? newLineChars, }; diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildDependency.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildDependency.cs index 4d380743a..28fba2865 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildDependency.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildDependency.cs @@ -14,5 +14,5 @@ internal sealed class XmlBuildDependency(SlnxFile root, XmlElement element) : { public Keyword ItemRefAttribute => Keyword.Project; - internal string Project => this.ItemRef; + internal string Project => ItemRef; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildType.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildType.cs index 853de2c89..68b3a52bb 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildType.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlBuildType.cs @@ -14,5 +14,5 @@ internal sealed class XmlBuildType(SlnxFile root, XmlElement element) : { public Keyword ItemRefAttribute => Keyword.Name; - internal string Name => this.ItemRef; + internal string Name => ItemRef; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfiguration.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfiguration.cs index a130df9bb..5431d6354 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfiguration.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfiguration.cs @@ -19,14 +19,14 @@ internal abstract class XmlConfiguration(SlnxFile root, XmlElement element, Keyw internal string Solution { - get => this.GetXmlAttribute(Keyword.Solution) ?? string.Empty; - set => this.UpdateXmlAttribute(Keyword.Solution, value); + get => GetXmlAttribute(Keyword.Solution) ?? string.Empty; + set => UpdateXmlAttribute(Keyword.Solution, value); } internal string Project { - get => this.GetXmlAttribute(Keyword.Project) ?? string.Empty; - set => this.UpdateXmlAttribute(Keyword.Project, value); + get => GetXmlAttribute(Keyword.Project) ?? string.Empty; + set => UpdateXmlAttribute(Keyword.Project, value); } private protected override bool AllowEmptyItemRef => true; @@ -35,11 +35,11 @@ internal string Project internal ConfigurationRule? ToModel() { - BuildDimension dimension = this.Dimension; + BuildDimension dimension = Dimension; // Set default value for build rule to 'true' and deploy rule to 'false'. string projectValue = - this.Project.NullIfEmpty() ?? + Project.NullIfEmpty() ?? dimension switch { BuildDimension.Build or BuildDimension.Deploy => bool.TrueString, @@ -51,10 +51,12 @@ internal string Project throw SolutionException.Create(Errors.MissingProjectValue, this, SolutionErrorType.MissingProjectValue); } - if (!ModelHelper.TrySplitFullConfiguration(this.Root.StringTable, this.Solution, out string? solutionBuildType, out string? solutionPlatform) && - !this.Solution.IsNullOrEmpty()) + if (!ModelHelper.TrySplitFullConfiguration(Root.StringTable, Solution, out string? solutionBuildType, + out string? solutionPlatform) && + !Solution.IsNullOrEmpty()) { - throw SolutionException.Create(string.Format(Errors.InvalidConfiguration_Args1, this.Solution), this, SolutionErrorType.InvalidConfiguration); + throw SolutionException.Create(string.Format(Errors.InvalidConfiguration_Args1, Solution), this, + SolutionErrorType.InvalidConfiguration); } if (solutionBuildType is BuildTypeNames.All or null) @@ -72,7 +74,7 @@ internal string Project dimension, solutionBuildType: solutionBuildType, solutionPlatform: solutionPlatform, - projectValue: this.GetTableString(projectValue)); + projectValue: GetTableString(projectValue)); } #endregion @@ -83,16 +85,18 @@ internal bool ApplyModelToXml(ConfigurationRule configurationRule) string value = configurationRule.Dimension switch { // For build or deploy the default value is 'true'. Use lowercase 'false' to match the XML boolean. - BuildDimension.Build or BuildDimension.Deploy => bool.Parse(configurationRule.ProjectValue) ? string.Empty : Keywords.XmlFalse, + BuildDimension.Build or BuildDimension.Deploy => bool.Parse(configurationRule.ProjectValue) + ? string.Empty + : Keywords.XmlFalse, _ => configurationRule.ProjectValue, }; - if (StringComparer.Ordinal.Equals(this.Project, value)) + if (StringComparer.Ordinal.Equals(Project, value)) { return false; } - this.Project = value; + Project = value; return true; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfigurations.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfigurations.cs index 4fa1a5d74..b6603113e 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfigurations.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlConfigurations.cs @@ -13,9 +13,9 @@ internal sealed class XmlConfigurations(SlnxFile root, XmlElement element) : XmlContainer(root, element, Keyword.Configurations), IItemRefDecorator { - private ItemRefList buildType = new ItemRefList(ignoreCase: true); - private ItemRefList platforms = new ItemRefList(ignoreCase: true); - private ItemRefList projectTypes = new ItemRefList(); + private ItemRefList buildType = new(ignoreCase: true); + private ItemRefList platforms = new(ignoreCase: true); + private ItemRefList projectTypes = new(); public Keyword ItemRefAttribute => Keyword.Configurations; @@ -32,9 +32,9 @@ private protected override string RawItemRef { return elementName switch { - Keyword.Platform => new XmlPlatform(this.Root, element), - Keyword.BuildType => new XmlBuildType(this.Root, element), - Keyword.ProjectType => new XmlProjectType(this.Root, element), + Keyword.Platform => new XmlPlatform(Root, element), + Keyword.BuildType => new XmlBuildType(Root, element), + Keyword.ProjectType => new XmlProjectType(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -45,13 +45,15 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlPlatform platform: - this.platforms.Add(platform); + platforms.Add(platform); break; + case XmlBuildType buildType: this.buildType.Add(buildType); break; + case XmlProjectType projectType: - this.projectTypes.Add(projectType); + projectTypes.Add(projectType); break; } @@ -63,8 +65,8 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) { return typeof(TDecorator).Name switch { - nameof(XmlBuildType) => this.platforms.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlPlatform) => this.projectTypes.FirstOrDefault(), + nameof(XmlBuildType) => platforms.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlPlatform) => projectTypes.FirstOrDefault(), nameof(XmlProjectType) => null, _ => null, }; @@ -74,7 +76,7 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) internal void AddToModel(SolutionModel solution) { - foreach (XmlPlatform platform in this.platforms.GetItems()) + foreach (XmlPlatform platform in platforms.GetItems()) { try { @@ -104,17 +106,15 @@ internal void AddToModel(SolutionModel solution) /// internal ProjectTypeTable? GetProjectTypeTable() { - List declaredTypes = new List(this.projectTypes.ItemsCount); - foreach (XmlProjectType projectType in this.projectTypes.GetItems()) + List declaredTypes = new(projectTypes.ItemsCount); + foreach (XmlProjectType projectType in projectTypes.GetItems()) { declaredTypes.Add(projectType.ToModel()); } try { - return declaredTypes.Count > 0 ? - new ProjectTypeTable(declaredTypes) : - null; + return declaredTypes.Count > 0 ? new ProjectTypeTable(declaredTypes) : null; } catch (SolutionException ex) { @@ -130,21 +130,22 @@ internal bool ApplyModelToXml(SolutionModel modelSolution) bool modified = false; // BuildTypes - modified |= this.ApplyModelItemsToXml( + modified |= ApplyModelItemsToXml( itemRefs: modelSolution.IsBuildTypeImplicit() ? null : modelSolution.BuildTypes, - decoratorItems: ref this.buildType, + decoratorItems: ref buildType, decoratorElementName: Keyword.BuildType); // Platforms - modified |= this.ApplyModelItemsToXml( + modified |= ApplyModelItemsToXml( itemRefs: modelSolution.IsPlatformImplicit() ? null : modelSolution.Platforms, - decoratorItems: ref this.platforms, + decoratorItems: ref platforms, decoratorElementName: Keyword.Platform); // Project Types - modified |= this.ApplyModelItemsToXml( - modelItems: modelSolution.ProjectTypes.ToList(type => (ItemRef: XmlProjectType.GetItemRef(type.Name, type.Extension, type.ProjectTypeId), Item: type)), - ref this.projectTypes, + modified |= ApplyModelItemsToXml( + modelItems: modelSolution.ProjectTypes.ToList(type => + (ItemRef: XmlProjectType.GetItemRef(type.Name, type.Extension, type.ProjectTypeId), Item: type)), + ref projectTypes, Keyword.ProjectType, applyModelToXml: static (newProjectTypes, newValue) => newProjectTypes.ApplyModelToXml(newValue)); diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs index 7f4dcf79e..54bbf0cde 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.ApplyModel.cs @@ -19,8 +19,8 @@ internal List DebugChildNodes { get { - List nodeDescriptions = new(this.XmlElement.ChildNodes.Count); - foreach (XmlNode xmlNode in this.XmlElement.ChildNodes) + List nodeDescriptions = new(XmlElement.ChildNodes.Count); + foreach (XmlNode xmlNode in XmlElement.ChildNodes) { string value = xmlNode.Value ?? string.Empty; string nodeDescription = xmlNode switch @@ -38,6 +38,7 @@ internal List DebugChildNodes _ => $"Unexpected: {xmlNode.GetType().Name} {value}", }; + nodeDescriptions.Add(nodeDescription); } @@ -68,7 +69,7 @@ internal bool ApplyModelItemsToXml( bool modified = false; modelItems ??= []; - ListBuilderStruct toRemove = new ListBuilderStruct(); + ListBuilderStruct toRemove = new(); #if DEBUG @@ -76,16 +77,24 @@ internal bool ApplyModelItemsToXml( switch (decoratorElementName) { case Keyword.BuildType: break; + case Keyword.Platform: break; + case Keyword.Property: break; + case Keyword.Project: break; + case Keyword.Folder: break; + case Keyword.Properties: break; + case Keyword.File: break; + case Keyword.Build: break; + case Keyword.Deploy: break; + case Keyword.ProjectType: break; - default: break; } #endif @@ -116,7 +125,7 @@ internal bool ApplyModelItemsToXml( // Remove elements that are no longer in the model. foreach (TDecorator decoratorItem in toRemove) { - this.RemoveXmlChild(decoratorItem); + RemoveXmlChild(decoratorItem); decoratorItems.Remove(decoratorItem); } @@ -125,9 +134,11 @@ internal bool ApplyModelItemsToXml( foreach ((string itemRef, TModelItem modelItem) in modelItems) { // Find position to insert before based on general areas and alphabetical order. - XmlDecorator? insertBefore = decoratorItems.TryFindNext(itemRef, out TDecorator? insertBeforeLocal) ? insertBeforeLocal : this.FindNextDecorator(); + XmlDecorator? insertBefore = decoratorItems.TryFindNext(itemRef, out TDecorator? insertBeforeLocal) + ? insertBeforeLocal + : FindNextDecorator(); - TDecorator newDecorator = (TDecorator)this.CreateAndAddChild(decoratorElementName, itemRef, insertBefore); + TDecorator newDecorator = (TDecorator)CreateAndAddChild(decoratorElementName, itemRef, insertBefore); _ = applyModelToXml?.Invoke(newDecorator, modelItem); modified = true; } @@ -152,8 +163,11 @@ static int IndexOfItemRef(List<(string ItemRef, TModelItem Item)> modelItems, st } // Used to sort the model items so they are inserted in sorted order. - static int ComparisonOrdinal((string ItemRef, TModelItem Item) a, (string ItemRef, TModelItem Item) b) => StringComparer.Ordinal.Compare(a.ItemRef, b.ItemRef); - static int ComparisonOrdinalIgnoreCase((string ItemRef, TModelItem Item) a, (string ItemRef, TModelItem Item) b) => StringComparer.OrdinalIgnoreCase.Compare(a.ItemRef, b.ItemRef); + static int ComparisonOrdinal((string ItemRef, TModelItem Item) a, (string ItemRef, TModelItem Item) b) => + StringComparer.Ordinal.Compare(a.ItemRef, b.ItemRef); + + static int ComparisonOrdinalIgnoreCase((string ItemRef, TModelItem Item) a, (string ItemRef, TModelItem Item) b) => + StringComparer.OrdinalIgnoreCase.Compare(a.ItemRef, b.ItemRef); } // Helper for updates that only update their itemRefs and don't need to make other changes. @@ -164,7 +178,7 @@ internal bool ApplyModelItemsToXml( where TDecorator : XmlDecorator, IItemRefDecorator { List<(string ItemRef, string Item)>? modelItems = itemRefs?.ToList(itemRefs => (ItemRef: itemRefs, Item: itemRefs)); - return this.ApplyModelItemsToXml(modelItems, ref decoratorItems, decoratorElementName, applyModelToXml: null); + return ApplyModelItemsToXml(modelItems, ref decoratorItems, decoratorElementName, applyModelToXml: null); } #region Manipulate XML @@ -183,10 +197,10 @@ private protected void RemoveXmlChild(XmlDecorator? childToRemove) _ = node.ParentNode?.RemoveChild(node); } - if (!this.XmlElement.ChildElements().Any()) + if (!XmlElement.ChildElements().Any()) { // This clears out all child nodes and collapses the element to a self-closing tag. - this.XmlElement.IsEmpty = true; + XmlElement.IsEmpty = true; } } @@ -196,43 +210,44 @@ private protected void RemoveXmlChild(XmlDecorator? childToRemove) /// private protected XmlDecorator CreateAndAddChild(Keyword type, string? itemRef, XmlDecorator? insertBefore) { - XmlElement newElement = this.CreateXmlChild(type, insertBefore); - XmlDecorator? newDecorator = this.CreateChildDecorator(newElement, itemRef, validateItemRef: true); + XmlElement newElement = CreateXmlChild(type, insertBefore); + XmlDecorator? newDecorator = CreateChildDecorator(newElement, itemRef, validateItemRef: true); return newDecorator ?? throw new InvalidOperationException("Requested item doesn't not created by child factory."); } private XmlElement CreateXmlChild(Keyword type, XmlDecorator? insertBefore) { - XmlElement newElement = this.XmlElement.OwnerDocument.CreateElement(type.ToXmlString()); + XmlElement newElement = XmlElement.OwnerDocument.CreateElement(type.ToXmlString()); - return insertBefore is null ? - this.AppendChildWithWhitespace(newElement) : - this.InsertBeforeWithWhitespace(newElement, insertBefore); + return insertBefore is null + ? AppendChildWithWhitespace(newElement) + : InsertBeforeWithWhitespace(newElement, insertBefore); } private XmlElement AppendChildWithWhitespace(XmlElement newElement) { - if (this.Root.SerializationSettings.PreserveWhitespace == true) + if (Root.SerializationSettings.PreserveWhitespace == true) { // This is the whitespace that goes after the last child element. If it exists reuse it, otherwise this // indent should be the same at the parent level. - if (this.XmlElement.LastChild is not XmlWhitespace afterWhitespace) + if (XmlElement.LastChild is not XmlWhitespace afterWhitespace) { - afterWhitespace = this.XmlElement.OwnerDocument.CreateWhitespace(this.GetNewLineAndIndent().ToString()); - _ = this.XmlElement.AppendChild(afterWhitespace); + afterWhitespace = XmlElement.OwnerDocument.CreateWhitespace(GetNewLineAndIndent().ToString()); + _ = XmlElement.AppendChild(afterWhitespace); } - _ = this.XmlElement.InsertBefore(newElement, afterWhitespace); + _ = XmlElement.InsertBefore(newElement, afterWhitespace); // This is the new line whitespace between this and the previous element. // Just add an indent to the parent level. - XmlWhitespace beforeWhitespace = this.XmlElement.OwnerDocument.CreateWhitespace( - this.GetNewLineAndIndent().ToString() + this.Root.SerializationSettings.IndentChars); - _ = this.XmlElement.InsertBefore(beforeWhitespace, newElement); + XmlWhitespace beforeWhitespace = XmlElement.OwnerDocument.CreateWhitespace( + GetNewLineAndIndent().ToString() + Root.SerializationSettings.IndentChars); + + _ = XmlElement.InsertBefore(beforeWhitespace, newElement); } else { - _ = this.XmlElement.AppendChild(newElement); + _ = XmlElement.AppendChild(newElement); } return newElement; @@ -240,21 +255,22 @@ private XmlElement AppendChildWithWhitespace(XmlElement newElement) private XmlElement InsertBeforeWithWhitespace(XmlElement newElement, XmlDecorator insertBefore) { - if (this.Root.SerializationSettings.PreserveWhitespace == true) + if (Root.SerializationSettings.PreserveWhitespace == true) { XmlNode insertBeforeNode = insertBefore.GetFirstTrivia(); - _ = this.XmlElement.InsertBefore(newElement, insertBeforeNode); + _ = XmlElement.InsertBefore(newElement, insertBeforeNode); // This is the new line whitespace between this and the previous element. // Just add an indent to the parent level. - XmlWhitespace beforeWhitespace = this.XmlElement.OwnerDocument.CreateWhitespace( - this.GetNewLineAndIndent().ToString() + this.Root.SerializationSettings.IndentChars); - _ = this.XmlElement.InsertBefore(beforeWhitespace, newElement); + XmlWhitespace beforeWhitespace = XmlElement.OwnerDocument.CreateWhitespace( + GetNewLineAndIndent().ToString() + Root.SerializationSettings.IndentChars); + + _ = XmlElement.InsertBefore(beforeWhitespace, newElement); } else { - _ = this.XmlElement.InsertBefore(newElement, insertBefore.XmlElement); + _ = XmlElement.InsertBefore(newElement, insertBefore.XmlElement); } return newElement; diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.cs index 84514a44c..b914239cf 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainer.cs @@ -42,9 +42,9 @@ internal override void UpdateFromXml() { base.UpdateFromXml(); - foreach (XmlElement childXmlElement in this.XmlElement.ChildElements()) + foreach (XmlElement childXmlElement in XmlElement.ChildElements()) { - _ = this.CreateChildDecorator(childXmlElement); + _ = CreateChildDecorator(childXmlElement); } } @@ -54,7 +54,7 @@ internal override void UpdateFromXml() /// private XmlDecorator? CreateChildDecorator(XmlElement xmlElement, string? itemRef = null, bool validateItemRef = false) { - XmlDecorator? xmlDecorator = this.ChildDecoratorFactory(xmlElement, Keywords.ToKeyword(xmlElement.Name)); + XmlDecorator? xmlDecorator = ChildDecoratorFactory(xmlElement, Keywords.ToKeyword(xmlElement.Name)); if (xmlDecorator is null) { return null; @@ -67,11 +67,12 @@ internal override void UpdateFromXml() if (validateItemRef && !xmlDecorator.IsValid()) { - throw new SolutionArgumentException(string.Format(Errors.InvalidItemRef_Args2, itemRef, xmlDecorator.ElementName), SolutionErrorType.InvalidItemRef); + throw new SolutionArgumentException(string.Format(Errors.InvalidItemRef_Args2, itemRef, xmlDecorator.ElementName), + SolutionErrorType.InvalidItemRef); } xmlDecorator.UpdateFromXml(); - this.OnNewChildDecoratorAdded(xmlDecorator); + OnNewChildDecoratorAdded(xmlDecorator); return xmlDecorator; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainerWithProperties.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainerWithProperties.cs index e0dd998a5..1d25d6b9b 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainerWithProperties.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlContainerWithProperties.cs @@ -9,11 +9,11 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; /// /// Represents a decorator that wraps an that is a container element with properties. /// -internal abstract partial class XmlContainerWithProperties(SlnxFile root, XmlElement element, Keyword elementName) : +internal abstract class XmlContainerWithProperties(SlnxFile root, XmlElement element, Keyword elementName) : XmlContainer(root, element, elementName) { #pragma warning disable SA1401 // Fields should be private - private protected ItemRefList propertyBags = new ItemRefList(); + private protected ItemRefList propertyBags = new(); #pragma warning restore SA1401 // Fields should be private /// @@ -21,7 +21,7 @@ internal abstract partial class XmlContainerWithProperties(SlnxFile root, XmlEle { return elementName switch { - Keyword.Properties => new XmlProperties(this.Root, element), + Keyword.Properties => new XmlProperties(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -32,7 +32,7 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlProperties properties: - this.propertyBags.Add(properties); + propertyBags.Add(properties); break; } @@ -42,9 +42,9 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) // Update the Xml DOM with changes from the model. internal bool ApplyModelToXml(IReadOnlyList? modelPropertyBags) { - return this.ApplyModelItemsToXml( + return ApplyModelItemsToXml( modelItems: modelPropertyBags?.ToList(propertyBag => (ItemRef: propertyBag.Id, Item: propertyBag)), - decoratorItems: ref this.propertyBags, + decoratorItems: ref propertyBags, decoratorElementName: Keyword.Properties, applyModelToXml: static (newProperties, newValue) => newProperties.ApplyModelToXml(newValue)); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.ApplyModel.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.ApplyModel.cs index c141d68e2..bfd801c7a 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.ApplyModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.ApplyModel.cs @@ -12,17 +12,17 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; internal abstract partial class XmlDecorator { // CONSIDER: Use StringTable if strings will be kept. - internal string? GetXmlAttribute(Keyword keyword) => this.XmlElement.GetAttribute(keyword.ToXmlString()).Trim().NullIfEmpty(); + internal string? GetXmlAttribute(Keyword keyword) => XmlElement.GetAttribute(keyword.ToXmlString()).Trim().NullIfEmpty(); internal void UpdateXmlAttribute(Keyword keyword, bool isDefault, T value, Func toString) { if (isDefault) { - this.XmlElement.RemoveAttribute(keyword.ToXmlString()); + XmlElement.RemoveAttribute(keyword.ToXmlString()); } else { - this.XmlElement.SetAttribute(keyword.ToXmlString(), toString(value)); + XmlElement.SetAttribute(keyword.ToXmlString(), toString(value)); } } @@ -30,20 +30,20 @@ internal List GetElementAndTrivia() { List trivia = new(8); - XmlNode? previous = this.XmlElement.PreviousSibling; + XmlNode? previous = XmlElement.PreviousSibling; while (previous is XmlWhitespace or XmlComment) { trivia.Add(previous); previous = previous.PreviousSibling; } - trivia.Add(this.XmlElement); + trivia.Add(XmlElement); return trivia; } internal XmlNode GetFirstTrivia() { - XmlNode? previous = this.XmlElement.PreviousSibling; + XmlNode? previous = XmlElement.PreviousSibling; XmlNode? trivia = null; while (previous is XmlWhitespace or XmlComment) { @@ -51,20 +51,20 @@ internal XmlNode GetFirstTrivia() previous = previous.PreviousSibling; } - return trivia ?? this.XmlElement; + return trivia ?? XmlElement; } internal StringSpan GetNewLineAndIndent() { // The solution node doesn't have a newline before it, so create one. - if (this.ElementName == Keyword.Solution) + if (ElementName == Keyword.Solution) { - return (this.Root.SerializationSettings.NewLine ?? Environment.NewLine).AsSpan(); + return (Root.SerializationSettings.NewLine ?? Environment.NewLine).AsSpan(); } - return this.XmlElement.PreviousSibling is not XmlWhitespace previousWhitespace ? - StringSpan.Empty : - OnlyOneLine(previousWhitespace.Value.AsSpan()); + return XmlElement.PreviousSibling is not XmlWhitespace previousWhitespace + ? StringSpan.Empty + : OnlyOneLine(previousWhitespace.Value.AsSpan()); static StringSpan OnlyOneLine(StringSpan value) { diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.cs index 6d385ea49..54649e107 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlDecorator.cs @@ -20,12 +20,13 @@ internal abstract partial class XmlDecorator private protected XmlDecorator(SlnxFile root, XmlElement element, Keyword elementName) { - this.Root = root; - this.XmlElement = element; - this.ElementName = elementName; - if (this.ElementName != Keywords.ToKeyword(element.Name)) + Root = root; + XmlElement = element; + ElementName = elementName; + if (ElementName != Keywords.ToKeyword(element.Name)) { - throw new SolutionArgumentException($"Expected element name {this.ElementName}, but got {element.Name}", SolutionErrorType.InvalidXmlDecoratorElementName); + throw new SolutionArgumentException($"Expected element name {ElementName}, but got {element.Name}", + SolutionErrorType.InvalidXmlDecoratorElementName); } } @@ -34,13 +35,13 @@ private protected XmlDecorator(SlnxFile root, XmlElement element, Keyword elemen /// public string ItemRef { - get => this.itemRef ??= this.RawItemRef; + get => itemRef ??= RawItemRef; set { if (this is IItemRefDecorator) { - this.RawItemRef = value; - this.itemRef = value; + RawItemRef = value; + itemRef = value; } } } @@ -61,9 +62,11 @@ public string ItemRef #if DEBUG - internal string DebugItemRef => this is IItemRefDecorator itemRefDecorator ? $"({itemRefDecorator.ItemRefAttribute}={this.ItemRef})" : string.Empty; + internal string DebugItemRef => this is IItemRefDecorator itemRefDecorator + ? $"({itemRefDecorator.ItemRefAttribute}={ItemRef})" + : string.Empty; - internal virtual string DebugDisplay => $"{this.ElementName} {this.DebugItemRef}"; + internal virtual string DebugDisplay => $"{ElementName} {DebugItemRef}"; #endif @@ -79,14 +82,14 @@ public string ItemRef /// private protected virtual string RawItemRef { - get => this is IItemRefDecorator itemRefDecorator ? - this.GetXmlAttribute(itemRefDecorator.ItemRefAttribute) ?? string.Empty : - string.Empty; + get => this is IItemRefDecorator itemRefDecorator + ? GetXmlAttribute(itemRefDecorator.ItemRefAttribute) ?? string.Empty + : string.Empty; set { if (this is IItemRefDecorator itemRefDecorator) { - this.UpdateXmlAttribute(itemRefDecorator.ItemRefAttribute, value); + UpdateXmlAttribute(itemRefDecorator.ItemRefAttribute, value); } } } @@ -95,12 +98,12 @@ private protected virtual string RawItemRef internal virtual bool IsValid() { - if (this.IsSingleton) + if (IsSingleton) { - return this.ItemRef.IsNullOrEmpty(); + return ItemRef.IsNullOrEmpty(); } - return this.AllowEmptyItemRef || !string.IsNullOrWhiteSpace(this.ItemRef); + return AllowEmptyItemRef || !string.IsNullOrWhiteSpace(ItemRef); } #region Update decorator from XML @@ -111,7 +114,7 @@ internal virtual bool IsValid() /// internal virtual void UpdateFromXml() { - _ = this.ItemRef; + _ = ItemRef; } #endregion @@ -119,29 +122,29 @@ internal virtual void UpdateFromXml() #region Attribute Helpers internal Guid GetXmlAttributeGuid(Keyword keyword, Guid defaultValue = default) => - Guid.TryParse(this.GetXmlAttribute(keyword), out Guid guid) ? guid : defaultValue; + Guid.TryParse(GetXmlAttribute(keyword), out Guid guid) ? guid : defaultValue; internal void UpdateXmlAttributeGuid(Keyword keyword, Guid value) => - this.UpdateXmlAttribute(keyword, isDefault: value == Guid.Empty, value, guid => guid.ToString()); + UpdateXmlAttribute(keyword, isDefault: value == Guid.Empty, value, guid => guid.ToString()); internal bool GetXmlAttributeBool(Keyword keyword, bool defaultValue = false) => - bool.TryParse(this.GetXmlAttribute(keyword), out bool boolValue) ? boolValue : defaultValue; + bool.TryParse(GetXmlAttribute(keyword), out bool boolValue) ? boolValue : defaultValue; // Note: The XML schema for boolean only allows lowercase "true" or "false". internal void UpdateXmlAttributeBool(Keyword keyword, bool value, bool defaultValue = false) => - this.UpdateXmlAttribute(keyword, isDefault: value == defaultValue, value, b => b.ToXmlBool()); + UpdateXmlAttribute(keyword, isDefault: value == defaultValue, value, b => b.ToXmlBool()); internal void UpdateXmlAttribute(Keyword keyword, string? value) => - this.UpdateXmlAttribute(keyword, isDefault: value.IsNullOrEmpty(), value, str => str ?? string.Empty); + UpdateXmlAttribute(keyword, isDefault: value.IsNullOrEmpty(), value, str => str ?? string.Empty); #endregion #region Helper methods [return: NotNullIfNotNull(nameof(str))] - private protected string? GetTableString(string? str) => this.Root.StringTable.GetString(str); + private protected string? GetTableString(string? str) => Root.StringTable.GetString(str); - private protected string GetTableString(StringSpan str) => this.Root.StringTable.GetString(str); + private protected string GetTableString(StringSpan str) => Root.StringTable.GetString(str); #endregion } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFile.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFile.cs index bc175e7b5..92ea15af9 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFile.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFile.cs @@ -14,5 +14,5 @@ internal sealed class XmlFile(SlnxFile root, XmlElement element) : { public Keyword ItemRefAttribute => Keyword.Path; - internal string Path => this.ItemRef; + internal string Path => ItemRef; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFolder.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFolder.cs index 41436c84d..ba582a2c4 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFolder.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlFolder.cs @@ -15,22 +15,22 @@ internal sealed class XmlFolder(SlnxFile root, XmlSolution xmlSolution, XmlEleme IItemRefDecorator { private readonly XmlSolution xmlSolution = xmlSolution; - private ItemRefList files = new ItemRefList(ignoreCase: true); - private ItemRefList folderProjects = new ItemRefList(ignoreCase: true); + private ItemRefList files = new(ignoreCase: true); + private ItemRefList folderProjects = new(ignoreCase: true); public Keyword ItemRefAttribute => Keyword.Name; - internal string Name => this.ItemRef; + internal string Name => ItemRef; internal Guid Id { - get => this.GetXmlAttributeGuid(Keyword.Id); - set => this.UpdateXmlAttributeGuid(Keyword.Id, value); + get => GetXmlAttributeGuid(Keyword.Id); + set => UpdateXmlAttributeGuid(Keyword.Id, value); } #if DEBUG - internal override string DebugDisplay => $"{base.DebugDisplay} FolderProjects={this.folderProjects} Files={this.files}"; + internal override string DebugDisplay => $"{base.DebugDisplay} FolderProjects={folderProjects} Files={files}"; #endif @@ -40,8 +40,8 @@ internal Guid Id return elementName switch { // Forward project handling to the solution decorator. - Keyword.Project => this.xmlSolution.CreateProjectDecorator(element, xmlParentFolder: this), - Keyword.File => new XmlFile(this.Root, element), + Keyword.Project => xmlSolution.CreateProjectDecorator(element, xmlParentFolder: this), + Keyword.File => new XmlFile(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -52,10 +52,11 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlFile file: - this.files.Add(file); + files.Add(file); break; + case XmlProject project: - this.folderProjects.Add(project); + folderProjects.Add(project); break; } @@ -67,34 +68,35 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) { return typeof(TDecorator).Name switch { - nameof(XmlFile) => this.folderProjects.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlProject) => this.propertyBags.FirstOrDefault(), + nameof(XmlFile) => folderProjects.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlProject) => propertyBags.FirstOrDefault(), _ => null, }; } #region Deserialize model - internal void AddToModel(SolutionModel solutionModel, List<(XmlProject XmlProject, SolutionProjectModel ModelProject)> newProjects) + internal void AddToModel(SolutionModel solutionModel, + List<(XmlProject XmlProject, SolutionProjectModel ModelProject)> newProjects) { try { - SolutionFolderModel folderModel = solutionModel.AddFolder(this.Name); - folderModel.Id = this.Id; + SolutionFolderModel folderModel = solutionModel.AddFolder(Name); + folderModel.Id = Id; - foreach (XmlFile file in this.files.GetItems()) + foreach (XmlFile file in files.GetItems()) { string modelPath = PathExtensions.ConvertToModel(file.Path); folderModel.AddFile(modelPath); - this.Root.UserPaths[modelPath] = file.Path; + Root.UserPaths[modelPath] = file.Path; } - foreach (XmlProperties properties in this.propertyBags.GetItems()) + foreach (XmlProperties properties in propertyBags.GetItems()) { properties.AddToModel(folderModel); } - foreach (XmlProject project in this.folderProjects.GetItems()) + foreach (XmlProject project in folderProjects.GetItems()) { newProjects.Add((project, project.AddToModel(solutionModel))); } @@ -115,31 +117,32 @@ internal bool ApplyModelToXml(SolutionFolderModel modelFolder) // Attributes Guid id = modelFolder.IsDefaultId ? Guid.Empty : modelFolder.Id; - if (this.Id != id) + if (Id != id) { - this.Id = id; + Id = id; modified = true; } // Files - modified |= this.ApplyModelItemsToXml( - itemRefs: modelFolder.Files?.ToList(this.Root.ConvertToUserPath), - decoratorItems: ref this.files, + modified |= ApplyModelItemsToXml( + itemRefs: modelFolder.Files?.ToList(Root.ConvertToUserPath), + decoratorItems: ref files, decoratorElementName: Keyword.File); // Projects List<(string ItemRef, SolutionProjectModel Item)> projectsInFolder = modelSolution.SolutionProjects.WhereToList( (project, solutionFolderModel) => ReferenceEquals(project.Parent, solutionFolderModel), - (project, _) => (ItemRef: this.Root.ConvertToUserPath(project.ItemRef), Item: project), + (project, _) => (ItemRef: Root.ConvertToUserPath(project.ItemRef), Item: project), modelFolder); - modified |= this.ApplyModelItemsToXml( + + modified |= ApplyModelItemsToXml( modelItems: projectsInFolder, - ref this.folderProjects, + ref folderProjects, Keyword.Project, applyModelToXml: static (newProject, modelProject) => newProject.ApplyModelToXml(modelProject)); // Properties - modified |= this.ApplyModelToXml(modelFolder.Properties); + modified |= ApplyModelToXml(modelFolder.Properties); return modified; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlPlatform.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlPlatform.cs index 8cd15c557..a1f98aa07 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlPlatform.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlPlatform.cs @@ -14,5 +14,5 @@ internal sealed class XmlPlatform(SlnxFile root, XmlElement element) : { public Keyword ItemRefAttribute => Keyword.Name; - internal string Name => this.ItemRef; + internal string Name => ItemRef; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.ApplyModel.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.ApplyModel.cs index 78c5afa88..3dee46b97 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.ApplyModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.ApplyModel.cs @@ -13,41 +13,42 @@ internal bool ApplyModelToXml(SolutionProjectModel modelProject) bool modified = false; // Attributes - string type = this.Root.ProjectTypes.GetConciseType(modelProject); - if (!StringComparer.Ordinal.Equals(this.Type, type)) + string type = Root.ProjectTypes.GetConciseType(modelProject); + if (!StringComparer.Ordinal.Equals(Type, type)) { - this.Type = type.NullIfEmpty(); + Type = type.NullIfEmpty(); modified = true; } string? displayName = - modelProject.DisplayName is null || StringExtensions.EqualsOrdinal(this.DefaultDisplayName, modelProject.ActualDisplayName) ? - null : - modelProject.DisplayName; - if (!StringComparer.Ordinal.Equals(this.DisplayName, displayName)) + modelProject.DisplayName is null || DefaultDisplayName.EqualsOrdinal(modelProject.ActualDisplayName) + ? null + : modelProject.DisplayName; + + if (!StringComparer.Ordinal.Equals(DisplayName, displayName)) { - this.DisplayName = displayName; + DisplayName = displayName; modified = true; } Guid id = modelProject.IsDefaultId ? Guid.Empty : modelProject.Id; - if (this.Id != id) + if (Id != id) { - this.Id = id; + Id = id; modified = true; } // BuildDependencies - modified |= this.ApplyModelItemsToXml( - itemRefs: modelProject.Dependencies?.ToList(dependencyProject => this.Root.ConvertToUserPath(dependencyProject.FilePath)), - decoratorItems: ref this.buildDependencies, + modified |= ApplyModelItemsToXml( + itemRefs: modelProject.Dependencies?.ToList(dependencyProject => Root.ConvertToUserPath(dependencyProject.FilePath)), + decoratorItems: ref buildDependencies, decoratorElementName: Keyword.BuildDependency); // Configurations - modified |= this.configurationRules.ApplyModelToXml(this, modelProject.ProjectConfigurationRules); + modified |= configurationRules.ApplyModelToXml(this, modelProject.ProjectConfigurationRules); // Properties - modified |= this.ApplyModelToXml(modelProject.Properties); + modified |= ApplyModelToXml(modelProject.Properties); return modified; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.cs index 4fa3f19ad..1dce570e4 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProject.cs @@ -14,37 +14,37 @@ internal sealed partial class XmlProject(SlnxFile root, XmlFolder? xmlParentFold XmlContainerWithProperties(root, element, Keyword.Project), IItemRefDecorator { - private ItemRefList buildDependencies = new ItemRefList(ignoreCase: true); - private ItemConfigurationRulesList configurationRules = new ItemConfigurationRulesList(); + private ItemRefList buildDependencies = new(ignoreCase: true); + private ItemConfigurationRulesList configurationRules = new(); public Keyword ItemRefAttribute => Keyword.Path; - internal string Path => this.ItemRef; + internal string Path => ItemRef; - internal StringSpan DefaultDisplayName => PathExtensions.GetStandardDisplayName(PathExtensions.ConvertToModel(this.Path)); + internal StringSpan DefaultDisplayName => PathExtensions.GetStandardDisplayName(PathExtensions.ConvertToModel(Path)); internal Guid Id { - get => this.GetXmlAttributeGuid(Keyword.Id); - set => this.UpdateXmlAttributeGuid(Keyword.Id, value); + get => GetXmlAttributeGuid(Keyword.Id); + set => UpdateXmlAttributeGuid(Keyword.Id, value); } internal string? DisplayName { - get => this.GetXmlAttribute(Keyword.DisplayName); - set => this.UpdateXmlAttribute(Keyword.DisplayName, value); + get => GetXmlAttribute(Keyword.DisplayName); + set => UpdateXmlAttribute(Keyword.DisplayName, value); } internal string? Type { - get => this.GetXmlAttribute(Keyword.Type); - set => this.UpdateXmlAttribute(Keyword.Type, value); + get => GetXmlAttribute(Keyword.Type); + set => UpdateXmlAttribute(Keyword.Type, value); } internal bool DefaultStartup { - get => this.GetXmlAttributeBool(Keyword.DefaultStartup, defaultValue: false); - set => this.UpdateXmlAttributeBool(Keyword.DefaultStartup, value); + get => GetXmlAttributeBool(Keyword.DefaultStartup, defaultValue: false); + set => UpdateXmlAttributeBool(Keyword.DefaultStartup, value); } internal XmlFolder? ParentFolder { get; } = xmlParentFolder; @@ -54,11 +54,11 @@ internal bool DefaultStartup { return elementName switch { - Keyword.BuildDependency => new XmlBuildDependency(this.Root, element), - Keyword.BuildType => new XmlConfigurationBuildType(this.Root, element), - Keyword.Platform => new XmlConfigurationPlatform(this.Root, element), - Keyword.Build => new XmlConfigurationBuild(this.Root, element), - Keyword.Deploy => new XmlConfigurationDeploy(this.Root, element), + Keyword.BuildDependency => new XmlBuildDependency(Root, element), + Keyword.BuildType => new XmlConfigurationBuildType(Root, element), + Keyword.Platform => new XmlConfigurationPlatform(Root, element), + Keyword.Build => new XmlConfigurationBuild(Root, element), + Keyword.Deploy => new XmlConfigurationDeploy(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -69,10 +69,11 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlBuildDependency buildDependency: - this.buildDependencies.Add(buildDependency); + buildDependencies.Add(buildDependency); break; + case XmlConfiguration configuration: - this.configurationRules.Add(configuration); + configurationRules.Add(configuration); break; } @@ -84,9 +85,10 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) { return typeof(TDecorator).Name switch { - nameof(XmlBuildDependency) => this.configurationRules.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlConfiguration) or nameof(XmlConfigurationBuildType) or nameof(XmlConfigurationPlatform) or nameof(XmlConfigurationBuild) or nameof(XmlConfigurationDeploy) => - this.configurationRules.FindNextDecorator() ?? this.propertyBags.FirstOrDefault(), + nameof(XmlBuildDependency) => configurationRules.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlConfiguration) or nameof(XmlConfigurationBuildType) or nameof(XmlConfigurationPlatform) + or nameof(XmlConfigurationBuild) or nameof(XmlConfigurationDeploy) => + configurationRules.FindNextDecorator() ?? propertyBags.FirstOrDefault(), _ => null, }; } @@ -98,43 +100,44 @@ internal SolutionProjectModel AddToModel(SolutionModel solution) try { SolutionFolderModel? parentFolder = null; - if (this.ParentFolder is not null) + if (ParentFolder is not null) { - SolutionFolderModel? foundParentFolder = solution.FindFolder(this.ParentFolder.ItemRef); + SolutionFolderModel? foundParentFolder = solution.FindFolder(ParentFolder.ItemRef); if (foundParentFolder is not null) { parentFolder = foundParentFolder; } else { - throw SolutionException.Create(string.Format(Errors.InvalidFolderReference_Args1, this.ParentFolder.Name), this, SolutionErrorType.InvalidFolderReference); + throw SolutionException.Create(string.Format(Errors.InvalidFolderReference_Args1, ParentFolder.Name), this, + SolutionErrorType.InvalidFolderReference); } } SolutionProjectModel projectModel = solution.AddProject( - filePath: PathExtensions.ConvertToModel(this.Path), - projectTypeName: this.Type ?? string.Empty, + filePath: PathExtensions.ConvertToModel(Path), + projectTypeName: Type ?? string.Empty, folder: parentFolder); - projectModel.Id = this.Id; - projectModel.DisplayName = this.DisplayName; + projectModel.Id = Id; + projectModel.DisplayName = DisplayName; - foreach (ConfigurationRule configurationRule in this.configurationRules.ToModel()) + foreach (ConfigurationRule configurationRule in configurationRules.ToModel()) { projectModel.AddProjectConfigurationRule(configurationRule); } - foreach (XmlProperties properties in this.propertyBags.GetItems()) + foreach (XmlProperties properties in propertyBags.GetItems()) { properties.AddToModel(projectModel); } - if (this.DefaultStartup) + if (DefaultStartup) { solution.MoveProjectFirst(projectModel); } - this.Root.UserPaths[projectModel.FilePath] = this.Path; + Root.UserPaths[projectModel.FilePath] = Path; return projectModel; } @@ -146,7 +149,7 @@ internal SolutionProjectModel AddToModel(SolutionModel solution) internal void AddDependenciesToModel(SolutionModel solution, SolutionProjectModel projectModel) { - foreach (XmlBuildDependency buildDependency in this.buildDependencies.GetItems()) + foreach (XmlBuildDependency buildDependency in buildDependencies.GetItems()) { string dependencyItemRef = PathExtensions.ConvertToModel(buildDependency.Project); SolutionProjectModel? dependencyProject = solution.FindProject(dependencyItemRef); @@ -163,7 +166,8 @@ internal void AddDependenciesToModel(SolutionModel solution, SolutionProjectMode } else { - throw SolutionException.Create(string.Format(Errors.InvalidProjectReference_Args1, dependencyItemRef), buildDependency, SolutionErrorType.InvalidProjectReference); + throw SolutionException.Create(string.Format(Errors.InvalidProjectReference_Args1, dependencyItemRef), + buildDependency, SolutionErrorType.InvalidProjectReference); } } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProjectType.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProjectType.cs index 0aec95c94..b3a5124a9 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProjectType.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProjectType.cs @@ -14,36 +14,36 @@ internal sealed class XmlProjectType(SlnxFile root, XmlElement element) : XmlContainer(root, element, Keyword.ProjectType), IItemRefDecorator { - private ItemConfigurationRulesList configurationRules = new ItemConfigurationRulesList(); + private ItemConfigurationRulesList configurationRules = new(); public Keyword ItemRefAttribute => Keyword.TypeId; /// internal Guid TypeId { - get => this.GetXmlAttributeGuid(Keyword.TypeId); - set => this.UpdateXmlAttributeGuid(Keyword.TypeId, value); + get => GetXmlAttributeGuid(Keyword.TypeId); + set => UpdateXmlAttributeGuid(Keyword.TypeId, value); } /// internal string? Name { - get => this.GetXmlAttribute(Keyword.Name); - set => this.UpdateXmlAttribute(Keyword.Name, value); + get => GetXmlAttribute(Keyword.Name); + set => UpdateXmlAttribute(Keyword.Name, value); } /// internal string? Extension { - get => this.GetXmlAttribute(Keyword.Extension); - set => this.UpdateXmlAttribute(Keyword.Extension, value); + get => GetXmlAttribute(Keyword.Extension); + set => UpdateXmlAttribute(Keyword.Extension, value); } /// internal string? BasedOn { - get => this.GetXmlAttribute(Keyword.BasedOn); - set => this.UpdateXmlAttribute(Keyword.BasedOn, value); + get => GetXmlAttribute(Keyword.BasedOn); + set => UpdateXmlAttribute(Keyword.BasedOn, value); } /// @@ -55,8 +55,8 @@ internal string? BasedOn /// internal bool IsBuildable { - get => this.GetXmlAttributeBool(Keyword.IsBuildable, defaultValue: true); - set => this.UpdateXmlAttributeBool(Keyword.IsBuildable, value, defaultValue: true); + get => GetXmlAttributeBool(Keyword.IsBuildable, defaultValue: true); + set => UpdateXmlAttributeBool(Keyword.IsBuildable, value, defaultValue: true); } /// @@ -69,8 +69,8 @@ internal bool IsBuildable /// internal bool SupportsPlatform { - get => this.GetXmlAttributeBool(Keyword.SupportsPlatform, defaultValue: true); - set => this.UpdateXmlAttributeBool(Keyword.SupportsPlatform, value, defaultValue: true); + get => GetXmlAttributeBool(Keyword.SupportsPlatform, defaultValue: true); + set => UpdateXmlAttributeBool(Keyword.SupportsPlatform, value, defaultValue: true); } private protected override bool AllowEmptyItemRef => true; @@ -81,23 +81,23 @@ internal bool SupportsPlatform /// private protected override string RawItemRef { - get => GetItemRef(this.Name, this.Extension, this.TypeId); + get => GetItemRef(Name, Extension, TypeId); set { if (value.IsNullOrEmpty()) { - this.Name = null; - this.Extension = null; - this.TypeId = Guid.Empty; + Name = null; + Extension = null; + TypeId = Guid.Empty; } else if (value.EndsWith('⁂')) { - this.Name = null; - this.Extension = value.Substring(0, value.Length - 1); + Name = null; + Extension = value.Substring(0, value.Length - 1); } else { - this.Name = value; + Name = value; } } } @@ -105,9 +105,7 @@ private protected override string RawItemRef internal static string GetItemRef(string? name, string? extension, Guid typeId) { // Return empty string for default project type ItemRef. - return name is null && extension is null && typeId == Guid.Empty ? - string.Empty : - name ?? $"{extension}⁂"; + return name is null && extension is null && typeId == Guid.Empty ? string.Empty : name ?? $"{extension}⁂"; } /// @@ -115,10 +113,10 @@ internal static string GetItemRef(string? name, string? extension, Guid typeId) { return elementName switch { - Keyword.BuildType => new XmlConfigurationBuildType(this.Root, element), - Keyword.Platform => new XmlConfigurationPlatform(this.Root, element), - Keyword.Build => new XmlConfigurationBuild(this.Root, element), - Keyword.Deploy => new XmlConfigurationDeploy(this.Root, element), + Keyword.BuildType => new XmlConfigurationBuildType(Root, element), + Keyword.Platform => new XmlConfigurationPlatform(Root, element), + Keyword.Build => new XmlConfigurationBuild(Root, element), + Keyword.Deploy => new XmlConfigurationDeploy(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -129,7 +127,7 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlConfiguration configuration: - this.configurationRules.Add(configuration); + configurationRules.Add(configuration); break; } @@ -139,7 +137,7 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) /// internal override XmlDecorator? FindNextDecorator() { - return this.configurationRules.FindNextDecorator(); + return configurationRules.FindNextDecorator(); } internal override bool IsValid() @@ -150,15 +148,15 @@ internal override bool IsValid() internal ProjectType ToModel() { ConfigurationRule[] rules = - !this.IsBuildable ? ProjectTypeTable.NoBuildRules : - !this.SupportsPlatform ? [ProjectTypeTable.NoPlatformsRule, .. this.configurationRules.ToModel()] : - /*default*/ [.. this.configurationRules.ToModel()]; + !IsBuildable ? ProjectTypeTable.NoBuildRules : + !SupportsPlatform ? [ProjectTypeTable.NoPlatformsRule, .. configurationRules.ToModel()] : + /*default*/ [.. configurationRules.ToModel()]; - return new ProjectType(this.TypeId, rules) + return new ProjectType(TypeId, rules) { - Name = this.GetTableString(this.Name), - Extension = this.Extension, - BasedOn = this.BasedOn, + Name = GetTableString(Name), + Extension = Extension, + BasedOn = BasedOn, }; } @@ -166,43 +164,43 @@ internal ProjectType ToModel() internal bool ApplyModelToXml(ProjectType modelProjectType) { bool modified = false; - if (!StringComparer.Ordinal.Equals(this.Name, modelProjectType.Name)) + if (!StringComparer.Ordinal.Equals(Name, modelProjectType.Name)) { - this.Name = modelProjectType.Name; + Name = modelProjectType.Name; modified = true; } - if (!StringComparer.Ordinal.Equals(this.Extension, modelProjectType.Extension)) + if (!StringComparer.Ordinal.Equals(Extension, modelProjectType.Extension)) { - this.Extension = modelProjectType.Extension; + Extension = modelProjectType.Extension; modified = true; } - if (this.TypeId != modelProjectType.ProjectTypeId) + if (TypeId != modelProjectType.ProjectTypeId) { - this.TypeId = modelProjectType.ProjectTypeId; + TypeId = modelProjectType.ProjectTypeId; modified = true; } - if (this.BasedOn != modelProjectType.BasedOn) + if (BasedOn != modelProjectType.BasedOn) { - this.BasedOn = modelProjectType.BasedOn; + BasedOn = modelProjectType.BasedOn; modified = true; } - ConfigurationRuleFollower rules = new ConfigurationRuleFollower(modelProjectType.ConfigurationRules); + ConfigurationRuleFollower rules = new(modelProjectType.ConfigurationRules); bool isBuildable = rules.GetIsBuildable() ?? true; bool supportsPlatform = rules.GetProjectPlatform() != PlatformNames.Missing; - if (this.IsBuildable != isBuildable) + if (IsBuildable != isBuildable) { - this.IsBuildable = isBuildable; + IsBuildable = isBuildable; modified = true; } - if (this.SupportsPlatform != supportsPlatform) + if (SupportsPlatform != supportsPlatform) { - this.SupportsPlatform = supportsPlatform; + SupportsPlatform = supportsPlatform; modified = true; } @@ -212,7 +210,7 @@ internal bool ApplyModelToXml(ProjectType modelProjectType) !supportsPlatform ? RemovePlatformRules(modelProjectType.ConfigurationRules) : modelProjectType.ConfigurationRules; - modified |= this.configurationRules.ApplyModelToXml(this, rulesToApply); + modified |= configurationRules.ApplyModelToXml(this, rulesToApply); return modified; // Remove any platform rules from the list. diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperties.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperties.cs index f302a13a8..f1775dc24 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperties.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperties.cs @@ -9,22 +9,22 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; /// /// Represents a collection of properties. Can be a child of a Solution, Project or Folder. /// -internal sealed partial class XmlProperties(SlnxFile root, XmlElement element) : +internal sealed class XmlProperties(SlnxFile root, XmlElement element) : XmlContainer(root, element, Keyword.Properties), IItemRefDecorator { - private ItemRefList properties = new ItemRefList(ignoreCase: true); + private ItemRefList properties = new(ignoreCase: true); public Keyword ItemRefAttribute => Keyword.Name; - internal string Name => this.ItemRef; + internal string Name => ItemRef; private protected override bool AllowEmptyItemRef => true; private PropertiesScope Scope { - get => StringToScope(this.GetXmlAttribute(Keyword.Scope) ?? string.Empty); - set => this.UpdateXmlAttribute(Keyword.Scope, isDefault: value == PropertiesScope.PreLoad, value, ScopeToString); + get => StringToScope(GetXmlAttribute(Keyword.Scope) ?? string.Empty); + set => UpdateXmlAttribute(Keyword.Scope, isDefault: value == PropertiesScope.PreLoad, value, ScopeToString); } /// @@ -32,7 +32,7 @@ private PropertiesScope Scope { return elementName switch { - Keyword.Property => new XmlProperty(this.Root, element), + Keyword.Property => new XmlProperty(Root, element), _ => base.ChildDecoratorFactory(element, elementName), }; } @@ -43,7 +43,7 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlProperty property: - this.properties.Add(property); + properties.Add(property); break; } @@ -62,7 +62,7 @@ internal void AddToModel(PropertyContainerModel model) try { // Even if there are no properties in this property table, create a model entry so the xml isn't deleted. - SolutionPropertyBag propertyBag = model.AddProperties(id: this.Name, scope: this.Scope); + SolutionPropertyBag propertyBag = model.AddProperties(id: Name, scope: Scope); foreach (XmlProperty properties in this.properties.GetItems()) { propertyBag.Add(properties.Name, properties.Value); @@ -82,16 +82,16 @@ internal bool ApplyModelToXml(SolutionPropertyBag modelProperties) bool modified = false; // Scope - if (this.Scope != modelProperties.Scope) + if (Scope != modelProperties.Scope) { - this.Scope = modelProperties.Scope; + Scope = modelProperties.Scope; modified = true; } // Properties - modified |= this.ApplyModelItemsToXml( + modified |= ApplyModelItemsToXml( modelItems: modelProperties.ToList(property => (ItemRef: property.Key, Item: property.Value)), - decoratorItems: ref this.properties, + decoratorItems: ref properties, decoratorElementName: Keyword.Property, applyModelToXml: static (newProperty, newValue) => newProperty.ApplyModelToXml(newValue)); diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperty.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperty.cs index a1f72017a..31081d493 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperty.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlProperty.cs @@ -8,30 +8,30 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; /// /// Child of a Properties node that represents a property name/value pair. /// -internal sealed partial class XmlProperty(SlnxFile root, XmlElement element) : +internal sealed class XmlProperty(SlnxFile root, XmlElement element) : XmlDecorator(root, element, Keyword.Property), IItemRefDecorator { public Keyword ItemRefAttribute => Keyword.Name; - internal string Name => this.ItemRef; + internal string Name => ItemRef; internal string Value { - get => this.GetXmlAttribute(Keyword.Value) ?? string.Empty; - set => this.UpdateXmlAttribute(Keyword.Value, value); + get => GetXmlAttribute(Keyword.Value) ?? string.Empty; + set => UpdateXmlAttribute(Keyword.Value, value); } // Update the Xml DOM with changes from the model. internal bool ApplyModelToXml(string newValue) { // Don't update the value if it is already the same. - if (StringComparer.Ordinal.Equals(this.Value, newValue)) + if (StringComparer.Ordinal.Equals(Value, newValue)) { return false; } - this.Value = newValue; + Value = newValue; return true; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.ApplyModel.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.ApplyModel.cs index 044cacda2..0001866c4 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.ApplyModel.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.ApplyModel.cs @@ -18,40 +18,41 @@ internal bool ApplyModelToXml(SolutionModel modelSolution) // Attributes string description = modelSolution.Description ?? string.Empty; - if (!StringComparer.Ordinal.Equals(this.Description, description)) + if (!StringComparer.Ordinal.Equals(Description, description)) { - this.Description = description; + Description = description; modified = true; } // Configurations // Use the item ref logic to allow only a single "Configurations" element, and use string.Empty as the item ref. - modified |= this.ApplyModelItemsToXml( + modified |= ApplyModelItemsToXml( modelItems: modelSolution.IsConfigurationImplicit() ? null : [(ItemRef: string.Empty, Item: modelSolution)], - ref this.configurationsSingle, + ref configurationsSingle, Keyword.Configurations, applyModelToXml: static (newConfigs, newValue) => newConfigs.ApplyModelToXml(newValue)); // Folders - modified |= this.ApplyModelItemsToXml( + modified |= ApplyModelItemsToXml( modelItems: modelSolution.SolutionFolders.ToList(folder => (folder.ItemRef, Item: folder)), - ref this.folders, + ref folders, Keyword.Folder, applyModelToXml: static (newFolder, newValue) => newFolder.ApplyModelToXml(newValue)); // Projects List<(string ItemRef, SolutionProjectModel Item)> solutionProjects = modelSolution.SolutionProjects.WhereToList( (project, _) => project.Parent is null, - (project, _) => (ItemRef: this.Root.ConvertToUserPath(project.ItemRef), Item: project), + (project, _) => (ItemRef: Root.ConvertToUserPath(project.ItemRef), Item: project), (object?)null); - modified |= this.ApplyModelItemsToXml( + + modified |= ApplyModelItemsToXml( modelItems: solutionProjects, - ref this.rootProjects, + ref rootProjects, Keyword.Project, applyModelToXml: static (newProject, newValue) => newProject.ApplyModelToXml(newValue)); // Properties - modified |= this.ApplyModelToXml(modelSolution.Properties); + modified |= ApplyModelToXml(modelSolution.Properties); return modified; } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.cs index fd6cf32f0..e2471d279 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDecorators/XmlSolution.cs @@ -12,25 +12,25 @@ namespace Fallout.Persistence.Solution.Serializer.Xml.XmlDecorators; internal sealed partial class XmlSolution(SlnxFile file, XmlElement element) : XmlContainerWithProperties(file, element, Keyword.Solution) { - private ItemRefList configurationsSingle = new ItemRefList(); - private ItemRefList folders = new ItemRefList(ignoreCase: true); - private ItemRefList rootProjects = new ItemRefList(ignoreCase: true); + private ItemRefList configurationsSingle = new(); + private ItemRefList folders = new(ignoreCase: true); + private ItemRefList rootProjects = new(ignoreCase: true); internal string? Description { - get => this.GetXmlAttribute(Keyword.Description); - set => this.UpdateXmlAttribute(Keyword.Description, value); + get => GetXmlAttribute(Keyword.Description); + set => UpdateXmlAttribute(Keyword.Description, value); } internal string? Version { - get => this.GetXmlAttribute(Keyword.Version); - set => this.UpdateXmlAttribute(Keyword.Version, value); + get => GetXmlAttribute(Keyword.Version); + set => UpdateXmlAttribute(Keyword.Version, value); } #if DEBUG - internal override string DebugDisplay => $"{base.DebugDisplay} RootProjects={this.rootProjects} Folders={this.folders}"; + internal override string DebugDisplay => $"{base.DebugDisplay} RootProjects={rootProjects} Folders={folders}"; #endif @@ -39,16 +39,16 @@ internal string? Version { return elementName switch { - Keyword.Configurations => new XmlConfigurations(this.Root, element), - Keyword.Project => this.CreateProjectDecorator(element, xmlParentFolder: null), - Keyword.Folder => new XmlFolder(this.Root, this, element), + Keyword.Configurations => new XmlConfigurations(Root, element), + Keyword.Project => CreateProjectDecorator(element, xmlParentFolder: null), + Keyword.Folder => new XmlFolder(Root, this, element), _ => base.ChildDecoratorFactory(element, elementName), }; } internal XmlProject CreateProjectDecorator(XmlElement element, XmlFolder? xmlParentFolder) { - return new XmlProject(this.Root, xmlParentFolder, element); + return new XmlProject(Root, xmlParentFolder, element); } /// @@ -57,13 +57,15 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) switch (childDecorator) { case XmlFolder folder: - this.folders.Add(folder); + folders.Add(folder); break; + case XmlProject project: - this.rootProjects.Add(project); + rootProjects.Add(project); break; + case XmlConfigurations configurations: - this.configurationsSingle.Add(configurations); + configurationsSingle.Add(configurations); break; } @@ -75,9 +77,9 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) { return typeof(TDecorator).Name switch { - nameof(XmlConfigurations) => this.folders.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlFolder) => this.rootProjects.FirstOrDefault() ?? this.FindNextDecorator(), - nameof(XmlProject) => this.propertyBags.FirstOrDefault(), + nameof(XmlConfigurations) => folders.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlFolder) => rootProjects.FirstOrDefault() ?? FindNextDecorator(), + nameof(XmlProject) => propertyBags.FirstOrDefault(), _ => null, }; } @@ -87,40 +89,42 @@ internal override void OnNewChildDecoratorAdded(XmlDecorator childDecorator) internal SolutionModel ToModel() { // Ensure the file version is supported. - string? fileVersion = this.Version; + string? fileVersion = Version; if (!fileVersion.IsNullOrEmpty()) { try { - this.Root.FileVersion = new Version(fileVersion); + Root.FileVersion = new Version(fileVersion); } catch (Exception ex) when (SolutionException.ShouldWrap(ex)) { - throw SolutionException.Create(ex, this, string.Format(Errors.InvalidVersion_Args1, fileVersion), SolutionErrorType.InvalidVersion); + throw SolutionException.Create(ex, this, string.Format(Errors.InvalidVersion_Args1, fileVersion), + SolutionErrorType.InvalidVersion); } - if (this.Root.FileVersion.Major > SlnxFile.CurrentVersion) + if (Root.FileVersion.Major > SlnxFile.CurrentVersion) { - throw SolutionException.Create(string.Format(Errors.UnsupportedVersion_Args1, fileVersion), this, SolutionErrorType.UnsupportedVersion); + throw SolutionException.Create(string.Format(Errors.UnsupportedVersion_Args1, fileVersion), this, + SolutionErrorType.UnsupportedVersion); } } - SolutionModel solutionModel = new SolutionModel + SolutionModel solutionModel = new() { - StringTable = this.Root.StringTable, - Description = this.Description, + StringTable = Root.StringTable, + Description = Description, // Project types are loaded earlier when parsing the XML since they are needed to resolve projects. - ProjectTypes = this.Root.ProjectTypes.ProjectTypes, + ProjectTypes = Root.ProjectTypes.ProjectTypes, }; - List<(XmlProject, SolutionProjectModel)> newProjects = new List<(XmlProject, SolutionProjectModel)>(this.rootProjects.ItemsCount); - foreach (XmlProject project in this.rootProjects.GetItems()) + List<(XmlProject, SolutionProjectModel)> newProjects = new(rootProjects.ItemsCount); + foreach (XmlProject project in rootProjects.GetItems()) { newProjects.Add((project, project.AddToModel(solutionModel))); } - foreach (XmlFolder folder in this.folders.GetItems()) + foreach (XmlFolder folder in folders.GetItems()) { folder.AddToModel(solutionModel, newProjects); } @@ -131,7 +135,7 @@ internal SolutionModel ToModel() xmlProject.AddDependenciesToModel(solutionModel, modelProject); } - foreach (XmlConfigurations configurations in this.configurationsSingle.GetItems()) + foreach (XmlConfigurations configurations in configurationsSingle.GetItems()) { configurations.AddToModel(solutionModel); } @@ -150,7 +154,7 @@ internal SolutionModel ToModel() solutionModel.AddPlatform(PlatformNames.AnySpaceCPU); } - foreach (XmlProperties properties in this.propertyBags.GetItems()) + foreach (XmlProperties properties in propertyBags.GetItems()) { properties.AddToModel(solutionModel); } @@ -163,7 +167,7 @@ internal SolutionModel ToModel() /// internal ProjectTypeTable GetProjectTypeTable() { - foreach (XmlConfigurations xmlConfigurations in this.configurationsSingle.GetItems()) + foreach (XmlConfigurations xmlConfigurations in configurationsSingle.GetItems()) { ProjectTypeTable? propertyTypeTable = xmlConfigurations.GetProjectTypeTable(); if (propertyTypeTable is not null) @@ -180,7 +184,7 @@ internal ProjectTypeTable GetProjectTypeTable() // Try to figure out indentation and line ending default from the XML. internal bool TryGetFormatting(out StringSpan newLine, out StringSpan indent) { - foreach (XmlDecorator decorator in this.folders.GetItems()) + foreach (XmlDecorator decorator in folders.GetItems()) { if (TryDecorator(decorator, newLine: out newLine, indent: out indent)) { @@ -188,7 +192,7 @@ internal bool TryGetFormatting(out StringSpan newLine, out StringSpan indent) } } - foreach (XmlDecorator decorator in this.rootProjects.GetItems()) + foreach (XmlDecorator decorator in rootProjects.GetItems()) { if (TryDecorator(decorator, newLine: out newLine, indent: out indent)) { @@ -196,7 +200,7 @@ internal bool TryGetFormatting(out StringSpan newLine, out StringSpan indent) } } - foreach (XmlDecorator decorator in this.propertyBags.GetItems()) + foreach (XmlDecorator decorator in propertyBags.GetItems()) { if (TryDecorator(decorator, newLine: out newLine, indent: out indent)) { @@ -204,7 +208,7 @@ internal bool TryGetFormatting(out StringSpan newLine, out StringSpan indent) } } - foreach (XmlConfigurations configurations in this.configurationsSingle.GetItems()) + foreach (XmlConfigurations configurations in configurationsSingle.GetItems()) { if (TryDecorator(configurations, newLine: out newLine, indent: out indent)) { diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDomUtilities.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDomUtilities.cs index 76d4579dc..7a2a1e323 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDomUtilities.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlDomUtilities.cs @@ -7,7 +7,7 @@ namespace Fallout.Persistence.Solution.Serializer.Xml; internal static class XmlDomUtilities { - public static XmlElementAttributes Attributes(this XmlElement? element) => new XmlElementAttributes(element?.Attributes); + public static XmlElementAttributes Attributes(this XmlElement? element) => new(element?.Attributes); - public static XmlElementSubElementsEnumerable ChildElements(this XmlNode? element) => new XmlElementSubElementsEnumerable(element, filterByName: null); + public static XmlElementSubElementsEnumerable ChildElements(this XmlNode? element) => new(element, filterByName: null); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementAttributes.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementAttributes.cs index e3c5f759f..d33f0e38e 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementAttributes.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementAttributes.cs @@ -14,17 +14,17 @@ internal ref struct XmlElementAttributes(XmlAttributeCollection? element) public readonly int Count => element?.Count ?? 0; - public readonly XmlAttribute Current => element![this.index]; + public readonly XmlAttribute Current => element![index]; - public readonly XmlElementAttributes GetEnumerator() => new XmlElementAttributes(element); + public readonly XmlElementAttributes GetEnumerator() => new(element); public bool MoveNext() { - if (element is null || this.index >= element.Count) + if (element is null || index >= element.Count) { return false; } - return ++this.index < element.Count; + return ++index < element.Count; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElements.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElements.cs index 388d957ae..584d0e694 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElements.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElements.cs @@ -12,30 +12,30 @@ internal ref struct XmlElementSubElements(XmlNode? element, string? filterByName { private XmlNode? child; - public readonly XmlElement Current => (object.ReferenceEquals(this.child, element) ? null : this.child as XmlElement)!; + public readonly XmlElement Current => (ReferenceEquals(child, element) ? null : child as XmlElement)!; public bool MoveNext() { // use element as "sentinel end value", null as before first. (if element is null it is also an end as coincidence). - if (object.ReferenceEquals(this.child, element) || element is null) + if (ReferenceEquals(child, element) || element is null) { return false; } do { - this.child = this.child is null ? element.FirstChild : this.child.NextSibling; - if (this.child is XmlElement) + child = child is null ? element.FirstChild : child.NextSibling; + if (child is XmlElement) { - if (filterByName is null || this.child.Name == filterByName) + if (filterByName is null || child.Name == filterByName) { return true; } } } - while (this.child is not null); + while (child is not null); - this.child = element; + child = element; return false; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElementsEnumerable.cs b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElementsEnumerable.cs index 8d5be1ed4..d8930e248 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElementsEnumerable.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Serializer/Xml/XmlElementSubElementsEnumerable.cs @@ -7,7 +7,7 @@ namespace Fallout.Persistence.Solution.Serializer.Xml; internal readonly ref struct XmlElementSubElementsEnumerable(XmlNode? element, string? filterByName) { - public readonly XmlElementSubElements GetEnumerator() => new XmlElementSubElements(element, filterByName); + public readonly XmlElementSubElements GetEnumerator() => new(element, filterByName); internal readonly bool Any() { diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/Argument.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/Argument.cs index feff85bb9..c74c0e866 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/Argument.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/Argument.cs @@ -33,15 +33,16 @@ internal static void ThrowIfNullOrEmpty([NotNull] string? argument, string? para internal static void Throw(string? paramName) => throw new ArgumentNullException(paramName); #else - /// - internal static void ThrowIfNull([NotNull] object? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null) + internal static void ThrowIfNull([NotNull] object? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName + = null) { ArgumentNullException.ThrowIfNull(argument, paramName); } /// - internal static void ThrowIfNullOrEmpty([NotNull] string? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName = null) + internal static void ThrowIfNullOrEmpty([NotNull] string? argument, [System.Runtime.CompilerServices.CallerArgumentExpression(nameof(argument))] string? paramName + = null) { ArgumentException.ThrowIfNullOrEmpty(argument, paramName); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/CollectionExtensions.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/CollectionExtensions.cs index 8cf81d8e2..f001b9590 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/CollectionExtensions.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/CollectionExtensions.cs @@ -40,9 +40,10 @@ internal static bool IsNullOrEmpty([NotNullWhen(false)] this IReadOnlyCollect return collection is null || collection.Count == 0; } - internal static ReadOnlyListStructEnumerable GetStructEnumerable(this IReadOnlyList? list) => new ReadOnlyListStructEnumerable(list); + internal static ReadOnlyListStructEnumerable GetStructEnumerable(this IReadOnlyList? list) => new(list); - internal static ReadOnlyListStructReverseEnumerable GetStructReverseEnumerable(this IReadOnlyList? list) => new ReadOnlyListStructReverseEnumerable(list); + internal static ReadOnlyListStructReverseEnumerable GetStructReverseEnumerable(this IReadOnlyList? list) => + new(list); /// /// Creates an array from a with a selector to transform the items. @@ -52,9 +53,10 @@ internal static bool IsNullOrEmpty([NotNullWhen(false)] this IReadOnlyCollect /// The input collection. /// A way to convert TSource to TResult. /// An array of the new items. - internal static List ToList(this IReadOnlyCollection collection, Func selector) + internal static List ToList(this IReadOnlyCollection collection, + Func selector) { - List list = new List(collection.Count); + List list = new(collection.Count); foreach (TSource item in collection) { list.Add(selector(item)); @@ -80,7 +82,7 @@ internal static List WhereToList( Func selector, TState state) { - List list = new List(collection.Count); + List list = new(collection.Count); foreach (TSource item in collection) { if (predicate(item, state)) diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/Lictionary`2.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/Lictionary`2.cs index 827333165..63606e5dc 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/Lictionary`2.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/Lictionary`2.cs @@ -15,7 +15,7 @@ namespace Fallout.Persistence.Solution.Utilities; internal readonly struct Lictionary : IReadOnlyDictionary where TKey : notnull { - private static readonly EntryKeyComparer DefaultComparer = new EntryKeyComparer(Comparer.Default); + private static readonly EntryKeyComparer DefaultComparer = new(Comparer.Default); private readonly List> items; private readonly EntryKeyComparer comparer; @@ -28,18 +28,18 @@ public Lictionary() internal Lictionary(int capacity, IComparer? comparer = null) { this.comparer = comparer is null ? DefaultComparer : new EntryKeyComparer(comparer); - this.items = new List>(capacity); + items = new List>(capacity); } internal Lictionary(IReadOnlyCollection> values, IComparer? comparer = null) { Argument.ThrowIfNull(values, nameof(values)); this.comparer = comparer is null ? DefaultComparer : new EntryKeyComparer(comparer); - this.items = [.. values]; - this.items.Sort(this.comparer); + items = [.. values]; + items.Sort(this.comparer); KeyValuePair lastEntry = default; - foreach (KeyValuePair entry in this.items) + foreach (KeyValuePair entry in items) { if (this.comparer.Equals(lastEntry, entry)) { @@ -54,28 +54,28 @@ internal Lictionary(IReadOnlyCollection> values, ICom public IEnumerable Values => this.Select(x => x.Value); - public int Count => this.items.Count; + public int Count => items.Count; public TValue this[TKey key] { - get => this.TryGetValue(key, out TValue? value) ? value : throw new KeyNotFoundException(nameof(key)); + get => TryGetValue(key, out TValue? value) ? value : throw new KeyNotFoundException(nameof(key)); set { - int index = this.BinarySearch(key); + int index = BinarySearch(key); if (index >= 0) { - this.items[index] = new(key, value); + items[index] = new KeyValuePair(key, value); } else { - this.items.Insert(~index, new(key, value)); + items.Insert(~index, new KeyValuePair(key, value)); } } } - public TValue this[int index] => this.items[index].Value; + public TValue this[int index] => items[index].Value; - public bool ContainsKey(TKey key) => this.BinarySearch(key) >= 0; + public bool ContainsKey(TKey key) => BinarySearch(key) >= 0; #if NETFRAMEWORK || NETSTANDARD #nullable disable warnings @@ -85,10 +85,10 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) #nullable restore #endif { - int index = this.BinarySearch(key); + int index = BinarySearch(key); if (index >= 0) { - value = this.items[index].Value; + value = items[index].Value; return true; } else @@ -98,15 +98,15 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) } } - IEnumerator> IEnumerable>.GetEnumerator() => this.items.GetEnumerator(); + IEnumerator> IEnumerable>.GetEnumerator() => items.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => this.items.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator(); - public List>.Enumerator GetEnumerator() => this.items.GetEnumerator(); + public List>.Enumerator GetEnumerator() => items.GetEnumerator(); internal void Add(TKey key, TValue value) { - if (!this.TryAdd(key, value)) + if (!TryAdd(key, value)) { throw new ArgumentException(Errors.DuplicateKey, nameof(key)); } @@ -114,38 +114,38 @@ internal void Add(TKey key, TValue value) internal bool TryAdd(TKey key, TValue value) { - int index = this.BinarySearch(key); + int index = BinarySearch(key); if (index >= 0) { return false; } else { - this.items.Insert(~index, new KeyValuePair(key, value)); + items.Insert(~index, new KeyValuePair(key, value)); return true; } } internal bool Remove(TKey key) { - int index = this.BinarySearch(key); + int index = BinarySearch(key); if (index >= 0) { - this.items.RemoveAt(index); + items.RemoveAt(index); return true; } return false; } - internal void Clear() => this.items.Clear(); + internal void Clear() => items.Clear(); internal bool TryFindNext(TKey key, [MaybeNullWhen(false)] out TValue? value) { - int index = ~this.BinarySearch(key); - if (index >= 0 && index < this.items.Count) + int index = ~BinarySearch(key); + if (index >= 0 && index < items.Count) { - value = this.items[index].Value; + value = items[index].Value; return true; } else @@ -158,9 +158,9 @@ internal bool TryFindNext(TKey key, [MaybeNullWhen(false)] out TValue? value) internal void EnsureCapacity(int capacity) { #if NETFRAMEWORK || NETSTANDARD - if (capacity > this.items.Capacity) + if (capacity > items.Capacity) { - this.items.Capacity = capacity; + items.Capacity = capacity; } #else _ = this.items.EnsureCapacity(capacity); @@ -170,7 +170,7 @@ internal void EnsureCapacity(int capacity) private int BinarySearch(TKey key) { Argument.ThrowIfNull(key, nameof(key)); - return this.items.BinarySearch(new(key, default!), this.comparer); + return items.BinarySearch(new KeyValuePair(key, default!), comparer); } private sealed class EntryKeyComparer(IComparer keyComparer) : IComparer> diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/ListBuilderStruct`1.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/ListBuilderStruct`1.cs index ade625940..e6dfca0a9 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/ListBuilderStruct`1.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/ListBuilderStruct`1.cs @@ -32,7 +32,7 @@ internal ListBuilderStruct(int capacity) { if (capacity > 4) { - this.items = new List(capacity - 4); + items = new List(capacity - 4); } } @@ -44,11 +44,11 @@ readonly get { return index switch { - 0 => this.item0, - 1 => this.item1, - 2 => this.item2, - 3 => this.item3, - _ => this.items![index - 4], + 0 => item0, + 1 => item1, + 2 => item2, + 3 => item3, + _ => items![index - 4], }; } @@ -56,11 +56,15 @@ readonly get { switch (index) { - case 0: this.item0 = value; break; - case 1: this.item1 = value; break; - case 2: this.item2 = value; break; - case 3: this.item3 = value; break; - default: this.items![index - 4] = value; break; + case 0: item0 = value; break; + + case 1: item1 = value; break; + + case 2: item2 = value; break; + + case 3: item3 = value; break; + + default: items![index - 4] = value; break; } } } @@ -72,24 +76,28 @@ public readonly Enumerator GetEnumerator() internal void Add(T item) { - switch (this.Count) + switch (Count) { - case 0: this.item0 = item; break; - case 1: this.item1 = item; break; - case 2: this.item2 = item; break; - case 3: this.item3 = item; break; + case 0: item0 = item; break; + + case 1: item1 = item; break; + + case 2: item2 = item; break; + + case 3: item3 = item; break; + default: - this.items ??= []; - this.items.Add(item); + items ??= []; + items.Add(item); break; } - this.Count++; + Count++; } internal void AddRange(IReadOnlyCollection items) { - int newCapacity = this.Count + items.Count; + int newCapacity = Count + items.Count; if (newCapacity > 4) { this.items ??= new List(newCapacity - 4); @@ -98,27 +106,27 @@ internal void AddRange(IReadOnlyCollection items) foreach (T item in items) { - this.Add(item); + Add(item); } } internal readonly T[] ToArray() { - return this.Count switch + return Count switch { 0 => [], - 1 => [this.item0], - 2 => [this.item0, this.item1], - 3 => [this.item0, this.item1, this.item2], - 4 => [this.item0, this.item1, this.item2, this.item3], - _ => [this.item0, this.item1, this.item2, this.item3, .. this.items!], + 1 => [item0], + 2 => [item0, item1], + 3 => [item0, item1, item2], + 4 => [item0, item1, item2, item3], + _ => [item0, item1, item2, item3, .. items!], }; } internal void Clear() { - this.Count = 0; - this.items = null; + Count = 0; + items = null; } internal ref struct Enumerator(ListBuilderStruct builder) @@ -126,12 +134,12 @@ internal ref struct Enumerator(ListBuilderStruct builder) private readonly ListBuilderStruct builder = builder; private int index = -1; - public readonly T Current => this.builder[this.index]; + public readonly T Current => builder[index]; public bool MoveNext() { - this.index++; - return this.index < this.builder.Count; + index++; + return index < builder.Count; } } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/ListStructEnumerable`1.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/ListStructEnumerable`1.cs index 68c31e6c0..062a2f817 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/ListStructEnumerable`1.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/ListStructEnumerable`1.cs @@ -17,32 +17,32 @@ internal readonly ref struct ListStructEnumerable(List? list) internal readonly ref struct ReadOnlyListStructEnumerable(IReadOnlyList? list) { - public ReadOnlyListStructEnumerator GetEnumerator() => new ReadOnlyListStructEnumerator(list); + public ReadOnlyListStructEnumerator GetEnumerator() => new(list); } internal ref struct ReadOnlyListStructEnumerator(IReadOnlyList? list) { private int index = -1; - public readonly T Current => list![this.index]; + public readonly T Current => list![index]; - public bool MoveNext() => ++this.index < (list?.Count ?? 0); + public bool MoveNext() => ++index < (list?.Count ?? 0); } internal readonly ref struct ReadOnlyListStructReverseEnumerable(IReadOnlyList? list) { - public ReadOnlyListStructReverseEnumerator GetEnumerator() => new ReadOnlyListStructReverseEnumerator(list); + public ReadOnlyListStructReverseEnumerator GetEnumerator() => new(list); } internal ref struct ReadOnlyListStructReverseEnumerator(IReadOnlyList? list) { private int index = list?.Count ?? 0; - public readonly T Current => list![this.index]; + public readonly T Current => list![index]; public bool MoveNext() { - this.index--; - return this.index >= 0; + index--; + return index >= 0; } } diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/PathExtensions.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/PathExtensions.cs index a2dc1d95c..5f2265860 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/PathExtensions.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/PathExtensions.cs @@ -14,9 +14,9 @@ internal static class PathExtensions [return: NotNullIfNotNull(nameof(persistencePath))] internal static string? ConvertBackslashToModel(string? persistencePath) { - return persistencePath.IsNullOrEmpty() || IsWindows || !persistencePath.Contains('\\') ? - persistencePath : - persistencePath.Replace('\\', Path.DirectorySeparatorChar); + return persistencePath.IsNullOrEmpty() || IsWindows || !persistencePath.Contains('\\') + ? persistencePath + : persistencePath.Replace('\\', Path.DirectorySeparatorChar); } [return: NotNullIfNotNull(nameof(persistencePath))] @@ -24,25 +24,25 @@ internal static class PathExtensions { char altSlash = IsWindows ? Path.AltDirectorySeparatorChar : '\\'; - return persistencePath.IsNullOrEmpty() || !persistencePath.Contains(altSlash) || IsUri(persistencePath) ? - persistencePath : - persistencePath.Replace(altSlash, Path.DirectorySeparatorChar); + return persistencePath.IsNullOrEmpty() || !persistencePath.Contains(altSlash) || IsUri(persistencePath) + ? persistencePath + : persistencePath.Replace(altSlash, Path.DirectorySeparatorChar); } [return: NotNullIfNotNull(nameof(modelPath))] internal static string? ConvertModelToBackslashPath(string? modelPath) { - return modelPath is null || IsWindows || !modelPath.Contains(Path.DirectorySeparatorChar) || IsUri(modelPath) ? - modelPath : - modelPath.Replace(Path.DirectorySeparatorChar, '\\'); + return modelPath is null || IsWindows || !modelPath.Contains(Path.DirectorySeparatorChar) || IsUri(modelPath) + ? modelPath + : modelPath.Replace(Path.DirectorySeparatorChar, '\\'); } [return: NotNullIfNotNull(nameof(modelPath))] internal static string? ConvertModelToForwardSlashPath(string? modelPath) { - return modelPath is null || !IsWindows || !modelPath.Contains(Path.DirectorySeparatorChar) || IsUri(modelPath) ? - modelPath : - modelPath.Replace(Path.DirectorySeparatorChar, '/'); + return modelPath is null || !IsWindows || !modelPath.Contains(Path.DirectorySeparatorChar) || IsUri(modelPath) + ? modelPath + : modelPath.Replace(Path.DirectorySeparatorChar, '/'); } internal static StringSpan GetStandardDisplayName(string filePath) @@ -76,5 +76,6 @@ internal static StringSpan GetExtension(StringSpan filePath) #endif - private static bool IsUri(StringSpan filePath) => !filePath.IsEmpty && filePath.Contains("://".AsSpan(), StringComparison.Ordinal); + private static bool IsUri(StringSpan filePath) => + !filePath.IsEmpty && filePath.Contains("://".AsSpan(), StringComparison.Ordinal); } diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/SpanExtensions.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/SpanExtensions.cs index ce74163aa..f0c987e41 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/SpanExtensions.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/SpanExtensions.cs @@ -15,7 +15,8 @@ internal static class SpanExtensions /// The delimiter to use. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char separator, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char separator, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new CharSpanSplitEnumerator(span, separator, int.MaxValue, splitOptions); } @@ -28,7 +29,8 @@ internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char /// The maximum number of elements to return. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char separator, int count, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char separator, int count, + StringSplitOptions splitOptions = StringSplitOptions.None) { if (count < 0) { @@ -45,7 +47,8 @@ internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, char /// The separator to use. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new CharSpanSplitEnumerator(span, separator, int.MaxValue, splitOptions); } @@ -58,7 +61,8 @@ internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, Read /// The maximum number of elements to return. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, int count, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, int count, + StringSplitOptions splitOptions = StringSplitOptions.None) { if (count < 0) { @@ -75,7 +79,8 @@ internal static CharSpanSplitEnumerator Split(this ReadOnlySpan span, Read /// The separator to use. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static StringSplitEnumerator Split(this ReadOnlySpan span, string separator, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static StringSplitEnumerator Split(this ReadOnlySpan span, string separator, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new StringSplitEnumerator(span, separator, int.MaxValue, splitOptions); } @@ -88,7 +93,8 @@ internal static StringSplitEnumerator Split(this ReadOnlySpan span, string /// The maximum number of elements to return. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static StringSplitEnumerator Split(this ReadOnlySpan span, string separator, int count, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static StringSplitEnumerator Split(this ReadOnlySpan span, string separator, int count, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new StringSplitEnumerator(span, separator, count, splitOptions); } @@ -100,7 +106,8 @@ internal static StringSplitEnumerator Split(this ReadOnlySpan span, string /// The separator to use. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static StringSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static StringSplitEnumerator Split(this ReadOnlySpan span, ReadOnlySpan separator, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new StringSplitEnumerator(span, separator, int.MaxValue, splitOptions); } @@ -113,7 +120,8 @@ internal static StringSplitEnumerator Split(this ReadOnlySpan span, ReadOn /// The maximum number of elements to return. /// enum indicating how split should function. /// A that can be enumerated to evaluate the segments. - internal static StringSplitEnumerator Split(this ReadOnlySpan span, string[] separator, int count, StringSplitOptions splitOptions = StringSplitOptions.None) + internal static StringSplitEnumerator Split(this ReadOnlySpan span, string[] separator, int count, + StringSplitOptions splitOptions = StringSplitOptions.None) { return new StringSplitEnumerator(span, separator, count, splitOptions); } @@ -174,12 +182,14 @@ internal CharSpanSplitEnumerator(ReadOnlySpan span, char separator, int co /// The separator to use. /// The maximum number of elements to return. /// enum indicating how split should function. - internal CharSpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, int count, StringSplitOptions splitOptions) + internal CharSpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, int count, + StringSplitOptions splitOptions) : this(span, default, separator, multiCharSeparator: true, count, splitOptions) { } - private CharSpanSplitEnumerator(ReadOnlySpan span, char separator, ReadOnlySpan separators, bool multiCharSeparator, int count, StringSplitOptions splitOptions) + private CharSpanSplitEnumerator(ReadOnlySpan span, char separator, ReadOnlySpan separators, + bool multiCharSeparator, int count, StringSplitOptions splitOptions) { this.splitOptions = splitOptions; this.separator = separator; @@ -188,25 +198,28 @@ private CharSpanSplitEnumerator(ReadOnlySpan span, char separator, ReadOnl if (multiCharSeparator) { - this.removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; + removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == + StringSplitOptions.RemoveEmptyEntries; #if NET5_0_OR_GREATER - this.trimEntries = (splitOptions & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries && !separators.IsEmpty; + this.trimEntries = + (splitOptions & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries && !separators.IsEmpty; #endif } else { - this.removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; + removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == + StringSplitOptions.RemoveEmptyEntries; #if NET5_0_OR_GREATER this.trimEntries = (splitOptions & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries; #endif } - this.originalSpan = span; - this.internalSpan = span; - this.originalCount = count; + originalSpan = span; + internalSpan = span; + originalCount = count; this.count = count; - this.Current = default; - this.endReached = false; + Current = default; + endReached = false; } /// @@ -226,36 +239,36 @@ private CharSpanSplitEnumerator(ReadOnlySpan span, char separator, ReadOnl /// indicating if there was another item. public bool MoveNext() { - if (this.endReached || this.count == 0) + if (endReached || count == 0) { return false; } - if (this.count == 1) + if (count == 1) { - return this.CalculateFinalItem(); + return CalculateFinalItem(); } while (true) { - int separatorIndex = this.GetSeparatorIndex(); + int separatorIndex = GetSeparatorIndex(); if (separatorIndex < 0) { - this.Current = this.internalSpan; - this.internalSpan = []; - this.endReached = true; + Current = internalSpan; + internalSpan = []; + endReached = true; - return this.NextSectionFound(); + return NextSectionFound(); } else { - this.Current = this.internalSpan.Slice(0, separatorIndex); - this.internalSpan = this.internalSpan.Slice(separatorIndex + 1); + Current = internalSpan.Slice(0, separatorIndex); + internalSpan = internalSpan.Slice(separatorIndex + 1); - if (this.NextSectionFound()) + if (NextSectionFound()) { - --this.count; + --count; return true; } @@ -268,10 +281,10 @@ public bool MoveNext() /// internal void Reset() { - this.internalSpan = this.originalSpan; - this.count = this.originalCount; - this.Current = default; - this.endReached = false; + internalSpan = originalSpan; + count = originalCount; + Current = default; + endReached = false; } /// @@ -281,14 +294,15 @@ internal void Reset() /// The array of . internal readonly string[] ToArray() { - int count = this.Count(); + int count = Count(); if (count == 0) { return []; } - CharSpanSplitEnumerator toArrayEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiCharSeparator, this.originalCount, this.splitOptions); + CharSpanSplitEnumerator toArrayEnumerator = + new(originalSpan, separator, separators, multiCharSeparator, originalCount, splitOptions); string[] result = new string[count]; for (int i = 0; i < result.Length && toArrayEnumerator.MoveNext(); ++i) @@ -306,7 +320,7 @@ internal readonly string[] ToArray() /// A of . internal readonly List ToList() { - int count = this.Count(); + int count = Count(); List result = new(count); if (count == 0) @@ -314,7 +328,9 @@ internal readonly List ToList() return result; } - CharSpanSplitEnumerator toArrayEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiCharSeparator, this.originalCount, this.splitOptions); + CharSpanSplitEnumerator toArrayEnumerator = + new(originalSpan, separator, separators, multiCharSeparator, originalCount, splitOptions); + foreach (ReadOnlySpan item in toArrayEnumerator) { result.Add(item.ToString()); @@ -331,7 +347,9 @@ internal readonly List ToList() internal readonly int Count() { int count = 0; - CharSpanSplitEnumerator countEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiCharSeparator, this.originalCount, this.splitOptions); + CharSpanSplitEnumerator countEnumerator = + new(originalSpan, separator, separators, multiCharSeparator, originalCount, splitOptions); + while (countEnumerator.MoveNext()) { ++count; @@ -347,7 +365,9 @@ internal readonly int Count() /// The first result or throws if there are none. internal readonly ReadOnlySpan First() { - CharSpanSplitEnumerator firstEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiCharSeparator, this.originalCount, this.splitOptions); + CharSpanSplitEnumerator firstEnumerator = + new(originalSpan, separator, separators, multiCharSeparator, originalCount, splitOptions); + if (!firstEnumerator.MoveNext()) { throw new InvalidOperationException(); @@ -363,7 +383,9 @@ internal readonly ReadOnlySpan First() /// The last result or throws if there are none. internal readonly ReadOnlySpan Last() { - CharSpanSplitEnumerator lastEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiCharSeparator, this.originalCount, this.splitOptions); + CharSpanSplitEnumerator lastEnumerator = + new(originalSpan, separator, separators, multiCharSeparator, originalCount, splitOptions); + ReadOnlySpan result = []; bool anyFound = false; @@ -383,25 +405,25 @@ internal readonly ReadOnlySpan Last() private readonly int GetSeparatorIndex() { - if (!this.multiCharSeparator) + if (!multiCharSeparator) { - return this.internalSpan.IndexOf(this.separator); + return internalSpan.IndexOf(separator); } - if (this.separators.Length != 0) + if (separators.Length != 0) { - return this.internalSpan.IndexOfAny(this.separators); + return internalSpan.IndexOfAny(separators); } - return this.internalSpan.IndexOfFirstWhitespaceCharacter(); + return internalSpan.IndexOfFirstWhitespaceCharacter(); } private bool CalculateFinalItem() { - if (this.removeEmptyEntries) + if (removeEmptyEntries) { int i = 0; - for (; i < this.internalSpan.Length; ++i) + for (; i < internalSpan.Length; ++i) { #if NET5_0_OR_GREATER if (this.trimEntries) @@ -420,37 +442,37 @@ private bool CalculateFinalItem() } } #endif - char currentChar = this.internalSpan[i]; + char currentChar = internalSpan[i]; - if (this.multiCharSeparator) + if (multiCharSeparator) { - if (!this.AnyMultiCharSeparatorMatches(currentChar)) + if (!AnyMultiCharSeparatorMatches(currentChar)) { break; } } - else if (currentChar != this.separator) + else if (currentChar != separator) { break; } } - if (i < this.internalSpan.Length) + if (i < internalSpan.Length) { - this.internalSpan = this.internalSpan.Slice(i); + internalSpan = internalSpan.Slice(i); } else { - this.internalSpan = []; + internalSpan = []; } } - this.count = 0; - this.endReached = true; - this.Current = this.internalSpan; - this.internalSpan = []; + count = 0; + endReached = true; + Current = internalSpan; + internalSpan = []; - return this.NextSectionFound(); + return NextSectionFound(); } private bool NextSectionFound() @@ -461,12 +483,12 @@ private bool NextSectionFound() this.Current = this.Current.Trim(); } #endif - return !this.removeEmptyEntries || !this.Current.IsEmpty; + return !removeEmptyEntries || !Current.IsEmpty; } private readonly bool AnyMultiCharSeparatorMatches(char currentChar) { - if (this.UseWhitespaceAsSeparator()) + if (UseWhitespaceAsSeparator()) { if (char.IsWhiteSpace(currentChar)) { @@ -475,9 +497,9 @@ private readonly bool AnyMultiCharSeparatorMatches(char currentChar) } else { - for (int i = 0; i < this.separators.Length; ++i) + for (int i = 0; i < separators.Length; ++i) { - if (currentChar == this.separators[i]) + if (currentChar == separators[i]) { return true; } @@ -489,7 +511,7 @@ private readonly bool AnyMultiCharSeparatorMatches(char currentChar) private readonly bool UseWhitespaceAsSeparator() { - return this.separators.Length == 0; + return separators.Length == 0; } } @@ -535,30 +557,33 @@ internal StringSplitEnumerator(ReadOnlySpan span, string separator, int co /// The separator to use. /// The maximum number of elements to return. /// enum indicating how split should function. - internal StringSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, int count, StringSplitOptions splitOptions) + internal StringSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, int count, + StringSplitOptions splitOptions) : this(span, [], separator, multiStringSeparator: true, count, splitOptions) { } - private StringSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, ReadOnlySpan separators, bool multiStringSeparator, int count, StringSplitOptions splitOptions) + private StringSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separator, ReadOnlySpan separators, + bool multiStringSeparator, int count, StringSplitOptions splitOptions) { this.splitOptions = splitOptions; this.separator = separator; this.separators = separators; this.multiStringSeparator = multiStringSeparator; - this.removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; - this.originalSpan = span; - this.internalSpan = span; - this.originalCount = count; + removeEmptyEntries = (splitOptions & StringSplitOptions.RemoveEmptyEntries) == StringSplitOptions.RemoveEmptyEntries; + originalSpan = span; + internalSpan = span; + originalCount = count; this.count = count; - this.Current = default; - this.endReached = false; + Current = default; + endReached = false; if (multiStringSeparator) { #if NET5_0_OR_GREATER - this.trimEntries = (splitOptions & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries && this.separators.Length > 0; + this.trimEntries = + (splitOptions & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries && this.separators.Length > 0; this.firstIteration = true; #endif } @@ -589,34 +614,34 @@ private StringSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separa public bool MoveNext() { // we were passed a count of 0 and should return an empty enumerator. - if (this.endReached || this.count == 0) + if (endReached || count == 0) { return false; } - if (this.count == 1) + if (count == 1) { - return this.CalculateFinalItem(); + return CalculateFinalItem(); } - if (!this.multiStringSeparator && this.separator.IsEmpty) + if (!multiStringSeparator && separator.IsEmpty) { - this.Current = this.internalSpan; - this.internalSpan = []; - this.endReached = true; + Current = internalSpan; + internalSpan = []; + endReached = true; - return this.NextSectionFound(); + return NextSectionFound(); } while (true) { - (int separatorIndex, int separatorLength) = this.GetNextSeparatorAndLength(); + (int separatorIndex, int separatorLength) = GetNextSeparatorAndLength(); if (separatorIndex < 0 || separatorLength < 0) { - this.Current = this.internalSpan; - this.internalSpan = []; - this.endReached = true; + Current = internalSpan; + internalSpan = []; + endReached = true; #if NET5_0_OR_GREATER if (this.trimEntries && (!this.firstIteration || !this.multiStringSeparator)) @@ -627,18 +652,18 @@ public bool MoveNext() this.firstIteration = false; #endif - return !this.removeEmptyEntries || !this.Current.IsEmpty; + return !removeEmptyEntries || !Current.IsEmpty; } else { - this.Current = this.internalSpan.Slice(0, separatorIndex); - this.internalSpan = this.internalSpan.Slice(separatorIndex + separatorLength); + Current = internalSpan.Slice(0, separatorIndex); + internalSpan = internalSpan.Slice(separatorIndex + separatorLength); #if NET5_0_OR_GREATER this.firstIteration = false; #endif - if (this.NextSectionFound()) + if (NextSectionFound()) { - --this.count; + --count; return true; } @@ -654,7 +679,9 @@ public bool MoveNext() internal readonly int Count() { int count = 0; - StringSplitEnumerator countEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiStringSeparator, this.originalCount, this.splitOptions); + StringSplitEnumerator countEnumerator = new(originalSpan, separator, separators, multiStringSeparator, originalCount, + splitOptions); + while (countEnumerator.MoveNext()) { ++count; @@ -670,7 +697,9 @@ internal readonly int Count() /// The first result or throws if there are none. internal readonly ReadOnlySpan First() { - StringSplitEnumerator firstEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiStringSeparator, this.originalCount, this.splitOptions); + StringSplitEnumerator firstEnumerator = new(originalSpan, separator, separators, multiStringSeparator, originalCount, + splitOptions); + if (!firstEnumerator.MoveNext()) { throw new InvalidOperationException(); @@ -686,7 +715,9 @@ internal readonly ReadOnlySpan First() /// The last result or throws if there are none. internal readonly ReadOnlySpan Last() { - StringSplitEnumerator lastEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiStringSeparator, this.originalCount, this.splitOptions); + StringSplitEnumerator lastEnumerator = new(originalSpan, separator, separators, multiStringSeparator, originalCount, + splitOptions); + ReadOnlySpan result = []; bool anyFound = false; while (lastEnumerator.MoveNext()) @@ -710,14 +741,15 @@ internal readonly ReadOnlySpan Last() /// The array of . internal readonly string[] ToArray() { - int count = this.Count(); + int count = Count(); if (count == 0) { return []; } - StringSplitEnumerator toArrayEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiStringSeparator, this.originalCount, this.splitOptions); + StringSplitEnumerator toArrayEnumerator = new(originalSpan, separator, separators, multiStringSeparator, + originalCount, splitOptions); string[] result = new string[count]; for (int i = 0; i < result.Length && toArrayEnumerator.MoveNext(); ++i) @@ -735,7 +767,7 @@ internal readonly string[] ToArray() /// A of . internal readonly List ToList() { - int count = this.Count(); + int count = Count(); List result = new(count); if (count == 0) @@ -743,7 +775,9 @@ internal readonly List ToList() return result; } - StringSplitEnumerator toArrayEnumerator = new(this.originalSpan, this.separator, this.separators, this.multiStringSeparator, this.originalCount, this.splitOptions); + StringSplitEnumerator toArrayEnumerator = new(originalSpan, separator, separators, multiStringSeparator, + originalCount, splitOptions); + foreach (ReadOnlySpan item in toArrayEnumerator) { result.Add(item.ToString()); @@ -757,29 +791,29 @@ internal readonly List ToList() /// internal void Reset() { - this.internalSpan = this.originalSpan; - this.count = this.originalCount; - this.Current = default; - this.endReached = false; + internalSpan = originalSpan; + count = originalCount; + Current = default; + endReached = false; } private readonly (int Index, int SeparatorLength) GetNextSeparatorAndLength() { - if (this.multiStringSeparator) + if (multiStringSeparator) { - return this.FindFirstSeparator(); + return FindFirstSeparator(); } else { - return (this.internalSpan.IndexOf(this.separator), this.separator.Length); + return (internalSpan.IndexOf(separator), separator.Length); } } private bool CalculateFinalItem() { - if (this.removeEmptyEntries) + if (removeEmptyEntries) { - while (!this.internalSpan.IsEmpty) + while (!internalSpan.IsEmpty) { #if NET5_0_OR_GREATER if (this.trimEntries) @@ -788,51 +822,51 @@ private bool CalculateFinalItem() } #endif - if (this.multiStringSeparator) + if (multiStringSeparator) { - if (!this.AnyMultiStringSeparatorMatches()) + if (!AnyMultiStringSeparatorMatches()) { break; } } else { - if (!this.internalSpan.StartsWith(this.separator, StringComparison.Ordinal)) + if (!internalSpan.StartsWith(separator, StringComparison.Ordinal)) { break; } - this.internalSpan = this.internalSpan.Slice(this.separator.Length); + internalSpan = internalSpan.Slice(separator.Length); } } } - this.count = 0; - this.endReached = true; - this.Current = this.internalSpan; - this.internalSpan = []; + count = 0; + endReached = true; + Current = internalSpan; + internalSpan = []; - return this.NextSectionFound(); + return NextSectionFound(); } private bool AnyMultiStringSeparatorMatches() { - if (this.UseWhitespaceAsSeparator()) + if (UseWhitespaceAsSeparator()) { - if (char.IsWhiteSpace(this.internalSpan[0])) + if (char.IsWhiteSpace(internalSpan[0])) { - this.internalSpan = this.internalSpan.Slice(1); + internalSpan = internalSpan.Slice(1); return true; } } else { - for (int i = 0; i < this.separators.Length; ++i) + for (int i = 0; i < separators.Length; ++i) { - ReadOnlySpan separatorSpan = this.separators[i].AsSpan(); - if (!separatorSpan.IsEmpty && this.internalSpan.StartsWith(separatorSpan, StringComparison.Ordinal)) + ReadOnlySpan separatorSpan = separators[i].AsSpan(); + if (!separatorSpan.IsEmpty && internalSpan.StartsWith(separatorSpan, StringComparison.Ordinal)) { - this.internalSpan = this.internalSpan.Slice(separatorSpan.Length); + internalSpan = internalSpan.Slice(separatorSpan.Length); return true; } } @@ -843,7 +877,7 @@ private bool AnyMultiStringSeparatorMatches() private readonly bool UseWhitespaceAsSeparator() { - return this.separators.Length == 0; + return separators.Length == 0; } private bool NextSectionFound() @@ -855,27 +889,27 @@ private bool NextSectionFound() } #endif - return !this.removeEmptyEntries || !this.Current.IsEmpty; + return !removeEmptyEntries || !Current.IsEmpty; } private readonly (int Index, int SeparatorLength) FindFirstSeparator() { // string.Split treats an empty array as split on whitespace. - if (this.UseWhitespaceAsSeparator()) + if (UseWhitespaceAsSeparator()) { - return (this.internalSpan.IndexOfFirstWhitespaceCharacter(), 1); + return (internalSpan.IndexOfFirstWhitespaceCharacter(), 1); } else { int index = -1; int separatorLength = -1; - for (int i = 0; i < this.separators.Length; ++i) + for (int i = 0; i < separators.Length; ++i) { - string currentSeparator = this.separators[i]; + string currentSeparator = separators[i]; if (!string.IsNullOrEmpty(currentSeparator)) { - int currentIndex = this.internalSpan.IndexOf(this.separators[i].AsSpan()); + int currentIndex = internalSpan.IndexOf(separators[i].AsSpan()); if (currentIndex >= 0 && (index < 0 || currentIndex < index)) { separatorLength = currentSeparator.Length; diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/StringExtensions.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/StringExtensions.cs index 1a720ebd0..e303818c4 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/StringExtensions.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/StringExtensions.cs @@ -8,27 +8,29 @@ internal static class StringExtensions /// internal static bool IsNullOrEmpty([NotNullWhen(returnValue: false)] this string? s) => string.IsNullOrEmpty(s); - internal static string? NullIfEmpty(this string? str) => IsNullOrEmpty(str) ? null : str; + internal static string? NullIfEmpty(this string? str) => str.IsNullOrEmpty() ? null : str; internal static Guid? NullIfEmpty(this Guid? guid) => guid == Guid.Empty ? null : guid; internal static Guid? NullIfEmpty(this Guid guid) => guid == Guid.Empty ? null : guid; - internal static bool EqualsOrdinal(this StringSpan span, StringSpan str) => MemoryExtensions.Equals(span, str, StringComparison.Ordinal); + internal static bool EqualsOrdinal(this StringSpan span, StringSpan str) => span.Equals(str, StringComparison.Ordinal); - internal static bool EqualsOrdinalIgnoreCase(this StringSpan span, StringSpan str) => MemoryExtensions.Equals(span, str, StringComparison.OrdinalIgnoreCase); + internal static bool EqualsOrdinalIgnoreCase(this StringSpan span, StringSpan str) => + span.Equals(str, StringComparison.OrdinalIgnoreCase); #if NETFRAMEWORK || NETSTANDARD - internal static bool EqualsOrdinal(this StringSpan span, string str) => EqualsOrdinal(span, str.AsSpan()); + internal static bool EqualsOrdinal(this StringSpan span, string str) => span.EqualsOrdinal(str.AsSpan()); - internal static bool EqualsOrdinalIgnoreCase(this StringSpan span, string str) => EqualsOrdinalIgnoreCase(span, str.AsSpan()); + internal static bool EqualsOrdinalIgnoreCase(this StringSpan span, string str) => span.EqualsOrdinalIgnoreCase(str.AsSpan()); internal static int IndexOf(this StringSpan span, string str) => span.IndexOf(str.AsSpan()); internal static bool StartsWith(this StringSpan span, string str) => span.StartsWith(str.AsSpan()); - internal static bool StartsWith(this StringSpan span, string str, StringComparison comparisonType) => span.StartsWith(str.AsSpan(), comparisonType); + internal static bool StartsWith(this StringSpan span, string str, StringComparison comparisonType) => + span.StartsWith(str.AsSpan(), comparisonType); internal static bool StartsWith(this string str, char value) => !str.IsNullOrEmpty() && str[0] == value; diff --git a/src/Persistence/Fallout.Persistence.Solution/Utilities/StringTokenizer.cs b/src/Persistence/Fallout.Persistence.Solution/Utilities/StringTokenizer.cs index 75b864bcc..217a0c713 100644 --- a/src/Persistence/Fallout.Persistence.Solution/Utilities/StringTokenizer.cs +++ b/src/Persistence/Fallout.Persistence.Solution/Utilities/StringTokenizer.cs @@ -10,141 +10,143 @@ namespace Fallout.Persistence.Solution.Utilities; /// internal ref struct StringTokenizer { - private readonly string line; - private int currentPos; private StringSpan state; internal StringTokenizer(string? str) { - this.IsNull = str is null; - this.line = str ?? string.Empty; - this.currentPos = 0; - this.state = this.line.AsSpan(); + IsNull = str is null; + StringLine = str ?? string.Empty; + CurrentPos = 0; + state = StringLine.AsSpan(); } internal bool IsNull { get; } - internal readonly bool IsEmpty => this.state.IsEmpty; + internal readonly bool IsEmpty => state.IsEmpty; // First char in remaining line, '\0' if empty. - internal readonly char CurrentChar => this.state.IsEmpty ? '\0' : this.state[0]; + internal readonly char CurrentChar => state.IsEmpty ? '\0' : state[0]; - internal readonly StringSpan Current => this.state; + internal readonly StringSpan Current => state; - internal readonly int CurrentPos => this.currentPos; + internal int CurrentPos { get; private set; } - internal readonly string StringLine => this.line; + internal string StringLine { get; } // charact in given position, or 0 if index is out of bounds. - internal readonly char this[int index] => index >= 0 && index < this.state.Length ? this.state[index] : '\0'; + internal readonly char this[int index] => index >= 0 && index < state.Length ? state[index] : '\0'; // both use the same semantic as VS parser, with minor reduction in slicing and dicing ... internal StringSpan NextToken(string delimiters) { - if (this.IsEmpty) + if (IsEmpty) { return StringSpan.Empty; } int skipLeading = 0; - while (skipLeading < this.state.Length && delimiters.Contains(this.state[skipLeading])) + while (skipLeading < state.Length && delimiters.Contains(state[skipLeading])) { skipLeading++; } int nextDelimiter = skipLeading; - while (nextDelimiter < this.state.Length && delimiters.IndexOf(this.state[nextDelimiter]) < 0) + while (nextDelimiter < state.Length && delimiters.IndexOf(state[nextDelimiter]) < 0) { nextDelimiter++; } - return this.GetNextToken(skipLeading, nextDelimiter); + return GetNextToken(skipLeading, nextDelimiter); } internal StringSpan NextTokenKeep(char delimiter) { - if (this.IsEmpty) + if (IsEmpty) { return StringSpan.Empty; } int skipLeading = 0; - while (skipLeading < this.state.Length && this.state[skipLeading] == delimiter) + while (skipLeading < state.Length && state[skipLeading] == delimiter) { skipLeading++; } int nextDelimiter = skipLeading; - while (nextDelimiter < this.state.Length && this.state[nextDelimiter] != delimiter) + while (nextDelimiter < state.Length && state[nextDelimiter] != delimiter) { nextDelimiter++; } - StringSpan result = nextDelimiter > skipLeading ? this.state.Slice(skipLeading, nextDelimiter - skipLeading) : StringSpan.Empty; - this.currentPos += nextDelimiter; - this.state = this.state.Slice(nextDelimiter); + StringSpan result = nextDelimiter > skipLeading + ? state.Slice(skipLeading, nextDelimiter - skipLeading) + : StringSpan.Empty; + + CurrentPos += nextDelimiter; + state = state.Slice(nextDelimiter); return result; } internal StringSpan NextToken(char delimiter) { - if (this.IsEmpty) + if (IsEmpty) { return StringSpan.Empty; } int skipLeading = 0; - while (skipLeading < this.state.Length && this.state[skipLeading] == delimiter) + while (skipLeading < state.Length && state[skipLeading] == delimiter) { skipLeading++; } int nextDelimiter = skipLeading; - while (nextDelimiter < this.state.Length && this.state[nextDelimiter] != delimiter) + while (nextDelimiter < state.Length && state[nextDelimiter] != delimiter) { nextDelimiter++; } - return this.GetNextToken(skipLeading, nextDelimiter); + return GetNextToken(skipLeading, nextDelimiter); } internal void TrimStart() { - int old = this.state.Length; - this.state = this.state.TrimStart(); - this.currentPos += old - this.state.Length; + int old = state.Length; + state = state.TrimStart(); + CurrentPos += old - state.Length; } internal readonly bool StartsWithAt(string match, int pos) { - if (string.IsNullOrEmpty(match) || this.state.Length < match.Length + pos) + if (string.IsNullOrEmpty(match) || state.Length < match.Length + pos) { return false; } - return this.state.Slice(pos).StartsWith(match); + return state.Slice(pos).StartsWith(match); } internal readonly bool StartsWith(string match) { - if (string.IsNullOrEmpty(match) || this.state.Length < match.Length) + if (string.IsNullOrEmpty(match) || state.Length < match.Length) { return false; } - return this.state.StartsWith(match); + return state.StartsWith(match); } // will advance tokenizer if it starts with the specified prefix, but only if it is followed by whitesapce or end of line. - internal bool SliceIfStartsWithAndEmptyAfter(string prefix) => this[prefix.Length].IsWhiteSpace() && this.SliceIfStartsWith(prefix); + internal bool SliceIfStartsWithAndEmptyAfter(string prefix) => + this[prefix.Length].IsWhiteSpace() && SliceIfStartsWith(prefix); // will advance tokenizer if it starts with the specified prefix. internal bool SliceIfStartsWith(string prefix) { - if (this.StartsWith(prefix)) + if (StartsWith(prefix)) { - this.Slice(prefix.Length); + Slice(prefix.Length); return true; } @@ -158,29 +160,29 @@ internal void Slice(int start) return; } - if (start >= 0 && start < this.state.Length) + if (start >= 0 && start < state.Length) { - this.currentPos += start; - this.state = this.state.Slice(start); + CurrentPos += start; + state = state.Slice(start); } else { - this.currentPos += this.state.Length; - this.state = StringSpan.Empty; + CurrentPos += state.Length; + state = StringSpan.Empty; } } internal void TrimStartAndSkip(char c1, char c2 = (char)0) { - if (this.IsEmpty) + if (IsEmpty) { return; } int skipLeading = 0; - while (skipLeading < this.state.Length) + while (skipLeading < state.Length) { - char c = this.state[skipLeading]; + char c = state[skipLeading]; if (!char.IsWhiteSpace(c) && c != c1 && c != c2) { break; @@ -189,49 +191,51 @@ internal void TrimStartAndSkip(char c1, char c2 = (char)0) skipLeading++; } - this.Slice(skipLeading); + Slice(skipLeading); } internal void Skip(string toSkip) { - if (this.IsEmpty || string.IsNullOrEmpty(toSkip)) + if (IsEmpty || string.IsNullOrEmpty(toSkip)) { return; } int skipLeading = 0; - while (skipLeading < this.state.Length && toSkip.Contains(this.state[skipLeading])) + while (skipLeading < state.Length && toSkip.Contains(state[skipLeading])) { skipLeading++; } - this.Slice(skipLeading); + Slice(skipLeading); } - internal void SkipAll() => this.Slice(-1); + internal void SkipAll() => Slice(-1); internal void Reset() { - this.currentPos = 0; - this.state = this.line.AsSpan(); + CurrentPos = 0; + state = StringLine.AsSpan(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private StringSpan GetNextToken(int skipLeading, int nextDelimiter) { - StringSpan result = nextDelimiter > skipLeading ? this.state.Slice(skipLeading, nextDelimiter - skipLeading) : StringSpan.Empty; + StringSpan result = nextDelimiter > skipLeading + ? state.Slice(skipLeading, nextDelimiter - skipLeading) + : StringSpan.Empty; // note +1 capture the case when a delimiter is the last character. The code would always remove the closing delimiter if any. nextDelimiter++; - if (nextDelimiter < this.state.Length) + if (nextDelimiter < state.Length) { - this.currentPos += nextDelimiter; - this.state = this.state.Slice(nextDelimiter); + CurrentPos += nextDelimiter; + state = state.Slice(nextDelimiter); } else { - this.currentPos += this.state.Length; - this.state = StringSpan.Empty; + CurrentPos += state.Length; + state = StringSpan.Empty; } return result; diff --git a/src/Persistence/Fallout.Solution/Model.cs b/src/Persistence/Fallout.Solution/Model.cs index cdc84fe94..a40405a60 100644 --- a/src/Persistence/Fallout.Solution/Model.cs +++ b/src/Persistence/Fallout.Solution/Model.cs @@ -4,18 +4,20 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; -using Fallout.Persistence.Solution.Model; -using Fallout.Persistence.Solution.Serializer; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; +using Fallout.Persistence.Solution.Model; +using Fallout.Persistence.Solution.Serializer; namespace Fallout.Solutions; public interface IProjectContainer { IProjectContainer Parent { get; } + IReadOnlyCollection Projects { get; } + IReadOnlyCollection SolutionFolders { get; } } @@ -37,7 +39,7 @@ public static Project GetProject(this IProjectContainer container, string name) public class Solution(SolutionModel model, AbsolutePath path = null) : IProjectContainer, IAbsolutePathHolder { - private ConcurrentDictionary wrappers { get; } = new(); + private ConcurrentDictionary wrappers { get; } = new(); internal T GetOrCreate(object model) { @@ -55,15 +57,22 @@ internal T GetOrCreate(object model) public SolutionModel GetModel() => model; public AbsolutePath Path { get; set; } = path; + public string Name => Path?.NameWithoutExtension; + public string FileName => Path?.Name; + public AbsolutePath Directory => Path?.Parent; public IReadOnlyCollection AllProjects => model.SolutionProjects.Select(GetOrCreate).ToList(); - public IReadOnlyCollection AllSolutionFolders => model.SolutionFolders.Select(GetOrCreate).ToList(); + + public IReadOnlyCollection AllSolutionFolders => + model.SolutionFolders.Select(GetOrCreate).ToList(); IProjectContainer IProjectContainer.Parent => null; + public IReadOnlyCollection Projects => AllProjects.Where(x => x.Parent == this).ToList(); + public IReadOnlyCollection SolutionFolders => AllSolutionFolders.Where(x => x.Parent == this).ToList(); public static implicit operator string(Solution solution) => solution.Path; @@ -75,6 +84,7 @@ public IEnumerable GetAllProjects(string wildcardPattern) var regex = new Regex(wildcardPattern .Replace(".", "\\.") .Replace("*", ".*")); + return AllProjects.Where(x => regex.IsMatch(x.Name)); } @@ -91,6 +101,7 @@ public class SolutionItem(SolutionItemModel model, Solution solution) public string Name => model.ActualDisplayName; public Solution Solution => solution; + public IProjectContainer Parent => (IProjectContainer)model.Parent?.Apply(solution.GetOrCreate) ?? solution; public override string ToString() => model.ActualDisplayName; @@ -101,7 +112,9 @@ public class SolutionFolder(SolutionFolderModel model, Solution solution) : Solu public SolutionFolderModel GetModel() => model; public IReadOnlyCollection Projects => Solution.AllProjects.Where(x => x.Parent == this).ToList(); - public IReadOnlyCollection SolutionFolders => Solution.AllSolutionFolders.Where(x => x.Parent == this).ToList(); + + public IReadOnlyCollection SolutionFolders => + Solution.AllSolutionFolders.Where(x => x.Parent == this).ToList(); } public class Project(SolutionProjectModel model, Solution solution) : SolutionItem(model, solution), IAbsolutePathHolder @@ -109,8 +122,11 @@ public class Project(SolutionProjectModel model, Solution solution) : SolutionIt public SolutionProjectModel GetModel() => model; public string RelativePath => model.FilePath; + public AbsolutePath Path => Solution.Directory.NotNull() / model.FilePath; + public string FileName => System.IO.Path.GetFileName(RelativePath); + public AbsolutePath Directory => Path?.Parent; public static implicit operator string(Project project) => project.Path; diff --git a/src/Persistence/Fallout.Solution/SolutionModelExtensions.cs b/src/Persistence/Fallout.Solution/SolutionModelExtensions.cs index 015634ee0..da8f011d8 100644 --- a/src/Persistence/Fallout.Solution/SolutionModelExtensions.cs +++ b/src/Persistence/Fallout.Solution/SolutionModelExtensions.cs @@ -1,8 +1,8 @@ using System.Threading; -using Fallout.Persistence.Solution.Serializer; using Fallout.Common; using Fallout.Common.IO; using Fallout.Common.Utilities; +using Fallout.Persistence.Solution.Serializer; namespace Fallout.Solutions; diff --git a/src/Shims/Nuke.Build/ShimMarker.cs b/src/Shims/Nuke.Build/ShimMarker.cs index eda952a72..52cad7269 100644 --- a/src/Shims/Nuke.Build/ShimMarker.cs +++ b/src/Shims/Nuke.Build/ShimMarker.cs @@ -2,6 +2,8 @@ // namespace begins with "Fallout.Build." into the corresponding "Nuke.Build." // namespace. -[assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder( +using Fallout.Migrate.Shims; + +[assembly: ShimAllPublicTypesUnder( fromNamespacePrefix: "Fallout.Build", toNamespacePrefix: "Nuke.Build")] diff --git a/src/Shims/Nuke.Common/CI/AppVeyor/AppVeyor.cs b/src/Shims/Nuke.Common/CI/AppVeyor/AppVeyor.cs index dcb0a5a9b..5ea52b87d 100644 --- a/src/Shims/Nuke.Common/CI/AppVeyor/AppVeyor.cs +++ b/src/Shims/Nuke.Common/CI/AppVeyor/AppVeyor.cs @@ -10,12 +10,12 @@ namespace Nuke.Common.CI.AppVeyor; public static class AppVeyor { - public static global::Fallout.Common.CI.AppVeyor.AppVeyor Instance - => global::Fallout.Common.CI.AppVeyor.AppVeyor.Instance; + public static Fallout.Common.CI.AppVeyor.AppVeyor Instance + => Fallout.Common.CI.AppVeyor.AppVeyor.Instance; public static int MessageLimit { - get => global::Fallout.Common.CI.AppVeyor.AppVeyor.MessageLimit; - set => global::Fallout.Common.CI.AppVeyor.AppVeyor.MessageLimit = value; + get => Fallout.Common.CI.AppVeyor.AppVeyor.MessageLimit; + set => Fallout.Common.CI.AppVeyor.AppVeyor.MessageLimit = value; } } diff --git a/src/Shims/Nuke.Common/CI/AzurePipelines/AzurePipelines.cs b/src/Shims/Nuke.Common/CI/AzurePipelines/AzurePipelines.cs index e84165af5..283ff0cb7 100644 --- a/src/Shims/Nuke.Common/CI/AzurePipelines/AzurePipelines.cs +++ b/src/Shims/Nuke.Common/CI/AzurePipelines/AzurePipelines.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.AzurePipelines; public static class AzurePipelines { - public static global::Fallout.Common.CI.AzurePipelines.AzurePipelines Instance - => global::Fallout.Common.CI.AzurePipelines.AzurePipelines.Instance; + public static Fallout.Common.CI.AzurePipelines.AzurePipelines Instance + => Fallout.Common.CI.AzurePipelines.AzurePipelines.Instance; } diff --git a/src/Shims/Nuke.Common/CI/Bamboo/Bamboo.cs b/src/Shims/Nuke.Common/CI/Bamboo/Bamboo.cs index b13fa265c..1ce858fc0 100644 --- a/src/Shims/Nuke.Common/CI/Bamboo/Bamboo.cs +++ b/src/Shims/Nuke.Common/CI/Bamboo/Bamboo.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.Bamboo; public static class Bamboo { - public static global::Fallout.Common.CI.Bamboo.Bamboo Instance - => global::Fallout.Common.CI.Bamboo.Bamboo.Instance; + public static Fallout.Common.CI.Bamboo.Bamboo Instance + => Fallout.Common.CI.Bamboo.Bamboo.Instance; } diff --git a/src/Shims/Nuke.Common/CI/Bitbucket/Bitbucket.cs b/src/Shims/Nuke.Common/CI/Bitbucket/Bitbucket.cs index 0a9aeef82..936460a44 100644 --- a/src/Shims/Nuke.Common/CI/Bitbucket/Bitbucket.cs +++ b/src/Shims/Nuke.Common/CI/Bitbucket/Bitbucket.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.Bitbucket; public static class Bitbucket { - public static global::Fallout.Common.CI.Bitbucket.Bitbucket Instance - => global::Fallout.Common.CI.Bitbucket.Bitbucket.Instance; + public static Fallout.Common.CI.Bitbucket.Bitbucket Instance + => Fallout.Common.CI.Bitbucket.Bitbucket.Instance; } diff --git a/src/Shims/Nuke.Common/CI/Bitrise/Bitrise.cs b/src/Shims/Nuke.Common/CI/Bitrise/Bitrise.cs index 91b4c8773..8ca8185b6 100644 --- a/src/Shims/Nuke.Common/CI/Bitrise/Bitrise.cs +++ b/src/Shims/Nuke.Common/CI/Bitrise/Bitrise.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.Bitrise; public static class Bitrise { - public static global::Fallout.Common.CI.Bitrise.Bitrise Instance - => global::Fallout.Common.CI.Bitrise.Bitrise.Instance; + public static Fallout.Common.CI.Bitrise.Bitrise Instance + => Fallout.Common.CI.Bitrise.Bitrise.Instance; } diff --git a/src/Shims/Nuke.Common/CI/GitHubActions/GitHubActions.cs b/src/Shims/Nuke.Common/CI/GitHubActions/GitHubActions.cs index a6fa845e7..1dab98b3b 100644 --- a/src/Shims/Nuke.Common/CI/GitHubActions/GitHubActions.cs +++ b/src/Shims/Nuke.Common/CI/GitHubActions/GitHubActions.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.GitHubActions; public static class GitHubActions { - public static global::Fallout.Common.CI.GitHubActions.GitHubActions Instance - => global::Fallout.Common.CI.GitHubActions.GitHubActions.Instance; + public static Fallout.Common.CI.GitHubActions.GitHubActions Instance + => Fallout.Common.CI.GitHubActions.GitHubActions.Instance; } diff --git a/src/Shims/Nuke.Common/CI/GitLab/GitLab.cs b/src/Shims/Nuke.Common/CI/GitLab/GitLab.cs index c21b46478..8e05f7e05 100644 --- a/src/Shims/Nuke.Common/CI/GitLab/GitLab.cs +++ b/src/Shims/Nuke.Common/CI/GitLab/GitLab.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.GitLab; public static class GitLab { - public static global::Fallout.Common.CI.GitLab.GitLab Instance - => global::Fallout.Common.CI.GitLab.GitLab.Instance; + public static Fallout.Common.CI.GitLab.GitLab Instance + => Fallout.Common.CI.GitLab.GitLab.Instance; } diff --git a/src/Shims/Nuke.Common/CI/Jenkins/Jenkins.cs b/src/Shims/Nuke.Common/CI/Jenkins/Jenkins.cs index 645cdee85..9a4442ade 100644 --- a/src/Shims/Nuke.Common/CI/Jenkins/Jenkins.cs +++ b/src/Shims/Nuke.Common/CI/Jenkins/Jenkins.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.Jenkins; public static class Jenkins { - public static global::Fallout.Common.CI.Jenkins.Jenkins Instance - => global::Fallout.Common.CI.Jenkins.Jenkins.Instance; + public static Fallout.Common.CI.Jenkins.Jenkins Instance + => Fallout.Common.CI.Jenkins.Jenkins.Instance; } diff --git a/src/Shims/Nuke.Common/CI/TeamCity/TeamCity.cs b/src/Shims/Nuke.Common/CI/TeamCity/TeamCity.cs index 9a80ef75b..68b3166a2 100644 --- a/src/Shims/Nuke.Common/CI/TeamCity/TeamCity.cs +++ b/src/Shims/Nuke.Common/CI/TeamCity/TeamCity.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.TeamCity; public static class TeamCity { - public static global::Fallout.Common.CI.TeamCity.TeamCity Instance - => global::Fallout.Common.CI.TeamCity.TeamCity.Instance; + public static Fallout.Common.CI.TeamCity.TeamCity Instance + => Fallout.Common.CI.TeamCity.TeamCity.Instance; } diff --git a/src/Shims/Nuke.Common/CI/TravisCI/TravisCI.cs b/src/Shims/Nuke.Common/CI/TravisCI/TravisCI.cs index 59928cf63..c86c20128 100644 --- a/src/Shims/Nuke.Common/CI/TravisCI/TravisCI.cs +++ b/src/Shims/Nuke.Common/CI/TravisCI/TravisCI.cs @@ -6,6 +6,6 @@ namespace Nuke.Common.CI.TravisCI; public static class TravisCI { - public static global::Fallout.Common.CI.TravisCI.TravisCI Instance - => global::Fallout.Common.CI.TravisCI.TravisCI.Instance; + public static Fallout.Common.CI.TravisCI.TravisCI Instance + => Fallout.Common.CI.TravisCI.TravisCI.Instance; } diff --git a/src/Shims/Nuke.Common/ShimMarker.cs b/src/Shims/Nuke.Common/ShimMarker.cs index 9c643102c..c364df95e 100644 --- a/src/Shims/Nuke.Common/ShimMarker.cs +++ b/src/Shims/Nuke.Common/ShimMarker.cs @@ -11,16 +11,22 @@ // a Fallout-side entry-point grace shim (#257 mitigation) — without this // exclusion, Rule 1 and the Fallout.Solutions rule would both emit // Nuke.Common.ProjectModel.Solution, colliding (CS0263/CS0111). -[assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder( + +using Fallout.Migrate.Shims; + +[assembly: ShimAllPublicTypesUnder( fromNamespacePrefix: "Fallout.Common", toNamespacePrefix: "Nuke.Common", - ExceptNamespacePrefixes = new[] { "Fallout.Common.ProjectModel" })] + ExceptNamespacePrefixes = new[] + { + "Fallout.Common.ProjectModel" + })] // The solution-handling types moved from Fallout.Common.ProjectModel to the // dedicated Fallout.Solutions namespace in v11 (see #248 and the broader // onion-layering work). For NUKE-era consumers, mirror them into the legacy // Nuke.Common.ProjectModel namespace so existing `using Nuke.Common.ProjectModel;` // + `[Solution] readonly Solution Solution;` keep compiling. -[assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder( +[assembly: ShimAllPublicTypesUnder( fromNamespacePrefix: "Fallout.Solutions", toNamespacePrefix: "Nuke.Common.ProjectModel")] diff --git a/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs b/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs index 4bbe9b7a7..55a2bfdbc 100644 --- a/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs +++ b/src/Shims/Nuke.Common/Utilities/DelegateDisposable.cs @@ -12,11 +12,11 @@ namespace Nuke.Common.Utilities; public static class DelegateDisposable { public static IDisposable CreateBracket(Action setup = null, Action cleanup = null) - => global::Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); + => Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); public static IDisposable CreateBracket(Func setup, Action cleanup) - => global::Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); + => Fallout.Common.Utilities.DelegateDisposable.CreateBracket(setup, cleanup); public static IDisposable SetAndRestore(Expression> memberProvider, T value) - => global::Fallout.Common.Utilities.DelegateDisposable.SetAndRestore(memberProvider, value); + => Fallout.Common.Utilities.DelegateDisposable.SetAndRestore(memberProvider, value); } diff --git a/src/Shims/Nuke.Components/IHazArtifacts.cs b/src/Shims/Nuke.Components/IHazArtifacts.cs index 54e591464..062a1b895 100644 --- a/src/Shims/Nuke.Components/IHazArtifacts.cs +++ b/src/Shims/Nuke.Components/IHazArtifacts.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasArtifacts. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazArtifacts : IHasArtifacts { } +[Obsolete( + "Renamed to IHasArtifacts. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazArtifacts : IHasArtifacts +{ +} diff --git a/src/Shims/Nuke.Components/IHazChangelog.cs b/src/Shims/Nuke.Components/IHazChangelog.cs index 8f053fcc9..f63f5767d 100644 --- a/src/Shims/Nuke.Components/IHazChangelog.cs +++ b/src/Shims/Nuke.Components/IHazChangelog.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasChangelog. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazChangelog : IHasChangelog { } +[Obsolete( + "Renamed to IHasChangelog. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazChangelog : IHasChangelog +{ +} diff --git a/src/Shims/Nuke.Components/IHazConfiguration.cs b/src/Shims/Nuke.Components/IHazConfiguration.cs index f9d2d2069..9c26beb4e 100644 --- a/src/Shims/Nuke.Components/IHazConfiguration.cs +++ b/src/Shims/Nuke.Components/IHazConfiguration.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasConfiguration. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazConfiguration : IHasConfiguration { } +[Obsolete( + "Renamed to IHasConfiguration. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazConfiguration : IHasConfiguration +{ +} diff --git a/src/Shims/Nuke.Components/IHazGitRepository.cs b/src/Shims/Nuke.Components/IHazGitRepository.cs index 69cda3e90..ff4b580e3 100644 --- a/src/Shims/Nuke.Components/IHazGitRepository.cs +++ b/src/Shims/Nuke.Components/IHazGitRepository.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasGitRepository. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazGitRepository : IHasGitRepository { } +[Obsolete( + "Renamed to IHasGitRepository. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazGitRepository : IHasGitRepository +{ +} diff --git a/src/Shims/Nuke.Components/IHazGitVersion.cs b/src/Shims/Nuke.Components/IHazGitVersion.cs index d0fbe8b4a..b99a2b2fa 100644 --- a/src/Shims/Nuke.Components/IHazGitVersion.cs +++ b/src/Shims/Nuke.Components/IHazGitVersion.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasGitVersion. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazGitVersion : IHasGitVersion { } +[Obsolete( + "Renamed to IHasGitVersion. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazGitVersion : IHasGitVersion +{ +} diff --git a/src/Shims/Nuke.Components/IHazNerdbankGitVersioning.cs b/src/Shims/Nuke.Components/IHazNerdbankGitVersioning.cs index 8258e8ea9..24f216c33 100644 --- a/src/Shims/Nuke.Components/IHazNerdbankGitVersioning.cs +++ b/src/Shims/Nuke.Components/IHazNerdbankGitVersioning.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasNerdbankGitVersioning. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazNerdbankGitVersioning : IHasNerdbankGitVersioning { } +[Obsolete( + "Renamed to IHasNerdbankGitVersioning. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazNerdbankGitVersioning : IHasNerdbankGitVersioning +{ +} diff --git a/src/Shims/Nuke.Components/IHazReports.cs b/src/Shims/Nuke.Components/IHazReports.cs index d6180c67a..566eb23c9 100644 --- a/src/Shims/Nuke.Components/IHazReports.cs +++ b/src/Shims/Nuke.Components/IHazReports.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasReports. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazReports : IHasReports { } +[Obsolete( + "Renamed to IHasReports. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazReports : IHasReports +{ +} diff --git a/src/Shims/Nuke.Components/IHazSolution.cs b/src/Shims/Nuke.Components/IHazSolution.cs index 831ac55a4..6898117a0 100644 --- a/src/Shims/Nuke.Components/IHazSolution.cs +++ b/src/Shims/Nuke.Components/IHazSolution.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasSolution. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazSolution : IHasSolution { } +[Obsolete( + "Renamed to IHasSolution. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazSolution : IHasSolution +{ +} diff --git a/src/Shims/Nuke.Components/IHazTwitterCredentials.cs b/src/Shims/Nuke.Components/IHazTwitterCredentials.cs index 9a38057ca..c8171642e 100644 --- a/src/Shims/Nuke.Components/IHazTwitterCredentials.cs +++ b/src/Shims/Nuke.Components/IHazTwitterCredentials.cs @@ -2,5 +2,8 @@ namespace Nuke.Components; -[Obsolete("Renamed to IHasTwitterCredentials. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] -public interface IHazTwitterCredentials : IHasTwitterCredentials { } +[Obsolete( + "Renamed to IHasTwitterCredentials. The IHaz* names were legacy NUKE conventions; renamed for clarity in Fallout v11. This alias is shipped only in the Nuke.Components transition shim and will be removed in v12.")] +public interface IHazTwitterCredentials : IHasTwitterCredentials +{ +} diff --git a/src/Shims/Nuke.Components/ShimMarker.cs b/src/Shims/Nuke.Components/ShimMarker.cs index a107f92bc..a8859de40 100644 --- a/src/Shims/Nuke.Components/ShimMarker.cs +++ b/src/Shims/Nuke.Components/ShimMarker.cs @@ -3,6 +3,8 @@ // "Nuke.Components" namespace. The bulk of this assembly is the component // interface family (ICompile, IRestore, IPack, ITest, IPublish, IHas*). -[assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder( +using Fallout.Migrate.Shims; + +[assembly: ShimAllPublicTypesUnder( fromNamespacePrefix: "Fallout.Components", toNamespacePrefix: "Nuke.Components")] diff --git a/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxFixtureBuilder.cs b/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxFixtureBuilder.cs index 54a498b62..9f9045372 100644 --- a/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxFixtureBuilder.cs +++ b/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxFixtureBuilder.cs @@ -42,6 +42,7 @@ public static string Build(int projectCount, bool withFolders) { AppendProject(sb, p, indent: " "); } + sb.AppendLine(" "); } } diff --git a/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxParseBenchmarks.cs b/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxParseBenchmarks.cs index 714c4e293..d08c722d9 100644 --- a/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxParseBenchmarks.cs +++ b/tests/Benchmarks/Fallout.Persistence.Solution.Benchmarks/SlnxParseBenchmarks.cs @@ -13,7 +13,7 @@ namespace Fallout.Persistence.Solution.Benchmarks; // Each iteration measures `serializer.OpenAsync(path, ct)` end-to-end on a // pre-staged temp .slnx file. Fixture generation is in `[GlobalSetup]` so // its cost is excluded from measurements. -[MemoryDiagnoser] // alloc + GC pressure are interesting alongside time +[MemoryDiagnoser] // alloc + GC pressure are interesting alongside time [BenchmarkCategory("slnx", "parse")] public class SlnxParseBenchmarks { @@ -36,7 +36,9 @@ public void Setup() public void Cleanup() { if (File.Exists(_fixturePath)) + { File.Delete(_fixturePath); + } } [Benchmark] diff --git a/tests/Consumers/Fallout.Consumer.Local/Build.cs b/tests/Consumers/Fallout.Consumer.Local/Build.cs index 71da5a48e..bf7d79793 100644 --- a/tests/Consumers/Fallout.Consumer.Local/Build.cs +++ b/tests/Consumers/Fallout.Consumer.Local/Build.cs @@ -3,8 +3,10 @@ // public Fallout surface in the current PR. using Fallout.Common; -using Fallout.Common.IO; -using Fallout.Solutions; // was Fallout.Common.ProjectModel; — renamed in #254 (persistence layering + namespace cleanup) +using Fallout.Solutions; +using Serilog; + +// was Fallout.Common.ProjectModel; — renamed in #254 (persistence layering + namespace cleanup) internal class Build : FalloutBuild { @@ -16,7 +18,7 @@ internal class Build : FalloutBuild private Target Default => _ => _ .Executes(() => { - Serilog.Log.Information("hello from fallout consumer (local source)"); - Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? ""); + Log.Information("hello from fallout consumer (local source)"); + Log.Information("solution name: {Name}", Solution?.Name ?? ""); }); } diff --git a/tests/Consumers/Fallout.Consumer.NuGet/Build.cs b/tests/Consumers/Fallout.Consumer.NuGet/Build.cs index 704f9e3d6..1db1504dc 100644 --- a/tests/Consumers/Fallout.Consumer.NuGet/Build.cs +++ b/tests/Consumers/Fallout.Consumer.NuGet/Build.cs @@ -4,8 +4,8 @@ // consumer's perspective. using Fallout.Common; -using Fallout.Common.IO; using Fallout.Common.ProjectModel; +using Serilog; internal class Build : FalloutBuild { @@ -17,7 +17,7 @@ internal class Build : FalloutBuild private Target Default => _ => _ .Executes(() => { - Serilog.Log.Information("hello from fallout consumer (pinned nuget 11.0.8)"); - Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? ""); + Log.Information("hello from fallout consumer (pinned nuget 11.0.8)"); + Log.Information("solution name: {Name}", Solution?.Name ?? ""); }); } diff --git a/tests/Consumers/Fallout.Consumer.ProjectModelShim/Build.cs b/tests/Consumers/Fallout.Consumer.ProjectModelShim/Build.cs index fa1f82c03..8d8c0b444 100644 --- a/tests/Consumers/Fallout.Consumer.ProjectModelShim/Build.cs +++ b/tests/Consumers/Fallout.Consumer.ProjectModelShim/Build.cs @@ -5,8 +5,10 @@ // users upgrading across the SolutionModel → Solution / Fallout.Solutions rename. using Fallout.Common; -using Fallout.Common.IO; -using Fallout.Common.ProjectModel; // the interim namespace, now served by the transition shim +using Fallout.Common.ProjectModel; +using Serilog; + +// the interim namespace, now served by the transition shim internal class Build : FalloutBuild { @@ -18,7 +20,7 @@ internal class Build : FalloutBuild private Target Default => _ => _ .Executes(() => { - Serilog.Log.Information("hello from fallout consumer (Fallout.Common.ProjectModel shim)"); - Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? ""); + Log.Information("hello from fallout consumer (Fallout.Common.ProjectModel shim)"); + Log.Information("solution name: {Name}", Solution?.Name ?? ""); }); } diff --git a/tests/Consumers/Nuke.Consumer/Build.cs b/tests/Consumers/Nuke.Consumer/Build.cs index 812d2003d..5857ef506 100644 --- a/tests/Consumers/Nuke.Consumer/Build.cs +++ b/tests/Consumers/Nuke.Consumer/Build.cs @@ -5,10 +5,8 @@ // users from silent breakage. using Nuke.Common; -using Nuke.Common.IO; using Nuke.Common.ProjectModel; -using Nuke.Components; - +using Serilog; // The shim generator skips delegates by C# language limitation (see SHIM002 — // can't subclass a delegate cross-assembly). `Target` is a delegate in // Fallout.Common, so NUKE-era code referencing `Target` needs either @@ -26,7 +24,7 @@ internal class Build : NukeBuild private Target Default => _ => _ .Executes(() => { - Serilog.Log.Information("hello from nuke consumer (via shim)"); - Serilog.Log.Information("solution name: {Name}", Solution?.Name ?? ""); + Log.Information("hello from nuke consumer (via shim)"); + Log.Information("solution name: {Name}", Solution?.Name ?? ""); }); } diff --git a/tests/Fallout.Build.Specs/BlockBannerGeometrySpecs.cs b/tests/Fallout.Build.Specs/BlockBannerGeometrySpecs.cs index 71c6d3a15..1bfcd663f 100644 --- a/tests/Fallout.Build.Specs/BlockBannerGeometrySpecs.cs +++ b/tests/Fallout.Build.Specs/BlockBannerGeometrySpecs.cs @@ -99,6 +99,7 @@ private static string Capture(Action action) // leak a live console-writing host into the rest of the collection. var instanceProperty = typeof(Host).GetProperty("Instance", ReflectionUtility.Static) .NotNull("typeof(Host).GetProperty(\"Instance\") != null"); + var originalInstance = instanceProperty.GetValue(obj: null); using var writer = new StringWriter(); diff --git a/tests/Fallout.Build.Specs/BuildContextSpecs.cs b/tests/Fallout.Build.Specs/BuildContextSpecs.cs index 73f0c47ba..365b5d92f 100644 --- a/tests/Fallout.Build.Specs/BuildContextSpecs.cs +++ b/tests/Fallout.Build.Specs/BuildContextSpecs.cs @@ -1,10 +1,9 @@ using System; using System.Reflection; -using FluentAssertions; using Fallout.Common.Execution; -using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.ValueInjection; +using FluentAssertions; using Serilog.Events; using Serilog.Parsing; using Xunit; @@ -83,7 +82,7 @@ public void Dispose_resets_the_tool_path_resolver_configuration() { NuGetToolPathResolver.EmbeddedPackagesDirectory = "/packages"; NuGetToolPathResolver.NuGetPackagesConfigFile = "/packages.config"; - NpmToolPathResolver.NpmPackageJsonFile = (AbsolutePath)"/npm/package.json"; + NpmToolPathResolver.NpmPackageJsonFile = "/npm/package.json"; } NuGetToolPathResolver.EmbeddedPackagesDirectory.Should().BeNull(); @@ -99,7 +98,9 @@ public void Dispose_clears_the_value_injection_cache() var subject = new Subject(); using (BuildContext.Activate()) + { ValueInjectionUtility.TryGetValue(() => subject.Value).Should().Be("1"); + } // The context's teardown cleared the cache, so this read re-injects rather than replaying the // value the previous run computed. @@ -130,7 +131,9 @@ public void Dispose_runs_the_per_run_teardown() var sentinel = $"build-context-teardown-{Guid.NewGuid()}"; using (BuildContext.Activate()) + { sink.Emit(CreateLogEvent(sentinel)); + } // Leaving the scope disposes the context, whose teardown clears the shared in-memory sink so a // subsequent run in the same process starts clean. Assert on the sentinel specifically so a diff --git a/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs b/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs index 00b8b9484..3aac0a829 100644 --- a/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs +++ b/tests/Fallout.Build.Specs/BuildExecutorSpecs.cs @@ -1,17 +1,31 @@ using System; using System.Linq; -using FluentAssertions; using Fallout.Common.Execution; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs.Execution; public class BuildExecutorSpecs { - private ExecutableTarget A = new() { Name = nameof(A), Status = ExecutionStatus.Scheduled }; - private ExecutableTarget B = new() { Name = nameof(B), Status = ExecutionStatus.Scheduled }; - private ExecutableTarget C = new() { Name = nameof(C), Status = ExecutionStatus.Scheduled }; + private readonly ExecutableTarget A = new() + { + Name = nameof(A), + Status = ExecutionStatus.Scheduled + }; + + private readonly ExecutableTarget B = new() + { + Name = nameof(B), + Status = ExecutionStatus.Scheduled + }; + + private readonly ExecutableTarget C = new() + { + Name = nameof(C), + Status = ExecutionStatus.Scheduled + }; public BuildExecutorSpecs() { @@ -34,7 +48,11 @@ public void TestDefault() [Fact] public void TestParameterSkipped() { - ExecuteBuild(skippedTargets: new[] { A }); + ExecuteBuild(skippedTargets: new[] + { + A + }); + AssertSucceeded(B, C); AssertSkipped(A); A.Skipped.Should().Be("via parameter"); @@ -64,7 +82,11 @@ public void TestParameterSkipped_Invoked() public void TestParameterSkipped_DependencyBehavior_Skip() { B.DependencyBehavior = DependencyBehavior.Skip; - ExecuteBuild(skippedTargets: new[] { B }); + ExecuteBuild(skippedTargets: new[] + { + B + }); + AssertSucceeded(C); AssertSkipped(A, B); A.Skipped.Should().Be("because of B"); @@ -193,8 +215,20 @@ private void ExecuteBuild(ExecutableTarget[] skippedTargets = null) static string[] SelectNames(ExecutableTarget[] targets) => targets?.Select(x => x.Name).ToArray(); var build = new TestBuild(); - build.ExecutableTargets = new[] { A, B, C }; - build.ExecutionPlan = new[] { A, B, C }; + build.ExecutableTargets = new[] + { + A, + B, + C + }; + + build.ExecutionPlan = new[] + { + A, + B, + C + }; + BuildExecutor.Execute(build, SelectNames(skippedTargets)); } diff --git a/tests/Fallout.Build.Specs/BuildGraphUtilitySpecs.cs b/tests/Fallout.Build.Specs/BuildGraphUtilitySpecs.cs index 38776eb30..85f2458fb 100644 --- a/tests/Fallout.Build.Specs/BuildGraphUtilitySpecs.cs +++ b/tests/Fallout.Build.Specs/BuildGraphUtilitySpecs.cs @@ -24,16 +24,32 @@ public class BuildGraphUtilitySpecs // A representative graph exercising every emitted field and relation kind. private static IReadOnlyCollection SampleGraph() { - var restore = new ExecutableTarget { Name = "Restore", Listed = true }; + var restore = new ExecutableTarget + { + Name = "Restore", + Listed = true + }; + var compile = new ExecutableTarget - { - Name = "Compile", - Description = "Builds all projects", - Listed = true, - Member = MemberOf(nameof(SampleBuild.Compile)), - }; - var test = new ExecutableTarget { Name = "Test", Listed = true, IsDefault = true }; - var publish = new ExecutableTarget { Name = "Publish", Listed = false }; + { + Name = "Compile", + Description = "Builds all projects", + Listed = true, + Member = MemberOf(nameof(SampleBuild.Compile)), + }; + + var test = new ExecutableTarget + { + Name = "Test", + Listed = true, + IsDefault = true + }; + + var publish = new ExecutableTarget + { + Name = "Publish", + Listed = false + }; compile.ExecutionDependencies.Add(restore); test.ExecutionDependencies.Add(compile); @@ -42,7 +58,13 @@ private static IReadOnlyCollection SampleGraph() compile.Triggers.Add(publish); // Deliberately unsorted so the ordinal ordering guarantee is exercised. - return new[] { test, publish, compile, restore }; + return new[] + { + test, + publish, + compile, + restore + }; } [Fact] @@ -86,12 +108,30 @@ public void Relation_kinds_map_to_their_own_fields() [Fact] public void Dependency_lists_are_ordered_ordinally() { - var target = new ExecutableTarget { Name = "Root" }; - target.ExecutionDependencies.Add(new ExecutableTarget { Name = "Zebra" }); - target.ExecutionDependencies.Add(new ExecutableTarget { Name = "Alpha" }); - target.ExecutionDependencies.Add(new ExecutableTarget { Name = "Mango" }); - - var model = BuildGraphUtility.GetModel(new[] { target }, SampleVersion).Targets.Single(); + var target = new ExecutableTarget + { + Name = "Root" + }; + + target.ExecutionDependencies.Add(new ExecutableTarget + { + Name = "Zebra" + }); + + target.ExecutionDependencies.Add(new ExecutableTarget + { + Name = "Alpha" + }); + + target.ExecutionDependencies.Add(new ExecutableTarget + { + Name = "Mango" + }); + + var model = BuildGraphUtility.GetModel(new[] + { + target + }, SampleVersion).Targets.Single(); model.DependsOn.Should().Equal("Alpha", "Mango", "Zebra"); } @@ -127,6 +167,7 @@ public void Optional_string_fields_are_emitted_as_null_rather_than_omitted() var restore = doc.RootElement.GetProperty("targets").EnumerateArray() .Single(x => x.GetProperty("name").GetString() == "Restore"); + restore.GetProperty("description").ValueKind.Should().Be(JsonValueKind.Null); restore.GetProperty("declaredIn").ValueKind.Should().Be(JsonValueKind.Null); } diff --git a/tests/Fallout.Build.Specs/BuiltInParameterTextSpecs.cs b/tests/Fallout.Build.Specs/BuiltInParameterTextSpecs.cs index 01f367156..394746039 100644 --- a/tests/Fallout.Build.Specs/BuiltInParameterTextSpecs.cs +++ b/tests/Fallout.Build.Specs/BuiltInParameterTextSpecs.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; @@ -22,7 +21,10 @@ public static TheoryData BuiltInParameters { var data = new TheoryData(); foreach (var (name, description) in GetBuiltInParameters()) + { data.Add(name, description); + } + return data; } } diff --git a/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs index a96bb821a..621ab0910 100644 --- a/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs +++ b/tests/Fallout.Build.Specs/CompletionUtilitySpecs.cs @@ -1,11 +1,9 @@ -using System; using System.Collections.Generic; -using System.Linq; using System.Text.Json; using System.Threading.Tasks; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Utilities; +using FluentAssertions; using VerifyTests; using VerifyXunit; using Xunit; @@ -15,7 +13,9 @@ namespace Fallout.Common.Specs; public class CompletionUtilitySpecs { private readonly VerifySettings _verifySettings; + private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull(); + private static AbsolutePath TestDirectory => RootDirectory / "tests" / "Fallout.Build.Specs"; public CompletionUtilitySpecs() @@ -29,7 +29,11 @@ public async Task TestGetCompletionItemsTargetBuild() { var file = TestDirectory / "SchemaUtilitySpecs.TestTargetBuild.verified.json"; var schema = JsonDocument.Parse(file.ReadAllText()); - var items = CompletionUtility.GetItemsFromSchema(schema, new[] { "dev" }); + var items = CompletionUtility.GetItemsFromSchema(schema, new[] + { + "dev" + }); + await Verifier.Verify(items, _verifySettings); } @@ -46,70 +50,140 @@ public async Task TestGetCompletionItemsParameterBuild() public async Task TestGetCompletionItemsFromSchema() { var schema = JsonDocument.Parse(""" - { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Build Schema", - "definitions": { - "build": { - "type": "object", - "properties": { - "NoLogo": { - "type": "boolean", - "description": "Disables displaying the Fallout logo" - }, - "Configuration": { - "type": "string", - "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", - "enum": [ - "Debug", - "Release" - ] - }, - "Profile": { - "type": "array", - "description": "Defines the profiles to load", - "items": { - "type": "string" - } - }, - "Target": { - "type": "array", - "description": "List of targets to be invoked. Default is '{default_target}'", - "items": { - "type": "string", - "enum": [ - "Restore", - "Compile" - ] - } - } - } - } - } - } - """); - var profileNames = new[] { "dev" }; + { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Build Schema", + "definitions": { + "build": { + "type": "object", + "properties": { + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the Fallout logo" + }, + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "type": "string", + "enum": [ + "Restore", + "Compile" + ] + } + } + } + } + } + } + """); + + var profileNames = new[] + { + "dev" + }; + var items = CompletionUtility.GetItemsFromSchema(schema, profileNames); await Verifier.Verify(items, _verifySettings); } [Theory] - [InlineData("", new[] { "Compile", "GitHub-Publish", "--target", "--api-key", "--nuget-source" })] - [InlineData("-", new[] { "--target", "--api-key", "--nuget-source" })] - [InlineData("-t", new[] { "-target" })] - [InlineData("-Api", new[] { "-ApiKey" })] - [InlineData("--api", new[] { "--api-key" })] - [InlineData("--Nu", new[] { "--Nuget-source" })] - [InlineData("-ApiKey ", new[] { "--target", "--nuget-source" })] - [InlineData("--api-key ", new[] { "--target", "--nuget-source" })] - [InlineData("--target ", new[] { "Compile", "GitHub-Publish", "--api-key", "--nuget-source" })] - [InlineData("--target G", new[] { "GitHub-Publish" })] - [InlineData("--target -", new[] { "--api-key", "--nuget-source" })] - [InlineData("--target compile ", new[] { "GitHub-Publish", "--api-key", "--nuget-source" })] - [InlineData("C", new[] { "Compile" })] - [InlineData("Compile ", new[] { "GitHub-Publish", "--target", "--api-key", "--nuget-source" })] - [InlineData("Compile gith", new[] { "github-publish" })] - [InlineData("Compile GITH", new[] { "GITHUB-PUBLISH" })] + [InlineData("", new[] + { + "Compile", + "GitHub-Publish", + "--target", + "--api-key", + "--nuget-source" + })] + [InlineData("-", new[] + { + "--target", + "--api-key", + "--nuget-source" + })] + [InlineData("-t", new[] + { + "-target" + })] + [InlineData("-Api", new[] + { + "-ApiKey" + })] + [InlineData("--api", new[] + { + "--api-key" + })] + [InlineData("--Nu", new[] + { + "--Nuget-source" + })] + [InlineData("-ApiKey ", new[] + { + "--target", + "--nuget-source" + })] + [InlineData("--api-key ", new[] + { + "--target", + "--nuget-source" + })] + [InlineData("--target ", new[] + { + "Compile", + "GitHub-Publish", + "--api-key", + "--nuget-source" + })] + [InlineData("--target G", new[] + { + "GitHub-Publish" + })] + [InlineData("--target -", new[] + { + "--api-key", + "--nuget-source" + })] + [InlineData("--target compile ", new[] + { + "GitHub-Publish", + "--api-key", + "--nuget-source" + })] + [InlineData("C", new[] + { + "Compile" + })] + [InlineData("Compile ", new[] + { + "GitHub-Publish", + "--target", + "--api-key", + "--nuget-source" + })] + [InlineData("Compile gith", new[] + { + "github-publish" + })] + [InlineData("Compile GITH", new[] + { + "GITHUB-PUBLISH" + })] public void TestGetRelevantCompletionItems(string words, string[] expectedItems) { var completionItems = @@ -119,6 +193,7 @@ public void TestGetRelevantCompletionItems(string words, string[] expectedItems) { "ApiKey", [] }, { "NuGetSource", [] } }; + CompletionUtility.GetRelevantItems(words, completionItems) .Should() .BeEquivalentTo(expectedItems); diff --git a/tests/Fallout.Build.Specs/ConsoleOutputCharacterSpecs.cs b/tests/Fallout.Build.Specs/ConsoleOutputCharacterSpecs.cs index 36f9cc753..308afcd65 100644 --- a/tests/Fallout.Build.Specs/ConsoleOutputCharacterSpecs.cs +++ b/tests/Fallout.Build.Specs/ConsoleOutputCharacterSpecs.cs @@ -21,13 +21,19 @@ public class ConsoleOutputCharacterSpecs /// Leading spaces on the logo's tagline lines. private const int TaglineIndent = 21; - public static TheoryData BlankTexts => new() { null, string.Empty, " ", " " }; + public static TheoryData BlankTexts => new() + { + null, + string.Empty, + " ", + " " + }; [Theory] [MemberData(nameof(BlankTexts))] public void The_ansi_theme_renders_blank_text_as_an_empty_string(string text) { - var theme = (IHostTheme)AnsiConsoleHostTheme.Default256AnsiColorTheme; + var theme = AnsiConsoleHostTheme.Default256AnsiColorTheme; theme.FormatInformation(text).Should().BeEmpty(); } @@ -36,7 +42,7 @@ public void The_ansi_theme_renders_blank_text_as_an_empty_string(string text) [MemberData(nameof(BlankTexts))] public void The_system_theme_renders_blank_text_as_an_empty_string(string text) { - var theme = (IHostTheme)SystemConsoleHostTheme.DefaultSystemColorTheme; + var theme = SystemConsoleHostTheme.DefaultSystemColorTheme; theme.FormatInformation(text).Should().BeEmpty(); } @@ -44,8 +50,8 @@ public void The_system_theme_renders_blank_text_as_an_empty_string(string text) [Fact] public void The_two_themes_agree_on_blank_text() { - var ansi = (IHostTheme)AnsiConsoleHostTheme.Default256AnsiColorTheme; - var system = (IHostTheme)SystemConsoleHostTheme.DefaultSystemColorTheme; + var ansi = AnsiConsoleHostTheme.Default256AnsiColorTheme; + var system = SystemConsoleHostTheme.DefaultSystemColorTheme; ansi.FormatInformation(text: null).Should().Be(system.FormatInformation(text: null)); } @@ -53,7 +59,7 @@ public void The_two_themes_agree_on_blank_text() [Fact] public void Blank_themed_output_carries_no_zero_width_space() { - var theme = (IHostTheme)AnsiConsoleHostTheme.Default256AnsiColorTheme; + var theme = AnsiConsoleHostTheme.Default256AnsiColorTheme; theme.FormatInformation(text: null).Should().NotContain(ZeroWidthSpace); } diff --git a/tests/Fallout.Build.Specs/ControlFlowSpecs.cs b/tests/Fallout.Build.Specs/ControlFlowSpecs.cs index 8d35a70f3..c728e082c 100644 --- a/tests/Fallout.Build.Specs/ControlFlowSpecs.cs +++ b/tests/Fallout.Build.Specs/ControlFlowSpecs.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using FluentAssertions; using Xunit; @@ -18,7 +17,9 @@ void OnSecondExecution() { executions++; if (executions != 2) + { throw new Exception(executions.ToString()); + } } ControlFlow.ExecuteWithRetry(OnSecondExecution); diff --git a/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs b/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs index 6521aafe5..71be7dc89 100644 --- a/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs +++ b/tests/Fallout.Build.Specs/DefaultInterfaceExecutionSpecs.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using System.Linq.Expressions; -using FluentAssertions; using Fallout.Common.Execution; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs.Execution; @@ -135,7 +135,8 @@ public void TestDeclaringAndImplementingComponent() private interface IParameterInterface : IFalloutBuild { - [Parameter] string StringParameter => TryGetValue(() => StringParameter); + [Parameter] + string StringParameter => TryGetValue(() => StringParameter); Target HelloWorld => _ => _ .Requires(() => StringParameter) @@ -144,7 +145,9 @@ private interface IParameterInterface }); } - private class ParameterBuild : FalloutBuild, IParameterInterface { } + private class ParameterBuild : FalloutBuild, IParameterInterface + { + } private class TestBuild : FalloutBuild, ITestBuild { @@ -186,9 +189,13 @@ private class OverriddenDuplicatedTargetTestBuild : FalloutBuild, ITestBuild, ID private interface ITestBuild { string Description => DefaultInterfaceExecutionSpecs.Description; + Action Action => DefaultInterfaceExecutionSpecs.Action; + Expression> Requirement => DefaultInterfaceExecutionSpecs.Requirement; + Func StaticCondition => DefaultInterfaceExecutionSpecs.StaticCondition; + Func DynamicCondition => DefaultInterfaceExecutionSpecs.DynamicCondition; Target A => _ => _ diff --git a/tests/Fallout.Build.Specs/DuplicateParameterSpecs.cs b/tests/Fallout.Build.Specs/DuplicateParameterSpecs.cs index 344d1fbaf..62f633611 100644 --- a/tests/Fallout.Build.Specs/DuplicateParameterSpecs.cs +++ b/tests/Fallout.Build.Specs/DuplicateParameterSpecs.cs @@ -1,7 +1,5 @@ -using System; using System.Linq; using System.Reflection; -using Fallout.Common.Execution; using Fallout.Common.ValueInjection; using FluentAssertions; using Xunit; @@ -38,7 +36,12 @@ public void The_build_class_declaration_wins_over_the_interface() public void Parameters_that_do_not_collide_are_all_kept() { GetDashedNames() - .Should().Contain(new[] { "api-key", "only-on-the-interface", "only-on-the-build" }); + .Should().Contain(new[] + { + "api-key", + "only-on-the-interface", + "only-on-the-build" + }); } [Fact] diff --git a/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs b/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs index 668117743..282ef4d15 100644 --- a/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs +++ b/tests/Fallout.Build.Specs/ExecutableTargetFactorySpecs.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using System.Linq.Expressions; -using FluentAssertions; using Fallout.Common.Execution; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs.Execution; @@ -49,11 +49,11 @@ public void Test() private class TestBuild : FalloutBuild { - public string Description = "description"; - public Action Action = () => { }; - public Expression> Requirement = () => true; - public Func StaticCondition = () => true; - public Func DynamicCondition = () => false; + public readonly string Description = "description"; + public readonly Action Action = () => { }; + public readonly Expression> Requirement = () => true; + public readonly Func StaticCondition = () => true; + public readonly Func DynamicCondition = () => false; public Target A => _ => _ .Description(Description) @@ -111,6 +111,7 @@ private interface ITestSharedBuild .Executes(() => { }); Target AbstractSharedTarget { get; } + Target ExplicitSharedTarget => _ => _.Description("WRONG"); } @@ -131,6 +132,7 @@ private abstract class TestIntermediateBuild : TestBaseBuild, ITestSharedBuild .DependsOn(SpecificTarget); public abstract Target AbstractSharedTarget { get; } + Target ITestSharedBuild.ExplicitSharedTarget => _ => _.Description("RIGHT"); } @@ -138,8 +140,11 @@ private interface IAnotherSharedBuild : IFalloutBuild { Target ExplicitTargetWithDefault => _ => _.Description("WRONG"); + Target ExplicitTargetWithoutDefault { get; } + Target TargetWithDefault => _ => _.Description("RIGHT"); + Target ExplicitTarget => _ => _; } @@ -156,7 +161,9 @@ private class TestFinalBuild : TestIntermediateBuild, IAnotherSharedBuild public override Target AbstractSharedTarget => _ => _.Description("RIGHT"); Target IAnotherSharedBuild.ExplicitTargetWithDefault => _ => _.Description("RIGHT"); + Target IAnotherSharedBuild.ExplicitTargetWithoutDefault => _ => _.Description("RIGHT"); + Target IAnotherSharedBuild.ExplicitTarget => _ => _; } } diff --git a/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs b/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs index 1586198d3..44fb9df88 100644 --- a/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs +++ b/tests/Fallout.Build.Specs/ExecutionPlannerSpecs.cs @@ -1,33 +1,60 @@ -using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions; using Fallout.Common.Execution; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs.Execution; public class ExecutionPlannerSpecs { - private ExecutableTarget A = new() { Name = nameof(A), IsDefault = true }; - private ExecutableTarget B = new() { Name = nameof(B) }; - private ExecutableTarget C = new() { Name = nameof(C) }; + private readonly ExecutableTarget A = new() + { + Name = nameof(A), + IsDefault = true + }; + + private readonly ExecutableTarget B = new() + { + Name = nameof(B) + }; + + private readonly ExecutableTarget C = new() + { + Name = nameof(C) + }; [Fact] public void TestDefault() { - GetPlan().Should().BeEquivalentTo(new[] { A }); + GetPlan().Should().BeEquivalentTo(new[] + { + A + }); } [Fact] public void TestInvoked() { - GetPlan(invokedTargets: new[] { B }).Should().Equal(B); + GetPlan(invokedTargets: new[] + { + B + }).Should().Equal(B); + A.Invoked.Should().BeFalse(); B.Invoked.Should().BeTrue(); C.Invoked.Should().BeFalse(); - GetPlan(invokedTargets: new[] { A, B }).Should().BeEquivalentTo(new[] { A, B }); + GetPlan(invokedTargets: new[] + { + A, + B + }).Should().BeEquivalentTo(new[] + { + A, + B + }); + A.Invoked.Should().BeTrue(); B.Invoked.Should().BeTrue(); C.Invoked.Should().BeFalse(); @@ -58,10 +85,20 @@ public void TestOrderDependencies() { B.OrderDependencies.Add(A); C.OrderDependencies.Add(A); - GetPlan(invokedTargets: new[] { B, A, C }).First().Should().Be(A); + GetPlan(invokedTargets: new[] + { + B, + A, + C + }).First().Should().Be(A); C.OrderDependencies.Add(B); - GetPlan(invokedTargets: new[] { A, C, B }).Should().Equal(A, B, C); + GetPlan(invokedTargets: new[] + { + A, + C, + B + }).Should().Equal(A, B, C); } [Fact] @@ -77,7 +114,10 @@ public void TestMixedDependencies() public void TestExternalDependency() { A.ExecutionDependencies.Add(B); - B.ExecutionDependencies.Add(new ExecutableTarget { Name = "External" }); + B.ExecutionDependencies.Add(new ExecutableTarget + { + Name = "External" + }); GetPlan().Should().Equal(B, A); } @@ -85,7 +125,10 @@ public void TestExternalDependency() [Fact] public void TestExternalTrigger() { - A.Triggers.Add(new ExecutableTarget { Name = "External" }); + A.Triggers.Add(new ExecutableTarget + { + Name = "External" + }); GetPlan().Should().Equal(A); } @@ -94,7 +137,12 @@ private IEnumerable GetPlan(ExecutableTarget[] invokedTargets { static string[] SelectNames(ExecutableTarget[] targets) => targets?.Select(x => x.Name).ToArray(); - return ExecutionPlanner.GetExecutionPlan(new[] { A, B, C }, SelectNames(invokedTargets)); + return ExecutionPlanner.GetExecutionPlan(new[] + { + A, + B, + C + }, SelectNames(invokedTargets)); } private void AddTrigger(ExecutableTarget source, ExecutableTarget target) diff --git a/tests/Fallout.Build.Specs/GitRepositorySpecs.cs b/tests/Fallout.Build.Specs/GitRepositorySpecs.cs index c87cbeafb..08402e41d 100644 --- a/tests/Fallout.Build.Specs/GitRepositorySpecs.cs +++ b/tests/Fallout.Build.Specs/GitRepositorySpecs.cs @@ -1,7 +1,8 @@ using System; +using System.Diagnostics; using System.IO; -using FluentAssertions; using Fallout.Common.Git; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -71,7 +72,9 @@ public void Fails_for_non_git_directory() finally { if (Directory.Exists(tempDir)) + { Directory.Delete(tempDir, recursive: true); + } } } @@ -129,7 +132,7 @@ public void Handles_duplicate_config_keys_caused_by_vs_code() private static void RunGit(string workingDirectory, string arguments) { - var startInfo = new System.Diagnostics.ProcessStartInfo("git", arguments) + var startInfo = new ProcessStartInfo("git", arguments) { WorkingDirectory = workingDirectory, RedirectStandardOutput = true, @@ -137,7 +140,7 @@ private static void RunGit(string workingDirectory, string arguments) UseShellExecute = false }; - using var process = System.Diagnostics.Process.Start(startInfo).NotNull(); + using var process = Process.Start(startInfo).NotNull(); process.WaitForExit(); process.ExitCode.Should().Be(0, process.StandardError.ReadToEnd()); } diff --git a/tests/Fallout.Build.Specs/GitRepositoryWorktreeSpecs.cs b/tests/Fallout.Build.Specs/GitRepositoryWorktreeSpecs.cs index 830be65c2..8244f4479 100644 --- a/tests/Fallout.Build.Specs/GitRepositoryWorktreeSpecs.cs +++ b/tests/Fallout.Build.Specs/GitRepositoryWorktreeSpecs.cs @@ -1,10 +1,10 @@ using System; using System.IO; using System.Linq; -using FluentAssertions; using Fallout.Common.Git; using Fallout.Common.IO; using Fallout.Common.Tooling; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -159,7 +159,8 @@ private static AbsolutePath CreateWorktree(AbsolutePath tempDir, string worktree // on a named branch. A tag-triggered release build checks out a detached HEAD, where // GitRepository.Branch is null; deriving the base ref from it previously produced an // invalid `git worktree add` invocation and failed the release pack. - ProcessTasks.StartProcess("git", $"worktree add -b {uniqueBranchName} {worktreePath} HEAD", workingDirectory: Directory.GetCurrentDirectory()) + ProcessTasks.StartProcess("git", $"worktree add -b {uniqueBranchName} {worktreePath} HEAD", + workingDirectory: Directory.GetCurrentDirectory()) .AssertZeroExitCode(); return worktreePath; @@ -168,7 +169,9 @@ private static AbsolutePath CreateWorktree(AbsolutePath tempDir, string worktree private static void CleanupWorktree(AbsolutePath worktreePath) { if (!worktreePath.DirectoryExists()) + { return; + } try { @@ -185,7 +188,9 @@ private static void CleanupWorktree(AbsolutePath worktreePath) private static void CleanupTemporaryDirectory(AbsolutePath tempDir) { if (!tempDir.DirectoryExists()) + { return; + } try { @@ -197,7 +202,8 @@ private static void CleanupTemporaryDirectory(AbsolutePath tempDir) } } - private static void AssertWorktreeRepository(GitRepository worktreeRepo, GitRepository mainRepo, AbsolutePath expectedWorktreePath) + private static void AssertWorktreeRepository(GitRepository worktreeRepo, GitRepository mainRepo, + AbsolutePath expectedWorktreePath) { worktreeRepo.Endpoint.Should().Be(mainRepo.Endpoint); worktreeRepo.Identifier.Should().Be(mainRepo.Identifier); diff --git a/tests/Fallout.Build.Specs/HostInitializer.cs b/tests/Fallout.Build.Specs/HostInitializer.cs index 730047626..f656e4f75 100644 --- a/tests/Fallout.Build.Specs/HostInitializer.cs +++ b/tests/Fallout.Build.Specs/HostInitializer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Runtime.CompilerServices; using Fallout.Common.Utilities; diff --git a/tests/Fallout.Build.Specs/InMemorySinkSpecs.cs b/tests/Fallout.Build.Specs/InMemorySinkSpecs.cs index dc7c0ec88..e254a2eaa 100644 --- a/tests/Fallout.Build.Specs/InMemorySinkSpecs.cs +++ b/tests/Fallout.Build.Specs/InMemorySinkSpecs.cs @@ -1,6 +1,6 @@ using System; -using FluentAssertions; using Fallout.Common.Execution; +using FluentAssertions; using Serilog.Events; using Serilog.Parsing; using Xunit; diff --git a/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs b/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs index baa03ca34..bf71bb164 100644 --- a/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs +++ b/tests/Fallout.Build.Specs/ParameterServiceSpecs.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using FluentAssertions; using Fallout.Common.IO; -using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; using static Fallout.Common.Utilities.ReflectionUtility; @@ -11,7 +9,8 @@ namespace Fallout.Common.Specs; public class ParameterServiceSpecs { - private ParameterService GetService(string[] commandLineArguments = null, IDictionary environmentVariables = null) + private ParameterService GetService(string[] commandLineArguments = null, + IDictionary environmentVariables = null) { commandLineArguments ??= new string[0]; environmentVariables ??= new Dictionary(); @@ -43,7 +42,11 @@ public void TestNotSupplied(Type destinationType, object expectedValue) [InlineData("switch3", typeof(bool), false)] [InlineData("switch4", typeof(bool), false)] [InlineData("switch5", typeof(bool), true)] - [InlineData("array1", typeof(string[]), new[] { "element1", "element2" })] + [InlineData("array1", typeof(string[]), new[] + { + "element1", + "element2" + })] [InlineData("array2", typeof(string[]), new string[0])] [InlineData("array3", typeof(string[]), null)] [InlineData("notsupplied1", typeof(bool), false)] @@ -68,6 +71,7 @@ public void TestEnvironmentVariables(string parameter, Type destinationType, obj { "array1", "element1+element2" }, { "array2", "" }, }); + service.GetParameter(parameter, destinationType, separator: '+').Should().BeEquivalentTo(expectedValue); } @@ -75,14 +79,14 @@ public void TestEnvironmentVariables(string parameter, Type destinationType, obj public void TestExpression() { var service = GetService( - [ - "--string", - "--set", - "1", - "2", - "3", - "--interface-param" - ]); + [ + "--string", + "--set", + "1", + "2", + "3", + "--interface-param" + ]); var build = new TestBuild(); @@ -93,9 +97,15 @@ public void TestExpression() .Should().BeOfType().Subject.Should().BeTrue(); ParameterService.GetFromMemberInfo(GetMemberInfo(() => build.Set), destinationType: null, service.GetParameter) - .Should().BeOfType().Subject.Should().BeEquivalentTo(new[] { 1, 2, 3 }); + .Should().BeOfType().Subject.Should().BeEquivalentTo(new[] + { + 1, + 2, + 3 + }); - ParameterService.GetFromMemberInfo(GetMemberInfo(() => ((ITestComponent)build).Param), destinationType: null, service.GetParameter) + ParameterService.GetFromMemberInfo(GetMemberInfo(() => ((ITestComponent)build).Param), destinationType: null, + service.GetParameter) .Should().BeOfType().Subject.Should().BeTrue(); } @@ -111,8 +121,10 @@ public void TestValueSet() (nameof(Verbosity.Quiet), Verbosity.Quiet), (nameof(Verbosity.Verbose), Verbosity.Verbose), }; + ParameterService.GetParameterValueSet(GetMemberInfo(() => FalloutBuild.Verbosity), instance: null) .Should().BeEquivalentTo(verbosities); + ParameterService.GetParameterValueSet(GetMemberInfo(() => build.Verbosities), instance: null) .Should().BeEquivalentTo(verbosities); } @@ -123,7 +135,11 @@ public void TestPrecedence() var emptyArguments = new ArgumentParser(string.Empty); var emptyEnvironmentVariables = new Dictionary(); - var environmentVariables = new Dictionary { ["string"] = "environmentVariables" }; + var environmentVariables = new Dictionary + { + ["string"] = "environmentVariables" + }; + var commandLineArguments = new ArgumentParser("--string commandLine"); var parameterFileArguments = new Func((_, _) => "parameterFile"); var commitMessageArguments = new ArgumentParser("--string commitMessage"); @@ -132,9 +148,14 @@ public void TestPrecedence() { ArgumentsFromFilesService = parameterFileArguments }; + service.GetParameter("string", typeof(string), separator: null).Should().Be("parameterFile"); - service = new ParameterService(() => emptyArguments, () => environmentVariables) { ArgumentsFromFilesService = parameterFileArguments }; + service = new ParameterService(() => emptyArguments, () => environmentVariables) + { + ArgumentsFromFilesService = parameterFileArguments + }; + service.GetParameter("string", typeof(string), separator: null).Should().Be("environmentVariables"); service = new ParameterService(() => commandLineArguments, () => environmentVariables); @@ -145,21 +166,28 @@ public void TestPrecedence() ArgumentsFromFilesService = parameterFileArguments, ArgumentsFromCommitMessageService = commitMessageArguments }; + service.GetParameter("string", typeof(string), separator: null).Should().Be("commitMessage"); } #pragma warning disable CS0649 private class TestBuild : FalloutBuild, ITestComponent { - [Parameter] public string String; - [Parameter] public int[] Set; - [Parameter] public Verbosity[] Verbosities; + [Parameter] + public string String; + + [Parameter] + public int[] Set; + + [Parameter] + public Verbosity[] Verbosities; } [ParameterPrefix("Interface")] private interface ITestComponent : IFalloutBuild { - [Parameter] bool Param => TryGetValue(() => Param) ?? false; + [Parameter] + bool Param => TryGetValue(() => Param) ?? false; } #pragma warning restore CS0649 } diff --git a/tests/Fallout.Build.Specs/PromptConfirmationGlyphSpecs.cs b/tests/Fallout.Build.Specs/PromptConfirmationGlyphSpecs.cs index 962365758..8a6903115 100644 --- a/tests/Fallout.Build.Specs/PromptConfirmationGlyphSpecs.cs +++ b/tests/Fallout.Build.Specs/PromptConfirmationGlyphSpecs.cs @@ -38,7 +38,8 @@ public void An_accepted_input_shows_the_value_after_the_check() [Fact] public void An_accepted_choice_is_marked_with_a_check() { - var console = Prompt(() => ConsoleUtility.PromptForChoice("Pick?", (Value: 1, Description: "first"), (Value: 2, Description: "second"))); + var console = Prompt(() => + ConsoleUtility.PromptForChoice("Pick?", (Value: 1, Description: "first"), (Value: 2, Description: "second"))); console.LastLine.Should().Be($"{CheckMark} first"); } @@ -91,7 +92,9 @@ private class FakeConsole : IConsole public string LastLine => Lines.Last(x => !x.IsNullOrWhiteSpace()).TrimEnd(); public int BufferWidth => 80; + public int CursorLeft { get; set; } + public int CursorTop { get; set; } public void Write(string value, Color? color = null) diff --git a/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs index 106a1ef41..4c311cacf 100644 --- a/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs +++ b/tests/Fallout.Build.Specs/SchemaUtilitySpecs.cs @@ -1,11 +1,8 @@ -using System; using System.ComponentModel; -using System.Linq; using System.Threading.Tasks; using Fallout.Common.Execution; using Fallout.Common.IO; using Fallout.Common.Tooling; -using Fallout.Common.Utilities; using VerifyXunit; using Xunit; @@ -53,39 +50,60 @@ private class EmptyBuild : FalloutBuild private class TargetBuild : FalloutBuild, ITargetComponent { Target RegularTarget => _ => _; + public Target ImplementedTarget => _ => _; + Target ITargetComponent.ExplicitTarget => _ => _; } private interface ITargetComponent : IFalloutBuild { Target InheritedTarget => _ => _; + Target ImplementedTarget => _ => _; + Target ExplicitTarget => _ => _; } private class ParameterBuild : FalloutBuild, IParameterComponent { - [Parameter] readonly string RegularParam; - [Parameter] [Secret] readonly string SecretParam; + [Parameter] + readonly string RegularParam; + + [Parameter] + [Secret] + readonly string SecretParam; + + [Parameter] + readonly bool BooleanParam; - [Parameter] readonly bool BooleanParam; - [Parameter] readonly bool? NullableBooleanParam; + [Parameter] + readonly bool? NullableBooleanParam; - [Parameter] readonly string[] StringArrayParam; - [Parameter] readonly int[] IntegerArrayParam; + [Parameter] + readonly string[] StringArrayParam; - [Parameter] readonly CustomEnumeration CustomEnumerationParam; - [Parameter] readonly CustomEnumeration[] CustomEnumerationArrayParam; + [Parameter] + readonly int[] IntegerArrayParam; - [Parameter] readonly ComplexType ComplexTypeParam; - [Parameter] readonly ComplexType[] ComplexTypeArrayParam; + [Parameter] + readonly CustomEnumeration CustomEnumerationParam; + + [Parameter] + readonly CustomEnumeration[] CustomEnumerationArrayParam; + + [Parameter] + readonly ComplexType ComplexTypeParam; + + [Parameter] + readonly ComplexType[] ComplexTypeArrayParam; } [ParameterPrefix("Component")] private interface IParameterComponent : IFalloutBuild { - [Parameter] string InheritedParam => TryGetValue(() => InheritedParam); + [Parameter] + string InheritedParam => TryGetValue(() => InheritedParam); } private class ComplexType @@ -104,8 +122,15 @@ private class ComplexSubType [TypeConverter(typeof(TypeConverter))] private class CustomEnumeration : Enumeration { - public static CustomEnumeration Debug = new() { Value = nameof(Debug) }; - public static CustomEnumeration Release = new() { Value = nameof(Release) }; + public static CustomEnumeration Debug = new() + { + Value = nameof(Debug) + }; + + public static CustomEnumeration Release = new() + { + Value = nameof(Release) + }; public static implicit operator string(CustomEnumeration configuration) { @@ -115,7 +140,8 @@ public static implicit operator string(CustomEnumeration configuration) private class CustomParameterAttributeBuild : FalloutBuild { - [CustomParameter] readonly ComplexType ComplexTypeParamWithAttribute; + [CustomParameter] + readonly ComplexType ComplexTypeParamWithAttribute; } private class CustomParameterAttribute : ParameterAttribute; diff --git a/tests/Fallout.Build.Specs/Utilities/ConsoleUtilitySpecs.cs b/tests/Fallout.Build.Specs/Utilities/ConsoleUtilitySpecs.cs index fec23bac3..35c46ad52 100644 --- a/tests/Fallout.Build.Specs/Utilities/ConsoleUtilitySpecs.cs +++ b/tests/Fallout.Build.Specs/Utilities/ConsoleUtilitySpecs.cs @@ -1,10 +1,10 @@ -using Fallout.Common.Utilities; -using Xunit; using System; using System.Collections.Generic; using System.Drawing; using Fallout.Build.Utilities; +using Fallout.Common.Utilities; using FluentAssertions; +using Xunit; namespace Fallout.Build.Tests.Utilities; @@ -12,13 +12,18 @@ public class ConsoleUtilitySpecs { private class MockConsole : IConsole { - public int BufferWidth { get; set; } = 80; + public int BufferWidth { get; } = 80; + public int CursorLeft { get; set; } + public int CursorTop { get; set; } + public Queue Keys { get; } = new(); + public void Write(string value, Color? color = null) { } public void WriteLine() { } public void WriteLine(string value, Color? color = null) { } + public ConsoleKeyInfo ReadKey(bool intercept) => Keys.Count > 0 ? Keys.Dequeue() : new ConsoleKeyInfo('a', ConsoleKey.A, false, false, false); diff --git a/tests/Fallout.Build.Specs/Utilities/ValidInputData.cs b/tests/Fallout.Build.Specs/Utilities/ValidInputData.cs index 6116e79ba..b459c8f62 100644 --- a/tests/Fallout.Build.Specs/Utilities/ValidInputData.cs +++ b/tests/Fallout.Build.Specs/Utilities/ValidInputData.cs @@ -1,7 +1,6 @@ +using System; using System.Collections; using System.Collections.Generic; -using System; -using Xunit; namespace Fallout.Build.Tests.Utilities; @@ -10,21 +9,21 @@ public class ValidInputData : IEnumerable public IEnumerator GetEnumerator() { yield return [ConsoleKey.A, 'a', true]; - yield return [ConsoleKey.Z, 'z', true ]; - yield return [ConsoleKey.D0, '0', true ]; - yield return [ConsoleKey.D9, '9', true ]; - yield return [ConsoleKey.Enter, '\r', false ]; - yield return [ConsoleKey.Backspace, '\b', false ]; - yield return [ConsoleKey.F8, '\0', false ]; - yield return [ConsoleKey.Spacebar, ' ', false ]; - yield return [ConsoleKey.OemPeriod, '.', true ]; - yield return [ConsoleKey.Divide, '/', true ]; - yield return [ConsoleKey.OemMinus, '-', true ]; - yield return [ConsoleKey.NoName, 'ä', true ]; - yield return [ConsoleKey.NoName, 'ö', true ]; - yield return [ConsoleKey.NoName, '中', true ]; - yield return [ConsoleKey.NoName, 'д', true ]; - yield return [ConsoleKey.NoName, 'α', true ]; + yield return [ConsoleKey.Z, 'z', true]; + yield return [ConsoleKey.D0, '0', true]; + yield return [ConsoleKey.D9, '9', true]; + yield return [ConsoleKey.Enter, '\r', false]; + yield return [ConsoleKey.Backspace, '\b', false]; + yield return [ConsoleKey.F8, '\0', false]; + yield return [ConsoleKey.Spacebar, ' ', false]; + yield return [ConsoleKey.OemPeriod, '.', true]; + yield return [ConsoleKey.Divide, '/', true]; + yield return [ConsoleKey.OemMinus, '-', true]; + yield return [ConsoleKey.NoName, 'ä', true]; + yield return [ConsoleKey.NoName, 'ö', true]; + yield return [ConsoleKey.NoName, '中', true]; + yield return [ConsoleKey.NoName, 'д', true]; + yield return [ConsoleKey.NoName, 'α', true]; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/tests/Fallout.Build.Specs/ValueInjectionUtilitySpecs.cs b/tests/Fallout.Build.Specs/ValueInjectionUtilitySpecs.cs index 5d8c6fa18..ee6881220 100644 --- a/tests/Fallout.Build.Specs/ValueInjectionUtilitySpecs.cs +++ b/tests/Fallout.Build.Specs/ValueInjectionUtilitySpecs.cs @@ -1,6 +1,6 @@ using System.Reflection; -using FluentAssertions; using Fallout.Common.ValueInjection; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; diff --git a/tests/Fallout.Build.Specs/VerbosityLevelMappingSpecs.cs b/tests/Fallout.Build.Specs/VerbosityLevelMappingSpecs.cs index 6f8762d5d..c57090190 100644 --- a/tests/Fallout.Build.Specs/VerbosityLevelMappingSpecs.cs +++ b/tests/Fallout.Build.Specs/VerbosityLevelMappingSpecs.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Fallout.Common.Execution; using FluentAssertions; using Serilog; @@ -150,7 +149,10 @@ public static TheoryData AllLogEventLevels { var data = new TheoryData(); foreach (var level in Enum.GetValues()) + { data.Add(level); + } + return data; } } @@ -183,7 +185,9 @@ private static LogEventLevel[] Capture() .CreateLogger(); foreach (var level in Enum.GetValues()) + { logger.Write(level, "a {Level} line", level); + } return sink.Levels.ToArray(); } diff --git a/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs b/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs index ff5f8c9a5..c05f3f7e3 100644 --- a/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs +++ b/tests/Fallout.Build.Specs/VerifySpecsInitializer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Runtime.CompilerServices; using VerifyTests; diff --git a/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs b/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs index 679c86075..bbe9c6edd 100644 --- a/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs +++ b/tests/Fallout.Cli.Specs/BuildProjectResolverSpecs.cs @@ -1,7 +1,6 @@ using System; using System.IO; using Fallout.Common.IO; -using Fallout.Common.Utilities; using FluentAssertions; using Xunit; @@ -97,7 +96,9 @@ private TempRepoRoot(AbsolutePath path) public static TempRepoRoot Create() { - var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), "fallout-test-" + Guid.NewGuid().ToString("N")); + var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), + "fallout-test-" + Guid.NewGuid().ToString("N")); + dir.CreateDirectory(); return new TempRepoRoot(dir); } @@ -117,7 +118,9 @@ public AbsolutePath WriteBuildProjectAtConvention() public void Dispose() { if (Directory.Exists(Path)) + { Directory.Delete(Path, recursive: true); + } } } } diff --git a/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs b/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs index 1a3f968ea..ac424ed09 100644 --- a/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs +++ b/tests/Fallout.Cli.Specs/CakeConversionSpecs.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; -using NuGet.Versioning; using Fallout.Common; using Fallout.Common.IO; +using FluentAssertions; +using NuGet.Versioning; using VerifyXunit; using Xunit; @@ -34,11 +34,15 @@ public void TestPackages() packages.Should().Contain((PackageManager.ReferenceType, "SharpZipLib", "1.2.0")); packages.Should().Contain(x => x.Id == "TeamCity.Dotnet.Integration" && NuGetVersion.Parse(x.Version) > NuGetVersion.Parse("1.0.10")); + packages.Should().NotContain(x => x.Id.Contains("Cake")); } private static AbsolutePath CakeScriptsDirectory => RootDirectory / "tests" / "Fallout.Cli.Specs" / "cake-scripts"; public static IEnumerable CakeFileNames - => CakeScriptsDirectory.GlobFiles(CakeConverter.FilePattern).Select(x => new object[] { x }); + => CakeScriptsDirectory.GlobFiles(CakeConverter.FilePattern).Select(x => new object[] + { + x + }); } diff --git a/tests/Fallout.Cli.Specs/CommandDispatcherSpecs.cs b/tests/Fallout.Cli.Specs/CommandDispatcherSpecs.cs index cc1d38456..a57197092 100644 --- a/tests/Fallout.Cli.Specs/CommandDispatcherSpecs.cs +++ b/tests/Fallout.Cli.Specs/CommandDispatcherSpecs.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Fallout.Cli.Commands; @@ -12,7 +11,7 @@ namespace Fallout.Cli.Specs; public class CommandDispatcherSpecs { - private static readonly AbsolutePath SomeRoot = (AbsolutePath)Path.Combine(Path.GetTempPath(), "fallout-dispatch-root"); + private static readonly AbsolutePath SomeRoot = Path.Combine(Path.GetTempPath(), "fallout-dispatch-root"); private static readonly AbsolutePath SomeScript = SomeRoot / "build.sh"; [Fact] @@ -20,7 +19,11 @@ public async Task Dispatch_ColonCommand_InvokesMatchingCommandWithForwardedArgs( { var run = new RecordingCommand("run"); var setup = new RecordingCommand("setup"); - var dispatcher = new CommandDispatcher(new IFalloutCommand[] { run, setup }, new FakePrompts()); + var dispatcher = new CommandDispatcher(new IFalloutCommand[] + { + run, + setup + }, new FakePrompts()); var exitCode = await dispatcher.DispatchAsync([":setup", "alpha", "beta"], SomeRoot, SomeScript); @@ -39,7 +42,10 @@ public async Task Dispatch_ColonCommand_InvokesMatchingCommandWithForwardedArgs( public async Task Dispatch_MatchesNameCaseInsensitively(string token) { var setup = new RecordingCommand("setup"); - var dispatcher = new CommandDispatcher(new IFalloutCommand[] { setup }, new FakePrompts()); + var dispatcher = new CommandDispatcher(new IFalloutCommand[] + { + setup + }, new FakePrompts()); await dispatcher.DispatchAsync([token], SomeRoot, SomeScript); @@ -54,7 +60,10 @@ public async Task Dispatch_MatchesNameIgnoringDashes(string token) { // Preserves every spelling the historical reflection dispatch accepted. var addPackage = new RecordingCommand("add-package"); - var dispatcher = new CommandDispatcher(new IFalloutCommand[] { addPackage }, new FakePrompts()); + var dispatcher = new CommandDispatcher(new IFalloutCommand[] + { + addPackage + }, new FakePrompts()); await dispatcher.DispatchAsync([token], SomeRoot, SomeScript); @@ -65,7 +74,10 @@ public async Task Dispatch_MatchesNameIgnoringDashes(string token) public async Task Dispatch_ReturnsCommandExitCode() { var trigger = new RecordingCommand("trigger", exitCode: 42); - var dispatcher = new CommandDispatcher(new IFalloutCommand[] { trigger }, new FakePrompts()); + var dispatcher = new CommandDispatcher(new IFalloutCommand[] + { + trigger + }, new FakePrompts()); (await dispatcher.DispatchAsync([":trigger"], SomeRoot, SomeScript)).Should().Be(42); } @@ -74,20 +86,27 @@ public async Task Dispatch_ReturnsCommandExitCode() public async Task Dispatch_UnknownCommand_ThrowsWithAvailableCommandListing() { var dispatcher = new CommandDispatcher( - new IFalloutCommand[] { new RecordingCommand("run"), new RecordingCommand("setup") }, + new IFalloutCommand[] + { + new RecordingCommand("run"), + new RecordingCommand("setup") + }, new FakePrompts()); var action = () => dispatcher.DispatchAsync([":bogus"], SomeRoot, SomeScript); (await action.Should().ThrowAsync() - .WithMessage("*'bogus' is not supported*")) + .WithMessage("*'bogus' is not supported*")) .And.Message.Should().ContainAll("- run", "- setup"); } [Fact] public async Task Dispatch_EmptyCommandToken_Fails() { - var dispatcher = new CommandDispatcher(new IFalloutCommand[] { new RecordingCommand("run") }, new FakePrompts()); + var dispatcher = new CommandDispatcher(new IFalloutCommand[] + { + new RecordingCommand("run") + }, new FakePrompts()); var action = () => dispatcher.DispatchAsync([":"], SomeRoot, SomeScript); @@ -99,7 +118,11 @@ public async Task Dispatch_NoCommand_NullRoot_Confirmed_InvokesSetup() { var setup = new RecordingCommand("setup"); var dispatcher = new CommandDispatcher( - new IFalloutCommand[] { new RecordingCommand("run"), setup }, + new IFalloutCommand[] + { + new RecordingCommand("run"), + setup + }, new FakePrompts(confirm: true)); await dispatcher.DispatchAsync(["whatever"], rootDirectory: null, buildScript: null); @@ -114,7 +137,11 @@ public async Task Dispatch_NoCommand_NullRoot_Declined_ReturnsZeroWithoutSetup() { var setup = new RecordingCommand("setup"); var dispatcher = new CommandDispatcher( - new IFalloutCommand[] { new RecordingCommand("run"), setup }, + new IFalloutCommand[] + { + new RecordingCommand("run"), + setup + }, new FakePrompts(confirm: false)); var exitCode = await dispatcher.DispatchAsync(["whatever"], rootDirectory: null, buildScript: null); @@ -130,7 +157,11 @@ public async Task Dispatch_NoCommand_WithRoot_InvokesRunWithResolvedBuildProject var buildProject = root.WriteBuildProjectAtConvention(); var run = new RecordingCommand("run"); var dispatcher = new CommandDispatcher( - new IFalloutCommand[] { run, new RecordingCommand("setup") }, + new IFalloutCommand[] + { + run, + new RecordingCommand("setup") + }, new FakePrompts()); await dispatcher.DispatchAsync(["compile", "--verbose"], root.Path, buildScript: null); @@ -151,9 +182,13 @@ public RecordingCommand(string name, int exitCode = 0) } public string Name { get; } + public bool WasCalled { get; private set; } + public string[] ReceivedArgs { get; private set; } + public AbsolutePath ReceivedRoot { get; private set; } + public AbsolutePath ReceivedBuildScript { get; private set; } public Task ExecuteAsync(string[] args, AbsolutePath rootDirectory, AbsolutePath buildScript) @@ -196,7 +231,9 @@ private TempRoot(AbsolutePath path) public static TempRoot Create() { - var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), "fallout-dispatch-" + Guid.NewGuid().ToString("N")); + var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), + "fallout-dispatch-" + Guid.NewGuid().ToString("N")); + dir.CreateDirectory(); return new TempRoot(dir); } diff --git a/tests/Fallout.Cli.Specs/Commands/CakeCleanCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/CakeCleanCommandSpecs.cs index a06489b75..cdb288387 100644 --- a/tests/Fallout.Cli.Specs/Commands/CakeCleanCommandSpecs.cs +++ b/tests/Fallout.Cli.Specs/Commands/CakeCleanCommandSpecs.cs @@ -1,6 +1,6 @@ +using System.Threading.Tasks; using Fallout.Cli.Commands; using FluentAssertions; -using System.Threading.Tasks; using Xunit; namespace Fallout.Cli.Specs.Commands; @@ -15,7 +15,10 @@ public void Command_is_named_cake_clean() public async Task Declining_the_deletion_prompt_deletes_nothing_and_returns_zero() { // ConfirmationResult = false → the "Delete?" prompt is declined, so no .cake files are removed. - var prompts = new FakeConsolePrompts { ConfirmationResult = false }; + var prompts = new FakeConsolePrompts + { + ConfirmationResult = false + }; (await new CakeCleanCommand(prompts).ExecuteAsync([], rootDirectory: null, buildScript: null)).Should().Be(0); } diff --git a/tests/Fallout.Cli.Specs/Commands/CompleteCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/CompleteCommandSpecs.cs index 5f1119ea4..16336de2e 100644 --- a/tests/Fallout.Cli.Specs/Commands/CompleteCommandSpecs.cs +++ b/tests/Fallout.Cli.Specs/Commands/CompleteCommandSpecs.cs @@ -1,9 +1,9 @@ using System; using System.IO; +using System.Threading.Tasks; using Fallout.Cli.Commands; using Fallout.Common.IO; using FluentAssertions; -using System.Threading.Tasks; using Xunit; namespace Fallout.Cli.Specs.Commands; diff --git a/tests/Fallout.Cli.Specs/Commands/FakeConsolePrompts.cs b/tests/Fallout.Cli.Specs/Commands/FakeConsolePrompts.cs index ea5855910..03b3ed83c 100644 --- a/tests/Fallout.Cli.Specs/Commands/FakeConsolePrompts.cs +++ b/tests/Fallout.Cli.Specs/Commands/FakeConsolePrompts.cs @@ -31,12 +31,16 @@ public void ClearPreviousLine() { } public void ConfirmExecution(string title, Action action) { if (InvokeConfirmedActions) + { action(); + } } public async Task ConfirmExecutionAsync(string title, Func action) { if (InvokeConfirmedActions) + { await action(); + } } } diff --git a/tests/Fallout.Cli.Specs/Commands/GetConfigurationCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/GetConfigurationCommandSpecs.cs index 186dda3fa..c49b35f39 100644 --- a/tests/Fallout.Cli.Specs/Commands/GetConfigurationCommandSpecs.cs +++ b/tests/Fallout.Cli.Specs/Commands/GetConfigurationCommandSpecs.cs @@ -1,9 +1,9 @@ using System; using System.IO; +using System.Threading.Tasks; using Fallout.Cli.Commands; using Fallout.Common.IO; using FluentAssertions; -using System.Threading.Tasks; using Xunit; namespace Fallout.Cli.Specs.Commands; diff --git a/tests/Fallout.Cli.Specs/Commands/SecretsCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/SecretsCommandSpecs.cs index a6227255d..24bcf4b5b 100644 --- a/tests/Fallout.Cli.Specs/Commands/SecretsCommandSpecs.cs +++ b/tests/Fallout.Cli.Specs/Commands/SecretsCommandSpecs.cs @@ -1,7 +1,7 @@ using System; +using System.Threading.Tasks; using Fallout.Cli.Commands; using FluentAssertions; -using System.Threading.Tasks; using Xunit; namespace Fallout.Cli.Specs.Commands; diff --git a/tests/Fallout.Cli.Specs/Commands/UpdateCommandSpecs.cs b/tests/Fallout.Cli.Specs/Commands/UpdateCommandSpecs.cs index d57cbaed9..ac115c857 100644 --- a/tests/Fallout.Cli.Specs/Commands/UpdateCommandSpecs.cs +++ b/tests/Fallout.Cli.Specs/Commands/UpdateCommandSpecs.cs @@ -1,10 +1,10 @@ using System; using System.IO; +using System.Threading.Tasks; using Fallout.Cli.Commands; using Fallout.Common.IO; using Fallout.Solutions; using FluentAssertions; -using System.Threading.Tasks; using Xunit; namespace Fallout.Cli.Specs.Commands; @@ -13,7 +13,8 @@ public class UpdateCommandSpecs { [Fact] public void Command_is_named_update() - => new UpdateCommand(new FakeConsolePrompts(), new ConfigurationReader(), new BuildScaffolder()).Name.Should().Be("update"); + => new UpdateCommand(new FakeConsolePrompts(), new ConfigurationReader(), new BuildScaffolder()).Name.Should() + .Be("update"); [Fact] public void Updating_the_build_project_retargets_it_to_net10() @@ -22,6 +23,7 @@ public void Updating_the_build_project_retargets_it_to_net10() // framework Fallout.Common ships for. Guards against the literal drifting off net10.0. var projectFile = (AbsolutePath)Path.Combine( Path.GetTempPath(), "fallout-update-" + Guid.NewGuid().ToString("N"), "_build.csproj"); + projectFile.Parent.CreateDirectory(); projectFile.WriteAllText( """ @@ -31,6 +33,7 @@ public void Updating_the_build_project_retargets_it_to_net10() """); + try { var buildProject = ProjectModelTasks.ParseProject(projectFile); @@ -50,12 +53,17 @@ public async Task Declining_all_updates_without_a_build_script_completes_and_ret { var dir = (AbsolutePath)Path.Combine(Path.GetTempPath(), "fallout-update-" + Guid.NewGuid().ToString("N")); dir.CreateDirectory(); - var prompts = new FakeConsolePrompts { InvokeConfirmedActions = false }; + var prompts = new FakeConsolePrompts + { + InvokeConfirmedActions = false + }; + try { // No build script and every confirmation declined → no update steps run, but the command // still completes cleanly. - (await new UpdateCommand(prompts, new ConfigurationReader(), new BuildScaffolder()).ExecuteAsync([], dir, buildScript: null)).Should().Be(0); + (await new UpdateCommand(prompts, new ConfigurationReader(), new BuildScaffolder()).ExecuteAsync([], dir, + buildScript: null)).Should().Be(0); prompts.Completions.Should().Contain("Updates"); } diff --git a/tests/Fallout.Cli.Specs/ConfigurationReaderSpecs.cs b/tests/Fallout.Cli.Specs/ConfigurationReaderSpecs.cs index 4deb9a4af..ab166564f 100644 --- a/tests/Fallout.Cli.Specs/ConfigurationReaderSpecs.cs +++ b/tests/Fallout.Cli.Specs/ConfigurationReaderSpecs.cs @@ -58,7 +58,9 @@ private sealed class TempDir : IDisposable public static TempDir Create() { - var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), "fallout-cfgreader-" + Guid.NewGuid().ToString("N")); + var dir = (AbsolutePath)System.IO.Path.Combine(System.IO.Path.GetTempPath(), + "fallout-cfgreader-" + Guid.NewGuid().ToString("N")); + dir.CreateDirectory(); return new TempDir(dir); } @@ -66,7 +68,9 @@ public static TempDir Create() public void Dispose() { if (Directory.Exists(Path)) + { Directory.Delete(Path, recursive: true); + } } } } diff --git a/tests/Fallout.Cli.Specs/UpdateSolutionFileContentSpecs.cs b/tests/Fallout.Cli.Specs/UpdateSolutionFileContentSpecs.cs index 46ef79926..c78ebb48b 100644 --- a/tests/Fallout.Cli.Specs/UpdateSolutionFileContentSpecs.cs +++ b/tests/Fallout.Cli.Specs/UpdateSolutionFileContentSpecs.cs @@ -16,55 +16,55 @@ public class UpdateSolutionFileContentSpecs [InlineData( 1, """ - Microsoft Visual Studio Solution File, Format Version 12.00 - # Visual Studio 15 - VisualStudioVersion = 15.0.27703.2047 - MinimumVisualStudioVersion = 10.0.40219.1 - Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{1992CC3B-CF32-485C-8DBC-77D0B6F18A82}" - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {61B2112A-2DA2-45AC-AFF5-6C50A10BB92F} - EndGlobalSection - EndGlobal - """)] + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 15 + VisualStudioVersion = 15.0.27703.2047 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{1992CC3B-CF32-485C-8DBC-77D0B6F18A82}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1992CC3B-CF32-485C-8DBC-77D0B6F18A82}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {61B2112A-2DA2-45AC-AFF5-6C50A10BB92F} + EndGlobalSection + EndGlobal + """)] [InlineData( 2, """ - Microsoft Visual Studio Solution File, Format Version 12.00 - Global - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {77D83C53-98F5-4275-8217-14700BCA05BB} - EndGlobalSection - EndGlobal - """)] + Microsoft Visual Studio Solution File, Format Version 12.00 + Global + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77D83C53-98F5-4275-8217-14700BCA05BB} + EndGlobalSection + EndGlobal + """)] [InlineData( 3, """ - Microsoft Visual Studio Solution File, Format Version 12.00 - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - EndGlobal - """)] + Microsoft Visual Studio Solution File, Format Version 12.00 + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + EndGlobal + """)] public Task Test(int number, string input) { var content = input.SplitLineBreaks().ToList(); @@ -87,7 +87,12 @@ public Task TestXml(int number, string input) var content = XDocument.Load(new StringReader(input)); new BuildScaffolder().UpdateSolutionXmlFileContent(content, "RELATIVE"); - var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true }; + var settings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true + }; + var stringStream = new StringWriter(); using var writer = XmlWriter.Create(stringStream, settings); content.Save(writer); diff --git a/tests/Fallout.Cli.Specs/VerifySpecsInitializer.cs b/tests/Fallout.Cli.Specs/VerifySpecsInitializer.cs index aa3f89b70..e5e316f06 100644 --- a/tests/Fallout.Cli.Specs/VerifySpecsInitializer.cs +++ b/tests/Fallout.Cli.Specs/VerifySpecsInitializer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Runtime.CompilerServices; using VerifyTests; diff --git a/tests/Fallout.Common.Specs/CI/AzurePipelinesJobSpecs.cs b/tests/Fallout.Common.Specs/CI/AzurePipelinesJobSpecs.cs index 1bdefe3cd..ec9c08ddb 100644 --- a/tests/Fallout.Common.Specs/CI/AzurePipelinesJobSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/AzurePipelinesJobSpecs.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Threading.Tasks; +using System.Threading.Tasks; using Fallout.Common.CI.AzurePipelines; using Fallout.Common.CI.AzurePipelines.Configuration; using Xunit; @@ -11,21 +10,21 @@ public class AzurePipelinesJobSpecs [Fact] public Task DisplayName_with_an_apostrophe_is_yaml_escaped() => ConfigurationEntityVerifier.Verify(new AzurePipelinesJob - { - Name = "Compile", - DisplayName = "Bob's Job", - Dependencies = new AzurePipelinesJob[0], - Steps = new AzurePipelinesStep[0] - }); + { + Name = "Compile", + DisplayName = "Bob's Job", + Dependencies = new AzurePipelinesJob[0], + Steps = new AzurePipelinesStep[0] + }); [Fact] public Task VmImage_is_quoted_correctly_without_double_wrapping_quirk() => ConfigurationEntityVerifier.Verify(new AzurePipelinesJob - { - Name = "Compile", - DisplayName = "Compile Job", - Dependencies = new AzurePipelinesJob[0], - Steps = new AzurePipelinesStep[0], - Image = AzurePipelinesImage.Ubuntu2204 - }); + { + Name = "Compile", + DisplayName = "Compile Job", + Dependencies = new AzurePipelinesJob[0], + Steps = new AzurePipelinesStep[0], + Image = AzurePipelinesImage.Ubuntu2204 + }); } diff --git a/tests/Fallout.Common.Specs/CI/ConfigurationGenerationSpecs.cs b/tests/Fallout.Common.Specs/CI/ConfigurationGenerationSpecs.cs index 6b88bad9c..6100d5a76 100644 --- a/tests/Fallout.Common.Specs/CI/ConfigurationGenerationSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/ConfigurationGenerationSpecs.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -41,7 +40,11 @@ public Task Test(string testName, ITestConfigurationGenerator attribute) public static IEnumerable GetAttributes() { - return TestBuild.GetAttributes().Select(x => new object[] { x.TestName, x.Generator }); + return TestBuild.GetAttributes().Select(x => new object[] + { + x.TestName, + x.Generator + }); } [AppVeyorSecret("GitHubToken", "encrypted-yaml")] @@ -57,13 +60,36 @@ public class TestBuild : FalloutBuild { Description = "description", Version = "1.3.3.7", - NonEntryTargets = new[] { nameof(Clean) }, - VcsTriggeredTargets = new[] { nameof(Test), nameof(Pack) }, - ManuallyTriggeredTargets = new[] { nameof(Publish) }, - NightlyTriggeredTargets = new[] { nameof(Publish) }, - NightlyTriggerBranchFilters = new[] { "nightly_branch_filter" }, - VcsTriggerBranchFilters = new[] { "vcs_branch_filter" }, - ImportSecrets = new[] { "GitHubToken", "ManualToken" } + NonEntryTargets = new[] + { + nameof(Clean) + }, + VcsTriggeredTargets = new[] + { + nameof(Test), + nameof(Pack) + }, + ManuallyTriggeredTargets = new[] + { + nameof(Publish) + }, + NightlyTriggeredTargets = new[] + { + nameof(Publish) + }, + NightlyTriggerBranchFilters = new[] + { + "nightly_branch_filter" + }, + VcsTriggerBranchFilters = new[] + { + "vcs_branch_filter" + }, + ImportSecrets = new[] + { + "GitHubToken", + "ManualToken" + } } ); @@ -74,19 +100,52 @@ public class TestBuild : FalloutBuild AzurePipelinesImage.Ubuntu2204, AzurePipelinesImage.Windows2019) { - NonEntryTargets = new[] { nameof(Clean) }, - InvokedTargets = new[] { nameof(Test) }, - ExcludedTargets = new[] { nameof(Pack) }, + NonEntryTargets = new[] + { + nameof(Clean) + }, + InvokedTargets = new[] + { + nameof(Test) + }, + ExcludedTargets = new[] + { + nameof(Pack) + }, EnableAccessToken = true, - ImportVariableGroups = new[] { "variable-group-1" }, - ImportSecrets = new[] { nameof(ApiKey) }, + ImportVariableGroups = new[] + { + "variable-group-1" + }, + ImportSecrets = new[] + { + nameof(ApiKey) + }, TriggerBatch = true, - TriggerBranchesInclude = new[] { "included_branch" }, - TriggerBranchesExclude = new[] { "excluded_branch" }, - TriggerPathsInclude = new[] { "included_path" }, - TriggerPathsExclude = new[] { "excluded_path" }, - TriggerTagsInclude = new[] { "included_tags" }, - TriggerTagsExclude = new[] { "excluded_tags" }, + TriggerBranchesInclude = new[] + { + "included_branch" + }, + TriggerBranchesExclude = new[] + { + "excluded_branch" + }, + TriggerPathsInclude = new[] + { + "included_path" + }, + TriggerPathsExclude = new[] + { + "excluded_path" + }, + TriggerTagsInclude = new[] + { + "included_tags" + }, + TriggerTagsExclude = new[] + { + "excluded_tags" + }, Submodules = true, LargeFileStorage = false, Clean = true, @@ -101,13 +160,25 @@ public class TestBuild : FalloutBuild AppVeyorImage.UbuntuLatest, AppVeyorImage.VisualStudio2022) { - InvokedTargets = new[] { nameof(Test) }, - BranchesOnly = new[] { "only_branch" }, - BranchesExcept = new[] { "except_branch" }, + InvokedTargets = new[] + { + nameof(Test) + }, + BranchesOnly = new[] + { + "only_branch" + }, + BranchesExcept = new[] + { + "except_branch" + }, SkipTags = true, SkipBranchesWithPullRequest = true, Submodules = true, - Secrets = new[] { "GitHubToken" } + Secrets = new[] + { + "GitHubToken" + } } ); @@ -119,12 +190,28 @@ public class TestBuild : FalloutBuild GitHubActionsImage.UbuntuLatest, GitHubActionsImage.WindowsLatest) { - On = new[] { GitHubActionsTrigger.Push, GitHubActionsTrigger.PullRequest }, - InvokedTargets = new[] { nameof(Test) }, - ImportSecrets = new[] { nameof(ApiKey) }, + On = new[] + { + GitHubActionsTrigger.Push, + GitHubActionsTrigger.PullRequest + }, + InvokedTargets = new[] + { + nameof(Test) + }, + ImportSecrets = new[] + { + nameof(ApiKey) + }, EnableGitHubToken = true, - WritePermissions = new[] { GitHubActionsPermissions.Contents }, - ReadPermissions = new[] { GitHubActionsPermissions.Actions } + WritePermissions = new[] + { + GitHubActionsPermissions.Contents + }, + ReadPermissions = new[] + { + GitHubActionsPermissions.Actions + } } ); @@ -136,19 +223,52 @@ public class TestBuild : FalloutBuild GitHubActionsImage.UbuntuLatest, GitHubActionsImage.WindowsLatest) { - InvokedTargets = new[] { nameof(Test) }, + InvokedTargets = new[] + { + nameof(Test) + }, OnCronSchedule = "* 0 * * *", - OnPushBranches = new[] { "push_branch" }, - OnPushTags = new[] { "push_tag/*" }, - OnPushIncludePaths = new[] { "push_include_path" }, - OnPushExcludePaths = new[] { "push_exclude_path" }, - OnPullRequestBranches = new[] { "pull_request_branch" }, - OnPullRequestTags = new[] { "pull_request_tag" }, - OnPullRequestIncludePaths = new[] { "pull_request_include_path" }, - OnPullRequestExcludePaths = new[] { "pull_request_exclude_path/**" }, + OnPushBranches = new[] + { + "push_branch" + }, + OnPushTags = new[] + { + "push_tag/*" + }, + OnPushIncludePaths = new[] + { + "push_include_path" + }, + OnPushExcludePaths = new[] + { + "push_exclude_path" + }, + OnPullRequestBranches = new[] + { + "pull_request_branch" + }, + OnPullRequestTags = new[] + { + "pull_request_tag" + }, + OnPullRequestIncludePaths = new[] + { + "pull_request_include_path" + }, + OnPullRequestExcludePaths = new[] + { + "pull_request_exclude_path/**" + }, #pragma warning disable FALLOUTOBS001 // regression guard: the obsolete legacy path must still emit correctly - OnWorkflowDispatchOptionalInputs = new[] { "OptionalInput" }, - OnWorkflowDispatchRequiredInputs = new[] { "RequiredInput" }, + OnWorkflowDispatchOptionalInputs = new[] + { + "OptionalInput" + }, + OnWorkflowDispatchRequiredInputs = new[] + { + "RequiredInput" + }, #pragma warning restore FALLOUTOBS001 PublishCondition = "success() || failure()", Submodules = GitHubActionsSubmodules.Recursive, @@ -175,8 +295,14 @@ public class TestBuild : FalloutBuild "checkout-ref", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - InvokedTargets = new[] { nameof(Test) }, - OnPullRequestBranches = new[] { "main" }, + InvokedTargets = new[] + { + nameof(Test) + }, + OnPullRequestBranches = new[] + { + "main" + }, CheckoutRef = "${{ github.head_ref }}" } ); @@ -186,15 +312,22 @@ public class TestBuild : FalloutBuild "env-block", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push, GitHubActionsTrigger.PullRequest }, - InvokedTargets = new[] { nameof(Test) }, + On = new[] + { + GitHubActionsTrigger.Push, + GitHubActionsTrigger.PullRequest + }, + InvokedTargets = new[] + { + nameof(Test) + }, Env = new[] - { - "DOTNET_CLI_TELEMETRY_OPTOUT: 1", - "DOTNET_NOLOGO: true", - "NUGET_XMLDOC_MODE: skip", - "Configuration: Release" - } + { + "DOTNET_CLI_TELEMETRY_OPTOUT: 1", + "DOTNET_NOLOGO: true", + "NUGET_XMLDOC_MODE: skip", + "Configuration: Release" + } } ); @@ -205,11 +338,27 @@ public class TestBuild : FalloutBuild "env-block-with-permissions", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, - Env = new[] { "DOTNET_NOLOGO: true", "Configuration: Release" }, - WritePermissions = new[] { GitHubActionsPermissions.Contents }, - ReadPermissions = new[] { GitHubActionsPermissions.Actions }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, + Env = new[] + { + "DOTNET_NOLOGO: true", + "Configuration: Release" + }, + WritePermissions = new[] + { + GitHubActionsPermissions.Contents + }, + ReadPermissions = new[] + { + GitHubActionsPermissions.Actions + }, ConcurrencyCancelInProgress = true } ); @@ -221,15 +370,21 @@ public class TestBuild : FalloutBuild "checkout-with", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, FetchDepth = 0, CheckoutWith = new[] - { - "token: ${{ secrets.CI_PAT }}", - "path: src", - "persist-credentials: false" - } + { + "token: ${{ secrets.CI_PAT }}", + "path: src", + "persist-credentials: false" + } } ); @@ -239,9 +394,18 @@ public class TestBuild : FalloutBuild "checkout-with-only", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, - CheckoutWith = new[] { "path: src" } + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, + CheckoutWith = new[] + { + "path: src" + } } ); @@ -252,14 +416,20 @@ public class TestBuild : FalloutBuild "checkout-with-sparse", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, CheckoutWith = new[] - { - "sparse-checkout: |", - " src", - " build" - } + { + "sparse-checkout: |", + " src", + " build" + } } ); @@ -271,15 +441,39 @@ public class TestBuild : FalloutBuild "dispatch-typed-inputs", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - InvokedTargets = new[] { nameof(Test) }, + InvokedTargets = new[] + { + nameof(Test) + }, Inputs = new[] - { - new GitHubActionsInputAttribute("Plain"), - new GitHubActionsInputAttribute("Verbose") { Type = GitHubActionsInputType.Boolean, Default = "false" }, - new GitHubActionsInputAttribute("Retries") { Type = GitHubActionsInputType.Number, Default = "3" }, - new GitHubActionsInputAttribute("Channel") { Type = GitHubActionsInputType.Choice, Options = new[] { "alpha", "beta", "stable" }, Default = "beta" }, - new GitHubActionsInputAttribute("Target") { Type = GitHubActionsInputType.Environment } - } + { + new GitHubActionsInputAttribute("Plain"), + new GitHubActionsInputAttribute("Verbose") + { + Type = GitHubActionsInputType.Boolean, + Default = "false" + }, + new GitHubActionsInputAttribute("Retries") + { + Type = GitHubActionsInputType.Number, + Default = "3" + }, + new GitHubActionsInputAttribute("Channel") + { + Type = GitHubActionsInputType.Choice, + Options = new[] + { + "alpha", + "beta", + "stable" + }, + Default = "beta" + }, + new GitHubActionsInputAttribute("Target") + { + Type = GitHubActionsInputType.Environment + } + } } ); @@ -290,13 +484,32 @@ public class TestBuild : FalloutBuild "dispatch-input-scoping", new TestGitHubActionsAttribute("publish", GitHubActionsImage.UbuntuLatest) { - InvokedTargets = new[] { nameof(Test) }, - WorkflowNames = new[] { "publish", "build" }, + InvokedTargets = new[] + { + nameof(Test) + }, + WorkflowNames = new[] + { + "publish", + "build" + }, Inputs = new[] - { - new GitHubActionsInputAttribute("ForPublishOnly") { Workflows = new[] { "publish" } }, - new GitHubActionsInputAttribute("ForOtherWorkflow") { Workflows = new[] { "build" } } - } + { + new GitHubActionsInputAttribute("ForPublishOnly") + { + Workflows = new[] + { + "publish" + } + }, + new GitHubActionsInputAttribute("ForOtherWorkflow") + { + Workflows = new[] + { + "build" + } + } + } } ); @@ -307,15 +520,28 @@ public class TestBuild : FalloutBuild "dispatch-legacy-plus-typed", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - InvokedTargets = new[] { nameof(Test) }, + InvokedTargets = new[] + { + nameof(Test) + }, #pragma warning disable FALLOUTOBS001 // regression guard: the obsolete legacy path must still emit correctly - OnWorkflowDispatchOptionalInputs = new[] { "LegacyOptional" }, - OnWorkflowDispatchRequiredInputs = new[] { "LegacyRequired" }, + OnWorkflowDispatchOptionalInputs = new[] + { + "LegacyOptional" + }, + OnWorkflowDispatchRequiredInputs = new[] + { + "LegacyRequired" + }, #pragma warning restore FALLOUTOBS001 Inputs = new[] - { - new GitHubActionsInputAttribute("TypedFlag") { Type = GitHubActionsInputType.Boolean, Default = "true" } - } + { + new GitHubActionsInputAttribute("TypedFlag") + { + Type = GitHubActionsInputType.Boolean, + Default = "true" + } + } } ); @@ -324,9 +550,20 @@ public class TestBuild : FalloutBuild "runs-on-labels", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, - RunsOnLabels = new[] { "self-hosted", "linux", "x64" } + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, + RunsOnLabels = new[] + { + "self-hosted", + "linux", + "x64" + } } ); @@ -335,8 +572,14 @@ public class TestBuild : FalloutBuild "default-shell", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, DefaultShell = "pwsh" } ); @@ -348,11 +591,23 @@ public class TestBuild : FalloutBuild "default-shell-with-permissions", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Test) }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Test) + }, DefaultShell = "pwsh", - WritePermissions = new[] { GitHubActionsPermissions.Contents }, - ReadPermissions = new[] { GitHubActionsPermissions.Actions }, + WritePermissions = new[] + { + GitHubActionsPermissions.Contents + }, + ReadPermissions = new[] + { + GitHubActionsPermissions.Actions + }, ConcurrencyCancelInProgress = true } ); @@ -365,8 +620,14 @@ public class TestBuild : FalloutBuild "action-overrides", new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(Pack) }, + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(Pack) + }, CheckoutAction = "v8", SetupDotNetAction = "actions/setup-dotnet@v7", CacheAction = "0c907a75c2c80ebcb7f088228285e798b750cf8f # v4.2.4", @@ -379,19 +640,43 @@ public class TestBuild : FalloutBuild null, new TestSpaceAutomationAttribute("Name", "mcr.microsoft.com/dotnet/sdk:5.0") { - InvokedTargets = new[] { nameof(Test) }, + InvokedTargets = new[] + { + nameof(Test) + }, VolumeSize = "10.gb", ResourcesCpu = "1.cpu", ResourcesMemory = "2000.mb", OnPush = true, - OnPushBranchIncludes = new[] { "refs/heads/include" }, - OnPushBranchExcludes = new[] { "refs/heads/exclude" }, - OnPushBranchRegexIncludes = new[] { @"\binclude\b" }, - OnPushBranchRegexExcludes = new[] { @"\bexclude\b" }, - OnPushPathIncludes = new[] { "include-path" }, - OnPushPathExcludes = new[] { "exclude-path" }, + OnPushBranchIncludes = new[] + { + "refs/heads/include" + }, + OnPushBranchExcludes = new[] + { + "refs/heads/exclude" + }, + OnPushBranchRegexIncludes = new[] + { + @"\binclude\b" + }, + OnPushBranchRegexExcludes = new[] + { + @"\bexclude\b" + }, + OnPushPathIncludes = new[] + { + "include-path" + }, + OnPushPathExcludes = new[] + { + "exclude-path" + }, OnCronSchedule = "* 0 * * *", - ImportSecrets = new[] { "GitHubToken" }, + ImportSecrets = new[] + { + "GitHubToken" + }, TimeoutInMinutes = 15 } ); @@ -402,7 +687,8 @@ public class TestBuild : FalloutBuild public Target Clean => _ => _ .Before(Restore); - [Parameter] public readonly bool IgnoreFailedSources; + [Parameter] + public readonly bool IgnoreFailedSources; public Target Restore => _ => _ .Produces(SourceDirectory / "*/obj/**"); @@ -410,9 +696,26 @@ public class TestBuild : FalloutBuild [Parameter("Configuration for compilation")] public readonly Configuration Configuration = Configuration.Debug; - [Parameter] public readonly string[] StringArray = new[] { "first", "second" }; - [Parameter] public readonly int[] IntegerArray = new[] { 1, 2 }; - [Parameter] public readonly Configuration[] ConfigurationArray = new[] { Configuration.Debug, Configuration.Release }; + [Parameter] + public readonly string[] StringArray = new[] + { + "first", + "second" + }; + + [Parameter] + public readonly int[] IntegerArray = new[] + { + 1, + 2 + }; + + [Parameter] + public readonly Configuration[] ConfigurationArray = new[] + { + Configuration.Debug, + Configuration.Release + }; public AbsolutePath OutputDirectory => RootDirectory / "output"; @@ -443,7 +746,9 @@ public class TestBuild : FalloutBuild .Consumes(Test) .Produces(CoverageReportArchive); - [Parameter("NuGet Api Key")] [Secret] public readonly string ApiKey; + [Parameter("NuGet Api Key")] + [Secret] + public readonly string ApiKey; [Parameter("NuGet Source for Packages")] public readonly string Source = "https://api.nuget.org/v3/index.json"; @@ -461,8 +766,15 @@ public class TestBuild : FalloutBuild [TypeConverter(typeof(TypeConverter))] public class Configuration : Enumeration { - public static Configuration Debug = new() { Value = nameof(Debug) }; - public static Configuration Release = new() { Value = nameof(Release) }; + public static Configuration Debug = new() + { + Value = nameof(Debug) + }; + + public static Configuration Release = new() + { + Value = nameof(Release) + }; public static implicit operator string(Configuration configuration) { diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsActionReferenceSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsActionReferenceSpecs.cs index dd5e145a8..8c423293c 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsActionReferenceSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsActionReferenceSpecs.cs @@ -1,5 +1,4 @@ using System; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.CI.GitHubActions.Configuration; using Fallout.Common.Execution; @@ -101,8 +100,8 @@ public void Local_and_container_references_are_passed_through(string value) } [Theory] - [InlineData("actions/checkout")] // a complete reference missing its version - [InlineData("releases/v1")] // a bare ref that reads like an owner/repo + [InlineData("actions/checkout")] // a complete reference missing its version + [InlineData("releases/v1")] // a bare ref that reads like an owner/repo public void Slash_without_a_ref_is_rejected_as_ambiguous(string value) { var act = () => GitHubActionsActionReference.Resolve(GitHubActionsDefaults.CheckoutAction, value, Origin); @@ -111,9 +110,9 @@ public void Slash_without_a_ref_is_rejected_as_ambiguous(string value) } [Theory] - [InlineData("my-org/checkout # pinned by @ops-team")] // the '@' lives in the comment, not the ref - [InlineData("# v7.1.0")] // comment only, no reference at all - [InlineData("actions/checkout@")] // '@' with nothing after it + [InlineData("my-org/checkout # pinned by @ops-team")] // the '@' lives in the comment, not the ref + [InlineData("# v7.1.0")] // comment only, no reference at all + [InlineData("actions/checkout@")] // '@' with nothing after it [InlineData("@")] public void Reference_without_a_usable_ref_throws(string value) { @@ -150,10 +149,25 @@ public void Rejection_message_names_the_origin_of_the_value() [Fact] public void Each_step_resolves_against_its_own_default() { - new GitHubActionsCheckoutStep { Uses = "v8" }.Uses.Should().Be("actions/checkout@v8"); - new GitHubActionsCacheStep { Uses = "v4" }.Uses.Should().Be("actions/cache@v4"); - new GitHubActionsArtifactStep { Uses = "v8" }.Uses.Should().Be("actions/upload-artifact@v8"); - new GitHubActionsRunStep { SetupDotNetAction = "v7" }.SetupDotNetAction.Should().Be("actions/setup-dotnet@v7"); + new GitHubActionsCheckoutStep + { + Uses = "v8" + }.Uses.Should().Be("actions/checkout@v8"); + + new GitHubActionsCacheStep + { + Uses = "v4" + }.Uses.Should().Be("actions/cache@v4"); + + new GitHubActionsArtifactStep + { + Uses = "v8" + }.Uses.Should().Be("actions/upload-artifact@v8"); + + new GitHubActionsRunStep + { + SetupDotNetAction = "v7" + }.SetupDotNetAction.Should().Be("actions/setup-dotnet@v7"); } [Fact] @@ -168,7 +182,10 @@ public void Steps_default_to_the_pinned_versions() [Fact] public void Resetting_a_step_to_null_restores_its_default() { - var step = new GitHubActionsCheckoutStep { Uses = "v8" }; + var step = new GitHubActionsCheckoutStep + { + Uses = "v8" + }; step.Uses = null; @@ -181,10 +198,10 @@ public void Resetting_a_step_to_null_restores_its_default() public void Cache_override_is_validated_even_when_no_cache_step_is_emitted() { var act = () => GenerateWorkflow(attribute => - { - attribute.CacheKeyFiles = new string[0]; - attribute.CacheAction = "actions/cache"; - }); + { + attribute.CacheKeyFiles = new string[0]; + attribute.CacheAction = "actions/cache"; + }); act.Should().Throw().WithMessage($"*{nameof(GitHubActionsAttribute.CacheAction)}*"); } @@ -193,10 +210,10 @@ public void Cache_override_is_validated_even_when_no_cache_step_is_emitted() public void Artifact_override_is_validated_even_when_artifacts_are_disabled() { var act = () => GenerateWorkflow(attribute => - { - attribute.PublishArtifacts = false; - attribute.UploadArtifactAction = "my-org/upload-artifact"; - }); + { + attribute.PublishArtifacts = false; + attribute.UploadArtifactAction = "my-org/upload-artifact"; + }); act.Should().Throw() .WithMessage($"*{nameof(GitHubActionsAttribute.UploadArtifactAction)}*"); @@ -215,12 +232,12 @@ public void Validation_message_names_the_workflow() public void Well_formed_overrides_do_not_throw() { var act = () => GenerateWorkflow(attribute => - { - attribute.CheckoutAction = "@releases/v1"; - attribute.CacheAction = "v4"; - attribute.SetupDotNetAction = "actions/setup-dotnet@v7"; - attribute.UploadArtifactAction = $"my-org/upload-artifact@{Sha} # v7.1.0"; - }); + { + attribute.CheckoutAction = "@releases/v1"; + attribute.CacheAction = "v4"; + attribute.SetupDotNetAction = "actions/setup-dotnet@v7"; + attribute.UploadArtifactAction = $"my-org/upload-artifact@{Sha} # v7.1.0"; + }); act.Should().NotThrow(); } @@ -236,12 +253,19 @@ private static void GenerateWorkflow(Action configur var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var attribute = new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) } - }; + { + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + } + }; + configureAttribute(attribute); - ((ConfigurationAttributeBase)attribute).Build = build; + attribute.Build = build; attribute.GetConfiguration(relevantTargets); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsArtifactStepSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsArtifactStepSpecs.cs index 929e59291..460fc3d8c 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsArtifactStepSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsArtifactStepSpecs.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Threading.Tasks; +using System.Threading.Tasks; using Fallout.Common.CI.GitHubActions.Configuration; using Xunit; @@ -9,5 +8,10 @@ public class GitHubActionsArtifactStepSpecs { [Fact] public Task Name_with_an_apostrophe_is_yaml_escaped() - => ConfigurationEntityVerifier.Verify(new GitHubActionsArtifactStep { Name = "Bob's Build", Path = "out", Condition = "" }); + => ConfigurationEntityVerifier.Verify(new GitHubActionsArtifactStep + { + Name = "Bob's Build", + Path = "out", + Condition = "" + }); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.cs index cdcafc6e5..dad01e6c9 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepSpecs.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Fallout.Common.CI.GitHubActions.Configuration; @@ -25,59 +26,110 @@ private static Task Verify(GitHubActionsCustomStep step) [Fact] public Task Uses_step_with_with_block() => Verify(new GitHubActionsCustomStep - { - Name = "Setup Node", - Uses = "actions/setup-node@v4", - With = new() { ["node-version"] = "20" } - }); + { + Name = "Setup Node", + Uses = "actions/setup-node@v4", + With = new Dictionary + { + ["node-version"] = "20" + } + }); [Fact] public Task Uses_step_without_name_starts_with_uses() - => Verify(new GitHubActionsCustomStep { Uses = "actions/checkout@v4" }); + => Verify(new GitHubActionsCustomStep + { + Uses = "actions/checkout@v4" + }); [Fact] public Task Single_line_run_renders_inline() - => Verify(new GitHubActionsCustomStep { Name = "Echo", Run = new[] { "echo hi" } }); + => Verify(new GitHubActionsCustomStep + { + Name = "Echo", + Run = new[] + { + "echo hi" + } + }); // A name with a colon-space would be invalid YAML unquoted; it must be single-quoted like the built-in steps. [Fact] public Task Name_with_a_colon_is_quoted() - => Verify(new GitHubActionsCustomStep { Name = "Deploy: prod", Run = new[] { "echo hi" } }); + => Verify(new GitHubActionsCustomStep + { + Name = "Deploy: prod", + Run = new[] + { + "echo hi" + } + }); // An embedded apostrophe must be YAML-escaped by doubling (''), not the backslash the shared // SingleQuote() helper would produce (which is invalid inside a YAML single-quoted scalar). [Fact] public Task Name_with_an_apostrophe_is_yaml_escaped() - => Verify(new GitHubActionsCustomStep { Name = "Bob's step", Run = new[] { "echo hi" } }); + => Verify(new GitHubActionsCustomStep + { + Name = "Bob's step", + Run = new[] + { + "echo hi" + } + }); // Multi-entry with:/env: must render in a deterministic (ordinal) order regardless of insertion order. [Fact] public Task Multi_entry_with_renders_in_ordinal_order() => Verify(new GitHubActionsCustomStep - { - Uses = "some/action@v1", - With = new() { ["beta"] = "2", ["alpha"] = "1" } - }); + { + Uses = "some/action@v1", + With = new Dictionary + { + ["beta"] = "2", + ["alpha"] = "1" + } + }); [Fact] public Task Multi_line_run_renders_block_scalar() - => Verify(new GitHubActionsCustomStep { Run = new[] { "echo one", "echo two" } }); + => Verify(new GitHubActionsCustomStep + { + Run = new[] + { + "echo one", + "echo two" + } + }); [Fact] public Task All_optional_fields_render_in_fixed_order() => Verify(new GitHubActionsCustomStep - { - Name = "Full", - Id = "full", - Uses = "some/action@v1", - With = new() { ["k"] = "v" }, - Env = new() { ["E"] = "1" }, - If = "success()", - ContinueOnError = true, - TimeoutMinutes = 5 - }); + { + Name = "Full", + Id = "full", + Uses = "some/action@v1", + With = new Dictionary + { + ["k"] = "v" + }, + Env = new Dictionary + { + ["E"] = "1" + }, + If = "success()", + ContinueOnError = true, + TimeoutMinutes = 5 + }); [Fact] public Task Run_step_renders_shell_when_set() - => Verify(new GitHubActionsCustomStep { Run = new[] { "gci" }, Shell = "pwsh" }); + => Verify(new GitHubActionsCustomStep + { + Run = new[] + { + "gci" + }, + Shell = "pwsh" + }); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepValidationSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepValidationSpecs.cs index 0d4052c0d..600b547aa 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepValidationSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsCustomStepValidationSpecs.cs @@ -1,5 +1,5 @@ using System; -using Fallout.Common.CI; +using System.Collections.Generic; using Fallout.Common.CI.GitHubActions; using Fallout.Common.CI.GitHubActions.Configuration; using Fallout.Common.Execution; @@ -13,74 +13,141 @@ public class GitHubActionsCustomStepValidationSpecs private sealed class InjectingBuild : ConfigurationGenerationSpecs.TestBuild, IConfigureGitHubActions { public Action Hook { get; set; } + public void ConfigureSteps(GitHubActionsStepPipeline pipeline) => Hook?.Invoke(pipeline); } private static Action Generate(params GitHubActionsCustomStep[] steps) { var build = new InjectingBuild - { - Hook = p => - { - foreach (var step in steps) - p.Insert(GitHubActionsStepPosition.PostRun, step); - } - }; + { + Hook = p => + { + foreach (var step in steps) + { + p.Insert(GitHubActionsStepPosition.PostRun, step); + } + } + }; + var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var attribute = new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) } - }; - ((ConfigurationAttributeBase)attribute).Build = build; + { + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + } + }; + + attribute.Build = build; return () => attribute.GetConfiguration(relevantTargets); } [Fact] public void Step_with_both_uses_and_run_throws() - => Generate(new GitHubActionsCustomStep { Uses = "a/b@v1", Run = new[] { "echo" } }) + => Generate(new GitHubActionsCustomStep + { + Uses = "a/b@v1", + Run = new[] + { + "echo" + } + }) .Should().Throw(); [Fact] public void Step_with_neither_uses_nor_run_throws() - => Generate(new GitHubActionsCustomStep { Name = "empty" }) + => Generate(new GitHubActionsCustomStep + { + Name = "empty" + }) .Should().Throw(); [Theory] [InlineData("")] [InlineData(" ")] public void Run_with_only_whitespace_entries_throws(string entry) - => Generate(new GitHubActionsCustomStep { Run = new[] { entry } }) + => Generate(new GitHubActionsCustomStep + { + Run = new[] + { + entry + } + }) .Should().Throw(); [Fact] public void With_without_uses_throws() - => Generate(new GitHubActionsCustomStep { Run = new[] { "echo" }, With = new() { ["k"] = "v" } }) + => Generate(new GitHubActionsCustomStep + { + Run = new[] + { + "echo" + }, + With = new Dictionary + { + ["k"] = "v" + } + }) .Should().Throw(); [Fact] public void Shell_on_a_uses_step_throws() - => Generate(new GitHubActionsCustomStep { Uses = "a/b@v1", Shell = "pwsh" }) + => Generate(new GitHubActionsCustomStep + { + Uses = "a/b@v1", + Shell = "pwsh" + }) .Should().Throw(); [Fact] public void One_invalid_among_several_valid_throws() => Generate( - new GitHubActionsCustomStep { Uses = "a/b@v1" }, - new GitHubActionsCustomStep { Name = "bad" }, // neither uses nor run - new GitHubActionsCustomStep { Run = new[] { "echo" } }) + new GitHubActionsCustomStep + { + Uses = "a/b@v1" + }, + new GitHubActionsCustomStep + { + Name = "bad" + }, // neither uses nor run + new GitHubActionsCustomStep + { + Run = new[] + { + "echo" + } + }) .Should().Throw(); [Fact] public void Well_formed_uses_step_does_not_throw() - => Generate(new GitHubActionsCustomStep { Uses = "a/b@v1", With = new() { ["k"] = "v" } }) + => Generate(new GitHubActionsCustomStep + { + Uses = "a/b@v1", + With = new Dictionary + { + ["k"] = "v" + } + }) .Should().NotThrow(); [Fact] public void Well_formed_run_step_does_not_throw() - => Generate(new GitHubActionsCustomStep { Run = new[] { "echo hi" }, Shell = "pwsh" }) + => Generate(new GitHubActionsCustomStep + { + Run = new[] + { + "echo hi" + }, + Shell = "pwsh" + }) .Should().NotThrow(); // A custom step's Uses has no default to resolve a bare ref against, so the shorthand the built-in @@ -91,12 +158,21 @@ public void Well_formed_run_step_does_not_throw() [InlineData("a/b@v1 with: x")] [InlineData("a/b@v1\n with:\n x: y")] public void Uses_that_is_not_a_complete_reference_throws(string uses) - => Generate(new GitHubActionsCustomStep { Uses = uses }) + => Generate(new GitHubActionsCustomStep + { + Uses = uses + }) .Should().Throw(); // Collections are publicly settable, so a caller can null them. That must be treated as empty, not crash. [Fact] public void Null_collections_on_a_uses_step_do_not_throw() - => Generate(new GitHubActionsCustomStep { Uses = "a/b@v1", With = null, Env = null, Run = null }) + => Generate(new GitHubActionsCustomStep + { + Uses = "a/b@v1", + With = null, + Env = null, + Run = null + }) .Should().NotThrow(); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsDispatchInputSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsDispatchInputSpecs.cs index afbfb06d6..191bd2948 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsDispatchInputSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsDispatchInputSpecs.cs @@ -1,5 +1,4 @@ using System.IO; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.Execution; using FluentAssertions; @@ -17,7 +16,7 @@ private static string Render(TestGitHubActionsAttribute attribute) var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var stream = new MemoryStream(); - ((ConfigurationAttributeBase)attribute).Build = build; + attribute.Build = build; attribute.Stream = new StreamWriter(stream, leaveOpen: true); attribute.Generate(relevantTargets); @@ -32,22 +31,37 @@ public void Legacy_arrays_and_typed_string_inputs_emit_identical_yaml() { #pragma warning disable FALLOUTOBS001 // comparing the obsolete API against its typed replacement on purpose var legacy = Render(new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - OnWorkflowDispatchOptionalInputs = new[] { "Opt" }, - OnWorkflowDispatchRequiredInputs = new[] { "Req" } - }); + { + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + OnWorkflowDispatchOptionalInputs = new[] + { + "Opt" + }, + OnWorkflowDispatchRequiredInputs = new[] + { + "Req" + } + }); #pragma warning restore FALLOUTOBS001 var typed = Render(new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - Inputs = new[] - { - new GitHubActionsInputAttribute("Opt"), - new GitHubActionsInputAttribute("Req") { Required = true } - } - }); + { + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + Inputs = new[] + { + new GitHubActionsInputAttribute("Opt"), + new GitHubActionsInputAttribute("Req") + { + Required = true + } + } + }); typed.Should().Be(legacy); } @@ -58,13 +72,22 @@ public void Legacy_arrays_and_typed_string_inputs_emit_identical_yaml() public void Input_scoped_to_a_spaced_workflow_name_is_included() { var attribute = new TestGitHubActionsAttribute("My Workflow", GitHubActionsImage.UbuntuLatest) - { - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - Inputs = new[] - { - new GitHubActionsInputAttribute("Scoped") { Workflows = new[] { "My Workflow" } } - } - }; + { + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + Inputs = new[] + { + new GitHubActionsInputAttribute("Scoped") + { + Workflows = new[] + { + "My Workflow" + } + } + } + }; var render = () => Render(attribute); diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsEnvValidationSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsEnvValidationSpecs.cs index b975d5e04..adb3eaa51 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsEnvValidationSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsEnvValidationSpecs.cs @@ -1,8 +1,6 @@ using System; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.Execution; -using Fallout.Common.Tooling; using FluentAssertions; using Xunit; @@ -21,7 +19,10 @@ public class GitHubActionsEnvValidationSpecs [InlineData("KEY:value")] public void Malformed_env_entry_throws(string badEntry) { - var act = () => GetConfiguration(new[] { badEntry }); + var act = () => GetConfiguration(new[] + { + badEntry + }); act.Should().Throw(); } @@ -32,7 +33,10 @@ public void Malformed_env_entry_throws(string badEntry) [InlineData("EMPTY_VALUE:")] public void Well_formed_env_entry_does_not_throw(string goodEntry) { - var act = () => GetConfiguration(new[] { goodEntry }); + var act = () => GetConfiguration(new[] + { + goodEntry + }); act.Should().NotThrow(); } @@ -40,7 +44,11 @@ public void Well_formed_env_entry_does_not_throw(string goodEntry) [Fact] public void Malformed_entry_after_a_valid_one_still_throws() { - var act = () => GetConfiguration(new[] { "GOOD: 1", "BAD" }); + var act = () => GetConfiguration(new[] + { + "GOOD: 1", + "BAD" + }); act.Should().Throw(); } @@ -51,12 +59,19 @@ private static void GetConfiguration(string[] env) var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var attribute = new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - Env = env - }; - ((ConfigurationAttributeBase)attribute).Build = build; + { + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + Env = env + }; + + attribute.Build = build; attribute.GetConfiguration(relevantTargets); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsInputValidationSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsInputValidationSpecs.cs index d53a522bc..2f5acefd6 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsInputValidationSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsInputValidationSpecs.cs @@ -1,5 +1,4 @@ using System; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.Execution; using FluentAssertions; @@ -10,21 +9,37 @@ namespace Fallout.Common.Specs.CI; public class GitHubActionsInputValidationSpecs { [Theory] - [InlineData(GitHubActionsInputType.Choice, null, new string[0])] // choice without options - [InlineData(GitHubActionsInputType.Choice, "x", new[] { "a", "b" })] // default not in options - [InlineData(GitHubActionsInputType.String, null, new[] { "a" })] // options on a non-choice input - [InlineData(GitHubActionsInputType.Number, "abc", new string[0])] // non-numeric number default - [InlineData(GitHubActionsInputType.Boolean, "yes", new string[0])] // non-boolean boolean default + [InlineData(GitHubActionsInputType.Choice, null, new string[0])] // choice without options + [InlineData(GitHubActionsInputType.Choice, "x", new[] + { + "a", + "b" + })] // default not in options + [InlineData(GitHubActionsInputType.String, null, new[] + { + "a" + })] // options on a non-choice input + [InlineData(GitHubActionsInputType.Number, "abc", new string[0])] // non-numeric number default + [InlineData(GitHubActionsInputType.Boolean, "yes", new string[0])] // non-boolean boolean default public void Malformed_input_throws(GitHubActionsInputType type, string @default, string[] options) { var act = () => GetConfiguration( - new GitHubActionsInputAttribute("Input") { Type = type, Default = @default, Options = options }); + new GitHubActionsInputAttribute("Input") + { + Type = type, + Default = @default, + Options = options + }); act.Should().Throw(); } [Theory] - [InlineData(GitHubActionsInputType.Choice, "a", new[] { "a", "b" })] + [InlineData(GitHubActionsInputType.Choice, "a", new[] + { + "a", + "b" + })] [InlineData(GitHubActionsInputType.Number, "1.5", new string[0])] [InlineData(GitHubActionsInputType.Boolean, "true", new string[0])] [InlineData(GitHubActionsInputType.Environment, null, new string[0])] @@ -32,7 +47,12 @@ public void Malformed_input_throws(GitHubActionsInputType type, string @default, public void Well_formed_input_does_not_throw(GitHubActionsInputType type, string @default, string[] options) { var act = () => GetConfiguration( - new GitHubActionsInputAttribute("Input") { Type = type, Default = @default, Options = options }); + new GitHubActionsInputAttribute("Input") + { + Type = type, + Default = @default, + Options = options + }); act.Should().NotThrow(); } @@ -41,7 +61,13 @@ public void Well_formed_input_does_not_throw(GitHubActionsInputType type, string public void Unknown_workflow_name_throws() { var act = () => GetConfiguration( - new GitHubActionsInputAttribute("Input") { Workflows = new[] { "does-not-exist" } }); + new GitHubActionsInputAttribute("Input") + { + Workflows = new[] + { + "does-not-exist" + } + }); act.Should().Throw(); } @@ -74,11 +100,15 @@ private static void GetConfiguration(params GitHubActionsInputAttribute[] inputs // No shorthand On: the typed inputs alone drive the detailed workflow_dispatch trigger, so // ShortTriggers stays empty and the short-XOR-detailed assert does not interfere. var attribute = new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - Inputs = inputs - }; - ((ConfigurationAttributeBase)attribute).Build = build; + { + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + Inputs = inputs + }; + + attribute.Build = build; attribute.GetConfiguration(relevantTargets); } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsRunsOnLabelsValidationSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsRunsOnLabelsValidationSpecs.cs index 3c73b6100..da8f92895 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsRunsOnLabelsValidationSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsRunsOnLabelsValidationSpecs.cs @@ -1,5 +1,4 @@ using System; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.Execution; using FluentAssertions; @@ -13,8 +12,17 @@ public class GitHubActionsRunsOnLabelsValidationSpecs public void Matrix_with_runs_on_labels_throws() { var act = () => GetConfiguration( - new[] { GitHubActionsImage.UbuntuLatest, GitHubActionsImage.WindowsLatest }, - new[] { "self-hosted", "linux", "x64" }); + new[] + { + GitHubActionsImage.UbuntuLatest, + GitHubActionsImage.WindowsLatest + }, + new[] + { + "self-hosted", + "linux", + "x64" + }); act.Should().Throw().WithMessage("*RunsOnLabels*"); } @@ -23,8 +31,16 @@ public void Matrix_with_runs_on_labels_throws() public void Single_image_with_runs_on_labels_does_not_throw() { var act = () => GetConfiguration( - new[] { GitHubActionsImage.UbuntuLatest }, - new[] { "self-hosted", "linux", "x64" }); + new[] + { + GitHubActionsImage.UbuntuLatest + }, + new[] + { + "self-hosted", + "linux", + "x64" + }); act.Should().NotThrow(); } @@ -33,7 +49,11 @@ public void Single_image_with_runs_on_labels_does_not_throw() public void Matrix_without_runs_on_labels_does_not_throw() { var act = () => GetConfiguration( - new[] { GitHubActionsImage.UbuntuLatest, GitHubActionsImage.WindowsLatest }, + new[] + { + GitHubActionsImage.UbuntuLatest, + GitHubActionsImage.WindowsLatest + }, new string[0]); act.Should().NotThrow(); @@ -43,8 +63,14 @@ public void Matrix_without_runs_on_labels_does_not_throw() public void Single_label_does_not_throw() { var act = () => GetConfiguration( - new[] { GitHubActionsImage.UbuntuLatest }, - new[] { "self-hosted" }); + new[] + { + GitHubActionsImage.UbuntuLatest + }, + new[] + { + "self-hosted" + }); act.Should().NotThrow(); } @@ -56,8 +82,15 @@ public void Single_label_does_not_throw() public void Empty_or_whitespace_label_element_throws(string badLabel) { var act = () => GetConfiguration( - new[] { GitHubActionsImage.UbuntuLatest }, - new[] { "self-hosted", badLabel }); + new[] + { + GitHubActionsImage.UbuntuLatest + }, + new[] + { + "self-hosted", + badLabel + }); act.Should().Throw().WithMessage("*RunsOnLabels*"); } @@ -67,12 +100,19 @@ private static void GetConfiguration(GitHubActionsImage[] images, string[] runsO var build = new ConfigurationGenerationSpecs.TestBuild(); var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var attribute = new TestGitHubActionsAttribute(images[0], images[1..]) - { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) }, - RunsOnLabels = runsOnLabels - }; - ((ConfigurationAttributeBase)attribute).Build = build; + { + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + }, + RunsOnLabels = runsOnLabels + }; + + attribute.Build = build; attribute.GetConfiguration(relevantTargets); } } diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.cs index f5e31c220..9d1a392ce 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsStepInjectionSpecs.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using System.Threading.Tasks; -using Fallout.Common.CI; using Fallout.Common.CI.GitHubActions; using Fallout.Common.CI.GitHubActions.Configuration; using Fallout.Common.Execution; @@ -20,6 +21,7 @@ public class GitHubActionsStepInjectionSpecs private sealed class InjectingBuild : ConfigurationGenerationSpecs.TestBuild, IConfigureGitHubActions { public Action Hook { get; set; } + public void ConfigureSteps(GitHubActionsStepPipeline pipeline) => Hook?.Invoke(pipeline); } @@ -28,18 +30,29 @@ private static string Render( Action setup = null, ConfigurationGenerationSpecs.TestBuild build = null) { - build ??= new InjectingBuild { Hook = configure }; + build ??= new InjectingBuild + { + Hook = configure + }; + var relevantTargets = ExecutableTargetFactory.CreateAll(build, x => x.Compile); var attribute = new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest) - { - On = new[] { GitHubActionsTrigger.Push }, - InvokedTargets = new[] { nameof(ConfigurationGenerationSpecs.TestBuild.Test) } - }; + { + On = new[] + { + GitHubActionsTrigger.Push + }, + InvokedTargets = new[] + { + nameof(ConfigurationGenerationSpecs.TestBuild.Test) + } + }; + setup?.Invoke(attribute); var stream = new MemoryStream(); - ((ConfigurationAttributeBase)attribute).Build = build; + attribute.Build = build; attribute.Stream = new StreamWriter(stream, leaveOpen: true); attribute.Generate(relevantTargets); @@ -48,7 +61,14 @@ private static string Render( } private static GitHubActionsCustomStep Marker(string name) - => new GitHubActionsCustomStep { Name = name, Run = new[] { "echo " + name } }; + => new() + { + Name = name, + Run = new[] + { + "echo " + name + } + }; [Fact] public void Post_checkout_step_lands_after_checkout_before_cache() @@ -57,6 +77,7 @@ public void Post_checkout_step_lands_after_checkout_before_cache() yaml.IndexOf("uses: actions/checkout", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'mark'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'mark'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("uses: actions/cache", StringComparison.Ordinal)); } @@ -68,6 +89,7 @@ public void Pre_run_step_lands_after_cache_before_setup_dotnet() yaml.IndexOf("uses: actions/cache", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'mark'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'mark'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("Setup: .NET SDK", StringComparison.Ordinal)); } @@ -79,6 +101,7 @@ public void Post_run_step_lands_after_run_before_artifacts() yaml.IndexOf("run: dotnet fallout", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'mark'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'mark'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("Publish:", StringComparison.Ordinal)); } @@ -114,12 +137,16 @@ public void Positions_hold_when_cache_and_artifacts_absent() // All four land, in order, anchored to checkout + run block. yaml.IndexOf("uses: actions/checkout", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'post-checkout'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'post-checkout'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'pre-run'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'pre-run'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("Setup: .NET SDK", StringComparison.Ordinal)); + yaml.IndexOf("run: dotnet fallout", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'post-run'", StringComparison.Ordinal)); + yaml.IndexOf("name: 'post-run'", StringComparison.Ordinal) .Should().BeLessThan(yaml.IndexOf("name: 'job-end'", StringComparison.Ordinal)); } @@ -146,7 +173,9 @@ public void Steps_scope_to_the_named_workflow() var yaml = Render(p => { if (p.WorkflowName == "other") + { p.Insert(GitHubActionsStepPosition.PostRun, Marker("scoped")); + } }); yaml.Should().NotContain("name: 'scoped'"); @@ -156,15 +185,22 @@ public void Steps_scope_to_the_named_workflow() public void Run_step_without_shell_omits_shell_under_default_shell() { var yaml = Render( - p => p.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep { Name = "noshell", Run = new[] { "echo hi" } }), + p => p.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep + { + Name = "noshell", + Run = new[] + { + "echo hi" + } + }), setup: a => a.DefaultShell = "pwsh"); // Workflow-level defaults.run.shell is emitted, but the injected run step carries no shell: of its own. - yaml.Should().Contain("shell: pwsh"); // the workflow-level defaults block + yaml.Should().Contain("shell: pwsh"); // the workflow-level defaults block yaml.Should().Contain("name: 'noshell'"); yaml.Should().Contain("run: echo hi"); // The only "shell:" occurrence is the workflow-level default (one match). - System.Text.RegularExpressions.Regex.Matches(yaml, "shell:").Count.Should().Be(1); + Regex.Matches(yaml, "shell:").Count.Should().Be(1); } [Fact] @@ -183,21 +219,30 @@ public Task Rich_injection_renders_expected_yaml() var yaml = Render(p => { p.Insert(GitHubActionsStepPosition.PostCheckout, new GitHubActionsCustomStep - { - Name = "Setup Node", - Uses = "actions/setup-node@v4", - With = new() { ["node-version"] = "20" } - }); + { + Name = "Setup Node", + Uses = "actions/setup-node@v4", + With = new Dictionary + { + ["node-version"] = "20" + } + }); + p.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep - { - Name = "Perform CodeQL Analysis", - Uses = "github/codeql-action/analyze@v3", - If = "github.ref == 'refs/heads/main'" - }); + { + Name = "Perform CodeQL Analysis", + Uses = "github/codeql-action/analyze@v3", + If = "github.ref == 'refs/heads/main'" + }); + p.Insert(GitHubActionsStepPosition.JobEnd, new GitHubActionsCustomStep - { - Run = new[] { "echo done", "echo bye" } - }); + { + Run = new[] + { + "echo done", + "echo bye" + } + }); }); return Verifier.Verify(yaml); diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsStepPipelineSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsStepPipelineSpecs.cs index 90b8c9783..325c26e12 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsStepPipelineSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsStepPipelineSpecs.cs @@ -10,8 +10,14 @@ namespace Fallout.Common.Specs.CI; public class GitHubActionsStepPipelineSpecs { private static GitHubActionsStepPipeline NewPipeline() - => new GitHubActionsStepPipeline("build", GitHubActionsImage.UbuntuLatest, - new GitHubActionsStep[] { new GitHubActionsCustomStep { Uses = "actions/checkout@v6" } }); + => new("build", GitHubActionsImage.UbuntuLatest, + new GitHubActionsStep[] + { + new GitHubActionsCustomStep + { + Uses = "actions/checkout@v6" + } + }); [Fact] public void Context_is_exposed() @@ -27,8 +33,23 @@ public void Context_is_exposed() public void Inserts_at_one_position_preserve_call_order() { var pipeline = NewPipeline(); - pipeline.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep { Name = "first", Run = new[] { "a" } }); - pipeline.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep { Name = "second", Run = new[] { "b" } }); + pipeline.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep + { + Name = "first", + Run = new[] + { + "a" + } + }); + + pipeline.Insert(GitHubActionsStepPosition.PostRun, new GitHubActionsCustomStep + { + Name = "second", + Run = new[] + { + "b" + } + }); pipeline.GetInserts(GitHubActionsStepPosition.PostRun).Select(x => x.Name) .Should().Equal("first", "second"); @@ -39,10 +60,24 @@ public void Insert_range_appends_in_order() { var pipeline = NewPipeline(); pipeline.Insert(GitHubActionsStepPosition.PreRun, new[] - { - new GitHubActionsCustomStep { Name = "x", Run = new[] { "a" } }, - new GitHubActionsCustomStep { Name = "y", Run = new[] { "b" } } - }); + { + new GitHubActionsCustomStep + { + Name = "x", + Run = new[] + { + "a" + } + }, + new GitHubActionsCustomStep + { + Name = "y", + Run = new[] + { + "b" + } + } + }); pipeline.GetInserts(GitHubActionsStepPosition.PreRun).Select(x => x.Name) .Should().Equal("x", "y"); diff --git a/tests/Fallout.Common.Specs/CI/GitHubActionsWorkflowDispatchTriggerSpecs.cs b/tests/Fallout.Common.Specs/CI/GitHubActionsWorkflowDispatchTriggerSpecs.cs index 6d1e43f91..8c15574da 100644 --- a/tests/Fallout.Common.Specs/CI/GitHubActionsWorkflowDispatchTriggerSpecs.cs +++ b/tests/Fallout.Common.Specs/CI/GitHubActionsWorkflowDispatchTriggerSpecs.cs @@ -29,10 +29,16 @@ public void Legacy_arrays_emit_untyped_inputs() { #pragma warning disable FALLOUTOBS001 // exercising the restored obsolete trigger API on purpose var yaml = Render(new GitHubActionsWorkflowDispatchTrigger - { - OptionalInputs = new[] { "Opt" }, - RequiredInputs = new[] { "Req" } - }); + { + OptionalInputs = new[] + { + "Opt" + }, + RequiredInputs = new[] + { + "Req" + } + }); #pragma warning restore FALLOUTOBS001 yaml.Should().Contain("Opt:").And.Contain("required: false"); @@ -46,14 +52,21 @@ public void Legacy_arrays_emit_before_typed_inputs() { #pragma warning disable FALLOUTOBS001 var yaml = Render(new GitHubActionsWorkflowDispatchTrigger - { - OptionalInputs = new[] { "Legacy" }, - Inputs = new[] - { - new GitHubActionsWorkflowDispatchInput - { Name = "Typed", Type = GitHubActionsInputType.Boolean, Default = "true" } - } - }); + { + OptionalInputs = new[] + { + "Legacy" + }, + Inputs = new[] + { + new GitHubActionsWorkflowDispatchInput + { + Name = "Typed", + Type = GitHubActionsInputType.Boolean, + Default = "true" + } + } + }); #pragma warning restore FALLOUTOBS001 yaml.IndexOf("Legacy:", StringComparison.Ordinal) @@ -67,20 +80,34 @@ public void Legacy_arrays_and_typed_inputs_emit_identical_yaml() { #pragma warning disable FALLOUTOBS001 var legacy = Render(new GitHubActionsWorkflowDispatchTrigger - { - OptionalInputs = new[] { "Opt" }, - RequiredInputs = new[] { "Req" } - }); + { + OptionalInputs = new[] + { + "Opt" + }, + RequiredInputs = new[] + { + "Req" + } + }); #pragma warning restore FALLOUTOBS001 var typed = Render(new GitHubActionsWorkflowDispatchTrigger - { - Inputs = new[] - { - new GitHubActionsWorkflowDispatchInput { Name = "Opt", Required = false }, - new GitHubActionsWorkflowDispatchInput { Name = "Req", Required = true } - } - }); + { + Inputs = new[] + { + new GitHubActionsWorkflowDispatchInput + { + Name = "Opt", + Required = false + }, + new GitHubActionsWorkflowDispatchInput + { + Name = "Req", + Required = true + } + } + }); typed.Should().Be(legacy); } diff --git a/tests/Fallout.Common.Specs/CI/ISpecConfigurationGenerator.cs b/tests/Fallout.Common.Specs/CI/ISpecConfigurationGenerator.cs index 3663db91a..56e339f68 100644 --- a/tests/Fallout.Common.Specs/CI/ISpecConfigurationGenerator.cs +++ b/tests/Fallout.Common.Specs/CI/ISpecConfigurationGenerator.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common.CI; namespace Fallout.Common.Specs.CI; diff --git a/tests/Fallout.Common.Specs/CI/SpecAppVeyorAttribute.cs b/tests/Fallout.Common.Specs/CI/SpecAppVeyorAttribute.cs index ea9e16d19..503a04e91 100644 --- a/tests/Fallout.Common.Specs/CI/SpecAppVeyorAttribute.cs +++ b/tests/Fallout.Common.Specs/CI/SpecAppVeyorAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common.CI.AppVeyor; namespace Fallout.Common.Specs.CI; diff --git a/tests/Fallout.Common.Specs/CI/SpecAzurePipelinesAttribute.cs b/tests/Fallout.Common.Specs/CI/SpecAzurePipelinesAttribute.cs index 45829980c..9368d867f 100644 --- a/tests/Fallout.Common.Specs/CI/SpecAzurePipelinesAttribute.cs +++ b/tests/Fallout.Common.Specs/CI/SpecAzurePipelinesAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common.CI.AzurePipelines; namespace Fallout.Common.Specs.CI; diff --git a/tests/Fallout.Common.Specs/CI/SpecGitHubActionsAttribute.cs b/tests/Fallout.Common.Specs/CI/SpecGitHubActionsAttribute.cs index ddf2f05ec..7ee54e041 100644 --- a/tests/Fallout.Common.Specs/CI/SpecGitHubActionsAttribute.cs +++ b/tests/Fallout.Common.Specs/CI/SpecGitHubActionsAttribute.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -28,7 +27,10 @@ public TestGitHubActionsAttribute(string name, GitHubActionsImage image, params protected override IEnumerable DeclaredInputs => Inputs; - protected override ISet DeclaredWorkflowNames => (WorkflowNames ?? new[] { IdPostfix }).ToHashSet(); + protected override ISet DeclaredWorkflowNames => (WorkflowNames ?? new[] + { + IdPostfix + }).ToHashSet(); protected override StreamWriter CreateStream() { diff --git a/tests/Fallout.Common.Specs/CI/SpecSpaceAutomationAttribute.cs b/tests/Fallout.Common.Specs/CI/SpecSpaceAutomationAttribute.cs index 497526893..02393039d 100644 --- a/tests/Fallout.Common.Specs/CI/SpecSpaceAutomationAttribute.cs +++ b/tests/Fallout.Common.Specs/CI/SpecSpaceAutomationAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common.CI.SpaceAutomation; namespace Fallout.Common.Specs.CI; diff --git a/tests/Fallout.Common.Specs/CI/SpecTeamCityAttribute.cs b/tests/Fallout.Common.Specs/CI/SpecTeamCityAttribute.cs index f8d7e11f5..84edb6c29 100644 --- a/tests/Fallout.Common.Specs/CI/SpecTeamCityAttribute.cs +++ b/tests/Fallout.Common.Specs/CI/SpecTeamCityAttribute.cs @@ -1,6 +1,4 @@ -using System; using System.IO; -using System.Linq; using Fallout.Common.CI.TeamCity; namespace Fallout.Common.Specs.CI; diff --git a/tests/Fallout.Common.Specs/CISpecs.cs b/tests/Fallout.Common.Specs/CISpecs.cs index 81d76213e..cf5a9c842 100644 --- a/tests/Fallout.Common.Specs/CISpecs.cs +++ b/tests/Fallout.Common.Specs/CISpecs.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Reflection; -using FluentAssertions; using Fallout.Common.CI; using Fallout.Common.CI.AppVeyor; using Fallout.Common.CI.AzurePipelines; @@ -12,6 +11,7 @@ using Fallout.Common.CI.Jenkins; using Fallout.Common.CI.TeamCity; using Fallout.Common.CI.TravisCI; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -75,7 +75,11 @@ public static IEnumerable Properties(Type type) var instance = CreateInstance(type); return type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(x => new[] { x, instance }).ToArray(); + .Select(x => new[] + { + x, + instance + }).ToArray(); } private static void AssertProperty(object instance, PropertyInfo property) @@ -94,11 +98,16 @@ private static void AssertProperty(object instance, PropertyInfo property) // reference types and nullable value types; require non-null on plain value types. var allowsNull = !property.PropertyType.IsValueType || Nullable.GetUnderlyingType(property.PropertyType) != null; + if (!allowsNull) + { value.Should().NotBeNull(); + } if (value is not string strValue || property.GetCustomAttribute() != null) + { return; + } bool.TryParse(strValue, out _).Should().BeFalse("boolean"); long.TryParse(strValue, out _).Should().BeFalse("long"); @@ -110,14 +119,17 @@ private static void AssertProperty(object instance, PropertyInfo property) private static object CreateInstance(Type type) { - var bindingFlags = BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.OptionalParamBinding; - return Activator.CreateInstance(type, bindingFlags, binder: null, args: new object[0], culture: CultureInfo.CurrentCulture); + var bindingFlags = BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance | + BindingFlags.OptionalParamBinding; + + return Activator.CreateInstance(type, bindingFlags, binder: null, args: new object[0], + culture: CultureInfo.CurrentCulture); } private static bool IsRunning(Type type) { var property = type.GetProperty($"IsRunning{type.Name}", BindingFlags.NonPublic | BindingFlags.Static).NotNull(); - return (bool) property.GetValue(obj: null); + return (bool)property.GetValue(obj: null); } private class CITheoryAttribute : TheoryAttribute diff --git a/tests/Fallout.Common.Specs/ChangelogTasksSpecs.cs b/tests/Fallout.Common.Specs/ChangelogTasksSpecs.cs index d7ed76852..6c241ecb6 100644 --- a/tests/Fallout.Common.Specs/ChangelogTasksSpecs.cs +++ b/tests/Fallout.Common.Specs/ChangelogTasksSpecs.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Fallout.Common.ChangeLog; using Fallout.Common.Git; using Fallout.Common.IO; -using Fallout.Common.Tools.GitHub; +using FluentAssertions; using VerifyXunit; using Xunit; @@ -18,7 +17,8 @@ public class ChangelogTasksSpecs { private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull(); - private static AbsolutePath PathToChangelogReferenceFiles => RootDirectory / "tests" / "Fallout.Common.Specs" / "ChangelogReferenceFiles"; + private static AbsolutePath PathToChangelogReferenceFiles => + RootDirectory / "tests" / "Fallout.Common.Specs" / "ChangelogReferenceFiles"; [Theory] [MemberData(nameof(AllChangelogReference_1_0_0_Files))] @@ -110,9 +110,15 @@ public void ReadChangelog_EmptyChangelogFile_ThrowsInformativeException(string f public static IEnumerable AllChangelogReference_1_0_0_Files { - get => PathToChangelogReferenceFiles.GlobFiles("changelog_reference_1.0.0*.md").Select(x => new object[] { x }); + get => PathToChangelogReferenceFiles.GlobFiles("changelog_reference_1.0.0*.md").Select(x => new object[] + { + x + }); } public static IEnumerable AllChangelogReference_NUKE_Files - => PathToChangelogReferenceFiles.GlobFiles("changelog_reference_NUKE*.md").Select(x => new object[] { x }); + => PathToChangelogReferenceFiles.GlobFiles("changelog_reference_NUKE*.md").Select(x => new object[] + { + x + }); } diff --git a/tests/Fallout.Common.Specs/GitHubTasksSpecs.cs b/tests/Fallout.Common.Specs/GitHubTasksSpecs.cs index 8cba354ff..d00d872fc 100644 --- a/tests/Fallout.Common.Specs/GitHubTasksSpecs.cs +++ b/tests/Fallout.Common.Specs/GitHubTasksSpecs.cs @@ -1,9 +1,7 @@ -using System; -using System.Linq; -using FluentAssertions; using Fallout.Common.Git; using Fallout.Common.IO; using Fallout.Common.Tools.GitHub; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -17,13 +15,17 @@ public void GitHubRepositoryFromLocalDirectorySpec() { var repository = GitRepository.FromLocalDirectory(RootDirectory).NotNull(); if (!repository.IsGitHubRepository()) + { return; + } // The URL helpers below are branch-based (GetGitHubDownloadUrl asserts Branch != null). // A tag-triggered release build checks out a detached HEAD, which has no branch, so // there is nothing meaningful to assert here. if (repository.Branch is null) + { return; + } var rawUrl = $"https://raw.githubusercontent.com/{repository.Identifier}/{repository.Branch}"; var blobUrl = $"https://github.com/{repository.Identifier}/blob/{repository.Branch}"; diff --git a/tests/Fallout.Common.Specs/GitVersionParseSpecs.cs b/tests/Fallout.Common.Specs/GitVersionParseSpecs.cs index f7d68756d..d1a2d2568 100644 --- a/tests/Fallout.Common.Specs/GitVersionParseSpecs.cs +++ b/tests/Fallout.Common.Specs/GitVersionParseSpecs.cs @@ -3,9 +3,9 @@ // Distributed under the MIT License. // https://github.com/Fallout-build/Fallout/blob/main/LICENSE -using FluentAssertions; using Fallout.Common.Tools.GitVersion; using Fallout.Common.Utilities; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -19,38 +19,38 @@ public class GitVersionParseSpecs // Exact payload from issue #218 — BuildMetaData, CommitsSinceVersionSource, // PreReleaseNumber and WeightedPreReleaseNumber are numbers, not strings. private const string GitVersion6Json = """ - { - "AssemblySemFileVer": "8.10.1.0", - "AssemblySemVer": "8.10.1.0", - "BranchName": "switch-to-fallout", - "BuildMetaData": 7, - "CommitDate": "2026-05-26", - "CommitsSinceVersionSource": 7, - "EscapedBranchName": "switch-to-fallout", - "FullBuildMetaData": "7.Branch.switch-to-fallout.Sha.6419fa509c7934f0b34a0ea5e5306c44c0a9a259", - "FullSemVer": "8.10.1-switch-to-fallout.1+7", - "InformationalVersion": "8.10.1-switch-to-fallout.1+7.Branch.switch-to-fallout.Sha.6419fa509c7934f0b34a0ea5e5306c44c0a9a259", - "Major": 8, - "MajorMinorPatch": "8.10.1", - "Minor": 10, - "Patch": 1, - "PreReleaseLabel": "switch-to-fallout", - "PreReleaseLabelWithDash": "-switch-to-fallout", - "PreReleaseNumber": 1, - "PreReleaseTag": "switch-to-fallout.1", - "PreReleaseTagWithDash": "-switch-to-fallout.1", - "SemVer": "8.10.1-switch-to-fallout.1", - "Sha": "6419fa509c7934f0b34a0ea5e5306c44c0a9a259", - "ShortSha": "6419fa5", - "UncommittedChanges": 13, - "VersionSourceDistance": 7, - "VersionSourceIncrement": "None", - "VersionSourceSemVer": "8.10.0", - "VersionSourceSha": "0954811776a71005283221b91aabafc5fec332b7", - "WeightedPreReleaseNumber": 1 - } - """; - + { + "AssemblySemFileVer": "8.10.1.0", + "AssemblySemVer": "8.10.1.0", + "BranchName": "switch-to-fallout", + "BuildMetaData": 7, + "CommitDate": "2026-05-26", + "CommitsSinceVersionSource": 7, + "EscapedBranchName": "switch-to-fallout", + "FullBuildMetaData": "7.Branch.switch-to-fallout.Sha.6419fa509c7934f0b34a0ea5e5306c44c0a9a259", + "FullSemVer": "8.10.1-switch-to-fallout.1+7", + "InformationalVersion": "8.10.1-switch-to-fallout.1+7.Branch.switch-to-fallout.Sha.6419fa509c7934f0b34a0ea5e5306c44c0a9a259", + "Major": 8, + "MajorMinorPatch": "8.10.1", + "Minor": 10, + "Patch": 1, + "PreReleaseLabel": "switch-to-fallout", + "PreReleaseLabelWithDash": "-switch-to-fallout", + "PreReleaseNumber": 1, + "PreReleaseTag": "switch-to-fallout.1", + "PreReleaseTagWithDash": "-switch-to-fallout.1", + "SemVer": "8.10.1-switch-to-fallout.1", + "Sha": "6419fa509c7934f0b34a0ea5e5306c44c0a9a259", + "ShortSha": "6419fa5", + "UncommittedChanges": 13, + "VersionSourceDistance": 7, + "VersionSourceIncrement": "None", + "VersionSourceSemVer": "8.10.0", + "VersionSourceSha": "0954811776a71005283221b91aabafc5fec332b7", + "WeightedPreReleaseNumber": 1 + } + """; + [Fact] public void Can_parse_a_v6_response() { diff --git a/tests/Fallout.Common.Specs/SettingsSpecs.cs b/tests/Fallout.Common.Specs/SettingsSpecs.cs index a6dd051a0..e656766dc 100644 --- a/tests/Fallout.Common.Specs/SettingsSpecs.cs +++ b/tests/Fallout.Common.Specs/SettingsSpecs.cs @@ -1,12 +1,7 @@ using System; using System.IO; -using System.Linq; -using System.Threading.Tasks; using System.Text.Json; -using FluentAssertions; -using Fallout.Common.Tools.MSBuild; -using Fallout.Common.Tools.OpenCover; -using Fallout.Common.Tools.Xunit; +using System.Threading.Tasks; using Fallout.Common.IO; using Fallout.Common.Tooling; using Fallout.Common.Tools.CorFlags; @@ -14,7 +9,11 @@ using Fallout.Common.Tools.Docker; using Fallout.Common.Tools.DotNet; using Fallout.Common.Tools.Kubernetes; +using Fallout.Common.Tools.MSBuild; +using Fallout.Common.Tools.OpenCover; +using Fallout.Common.Tools.Xunit; using Fallout.Common.Utilities; +using FluentAssertions; using VerifyXunit; using Xunit; @@ -176,6 +175,7 @@ public Task TestDiscord() .SetType(DiscordEmbedType.article) .SetAuthor(_ => _ .SetName("author-name"))); + var result = JsonSerializer.Serialize(message, message.GetType(), Options.SerializerOptions); return Verifier.Verify(result); diff --git a/tests/Fallout.Common.Specs/VerifySpecsInitializer.cs b/tests/Fallout.Common.Specs/VerifySpecsInitializer.cs index ff5f8c9a5..c05f3f7e3 100644 --- a/tests/Fallout.Common.Specs/VerifySpecsInitializer.cs +++ b/tests/Fallout.Common.Specs/VerifySpecsInitializer.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Runtime.CompilerServices; using VerifyTests; diff --git a/tests/Fallout.Common.Specs/XmlTasksSpecs.cs b/tests/Fallout.Common.Specs/XmlTasksSpecs.cs index 51f8b8cac..7b0ab1f71 100644 --- a/tests/Fallout.Common.Specs/XmlTasksSpecs.cs +++ b/tests/Fallout.Common.Specs/XmlTasksSpecs.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Threading.Tasks; using System.Xml; -using FluentAssertions; using Fallout.Common.IO; +using FluentAssertions; using VerifyXunit; using Xunit; @@ -17,7 +17,9 @@ public class XmlTasksSpecs : IDisposable public void Dispose() { if (File.Exists(_tempFile)) + { File.Delete(_tempFile); + } } [Fact] diff --git a/tests/Fallout.Components.Specs/PublishPackageRouterSpecs.cs b/tests/Fallout.Components.Specs/PublishPackageRouterSpecs.cs index b2404144a..19f216a3e 100644 --- a/tests/Fallout.Components.Specs/PublishPackageRouterSpecs.cs +++ b/tests/Fallout.Components.Specs/PublishPackageRouterSpecs.cs @@ -27,8 +27,14 @@ public void Accepts_includes_then_applies_excludes() { Name = "nuget.org", Source = "https://api.nuget.org/v3/index.json", - IncludePackages = new[] { "Fallout.*" }, - ExcludePackages = new[] { "Nuke.*" }, + IncludePackages = new[] + { + "Fallout.*" + }, + ExcludePackages = new[] + { + "Nuke.*" + }, }; nugetOrg.Accepts("Fallout.Common.2026.1.0").Should().BeTrue(); @@ -42,8 +48,14 @@ public void Accepts_exclusion_wins_over_inclusion() { Name = "t", Source = "s", - IncludePackages = new[] { "*" }, - ExcludePackages = new[] { "Nuke.*" }, + IncludePackages = new[] + { + "*" + }, + ExcludePackages = new[] + { + "Nuke.*" + }, }; target.Accepts("Fallout.Common").Should().BeTrue(); @@ -54,8 +66,17 @@ public void Accepts_exclusion_wins_over_inclusion() public void Default_target_accepts_everything() { // The real github-packages routing: everything, including the Nuke.* shims. - var ghPackages = new PublishTarget { Name = "github-packages", Source = "s" }; - var all = new[] { "Fallout.Common.1.0.0", "Nuke.Common.1.0.0" }; + var ghPackages = new PublishTarget + { + Name = "github-packages", + Source = "s" + }; + + var all = new[] + { + "Fallout.Common.1.0.0", + "Nuke.Common.1.0.0" + }; PublishPackageRouter.Route(ghPackages, all).Should().BeEquivalentTo(all); } diff --git a/tests/Fallout.Core.Specs/ArchitectureFitnessSpecs.cs b/tests/Fallout.Core.Specs/ArchitectureFitnessSpecs.cs index 0c00c7f8f..260601e3b 100644 --- a/tests/Fallout.Core.Specs/ArchitectureFitnessSpecs.cs +++ b/tests/Fallout.Core.Specs/ArchitectureFitnessSpecs.cs @@ -1,7 +1,6 @@ -using System.Linq; using System.Reflection; -using FluentAssertions; using Fallout.Core.Planning; +using FluentAssertions; using NetArchTest.Rules; using Xunit; diff --git a/tests/Fallout.Core.Specs/TopoSortSpecs.cs b/tests/Fallout.Core.Specs/TopoSortSpecs.cs index 272f1936f..d775ecc84 100644 --- a/tests/Fallout.Core.Specs/TopoSortSpecs.cs +++ b/tests/Fallout.Core.Specs/TopoSortSpecs.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -using FluentAssertions; using Fallout.Core.Planning; +using FluentAssertions; using Xunit; namespace Fallout.Core.Specs; @@ -23,8 +23,23 @@ public void Orders_a_chain_roots_first() { // C depends on B depends on A. var plan = Order( - new[] { "A", "B", "C" }, - new Dictionary { ["B"] = new[] { "A" }, ["C"] = new[] { "B" } }); + new[] + { + "A", + "B", + "C" + }, + new Dictionary + { + ["B"] = new[] + { + "A" + }, + ["C"] = new[] + { + "B" + } + }); plan.HasCycles.Should().BeFalse(); // Roots (nothing depends on them) come first; a node always precedes its dependencies. @@ -35,21 +50,44 @@ public void Orders_a_chain_roots_first() public void Detects_a_cycle() { var plan = Order( - new[] { "A", "B" }, - new Dictionary { ["A"] = new[] { "B" }, ["B"] = new[] { "A" } }); + new[] + { + "A", + "B" + }, + new Dictionary + { + ["A"] = new[] + { + "B" + }, + ["B"] = new[] + { + "A" + } + }); plan.HasCycles.Should().BeTrue(); plan.Ordered.Should().BeEmpty(); plan.Cycles.Should().ContainSingle() - .Which.Should().BeEquivalentTo(new[] { "A", "B" }); + .Which.Should().BeEquivalentTo("A", "B"); } [Fact] public void Ignores_edges_to_unknown_nodes() { var plan = Order( - new[] { "A" }, - new Dictionary { ["A"] = new[] { "ghost" } }); + new[] + { + "A" + }, + new Dictionary + { + ["A"] = new[] + { + "ghost" + } + }); plan.HasCycles.Should().BeFalse(); plan.Ordered.Should().Equal("A"); @@ -60,22 +98,30 @@ public void Strict_mode_flags_an_ambiguous_step() { // Two independent roots, no ordering between them. var plan = Order( - new[] { "A", "B" }, + new[] + { + "A", + "B" + }, new Dictionary(), strict: true); plan.IsAmbiguous.Should().BeTrue(); - plan.AmbiguousStep.Should().BeEquivalentTo(new[] { "A", "B" }); + plan.AmbiguousStep.Should().BeEquivalentTo("A", "B"); } [Fact] public void Non_strict_mode_never_flags_ambiguity() { var plan = Order( - new[] { "A", "B" }, + new[] + { + "A", + "B" + }, new Dictionary()); plan.IsAmbiguous.Should().BeFalse(); - plan.Ordered.Should().BeEquivalentTo(new[] { "A", "B" }); + plan.Ordered.Should().BeEquivalentTo("A", "B"); } } diff --git a/tests/Fallout.Migrate.Analyzers.Specs/AnalyzerSpecHarness.cs b/tests/Fallout.Migrate.Analyzers.Specs/AnalyzerSpecHarness.cs index 37220bbba..d25d1801c 100644 --- a/tests/Fallout.Migrate.Analyzers.Specs/AnalyzerSpecHarness.cs +++ b/tests/Fallout.Migrate.Analyzers.Specs/AnalyzerSpecHarness.cs @@ -1,4 +1,4 @@ -using Fallout.Migrate.Analyzers; +using Fallout.Common.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing; @@ -20,7 +20,7 @@ internal static class AnalyzerTestHarness // reference Fallout.* to fire" guard. Without this every test would silently // pass because the guard short-circuits. public static readonly MetadataReference FalloutMarkerReference = - MetadataReference.CreateFromFile(typeof(global::Fallout.Common.Utilities.CompletionUtility).Assembly.Location); + MetadataReference.CreateFromFile(typeof(CompletionUtility).Assembly.Location); } internal sealed class AnalyzerSpecs : CSharpAnalyzerTest diff --git a/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationAnalyzerSpecs.cs b/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationAnalyzerSpecs.cs index f28985ca8..71d72c55e 100644 --- a/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationAnalyzerSpecs.cs +++ b/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationAnalyzerSpecs.cs @@ -1,4 +1,6 @@ using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; using Xunit; namespace Fallout.Migrate.Analyzers.Specs; @@ -9,86 +11,101 @@ public class NukeMigrationAnalyzerSpecs // Without these the compiler emits CS0246 alongside FALLOUT004 and the // verifier (rightly) complains about an unexpected extra diagnostic. private const string LegacyNukeStub = """ - namespace Nuke.Common - { - public class AbsolutePath { } - namespace Tools.DotNet - { - public static class DotNetTasks { public static int X() => 0; } - } - } - """; + namespace Nuke.Common + { + public class AbsolutePath { } + namespace Tools.DotNet + { + public static class DotNetTasks { public static int X() => 0; } + } + } + """; [Fact] public async Task FlagsUsingNamespaceDirective() { var source = $$""" - using {|FALLOUT004:Nuke.Common|}; - namespace X { class C { } } - {{LegacyNukeStub}} - """; + using {|FALLOUT004:Nuke.Common|}; + namespace X { class C { } } + {{LegacyNukeStub}} + """; - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } [Fact] public async Task FlagsUsingStaticDirective() { var source = $$""" - using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; - namespace X { class C { } } - {{LegacyNukeStub}} - """; + using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; + namespace X { class C { } } + {{LegacyNukeStub}} + """; - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } [Fact] public async Task FlagsFullyQualifiedTypeReference() { var source = $$""" - namespace X - { - class C - { - void M() - { - {|FALLOUT004:Nuke.Common.AbsolutePath|} x = null; - } - } - } - {{LegacyNukeStub}} - """; - - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + namespace X + { + class C + { + void M() + { + {|FALLOUT004:Nuke.Common.AbsolutePath|} x = null; + } + } + } + {{LegacyNukeStub}} + """; + + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } [Fact] public async Task FlagsBareNukeBuildBaseType() { const string source = """ - namespace X - { - class B : {|FALLOUT004:NukeBuild|} { } - class NukeBuild { } - } - """; - - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + namespace X + { + class B : {|FALLOUT004:NukeBuild|} { } + class NukeBuild { } + } + """; + + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } [Fact] public async Task FlagsBareINukeBuildBaseInterface() { const string source = """ - namespace X - { - interface IFoo : {|FALLOUT004:INukeBuild|} { } - interface INukeBuild { } - } - """; - - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + namespace X + { + interface IFoo : {|FALLOUT004:INukeBuild|} { } + interface INukeBuild { } + } + """; + + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } [Fact] @@ -96,15 +113,15 @@ public async Task DoesNotFireWhenProjectDoesNotReferenceFallout() { // Drop the Fallout marker reference for this test — the analyzer's guard // should short-circuit and produce no diagnostics. - var test = new Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest< + var test = new CSharpAnalyzerTest< NukeMigrationAnalyzer, - Microsoft.CodeAnalysis.Testing.DefaultVerifier> + DefaultVerifier> { TestCode = $$""" - using Nuke.Common; - namespace X { class C { } } - {{LegacyNukeStub}} - """, + using Nuke.Common; + namespace X { class C { } } + {{LegacyNukeStub}} + """, }; // No expected diagnostics: guard should suppress. @@ -115,19 +132,22 @@ namespace X { class C { } } public async Task DoesNotFireOnUnrelatedIdentifiers() { const string source = """ - namespace X - { - class C - { - void M() - { - var x = 1; - var y = System.DateTime.Now; - } - } - } - """; - - await new AnalyzerSpecs { TestCode = source }.RunAsync(); + namespace X + { + class C + { + void M() + { + var x = 1; + var y = System.DateTime.Now; + } + } + } + """; + + await new AnalyzerSpecs + { + TestCode = source + }.RunAsync(); } } diff --git a/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationCodeFixSpecs.cs b/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationCodeFixSpecs.cs index a9017bce1..a7106aef3 100644 --- a/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationCodeFixSpecs.cs +++ b/tests/Fallout.Migrate.Analyzers.Specs/NukeMigrationCodeFixSpecs.cs @@ -9,164 +9,188 @@ public class NukeMigrationCodeFixSpecs // legacy for the pre-fix source, target for the fixed source — otherwise the // verifier sees CS0246 errors alongside FALLOUT004 and fails the assertion. private const string LegacyNukeStub = """ - namespace Nuke.Common - { - public class AbsolutePath { } - namespace Tools.DotNet - { - public static class DotNetTasks { public static int X() => 0; } - } - } - """; + namespace Nuke.Common + { + public class AbsolutePath { } + namespace Tools.DotNet + { + public static class DotNetTasks { public static int X() => 0; } + } + } + """; private const string TargetFalloutStub = """ - namespace Fallout.Common - { - public class AbsolutePath { } - namespace Tools.DotNet - { - public static class DotNetTasks { public static int X() => 0; } - } - } - """; + namespace Fallout.Common + { + public class AbsolutePath { } + namespace Tools.DotNet + { + public static class DotNetTasks { public static int X() => 0; } + } + } + """; [Fact] public async Task RewritesUsingNamespaceDirective() { var source = $$""" - using {|FALLOUT004:Nuke.Common|}; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + using {|FALLOUT004:Nuke.Common|}; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; var fixedSource = $$""" - using Fallout.Common; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + using Fallout.Common; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } [Fact] public async Task RewritesUsingStaticDirective() { var source = $$""" - using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; var fixedSource = $$""" - using static Fallout.Common.Tools.DotNet.DotNetTasks; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + using static Fallout.Common.Tools.DotNet.DotNetTasks; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } [Fact] public async Task RewritesFullyQualifiedTypeReference() { var source = $$""" - namespace X - { - class C - { - object M() => typeof({|FALLOUT004:Nuke.Common.AbsolutePath|}); - } - } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + namespace X + { + class C + { + object M() => typeof({|FALLOUT004:Nuke.Common.AbsolutePath|}); + } + } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; var fixedSource = $$""" - namespace X - { - class C - { - object M() => typeof(Fallout.Common.AbsolutePath); - } - } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; - - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + namespace X + { + class C + { + object M() => typeof(Fallout.Common.AbsolutePath); + } + } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; + + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } [Fact] public async Task RewritesNukeBuildToFalloutBuild() { const string source = """ - namespace X - { - class B : {|FALLOUT004:NukeBuild|} { } - class NukeBuild { } - class FalloutBuild { } - } - """; + namespace X + { + class B : {|FALLOUT004:NukeBuild|} { } + class NukeBuild { } + class FalloutBuild { } + } + """; const string fixedSource = """ - namespace X - { - class B : FalloutBuild { } - class NukeBuild { } - class FalloutBuild { } - } - """; - - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + namespace X + { + class B : FalloutBuild { } + class NukeBuild { } + class FalloutBuild { } + } + """; + + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } [Fact] public async Task RewritesINukeBuildToIFalloutBuild() { const string source = """ - namespace X - { - interface IFoo : {|FALLOUT004:INukeBuild|} { } - interface INukeBuild { } - interface IFalloutBuild { } - } - """; + namespace X + { + interface IFoo : {|FALLOUT004:INukeBuild|} { } + interface INukeBuild { } + interface IFalloutBuild { } + } + """; const string fixedSource = """ - namespace X - { - interface IFoo : IFalloutBuild { } - interface INukeBuild { } - interface IFalloutBuild { } - } - """; - - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + namespace X + { + interface IFoo : IFalloutBuild { } + interface INukeBuild { } + interface IFalloutBuild { } + } + """; + + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } [Fact] public async Task PreservesLeadingCommentOnUsingDirective() { var source = $$""" - // Legacy build, mid-migration. - using {|FALLOUT004:Nuke.Common|}; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + // Legacy build, mid-migration. + using {|FALLOUT004:Nuke.Common|}; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; var fixedSource = $$""" - // Legacy build, mid-migration. - using Fallout.Common; - namespace X { class C { } } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; - - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + // Legacy build, mid-migration. + using Fallout.Common; + namespace X { class C { } } + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; + + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } } diff --git a/tests/Fallout.Migrate.Analyzers.Specs/RealWorldSmokeSpecs.cs b/tests/Fallout.Migrate.Analyzers.Specs/RealWorldSmokeSpecs.cs index 2f2b1522c..fdbdb7a5c 100644 --- a/tests/Fallout.Migrate.Analyzers.Specs/RealWorldSmokeSpecs.cs +++ b/tests/Fallout.Migrate.Analyzers.Specs/RealWorldSmokeSpecs.cs @@ -13,26 +13,26 @@ public class RealWorldSmokeSpecs // Stand-in declarations so the test compilation can resolve the legacy // Nuke.* namespace structure used in pre-rename consumer code. private const string LegacyNukeStub = """ - namespace Nuke.Common - { - public class NukeBuild { public static int Execute(System.Linq.Expressions.Expression> defaultTarget) => 0; } - public class Target { } - namespace ProjectModel { public class Solution { } } - namespace Tools.DotNet { public static class DotNetTasks { } } - } - namespace Nuke.Components { public interface IPack { } } - """; + namespace Nuke.Common + { + public class NukeBuild { public static int Execute(System.Linq.Expressions.Expression> defaultTarget) => 0; } + public class Target { } + namespace ProjectModel { public class Solution { } } + namespace Tools.DotNet { public static class DotNetTasks { } } + } + namespace Nuke.Components { public interface IPack { } } + """; private const string TargetFalloutStub = """ - namespace Fallout.Common - { - public class FalloutBuild { public static int Execute(System.Linq.Expressions.Expression> defaultTarget) => 0; } - public class Target { } - namespace ProjectModel { public class Solution { } } - namespace Tools.DotNet { public static class DotNetTasks { } } - } - namespace Fallout.Components { public interface IPack { } } - """; + namespace Fallout.Common + { + public class FalloutBuild { public static int Execute(System.Linq.Expressions.Expression> defaultTarget) => 0; } + public class Target { } + namespace ProjectModel { public class Solution { } } + namespace Tools.DotNet { public static class DotNetTasks { } } + } + namespace Fallout.Components { public interface IPack { } } + """; [Fact] public async Task RewritesRealisticPreRenameBuildClass() @@ -44,39 +44,43 @@ public async Task RewritesRealisticPreRenameBuildClass() // - fully-qualified Nuke.Common.ProjectModel.Solution as a type // - bare reference to a Components interface from Nuke.Components var source = $$""" - using {|FALLOUT004:Nuke.Common|}; - using {|FALLOUT004:Nuke.Common.ProjectModel|}; - using {|FALLOUT004:Nuke.Components|}; - using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; + using {|FALLOUT004:Nuke.Common|}; + using {|FALLOUT004:Nuke.Common.ProjectModel|}; + using {|FALLOUT004:Nuke.Components|}; + using static {|FALLOUT004:Nuke.Common.Tools.DotNet.DotNetTasks|}; - partial class Build : {|FALLOUT004:NukeBuild|}, {|FALLOUT004:Nuke.Components.IPack|} - { - public static int Main() => Execute(x => null); + partial class Build : {|FALLOUT004:NukeBuild|}, {|FALLOUT004:Nuke.Components.IPack|} + { + public static int Main() => Execute(x => null); - {|FALLOUT004:Nuke.Common.ProjectModel.Solution|} _solution; - } + {|FALLOUT004:Nuke.Common.ProjectModel.Solution|} _solution; + } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; var fixedSource = $$""" - using Fallout.Common; - using Fallout.Common.ProjectModel; - using Fallout.Components; - using static Fallout.Common.Tools.DotNet.DotNetTasks; + using Fallout.Common; + using Fallout.Common.ProjectModel; + using Fallout.Components; + using static Fallout.Common.Tools.DotNet.DotNetTasks; - partial class Build : FalloutBuild, Fallout.Components.IPack - { - public static int Main() => Execute(x => null); + partial class Build : FalloutBuild, Fallout.Components.IPack + { + public static int Main() => Execute(x => null); - Fallout.Common.ProjectModel.Solution _solution; - } + Fallout.Common.ProjectModel.Solution _solution; + } - {{LegacyNukeStub}} - {{TargetFalloutStub}} - """; + {{LegacyNukeStub}} + {{TargetFalloutStub}} + """; - await new CodeFixSpecs { TestCode = source, FixedCode = fixedSource }.RunAsync(); + await new CodeFixSpecs + { + TestCode = source, + FixedCode = fixedSource + }.RunAsync(); } } diff --git a/tests/Fallout.Migrate.Specs/RewriteBootstrapScriptsStepSpecs.cs b/tests/Fallout.Migrate.Specs/RewriteBootstrapScriptsStepSpecs.cs index 4c847fc84..b437f0995 100644 --- a/tests/Fallout.Migrate.Specs/RewriteBootstrapScriptsStepSpecs.cs +++ b/tests/Fallout.Migrate.Specs/RewriteBootstrapScriptsStepSpecs.cs @@ -55,8 +55,8 @@ public async Task Dot_nuke_directory_references_become_dot_fallout() public async Task Legacy_nuke_env_vars_are_renamed_to_their_fallout_equivalents() { (tempDirectory / "build.ps1").WriteAllText(""" - $env:NUKE_GLOBAL_TOOL_VERSION = "10.0" - """, eofLineBreak: false); + $env:NUKE_GLOBAL_TOOL_VERSION = "10.0" + """, eofLineBreak: false); await new RewriteBootstrapScriptsStep().ExecuteAsync(context, summary); @@ -66,9 +66,9 @@ public async Task Legacy_nuke_env_vars_are_renamed_to_their_fallout_equivalents( } [Theory] - [InlineData("build.sh", "export NUKE_TELEMETRY_OPTOUT=1")] // build.sh + [InlineData("build.sh", "export NUKE_TELEMETRY_OPTOUT=1")] // build.sh [InlineData("build.ps1", """$env:NUKE_TELEMETRY_OPTOUT = "1" """)] // build.ps1 - [InlineData("build.cmd", "set NUKE_TELEMETRY_OPTOUT=1")] // build.cmd + [InlineData("build.cmd", "set NUKE_TELEMETRY_OPTOUT=1")] // build.cmd public async Task Telemetry_opt_out_line_is_stripped_entirely(string fileName, string optOutLine) { // Telemetry was removed from Fallout (ADR-0010) — the opt-out is dropped, not renamed @@ -79,6 +79,7 @@ public async Task Telemetry_opt_out_line_is_stripped_entirely(string fileName, s {optOutLine} dotnet nuke "$@" """; + (tempDirectory / fileName).WriteAllText(input, eofLineBreak: false); await new RewriteBootstrapScriptsStep().ExecuteAsync(context, summary); @@ -104,16 +105,16 @@ public async Task Plain_word_nuke_in_prose_is_left_alone() public async Task Removes_Nuke_enterprise_unix_bootstrapper_leftovers() { (tempDirectory / "build.sh").WriteAllText(""" - echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" + echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)" - if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then - "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true - "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true - fi + if [[ ! -z ${NUKE_ENTERPRISE_TOKEN+x} && "$NUKE_ENTERPRISE_TOKEN" != "" ]]; then + "$DOTNET_EXE" nuget remove source "nuke-enterprise" &>/dev/null || true + "$DOTNET_EXE" nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password "$NUKE_ENTERPRISE_TOKEN" --store-password-in-clear-text &>/dev/null || true + fi - "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet - "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" - """, eofLineBreak: false); + "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet + "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" + """, eofLineBreak: false); await new CleanupBootstrapScriptsStep().ExecuteAsync(context, summary); @@ -149,16 +150,16 @@ public async Task Does_not_throw_when_Nuke_enterprise_check_is_last_in_file() public async Task Removes_Nuke_enterprise_windows_bootstrapper_leftovers() { (tempDirectory / "build.ps1").WriteAllText(""" - Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" + Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" - if (Test-Path env:NUKE_ENTERPRISE_TOKEN) { - & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null - & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null - } + if (Test-Path env:NUKE_ENTERPRISE_TOKEN) { + & $env:DOTNET_EXE nuget remove source "nuke-enterprise" > $null + & $env:DOTNET_EXE nuget add source "https://f.feedz.io/nuke/enterprise/nuget" --name "nuke-enterprise" --username "PAT" --password $env:NUKE_ENTERPRISE_TOKEN > $null + } - ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } - ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } - """, eofLineBreak: false); + ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } + ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } + """, eofLineBreak: false); await new CleanupBootstrapScriptsStep().ExecuteAsync(context, summary); diff --git a/tests/Fallout.Migrate.Specs/RewriteCsFilesStepSpecs.cs b/tests/Fallout.Migrate.Specs/RewriteCsFilesStepSpecs.cs index c98925c6b..8cc688983 100644 --- a/tests/Fallout.Migrate.Specs/RewriteCsFilesStepSpecs.cs +++ b/tests/Fallout.Migrate.Specs/RewriteCsFilesStepSpecs.cs @@ -30,10 +30,10 @@ public void Dispose() public async Task Nuke_using_directives_are_rewritten_to_fallout() { (tempDirectory / "Build.cs").WriteAllText(""" - using Nuke.Common; - using Nuke.Common.IO; - using Fallout.Common; - """); + using Nuke.Common; + using Nuke.Common.IO; + using Fallout.Common; + """); await new RewriteCsFilesStep().ExecuteAsync(context, summary); diff --git a/tests/Fallout.Migrate.Specs/RewriteCsprojsStepSpecs.cs b/tests/Fallout.Migrate.Specs/RewriteCsprojsStepSpecs.cs index f13466cc0..fe1fa19a0 100644 --- a/tests/Fallout.Migrate.Specs/RewriteCsprojsStepSpecs.cs +++ b/tests/Fallout.Migrate.Specs/RewriteCsprojsStepSpecs.cs @@ -312,8 +312,9 @@ public async Task Recognizes_a_version_variable_prefixed_with_Nuke() """; + (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); @@ -341,7 +342,7 @@ public async Task Leaves_an_arbitrary_version_variable_alone_but_updates_the_ver """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); @@ -367,7 +368,7 @@ public async Task Leaves_an_unreferenced_arbitrary_variable_alone() """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); @@ -392,7 +393,7 @@ public async Task Does_not_bump_variables_used_for_non_Fallout_packages() """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input, eofLineBreak: false); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); @@ -417,15 +418,15 @@ public async Task Decouples_ambiguously_used_variables() """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); buildCsproj.Should() - .Contain("10.1.0") - .And.Contain("") - .And.Contain("$(FalloutVersion)", Exactly.Once()); + .Contain("10.1.0") + .And.Contain("") + .And.Contain("$(FalloutVersion)", Exactly.Once()); } [Fact] @@ -441,7 +442,7 @@ public async Task Decouples_ambiguously_used_variables_even_when_no_property_gro """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); @@ -468,7 +469,7 @@ public async Task Decouples_ambiguously_used_variables_when_a_property_group_but """; (tempDirectory / "build" / "_build.csproj").WriteAllText(input); - + await new RewriteCsprojsStep().ExecuteAsync(context, summary); var buildCsproj = (tempDirectory / "build" / "_build.csproj").ReadAllText(); diff --git a/tests/Fallout.ProjectModel.Specs/ProjectModelSpecs.cs b/tests/Fallout.ProjectModel.Specs/ProjectModelSpecs.cs index 37c2b26e0..a3788d21e 100644 --- a/tests/Fallout.ProjectModel.Specs/ProjectModelSpecs.cs +++ b/tests/Fallout.ProjectModel.Specs/ProjectModelSpecs.cs @@ -1,8 +1,8 @@ using System; using System.Linq; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Solutions; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; diff --git a/tests/Fallout.Solution.Specs/SolutionSpec.cs b/tests/Fallout.Solution.Specs/SolutionSpec.cs index 0ea17c551..4d2d344f2 100644 --- a/tests/Fallout.Solution.Specs/SolutionSpec.cs +++ b/tests/Fallout.Solution.Specs/SolutionSpec.cs @@ -1,9 +1,7 @@ -using System; using System.Linq; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Solutions; -using Fallout.Common.Utilities; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; diff --git a/tests/Fallout.SourceGenerators.Specs/StronglyTypedSolutionGeneratorSpecs.cs b/tests/Fallout.SourceGenerators.Specs/StronglyTypedSolutionGeneratorSpecs.cs index 8b57f7aa6..46e83c74f 100644 --- a/tests/Fallout.SourceGenerators.Specs/StronglyTypedSolutionGeneratorSpecs.cs +++ b/tests/Fallout.SourceGenerators.Specs/StronglyTypedSolutionGeneratorSpecs.cs @@ -1,11 +1,12 @@ using System; using System.Linq; using System.Threading.Tasks; +using Basic.Reference.Assemblies; +using Fallout.Common; +using Fallout.Solutions; using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Fallout.Common; -using Fallout.Solutions; using VerifyXunit; using Xunit; @@ -71,7 +72,9 @@ partial class Build : FalloutBuild var result = driver.RunGenerators(inputCompilation).GetRunResult(); if (!result.Diagnostics.IsEmpty) + { throw new Exception(string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.GetMessage()))); + } result.GeneratedTrees.Should().BeEmpty(); } @@ -96,7 +99,9 @@ partial class Build : FalloutBuild var result = driver.RunGenerators(inputCompilation).GetRunResult(); if (!result.Diagnostics.IsEmpty) + { throw new Exception(string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.GetMessage()))); + } result.GeneratedTrees.Should().BeEmpty(); } @@ -121,7 +126,9 @@ partial class Build : FalloutBuild var result = driver.RunGenerators(inputCompilation).GetRunResult(); if (!result.Diagnostics.IsEmpty) + { throw new Exception(string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.GetMessage()))); + } result.GeneratedTrees.Should().BeEmpty(); } @@ -146,7 +153,9 @@ partial class Build : FalloutBuild var result = driver.RunGenerators(inputCompilation).GetRunResult(); if (!result.Diagnostics.IsEmpty) + { throw new Exception(string.Join(Environment.NewLine, result.Diagnostics.Select(x => x.GetMessage()))); + } result.GeneratedTrees.Should().BeEmpty(); } @@ -158,7 +167,7 @@ private static Compilation CreateCompilation(string source) { CSharpSyntaxTree.ParseText(source) }, - Basic.Reference.Assemblies.NetStandard20.References.All + NetStandard20.References.All .Concat(new[] { typeof(FalloutBuild), diff --git a/tests/Fallout.SourceGenerators.Specs/TransitionShimGeneratorSpecs.cs b/tests/Fallout.SourceGenerators.Specs/TransitionShimGeneratorSpecs.cs index 307f32483..b37db0800 100644 --- a/tests/Fallout.SourceGenerators.Specs/TransitionShimGeneratorSpecs.cs +++ b/tests/Fallout.SourceGenerators.Specs/TransitionShimGeneratorSpecs.cs @@ -1,6 +1,8 @@ +using System; using System.IO; using System.Linq; using System.Threading.Tasks; +using Basic.Reference.Assemblies; using FluentAssertions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -20,46 +22,52 @@ public class TransitionShimGeneratorSpecs public Task EmitsShimsForEachKindAndSkipsHardTier() { var canonical = CompileCanonicalAssembly(""" - namespace Fallout.Common - { - // Easy tier - public class Regular { public Regular(string a) {} public Regular() {} } - public abstract class Abstr { protected Abstr() {} } - public interface IFoo { } - [System.AttributeUsage(System.AttributeTargets.Class)] - public class MyAttr : System.Attribute { public MyAttr(int n) {} } - public class Generic where T : class { public Generic(T item) {} } - public class WithNested - { - public WithNested() {} - public class Nested { public Nested() {} } - } - - // Hard tier — sealed-class still skipped (deferred to session 2b) - public sealed class SealedThing { public SealedThing() {} } - public enum MyEnum { A, B } - public class PrivateCtorOnly { private PrivateCtorOnly(string x) {} } - - // Static-class with the various method shapes that need delegation - public static class StaticHelpers - { - public static int Plain(int a) { return a; } - public static void VoidReturn(string s) { } - public static T Generic(T input) where T : class { return input; } - public static int WithOptional(int a, int b = 7, string s = "hello") { return a + b; } - public static int Sum(params int[] nums) { return 0; } - public static int TryParse(string s, out int value) { value = 0; return 0; } - public static string AsHex(this byte b) { return b.ToString("x2"); } - } - } - """); + namespace Fallout.Common + { + // Easy tier + public class Regular { public Regular(string a) {} public Regular() {} } + public abstract class Abstr { protected Abstr() {} } + public interface IFoo { } + [System.AttributeUsage(System.AttributeTargets.Class)] + public class MyAttr : System.Attribute { public MyAttr(int n) {} } + public class Generic where T : class { public Generic(T item) {} } + public class WithNested + { + public WithNested() {} + public class Nested { public Nested() {} } + } + + // Hard tier — sealed-class still skipped (deferred to session 2b) + public sealed class SealedThing { public SealedThing() {} } + public enum MyEnum { A, B } + public class PrivateCtorOnly { private PrivateCtorOnly(string x) {} } + + // Static-class with the various method shapes that need delegation + public static class StaticHelpers + { + public static int Plain(int a) { return a; } + public static void VoidReturn(string s) { } + public static T Generic(T input) where T : class { return input; } + public static int WithOptional(int a, int b = 7, string s = "hello") { return a + b; } + public static int Sum(params int[] nums) { return 0; } + public static int TryParse(string s, out int value) { value = 0; return 0; } + public static string AsHex(this byte b) { return b.ToString("x2"); } + } + } + """); var shimCompilation = CSharpCompilation.Create("Nuke.TestShim", - new[] { CSharpSyntaxTree.ParseText(""" - [assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder("Fallout.Common", "Nuke.Common")] - """) }, - Basic.Reference.Assemblies.NetStandard20.References.All - .Concat(new[] { canonical }), + new[] + { + CSharpSyntaxTree.ParseText(""" + [assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder("Fallout.Common", "Nuke.Common")] + """) + }, + NetStandard20.References.All + .Concat(new[] + { + canonical + }), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var driver = CSharpGeneratorDriver.Create(new TransitionShimGenerator()); @@ -75,29 +83,32 @@ public static void VoidReturn(string s) { } public void SkipsCanonicalTypesAlreadyHandBridgedByConsumer() { var canonical = CompileCanonicalAssembly(""" - namespace Fallout.Common - { - public sealed class HandBridgedSealed { public HandBridgedSealed() {} } - public class HandBridgedRegular { public HandBridgedRegular() {} } - } - """); + namespace Fallout.Common + { + public sealed class HandBridgedSealed { public HandBridgedSealed() {} } + public class HandBridgedRegular { public HandBridgedRegular() {} } + } + """); var shimCompilation = CSharpCompilation.Create("Nuke.HandBridged", new[] { CSharpSyntaxTree.ParseText(""" - [assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder("Fallout.Common", "Nuke.Common")] - """), + [assembly: Fallout.Migrate.Shims.ShimAllPublicTypesUnder("Fallout.Common", "Nuke.Common")] + """), CSharpSyntaxTree.ParseText(""" - namespace Nuke.Common - { - public static class HandBridgedSealed { } - public static class HandBridgedRegular { } - } - """), + namespace Nuke.Common + { + public static class HandBridgedSealed { } + public static class HandBridgedRegular { } + } + """), }, - Basic.Reference.Assemblies.NetStandard20.References.All - .Concat(new[] { canonical }), + NetStandard20.References.All + .Concat(new[] + { + canonical + }), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var driver = CSharpGeneratorDriver.Create(new TransitionShimGenerator()); @@ -105,7 +116,7 @@ public static class HandBridgedRegular { } // No source emitted for the hand-bridged canonical types. result.GeneratedTrees - .Where(t => !t.FilePath.EndsWith("ShimAllPublicTypesUnderAttribute.g.cs", System.StringComparison.Ordinal)) + .Where(t => !t.FilePath.EndsWith("ShimAllPublicTypesUnderAttribute.g.cs", StringComparison.Ordinal)) .Should().BeEmpty(); // And no SHIM001 diagnostic for either hand-bridged type — the sealed @@ -117,16 +128,22 @@ public static class HandBridgedRegular { } public void EmitsNothingWhenNoMarkerAttributePresent() { var canonical = CompileCanonicalAssembly(""" - namespace Fallout.Common - { - public class Whatever { public Whatever() {} } - } - """); + namespace Fallout.Common + { + public class Whatever { public Whatever() {} } + } + """); var shimCompilation = CSharpCompilation.Create("Nuke.NoMarker", - new[] { CSharpSyntaxTree.ParseText("// no marker") }, - Basic.Reference.Assemblies.NetStandard20.References.All - .Concat(new[] { canonical }), + new[] + { + CSharpSyntaxTree.ParseText("// no marker") + }, + NetStandard20.References.All + .Concat(new[] + { + canonical + }), new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var driver = CSharpGeneratorDriver.Create(new TransitionShimGenerator()); @@ -134,15 +151,18 @@ public class Whatever { public Whatever() {} } // The post-init attribute definition is the only output expected. result.GeneratedTrees - .Where(t => !t.FilePath.EndsWith("ShimAllPublicTypesUnderAttribute.g.cs", System.StringComparison.Ordinal)) + .Where(t => !t.FilePath.EndsWith("ShimAllPublicTypesUnderAttribute.g.cs", StringComparison.Ordinal)) .Should().BeEmpty(); } private static MetadataReference CompileCanonicalAssembly(string source) { var compilation = CSharpCompilation.Create("Fallout.TestCanonical", - new[] { CSharpSyntaxTree.ParseText(source) }, - Basic.Reference.Assemblies.NetStandard20.References.All, + new[] + { + CSharpSyntaxTree.ParseText(source) + }, + NetStandard20.References.All, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var stream = new MemoryStream(); @@ -150,6 +170,7 @@ private static MetadataReference CompileCanonicalAssembly(string source) emit.Success.Should().BeTrue( because: "canonical test compilation should compile: {0}", string.Join("; ", emit.Diagnostics.Select(d => d.GetMessage()))); + stream.Position = 0; return MetadataReference.CreateFromStream(stream); } diff --git a/tests/Fallout.Tooling.Specs/ArgumentStringHandlerSpecs.cs b/tests/Fallout.Tooling.Specs/ArgumentStringHandlerSpecs.cs index 2970512e2..26d394af5 100644 --- a/tests/Fallout.Tooling.Specs/ArgumentStringHandlerSpecs.cs +++ b/tests/Fallout.Tooling.Specs/ArgumentStringHandlerSpecs.cs @@ -1,6 +1,6 @@ -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Tooling; +using FluentAssertions; using Xunit; // ReSharper disable StringLiteralAsInterpolationArgument @@ -49,10 +49,11 @@ public void TestAbsolutePath(string path, string expected) public void TestAbsolutePathCollection() { var paths = new AbsolutePath[] - { - "C:\\foo\\bar", - "/foo bar/foo" - }; + { + "C:\\foo\\bar", + "/foo bar/foo" + }; + ArgsToString($"start {paths} end").Should().Be("start C:\\foo\\bar \"/foo bar/foo\" end"); ArgsToString($"start {paths:sn} end").Should().Be("start C:\\foo\\bar '/foo bar/foo' end"); } diff --git a/tests/Fallout.Tooling.Specs/NuGetPackageResolverSpecs.cs b/tests/Fallout.Tooling.Specs/NuGetPackageResolverSpecs.cs index 1b25f001e..2f80c89e1 100644 --- a/tests/Fallout.Tooling.Specs/NuGetPackageResolverSpecs.cs +++ b/tests/Fallout.Tooling.Specs/NuGetPackageResolverSpecs.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Tooling; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -11,6 +11,7 @@ public class NuGetPackageResolverSpecs private static AbsolutePath RootDirectory => Constants.TryGetRootDirectoryFrom(EnvironmentInfo.WorkingDirectory).NotNull(); private static AbsolutePath ProjectFile => RootDirectory / "tests" / "Fallout.Tooling.Specs" / "Fallout.Tooling.Specs.csproj"; + private static AbsolutePath AssetsFile => ProjectFile.Parent / "obj" / "project.assets.json"; // This version have to match with @@ -23,7 +24,8 @@ public class NuGetPackageResolverSpecs [InlineData("Nuke.Generation.Specifications", false, false, null)] [InlineData("Nuke.Generation.Specifications", true, false, "0.1.0-alpha0019")] [InlineData("PathConstruction", false, false, "0.1.0")] - public async Task TestGetLatestPackageVersion(string packageId, bool includePrereleases, bool includeUnlisted, string expected) + public async Task TestGetLatestPackageVersion(string packageId, bool includePrereleases, bool includeUnlisted, + string expected) { var result = await NuGetVersionResolver.GetLatestVersion(packageId, includePrereleases, includeUnlisted); result.Should().Be(expected); @@ -32,7 +34,9 @@ public async Task TestGetLatestPackageVersion(string packageId, bool includePrer [Fact] public void TestGetGlobalInstalledPackage() { - var result = NuGetPackageResolver.GetGlobalInstalledPackage("xunit.runner.console", version: null, packagesConfigFile: null); + var result = NuGetPackageResolver.GetGlobalInstalledPackage("xunit.runner.console", version: null, + packagesConfigFile: null); + result.Should().NotBeNull(); result.Id.Should().Be("xunit.runner.console"); result.File.Name.Should().EndWith("nupkg"); @@ -42,7 +46,9 @@ public void TestGetGlobalInstalledPackage() [Fact] public void TestGetLocalInstalledPackageViaProjectFile() { - var result = NuGetPackageResolver.GetLocalInstalledPackage("xunit.runner.console", ProjectFile, resolveDependencies: false); + var result = NuGetPackageResolver.GetLocalInstalledPackage("xunit.runner.console", ProjectFile, + resolveDependencies: false); + result.Should().NotBeNull(); result.Version.OriginalVersion.Should().Be(XunitConsolePackageVersion); } @@ -50,7 +56,9 @@ public void TestGetLocalInstalledPackageViaProjectFile() [Fact] public void TestGetLocalInstalledPackageViaAssetsFile() { - var result = NuGetPackageResolver.GetLocalInstalledPackage("xunit.runner.console", AssetsFile, resolveDependencies: false); + var result = NuGetPackageResolver.GetLocalInstalledPackage("xunit.runner.console", AssetsFile, + resolveDependencies: false); + result.Version.OriginalVersion.Should().Be(XunitConsolePackageVersion); } diff --git a/tests/Fallout.Tooling.Specs/OptionsSpecs.cs b/tests/Fallout.Tooling.Specs/OptionsSpecs.cs index 4a58fa56f..e29e8e9e6 100644 --- a/tests/Fallout.Tooling.Specs/OptionsSpecs.cs +++ b/tests/Fallout.Tooling.Specs/OptionsSpecs.cs @@ -1,14 +1,12 @@ -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; -using System.Threading.Tasks; using System.Text.Json; -using FluentAssertions; +using System.Threading.Tasks; using Fallout.Common.Tooling; -using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using VerifyXunit; using Xunit; @@ -18,9 +16,13 @@ public class OptionsSpecs { // ReSharper disable UnassignedGetOnlyAutoProperty private string ScalarValue { get; } + private List ListValue { get; } + private ReadOnlyDictionary DictionaryValue { get; } + private LookupTable LookupValue { get; } + private Options NestedValue { get; } // ReSharper restore UnassignedGetOnlyAutoProperty @@ -54,14 +56,28 @@ public void TestList() { new Options().Modify(x => { - x.Set(() => ListValue, new[] { 1, 2, 3 }); + x.Set(() => ListValue, new[] + { + 1, + 2, + 3 + }); + x.Get>(() => ListValue).Should().Equal(1, 2, 3); - x.AddCollection(() => ListValue, [4, 5]); - x.Get>(() => ListValue).Should().EndWith(new[] { 4, 5 }); + x.AddCollection(() => ListValue, 4, 5); + x.Get>(() => ListValue).Should().EndWith(new[] + { + 4, + 5 + }); - x.RemoveCollection(() => ListValue, [2, 4]); - x.Get>(() => ListValue).Should().NotContain(new[] { 2, 4 }); + x.RemoveCollection(() => ListValue, 2, 4); + x.Get>(() => ListValue).Should().NotContain(new[] + { + 2, + 4 + }); x.ClearCollection(() => ListValue); x.Get>(() => ListValue).Should().BeEmpty(); @@ -94,7 +110,11 @@ public void TestLookup() { x.SetLookup(() => LookupValue, "key", "value1", "value2"); x.Get>(() => LookupValue).Should() - .Contain(x => x.Key == "key" && x.SequenceEqual(new[] { "value1", "value2" })); + .Contain(x => x.Key == "key" && x.SequenceEqual(new[] + { + "value1", + "value2" + })); x.AddLookup(() => LookupValue, "key", "value3"); x.AddLookup(() => LookupValue, "key2", "value1"); @@ -120,9 +140,27 @@ public void TestNested() new Options().Modify(y => { y.Set(() => ScalarValue, "scalar-value"); - y.Set(() => ListValue, new[] { 1, 2, 3 }); - y.Set(() => DictionaryValue, new Dictionary { ["key"] = "value" }); - y.Set(() => LookupValue, new LookupTable { ["key"] = new[] { 1, 2, 3 } }); + y.Set(() => ListValue, new[] + { + 1, + 2, + 3 + }); + + y.Set(() => DictionaryValue, new Dictionary + { + ["key"] = "value" + }); + + y.Set(() => LookupValue, new LookupTable + { + ["key"] = new[] + { + 1, + 2, + 3 + } + }); })); IOptions nested = x.Get(() => NestedValue); @@ -138,9 +176,28 @@ public Task TestSerialization() { IOptions options = new Options(); options.Set(() => ScalarValue, "scalar-value"); - options.Set(() => ListValue, new[] { 1, 2, 3 }); - options.Set(() => DictionaryValue, new Dictionary { ["key"] = "value" }); - options.Set(() => LookupValue, new LookupTable { ["key"] = new[] { 1, 2, 3 } }); + options.Set(() => ListValue, new[] + { + 1, + 2, + 3 + }); + + options.Set(() => DictionaryValue, new Dictionary + { + ["key"] = "value" + }); + + options.Set(() => LookupValue, new LookupTable + { + ["key"] = new[] + { + 1, + 2, + 3 + } + }); + options.Set(() => NestedValue, options); return Verifier.Verify(JsonSerializer.Serialize(options, options.GetType(), Options.SerializerOptions)); @@ -151,8 +208,12 @@ public Task TestSerialization() public class CustomEnumeration : Enumeration { public static CustomEnumeration Quiet = "quiet"; + public static implicit operator CustomEnumeration(string value) { - return new CustomEnumeration { Value = value }; + return new CustomEnumeration + { + Value = value + }; } } diff --git a/tests/Fallout.Tooling.Specs/ToolOptionsArgumentsSpecs.cs b/tests/Fallout.Tooling.Specs/ToolOptionsArgumentsSpecs.cs index 1fede9a94..0ff218e94 100644 --- a/tests/Fallout.Tooling.Specs/ToolOptionsArgumentsSpecs.cs +++ b/tests/Fallout.Tooling.Specs/ToolOptionsArgumentsSpecs.cs @@ -4,67 +4,116 @@ using System.Linq; using System.Reflection; using System.Text.Json; -using System.Text.Json.Nodes; -using FluentAssertions; using Fallout.Common.Tooling; -using Fallout.Common.Utilities; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; public class ToolOptionsArgumentsSpecs { - [Fact] public void TestBool_Simple() => Assert(new { Bool = true }, ["/bool:true",]); - [Fact] public void TestBool_FlagTrue() => Assert(new { Flag = true }, ["/flag"]); - [Fact] public void TestBool_FlagFalse() => Assert(new { Flag = false }, []); + [Fact] + public void TestBool_Simple() => Assert(new + { + Bool = true + }, ["/bool:true",]); + + [Fact] + public void TestBool_FlagTrue() => Assert(new + { + Flag = true + }, ["/flag"]); + + [Fact] + public void TestBool_FlagFalse() => Assert(new + { + Flag = false + }, []); private class BoolToolOptions : ToolOptions { - [Argument(Format = "/bool:{value}")] public bool Bool => Get(() => Bool); - [Argument(Format = "/flag")] public bool? Flag => Get(() => Flag); + [Argument(Format = "/bool:{value}")] + public bool Bool => Get(() => Bool); + + [Argument(Format = "/flag")] + public bool? Flag => Get(() => Flag); } - [Fact] public void TestString_Simple() => Assert(new { String = "value" }, ["--string", "value"]); - [Fact] public void TestString_Quoted() => Assert(new { String = "white space" }, ["--string", "\"white space\""]); + [Fact] + public void TestString_Simple() => Assert(new + { + String = "value" + }, ["--string", "value"]); + + [Fact] + public void TestString_Quoted() => Assert(new + { + String = "white space" + }, ["--string", "\"white space\""]); [Fact] public void TestString_Secret() { - var options = SetInternalOptions(new { Secret = "secret-value" }); + var options = SetInternalOptions(new + { + Secret = "secret-value" + }); + options.GetSecrets().Should().Equal("secret-value"); } private class StringToolOptions : ToolOptions { - [Argument(Format = "--string {value}")] public string String => Get(() => String); - [Argument(Format = "--secret {value}", Secret = true)] public string Secret => Get(() => Secret); + [Argument(Format = "--string {value}")] + public string String => Get(() => String); + + [Argument(Format = "--secret {value}", Secret = true)] + public string Secret => Get(() => Secret); } - [Fact] public void TestImplicit() => Assert(new { String = "value" }, ["implicit argument", "--string", "value"]); + [Fact] + public void TestImplicit() => Assert(new + { + String = "value" + }, ["implicit argument", "--string", "value"]); [Command(Arguments = "implicit argument")] private class ImplicitToolOptions : ToolOptions { - [Argument(Format = "--string {value}")] public string String => Get(() => String); + [Argument(Format = "--string {value}")] + public string String => Get(() => String); } [Fact] public void TestOrder() { // ReSharper disable SimilarAnonymousTypeNearby - Assert(new { Flag1 = true, Flag2 = true, }, ["/flag1", "/flag2"]); - Assert(new { Flag2 = true, Flag1 = true, }, ["/flag2", "/flag1"]); + Assert(new + { + Flag1 = true, + Flag2 = true, + }, ["/flag1", "/flag2"]); + + Assert(new + { + Flag2 = true, + Flag1 = true, + }, ["/flag2", "/flag1"]); // ReSharper restore SimilarAnonymousTypeNearby } private class OrderToolOptions : ToolOptions { - [Argument(Format = "/flag1")] public bool Flag1 => Get(() => Flag1); - [Argument(Format = "/flag2")] public bool Flag2 => Get(() => Flag2); + [Argument(Format = "/flag1")] + public bool Flag1 => Get(() => Flag1); + + [Argument(Format = "/flag2")] + public bool Flag2 => Get(() => Flag2); } - [Fact] public void TestPosition() => Assert( + [Fact] + public void TestPosition() => Assert( new { SecondToLast = "second-last", @@ -77,16 +126,39 @@ [Fact] public void TestPosition() => Assert( private class PositionToolOptions : ToolOptions { - [Argument(Format = "{value}")] public string Middle => Get(() => Middle); - [Argument(Format = "{value}", Position = -1)] public string Last => Get(() => Last); - [Argument(Format = "{value}", Position = 1)] public string First => Get(() => First); - [Argument(Format = "{value}", Position = 2)] public string Second => Get(() => Second); - [Argument(Format = "{value}", Position = -2)] public string SecondToLast => Get(() => SecondToLast); + [Argument(Format = "{value}")] + public string Middle => Get(() => Middle); + + [Argument(Format = "{value}", Position = -1)] + public string Last => Get(() => Last); + + [Argument(Format = "{value}", Position = 1)] + public string First => Get(() => First); + + [Argument(Format = "{value}", Position = 2)] + public string Second => Get(() => Second); + + [Argument(Format = "{value}", Position = -2)] + public string SecondToLast => Get(() => SecondToLast); } - [Fact] public void TestFormatter_Method1() => Assert(new { Time = DateTime.UnixEpoch.AddHours(1).AddMinutes(15) }, ["01:15"]); - [Fact] public void TestFormatter_Method2() => Assert(new { Date = DateTime.UnixEpoch }, ["01/01/1970"]); - [Fact] public void TestFormatter_TypeMethod() => Assert(new { Minutes = TimeSpan.FromMinutes(10) }, ["10"]); + [Fact] + public void TestFormatter_Method1() => Assert(new + { + Time = DateTime.UnixEpoch.AddHours(1).AddMinutes(15) + }, ["01:15"]); + + [Fact] + public void TestFormatter_Method2() => Assert(new + { + Date = DateTime.UnixEpoch + }, ["01/01/1970"]); + + [Fact] + public void TestFormatter_TypeMethod() => Assert(new + { + Minutes = TimeSpan.FromMinutes(10) + }, ["10"]); private class FormatToolOptions : ToolOptions { @@ -105,60 +177,201 @@ private class FormatToolOptions : ToolOptions private static class Formatter { - public static string FormatMinutes(TimeSpan timespan, PropertyInfo _) => timespan.TotalMinutes.ToString(CultureInfo.InvariantCulture); + public static string FormatMinutes(TimeSpan timespan, PropertyInfo _) => + timespan.TotalMinutes.ToString(CultureInfo.InvariantCulture); } - [Fact] public void TestList_Simple() => Assert(new { SimpleList = new[] { "a", "b" } }, ["--param", "a", "--param", "b"]); - [Fact] public void TestList_Quoted() => Assert(new { SimpleList = new[] { "white space" } }, ["--param", "\"white space\""]); - [Fact] public void TestList_Separator() => Assert(new { SeparatorList = new[] { "a", "b" } }, ["--param", "a+b"]); - [Fact] public void TestList_Whitespace() => Assert(new { WhitespaceList = new[] { "a", "b" } }, ["--param", "a", "b"]); - [Fact] public void TestList_QuoteMultiple() => Assert(new { QuotedList = new[] { "a", "b" } }, ["--param:\"a b\""]); - [Fact] public void TestList_QuoteMultiple_WhitespaceValue() => Assert(new { QuotedList = new[] { "a", "white space" } }, ["--param:\"a \\\"white space\\\"\""]); - [Fact] public void TestList_Formatted() => Assert(new { FormattedList = new[] { "true", "false" } }, ["--param=TRUE", "--param=FALSE"]); + [Fact] + public void TestList_Simple() => Assert(new + { + SimpleList = new[] + { + "a", + "b" + } + }, ["--param", "a", "--param", "b"]); + + [Fact] + public void TestList_Quoted() => Assert(new + { + SimpleList = new[] + { + "white space" + } + }, ["--param", "\"white space\""]); + + [Fact] + public void TestList_Separator() => Assert(new + { + SeparatorList = new[] + { + "a", + "b" + } + }, ["--param", "a+b"]); + + [Fact] + public void TestList_Whitespace() => Assert(new + { + WhitespaceList = new[] + { + "a", + "b" + } + }, ["--param", "a", "b"]); + + [Fact] + public void TestList_QuoteMultiple() => Assert(new + { + QuotedList = new[] + { + "a", + "b" + } + }, ["--param:\"a b\""]); + + [Fact] + public void TestList_QuoteMultiple_WhitespaceValue() => Assert(new + { + QuotedList = new[] + { + "a", + "white space" + } + }, ["--param:\"a \\\"white space\\\"\""]); + + [Fact] + public void TestList_Formatted() => Assert(new + { + FormattedList = new[] + { + "true", + "false" + } + }, ["--param=TRUE", "--param=FALSE"]); private class ListToolOptions : ToolOptions { - [Argument(Format = "--param {value}")] public IReadOnlyList SimpleList => Get>(() => SimpleList); - [Argument(Format = "--param {value}", Separator = "+")] public IReadOnlyList SeparatorList => Get>(() => SeparatorList); - [Argument(Format = "--param {value}", Separator = " ")] public IReadOnlyList WhitespaceList => Get>(() => WhitespaceList); - [Argument(Format = "--param:{value}", Separator = " ", QuoteMultiple = true)] public IReadOnlyList QuotedList => Get>(() => QuotedList); - [Argument(Format = "--param={value}", FormatterMethod = nameof(Format))] public IReadOnlyList FormattedList => Get>(() => FormattedList); + [Argument(Format = "--param {value}")] + public IReadOnlyList SimpleList => Get>(() => SimpleList); + + [Argument(Format = "--param {value}", Separator = "+")] + public IReadOnlyList SeparatorList => Get>(() => SeparatorList); + + [Argument(Format = "--param {value}", Separator = " ")] + public IReadOnlyList WhitespaceList => Get>(() => WhitespaceList); + + [Argument(Format = "--param:{value}", Separator = " ", QuoteMultiple = true)] + public IReadOnlyList QuotedList => Get>(() => QuotedList); + + [Argument(Format = "--param={value}", FormatterMethod = nameof(Format))] + public IReadOnlyList FormattedList => Get>(() => FormattedList); private string Format(bool value) => value.ToString().ToUpperInvariant(); } - private readonly Dictionary _simpleDictionary = new() { ["key1"] = 1, ["key2"] = "foobar" }; + private readonly Dictionary _simpleDictionary = new() + { + ["key1"] = 1, + ["key2"] = "foobar" + }; - [Fact] public void TestDictionary_Simple1() => Assert(new { SimpleDictionary = _simpleDictionary }, ["-p", "key1=1", "-p", "key2=foobar"]); - [Fact] public void TestDictionary_Simple2() => Assert(new { Simple2Dictionary = _simpleDictionary }, ["-p", "key1", "1", "-p", "key2", "foobar"]); - [Fact] public void TestDictionary_Separator() => Assert(new { SeparatorDictionary = _simpleDictionary }, ["/p:key1=1;key2=foobar"]); - [Fact] public void TestDictionary_Whitespace() => Assert(new { WhitespaceDictionary = _simpleDictionary }, ["--", "key1=1", "key2=foobar"]); - [Fact] public void TestDictionary_Formatted() => Assert(new { FormattedDictionary = _simpleDictionary }, ["/p:key1=1", "/p:key2=FOOBAR"]); + [Fact] + public void TestDictionary_Simple1() => Assert(new + { + SimpleDictionary = _simpleDictionary + }, ["-p", "key1=1", "-p", "key2=foobar"]); + + [Fact] + public void TestDictionary_Simple2() => Assert(new + { + Simple2Dictionary = _simpleDictionary + }, ["-p", "key1", "1", "-p", "key2", "foobar"]); + + [Fact] + public void TestDictionary_Separator() => Assert(new + { + SeparatorDictionary = _simpleDictionary + }, ["/p:key1=1;key2=foobar"]); + + [Fact] + public void TestDictionary_Whitespace() => Assert(new + { + WhitespaceDictionary = _simpleDictionary + }, ["--", "key1=1", "key2=foobar"]); + + [Fact] + public void TestDictionary_Formatted() => Assert(new + { + FormattedDictionary = _simpleDictionary + }, ["/p:key1=1", "/p:key2=FOOBAR"]); private class DictionaryToolOptions : ToolOptions { - [Argument(Format = "-p {key}={value}")] public IReadOnlyDictionary SimpleDictionary => Get>(() => SimpleDictionary); - [Argument(Format = "-p {key} {value}")] public IReadOnlyDictionary Simple2Dictionary => Get>(() => Simple2Dictionary); - [Argument(Format = "/p:{key}={value}", Separator = ";")] public IReadOnlyDictionary SeparatorDictionary => Get>(() => SeparatorDictionary); - [Argument(Format = "-- {key}={value}", Separator = " ")] public IReadOnlyDictionary WhitespaceDictionary => Get>(() => WhitespaceDictionary); - [Argument(Format = "/p:{key}={value}", FormatterMethod = nameof(Format))] public IReadOnlyDictionary FormattedDictionary => Get>(() => FormattedDictionary); + [Argument(Format = "-p {key}={value}")] + public IReadOnlyDictionary SimpleDictionary => Get>(() => SimpleDictionary); + + [Argument(Format = "-p {key} {value}")] + public IReadOnlyDictionary Simple2Dictionary => Get>(() => Simple2Dictionary); + + [Argument(Format = "/p:{key}={value}", Separator = ";")] + public IReadOnlyDictionary SeparatorDictionary => + Get>(() => SeparatorDictionary); + + [Argument(Format = "-- {key}={value}", Separator = " ")] + public IReadOnlyDictionary WhitespaceDictionary => + Get>(() => WhitespaceDictionary); + + [Argument(Format = "/p:{key}={value}", FormatterMethod = nameof(Format))] + public IReadOnlyDictionary FormattedDictionary => + Get>(() => FormattedDictionary); private string Format(object value) => value?.ToString()?.ToUpperInvariant(); } - private readonly LookupTable _simpleLookupTable = new() { ["key1"] = [1, 2], ["key2"] = [true, false] }; + private readonly LookupTable _simpleLookupTable = new() + { + ["key1"] = [1, 2], + ["key2"] = [true, false] + }; - [Fact] public void TestLookup_Simple() => Assert(new { SimpleLookup = _simpleLookupTable }, ["--param", "key1=1","--param", "key1=2", "--param", "key2=true", "--param", "key2=false"]); - [Fact] public void TestLookup_InnerSeparator() => Assert(new { InnerSeparatorLookup = _simpleLookupTable }, ["--param:key1=1,2", "--param:key2=true,false"]); - [Fact] public void TestLookup_Separator() => Assert(new { SeparatorLookup = _simpleLookupTable }, ["--param:key1=1,2;key2=true,false"]); - [Fact] public void TestLookup_Formatted() => Assert(new { FormattedLookup = _simpleLookupTable }, ["--param", "key1", "1+2", "--param", "key2", "TRUE+FALSE"]); + [Fact] + public void TestLookup_Simple() => Assert(new + { + SimpleLookup = _simpleLookupTable + }, ["--param", "key1=1", "--param", "key1=2", "--param", "key2=true", "--param", "key2=false"]); + + [Fact] + public void TestLookup_InnerSeparator() => Assert(new + { + InnerSeparatorLookup = _simpleLookupTable + }, ["--param:key1=1,2", "--param:key2=true,false"]); + + [Fact] + public void TestLookup_Separator() => Assert(new + { + SeparatorLookup = _simpleLookupTable + }, ["--param:key1=1,2;key2=true,false"]); + + [Fact] + public void TestLookup_Formatted() => Assert(new + { + FormattedLookup = _simpleLookupTable + }, ["--param", "key1", "1+2", "--param", "key2", "TRUE+FALSE"]); private class LookupToolOptions : ToolOptions { - [Argument(Format = "--param {key}={value}")] public ILookup SimpleLookup => Get>(() => SimpleLookup); - [Argument(Format = "--param:{key}={value}", InnerSeparator = ",")] public ILookup InnerSeparatorLookup => Get>(() => InnerSeparatorLookup); - [Argument(Format = "--param:{key}={value}", Separator = ";", InnerSeparator = ",")] public ILookup SeparatorLookup => Get>(() => SeparatorLookup); - [Argument(Format = "--param {key} {value}", InnerSeparator = "+", FormatterMethod = nameof(Format))] public ILookup FormattedLookup => Get>(() => FormattedLookup); + [Argument(Format = "--param {key}={value}")] + public ILookup SimpleLookup => Get>(() => SimpleLookup); + + [Argument(Format = "--param:{key}={value}", InnerSeparator = ",")] + public ILookup InnerSeparatorLookup => Get>(() => InnerSeparatorLookup); + + [Argument(Format = "--param:{key}={value}", Separator = ";", InnerSeparator = ",")] + public ILookup SeparatorLookup => Get>(() => SeparatorLookup); + + [Argument(Format = "--param {key} {value}", InnerSeparator = "+", FormatterMethod = nameof(Format))] + public ILookup FormattedLookup => Get>(() => FormattedLookup); private string Format(object value) => value?.ToString()?.ToUpperInvariant(); } @@ -168,13 +381,18 @@ public void TestAdditionalArguments() => Assert( new { String = "value", - ProcessAdditionalArguments = new[] { "first", "second" } + ProcessAdditionalArguments = new[] + { + "first", + "second" + } }, arguments: ["--string", "value", "first", "second"]); private class AdditionalArgumentsToolOptions : ToolOptions { - [Argument(Format = "--string {value}")] public string String => Get(() => String); + [Argument(Format = "--string {value}")] + public string String => Get(() => String); } private void Assert(object obj, IEnumerable arguments) diff --git a/tests/Fallout.Tooling.Specs/ToolPathResolverResetSpecs.cs b/tests/Fallout.Tooling.Specs/ToolPathResolverResetSpecs.cs index 57813aae0..5dfaa14fa 100644 --- a/tests/Fallout.Tooling.Specs/ToolPathResolverResetSpecs.cs +++ b/tests/Fallout.Tooling.Specs/ToolPathResolverResetSpecs.cs @@ -1,7 +1,7 @@ using System; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Tooling; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; diff --git a/tests/Fallout.Tooling.Specs/ToolTasksLoggerSpecs.cs b/tests/Fallout.Tooling.Specs/ToolTasksLoggerSpecs.cs index 38f930a05..0f68dc7d2 100644 --- a/tests/Fallout.Tooling.Specs/ToolTasksLoggerSpecs.cs +++ b/tests/Fallout.Tooling.Specs/ToolTasksLoggerSpecs.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using FluentAssertions; using Fallout.Common.Tooling; +using FluentAssertions; using Serilog; using Serilog.Core; using Serilog.Events; @@ -37,8 +36,22 @@ public void TestSimpleLogging() _logEvents.Should().BeEquivalentTo( [ - new { Level = LogEventLevel.Debug, MessageTemplate = new { Text = "debug" } }, - new { Level = LogEventLevel.Error, MessageTemplate = new { Text = "warning: some text" } }, + new + { + Level = LogEventLevel.Debug, + MessageTemplate = new + { + Text = "debug" + } + }, + new + { + Level = LogEventLevel.Error, + MessageTemplate = new + { + Text = "warning: some text" + } + }, ]); } @@ -58,10 +71,38 @@ public void TestRegexLogging() _logEvents.Should().BeEquivalentTo( [ - new { Level = LogEventLevel.Debug, MessageTemplate = new { Text = "debug" } }, - new { Level = LogEventLevel.Warning, MessageTemplate = new { Text = "warning: some text" } }, - new { Level = LogEventLevel.Error, MessageTemplate = new { Text = "SomeFile.cs error: more info" } }, - new { Level = LogEventLevel.Error, MessageTemplate = new { Text = "AnotherFile.cs error: more info" } }, + new + { + Level = LogEventLevel.Debug, + MessageTemplate = new + { + Text = "debug" + } + }, + new + { + Level = LogEventLevel.Warning, + MessageTemplate = new + { + Text = "warning: some text" + } + }, + new + { + Level = LogEventLevel.Error, + MessageTemplate = new + { + Text = "SomeFile.cs error: more info" + } + }, + new + { + Level = LogEventLevel.Error, + MessageTemplate = new + { + Text = "AnotherFile.cs error: more info" + } + }, ]); } @@ -79,8 +120,22 @@ public void TestErrorAsStandardLogging() _logEvents.Should().BeEquivalentTo( [ - new { Level = LogEventLevel.Debug, MessageTemplate = new { Text = "debug" } }, - new { Level = LogEventLevel.Warning, MessageTemplate = new { Text = "warning: some text" } }, + new + { + Level = LogEventLevel.Debug, + MessageTemplate = new + { + Text = "debug" + } + }, + new + { + Level = LogEventLevel.Warning, + MessageTemplate = new + { + Text = "warning: some text" + } + }, ]); } @@ -97,7 +152,14 @@ public void TestDefaultLevelLogging() _logEvents.Should().BeEquivalentTo( [ - new { Level = LogEventLevel.Debug, MessageTemplate = new { Text = "debug" } }, + new + { + Level = LogEventLevel.Debug, + MessageTemplate = new + { + Text = "debug" + } + }, ]); } } diff --git a/tests/Fallout.Tooling.Specs/ToolTasksToolPathSpecs.cs b/tests/Fallout.Tooling.Specs/ToolTasksToolPathSpecs.cs index 6b12fb072..b1e651888 100644 --- a/tests/Fallout.Tooling.Specs/ToolTasksToolPathSpecs.cs +++ b/tests/Fallout.Tooling.Specs/ToolTasksToolPathSpecs.cs @@ -1,8 +1,5 @@ -using System; -using System.ComponentModel; -using System.Linq; -using FluentAssertions; using Fallout.Common.Tooling; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -42,7 +39,6 @@ public void TestFromOptions_Framework() .Should().Contain("netcoreapp2.0").And.EndWith("xunit.console.dll"); } - [Fact] public void TestFromOverride() { diff --git a/tests/Fallout.Utilities.Specs/ArgumentParserSpecs.cs b/tests/Fallout.Utilities.Specs/ArgumentParserSpecs.cs index 6ff2ca7f8..73e6de826 100644 --- a/tests/Fallout.Utilities.Specs/ArgumentParserSpecs.cs +++ b/tests/Fallout.Utilities.Specs/ArgumentParserSpecs.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using FluentAssertions; using Xunit; @@ -8,16 +7,58 @@ namespace Fallout.Common.Specs; public class ArgumentParserSpecs { [Theory] - [InlineData("arg0 arg1 arg2", new[] { "arg0", "arg1", "arg2" })] - [InlineData("\"arg0 arg1\" arg2", new[] { "arg0 arg1", "arg2" })] - [InlineData("'arg0 arg1' arg2", new[] { "arg0 arg1", "arg2" })] - [InlineData("'arg0 \"arg1' arg2", new[] { "arg0 \"arg1", "arg2" })] - [InlineData("'arg0 \"arg1\"' arg2", new[] { "arg0 \"arg1\"", "arg2" })] - [InlineData("\"arg0 'arg1\" arg2", new[] { "arg0 'arg1", "arg2" })] - [InlineData("\"arg0 'arg1'\" arg2", new[] { "arg0 'arg1'", "arg2" })] - [InlineData("\"arg0 \\\"arg1\\\"\" arg2", new[] { "arg0 \"arg1\"", "arg2" })] - [InlineData("'arg0 \\'arg1\\'' arg2", new[] { "arg0 'arg1'", "arg2" })] - [InlineData("\\\\ \\ \\\\", new[] { "\\\\", "\\", "\\\\" })] + [InlineData("arg0 arg1 arg2", new[] + { + "arg0", + "arg1", + "arg2" + })] + [InlineData("\"arg0 arg1\" arg2", new[] + { + "arg0 arg1", + "arg2" + })] + [InlineData("'arg0 arg1' arg2", new[] + { + "arg0 arg1", + "arg2" + })] + [InlineData("'arg0 \"arg1' arg2", new[] + { + "arg0 \"arg1", + "arg2" + })] + [InlineData("'arg0 \"arg1\"' arg2", new[] + { + "arg0 \"arg1\"", + "arg2" + })] + [InlineData("\"arg0 'arg1\" arg2", new[] + { + "arg0 'arg1", + "arg2" + })] + [InlineData("\"arg0 'arg1'\" arg2", new[] + { + "arg0 'arg1'", + "arg2" + })] + [InlineData("\"arg0 \\\"arg1\\\"\" arg2", new[] + { + "arg0 \"arg1\"", + "arg2" + })] + [InlineData("'arg0 \\'arg1\\'' arg2", new[] + { + "arg0 'arg1'", + "arg2" + })] + [InlineData("\\\\ \\ \\\\", new[] + { + "\\\\", + "\\", + "\\\\" + })] public void TestParse(string commandLine, string[] expected) { var parser = new ArgumentParser(commandLine); @@ -28,7 +69,11 @@ public void TestParse(string commandLine, string[] expected) [Fact] public void TestNamed() { - var parser = new ArgumentParser(new[] { "--string", "foobar" }); + var parser = new ArgumentParser(new[] + { + "--string", + "foobar" + }); parser.HasArgument("string").Should().BeTrue(); @@ -45,12 +90,15 @@ public void TestBoolean() parser.GetNamedArgument("switch", typeof(bool)) .Should().BeOfType().Which .Should().BeTrue(); + parser.GetNamedArgument("true-bool", typeof(bool)) .Should().BeOfType().Which .Should().BeTrue(); + parser.GetNamedArgument("false-bool", typeof(bool)) .Should().BeOfType().Which .Should().BeFalse(); + parser.GetNamedArgument("non-existent-bool", typeof(bool?)) .Should().BeNull(); } @@ -63,21 +111,47 @@ public void TestCollections() parser.GetNamedArgument("empty-collection", typeof(string[])) .Should().BeOfType().Which .Should().BeEmpty(); + parser.GetNamedArgument("non-existent-collection", typeof(string[])) .Should().BeNull(); parser.GetNamedArgument("space-collection", typeof(int[]), separator: ' ') .Should().BeOfType().Which .Should().HaveCount(2); + parser.GetNamedArgument("plus-collection", typeof(int[]), separator: '+') .Should().BeOfType().Which .Should().HaveCount(2); } [Theory] - [InlineData(new[] { "1", "2", "3" }, new[] { 1, 2, 3 })] - [InlineData(new[] { "1", "2", "--named", "3" }, new[] { 1, 2 })] - [InlineData(new[] { "--named", "3" }, null)] + [InlineData(new[] + { + "1", + "2", + "3" + }, new[] + { + 1, + 2, + 3 + })] + [InlineData(new[] + { + "1", + "2", + "--named", + "3" + }, new[] + { + 1, + 2 + })] + [InlineData(new[] + { + "--named", + "3" + }, null)] public void TestAllPositional(string[] arguments, int[] expected) { var parser = new ArgumentParser(arguments); @@ -85,16 +159,54 @@ public void TestAllPositional(string[] arguments, int[] expected) } [Theory] - [InlineData(new[] { "arg1" }, 0, typeof(string), "arg1")] - [InlineData(new[] { "true" }, 0, typeof(bool), true)] - [InlineData(new[] { "arg1" }, 1, typeof(string), null)] - [InlineData(new[] { "arg1", "arg2" }, 1, typeof(string), "arg2")] - [InlineData(new[] { "posArg", "--named", "value" }, 0, typeof(string), "posArg")] - [InlineData(new[] { "posArg", "--named", "value" }, 1, typeof(string), null)] - [InlineData(new[] { "posArg", "--named", "value" }, 2, typeof(string), null)] + [InlineData(new[] + { + "arg1" + }, 0, typeof(string), "arg1")] + [InlineData(new[] + { + "true" + }, 0, typeof(bool), true)] + [InlineData(new[] + { + "arg1" + }, 1, typeof(string), null)] + [InlineData(new[] + { + "arg1", + "arg2" + }, 1, typeof(string), "arg2")] + [InlineData(new[] + { + "posArg", + "--named", + "value" + }, 0, typeof(string), "posArg")] + [InlineData(new[] + { + "posArg", + "--named", + "value" + }, 1, typeof(string), null)] + [InlineData(new[] + { + "posArg", + "--named", + "value" + }, 2, typeof(string), null)] // From end - [InlineData(new[] { "arg1", "arg2", "arg3" }, -1, typeof(string), "arg3")] - [InlineData(new[] { "arg1", "arg2", "arg3" }, -2, typeof(string), "arg2")] + [InlineData(new[] + { + "arg1", + "arg2", + "arg3" + }, -1, typeof(string), "arg3")] + [InlineData(new[] + { + "arg1", + "arg2", + "arg3" + }, -2, typeof(string), "arg2")] public void TestPositional(string[] arguments, int position, Type destinationType, object expected) { var parser = new ArgumentParser(arguments); diff --git a/tests/Fallout.Utilities.Specs/Collections/DictionaryExtensionsSpecs.cs b/tests/Fallout.Utilities.Specs/Collections/DictionaryExtensionsSpecs.cs index 51fb43d77..0970f6692 100644 --- a/tests/Fallout.Utilities.Specs/Collections/DictionaryExtensionsSpecs.cs +++ b/tests/Fallout.Utilities.Specs/Collections/DictionaryExtensionsSpecs.cs @@ -1,9 +1,7 @@ -using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using FluentAssertions; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -13,7 +11,12 @@ public class DictionaryExtensionsSpecs [Fact] public static void ToGeneric() { - var sourceDictionary = new Dictionary { { "key", "value" }, { "key2", "value2" } }; + var sourceDictionary = new Dictionary + { + { "key", "value" }, + { "key2", "value2" } + }; + IDictionary dict = sourceDictionary; dict.ToGeneric().Should().Equal(sourceDictionary); } diff --git a/tests/Fallout.Utilities.Specs/Collections/EnumerableExtensionsSpecs.cs b/tests/Fallout.Utilities.Specs/Collections/EnumerableExtensionsSpecs.cs index 63239b28b..ded203a7b 100644 --- a/tests/Fallout.Utilities.Specs/Collections/EnumerableExtensionsSpecs.cs +++ b/tests/Fallout.Utilities.Specs/Collections/EnumerableExtensionsSpecs.cs @@ -1,7 +1,6 @@ using System; -using System.Linq; -using FluentAssertions; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -11,14 +10,29 @@ public class EnumerableExtensionsSpecs [Fact] public void SingleOrDefaultOrError_ThrowsExceptionWithMessage() { - var x = new[] { "a", "a" }; + var x = new[] + { + "a", + "a" + }; + Action a = () => x.SingleOrDefaultOrError("error"); a.Should().Throw().WithMessage("error"); } [Theory] - [InlineData(new[] { "a", "b", "c" }, "a", "a")] - [InlineData(new[] { "a", "b", "c" }, "d", null)] + [InlineData(new[] + { + "a", + "b", + "c" + }, "a", "a")] + [InlineData(new[] + { + "a", + "b", + "c" + }, "d", null)] public void SingleOrDefaultOrError(string[] enumerable, string equalsWith, string expectedValue) { enumerable.SingleOrDefaultOrError(x => x.Equals(equalsWith), "error").Should().Be(expectedValue); diff --git a/tests/Fallout.Utilities.Specs/Collections/LookupTableSpecs.cs b/tests/Fallout.Utilities.Specs/Collections/LookupTableSpecs.cs index e7d787606..b23c6650b 100644 --- a/tests/Fallout.Utilities.Specs/Collections/LookupTableSpecs.cs +++ b/tests/Fallout.Utilities.Specs/Collections/LookupTableSpecs.cs @@ -1,7 +1,6 @@ using System; -using System.Linq; -using FluentAssertions; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; // ReSharper disable ArgumentsStyleLiteral @@ -24,11 +23,20 @@ public void Test() lookupTable.Add("second", value: 5); lookupTable.Should().HaveCount(2); lookupTable["first"].Should().HaveCount(3); - lookupTable["first"].Should().BeEquivalentTo(new[] { 2, 3, 4 }); + lookupTable["first"].Should().BeEquivalentTo(new[] + { + 2, + 3, + 4 + }); lookupTable.Remove("first", value: 3); lookupTable["first"].Should().HaveCount(2); - lookupTable["first"].Should().BeEquivalentTo(new[] { 2, 4 }); + lookupTable["first"].Should().BeEquivalentTo(new[] + { + 2, + 4 + }); lookupTable.Remove("first"); lookupTable["first"].Should().BeEmpty(); diff --git a/tests/Fallout.Utilities.Specs/EnvironmentInfoSpecs.cs b/tests/Fallout.Utilities.Specs/EnvironmentInfoSpecs.cs index d4cb5d04e..0969ebe4f 100644 --- a/tests/Fallout.Utilities.Specs/EnvironmentInfoSpecs.cs +++ b/tests/Fallout.Utilities.Specs/EnvironmentInfoSpecs.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using FluentAssertions; using Fallout.Common.IO; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; diff --git a/tests/Fallout.Utilities.Specs/IO/CompressionTasksSpecs.cs b/tests/Fallout.Utilities.Specs/IO/CompressionTasksSpecs.cs index 7e8fd9271..068967d3a 100644 --- a/tests/Fallout.Utilities.Specs/IO/CompressionTasksSpecs.cs +++ b/tests/Fallout.Utilities.Specs/IO/CompressionTasksSpecs.cs @@ -1,8 +1,6 @@ -using System; using System.IO; -using System.Linq; -using FluentAssertions; using Fallout.Common.IO; +using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -11,6 +9,7 @@ namespace Fallout.Common.Specs; public class CompressionTasksSpecs : FileSystemDependentSpecs { private AbsolutePath RootFile => TestTempDirectory / "root-file"; + private AbsolutePath NestedFile => TestTempDirectory / "a" / "b" / "c" / "nested-file"; public CompressionTasksSpecs(ITestOutputHelper testOutputHelper) diff --git a/tests/Fallout.Utilities.Specs/IO/FileSystemDependentSpecs.cs b/tests/Fallout.Utilities.Specs/IO/FileSystemDependentSpecs.cs index 0de1336c0..9f6e24805 100644 --- a/tests/Fallout.Utilities.Specs/IO/FileSystemDependentSpecs.cs +++ b/tests/Fallout.Utilities.Specs/IO/FileSystemDependentSpecs.cs @@ -1,8 +1,5 @@ -using System; using System.IO; -using System.Linq; using System.Reflection; -using Fallout.Common; using Fallout.Common.IO; using Xunit.Abstractions; @@ -11,17 +8,22 @@ namespace Fallout.Common.Specs; public abstract class FileSystemDependentSpecs { public ITestOutputHelper TestOutputHelper { get; } + public string TestName { get; } + public AbsolutePath ExecutionDirectory { get; } + public AbsolutePath TestProjectDirectory { get; } + public AbsolutePath RootDirectory { get; } + public AbsolutePath TestTempDirectory { get; } protected FileSystemDependentSpecs(ITestOutputHelper testOutputHelper) { TestOutputHelper = testOutputHelper; - TestName = ((ITest) testOutputHelper.GetType() + TestName = ((ITest)testOutputHelper.GetType() .GetField("test", BindingFlags.NonPublic | BindingFlags.Instance).NotNull() .GetValue(testOutputHelper).NotNull()).TestCase.TestMethod.Method.Name; diff --git a/tests/Fallout.Utilities.Specs/IO/MoveCopySpecs.cs b/tests/Fallout.Utilities.Specs/IO/MoveCopySpecs.cs index 37729a381..f1d3682bc 100644 --- a/tests/Fallout.Utilities.Specs/IO/MoveCopySpecs.cs +++ b/tests/Fallout.Utilities.Specs/IO/MoveCopySpecs.cs @@ -1,8 +1,8 @@ using System; using System.Linq; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Utilities.Collections; +using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -35,6 +35,7 @@ public void TestCopyFile() source.WriteAllText("fizzbuzz"); source.Copy(target, policy: ExistsPolicy.FileOverwrite) .Should().Be(target); + target.ReadAllText().Should().Be("fizzbuzz"); } @@ -72,6 +73,7 @@ public void TestCopyDirectory() source / "sub" / "source3.txt", source / "sub" / "source4.txt", }; + sourceFiles.ForEach(x => x.WriteAllText("source")); var target = TestTempDirectory / "target"; @@ -87,6 +89,7 @@ public void TestCopyDirectory() new Action(() => source.Copy(target, ExistsPolicy.DirectoryFail)) .Should().Throw().WithMessage("Policy disallows merging directories"); + target.GetFiles(depth: int.MaxValue).Should().HaveCount(3); source.Copy(target, ExistsPolicy.MergeAndSkip); @@ -107,12 +110,13 @@ public void TestMoveDirectory() { var source = TestTempDirectory / "source"; var sourceFiles = new[] - { - source / "source1.txt", - source / "source2.txt", - source / "sub" / "source3.txt", - source / "sub" / "source4.txt", - }; + { + source / "source1.txt", + source / "source2.txt", + source / "sub" / "source3.txt", + source / "sub" / "source4.txt", + }; + sourceFiles.ForEach(x => x.WriteAllText("source")); var target = TestTempDirectory / "target"; @@ -126,6 +130,7 @@ public void TestMoveDirectory() source.Move(target, ExistsPolicy.MergeAndSkip, deleteRemainingFiles: true) .Should().Be(target); + source.DirectoryExists().Should().BeFalse(); } } diff --git a/tests/Fallout.Utilities.Specs/IO/PathConstructionSpecs.cs b/tests/Fallout.Utilities.Specs/IO/PathConstructionSpecs.cs index 0dac9702b..686a79ccf 100644 --- a/tests/Fallout.Utilities.Specs/IO/PathConstructionSpecs.cs +++ b/tests/Fallout.Utilities.Specs/IO/PathConstructionSpecs.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using System.Text.Json; -using FluentAssertions; using Fallout.Common.IO; +using FluentAssertions; using Xunit; using static Fallout.Common.IO.PathConstruction; @@ -19,8 +19,8 @@ public class PathConstructionSpecs [InlineData("/", null)] public void TestParent(string path, string expected) { - ((AbsolutePath) path).Parent.Should().Be((AbsolutePath) expected); - ((string) ((AbsolutePath) path).Parent).Should().Be(expected); + ((AbsolutePath)path).Parent.Should().Be((AbsolutePath)expected); + ((string)((AbsolutePath)path).Parent).Should().Be(expected); ((AbsolutePath)path / ..).Should().Be((AbsolutePath)expected); } @@ -29,7 +29,7 @@ public void TestParent(string path, string expected) [InlineData("/foo", "/FOO", false)] public void TestEquality(string path1, string path2, bool expected) { - ((AbsolutePath) path1).Equals((AbsolutePath) path2).Should().Be(expected); + ((AbsolutePath)path1).Equals((AbsolutePath)path2).Should().Be(expected); } [Theory] @@ -162,40 +162,130 @@ public void TestNormalizePath_Throws(string input, char? separator, string messa } [Theory] - [InlineData(new object[] { "foo", "bar" }, "foo\\bar", "foo/bar")] - [InlineData(new object[] { "./foo", "bar" }, "foo\\bar", "foo/bar")] - [InlineData(new object[] { "foo", "..", ".", "", null, "bar", "foo" }, "bar\\foo", "bar/foo")] - [InlineData(new object[] { "..", ".", "..", "foo" }, "..\\..\\foo", "../../foo")] + [InlineData(new object[] + { + "foo", + "bar" + }, "foo\\bar", "foo/bar")] + [InlineData(new object[] + { + "./foo", + "bar" + }, "foo\\bar", "foo/bar")] + [InlineData(new object[] + { + "foo", + "..", + ".", + "", + null, + "bar", + "foo" + }, "bar\\foo", "bar/foo")] + [InlineData(new object[] + { + "..", + ".", + "..", + "foo" + }, "..\\..\\foo", "../../foo")] public void RelativePath(object[] parts, string expectedWindows, string expectedUnix) { ParseRelativePath(parts).Should().Be(EnvironmentInfo.IsWin ? expectedWindows : expectedUnix); } [Theory] - [InlineData(new object[] { "/bin", "foo", "..", "bar" }, "/bin/bar")] - [InlineData(new object[] { "C:", "windows", "foo", "..", "bar" }, "C:\\windows\\bar")] - [InlineData(new object[] { "\\\\server", "foo", "..", "bar" }, "\\\\server\\bar")] + [InlineData(new object[] + { + "/bin", + "foo", + "..", + "bar" + }, "/bin/bar")] + [InlineData(new object[] + { + "C:", + "windows", + "foo", + "..", + "bar" + }, "C:\\windows\\bar")] + [InlineData(new object[] + { + "\\\\server", + "foo", + "..", + "bar" + }, "\\\\server\\bar")] public void RelativePath_AsAbsolute(object[] parts, string expected) { ParseRelativePath(parts).Should().Be(expected); } [Theory] - [InlineData(new object[] { "/bin", "foo", "..", "bar" }, "/bin/bar")] - [InlineData(new object[] { "C:", "windows", "foo", "..", "bar" }, "C:\\windows\\bar")] - [InlineData(new object[] { "C:" }, "C:\\")] - [InlineData(new object[] { "C:\\", "windows" }, "C:\\windows")] - [InlineData(new object[] { "\\\\server", "foo", "..", "bar" }, "\\\\server\\bar")] + [InlineData(new object[] + { + "/bin", + "foo", + "..", + "bar" + }, "/bin/bar")] + [InlineData(new object[] + { + "C:", + "windows", + "foo", + "..", + "bar" + }, "C:\\windows\\bar")] + [InlineData(new object[] + { + "C:" + }, "C:\\")] + [InlineData(new object[] + { + "C:\\", + "windows" + }, "C:\\windows")] + [InlineData(new object[] + { + "\\\\server", + "foo", + "..", + "bar" + }, "\\\\server\\bar")] public void AbsolutePath(object[] parts, string expected) { ParseAbsolutePath(parts).Should().Be(expected); } [Theory] - [InlineData(new object[] { "C:", "", "..", "bar" }, "Cannot normalize 'C:\\..' beyond path root")] - [InlineData(new object[] { "/", "", "..", "bar" }, "Cannot normalize '/..' beyond path root")] - [InlineData(new object[] { "\\\\server", "", "..", "bar" }, "Cannot normalize '\\\\server\\..' beyond path root")] - [InlineData(new object[] { "foo", "bar" }, "Path 'foo' must be rooted")] + [InlineData(new object[] + { + "C:", + "", + "..", + "bar" + }, "Cannot normalize 'C:\\..' beyond path root")] + [InlineData(new object[] + { + "/", + "", + "..", + "bar" + }, "Cannot normalize '/..' beyond path root")] + [InlineData(new object[] + { + "\\\\server", + "", + "..", + "bar" + }, "Cannot normalize '\\\\server\\..' beyond path root")] + [InlineData(new object[] + { + "foo", + "bar" + }, "Path 'foo' must be rooted")] public void AbsolutePath_Throws(object[] parts, string expected) { Action action = () => ParseAbsolutePath(parts); @@ -205,19 +295,19 @@ public void AbsolutePath_Throws(object[] parts, string expected) [Fact] public void RelativePath_Specific() { - ((string) ((UnixRelativePath) "foo" / "bar")).Should().Be("foo/bar"); - ((string) ((WinRelativePath) "foo" / "bar")).Should().Be("foo\\bar"); + ((string)((UnixRelativePath)"foo" / "bar")).Should().Be("foo/bar"); + ((string)((WinRelativePath)"foo" / "bar")).Should().Be("foo\\bar"); - ((string) (UnixRelativePath) "foo\\bar").Should().Be("foo/bar"); - ((string) (WinRelativePath) "foo/bar").Should().Be("foo\\bar"); + ((string)(UnixRelativePath)"foo\\bar").Should().Be("foo/bar"); + ((string)(WinRelativePath)"foo/bar").Should().Be("foo\\bar"); } [Fact] public void RelativePath_Parent() { - ((string) ((UnixRelativePath) "foo/bar/foo" / ..)).Should().Be("foo/bar"); - ((string) ((WinRelativePath) "foo/bar/foo" / ..)).Should().Be("foo\\bar"); - ((string) ((RelativePath) "foo/bar" / ..)).Should().Be("foo"); + ((string)((UnixRelativePath)"foo/bar/foo" / ..)).Should().Be("foo/bar"); + ((string)((WinRelativePath)"foo/bar/foo" / ..)).Should().Be("foo\\bar"); + ((string)((RelativePath)"foo/bar" / ..)).Should().Be("foo"); } [Fact] @@ -233,11 +323,11 @@ public void TestSerialization() private static string ParseRelativePath(object[] parts) { - return parts.Skip(count: 1).Aggregate((RelativePath) (string) parts[0], (rp, p) => rp / (string) p); + return parts.Skip(count: 1).Aggregate((RelativePath)(string)parts[0], (rp, p) => rp / (string)p); } private static string ParseAbsolutePath(object[] parts) { - return parts.Skip(count: 1).Aggregate((AbsolutePath) (string) parts[0], (rp, p) => rp / (string) p); + return parts.Skip(count: 1).Aggregate((AbsolutePath)(string)parts[0], (rp, p) => rp / (string)p); } } diff --git a/tests/Fallout.Utilities.Specs/Reflection/ReflectionUtilitySpecs.cs b/tests/Fallout.Utilities.Specs/Reflection/ReflectionUtilitySpecs.cs index b90e41e79..9cad515ae 100644 --- a/tests/Fallout.Utilities.Specs/Reflection/ReflectionUtilitySpecs.cs +++ b/tests/Fallout.Utilities.Specs/Reflection/ReflectionUtilitySpecs.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; -using FluentAssertions; using Fallout.Common.IO; using Fallout.Common.Utilities; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs.Execution; diff --git a/tests/Fallout.Utilities.Specs/Text/SerializationSpecs.cs b/tests/Fallout.Utilities.Specs/Text/SerializationSpecs.cs index cd1237559..6af005d51 100644 --- a/tests/Fallout.Utilities.Specs/Text/SerializationSpecs.cs +++ b/tests/Fallout.Utilities.Specs/Text/SerializationSpecs.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Fallout.Common.IO; using Fallout.Common.Utilities; using Fallout.Utilities.Text.Yaml; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -46,21 +42,23 @@ public void XmlSpec() private static Data CreateData(string name) { return new Data - { - String = name, - Number = 5, - Boolean = true, - Nested = new Data - { - Boolean = false - } - }; + { + String = name, + Number = 5, + Boolean = true, + Nested = new Data + { + Boolean = false + } + }; } public class Data { public string String { get; set; } + public int Number { get; set; } + public bool Boolean { get; set; } public Data Nested { get; set; } diff --git a/tests/Fallout.Utilities.Specs/Text/StringExtensionsSpecs.cs b/tests/Fallout.Utilities.Specs/Text/StringExtensionsSpecs.cs index f1aafac0b..f24b4562c 100644 --- a/tests/Fallout.Utilities.Specs/Text/StringExtensionsSpecs.cs +++ b/tests/Fallout.Utilities.Specs/Text/StringExtensionsSpecs.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using FluentAssertions; using Fallout.Common.Utilities; +using FluentAssertions; using Xunit; namespace Fallout.Common.Specs; @@ -19,7 +17,7 @@ public void TestDoubleQuoteIfNeeded(string input, string output) [Theory] [InlineData("plain", "'plain'")] [InlineData("with: colon", "'with: colon'")] - [InlineData("Bob's step", "'Bob''s step'")] // YAML escapes an embedded quote by doubling it + [InlineData("Bob's step", "'Bob''s step'")] // YAML escapes an embedded quote by doubling it [InlineData("two ' ' quotes", "'two '' '' quotes'")] public void TestSingleQuoteYaml(string input, string output) { diff --git a/tests/Nuke.Common.Shim.Specs/HostDetectionSpecs.cs b/tests/Nuke.Common.Shim.Specs/HostDetectionSpecs.cs index c475e5cb2..43d6f2ce4 100644 --- a/tests/Nuke.Common.Shim.Specs/HostDetectionSpecs.cs +++ b/tests/Nuke.Common.Shim.Specs/HostDetectionSpecs.cs @@ -17,19 +17,20 @@ public class HostDetectionSpecs public void FalloutBuildHost_Resolves_DespiteConventionLessShimHostSubclass() { // Force the Nuke.Common shim assembly to load so detection sees its types. - var shimAssembly = typeof(global::Nuke.Common.NukeBuild).Assembly; + var shimAssembly = typeof(NukeBuild).Assembly; // Precondition: the shim really does emit a public, convention-less Host subclass. // Without one, this regression test has no teeth. var offenders = shimAssembly.GetTypes() - .Where(t => t.IsPublic && t.IsSubclassOf(typeof(global::Fallout.Common.Host))) + .Where(t => t.IsPublic && t.IsSubclassOf(typeof(Fallout.Common.Host))) .Where(t => t.GetProperty($"IsRunning{t.Name}", BindingFlags.Public | BindingFlags.Static) is null) .ToList(); + offenders.Should().NotBeEmpty(); // Touching FalloutBuild runs its static ctor -> Host.Default, scanning every public // Host subclass. Resolving the property (not throwing) is the assertion. - var host = global::Fallout.Common.FalloutBuild.Host; + var host = Fallout.Common.FalloutBuild.Host; host.Should().NotBeNull(); } } diff --git a/tests/Nuke.Common.Shim.Specs/SampleConsumerBuild.cs b/tests/Nuke.Common.Shim.Specs/SampleConsumerBuild.cs index 12dd96537..1fdd78654 100644 --- a/tests/Nuke.Common.Shim.Specs/SampleConsumerBuild.cs +++ b/tests/Nuke.Common.Shim.Specs/SampleConsumerBuild.cs @@ -3,17 +3,16 @@ // typical NUKE-era consumer Build.cs imports. If this file compiles, the MVP shim covers // the surface it claims to cover. Nothing here is executed. -#pragma warning disable CS0649 // unassigned readonly — these are injected at runtime -#pragma warning disable CS0169 // unused fields — same reason +#pragma warning disable CS0649 // unassigned readonly — these are injected at runtime +#pragma warning disable CS0169 // unused fields — same reason -using Nuke.Common; +using Fallout.Common.Git; +using Fallout.Common.IO; +using Fallout.Solutions; using Nuke.Common.CI.AppVeyor; using Nuke.Common.CI.AzurePipelines; using Nuke.Common.CI.GitHubActions; using Nuke.Common.CI.TeamCity; -using Nuke.Common.Git; -using Nuke.Common.IO; -using Nuke.Common.ProjectModel; using Nuke.Common.Utilities; namespace Nuke.Common.Shim.Specs; @@ -22,21 +21,26 @@ namespace Nuke.Common.Shim.Specs; // uses the canonical Fallout types via `Fallout.Common.Target` (shim doesn't // bridge delegates; that's `fallout-migrate`'s job). The [GlobbingOptions(...)] // attribute resolves through the shim now that the canonical is un-sealed. -[GlobbingOptions(Fallout.Common.IO.GlobbingCaseSensitivity.CaseInsensitive)] +[IO.GlobbingOptions(GlobbingCaseSensitivity.CaseInsensitive)] public abstract class SampleConsumerBuild : NukeBuild, INukeBuild { [Parameter("Configuration to build")] private readonly string Configuration; + [Parameter] private readonly bool RunTests; + [Secret] private readonly string NuGetApiKey; - [Solution] - private readonly Fallout.Solutions.Solution Solution; - [Solution("path/to/explicit.slnx")] - private readonly Fallout.Solutions.Solution ExplicitSolution; - [GitRepository] - private readonly Fallout.Common.Git.GitRepository GitRepository; + + [ProjectModel.Solution] + private readonly Solution Solution; + + [ProjectModel.Solution("path/to/explicit.slnx")] + private readonly Solution ExplicitSolution; + + [Git.GitRepository] + private readonly GitRepository GitRepository; // CI-host shims expose only the static `Instance` accessor. Consumers can // still chain into instance members because the returned type is canonical. @@ -60,6 +64,7 @@ private void TouchDelegateDisposableShim() using var bracket = DelegateDisposable.CreateBracket( setup: () => { }, cleanup: () => { }); + using var typed = DelegateDisposable.CreateBracket( setup: () => 42, cleanup: _ => { }); diff --git a/tests/Nuke.Components.Shim.Specs/SampleConsumerBuild.cs b/tests/Nuke.Components.Shim.Specs/SampleConsumerBuild.cs index 880fc9119..6148c8d3c 100644 --- a/tests/Nuke.Components.Shim.Specs/SampleConsumerBuild.cs +++ b/tests/Nuke.Components.Shim.Specs/SampleConsumerBuild.cs @@ -5,10 +5,9 @@ // they'll see CS0618 warnings pointing at the renamed IHas* form, but no hard // breakage. This file exercises every alias to lock the contract in. -#pragma warning disable CS0618 // IHaz* are intentionally obsolete; exercising them is the point. +#pragma warning disable CS0618 // IHaz* are intentionally obsolete; exercising them is the point. using Nuke.Common; -using Nuke.Components; namespace Nuke.Components.Shim.Specs; @@ -17,14 +16,14 @@ namespace Nuke.Components.Shim.Specs; // inheritance chain (IHaz* → IHas* → IFalloutBuild) resolves. public abstract class SampleConsumerBuildWithIHazAliases : NukeBuild, - IHazArtifacts, - IHazChangelog, - IHazConfiguration, - IHazGitRepository, - IHazGitVersion, - IHazNerdbankGitVersioning, - IHazReports, - IHazSolution, - IHazTwitterCredentials + IHazArtifacts, + IHazChangelog, + IHazConfiguration, + IHazGitRepository, + IHazGitVersion, + IHazNerdbankGitVersioning, + IHazReports, + IHazSolution, + IHazTwitterCredentials { }