@mflrevan/ucp 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +48 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +55 -6
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs +165 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/EditorStateSummary.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +22 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +1 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +10 -10
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +71 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +4 -4
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +6 -6
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +7 -7
- package/bridge/com.ucp.bridge/Editor/Controllers/RecordingController.cs +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +3 -3
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +5 -5
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs +70 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/UiController.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +1 -1
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs +468 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiHost.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs +554 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiLintService.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs +870 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiOperationManager.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs +496 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioApplier.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs +854 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioLoader.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs +374 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiScenarioModels.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs +690 -0
- package/bridge/com.ucp.bridge/Editor/Ui/UiVisualTreeInspector.cs.meta +11 -0
- package/bridge/com.ucp.bridge/{Runtime.meta → Editor/Ui.meta} +1 -1
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +79 -4
- package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +2 -2
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs +547 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiControllerTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs +253 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiLintServiceTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs +587 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UiScenarioTests.cs.meta +11 -0
- package/bridge/com.ucp.bridge/package.json +1 -1
- package/package.json +1 -1
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +0 -14
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +0 -7
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
#if UNITY_6000_0_OR_NEWER
|
|
2
|
+
using System;
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using UnityEditor;
|
|
8
|
+
using UnityEngine;
|
|
9
|
+
using UnityEngine.UIElements;
|
|
10
|
+
|
|
11
|
+
namespace UCP.Bridge
|
|
12
|
+
{
|
|
13
|
+
internal static class UiLintService
|
|
14
|
+
{
|
|
15
|
+
private const int DefaultMaxDiagnostics = 200;
|
|
16
|
+
private const int AbsoluteMaxDiagnostics = 1000;
|
|
17
|
+
|
|
18
|
+
internal static Dictionary<string, object> Run(Dictionary<string, object> parameters)
|
|
19
|
+
{
|
|
20
|
+
if (parameters == null || !parameters.TryGetValue("paths", out var pathsValue))
|
|
21
|
+
throw new ArgumentException("Missing 'paths' parameter");
|
|
22
|
+
|
|
23
|
+
var requestedPaths = ReadStringList(pathsValue, "paths");
|
|
24
|
+
if (requestedPaths.Count == 0)
|
|
25
|
+
throw new ArgumentException("'paths' must contain at least one path");
|
|
26
|
+
|
|
27
|
+
var failOnWarnings = ReadBool(parameters, "failOnWarnings", false);
|
|
28
|
+
var maxDiagnostics = ReadInt(
|
|
29
|
+
parameters,
|
|
30
|
+
"maxDiagnostics",
|
|
31
|
+
DefaultMaxDiagnostics,
|
|
32
|
+
1,
|
|
33
|
+
AbsoluteMaxDiagnostics);
|
|
34
|
+
|
|
35
|
+
var diagnostics = new DiagnosticCollector(maxDiagnostics);
|
|
36
|
+
var fixtureReports = new List<object>();
|
|
37
|
+
var pendingAssets = new SortedSet<string>(StringComparer.Ordinal);
|
|
38
|
+
|
|
39
|
+
foreach (var requestedPath in requestedPaths)
|
|
40
|
+
CollectRequestedPath(requestedPath, pendingAssets, fixtureReports, diagnostics);
|
|
41
|
+
|
|
42
|
+
var inspectedAssets = new HashSet<string>(StringComparer.Ordinal);
|
|
43
|
+
var assetReports = new List<object>();
|
|
44
|
+
|
|
45
|
+
while (pendingAssets.Count > 0)
|
|
46
|
+
{
|
|
47
|
+
var assetPath = pendingAssets.Min;
|
|
48
|
+
pendingAssets.Remove(assetPath);
|
|
49
|
+
if (!inspectedAssets.Add(assetPath))
|
|
50
|
+
continue;
|
|
51
|
+
|
|
52
|
+
var report = InspectAsset(assetPath, diagnostics);
|
|
53
|
+
assetReports.Add(report);
|
|
54
|
+
|
|
55
|
+
if (!report.TryGetValue("dependencies", out var dependenciesValue) ||
|
|
56
|
+
dependenciesValue is not IList dependencies)
|
|
57
|
+
{
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
foreach (var dependencyValue in dependencies)
|
|
62
|
+
{
|
|
63
|
+
var dependency = dependencyValue as string;
|
|
64
|
+
if (IsUiAssetPath(dependency) && !inspectedAssets.Contains(dependency))
|
|
65
|
+
pendingAssets.Add(dependency);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var passed = Passes(diagnostics.ErrorCount, diagnostics.WarningCount, failOnWarnings);
|
|
70
|
+
|
|
71
|
+
return new Dictionary<string, object>
|
|
72
|
+
{
|
|
73
|
+
["passed"] = passed,
|
|
74
|
+
["failOnWarnings"] = failOnWarnings,
|
|
75
|
+
["requestedPaths"] = requestedPaths.Cast<object>().ToList(),
|
|
76
|
+
["assetCount"] = assetReports.Count,
|
|
77
|
+
["fixtureCount"] = fixtureReports.Count,
|
|
78
|
+
["errorCount"] = diagnostics.ErrorCount,
|
|
79
|
+
["warningCount"] = diagnostics.WarningCount,
|
|
80
|
+
["diagnosticCount"] = diagnostics.ErrorCount + diagnostics.WarningCount,
|
|
81
|
+
["diagnosticsTruncated"] = diagnostics.Truncated,
|
|
82
|
+
["diagnostics"] = diagnostics.Items,
|
|
83
|
+
["assets"] = assetReports,
|
|
84
|
+
["fixtures"] = fixtureReports
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private static void CollectRequestedPath(
|
|
89
|
+
string requestedPath,
|
|
90
|
+
SortedSet<string> assets,
|
|
91
|
+
List<object> fixtureReports,
|
|
92
|
+
DiagnosticCollector diagnostics)
|
|
93
|
+
{
|
|
94
|
+
string assetPath;
|
|
95
|
+
try
|
|
96
|
+
{
|
|
97
|
+
assetPath = NormalizeProjectPath(requestedPath);
|
|
98
|
+
}
|
|
99
|
+
catch (ArgumentException ex)
|
|
100
|
+
{
|
|
101
|
+
diagnostics.Add("error", "UI_PATH_INVALID", requestedPath, "request", ex.Message);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (AssetDatabase.IsValidFolder(assetPath))
|
|
106
|
+
{
|
|
107
|
+
var childPaths = AssetDatabase.FindAssets(string.Empty, new[] { assetPath })
|
|
108
|
+
.Select(AssetDatabase.GUIDToAssetPath)
|
|
109
|
+
.OrderBy(path => path, StringComparer.Ordinal);
|
|
110
|
+
foreach (var childPath in childPaths)
|
|
111
|
+
{
|
|
112
|
+
if (IsUiAssetPath(childPath))
|
|
113
|
+
assets.Add(childPath);
|
|
114
|
+
else if (IsFixturePath(childPath))
|
|
115
|
+
CollectFixture(childPath, assets, fixtureReports, diagnostics);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (IsFixturePath(assetPath))
|
|
122
|
+
{
|
|
123
|
+
CollectFixture(assetPath, assets, fixtureReports, diagnostics);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!IsUiAssetPath(assetPath))
|
|
128
|
+
{
|
|
129
|
+
diagnostics.Add(
|
|
130
|
+
"error",
|
|
131
|
+
"UI_PATH_UNSUPPORTED",
|
|
132
|
+
assetPath,
|
|
133
|
+
"request",
|
|
134
|
+
"Expected a .uxml, .uss, .tss, .ucp-ui.json, or project folder path");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!AssetExists(assetPath))
|
|
139
|
+
{
|
|
140
|
+
diagnostics.Add("error", "UI_ASSET_NOT_FOUND", assetPath, "request", "Asset not found");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
assets.Add(assetPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static void CollectFixture(
|
|
148
|
+
string fixturePath,
|
|
149
|
+
SortedSet<string> assets,
|
|
150
|
+
List<object> fixtureReports,
|
|
151
|
+
DiagnosticCollector diagnostics)
|
|
152
|
+
{
|
|
153
|
+
if (!AssetExists(fixturePath))
|
|
154
|
+
{
|
|
155
|
+
diagnostics.Add("error", "UI_FIXTURE_NOT_FOUND", fixturePath, "fixture", "Fixture not found");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try
|
|
160
|
+
{
|
|
161
|
+
var report = UiScenarioLoader.ValidateFixture(fixturePath);
|
|
162
|
+
fixtureReports.Add(report);
|
|
163
|
+
|
|
164
|
+
if (report.TryGetValue("dependencies", out var dependenciesValue) &&
|
|
165
|
+
dependenciesValue is IList dependencies)
|
|
166
|
+
{
|
|
167
|
+
foreach (var dependencyValue in dependencies)
|
|
168
|
+
{
|
|
169
|
+
if (dependencyValue is not Dictionary<string, object> dependency ||
|
|
170
|
+
!dependency.TryGetValue("path", out var pathValue) ||
|
|
171
|
+
pathValue == null)
|
|
172
|
+
{
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
var dependencyPath = pathValue.ToString();
|
|
177
|
+
if (IsUiAssetPath(dependencyPath))
|
|
178
|
+
assets.Add(dependencyPath);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (UiScenarioException ex)
|
|
183
|
+
{
|
|
184
|
+
fixtureReports.Add(new Dictionary<string, object>
|
|
185
|
+
{
|
|
186
|
+
["valid"] = false,
|
|
187
|
+
["path"] = fixturePath,
|
|
188
|
+
["error"] = ex.ToDictionary()
|
|
189
|
+
});
|
|
190
|
+
diagnostics.Add("error", ex.Code, fixturePath, "fixture", ex.Message, ex.Location);
|
|
191
|
+
}
|
|
192
|
+
catch (Exception ex)
|
|
193
|
+
{
|
|
194
|
+
fixtureReports.Add(new Dictionary<string, object>
|
|
195
|
+
{
|
|
196
|
+
["valid"] = false,
|
|
197
|
+
["path"] = fixturePath
|
|
198
|
+
});
|
|
199
|
+
diagnostics.Add("error", "UI_FIXTURE_INVALID", fixturePath, "fixture", ex.Message);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static Dictionary<string, object> InspectAsset(
|
|
204
|
+
string assetPath,
|
|
205
|
+
DiagnosticCollector diagnostics)
|
|
206
|
+
{
|
|
207
|
+
var report = new Dictionary<string, object>
|
|
208
|
+
{
|
|
209
|
+
["path"] = assetPath,
|
|
210
|
+
["guid"] = AssetDatabase.AssetPathToGUID(assetPath),
|
|
211
|
+
["assetType"] = null,
|
|
212
|
+
["importerType"] = null,
|
|
213
|
+
["contentHash"] = string.Empty,
|
|
214
|
+
["importedWithErrors"] = false,
|
|
215
|
+
["importedWithWarnings"] = false,
|
|
216
|
+
["reimported"] = false,
|
|
217
|
+
["cloneAttempted"] = false,
|
|
218
|
+
["cloneSucceeded"] = false,
|
|
219
|
+
["cloneElementCount"] = 0,
|
|
220
|
+
["dependencies"] = new List<object>()
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (!AssetExists(assetPath))
|
|
224
|
+
{
|
|
225
|
+
diagnostics.Add("error", "UI_ASSET_NOT_FOUND", assetPath, "request", "Asset not found");
|
|
226
|
+
return report;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try
|
|
230
|
+
{
|
|
231
|
+
if (ShouldReimport(assetPath))
|
|
232
|
+
{
|
|
233
|
+
AssetDatabase.ImportAsset(
|
|
234
|
+
assetPath,
|
|
235
|
+
ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
|
|
236
|
+
report["reimported"] = true;
|
|
237
|
+
}
|
|
238
|
+
report["contentHash"] = AssetDatabase.GetAssetDependencyHash(assetPath).ToString();
|
|
239
|
+
}
|
|
240
|
+
catch (Exception ex)
|
|
241
|
+
{
|
|
242
|
+
diagnostics.Add("error", "UI_IMPORT_FAILED", assetPath, "import", ex.Message);
|
|
243
|
+
return report;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
var dependencies = AssetDatabase.GetDependencies(assetPath, true)
|
|
247
|
+
.Where(IsUiAssetPath)
|
|
248
|
+
.OrderBy(value => value, StringComparer.Ordinal)
|
|
249
|
+
.Cast<object>()
|
|
250
|
+
.ToList();
|
|
251
|
+
report["dependencies"] = dependencies;
|
|
252
|
+
|
|
253
|
+
var importer = AssetImporter.GetAtPath(assetPath);
|
|
254
|
+
report["importerType"] = importer == null ? null : importer.GetType().FullName;
|
|
255
|
+
|
|
256
|
+
var importHadError = false;
|
|
257
|
+
var importHadWarning = false;
|
|
258
|
+
var importLog = AssetImporter.GetImportLog(assetPath);
|
|
259
|
+
if (importLog?.logEntries != null)
|
|
260
|
+
{
|
|
261
|
+
foreach (var entry in importLog.logEntries)
|
|
262
|
+
{
|
|
263
|
+
var severity = ClassifyImportSeverity(entry.flags.ToString());
|
|
264
|
+
if (severity == null)
|
|
265
|
+
continue;
|
|
266
|
+
|
|
267
|
+
importHadError |= severity == "error";
|
|
268
|
+
importHadWarning |= severity == "warning";
|
|
269
|
+
diagnostics.Add(
|
|
270
|
+
severity,
|
|
271
|
+
severity == "error" ? "UI_IMPORT_ERROR" : "UI_IMPORT_WARNING",
|
|
272
|
+
assetPath,
|
|
273
|
+
"import",
|
|
274
|
+
entry.message,
|
|
275
|
+
entry.line,
|
|
276
|
+
entry.file,
|
|
277
|
+
entry.context == null ? null : entry.context.name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
|
|
282
|
+
report["assetType"] = asset == null ? null : asset.GetType().FullName;
|
|
283
|
+
if (asset == null)
|
|
284
|
+
{
|
|
285
|
+
diagnostics.Add("error", "UI_ASSET_LOAD_FAILED", assetPath, "import", "Asset did not load");
|
|
286
|
+
return report;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
var importedWithErrors = false;
|
|
290
|
+
var importedWithWarnings = false;
|
|
291
|
+
|
|
292
|
+
if (asset is StyleSheet styleSheet)
|
|
293
|
+
{
|
|
294
|
+
importedWithErrors = styleSheet.importedWithErrors;
|
|
295
|
+
importedWithWarnings = styleSheet.importedWithWarnings;
|
|
296
|
+
report["contentHash"] = styleSheet.contentHash.ToString();
|
|
297
|
+
}
|
|
298
|
+
else if (asset is VisualTreeAsset treeAsset)
|
|
299
|
+
{
|
|
300
|
+
importedWithErrors = treeAsset.importedWithErrors;
|
|
301
|
+
importedWithWarnings = treeAsset.importedWithWarnings;
|
|
302
|
+
report["contentHash"] = treeAsset.contentHash.ToString();
|
|
303
|
+
ProbeClone(treeAsset, assetPath, report, diagnostics);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
report["importedWithErrors"] = importedWithErrors;
|
|
307
|
+
report["importedWithWarnings"] = importedWithWarnings;
|
|
308
|
+
|
|
309
|
+
if (importedWithErrors && !importHadError)
|
|
310
|
+
{
|
|
311
|
+
diagnostics.Add(
|
|
312
|
+
"error",
|
|
313
|
+
"UI_IMPORT_ERROR",
|
|
314
|
+
assetPath,
|
|
315
|
+
"import",
|
|
316
|
+
"Unity reports that the asset imported with errors");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (importedWithWarnings && !importHadWarning)
|
|
320
|
+
{
|
|
321
|
+
diagnostics.Add(
|
|
322
|
+
"warning",
|
|
323
|
+
"UI_IMPORT_WARNING",
|
|
324
|
+
assetPath,
|
|
325
|
+
"import",
|
|
326
|
+
"Unity reports that the asset imported with warnings");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return report;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private static void ProbeClone(
|
|
333
|
+
VisualTreeAsset treeAsset,
|
|
334
|
+
string assetPath,
|
|
335
|
+
Dictionary<string, object> report,
|
|
336
|
+
DiagnosticCollector diagnostics)
|
|
337
|
+
{
|
|
338
|
+
report["cloneAttempted"] = true;
|
|
339
|
+
var cloneRoot = new VisualElement();
|
|
340
|
+
|
|
341
|
+
void CaptureCloneLog(string message, string stackTrace, LogType type)
|
|
342
|
+
{
|
|
343
|
+
var severity = ClassifyLogSeverity(type);
|
|
344
|
+
if (severity == null)
|
|
345
|
+
return;
|
|
346
|
+
|
|
347
|
+
diagnostics.Add(
|
|
348
|
+
severity,
|
|
349
|
+
severity == "error" ? "UI_UXML_CLONE_ERROR" : "UI_UXML_CLONE_WARNING",
|
|
350
|
+
assetPath,
|
|
351
|
+
"clone",
|
|
352
|
+
message);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Application.logMessageReceived += CaptureCloneLog;
|
|
356
|
+
try
|
|
357
|
+
{
|
|
358
|
+
treeAsset.CloneTree(cloneRoot);
|
|
359
|
+
report["cloneSucceeded"] = true;
|
|
360
|
+
report["cloneElementCount"] = CountElements(cloneRoot);
|
|
361
|
+
}
|
|
362
|
+
catch (Exception ex)
|
|
363
|
+
{
|
|
364
|
+
report["cloneSucceeded"] = false;
|
|
365
|
+
diagnostics.Add(
|
|
366
|
+
"error",
|
|
367
|
+
"UI_UXML_CLONE_EXCEPTION",
|
|
368
|
+
assetPath,
|
|
369
|
+
"clone",
|
|
370
|
+
ex.GetBaseException().Message);
|
|
371
|
+
}
|
|
372
|
+
finally
|
|
373
|
+
{
|
|
374
|
+
Application.logMessageReceived -= CaptureCloneLog;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private static int CountElements(VisualElement root)
|
|
379
|
+
{
|
|
380
|
+
var count = 1;
|
|
381
|
+
for (var index = 0; index < root.hierarchy.childCount; index++)
|
|
382
|
+
count += CountElements(root.hierarchy[index]);
|
|
383
|
+
return count;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private static string NormalizeProjectPath(string value)
|
|
387
|
+
{
|
|
388
|
+
return UiScenarioLoader.NormalizeAssetPath(value, null, "$request.paths");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private static bool AssetExists(string assetPath)
|
|
392
|
+
{
|
|
393
|
+
var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
|
394
|
+
return AssetDatabase.LoadMainAssetAtPath(assetPath) != null ||
|
|
395
|
+
File.Exists(Path.Combine(projectRoot, assetPath));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
internal static bool ShouldReimport(string assetPath)
|
|
399
|
+
{
|
|
400
|
+
if (!assetPath.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase))
|
|
401
|
+
return true;
|
|
402
|
+
var package = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
|
|
403
|
+
// Immutable packages already have imported artifacts. Inspect those
|
|
404
|
+
// artifacts and logs without forcing work on the package cache.
|
|
405
|
+
return package != null &&
|
|
406
|
+
(package.source == UnityEditor.PackageManager.PackageSource.Embedded ||
|
|
407
|
+
package.source == UnityEditor.PackageManager.PackageSource.Local);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private static bool IsUiAssetPath(string path)
|
|
411
|
+
{
|
|
412
|
+
return path != null &&
|
|
413
|
+
(path.EndsWith(".uxml", StringComparison.OrdinalIgnoreCase) ||
|
|
414
|
+
path.EndsWith(".uss", StringComparison.OrdinalIgnoreCase) ||
|
|
415
|
+
path.EndsWith(".tss", StringComparison.OrdinalIgnoreCase));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private static bool IsFixturePath(string path)
|
|
419
|
+
{
|
|
420
|
+
return path != null && path.EndsWith(".ucp-ui.json", StringComparison.OrdinalIgnoreCase);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
internal static bool Passes(int errorCount, int warningCount, bool failOnWarnings)
|
|
424
|
+
{
|
|
425
|
+
return errorCount == 0 && (!failOnWarnings || warningCount == 0);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
internal static string ClassifyImportSeverity(string flags)
|
|
429
|
+
{
|
|
430
|
+
if (string.IsNullOrEmpty(flags))
|
|
431
|
+
return null;
|
|
432
|
+
if (flags.IndexOf("error", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
433
|
+
return "error";
|
|
434
|
+
if (flags.IndexOf("warning", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
435
|
+
return "warning";
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
internal static string ClassifyLogSeverity(LogType type)
|
|
440
|
+
{
|
|
441
|
+
if (type == LogType.Warning)
|
|
442
|
+
return "warning";
|
|
443
|
+
if (type == LogType.Error || type == LogType.Assert || type == LogType.Exception)
|
|
444
|
+
return "error";
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private static List<string> ReadStringList(object value, string field)
|
|
449
|
+
{
|
|
450
|
+
if (value is not List<object> values)
|
|
451
|
+
throw new ArgumentException($"'{field}' must be an array of strings");
|
|
452
|
+
|
|
453
|
+
var result = new List<string>(values.Count);
|
|
454
|
+
foreach (var item in values)
|
|
455
|
+
{
|
|
456
|
+
if (item is not string text || string.IsNullOrWhiteSpace(text))
|
|
457
|
+
throw new ArgumentException($"'{field}' must contain only non-empty strings");
|
|
458
|
+
result.Add(text);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private static bool ReadBool(Dictionary<string, object> values, string field, bool fallback)
|
|
465
|
+
{
|
|
466
|
+
if (!values.TryGetValue(field, out var value) || value == null)
|
|
467
|
+
return fallback;
|
|
468
|
+
if (value is bool boolean)
|
|
469
|
+
return boolean;
|
|
470
|
+
throw new ArgumentException($"'{field}' must be a boolean");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private static int ReadInt(
|
|
474
|
+
Dictionary<string, object> values,
|
|
475
|
+
string field,
|
|
476
|
+
int fallback,
|
|
477
|
+
int minimum,
|
|
478
|
+
int maximum)
|
|
479
|
+
{
|
|
480
|
+
if (!values.TryGetValue(field, out var value) || value == null)
|
|
481
|
+
return fallback;
|
|
482
|
+
|
|
483
|
+
int parsed;
|
|
484
|
+
try
|
|
485
|
+
{
|
|
486
|
+
parsed = Convert.ToInt32(value);
|
|
487
|
+
}
|
|
488
|
+
catch (Exception)
|
|
489
|
+
{
|
|
490
|
+
throw new ArgumentException($"'{field}' must be an integer");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (parsed < minimum || parsed > maximum)
|
|
494
|
+
throw new ArgumentException($"'{field}' must be between {minimum} and {maximum}");
|
|
495
|
+
return parsed;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
internal sealed class DiagnosticCollector
|
|
499
|
+
{
|
|
500
|
+
private readonly int _limit;
|
|
501
|
+
|
|
502
|
+
internal DiagnosticCollector(int limit)
|
|
503
|
+
{
|
|
504
|
+
_limit = limit;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
internal int ErrorCount { get; private set; }
|
|
508
|
+
internal int WarningCount { get; private set; }
|
|
509
|
+
internal bool Truncated => ErrorCount + WarningCount > Items.Count;
|
|
510
|
+
internal List<object> Items { get; } = new();
|
|
511
|
+
|
|
512
|
+
internal void Add(
|
|
513
|
+
string severity,
|
|
514
|
+
string code,
|
|
515
|
+
string assetPath,
|
|
516
|
+
string source,
|
|
517
|
+
string message,
|
|
518
|
+
object line = null,
|
|
519
|
+
string file = null,
|
|
520
|
+
string context = null)
|
|
521
|
+
{
|
|
522
|
+
if (severity == "error")
|
|
523
|
+
ErrorCount++;
|
|
524
|
+
else if (severity == "warning")
|
|
525
|
+
WarningCount++;
|
|
526
|
+
|
|
527
|
+
if (Items.Count >= _limit)
|
|
528
|
+
{
|
|
529
|
+
if (severity != "error")
|
|
530
|
+
return;
|
|
531
|
+
var warningIndex = Items.FindLastIndex(item =>
|
|
532
|
+
item is Dictionary<string, object> diagnostic &&
|
|
533
|
+
Equals(diagnostic["severity"], "warning"));
|
|
534
|
+
if (warningIndex < 0)
|
|
535
|
+
return;
|
|
536
|
+
Items.RemoveAt(warningIndex);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
Items.Add(new Dictionary<string, object>
|
|
540
|
+
{
|
|
541
|
+
["severity"] = severity,
|
|
542
|
+
["code"] = code,
|
|
543
|
+
["assetPath"] = assetPath,
|
|
544
|
+
["source"] = source,
|
|
545
|
+
["message"] = message,
|
|
546
|
+
["line"] = line,
|
|
547
|
+
["file"] = file,
|
|
548
|
+
["context"] = context
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
#endif
|