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
@@ -5,13 +5,14 @@ const GdhRead = preload("reading.gd")
5
5
  const GdhLog = preload("logger.gd")
6
6
  const GdhPatterns = preload("patterns.gd")
7
7
  const GdhScriptCheck = preload("script_check.gd")
8
-
9
- const MODIFICATION_TYPES: Array[String] = ["add_constant", "add_variable", "add_signal", "add_function"]
8
+ const MODIFICATION_TYPES: Array[String] = [
9
+ "add_constant", "add_variable", "add_signal", "add_function",
10
+ ]
10
11
  ## The class each template's code is written for, and the base a script made from it extends
11
12
  ## when given none: the resource template's code is a Resource's, and on the default of Node it
12
13
  ## does not parse.
13
14
  const TEMPLATE_BASES: Dictionary = {
14
- "singleton": "Node", "state_machine": "Node", "component": "Node", "resource": "Resource"
15
+ "singleton": "Node", "state_machine": "Node", "component": "Node", "resource": "Resource",
15
16
  }
16
17
  const BLANKS_AROUND_FUNCTIONS: int = 2
17
18
 
@@ -35,7 +36,8 @@ func create_gdscript(params: Dictionary) -> Dictionary:
35
36
  _log.info("Creating GDScript: " + script_path)
36
37
 
37
38
  # Content is the whole file, so a header written above it would be a second one: a content that
38
- # starts with its own `extends` did not parse, and one without it was not the file that was sent.
39
+ # starts with its own `extends` did not parse, and one without it was not the file that was
40
+ # sent.
39
41
  if not content.is_empty():
40
42
  var alongside: Array[String] = []
41
43
  for name: String in ["class_name", "extends", "template"]:
@@ -64,8 +66,8 @@ func create_gdscript(params: Dictionary) -> Dictionary:
64
66
  "Script file already exists: " + full_script_path + ". script_edit modify adds to it."
65
67
  )
66
68
 
67
- # Content is left to the parse below: two scripts that name each other are written one at a time,
68
- # and the first does not compile until the second exists.
69
+ # Content is left to the parse below: two scripts that name each other are written one at a
70
+ # time, and the first does not compile until the second exists.
69
71
  if content.is_empty():
70
72
  var header: String = _header_refusal(cls_name_param, extends_class)
71
73
  if header.is_empty() and not template.is_empty():
@@ -79,7 +81,9 @@ func create_gdscript(params: Dictionary) -> Dictionary:
79
81
  _log.debug("Creating directory: " + script_dir)
80
82
  var error: Error = dir.make_dir_recursive(script_dir.substr(6))
81
83
  if error != OK:
82
- return _log.failure("Failed to create directory: " + script_dir + ", error: " + str(error))
84
+ return _log.failure(
85
+ "Failed to create directory: " + script_dir + ", error: " + str(error)
86
+ )
83
87
 
84
88
  var script_content: String = content
85
89
  if content.is_empty():
@@ -100,8 +104,13 @@ func create_gdscript(params: Dictionary) -> Dictionary:
100
104
  file.close()
101
105
  if not stored:
102
106
  return _log.failure("Failed to write to script file: " + full_script_path)
107
+ # The editor gives a script it makes a .uid beside it. A headless engine writes none until the
108
+ # project is imported, so a script made here was missing from every check tracking the sidecars.
109
+ var uid: String = _uid_beside(full_script_path)
103
110
 
104
- var written: Script = ResourceLoader.load(full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE)
111
+ var written: Script = ResourceLoader.load(
112
+ full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE
113
+ )
105
114
  var parses: bool = _parses(written)
106
115
  var answer: Dictionary = {
107
116
  "success": true,
@@ -110,7 +119,10 @@ func create_gdscript(params: Dictionary) -> Dictionary:
110
119
  "absolute_path": ProjectSettings.globalize_path(full_script_path),
111
120
  "template_used": template if not template.is_empty() else "none",
112
121
  "parses": parses,
122
+ "uid": null,
113
123
  }
124
+ if not uid.is_empty():
125
+ answer["uid"] = uid
114
126
  # Read off the file as the engine parsed it rather than off the arguments, which content leaves
115
127
  # out. A script that does not parse tells the engine nothing, and its empty base and name were
116
128
  # answered for a file whose header says otherwise, so those are read off the text instead.
@@ -125,6 +137,22 @@ func create_gdscript(params: Dictionary) -> Dictionary:
125
137
  return answer
126
138
 
127
139
 
140
+ ## The uid in the .uid file beside [param path], written first when there is none, as the editor
141
+ ## writes it for a script it makes; empty when it could not be written. A sidecar already there is
142
+ ## the one the engine reads the script's uid from, so it is kept.
143
+ static func _uid_beside(path: String) -> String:
144
+ var sidecar: String = path + ".uid"
145
+ if FileAccess.file_exists(sidecar):
146
+ return FileAccess.get_file_as_string(sidecar).strip_edges()
147
+ var text: String = ResourceUID.id_to_text(ResourceUID.create_id_for_path(path))
148
+ var file: FileAccess = FileAccess.open(sidecar, FileAccess.WRITE)
149
+ if file == null:
150
+ return ""
151
+ var stored: bool = file.store_string(text + "\n")
152
+ file.close()
153
+ return text if stored else ""
154
+
155
+
128
156
  ## The refusal for [param path], which is no GDScript file, naming what [param doing] it instead:
129
157
  ## a shader is made with resource_edit, and once it exists its text is edited as a file's.
130
158
  static func _not_gdscript(path: String, doing: String) -> String:
@@ -140,7 +168,7 @@ static func _not_gdscript(path: String, doing: String) -> String:
140
168
  ## extends extends RefCounted, and one with no class_name answers null for it, as an absent name
141
169
  ## does everywhere else rather than as a name that is empty.
142
170
  static func _header_written(text: String) -> Dictionary:
143
- var header: Dictionary = {"extends": "RefCounted", "class_name": null}
171
+ var header: Dictionary = { "extends": "RefCounted", "class_name": null }
144
172
  for line: String in text.split("\n"):
145
173
  # An inner class's extends is indented, and is not the script's.
146
174
  if line.begins_with(" ") or line.begins_with("\t"):
@@ -186,15 +214,18 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
186
214
 
187
215
  var lines: Array[String] = []
188
216
  lines.assign(original_content.split("\n"))
189
- # Loaded only to learn whether it parsed as it was. What the engine says of that file carries its
190
- # old line numbers, and beside the errors of the file written it named lines that had moved.
217
+ # Loaded only to learn whether it parsed as it was. What the engine says of that file carries
218
+ # its old line numbers, and beside the errors of the file written it named lines that had moved.
191
219
  printerr(GdhLog.ASIDE)
192
- var before: Script = ResourceLoader.load(full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE)
220
+ var before: Script = ResourceLoader.load(
221
+ full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE
222
+ )
193
223
  printerr(GdhLog.RESUMED)
194
224
  var parsed_before: bool = _parses(before)
195
225
 
196
- # All of them checked before any is made, so a call is made whole or not at all: one skipped for a
197
- # missing name used to leave the rest written and the answer saying success over an empty list.
226
+ # All of them checked before any is made, so a call is made whole or not at all: one skipped for
227
+ # a missing name used to leave the rest written and the answer saying success over an empty
228
+ # list.
198
229
  var declared: Dictionary = _declared(lines)
199
230
  var problems: Array[String] = _modification_problems(modifications, declared)
200
231
  if not problems.is_empty():
@@ -225,14 +256,22 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
225
256
  at = _add_function(lines, fields, placed)
226
257
  if at == -1:
227
258
  return _log.failure(
228
- "Nothing was changed: " + mod_type + " " + str(fields.get("name")) + " could not be placed"
259
+ (
260
+ "Nothing was changed: "
261
+ + mod_type
262
+ + " "
263
+ + str(fields.get("name"))
264
+ + " could not be placed"
265
+ )
229
266
  )
230
- placed.append({"type": mod_type, "name": str(fields.get("name")), "index": at})
267
+ placed.append({ "type": mod_type, "name": str(fields.get("name")), "index": at })
231
268
 
232
269
  var modifications_applied: Array[Dictionary] = []
233
270
  for entry: Dictionary in placed:
234
271
  var index: int = entry["index"]
235
- modifications_applied.append({"type": entry["type"], "name": entry["name"], "line": index + 1})
272
+ modifications_applied.append(
273
+ { "type": entry["type"], "name": entry["name"], "line": index + 1 }
274
+ )
236
275
 
237
276
  file = FileAccess.open(full_script_path, FileAccess.WRITE)
238
277
  if not file:
@@ -245,7 +284,9 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
245
284
 
246
285
  var caught: GdhScriptCheck.GdhCaught = GdhScriptCheck.GdhCaught.new()
247
286
  OS.add_logger(caught)
248
- var made: Script = ResourceLoader.load(full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE)
287
+ var made: Script = ResourceLoader.load(
288
+ full_script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE
289
+ )
249
290
  OS.remove_logger(caught)
250
291
  var parses: bool = _parses(made)
251
292
  # The names checked above are the script's own, and a member the base class declares, or a body
@@ -259,7 +300,10 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
259
300
  file.close()
260
301
  if not restored:
261
302
  return _log.failure(
262
- "%s would not parse with these additions, and could not be put back as it was" % script_path
303
+ (
304
+ "%s would not parse with these additions, and could not be put back as it was"
305
+ % script_path
306
+ )
263
307
  )
264
308
  return _log.failure(
265
309
  (
@@ -297,12 +341,15 @@ static func _header_refusal(named: String, base: String) -> String:
297
341
  var declared: Dictionary = _declared_classes()
298
342
  if not named.is_empty():
299
343
  if ClassDB.class_exists(named):
300
- return "class_name %s is the name of an engine class, which a script cannot take." % named
344
+ return (
345
+ "class_name %s is the name of an engine class, which a script cannot take." % named
346
+ )
301
347
  if declared.has(named):
302
348
  return (
303
349
  (
304
- "class_name %s is taken: %s declares it, and a second script of that name does not "
305
- + "compile. Give this one another class_name, or script_edit rename the first."
350
+ "class_name %s is taken: %s declares it, and a second script of that name does "
351
+ + "not compile. Give this one another class_name, or script_edit rename the "
352
+ + "first."
306
353
  )
307
354
  % [named, declared[named]]
308
355
  )
@@ -311,7 +358,8 @@ static func _header_refusal(named: String, base: String) -> String:
311
358
  if ClassDB.class_exists(base) or declared.has(base):
312
359
  return ""
313
360
  var said: String = (
314
- "extends names %s, which is neither an engine class nor a class_name this project declares" % base
361
+ "extends names %s, which is neither an engine class nor a class_name this project declares"
362
+ % base
315
363
  )
316
364
  var close: PackedStringArray = GdhRead.nearest(base, PackedStringArray(declared.keys()))
317
365
  if close.is_empty():
@@ -368,7 +416,9 @@ static func _declared_classes() -> Dictionary:
368
416
  declared[str(entry.get("class", ""))] = path
369
417
  for path: String in GdhFileWalk.new().find_files("res://", ".gd"):
370
418
  for line: String in FileAccess.get_file_as_string(path).split("\n"):
371
- var found: RegExMatch = GdhPatterns.declared_class(GdhPatterns.without_annotations(line))
419
+ var found: RegExMatch = GdhPatterns.declared_class(
420
+ GdhPatterns.without_annotations(line)
421
+ )
372
422
  if found != null and not declared.has(found.get_string(1)):
373
423
  declared[found.get_string(1)] = path
374
424
  return declared
@@ -403,20 +453,31 @@ static func _modification_problems(modifications: Array, declared: Dictionary) -
403
453
  )
404
454
  )
405
455
  if mod_type == "add_constant" and str(fields.get("defaultValue", "")).is_empty():
406
- problems.append(which + " is a constant with no defaultValue, and a constant needs a value")
456
+ problems.append(
457
+ which + " is a constant with no defaultValue, and a constant needs a value"
458
+ )
407
459
  if (
408
460
  not str(fields.get("exportHint", "")).is_empty()
409
461
  and fields.has("isExport")
410
462
  and not GdhRead.as_bool(fields["isExport"])
411
463
  ):
412
- problems.append(
413
- which + " has an exportHint and isExport false, and a hint is written only on an export"
464
+ (
465
+ problems
466
+ . append(
467
+ (
468
+ which
469
+ + " has an exportHint and isExport false, and a hint is written only on an "
470
+ + "export"
471
+ )
472
+ )
414
473
  )
415
474
  if mod_name.is_empty():
416
475
  problems.append(which + " has no name")
417
476
  continue
418
477
  if not mod_name.is_valid_unicode_identifier():
419
- problems.append(which + " is named '" + mod_name + "', which GDScript does not accept as a name")
478
+ problems.append(
479
+ which + " is named '" + mod_name + "', which GDScript does not accept as a name"
480
+ )
420
481
  continue
421
482
  if declared.has(mod_name):
422
483
  var existing: Dictionary = declared[mod_name]
@@ -427,7 +488,9 @@ static func _modification_problems(modifications: Array, declared: Dictionary) -
427
488
  )
428
489
  )
429
490
  elif added.has(mod_name):
430
- problems.append("%s adds %s, which modification %d adds too" % [which, mod_name, added[mod_name]])
491
+ problems.append(
492
+ "%s adds %s, which modification %d adds too" % [which, mod_name, added[mod_name]]
493
+ )
431
494
  else:
432
495
  added[mod_name] = index + 1
433
496
  return problems
@@ -455,11 +518,12 @@ static func _declared(lines: Array[String]) -> Dictionary:
455
518
  continue
456
519
  var matched: RegExMatch = declaration.search(GdhPatterns.without_annotations(raw))
457
520
  if matched != null and not found.has(matched.get_string(2)):
458
- found[matched.get_string(2)] = {"kind": matched.get_string(1), "line": i + 1}
521
+ found[matched.get_string(2)] = { "kind": matched.get_string(1), "line": i + 1 }
459
522
  return found
460
523
 
461
524
 
462
- # Inserts [param new_lines] at [param at] and moves every placed line at or below it down by as many.
525
+ # Inserts [param new_lines] at [param at] and moves every placed line at or below it down by as
526
+ # many.
463
527
  static func _insert(
464
528
  lines: Array[String], at: int, new_lines: Array[String], placed: Array[Dictionary]
465
529
  ) -> bool:
@@ -530,7 +594,8 @@ func _add_constant(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
530
594
  return after + block.find(const_line) if _insert(lines, after, block, placed) else -1
531
595
 
532
596
 
533
- # Inserts the declaration and answers with its zero-based index, or -1 for one that could not be placed.
597
+ # Inserts the declaration and answers with its zero-based index, or -1 for one that could not be
598
+ # placed.
534
599
  func _add_variable(lines: Array[String], mod: Dictionary, placed: Array[Dictionary]) -> int:
535
600
  var var_name: String = str(mod.get("name", ""))
536
601
  var var_type: String = str(mod.get("varType", ""))
@@ -617,7 +682,7 @@ func _add_signal(lines: Array[String], mod: Dictionary, placed: Array[Dictionary
617
682
  # Answers with the zero-based index of the `func` line, below the blank lines that separate it.
618
683
  #
619
684
  # Two blank lines on each side, counting those already there, because that is the spacing GDScript's
620
- # style guide gives functions and the one gdformat writes and gdlint checks: one blank line passed
685
+ # style guide gives functions and the one gdstyle and gdformat write: one blank line passed
621
686
  # the engine and failed every project gating on either. A function last in the file ends it with a
622
687
  # newline, which the file is written with only when its last line is empty.
623
688
  func _add_function(lines: Array[String], mod: Dictionary, placed: Array[Dictionary]) -> int:
@@ -640,9 +705,24 @@ func _add_function(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
640
705
 
641
706
  # The body's statements relative to the function, whether or not the caller indented them for
642
707
  # it: a body written with its tab already on came out two levels in. A blank line stays empty,
643
- # since an indent alone on it is trailing whitespace to every formatter.
644
- for bl: String in body.dedent().split("\n"):
645
- func_lines.append("" if bl.strip_edges().is_empty() else "\t" + bl)
708
+ # since an indent alone on it is trailing whitespace to every formatter. Each level is the
709
+ # file's own unit, because the engine refuses a file indented with spaces in one place and tabs
710
+ # in another, so a tab put into a script indented with spaces stopped it parsing.
711
+ var unit: String = _indent_unit(lines)
712
+ var body_lines: Array[String] = []
713
+ body_lines.assign(body.dedent().strip_edges(false, true).split("\n"))
714
+ var given_unit: String = _indent_unit(body_lines)
715
+ for bl: String in body_lines:
716
+ if bl.strip_edges().is_empty():
717
+ func_lines.append("")
718
+ continue
719
+ var stripped: String = bl.lstrip(" \t")
720
+ var leading: String = bl.substr(0, bl.length() - stripped.length())
721
+ var levels: int = floori(float(leading.length()) / given_unit.length())
722
+ if leading != given_unit.repeat(levels):
723
+ func_lines.append(unit + bl)
724
+ continue
725
+ func_lines.append(unit.repeat(levels + 1) + stripped)
646
726
 
647
727
  var below: int = _blank_run(lines, insert_line, 1)
648
728
  if insert_line + below < lines.size():
@@ -654,6 +734,21 @@ func _add_function(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
654
734
  return declared_at if _insert(lines, insert_line, func_lines, placed) else -1
655
735
 
656
736
 
737
+ ## One level of indentation in [param lines]: what the first line indented below an unindented
738
+ ## line ending a block opens with, or a tab, the engine's default, when no line is.
739
+ static func _indent_unit(lines: Array[String]) -> String:
740
+ var opener: bool = false
741
+ for line: String in lines:
742
+ var stripped: String = line.strip_edges()
743
+ if stripped.is_empty() or stripped.begins_with("#"):
744
+ continue
745
+ var leading: String = line.substr(0, line.length() - line.lstrip(" \t").length())
746
+ if opener and not leading.is_empty():
747
+ return leading
748
+ opener = leading.is_empty() and stripped.ends_with(":")
749
+ return "\t"
750
+
751
+
657
752
  # How many blank lines run from [param from] on in the direction of [param step], 1 or -1.
658
753
  static func _blank_run(lines: Array[String], from: int, step: int) -> int:
659
754
  var count: int = 0
@@ -751,9 +846,9 @@ func _line_after_function(lines: Array[String], prefix: String) -> int:
751
846
 
752
847
 
753
848
  # The first line belonging to the declaration on [param at]: the comments and the lines holding only
754
- # annotations directly above it, at its indentation. A function placed on [param at] itself takes the
755
- # next function's doc comment, which then documents the wrong one, and its `@rpc` line, which then
756
- # annotates it. A comment indented further is the end of the body above, and stays with it.
849
+ # annotations directly above it, at its indentation. A function placed on [param at] itself takes
850
+ # the next function's doc comment, which then documents the wrong one, and its `@rpc` line, which
851
+ # then annotates it. A comment indented further is the end of the body above, and stays with it.
757
852
  static func _start_of_declaration(lines: Array[String], at: int) -> int:
758
853
  var indent: String = _indentation(lines[at])
759
854
  var start: int = at
@@ -783,6 +878,8 @@ func _ready() -> void:
783
878
  \tpass
784
879
  """
785
880
  "state_machine":
881
+ # The made script's first line, which is under the limit in the file it is written to.
882
+ # gdstyle:ignore=format/max-line-length
786
883
  return """## A finite state machine: one state at a time, entered and left through change_state.
787
884
 
788
885
  signal state_changed(old_state: String, new_state: String)
@@ -834,6 +931,7 @@ func _call_state(state_name: String, method: String, args: Array) -> void:
834
931
  \t\tstate.callv(method, args)
835
932
  """
836
933
  "component":
934
+ # gdstyle:ignore=format/max-line-length
837
935
  return """## A component: a child node that adds one behaviour to its parent, switched off with disable().
838
936
 
839
937
  @export var enabled: bool = true
@@ -864,6 +962,7 @@ func disable() -> void:
864
962
  \tset_physics_process(false)
865
963
  """
866
964
  "resource":
965
+ # gdstyle:ignore=format/max-line-length
867
966
  return """## Data kept in a .tres file, which resource_edit create makes and the game loads.
868
967
 
869
968
  @export var id: String = ""
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env -S godot --headless --script
2
2
  extends SceneTree
3
3
 
4
- # The command line the server runs is `godot --headless --path <project> --script <this>
5
- # <operation> @file:<params.json> <answer.json>`, and the one JSON object written to the answer
6
- # file is the answer. Everything an operation does lives in a sibling module; this file is the
7
- # command table and the wire format.
4
+ # The command line the server runs is
5
+ # `godot --headless --path <project> --script <this> <operation> @file:<params.json> <answer.json>`,
6
+ # and the one JSON object written to the answer file is the answer. Everything an operation does
7
+ # lives in a sibling module; this file is the command table and the wire format.
8
8
  #
9
9
  # A file rather than a line on stdout, because the project's autoloads run after `_init` and print
10
10
  # into the same stream: an autoload printing a dictionary in `_process` was the last JSON line out
@@ -34,17 +34,14 @@ const GdhRead = preload("reading.gd")
34
34
  const GdhRenameChecks = preload("rename_checks.gd")
35
35
  const GdhResourceFiles = preload("resource_files.gd")
36
36
  const GdhScriptCheck = preload("script_check.gd")
37
-
38
37
  ## Printed once the answer is written, after which the engine runs the project's autoloads. Kept in
39
38
  ## step with `ANSWERED` in src/headless.ts.
40
39
  const ANSWERED: String = "[gdharness] answered"
41
-
42
40
  ## Printed in `_init` by an operation that runs once the project's autoloads are in the tree, and
43
41
  ## when it begins there. Kept in step with `WAITING` and `BEGUN` in src/headless.ts, which files
44
42
  ## what the autoloads print in between as theirs rather than the operation's.
45
43
  const WAITING: String = "[gdharness] waiting for the autoloads"
46
44
  const BEGUN: String = "[gdharness] begun"
47
-
48
45
  ## The operations that compile the project's scripts. The engine knows an autoload's name only once
49
46
  ## the autoload is in the tree, which is after `_init`, so a script naming one compiled here failed
50
47
  ## with "Identifier not found" while the game and the editor ran it. A scene tool, and a read of a
@@ -116,7 +113,9 @@ func _init() -> void:
116
113
  _answer(operation, params, answer_path)
117
114
 
118
115
 
119
- func _answer_after_the_autoloads(operation: String, params: Dictionary, answer_path: String) -> void:
116
+ func _answer_after_the_autoloads(
117
+ operation: String, params: Dictionary, answer_path: String
118
+ ) -> void:
120
119
  printerr(BEGUN)
121
120
  _answer(operation, params, answer_path)
122
121
 
@@ -131,7 +130,12 @@ func _answer(operation: String, params: Dictionary, answer_path: String) -> void
131
130
  var answer: FileAccess = FileAccess.open(answer_path, FileAccess.WRITE)
132
131
  if answer == null:
133
132
  _log.error(
134
- "Could not write the answer to " + answer_path + ": " + error_string(FileAccess.get_open_error())
133
+ (
134
+ "Could not write the answer to "
135
+ + answer_path
136
+ + ": "
137
+ + error_string(FileAccess.get_open_error())
138
+ )
135
139
  )
136
140
  quit(1)
137
141
  return
@@ -168,7 +172,9 @@ func _read_params(argument: String) -> Variant:
168
172
  var json: JSON = JSON.new()
169
173
  if json.parse(params_json) != OK:
170
174
  _log.error("Failed to parse JSON parameters: " + params_json)
171
- _log.error("JSON Error: " + json.get_error_message() + " at line " + str(json.get_error_line()))
175
+ _log.error(
176
+ "JSON Error: " + json.get_error_message() + " at line " + str(json.get_error_line())
177
+ )
172
178
  return null
173
179
 
174
180
  var params: Variant = json.get_data()
@@ -338,7 +344,7 @@ func _editor_tool(params: Dictionary) -> Dictionary:
338
344
  var path: String = own.resource_path.get_base_dir().path_join("editor_tools.gd")
339
345
  var tools: Script = ResourceLoader.load(path, "Script")
340
346
  if tools == null or not tools.can_instantiate():
341
- return {"ok": false, "error": "The editor tools did not load from %s." % path}
347
+ return { "ok": false, "error": "The editor tools did not load from %s." % path }
342
348
  var answer: Dictionary = tools.call("run", params)
343
349
  return answer
344
350