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.
- package/build/cli.js +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- 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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
197
|
-
# missing name used to leave the rest written and the answer saying success over an empty
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
305
|
-
+ "compile. Give this one another class_name, or script_edit rename the
|
|
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"
|
|
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(
|
|
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(
|
|
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
|
-
|
|
413
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
645
|
-
|
|
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
|
|
755
|
-
# next function's doc comment, which then documents the wrong one, and its `@rpc` line, which
|
|
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
|
|
5
|
-
# <operation> @file:<params.json> <answer.json>`,
|
|
6
|
-
# file is the answer. Everything an operation does
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|