diff --git a/Source/Linter/Private/LintRule.cpp b/Source/Linter/Private/LintRule.cpp index 4670b8b..b8a6b54 100644 --- a/Source/Linter/Private/LintRule.cpp +++ b/Source/Linter/Private/LintRule.cpp @@ -139,7 +139,7 @@ TArray FLintRuleViolation::AllRuleViolationsOfRuleGroup(cons TArray FLintRuleViolation::AllRuleViolationViolators(const TArray& RuleViolationCollection) { TArray Violators; for (const FLintRuleViolation& RuleViolation : RuleViolationCollection) { - Violators.Add(RuleViolation.Violator.Get()); + Violators.AddUnique(RuleViolation.Violator.Get()); } return Violators; } diff --git a/Source/Linter/Private/LintRuleSet.cpp b/Source/Linter/Private/LintRuleSet.cpp index f8b6a29..3b5b73f 100644 --- a/Source/Linter/Private/LintRuleSet.cpp +++ b/Source/Linter/Private/LintRuleSet.cpp @@ -1,28 +1,135 @@ #include "LintRuleSet.h" #include "AnyObject_LinterDummyClass.h" +#include "IPluginManager.h" +#include "JsonObjectWrapper.h" #include "LintRunner.h" #include "Linter.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Modules/ModuleManager.h" #include "HAL/RunnableThread.h" -ULintRuleSet::ULintRuleSet(const FObjectInitializer& ObjectInitializer) : - Super(ObjectInitializer) {} + +TArray> ULintResults::GetSharedViolations() const { + TArray> SharedRuleViolations; + for (const FLintRuleViolation& Violation : Violations) { + TSharedPtr SharedViolation = MakeShared(Violation); + SharedViolation->PopulateAssetData(); + SharedRuleViolations.Push(SharedViolation); + } + + return SharedRuleViolations; +} + +TSharedPtr ULintResults::GenerateJsonReport() const { + auto Report = MakeShared(); + + Report->SetStringField("Project", FPaths::GetBaseFilename(FPaths::GetProjectFilePath())); + Report->SetStringField("Result", Result.ToString()); + Report->SetNumberField("Warnings", Warnings); + Report->SetNumberField("Errors", Errors); + + TArray> PathArray; + for (const auto& Path : Paths) { + PathArray.Add(MakeShared(Path)); + } + Report->SetArrayField("Paths", PathArray); + + TArray> AssetsArray; + for (const auto& Asset : CheckedAssets) { +#if UE_VERSION_NEWER_THAN(5, 1, 0) + AssetsArray.Add(MakeShared(Asset.GetObjectPathString())); +#else + AssetsArray.Add(MakeShared(Asset.ObjectPath.ToString())); +#endif + } + Report->SetArrayField("CheckedAssets", AssetsArray); + + TArray> ViolationsArray; + for (const UObject* Violator : FLintRuleViolation::AllRuleViolationViolators(Violations)) { + TSharedPtr ViolationObject = MakeShareable(new FJsonObject); + + FAssetData AssetData; + TArray ViolatorViolations = FLintRuleViolation::AllRuleViolationsWithViolator(Violations, Violator); + + if (ViolatorViolations.Num() > 0) { + ViolatorViolations[0].PopulateAssetData(); + AssetData = ViolatorViolations[0].ViolatorAssetData; + + ViolationObject->SetStringField("AssetName", AssetData.AssetName.ToString()); + ViolationObject->SetStringField("AssetFullName", AssetData.GetFullName()); +#if UE_VERSION_NEWER_THAN(5, 1, 0) + ViolationObject->SetStringField("AssetPath", AssetData.GetObjectPathString()); +#else + ViolationObject->SetStringField("ViolatorAssetPath", AssetData.ObjectPath.ToString()); +#endif + //@TODO: Thumbnail export? + + TArray> ViolationObjects; + for (const FLintRuleViolation& Violation : ViolatorViolations) { + ULintRule* LintRule = Violation.ViolatedRule->GetDefaultObject(); + check(LintRule != nullptr); + + TSharedPtr RuleJsonObject = MakeShareable(new FJsonObject); + RuleJsonObject->SetStringField("Group", LintRule->RuleGroup.ToString()); + RuleJsonObject->SetStringField("Title", LintRule->RuleTitle.ToString()); + RuleJsonObject->SetStringField("Description", LintRule->RuleDescription.ToString()); + RuleJsonObject->SetStringField("RuleURL", LintRule->RuleURL); + RuleJsonObject->SetNumberField("Severity", static_cast(LintRule->RuleSeverity)); + RuleJsonObject->SetStringField("RecommendedAction", Violation.RecommendedAction.ToString()); + + ViolationObjects.Add(MakeShared(RuleJsonObject)); + } + + ViolationObject->SetArrayField("Violations", ViolationObjects); + } + + ViolationsArray.Add(MakeShared(ViolationObject)); + } + Report->SetArrayField("Violators", ViolationsArray); + + return Report; +} + +FString ULintResults::GenerateJsonReportString() const { + const TSharedPtr Report = GenerateJsonReport(); + + FString ReportString; + const TSharedRef>> Writer = TJsonWriterFactory>::Create(&ReportString); + FJsonSerializer::Serialize(Report.ToSharedRef(), Writer); + + return ReportString; +} + +FString ULintResults::GenerateHTML() const { + const FString ReportString = GenerateJsonReportString(); + + static const FString TemplatePath = IPluginManager::Get().FindPlugin("Linter")->GetBaseDir() / "Resources" /"LintReportTemplate.html"; + UE_LOG(LogLinter, Display, TEXT("Loading HTML report template from %s"), *TemplatePath); + + FString Template; + if (!FFileHelper::LoadFileToString(Template, *TemplatePath)) { + UE_LOG(LogLinter, Error, TEXT("Could not load HTML report template.")); + } + + Template.ReplaceInline(TEXT("{% Report %}"), *ReportString); + return Template; +} ULinterNamingConvention* ULintRuleSet::GetNamingConvention() const { return NamingConvention.Get(); } -TArray ULintRuleSet::LintPath(TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const { +ULintResults* ULintRuleSet::LintPath(TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const { // ReSharper disable once CppExpressionWithoutSideEffects NamingConvention.LoadSynchronous(); - TArray RuleViolations; + ULintResults* Results = NewObject(); if (AssetPaths.Num() == 0) { AssetPaths.Push(TEXT("/Game")); } + Results->Paths = AssetPaths; // Begin loading assets const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); @@ -31,8 +138,6 @@ TArray ULintRuleSet::LintPath(TArray AssetPaths, FS AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true); UE_LOG(LogLinter, Display, TEXT("Finished loading the asset registry. Loading assets...")); - TArray AssetList; - FARFilter ARFilter; ARFilter.bRecursivePaths = true; @@ -41,23 +146,23 @@ TArray ULintRuleSet::LintPath(TArray AssetPaths, FS ARFilter.PackagePaths.Push(FName(*AssetPath)); } - AssetRegistryModule.Get().GetAssets(ARFilter, AssetList); + AssetRegistryModule.Get().GetAssets(ARFilter, Results->CheckedAssets); TArray LintRunners; TArray Threads; if (ParentScopedSlowTask != nullptr) { - ParentScopedSlowTask->TotalAmountOfWork = AssetList.Num() + 2; + ParentScopedSlowTask->TotalAmountOfWork = Results->CheckedAssets.Num() + 2; ParentScopedSlowTask->CompletedWork = 0.0f; } - for (const FAssetData& Asset : AssetList) { + for (const FAssetData& Asset : Results->CheckedAssets) { check(Asset.IsValid()); UE_LOG(LogLinter, Verbose, TEXT("Creating Lint Thread for asset \"%s\"."), *Asset.AssetName.ToString()); UObject* Object = Asset.GetAsset(); check(Object != nullptr); - FLintRunner* Runner = new FLintRunner(Object, this, &RuleViolations, ParentScopedSlowTask); + FLintRunner* Runner = new FLintRunner(Object, this, &Results->Violations, ParentScopedSlowTask); check(Runner != nullptr); LintRunners.Add(Runner); @@ -88,20 +193,24 @@ TArray ULintRuleSet::LintPath(TArray AssetPaths, FS ParentScopedSlowTask->EnterProgressFrame(1.0f, NSLOCTEXT("Linter", "ScanTaskFinished", "Tabulating Data...")); } - return RuleViolations; -} - -TArray> ULintRuleSet::LintPathShared(const TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const { - TArray RuleViolations = LintPath(AssetPaths, ParentScopedSlowTask); - - TArray> SharedRuleViolations; - for (const FLintRuleViolation& Violation : RuleViolations) { - TSharedPtr SharedViolation = MakeShared(Violation); - SharedViolation->PopulateAssetData(); - SharedRuleViolations.Push(SharedViolation); + // Count Errors and Warnings + for (const FLintRuleViolation& Violation : Results->Violations) { + if (Violation.ViolatedRule->GetDefaultObject()->RuleSeverity <= ELintRuleSeverity::Error) { + Results->Errors++; + } else { + Results->Warnings++; + } } - return SharedRuleViolations; + // Generate Result String + Results->Result = FText::FormatNamed( + FText::FromString("Linted {NumAssets} Assets: {NumWarnings} {NumWarnings}|plural(one=warning,other=warnings), {NumErrors} {NumErrors}|plural(one=error,other=errors)."), + TEXT("NumAssets"), FText::FromString(FString::FromInt(Results->CheckedAssets.Num())), + TEXT("NumWarnings"), FText::FromString(FString::FromInt(Results->Warnings)), + TEXT("NumErrors"), FText::FromString(FString::FromInt(Results->Warnings)) + ); + + return Results; } const FLintRuleList* ULintRuleSet::GetLintRuleListForClass(const TSoftClassPtr Class) const { diff --git a/Source/Linter/Private/LinterCommandlet.cpp b/Source/Linter/Private/LinterCommandlet.cpp index 59afad6..d368a80 100644 --- a/Source/Linter/Private/LinterCommandlet.cpp +++ b/Source/Linter/Private/LinterCommandlet.cpp @@ -1,6 +1,7 @@ // Copyright 2020 Gamemakin LLC. All Rights Reserved. #include "LinterCommandlet.h" + #include "AssetRegistry/AssetRegistryModule.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" @@ -72,97 +73,31 @@ int32 ULinterCommandlet::Main(const FString& InParams) { UE_LOG(LinterCommandlet, Error, TEXT("Failed to load a rule set. Aborting. Returning error code 1.")); return 1; } - UE_LOG(LinterCommandlet, Display, TEXT("Using rule set: %s"), *RuleSet->GetFullName()); if (Paths.Num() == 0) { Paths.Add(TEXT("/Game")); } - UE_LOG(LinterCommandlet, Display, TEXT("Attempting to Lint paths: %s"), *FString::Join(Paths, TEXT(", "))); - const TArray RuleViolations = RuleSet->LintPath(Paths); - - int32 NumErrors = 0; - int32 NumWarnings = 0; - - for (const FLintRuleViolation& Violation : RuleViolations) { - if (Violation.ViolatedRule->GetDefaultObject()->RuleSeverity <= ELintRuleSeverity::Error) { - NumErrors++; - } else { - NumWarnings++; - } - } - - FString ResultsString = FText::FormatNamed(FText::FromString("Lint completed with {NumWarnings} {NumWarnings}|plural(one=warning,other=warnings), {NumErrors} {NumErrors}|plural(one=error,other=errors)."), TEXT("NumWarnings"), FText::FromString(FString::FromInt(NumWarnings)), TEXT("NumErrors"), FText::FromString(FString::FromInt(NumErrors))).ToString(); - UE_LOG(LinterCommandlet, Display, TEXT("Lint completed with %s."), *ResultsString); + const ULintResults* LintResults = RuleSet->LintPath(Paths); + UE_LOG(LinterCommandlet, Display, TEXT("%s"), *LintResults->Result.ToString()); - bool bWriteReport = Switches.Contains(TEXT("json")) || ParamsMap.Contains(TEXT("json")) || Switches.Contains(TEXT("html")) || ParamsMap.Contains(TEXT("html")); - if (bWriteReport) { + if (Switches.Contains("json") || ParamsMap.Contains("json") || Switches.Contains("html") || ParamsMap.Contains("html")) { UE_LOG(LinterCommandlet, Display, TEXT("Generating output report...")); - TSharedPtr RootJsonObject = MakeShareable(new FJsonObject); - TArray> ViolatorJsonObjects; - - TArray UniqueViolators = FLintRuleViolation::AllRuleViolationViolators(RuleViolations); - for (const UObject* Violator : UniqueViolators) { - TSharedPtr AssetJsonObject = MakeShareable(new FJsonObject); - TArray UniqueViolatorViolations = FLintRuleViolation::AllRuleViolationsWithViolator(RuleViolations, Violator); - - FAssetData AssetData; - if (UniqueViolatorViolations.Num() > 0) { - UniqueViolatorViolations[0].PopulateAssetData(); - AssetData = UniqueViolatorViolations[0].ViolatorAssetData; - AssetJsonObject->SetStringField(TEXT("ViolatorAssetName"), AssetData.AssetName.ToString()); -#if UE_VERSION_NEWER_THAN(5, 1, 0) - AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.GetObjectPathString()); -#else - AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.ObjectPath.ToString()); -#endif - AssetJsonObject->SetStringField(TEXT("ViolatorFullName"), AssetData.GetFullName()); - //@TODO: Thumbnail export? - - TArray> RuleViolationJsonObjects; - - for (const FLintRuleViolation& Violation : UniqueViolatorViolations) { - ULintRule* LintRule = Violation.ViolatedRule->GetDefaultObject(); - check(LintRule != nullptr); - - TSharedPtr RuleJsonObject = MakeShareable(new FJsonObject); - RuleJsonObject->SetStringField(TEXT("RuleGroup"), LintRule->RuleGroup.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleTitle"), LintRule->RuleTitle.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleDesc"), LintRule->RuleDescription.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleURL"), LintRule->RuleURL); - RuleJsonObject->SetNumberField(TEXT("RuleSeverity"), static_cast(LintRule->RuleSeverity)); - RuleJsonObject->SetStringField(TEXT("RuleRecommendedAction"), Violation.RecommendedAction.ToString()); - RuleViolationJsonObjects.Push(MakeShareable(new FJsonValueObject(RuleJsonObject))); - } - - AssetJsonObject->SetArrayField(TEXT("Violations"), RuleViolationJsonObjects); - } - - ViolatorJsonObjects.Add(MakeShareable(new FJsonValueObject(AssetJsonObject))); - } - - // Save off our JSON to a string - RootJsonObject->SetArrayField(TEXT("Violators"), ViolatorJsonObjects); - FString JsonReport; - TSharedRef>> Writer = TJsonWriterFactory>::Create(&JsonReport); - FJsonSerializer::Serialize(RootJsonObject.ToSharedRef(), Writer); - - // write json file if requested - if (Switches.Contains(TEXT("json")) || ParamsMap.Contains(FString(TEXT("json")))) { + // Write JSON file if requested + if (Switches.Contains("json") || ParamsMap.Contains(FString("json"))) { FDateTime Now = FDateTime::Now(); - FString JsonOutputName = TEXT("lint-report-") + Now.ToString() + TEXT(".json"); + FString JsonOutputName = "lint-report-" + Now.ToString() + ".json"; - const FString LintReportPath = FPaths::ProjectSavedDir() / TEXT("LintReports"); + const FString LintReportPath = FPaths::ProjectSavedDir() / "LintReports"; FString FullOutputPath = LintReportPath / JsonOutputName; - if (ParamsMap.Contains(FString(TEXT("json")))) { - const FString JsonOutputOverride = *ParamsMap.FindChecked(FString(TEXT("json"))); + if (ParamsMap.Contains("json")) { + const FString JsonOutputOverride = *ParamsMap.FindChecked(FString("json")); if (FPaths::IsRelative(JsonOutputOverride)) { - JsonOutputName = JsonOutputOverride; - FullOutputPath = LintReportPath / JsonOutputName; + FullOutputPath = LintReportPath / JsonOutputOverride; } else { FullOutputPath = JsonOutputOverride; } @@ -170,9 +105,10 @@ int32 ULinterCommandlet::Main(const FString& InParams) { FullOutputPath = FPaths::ConvertRelativePathToFull(FullOutputPath); IFileManager::Get().MakeDirectory(*FPaths::GetPath(FullOutputPath), true); - UE_LOG(LinterCommandlet, Display, TEXT("Exporting JSON report to %s"), *FullOutputPath); - if (FFileHelper::SaveStringToFile(JsonReport, *FullOutputPath)) { + + const FString ReportString = LintResults->GenerateJsonReportString(); + if (FFileHelper::SaveStringToFile(ReportString, *FullOutputPath)) { UE_LOG(LinterCommandlet, Display, TEXT("Exported JSON report successfully.")); } else { UE_LOG(LinterCommandlet, Error, TEXT("Failed to export JSON report. Aborting. Returning error code 1.")); @@ -202,21 +138,7 @@ int32 ULinterCommandlet::Main(const FString& InParams) { IFileManager::Get().MakeDirectory(*FPaths::GetPath(FullOutputPath), true); UE_LOG(LinterCommandlet, Display, TEXT("Exporting HTML report to %s"), *FullOutputPath); - FString TemplatePath = FPaths::Combine(*IPluginManager::Get().FindPlugin(TEXT("Linter"))->GetBaseDir(), TEXT("Resources"), TEXT("LintReportTemplate.html")); - UE_LOG(LinterCommandlet, Display, TEXT("Loading HTML report template from %s"), *TemplatePath); - - FString HTMLReport; - if (FFileHelper::LoadFileToString(HTMLReport, *TemplatePath)) { - UE_LOG(LinterCommandlet, Display, TEXT("Loading HTML report template successfully.")); - - HTMLReport.ReplaceInline(TEXT("{% TITLE %}"), *FPaths::GetBaseFilename(FPaths::GetProjectFilePath())); - HTMLReport.ReplaceInline(TEXT("{% RESULTS %}"), *ResultsString); - HTMLReport.ReplaceInline(TEXT("{% LINT_REPORT %}"), *JsonReport); - } else { - UE_LOG(LinterCommandlet, Error, TEXT("Failed to load HTML report template.")); - return 1; - } - + const FString HTMLReport = LintResults->GenerateHTML(); if (FFileHelper::SaveStringToFile(HTMLReport, *FullOutputPath)) { UE_LOG(LinterCommandlet, Display, TEXT("Exported HTML report successfully.")); } else { @@ -226,7 +148,7 @@ int32 ULinterCommandlet::Main(const FString& InParams) { } } - if (NumErrors > 0 || (Switches.Contains(TEXT("TreatWarningsAsErrors")) && NumWarnings > 0)) { + if (LintResults->Errors > 0 || (Switches.Contains(TEXT("TreatWarningsAsErrors")) && LintResults->Warnings > 0)) { UE_LOG(LinterCommandlet, Display, TEXT("Lint completed with errors. Returning error code 2.")); return 2; } diff --git a/Source/Linter/Private/UI/LintReport.cpp b/Source/Linter/Private/UI/LintReport.cpp index a1ffa62..927f234 100644 --- a/Source/Linter/Private/UI/LintReport.cpp +++ b/Source/Linter/Private/UI/LintReport.cpp @@ -233,9 +233,6 @@ void SLintReport::Construct(const FArguments& Args) { void SLintReport::Rebuild(const ULintRuleSet* SelectedLintRuleSet) { const float PaddingAmount = FLinterStyle::Get()->GetFloat("Linter.Padding"); - - NumErrors = 0; - NumWarnings = 0; bHasRanReport = false; if (SelectedLintRuleSet == nullptr) { @@ -255,60 +252,20 @@ void SLintReport::Rebuild(const ULintRuleSet* SelectedLintRuleSet) { FLinterModule& LinterModule = FModuleManager::LoadModuleChecked(TEXT("Linter")); TArray LintPaths = LinterModule.GetDesiredLintPaths(); - RuleViolations = SelectedLintRuleSet->LintPathShared(LintPaths, &SlowTask); - - for (TSharedPtr Violation : RuleViolations) { - if (Violation->ViolatedRule->GetDefaultObject()->RuleSeverity <= ELintRuleSeverity::Error) { - NumErrors++; - } else { - NumWarnings++; - } - } + LintResults = SelectedLintRuleSet->LintPath(LintPaths, &SlowTask); + RuleViolations = LintResults->GetSharedViolations(); TArray UniqueViolators = FLintRuleViolation::AllRuleViolationViolators(RuleViolations); - TSharedPtr ThumbnailPool = MakeShareable(new FAssetThumbnailPool(UniqueViolators.Num())); - - TSharedPtr RootJsonObject = MakeShareable(new FJsonObject); - TArray> ViolatorJsonObjects; + TSharedPtr ThumbnailPool = MakeShared(UniqueViolators.Num()); - for (UObject* Violator : UniqueViolators) { - // We might as well build JSON data here as we're iterating through all our rule violations anyway - TSharedPtr AssetJsonObject = MakeShareable(new FJsonObject); - TArray> UniqueViolatorViolations = FLintRuleViolation::AllRuleViolationsWithViolatorShared(RuleViolations, Violator); + for (const UObject* Violator : UniqueViolators) { + TArray> Violations = FLintRuleViolation::AllRuleViolationsWithViolatorShared(RuleViolations, Violator); FAssetData AssetData; - if (UniqueViolatorViolations.Num() > 0) { - AssetData = UniqueViolatorViolations[0]->ViolatorAssetData; - AssetJsonObject->SetStringField(TEXT("ViolatorAssetName"), AssetData.AssetName.ToString()); -#if UE_VERSION_NEWER_THAN(5, 1, 0) - AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.GetObjectPathString()); -#else - AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.ObjectPath.ToString()); -#endif - AssetJsonObject->SetStringField(TEXT("ViolatorFullName"), AssetData.GetFullName()); - //@TODO: Thumbnail export? - - TArray> RuleViolationJsonObjects; - - for (TSharedPtr Violation : UniqueViolatorViolations) { - ULintRule* LintRule = Violation->ViolatedRule->GetDefaultObject(); - check(LintRule != nullptr); - - TSharedPtr RuleJsonObject = MakeShareable(new FJsonObject); - RuleJsonObject->SetStringField(TEXT("RuleGroup"), LintRule->RuleGroup.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleTitle"), LintRule->RuleTitle.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleDesc"), LintRule->RuleDescription.ToString()); - RuleJsonObject->SetStringField(TEXT("RuleURL"), LintRule->RuleURL); - RuleJsonObject->SetNumberField(TEXT("RuleSeverity"), static_cast(LintRule->RuleSeverity)); - RuleJsonObject->SetStringField(TEXT("RuleRecommendedAction"), Violation->RecommendedAction.ToString()); - RuleViolationJsonObjects.Push(MakeShareable(new FJsonValueObject(RuleJsonObject))); - } - - AssetJsonObject->SetArrayField(TEXT("Violations"), RuleViolationJsonObjects); + if (Violations.Num() > 0) { + AssetData = Violations[0]->ViolatorAssetData; } - ViolatorJsonObjects.Add(MakeShareable(new FJsonValueObject(AssetJsonObject))); - // clang-format off // @formatter:off AssetDetailsScrollBoxPtr.Get()->AddSlot() @@ -318,7 +275,7 @@ void SLintReport::Rebuild(const ULintRuleSet* SelectedLintRuleSet) { [ SNew(SLintReportAssetDetails) .AssetData(AssetData) - .RuleViolations(UniqueViolatorViolations) + .RuleViolations(Violations) .ThumbnailPool(ThumbnailPool) ]; // clang-format on @@ -326,7 +283,7 @@ void SLintReport::Rebuild(const ULintRuleSet* SelectedLintRuleSet) { } TMultiMap> ViolationsMappedByRule = FLintRuleViolation::AllRuleViolationsMappedByViolatedLintRuleShared(RuleViolations); - TSharedPtr RuleThumbnailPool = MakeShareable(new FAssetThumbnailPool(ViolationsMappedByRule.Num())); // In case we ever want to render 'rule thumbnails' in the future + TSharedPtr RuleThumbnailPool = MakeShared(ViolationsMappedByRule.Num()); // In case we ever want to render 'rule thumbnails' in the future TArray UniqueRules; ViolationsMappedByRule.GetKeys(UniqueRules); @@ -352,25 +309,12 @@ void SLintReport::Rebuild(const ULintRuleSet* SelectedLintRuleSet) { } } - // Save off our JSON to a string - RootJsonObject->SetArrayField(TEXT("Violators"), ViolatorJsonObjects); - JsonReport.Empty(); - TSharedRef>> Writer = TJsonWriterFactory>::Create(&JsonReport); - FJsonSerializer::Serialize(RootJsonObject.ToSharedRef(), Writer); - // Update Summary Text Block - int32 NumAssets = UniqueViolators.Num(); - FText ResultsSummary = FText::FormatNamed(LOCTEXT("ErrorWarningDisplay", "{NumAssets} {NumAssets}|plural(one=Asset,other=Assets), {NumErrors} {NumErrors}|plural(one=Error,other=Errors), {NumWarnings} {NumWarnings}|plural(one=Warning,other=Warnings)"), TEXT("NumAssets"), NumAssets, TEXT("NumErrors"), NumErrors, TEXT("NumWarnings"), NumWarnings); - ResultsTextBlockPtr->SetText(ResultsSummary); + ResultsTextBlockPtr->SetText(LintResults->Result); - // Prepare the HTML Export - FString TemplatePath = FPaths::Combine(*IPluginManager::Get().FindPlugin(TEXT("Linter"))->GetBaseDir(), TEXT("Resources"), TEXT("LintReportTemplate.html")); - - if (FFileHelper::LoadFileToString(HTMLReport, *TemplatePath)) { - HTMLReport.ReplaceInline(TEXT("{% TITLE %}"), *FPaths::GetBaseFilename(FPaths::GetProjectFilePath())); - HTMLReport.ReplaceInline(TEXT("{% RESULTS %}"), *ResultsSummary.ToString()); - HTMLReport.ReplaceInline(TEXT("{% LINT_REPORT %}"), *JsonReport); - } + // Genereate JSON and HTML reports + JsonReport = LintResults->GenerateJsonReportString(); + HTMLReport = LintResults->GenerateHTML(); bHasRanReport = true; } diff --git a/Source/Linter/Private/UI/LintWizard.cpp b/Source/Linter/Private/UI/LintWizard.cpp index 1eb9e6c..7a61d46 100644 --- a/Source/Linter/Private/UI/LintWizard.cpp +++ b/Source/Linter/Private/UI/LintWizard.cpp @@ -189,7 +189,7 @@ void SLintWizard::Construct(const FArguments& InArgs) { SNew(STextBlock) .Text(LOCTEXT("MarketplaceNoErrorsRequired", "The Epic Marketplace requires you to have zero linting errors before submission and approval.")) .Visibility_Lambda([&]() { - return (LintReport.IsValid() && LintReport->bHasRanReport && LintReport->NumErrors > 0 && LintReport->LastUsedRuleSet != nullptr && LintReport->LastUsedRuleSet->bShowMarketplacePublishingInfoInLintWizard) ? EVisibility::HitTestInvisible : EVisibility::Collapsed; + return (LintReport.IsValid() && LintReport->bHasRanReport && LintReport->LintResults->Errors > 0 && LintReport->LastUsedRuleSet != nullptr && LintReport->LastUsedRuleSet->bShowMarketplacePublishingInfoInLintWizard) ? EVisibility::HitTestInvisible : EVisibility::Collapsed; }) ] // Title spacer @@ -213,7 +213,7 @@ void SLintWizard::Construct(const FArguments& InArgs) { + SWizard::Page() .OnEnter(this, &SLintWizard::OnMarketplaceRecommendationsEntered) .CanShow_Lambda([&]() { - return LintReport.IsValid() && LintReport->bHasRanReport && LintReport->NumErrors <= 0 && LintReport->LastUsedRuleSet != nullptr && LintReport->LastUsedRuleSet->bShowMarketplacePublishingInfoInLintWizard; + return LintReport.IsValid() && LintReport->bHasRanReport && LintReport->LintResults->Errors <= 0 && LintReport->LastUsedRuleSet != nullptr && LintReport->LastUsedRuleSet->bShowMarketplacePublishingInfoInLintWizard; }) [ SNew(SVerticalBox) diff --git a/Source/Linter/Public/LintRuleSet.h b/Source/Linter/Public/LintRuleSet.h index 651a3db..9ee978c 100644 --- a/Source/Linter/Public/LintRuleSet.h +++ b/Source/Linter/Public/LintRuleSet.h @@ -1,47 +1,73 @@ // Copyright 2019-2020 Gamemakin LLC. All Rights Reserved. #pragma once - #include "LinterNamingConvention.h" #include "Misc/ScopedSlowTask.h" #include "LintRule.h" #include "LintRuleSet.generated.h" + USTRUCT(BlueprintType) struct LINTER_API FLintRuleList { - GENERATED_USTRUCT_BODY() - - FLintRuleList() {} + GENERATED_BODY() UPROPERTY(EditAnywhere, Category = Default) TArray> LintRules; - bool RequiresGameThread() const;; + bool RequiresGameThread() const; bool PassesRules(UObject* ObjectToLint, const ULintRuleSet* ParentRuleSet, TArray& OutRuleViolations) const; }; -/** - *Comment - */ + UCLASS(BlueprintType, Blueprintable) -class LINTER_API ULintRuleSet : public UDataAsset { +class ULintResults : public UObject { GENERATED_BODY() public: - ULintRuleSet(const FObjectInitializer& ObjectInitializer); + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + int32 Warnings = 0; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + int32 Errors = 0; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + FText Result; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + TArray Paths; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + TArray CheckedAssets; + + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Lint") + TArray Violations; + + TArray> GetSharedViolations() const; + + TSharedPtr GenerateJsonReport() const; + + UFUNCTION(BlueprintCallable, Category = "Lint") + FString GenerateJsonReportString() const; + + UFUNCTION(BlueprintCallable, Category = "Lint") + FString GenerateHTML() const; +}; - //UFUNCTION(BlueprintCallable, Category = "Conventions") + +UCLASS(BlueprintType, Blueprintable) +class LINTER_API ULintRuleSet : public UDataAsset { + GENERATED_BODY() + +public: + // UFUNCTION(BlueprintCallable, Category = "Conventions") const FLintRuleList* GetLintRuleListForClass(TSoftClassPtr Class) const; UFUNCTION(BlueprintCallable, Category = "Conventions") ULinterNamingConvention* GetNamingConvention() const; /** Invoke this with a list of asset paths to recursively lint all assets in paths. */ - //UFUNCTION(BlueprintCallable, Category = "Lint") - TArray LintPath(TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask = nullptr) const; - - /** This is a temp dumb way to do this. */ - TArray> LintPathShared(TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask = nullptr) const; + // UFUNCTION(BlueprintCallable, Category = "Lint") + ULintResults* LintPath(TArray AssetPaths, FScopedSlowTask* ParentScopedSlowTask = nullptr) const; UPROPERTY(EditDefaultsOnly, Category = "Marketplace") bool bShowMarketplacePublishingInfoInLintWizard = false; diff --git a/Source/Linter/Public/UI/LintReport.h b/Source/Linter/Public/UI/LintReport.h index 8825a40..8d1f7ed 100644 --- a/Source/Linter/Public/UI/LintReport.h +++ b/Source/Linter/Public/UI/LintReport.h @@ -7,6 +7,9 @@ #include "LintRule.h" +class ULintResults; + + class SLintReport : public SCompoundWidget { public: SLATE_BEGIN_ARGS(SLintReport) { } @@ -21,14 +24,14 @@ class SLintReport : public SCompoundWidget { const ULintRuleSet* LastUsedRuleSet = nullptr; TSharedPtr ResultsTextBlockPtr; - TArray> RuleViolations; TSharedPtr ViewOptionsComboButton; TSharedPtr AssetDetailsScrollBoxPtr; TSharedPtr RuleDetailsScrollBoxPtr; + + ULintResults* LintResults = nullptr; + TArray> RuleViolations; FString JsonReport; FString HTMLReport; bool bHasRanReport = false; - int32 NumErrors = 0; - int32 NumWarnings = 0; };