Skip to content

Commit 607a255

Browse files
committed
Introduced LintResults Object
Collects the information collected during a lint run in a confined Object. Centralized JSON and HTML Generation
1 parent 3f09b15 commit 607a255

File tree

7 files changed

+211
-207
lines changed

7 files changed

+211
-207
lines changed

Source/Linter/Private/LintRule.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ TArray<FLintRuleViolation> FLintRuleViolation::AllRuleViolationsOfRuleGroup(cons
139139
TArray<UObject*> FLintRuleViolation::AllRuleViolationViolators(const TArray<FLintRuleViolation>& RuleViolationCollection) {
140140
TArray<UObject*> Violators;
141141
for (const FLintRuleViolation& RuleViolation : RuleViolationCollection) {
142-
Violators.Add(RuleViolation.Violator.Get());
142+
Violators.AddUnique(RuleViolation.Violator.Get());
143143
}
144144
return Violators;
145145
}

Source/Linter/Private/LintRuleSet.cpp

Lines changed: 131 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,135 @@
11
#include "LintRuleSet.h"
22

33
#include "AnyObject_LinterDummyClass.h"
4+
#include "IPluginManager.h"
5+
#include "JsonObjectWrapper.h"
46
#include "LintRunner.h"
57
#include "Linter.h"
68
#include "AssetRegistry/AssetRegistryModule.h"
79
#include "Modules/ModuleManager.h"
810
#include "HAL/RunnableThread.h"
911

10-
ULintRuleSet::ULintRuleSet(const FObjectInitializer& ObjectInitializer) :
11-
Super(ObjectInitializer) {}
12+
13+
TArray<TSharedPtr<FLintRuleViolation>> ULintResults::GetSharedViolations() const {
14+
TArray<TSharedPtr<FLintRuleViolation>> SharedRuleViolations;
15+
for (const FLintRuleViolation& Violation : Violations) {
16+
TSharedPtr<FLintRuleViolation> SharedViolation = MakeShared<FLintRuleViolation>(Violation);
17+
SharedViolation->PopulateAssetData();
18+
SharedRuleViolations.Push(SharedViolation);
19+
}
20+
21+
return SharedRuleViolations;
22+
}
23+
24+
TSharedPtr<FJsonObject> ULintResults::GenerateJsonReport() const {
25+
auto Report = MakeShared<FJsonObject>();
26+
27+
Report->SetStringField("Project", FPaths::GetBaseFilename(FPaths::GetProjectFilePath()));
28+
Report->SetStringField("Result", Result.ToString());
29+
Report->SetNumberField("Warnings", Warnings);
30+
Report->SetNumberField("Errors", Errors);
31+
32+
TArray<TSharedPtr<FJsonValue>> PathArray;
33+
for (const auto& Path : Paths) {
34+
PathArray.Add(MakeShared<FJsonValueString>(Path));
35+
}
36+
Report->SetArrayField("Paths", PathArray);
37+
38+
TArray<TSharedPtr<FJsonValue>> AssetsArray;
39+
for (const auto& Asset : CheckedAssets) {
40+
#if UE_VERSION_NEWER_THAN(5, 1, 0)
41+
AssetsArray.Add(MakeShared<FJsonValueString>(Asset.GetObjectPathString()));
42+
#else
43+
AssetsArray.Add(MakeShared<FJsonValueString>(Asset.ObjectPath.ToString()));
44+
#endif
45+
}
46+
Report->SetArrayField("CheckedAssets", AssetsArray);
47+
48+
TArray<TSharedPtr<FJsonValue>> ViolationsArray;
49+
for (const UObject* Violator : FLintRuleViolation::AllRuleViolationViolators(Violations)) {
50+
TSharedPtr<FJsonObject> ViolationObject = MakeShareable(new FJsonObject);
51+
52+
FAssetData AssetData;
53+
TArray<FLintRuleViolation> ViolatorViolations = FLintRuleViolation::AllRuleViolationsWithViolator(Violations, Violator);
54+
55+
if (ViolatorViolations.Num() > 0) {
56+
ViolatorViolations[0].PopulateAssetData();
57+
AssetData = ViolatorViolations[0].ViolatorAssetData;
58+
59+
ViolationObject->SetStringField("AssetName", AssetData.AssetName.ToString());
60+
ViolationObject->SetStringField("AssetFullName", AssetData.GetFullName());
61+
#if UE_VERSION_NEWER_THAN(5, 1, 0)
62+
ViolationObject->SetStringField("AssetPath", AssetData.GetObjectPathString());
63+
#else
64+
ViolationObject->SetStringField("ViolatorAssetPath", AssetData.ObjectPath.ToString());
65+
#endif
66+
//@TODO: Thumbnail export?
67+
68+
TArray<TSharedPtr<FJsonValue>> ViolationObjects;
69+
for (const FLintRuleViolation& Violation : ViolatorViolations) {
70+
ULintRule* LintRule = Violation.ViolatedRule->GetDefaultObject<ULintRule>();
71+
check(LintRule != nullptr);
72+
73+
TSharedPtr<FJsonObject> RuleJsonObject = MakeShareable(new FJsonObject);
74+
RuleJsonObject->SetStringField("Group", LintRule->RuleGroup.ToString());
75+
RuleJsonObject->SetStringField("Title", LintRule->RuleTitle.ToString());
76+
RuleJsonObject->SetStringField("Description", LintRule->RuleDescription.ToString());
77+
RuleJsonObject->SetStringField("RuleURL", LintRule->RuleURL);
78+
RuleJsonObject->SetNumberField("Severity", static_cast<int32>(LintRule->RuleSeverity));
79+
RuleJsonObject->SetStringField("RecommendedAction", Violation.RecommendedAction.ToString());
80+
81+
ViolationObjects.Add(MakeShared<FJsonValueObject>(RuleJsonObject));
82+
}
83+
84+
ViolationObject->SetArrayField("Violations", ViolationObjects);
85+
}
86+
87+
ViolationsArray.Add(MakeShared<FJsonValueObject>(ViolationObject));
88+
}
89+
Report->SetArrayField("Violators", ViolationsArray);
90+
91+
return Report;
92+
}
93+
94+
FString ULintResults::GenerateJsonReportString() const {
95+
const TSharedPtr<FJsonObject> Report = GenerateJsonReport();
96+
97+
FString ReportString;
98+
const TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&ReportString);
99+
FJsonSerializer::Serialize(Report.ToSharedRef(), Writer);
100+
101+
return ReportString;
102+
}
103+
104+
FString ULintResults::GenerateHTML() const {
105+
const FString ReportString = GenerateJsonReportString();
106+
107+
static const FString TemplatePath = IPluginManager::Get().FindPlugin("Linter")->GetBaseDir() / "Resources" /"LintReportTemplate.html";
108+
UE_LOG(LogLinter, Display, TEXT("Loading HTML report template from %s"), *TemplatePath);
109+
110+
FString Template;
111+
if (!FFileHelper::LoadFileToString(Template, *TemplatePath)) {
112+
UE_LOG(LogLinter, Error, TEXT("Could not load HTML report template."));
113+
}
114+
115+
Template.ReplaceInline(TEXT("{% Report %}"), *ReportString);
116+
return Template;
117+
}
12118

13119
ULinterNamingConvention* ULintRuleSet::GetNamingConvention() const {
14120
return NamingConvention.Get();
15121
}
16122

17-
TArray<FLintRuleViolation> ULintRuleSet::LintPath(TArray<FString> AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const {
123+
ULintResults* ULintRuleSet::LintPath(TArray<FString> AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const {
18124
// ReSharper disable once CppExpressionWithoutSideEffects
19125
NamingConvention.LoadSynchronous();
20126

21-
TArray<FLintRuleViolation> RuleViolations;
127+
ULintResults* Results = NewObject<ULintResults>();
22128

23129
if (AssetPaths.Num() == 0) {
24130
AssetPaths.Push(TEXT("/Game"));
25131
}
132+
Results->Paths = AssetPaths;
26133

27134
// Begin loading assets
28135
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
@@ -31,8 +138,6 @@ TArray<FLintRuleViolation> ULintRuleSet::LintPath(TArray<FString> AssetPaths, FS
31138
AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
32139
UE_LOG(LogLinter, Display, TEXT("Finished loading the asset registry. Loading assets..."));
33140

34-
TArray<FAssetData> AssetList;
35-
36141
FARFilter ARFilter;
37142
ARFilter.bRecursivePaths = true;
38143

@@ -41,23 +146,23 @@ TArray<FLintRuleViolation> ULintRuleSet::LintPath(TArray<FString> AssetPaths, FS
41146
ARFilter.PackagePaths.Push(FName(*AssetPath));
42147
}
43148

44-
AssetRegistryModule.Get().GetAssets(ARFilter, AssetList);
149+
AssetRegistryModule.Get().GetAssets(ARFilter, Results->CheckedAssets);
45150

46151
TArray<FLintRunner*> LintRunners;
47152
TArray<FRunnableThread*> Threads;
48153

49154
if (ParentScopedSlowTask != nullptr) {
50-
ParentScopedSlowTask->TotalAmountOfWork = AssetList.Num() + 2;
155+
ParentScopedSlowTask->TotalAmountOfWork = Results->CheckedAssets.Num() + 2;
51156
ParentScopedSlowTask->CompletedWork = 0.0f;
52157
}
53158

54-
for (const FAssetData& Asset : AssetList) {
159+
for (const FAssetData& Asset : Results->CheckedAssets) {
55160
check(Asset.IsValid());
56161
UE_LOG(LogLinter, Verbose, TEXT("Creating Lint Thread for asset \"%s\"."), *Asset.AssetName.ToString());
57162
UObject* Object = Asset.GetAsset();
58163
check(Object != nullptr);
59164

60-
FLintRunner* Runner = new FLintRunner(Object, this, &RuleViolations, ParentScopedSlowTask);
165+
FLintRunner* Runner = new FLintRunner(Object, this, &Results->Violations, ParentScopedSlowTask);
61166
check(Runner != nullptr);
62167

63168
LintRunners.Add(Runner);
@@ -88,20 +193,24 @@ TArray<FLintRuleViolation> ULintRuleSet::LintPath(TArray<FString> AssetPaths, FS
88193
ParentScopedSlowTask->EnterProgressFrame(1.0f, NSLOCTEXT("Linter", "ScanTaskFinished", "Tabulating Data..."));
89194
}
90195

91-
return RuleViolations;
92-
}
93-
94-
TArray<TSharedPtr<FLintRuleViolation>> ULintRuleSet::LintPathShared(const TArray<FString> AssetPaths, FScopedSlowTask* ParentScopedSlowTask /*= nullptr*/) const {
95-
TArray<FLintRuleViolation> RuleViolations = LintPath(AssetPaths, ParentScopedSlowTask);
96-
97-
TArray<TSharedPtr<FLintRuleViolation>> SharedRuleViolations;
98-
for (const FLintRuleViolation& Violation : RuleViolations) {
99-
TSharedPtr<FLintRuleViolation> SharedViolation = MakeShared<FLintRuleViolation>(Violation);
100-
SharedViolation->PopulateAssetData();
101-
SharedRuleViolations.Push(SharedViolation);
196+
// Count Errors and Warnings
197+
for (const FLintRuleViolation& Violation : Results->Violations) {
198+
if (Violation.ViolatedRule->GetDefaultObject<ULintRule>()->RuleSeverity <= ELintRuleSeverity::Error) {
199+
Results->Errors++;
200+
} else {
201+
Results->Warnings++;
202+
}
102203
}
103204

104-
return SharedRuleViolations;
205+
// Generate Result String
206+
Results->Result = FText::FormatNamed(
207+
FText::FromString("Linted {NumAssets} Assets: {NumWarnings} {NumWarnings}|plural(one=warning,other=warnings), {NumErrors} {NumErrors}|plural(one=error,other=errors)."),
208+
TEXT("NumAssets"), FText::FromString(FString::FromInt(Results->CheckedAssets.Num())),
209+
TEXT("NumWarnings"), FText::FromString(FString::FromInt(Results->Warnings)),
210+
TEXT("NumErrors"), FText::FromString(FString::FromInt(Results->Errors))
211+
);
212+
213+
return Results;
105214
}
106215

107216
const FLintRuleList* ULintRuleSet::GetLintRuleListForClass(const TSoftClassPtr<UObject> Class) const {

Source/Linter/Private/LinterCommandlet.cpp

Lines changed: 16 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2020 Gamemakin LLC. All Rights Reserved.
22

33
#include "LinterCommandlet.h"
4+
45
#include "AssetRegistry/AssetRegistryModule.h"
56
#include "Dom/JsonObject.h"
67
#include "Dom/JsonValue.h"
@@ -72,107 +73,42 @@ int32 ULinterCommandlet::Main(const FString& InParams) {
7273
UE_LOG(LinterCommandlet, Error, TEXT("Failed to load a rule set. Aborting. Returning error code 1."));
7374
return 1;
7475
}
75-
7676
UE_LOG(LinterCommandlet, Display, TEXT("Using rule set: %s"), *RuleSet->GetFullName());
7777

7878
if (Paths.Num() == 0) {
7979
Paths.Add(TEXT("/Game"));
8080
}
81-
8281
UE_LOG(LinterCommandlet, Display, TEXT("Attempting to Lint paths: %s"), *FString::Join(Paths, TEXT(", ")));
8382

84-
const TArray<FLintRuleViolation> RuleViolations = RuleSet->LintPath(Paths);
85-
86-
int32 NumErrors = 0;
87-
int32 NumWarnings = 0;
88-
89-
for (const FLintRuleViolation& Violation : RuleViolations) {
90-
if (Violation.ViolatedRule->GetDefaultObject<ULintRule>()->RuleSeverity <= ELintRuleSeverity::Error) {
91-
NumErrors++;
92-
} else {
93-
NumWarnings++;
94-
}
95-
}
96-
97-
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();
98-
UE_LOG(LinterCommandlet, Display, TEXT("Lint completed with %s."), *ResultsString);
83+
const ULintResults* LintResults = RuleSet->LintPath(Paths);
84+
UE_LOG(LinterCommandlet, Display, TEXT("%s"), *LintResults->Result.ToString());
9985

100-
bool bWriteReport = Switches.Contains(TEXT("json")) || ParamsMap.Contains(TEXT("json")) || Switches.Contains(TEXT("html")) || ParamsMap.Contains(TEXT("html"));
101-
if (bWriteReport) {
86+
if (Switches.Contains("json") || ParamsMap.Contains("json") || Switches.Contains("html") || ParamsMap.Contains("html")) {
10287
UE_LOG(LinterCommandlet, Display, TEXT("Generating output report..."));
10388

104-
TSharedPtr<FJsonObject> RootJsonObject = MakeShareable(new FJsonObject);
105-
TArray<TSharedPtr<FJsonValue>> ViolatorJsonObjects;
106-
107-
TArray<UObject*> UniqueViolators = FLintRuleViolation::AllRuleViolationViolators(RuleViolations);
108-
for (const UObject* Violator : UniqueViolators) {
109-
TSharedPtr<FJsonObject> AssetJsonObject = MakeShareable(new FJsonObject);
110-
TArray<FLintRuleViolation> UniqueViolatorViolations = FLintRuleViolation::AllRuleViolationsWithViolator(RuleViolations, Violator);
111-
112-
FAssetData AssetData;
113-
if (UniqueViolatorViolations.Num() > 0) {
114-
UniqueViolatorViolations[0].PopulateAssetData();
115-
AssetData = UniqueViolatorViolations[0].ViolatorAssetData;
116-
AssetJsonObject->SetStringField(TEXT("ViolatorAssetName"), AssetData.AssetName.ToString());
117-
#if UE_VERSION_NEWER_THAN(5, 1, 0)
118-
AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.GetObjectPathString());
119-
#else
120-
AssetJsonObject->SetStringField(TEXT("ViolatorAssetPath"), AssetData.ObjectPath.ToString());
121-
#endif
122-
AssetJsonObject->SetStringField(TEXT("ViolatorFullName"), AssetData.GetFullName());
123-
//@TODO: Thumbnail export?
124-
125-
TArray<TSharedPtr<FJsonValue>> RuleViolationJsonObjects;
126-
127-
for (const FLintRuleViolation& Violation : UniqueViolatorViolations) {
128-
ULintRule* LintRule = Violation.ViolatedRule->GetDefaultObject<ULintRule>();
129-
check(LintRule != nullptr);
130-
131-
TSharedPtr<FJsonObject> RuleJsonObject = MakeShareable(new FJsonObject);
132-
RuleJsonObject->SetStringField(TEXT("RuleGroup"), LintRule->RuleGroup.ToString());
133-
RuleJsonObject->SetStringField(TEXT("RuleTitle"), LintRule->RuleTitle.ToString());
134-
RuleJsonObject->SetStringField(TEXT("RuleDesc"), LintRule->RuleDescription.ToString());
135-
RuleJsonObject->SetStringField(TEXT("RuleURL"), LintRule->RuleURL);
136-
RuleJsonObject->SetNumberField(TEXT("RuleSeverity"), static_cast<int32>(LintRule->RuleSeverity));
137-
RuleJsonObject->SetStringField(TEXT("RuleRecommendedAction"), Violation.RecommendedAction.ToString());
138-
RuleViolationJsonObjects.Push(MakeShareable(new FJsonValueObject(RuleJsonObject)));
139-
}
140-
141-
AssetJsonObject->SetArrayField(TEXT("Violations"), RuleViolationJsonObjects);
142-
}
143-
144-
ViolatorJsonObjects.Add(MakeShareable(new FJsonValueObject(AssetJsonObject)));
145-
}
146-
147-
// Save off our JSON to a string
148-
RootJsonObject->SetArrayField(TEXT("Violators"), ViolatorJsonObjects);
149-
FString JsonReport;
150-
TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&JsonReport);
151-
FJsonSerializer::Serialize(RootJsonObject.ToSharedRef(), Writer);
152-
153-
// write json file if requested
154-
if (Switches.Contains(TEXT("json")) || ParamsMap.Contains(FString(TEXT("json")))) {
89+
// Write JSON file if requested
90+
if (Switches.Contains("json") || ParamsMap.Contains(FString("json"))) {
15591
FDateTime Now = FDateTime::Now();
156-
FString JsonOutputName = TEXT("lint-report-") + Now.ToString() + TEXT(".json");
92+
FString JsonOutputName = "lint-report-" + Now.ToString() + ".json";
15793

158-
const FString LintReportPath = FPaths::ProjectSavedDir() / TEXT("LintReports");
94+
const FString LintReportPath = FPaths::ProjectSavedDir() / "LintReports";
15995
FString FullOutputPath = LintReportPath / JsonOutputName;
16096

161-
if (ParamsMap.Contains(FString(TEXT("json")))) {
162-
const FString JsonOutputOverride = *ParamsMap.FindChecked(FString(TEXT("json")));
97+
if (ParamsMap.Contains("json")) {
98+
const FString JsonOutputOverride = *ParamsMap.FindChecked(FString("json"));
16399
if (FPaths::IsRelative(JsonOutputOverride)) {
164-
JsonOutputName = JsonOutputOverride;
165-
FullOutputPath = LintReportPath / JsonOutputName;
100+
FullOutputPath = LintReportPath / JsonOutputOverride;
166101
} else {
167102
FullOutputPath = JsonOutputOverride;
168103
}
169104
}
170105

171106
FullOutputPath = FPaths::ConvertRelativePathToFull(FullOutputPath);
172107
IFileManager::Get().MakeDirectory(*FPaths::GetPath(FullOutputPath), true);
173-
174108
UE_LOG(LinterCommandlet, Display, TEXT("Exporting JSON report to %s"), *FullOutputPath);
175-
if (FFileHelper::SaveStringToFile(JsonReport, *FullOutputPath)) {
109+
110+
const FString ReportString = LintResults->GenerateJsonReportString();
111+
if (FFileHelper::SaveStringToFile(ReportString, *FullOutputPath)) {
176112
UE_LOG(LinterCommandlet, Display, TEXT("Exported JSON report successfully."));
177113
} else {
178114
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) {
202138
IFileManager::Get().MakeDirectory(*FPaths::GetPath(FullOutputPath), true);
203139
UE_LOG(LinterCommandlet, Display, TEXT("Exporting HTML report to %s"), *FullOutputPath);
204140

205-
FString TemplatePath = FPaths::Combine(*IPluginManager::Get().FindPlugin(TEXT("Linter"))->GetBaseDir(), TEXT("Resources"), TEXT("LintReportTemplate.html"));
206-
UE_LOG(LinterCommandlet, Display, TEXT("Loading HTML report template from %s"), *TemplatePath);
207-
208-
FString HTMLReport;
209-
if (FFileHelper::LoadFileToString(HTMLReport, *TemplatePath)) {
210-
UE_LOG(LinterCommandlet, Display, TEXT("Loading HTML report template successfully."));
211-
212-
HTMLReport.ReplaceInline(TEXT("{% TITLE %}"), *FPaths::GetBaseFilename(FPaths::GetProjectFilePath()));
213-
HTMLReport.ReplaceInline(TEXT("{% RESULTS %}"), *ResultsString);
214-
HTMLReport.ReplaceInline(TEXT("{% LINT_REPORT %}"), *JsonReport);
215-
} else {
216-
UE_LOG(LinterCommandlet, Error, TEXT("Failed to load HTML report template."));
217-
return 1;
218-
}
219-
141+
const FString HTMLReport = LintResults->GenerateHTML();
220142
if (FFileHelper::SaveStringToFile(HTMLReport, *FullOutputPath)) {
221143
UE_LOG(LinterCommandlet, Display, TEXT("Exported HTML report successfully."));
222144
} else {
@@ -226,7 +148,7 @@ int32 ULinterCommandlet::Main(const FString& InParams) {
226148
}
227149
}
228150

229-
if (NumErrors > 0 || (Switches.Contains(TEXT("TreatWarningsAsErrors")) && NumWarnings > 0)) {
151+
if (LintResults->Errors > 0 || (Switches.Contains(TEXT("TreatWarningsAsErrors")) && LintResults->Warnings > 0)) {
230152
UE_LOG(LinterCommandlet, Display, TEXT("Lint completed with errors. Returning error code 2."));
231153
return 2;
232154
}

0 commit comments

Comments
 (0)