gdharness 1.3.41 → 1.3.43

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 (107) hide show
  1. package/build/cli.js +131 -47
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +37 -12
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
  33. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
  34. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
  35. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  36. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  37. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  38. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  39. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  40. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  41. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  42. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  43. package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
  44. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  45. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
  46. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  47. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  48. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  49. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  50. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  51. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  52. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  53. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
  54. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  55. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  56. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  57. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  58. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  59. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  60. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
  61. package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
  62. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  63. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  64. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  65. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  66. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  67. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  68. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  69. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  70. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  71. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  72. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  73. package/build/godot/loader/loader.gd +9 -9
  74. package/build/godot/operations/animation_reads.gd +84 -35
  75. package/build/godot/operations/audio_buses.gd +48 -19
  76. package/build/godot/operations/class_cache.gd +16 -11
  77. package/build/godot/operations/classdb_queries.gd +89 -44
  78. package/build/godot/operations/declared_values.gd +68 -32
  79. package/build/godot/operations/dependencies.gd +139 -93
  80. package/build/godot/operations/editor_tools.gd +9 -10
  81. package/build/godot/operations/export_presets.gd +100 -54
  82. package/build/godot/operations/file_reads.gd +114 -39
  83. package/build/godot/operations/file_walk.gd +12 -5
  84. package/build/godot/operations/gdscript_analysis.gd +105 -66
  85. package/build/godot/operations/gdscript_authoring.gd +138 -39
  86. package/build/godot/operations/godot_operations.gd +17 -11
  87. package/build/godot/operations/import_pipeline.gd +110 -59
  88. package/build/godot/operations/input_actions.gd +63 -39
  89. package/build/godot/operations/patterns.gd +3 -1
  90. package/build/godot/operations/plugins.gd +36 -16
  91. package/build/godot/operations/project_config.gd +144 -27
  92. package/build/godot/operations/project_diagnostics.gd +24 -11
  93. package/build/godot/operations/project_file.gd +16 -16
  94. package/build/godot/operations/project_validation.gd +71 -44
  95. package/build/godot/operations/property_paths.gd +22 -12
  96. package/build/godot/operations/property_values.gd +143 -78
  97. package/build/godot/operations/reading.gd +37 -12
  98. package/build/godot/operations/rename_checks.gd +2 -2
  99. package/build/godot/operations/resource_files.gd +11 -9
  100. package/build/godot/operations/same_values.gd +4 -4
  101. package/build/godot/operations/scene_connections.gd +27 -13
  102. package/build/godot/operations/script_check.gd +52 -49
  103. package/build/godot/operations/serialisation.gd +75 -51
  104. package/build/godot/operations/setting_reads.gd +7 -6
  105. package/build/godot/operations/value_shapes.gd +5 -6
  106. package/build/godot/tests/listed.gd +321 -268
  107. package/package.json +1 -1
@@ -1,5 +1,4 @@
1
1
  extends RefCounted
2
-
3
2
  ## Scenes and resources read from their files, by the editor's tools and by a headless engine with
4
3
  ## none connected, so a read answers the same either way.
5
4
  ##
@@ -30,7 +29,10 @@ static func scene_tree(
30
29
  if not laid.get("ok", false):
31
30
  return laid
32
31
  var nodes: Dictionary = laid["nodes"]
33
- return {"ok": true, "tree": _entry(nodes, ".", include_properties, include_defaults, depth, 0)}
32
+ return {
33
+ "ok": true,
34
+ "tree": _entry(nodes, ".", include_properties, include_defaults, depth, 0),
35
+ }
34
36
 
35
37
 
36
38
  ## The node at [param node_path] in [param scene_path] as {ok, name, type, properties}, laid as
@@ -45,7 +47,9 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
45
47
  if not nodes.has(path):
46
48
  return {}
47
49
  var found: Dictionary = nodes[path]
48
- return {"ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"]}
50
+ return {
51
+ "ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"],
52
+ }
49
53
 
50
54
 
51
55
  ## The node at [param node_path] in [param scene_path] as [method scene_node] answers it, when it is
@@ -67,7 +71,7 @@ static func scene_node_of_kind(scene_path: String, node_path: String, kind: Stri
67
71
  "ok": true,
68
72
  "name": nodes[path]["name"],
69
73
  "type": found,
70
- "properties": nodes[path]["properties"]
74
+ "properties": nodes[path]["properties"],
71
75
  }
72
76
  else:
73
77
  var hinted: String = said
@@ -79,7 +83,7 @@ static func scene_node_of_kind(scene_path: String, node_path: String, kind: Stri
79
83
  var type: String = str(nodes[at]["type"])
80
84
  if ClassDB.class_exists(type) and ClassDB.is_parent_class(type, kind):
81
85
  var _held: bool = held.append(str(at))
82
- return {"ok": false, "error": GdhRead.not_of_kind(said, node_path, found, kind, held)}
86
+ return { "ok": false, "error": GdhRead.not_of_kind(said, node_path, found, kind, held) }
83
87
 
84
88
 
85
89
  ## [param said], the refusal for [param node_path], which the scene laid as [param nodes] does not
@@ -100,35 +104,38 @@ static func _missing_in(nodes: Dictionary, said: String, node_path: String) -> S
100
104
  var children: Array[String] = nodes[at]["children"]
101
105
  for child: String in children:
102
106
  var _named: bool = names.append(str(nodes[child]["name"]))
103
- return GdhRead.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
107
+ return GdhRead.missing_below(
108
+ said, str(nodes["."]["name"]), reached, parts[reached.size()], names
109
+ )
104
110
 
105
111
 
106
112
  ## Every node of [param scene_path] as {ok, nodes}, keyed by path, or a refusal.
107
113
  static func _laid(scene_path: String) -> Dictionary:
108
114
  if not FileAccess.file_exists(scene_path):
109
- return {"ok": false, "error": "Scene not found: " + scene_path}
115
+ return { "ok": false, "error": "Scene not found: " + scene_path }
110
116
  # Loaded without naming the type, so a resource handed in by mistake loads as what it is and the
111
117
  # refusal can say so, where asking for a PackedScene printed the loader's error and a backtrace.
112
118
  var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
113
119
  if loaded == null:
114
- return {"ok": false, "error": "Failed to load %s as a scene" % scene_path}
120
+ return { "ok": false, "error": "Failed to load %s as a scene" % scene_path }
115
121
  if not loaded is PackedScene:
116
122
  var sort: String = GdhRead.with_article(loaded.get_class())
117
123
  return {
118
124
  "ok": false,
119
- "error": "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort]
125
+ "error":
126
+ "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort],
120
127
  }
121
128
  var packed: PackedScene = loaded
122
129
  var nodes: Dictionary = {}
123
130
  var refused: String = _lay(packed.get_state(), ".", nodes, [scene_path], packed)
124
131
  if not refused.is_empty():
125
- return {"ok": false, "error": refused}
132
+ return { "ok": false, "error": refused }
126
133
  if not nodes.has("."):
127
- return {"ok": false, "error": "%s holds no nodes" % scene_path}
134
+ return { "ok": false, "error": "%s holds no nodes" % scene_path }
128
135
  var base: SceneState = packed.get_state().get_base_scene_state()
129
136
  if base != null:
130
137
  nodes["."]["inherits"] = base.get_path()
131
- return {"ok": true, "nodes": nodes}
138
+ return { "ok": true, "nodes": nodes }
132
139
 
133
140
 
134
141
  ## Lays the nodes of [param state] into [param nodes] under [param prefix], keyed by their path
@@ -152,8 +159,8 @@ static func _lay(
152
159
  var path: String = _joined(prefix, str(state.get_node_path(index)))
153
160
  var recovered: bool = false
154
161
  if not nodes.has(path) and index > 0 and _overrides(state, index) and ids[index] >= 0:
155
- # The engine finds an entry whose node was renamed or moved in the scene it comes from by
156
- # the node's unique_id, and keeps the node's new name.
162
+ # The engine finds an entry whose node was renamed or moved in the scene it comes from
163
+ # by the node's unique_id, and keeps the node's new name.
157
164
  var parent: String = _joined(prefix, str(state.get_node_path(index, true)))
158
165
  var found: String = _by_id(nodes, _owner_of(nodes, parent, prefix), ids[index])
159
166
  recovered = not found.is_empty()
@@ -164,7 +171,9 @@ static func _lay(
164
171
  var instance: PackedScene = state.get_node_instance(index)
165
172
  if instance != null:
166
173
  if within.has(instance.resource_path):
167
- return "%s instances %s, which holds it" % [within.back(), instance.resource_path]
174
+ return (
175
+ "%s instances %s, which holds it" % [within.back(), instance.resource_path]
176
+ )
168
177
  var refused: String = _lay(
169
178
  instance.get_state(), path, nodes, within + [instance.resource_path]
170
179
  )
@@ -178,15 +187,16 @@ static func _lay(
178
187
  nodes[path] = _made("InstancePlaceholder")
179
188
  nodes[path]["instanceOf"] = state.get_node_instance_placeholder(index)
180
189
  elif str(state.get_node_type(index)).is_empty():
181
- # An entry for a node of an instance that no longer has it, which the engine skips too.
190
+ # An entry for a node of an instance that no longer has it, which the engine skips
191
+ # too.
182
192
  continue
183
193
  else:
184
194
  nodes[path] = _made(str(state.get_node_type(index)))
185
195
  nodes[path]["madeHere"] = loaded != null
186
196
  if ids[index] >= 0:
187
197
  nodes[path]["uniqueId"] = ids[index]
188
- # Laid at the root with no scene handed in is a scene the one being read inherits, and the
189
- # deepest is laid first, so this is the file that makes the node.
198
+ # Laid at the root with no scene handed in is a scene the one being read inherits, and
199
+ # the deepest is laid first, so this is the file that makes the node.
190
200
  if prefix == "." and loaded == null:
191
201
  nodes[path]["inheritedFrom"] = state.get_path()
192
202
  # The name is the state's even for an entry laid already, since an inherited scene names its
@@ -251,8 +261,8 @@ static func _editable_in(scene_path: String, given_scene: PackedScene = null) ->
251
261
 
252
262
 
253
263
  ## The unique_id of each node entry in the text scene at [param scene_path], in the order its state
254
- ## holds them, -1 where an entry has none. All -1 for a binary scene, and for a file whose entries do
255
- ## not number [param count], since SceneState does not give the ids to a script.
264
+ ## holds them, -1 where an entry has none. All -1 for a binary scene, and for a file whose entries
265
+ ## do not number [param count], since SceneState does not give the ids to a script.
256
266
  static func _unique_ids(scene_path: String, count: int) -> Array[int]:
257
267
  var ids: Array[int] = []
258
268
  var _sized: int = ids.resize(count)
@@ -289,8 +299,9 @@ static func _owner_of(nodes: Dictionary, parent: String, prefix: String) -> Stri
289
299
  return at if nodes.has(at) else prefix
290
300
 
291
301
 
292
- ## The path of the node [param owner] owns whose unique_id is [param id], or "": one under an instance
293
- ## nested below [param owner] belongs to that instance's file, whose ids are numbered apart.
302
+ ## The path of the node [param owner] owns whose unique_id is [param id], or "": one under an
303
+ ## instance nested below [param owner] belongs to that instance's file, whose ids are numbered
304
+ ## apart.
294
305
  static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
295
306
  for path: String in nodes:
296
307
  if path == owner or not (owner == "." or path.begins_with(owner + "/")):
@@ -307,7 +318,7 @@ static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
307
318
  static func _made(type: String) -> Dictionary:
308
319
  var children: Array[String] = []
309
320
  var groups: Array[String] = []
310
- return {"name": "", "type": type, "properties": {}, "children": children, "groups": groups}
321
+ return { "name": "", "type": type, "properties": {}, "children": children, "groups": groups }
311
322
 
312
323
 
313
324
  ## Puts [param path] among [param parent]'s children, at [param at] when the file says where (a node
@@ -342,7 +353,9 @@ static func _entry(
342
353
  ) -> Dictionary:
343
354
  var node: Dictionary = nodes[path]
344
355
  var children: Array[Dictionary] = []
345
- var data: Dictionary = {"name": node["name"], "type": node["type"], "path": path, "children": children}
356
+ var data: Dictionary = {
357
+ "name": node["name"], "type": node["type"], "path": path, "children": children,
358
+ }
346
359
  # The root's file is the scene being read, so only another node's names a second scene.
347
360
  if path != "." and node.has("instanceOf"):
348
361
  data["instanceOf"] = node["instanceOf"]
@@ -369,7 +382,8 @@ static func _entry(
369
382
  if cells >= 0:
370
383
  data["cellCount"] = cells
371
384
  data["cellsListedBy"] = (
372
- "scene_node get with nodePath %s, which lists them as set_tilemap_cells takes them" % path
385
+ "scene_node get with nodePath %s, which lists them as set_tilemap_cells takes them"
386
+ % path
373
387
  )
374
388
  if not node.get("madeHere", false):
375
389
  var kept: Array = node.get("setHere", [])
@@ -383,14 +397,16 @@ static func _entry(
383
397
  data["childrenNotListed"] = below.size()
384
398
  return data
385
399
  for child: String in below:
386
- children.append(_entry(nodes, child, include_properties, include_defaults, depth, current_depth + 1))
400
+ children.append(
401
+ _entry(nodes, child, include_properties, include_defaults, depth, current_depth + 1)
402
+ )
387
403
  return data
388
404
 
389
405
 
390
406
  ## How many cells a TileMapLayer or TileMap of [param type] holds under the values [param given], or
391
- ## -1 when it is another node or the file sets none of its cells. The file keeps them as packed bytes,
392
- ## which a read of the tree answered with alone, so a caller decoded the base64 to learn what was
393
- ## painted. Only cells the file sets are counted: an instance's come from its own scene.
407
+ ## -1 when it is another node or the file sets none of its cells. The file keeps them as packed
408
+ ## bytes, which a read of the tree answered with alone, so a caller decoded the base64 to learn what
409
+ ## was painted. Only cells the file sets are counted: an instance's come from its own scene.
394
410
  static func _cell_count(type: String, given: Dictionary) -> int:
395
411
  if not ClassDB.class_exists(type) or not ClassDB.can_instantiate(type):
396
412
  return -1
@@ -400,7 +416,10 @@ static func _cell_count(type: String, given: Dictionary) -> int:
400
416
  var painted: bool = false
401
417
  for property: Variant in given:
402
418
  var named: String = str(property)
403
- if named == "tile_map_data" or (named.begins_with("layer_") and named.ends_with("/tile_data")):
419
+ if (
420
+ named == "tile_map_data"
421
+ or (named.begins_with("layer_") and named.ends_with("/tile_data"))
422
+ ):
404
423
  painted = true
405
424
  if not painted:
406
425
  return -1
@@ -439,7 +458,10 @@ static func _stored(type: String, given: Dictionary, include_defaults: bool) ->
439
458
  if script != null:
440
459
  for declared: Dictionary in script.get_script_property_list():
441
460
  var property: String = str(declared.get("name", ""))
442
- if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
461
+ if (
462
+ not property.is_empty()
463
+ and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE
464
+ ):
443
465
  scripted[property] = script.get_property_default_value(property)
444
466
  var values: Dictionary = {}
445
467
  var defaults: Dictionary = {}
@@ -451,7 +473,10 @@ static func _stored(type: String, given: Dictionary, include_defaults: bool) ->
451
473
  bare.set(str(property), given[property])
452
474
  for declared: Dictionary in bare.get_property_list():
453
475
  var property: String = str(declared.get("name", ""))
454
- if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
476
+ if (
477
+ not property.is_empty()
478
+ and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE
479
+ ):
455
480
  values[property] = bare.get(property)
456
481
  defaults[property] = plain.get(property)
457
482
  for made: Object in [bare, plain]:
@@ -476,7 +501,7 @@ static func _stored(type: String, given: Dictionary, include_defaults: bool) ->
476
501
  left_out += 1
477
502
  continue
478
503
  answered[property] = serialiser.serialize_value(values[property])
479
- return {"properties": answered, "left_out": left_out}
504
+ return { "properties": answered, "left_out": left_out }
480
505
 
481
506
 
482
507
  ## What a resource file holds, in the form modify takes, so a value read here can be written back.
@@ -488,10 +513,13 @@ static func _stored(type: String, given: Dictionary, include_defaults: bool) ->
488
513
  static func resource(res_path: String, include_defaults: bool) -> Dictionary:
489
514
  var file: String = res_path.get_slice("::", 0)
490
515
  if not FileAccess.file_exists(file):
491
- return {"ok": false, "error": GdhFileWalk.new().with_nearest_files("No resource at " + file, file)}
516
+ return {
517
+ "ok": false,
518
+ "error": GdhFileWalk.new().with_nearest_files("No resource at " + file, file),
519
+ }
492
520
  var whole: Resource = ResourceLoader.load(file, "", ResourceLoader.CACHE_MODE_IGNORE)
493
521
  if whole == null:
494
- return {"ok": false, "error": "%s did not load as a resource." % file}
522
+ return { "ok": false, "error": "%s did not load as a resource." % file }
495
523
  # The file's resource is held until the read is done, since what it keeps can depend on it: a
496
524
  # TileSet that goes unlinks its sources, and an atlas source with no TileSet lists none of its
497
525
  # tiles' physics, navigation or custom data.
@@ -500,14 +528,20 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
500
528
  var id: String = res_path.trim_prefix(file + "::")
501
529
  var inner: Resource = _kept_in(whole, file, id, {})
502
530
  if inner == null:
503
- return {"ok": false, "error": "%s keeps no resource with the id %s." % [file, id]}
531
+ return { "ok": false, "error": "%s keeps no resource with the id %s." % [file, id] }
504
532
  loaded = inner
505
533
  # A scene's own properties are its packed bundle, which says nothing a caller can use.
506
534
  if loaded is PackedScene:
507
535
  return {
508
536
  "ok": false,
509
537
  "error":
510
- "%s is a scene: scene_tree reads its nodes, and scene_node get one node's properties." % res_path
538
+ (
539
+ (
540
+ "%s is a scene: scene_tree reads its nodes, and scene_node get one node's "
541
+ + "properties."
542
+ )
543
+ % res_path
544
+ ),
511
545
  }
512
546
 
513
547
  var script: Script = loaded.get_script()
@@ -517,9 +551,25 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
517
551
  scripted[str(declared.get("name", ""))] = true
518
552
  var serialiser: GdhSerialisation = GdhSerialisation.new()
519
553
  var properties: Dictionary = {}
554
+ # A headless engine runs a script that is not a tool as the file loads, so what its _init and
555
+ # setters gave other properties read as values the file sets; the file's own lines say which it
556
+ # does. The editor runs no such script.
557
+ var untooled: bool = script != null and not script.is_tool() and not Engine.is_editor_hint()
558
+ var from_file: bool = (
559
+ untooled and file.ends_with(".tres") and not GdhShapes.STORED_APART.has(loaded.get_class())
560
+ )
561
+ var set_in_file: Dictionary = (
562
+ _names_set_in(
563
+ FileAccess.get_file_as_string(file), res_path.trim_prefix(file).trim_prefix("::")
564
+ )
565
+ if from_file
566
+ else {}
567
+ )
520
568
  # A Curve's points are read off one with none, since the engine has no default for point_0.
521
569
  var fresh: Object = (
522
- ClassDB.instantiate(loaded.get_class()) if GdhShapes.STORED_APART.has(loaded.get_class()) else null
570
+ ClassDB.instantiate(loaded.get_class())
571
+ if GdhShapes.STORED_APART.has(loaded.get_class())
572
+ else null
523
573
  )
524
574
  for p: Dictionary in GdhShapes.read_properties(loaded):
525
575
  var property: String = str(p.get("name", ""))
@@ -538,9 +588,14 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
538
588
  )
539
589
  if GdhSame.same(value, default):
540
590
  continue
591
+ if from_file and not set_in_file.has(property):
592
+ continue
541
593
  properties[property] = serialiser.serialize_value(value)
542
594
  var answer: Dictionary = {
543
- "ok": true, "resourcePath": res_path, "resourceType": loaded.get_class(), "properties": properties
595
+ "ok": true,
596
+ "resourcePath": res_path,
597
+ "resourceType": loaded.get_class(),
598
+ "properties": properties,
544
599
  }
545
600
  if script != null:
546
601
  answer["script"] = script.resource_path
@@ -552,6 +607,26 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
552
607
  return answer
553
608
 
554
609
 
610
+ ## The names the [code][resource][/code] section of [param text], a .tres file, sets, or those of
611
+ ## the embedded resource with the id [param id] when one is given, as a set.
612
+ static func _names_set_in(text: String, id: String) -> Dictionary:
613
+ var names: Dictionary = {}
614
+ var inside: bool = false
615
+ var key: RegEx = RegEx.create_from_string("^([^\\s=\\[][^=]*?) = ")
616
+ for line: String in text.split("\n"):
617
+ if line.begins_with("["):
618
+ inside = (
619
+ line.begins_with("[resource]")
620
+ if id.is_empty()
621
+ else line.begins_with("[sub_resource") and line.contains('id="%s"' % id)
622
+ )
623
+ continue
624
+ var found: RegExMatch = key.search(line) if inside else null
625
+ if found != null:
626
+ names[found.get_string(1)] = true
627
+ return names
628
+
629
+
555
630
  ## The resource [param file] keeps under [param id], found in [param value] or anything it holds.
556
631
  ## The engine loads such a path only while the file is cached, and a read loads the file uncached,
557
632
  ## so it is looked for among what the file holds. A resource of another file is neither taken nor
@@ -23,12 +23,15 @@ static func missing_reason(path: String) -> String:
23
23
  if id == ResourceUID.INVALID_ID or not ResourceUID.has_id(id):
24
24
  return (
25
25
  path
26
- + " does not resolve: no file in the project's uid cache has it, so the file has gone or the"
27
- + " project has not been imported yet (project_import refresh_uids imports it)"
26
+ + " does not resolve: no file in the project's uid cache has it, so the file has "
27
+ + "gone or the project has not been imported yet (project_import refresh_uids "
28
+ + "imports it)"
28
29
  )
29
30
  var resolved: String = ResourceUID.get_id_path(id)
30
31
  return (
31
- "" if FileAccess.file_exists(resolved) else path + " names " + resolved + ", which does not exist"
32
+ ""
33
+ if FileAccess.file_exists(resolved)
34
+ else path + " names " + resolved + ", which does not exist"
32
35
  )
33
36
  return "" if FileAccess.file_exists(path) else path + " does not exist"
34
37
 
@@ -41,7 +44,9 @@ func with_nearest_files(said: String, path: String) -> String:
41
44
  # One the refusal names is the file it refuses, as a script given for a scene is. Word by word,
42
45
  # since res://brain.gd is inside res://brain.gdd.
43
46
  var named: Array[String] = []
44
- for word: String in said.replace("\n", " ").replace(",", " ").replace(";", " ").split(" ", false):
47
+ for word: String in said.replace("\n", " ").replace(",", " ").replace(";", " ").split(
48
+ " ", false
49
+ ):
45
50
  named.append(word.rstrip(".:"))
46
51
  var namesakes: Array[String] = []
47
52
  for file: String in _namesakes(path):
@@ -78,7 +83,9 @@ func with_nearest_files(said: String, path: String) -> String:
78
83
  )
79
84
  if files.size() <= GdhRead.NAMED_CHILDREN:
80
85
  return "%s. The project's .%s files are %s." % [said, extension, ", ".join(files)]
81
- return "%s. None of the project's %d .%s files is named like it." % [said, files.size(), extension]
86
+ return (
87
+ "%s. None of the project's %d .%s files is named like it." % [said, files.size(), extension]
88
+ )
82
89
 
83
90
 
84
91
  # The files in [param path]'s folder named as it is with another extension, leaving out the import