gdharness 1.3.0 → 1.3.2

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.
Files changed (72) hide show
  1. package/build/cli.js +322 -96
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
  3. package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
  4. package/build/godot/addons/gdharness_editor/declared_values.gd +116 -0
  5. package/build/godot/addons/gdharness_editor/declared_values.gd.uid +1 -0
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +65 -25
  7. package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
  8. package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
  9. package/build/godot/addons/gdharness_editor/property_values.gd +222 -59
  10. package/build/godot/addons/gdharness_editor/reading.gd +65 -0
  11. package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +49 -94
  13. package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
  14. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
  15. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
  16. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  17. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
  18. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +175 -74
  19. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
  20. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
  21. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
  22. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +88 -84
  23. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
  24. package/build/godot/addons/gdharness_editor/value_shapes.gd +118 -0
  25. package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
  26. package/build/godot/addons/gdharness_runtime/reading.gd +65 -0
  27. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
  28. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
  29. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
  30. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
  31. package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
  32. package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
  33. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
  34. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
  35. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
  36. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
  37. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
  38. package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
  39. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
  40. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
  41. package/build/godot/addons/gdharness_runtime/runtime_values.gd +52 -25
  42. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
  43. package/build/godot/addons/gdharness_runtime/serialisation.gd +49 -94
  44. package/build/godot/addons/gdharness_runtime/value_shapes.gd +118 -0
  45. package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
  46. package/build/godot/operations/animation_reads.gd +7 -19
  47. package/build/godot/operations/audio_buses.gd +10 -10
  48. package/build/godot/operations/class_cache.gd +16 -16
  49. package/build/godot/operations/classdb_queries.gd +25 -15
  50. package/build/godot/operations/declared_values.gd +116 -0
  51. package/build/godot/operations/dependencies.gd +141 -33
  52. package/build/godot/operations/editor_tools.gd +12 -12
  53. package/build/godot/operations/export_presets.gd +14 -14
  54. package/build/godot/operations/file_reads.gd +65 -25
  55. package/build/godot/operations/file_walk.gd +34 -0
  56. package/build/godot/operations/gdscript_analysis.gd +27 -22
  57. package/build/godot/operations/gdscript_authoring.gd +122 -17
  58. package/build/godot/operations/godot_operations.gd +66 -63
  59. package/build/godot/operations/import_pipeline.gd +59 -20
  60. package/build/godot/operations/input_actions.gd +90 -18
  61. package/build/godot/operations/logger.gd +1 -1
  62. package/build/godot/operations/plugins.gd +8 -8
  63. package/build/godot/operations/project_config.gd +83 -20
  64. package/build/godot/operations/project_diagnostics.gd +36 -11
  65. package/build/godot/operations/property_values.gd +222 -59
  66. package/build/godot/operations/reading.gd +65 -0
  67. package/build/godot/operations/rename_checks.gd +3 -3
  68. package/build/godot/operations/resource_files.gd +9 -6
  69. package/build/godot/operations/script_check.gd +23 -7
  70. package/build/godot/operations/serialisation.gd +49 -94
  71. package/build/godot/operations/value_shapes.gd +118 -0
  72. package/package.json +1 -1
@@ -1,15 +1,17 @@
1
1
  extends RefCounted
2
2
 
3
- const Read = preload("reading.gd")
4
- const Log = preload("logger.gd")
5
- const ProjectFile = preload("project_file.gd")
6
- const Serialisation = preload("serialisation.gd")
3
+ const GdhFileWalk = preload("file_walk.gd")
4
+ const GdhRead = preload("reading.gd")
5
+ const GdhLog = preload("logger.gd")
6
+ const GdhProjectFile = preload("project_file.gd")
7
+ const GdhSerialisation = preload("serialisation.gd")
8
+ const GdhShapes = preload("value_shapes.gd")
7
9
 
8
- var _log: Log
9
- var _values: Serialisation = Serialisation.new()
10
+ var _log: GdhLog
11
+ var _values: GdhSerialisation = GdhSerialisation.new()
10
12
 
11
13
 
12
- func _init(p_log: Log) -> void:
14
+ func _init(p_log: GdhLog) -> void:
13
15
  _log = p_log
14
16
 
15
17
 
@@ -76,7 +78,7 @@ func _settings_under(prefix: String) -> Dictionary:
76
78
  var name: String = str(property.get("name", ""))
77
79
  if not name.begins_with(prefix):
78
80
  continue
79
- var kind: int = Read.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
81
+ var kind: int = GdhRead.as_int(property.get("type", TYPE_NIL), TYPE_NIL)
80
82
  var value: Variant = ProjectSettings.get_setting(name)
81
83
  var described: Dictionary = {
82
84
  "setting": name,
@@ -113,12 +115,23 @@ func set_project_setting(params: Dictionary) -> Dictionary:
113
115
  var default_value: Variant = ProjectSettings.property_get_revert(setting_path)
114
116
  var was_default: bool = had_value and default_value != null and old_value == default_value
115
117
 
116
- var final_value: Variant = _values.deserialize_value(value)
117
118
  var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
119
+ # A value of fields or a colour's html code is fitted as a property write fits it, so a value one
120
+ # of them takes the other takes too. A number is left to the check below, which refuses one the
121
+ # setting would cut where fitting it would cut it.
122
+ var final_value: Variant = _values.deserialize_value(value)
123
+ if final_value is Dictionary or (wanted == TYPE_COLOR and final_value is String):
124
+ final_value = _values.fitted(value, wanted)
118
125
  if wanted > TYPE_NIL and final_value != null and typeof(final_value) != wanted:
119
126
  if not _converts_faithfully(final_value, wanted):
120
127
  return _log.failure(_cannot_hold(setting_path, wanted, final_value))
121
128
  final_value = type_convert(final_value, wanted)
129
+ # JSON carries every number as a float and a new setting has no declared type to fit one to, so a
130
+ # whole number was saved as 800.0 beside an answer saying 800.
131
+ if not had_value and wanted == TYPE_NIL and final_value is float:
132
+ var number: float = final_value
133
+ if number == floorf(number) and absf(number) < 9.0e15:
134
+ final_value = int(number)
122
135
 
123
136
  var answer: Dictionary = {
124
137
  "setting_path": setting_path,
@@ -135,13 +148,36 @@ func set_project_setting(params: Dictionary) -> Dictionary:
135
148
  answer["unchanged"] = true
136
149
  return answer
137
150
 
151
+ var near: PackedStringArray = []
152
+ if not had_value:
153
+ near = _settings_near(setting_path)
154
+ if not near.is_empty():
155
+ answer["note"] = (
156
+ (
157
+ "No setting %s existed, so this wrote a new one, which the engine reads only where"
158
+ + " something asks for it by that name. The nearest %s %s."
159
+ )
160
+ % [setting_path, "setting is" if near.size() == 1 else "settings are", ", ".join(near)]
161
+ )
138
162
  ProjectSettings.set_setting(setting_path, final_value)
139
- var err: Error = ProjectFile.save()
163
+ var err: Error = GdhProjectFile.save()
140
164
  if err != OK:
141
165
  return _log.failure("Failed to save project.godot: " + error_string(err))
142
166
  return answer
143
167
 
144
168
 
169
+ # The settings in the same section as [param setting_path] named nearest it, for a new one that is
170
+ # likely a misspelt one: a setting nothing declares is written all the same, and is never read.
171
+ static func _settings_near(setting_path: String) -> PackedStringArray:
172
+ var section: String = setting_path.get_slice("/", 0) + "/"
173
+ var names: PackedStringArray = []
174
+ for info: Dictionary in ProjectSettings.get_property_list():
175
+ var name: String = str(info.get("name", ""))
176
+ if name.begins_with(section) and name != setting_path:
177
+ var _added: bool = names.append(name)
178
+ return GdhRead.nearest(setting_path, names)
179
+
180
+
145
181
  # The type a setting wants, or TYPE_NIL where the engine names none and there is nothing to go on.
146
182
  #
147
183
  # JSON carries one number type, so every number arrives as a float and an int setting was written
@@ -157,7 +193,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
157
193
  func _wanted_type(setting_path: String, current: Variant) -> int:
158
194
  for info: Dictionary in ProjectSettings.get_property_list():
159
195
  if str(info.get("name", "")) == setting_path:
160
- return Read.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
196
+ return GdhRead.as_int(info.get("type", TYPE_NIL), TYPE_NIL)
161
197
  return typeof(current)
162
198
 
163
199
 
@@ -181,19 +217,20 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
181
217
  if wanted in numbers and typeof(value) in numbers:
182
218
  return "%s, and %s is not the same number as one.%s" % [declared, str(value), cost]
183
219
  var given: String = type_string(typeof(value))
184
- return "%s, and %s (%s) cannot become one." % [declared, JSON.stringify(value), given]
220
+ var remedy: String = GdhShapes.remedy(value, wanted)
221
+ return "%s, and %s (%s) cannot become one.%s" % [declared, JSON.stringify(value), given, remedy]
185
222
 
186
223
 
187
224
  # Add an autoload singleton
188
225
  func add_autoload(params: Dictionary) -> Dictionary:
189
226
  var name: String = str(params.get("name", ""))
190
227
  var path: String = str(params.get("path", ""))
191
- var global: bool = Read.as_bool(params.get("global", true), true)
228
+ var global: bool = GdhRead.as_bool(params.get("global", true), true)
192
229
 
193
230
  # There is no disabled autoload in Godot 4: every entry in the list is loaded under the root at
194
231
  # every start. `enabled: false` used to write the entry without its asterisk and answer that it
195
232
  # was disabled, and the autoload ran all the same.
196
- if not Read.as_bool(params.get("enabled", true), true):
233
+ if not GdhRead.as_bool(params.get("enabled", true), true):
197
234
  return _log.failure(
198
235
  (
199
236
  "An autoload cannot be added disabled: Godot loads every autoload in the list at every"
@@ -208,7 +245,9 @@ func add_autoload(params: Dictionary) -> Dictionary:
208
245
  _log.info("Adding autoload: " + name + " -> " + path)
209
246
 
210
247
  if not FileAccess.file_exists(path):
211
- return _log.failure("Autoload file does not exist: " + path)
248
+ return _log.failure(
249
+ GdhFileWalk.new().with_nearest_files("Autoload file does not exist: " + path, path)
250
+ )
212
251
 
213
252
  # Through ProjectSettings so the file is saved the way the editor saves it, which includes
214
253
  # putting Godot's header back and dropping any comment somebody wrote into the file by hand.
@@ -218,8 +257,18 @@ func add_autoload(params: Dictionary) -> Dictionary:
218
257
  # An asterisk in front of the path makes the autoload's name a global variable, the editor's
219
258
  # "Global Variable" column. Without it the node still comes up under the root; only the name
220
259
  # stops resolving.
221
- ProjectSettings.set_setting(setting, ("*" if global else "") + path)
222
- var err: Error = ProjectFile.save()
260
+ var entry: String = ("*" if global else "") + path
261
+ if was_updated and str(ProjectSettings.get_setting(setting)) == entry:
262
+ return {
263
+ "name": name,
264
+ "path": path,
265
+ "enabled": true,
266
+ "global": global,
267
+ "action": "unchanged",
268
+ "saved": false
269
+ }
270
+ ProjectSettings.set_setting(setting, entry)
271
+ var err: Error = GdhProjectFile.save()
223
272
  if err != OK:
224
273
  return _log.failure("Failed to save project.godot: " + error_string(err))
225
274
 
@@ -229,6 +278,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
229
278
  "enabled": true,
230
279
  "global": global,
231
280
  "action": "updated" if was_updated else "added",
281
+ "saved": true,
232
282
  }
233
283
 
234
284
 
@@ -244,7 +294,7 @@ func remove_autoload(params: Dictionary) -> Dictionary:
244
294
 
245
295
  var old_value: String = str(ProjectSettings.get_setting(setting))
246
296
  ProjectSettings.set_setting(setting, null)
247
- var err: Error = ProjectFile.save()
297
+ var err: Error = GdhProjectFile.save()
248
298
  if err != OK:
249
299
  return _log.failure("Failed to save project.godot: " + error_string(err))
250
300
 
@@ -288,12 +338,25 @@ func set_main_scene(params: Dictionary) -> Dictionary:
288
338
  _log.info("Setting main scene: " + scene_path)
289
339
 
290
340
  if not FileAccess.file_exists(scene_path):
291
- return _log.failure("Scene file does not exist: " + scene_path)
341
+ return _log.failure(
342
+ GdhFileWalk.new().with_nearest_files("Scene file does not exist: " + scene_path, scene_path)
343
+ )
344
+
345
+ # The engine starts the game by instancing the main scene, so a script or any other resource set
346
+ # there was saved and answered as set, and the game then would not start.
347
+ if not ResourceLoader.exists(scene_path, "PackedScene"):
348
+ var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
349
+ var kind: String = loaded.get_class() if loaded != null else ""
350
+ var said: String = (
351
+ "%s is %s, not a scene, and the main scene has to be one"
352
+ % [scene_path, "a " + kind if not kind.is_empty() else "no resource the engine loads"]
353
+ )
354
+ return _log.failure(GdhFileWalk.new().with_nearest_files(said, scene_path.get_basename() + ".tscn"))
292
355
 
293
356
  var old_main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
294
357
 
295
358
  ProjectSettings.set_setting("application/run/main_scene", scene_path)
296
- var err: Error = ProjectFile.save()
359
+ var err: Error = GdhProjectFile.save()
297
360
 
298
361
  if err != OK:
299
362
  return _log.failure("Failed to save project settings: " + str(err))
@@ -1,21 +1,24 @@
1
1
  extends RefCounted
2
2
 
3
- const FileWalk = preload("file_walk.gd")
4
- const ImportPipeline = preload("import_pipeline.gd")
5
- const Log = preload("logger.gd")
3
+ const GdhDependencies = preload("dependencies.gd")
4
+ const GdhFileWalk = preload("file_walk.gd")
5
+ const GdhImportPipeline = preload("import_pipeline.gd")
6
+ const GdhLog = preload("logger.gd")
7
+ const GdhRead = preload("reading.gd")
8
+ const GdhScriptCheck = preload("script_check.gd")
6
9
 
7
- var _log: Log
8
- var _files: FileWalk = FileWalk.new()
10
+ var _log: GdhLog
11
+ var _files: GdhFileWalk = GdhFileWalk.new()
9
12
 
10
13
 
11
- func _init(p_log: Log) -> void:
14
+ func _init(p_log: GdhLog) -> void:
12
15
  _log = p_log
13
16
 
14
17
 
15
18
  # Get comprehensive project health check with scoring
16
19
  func get_project_health(params: Dictionary) -> Dictionary:
17
20
  var check_categories: Array = params.get(
18
- "categories", ["structure", "resources", "scripts", "scenes", "config"]
21
+ "categories", ["structure", "resources", "scripts", "references", "scenes", "config"]
19
22
  )
20
23
 
21
24
  _log.info("Performing project health check")
@@ -44,7 +47,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
44
47
  # Every format the engine imports, the list the import status reads: five of them were checked
45
48
  # here, so a project of glTF models, fonts and WebP textures had nothing to reimport.
46
49
  var resources: Array[String] = _files.find_files_with_extensions(
47
- "res://", ImportPipeline.IMPORTABLE_EXTENSIONS
50
+ "res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS
48
51
  )
49
52
 
50
53
  var missing_imports: int = 0
@@ -87,12 +90,34 @@ func get_project_health(params: Dictionary) -> Dictionary:
87
90
  if todo_count > 0:
88
91
  recommendations.append("%d scripts have TODO or FIXME comments to look at" % todo_count)
89
92
 
90
- checks["scripts"] = {
93
+ # Compiled by the server before this runs, since an engine stops at the first script that
94
+ # fails, and handed in. Without it the check read only comments and said "passed" beside
95
+ # scripts the engine refused.
96
+ var compiled: Dictionary = params.get("compiled", {})
97
+ var failures: Array = compiled.get("failures", [])
98
+ for failure: Dictionary in failures:
99
+ details.append(GdhScriptCheck.failure_line(failure))
100
+ deductions += 10
101
+
102
+ var scripts: Dictionary = {
91
103
  "name": "Scripts",
92
104
  "passed": details.is_empty(),
93
105
  "details": details,
94
106
  "total_scripts": script_files.size()
95
107
  }
108
+ if compiled.has("checked"):
109
+ scripts["compiled"] = GdhRead.as_int(compiled["checked"])
110
+ else:
111
+ scripts["not_compiled"] = str(compiled.get("not_checked", "no compile was asked for"))
112
+ checks["scripts"] = scripts
113
+
114
+ # 3b. References
115
+ if "references" in check_categories:
116
+ var details: Array[String] = []
117
+ for entry: Dictionary in GdhDependencies.new(_log).missing_references():
118
+ details.append(GdhDependencies.missing_line(entry))
119
+ deductions += 5
120
+ checks["references"] = {"name": "References", "passed": details.is_empty(), "details": details}
96
121
 
97
122
  # 4. Scenes Check
98
123
  if "scenes" in check_categories:
@@ -119,8 +144,8 @@ func get_project_health(params: Dictionary) -> Dictionary:
119
144
  if main_scene.is_empty():
120
145
  details.append("No main scene configured")
121
146
  deductions += 10
122
- elif not FileWalk.missing_reason(main_scene).is_empty():
123
- details.append("Main scene " + FileWalk.missing_reason(main_scene))
147
+ elif not GdhFileWalk.missing_reason(main_scene).is_empty():
148
+ details.append("Main scene " + GdhFileWalk.missing_reason(main_scene))
124
149
  deductions += 15
125
150
 
126
151
  var project_name: String = str(ProjectSettings.get_setting("application/config/name", ""))