gdharness 1.3.2 → 1.3.4
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 +163 -85
- package/build/godot/addons/gdharness_editor/animation_reads.gd +14 -11
- package/build/godot/addons/gdharness_editor/declared_values.gd +157 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +15 -3
- package/build/godot/addons/gdharness_editor/file_walk.gd +134 -0
- package/build/godot/addons/gdharness_editor/file_walk.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd +44 -23
- package/build/godot/addons/gdharness_editor/property_paths.gd +225 -0
- package/build/godot/addons/gdharness_editor/property_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +249 -227
- package/build/godot/addons/gdharness_editor/reading.gd +15 -0
- package/build/godot/addons/gdharness_editor/same_values.gd +177 -0
- package/build/godot/addons/gdharness_editor/same_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +100 -15
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -3
- package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +402 -55
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -2
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +91 -0
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +88 -5
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +102 -337
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +363 -0
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +15 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +10 -26
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +3 -2
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +2 -1
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +30 -0
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -3
- package/build/godot/operations/animation_reads.gd +14 -11
- package/build/godot/operations/declared_values.gd +157 -4
- package/build/godot/operations/dependencies.gd +14 -39
- package/build/godot/operations/editor_tools.gd +7 -3
- package/build/godot/operations/file_reads.gd +15 -3
- package/build/godot/operations/file_walk.gd +1 -0
- package/build/godot/operations/gdscript_analysis.gd +229 -75
- package/build/godot/operations/gdscript_authoring.gd +142 -34
- package/build/godot/operations/import_pipeline.gd +17 -6
- package/build/godot/operations/logger.gd +7 -0
- package/build/godot/operations/property_paths.gd +225 -0
- package/build/godot/operations/property_values.gd +249 -227
- package/build/godot/operations/reading.gd +15 -0
- package/build/godot/operations/same_values.gd +177 -0
- package/build/godot/operations/serialisation.gd +75 -3
- package/package.json +1 -1
|
@@ -9,6 +9,39 @@ const GdhLog = preload("logger.gd")
|
|
|
9
9
|
var _log: GdhLog
|
|
10
10
|
## Kept for the call, so the project's class names are read once however many bases are walked.
|
|
11
11
|
var _dependencies: GdhDependencies = null
|
|
12
|
+
## The script being read, which an inner class's base is resolved against.
|
|
13
|
+
var _reading: String = ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## What one class body declares, the script's own or an inner class's.
|
|
17
|
+
class GdhMembers:
|
|
18
|
+
var declared_class_name: Variant = null
|
|
19
|
+
var extends_name: String = "RefCounted"
|
|
20
|
+
var signals: Array[Dictionary] = []
|
|
21
|
+
var variables: Array[Dictionary] = []
|
|
22
|
+
var functions: Array[Dictionary] = []
|
|
23
|
+
var constants: Array[Dictionary] = []
|
|
24
|
+
var enums: Array[Dictionary] = []
|
|
25
|
+
var inner_classes: Array[Dictionary] = []
|
|
26
|
+
## The index of the last line the body holds.
|
|
27
|
+
var last: int = -1
|
|
28
|
+
## An inner class's name and the line its `class` is on.
|
|
29
|
+
var inner_name: String = ""
|
|
30
|
+
var line: int = 0
|
|
31
|
+
|
|
32
|
+
## The inner class as its entry under inner_classes, in the fields a script's own are in.
|
|
33
|
+
func as_inner() -> Dictionary:
|
|
34
|
+
return {
|
|
35
|
+
"name": inner_name,
|
|
36
|
+
"line": line,
|
|
37
|
+
"extends": extends_name,
|
|
38
|
+
"signals": signals,
|
|
39
|
+
"variables": variables,
|
|
40
|
+
"functions": functions,
|
|
41
|
+
"constants": constants,
|
|
42
|
+
"enums": enums,
|
|
43
|
+
"inner_classes": inner_classes,
|
|
44
|
+
}
|
|
12
45
|
|
|
13
46
|
|
|
14
47
|
func _init(p_log: GdhLog) -> void:
|
|
@@ -41,19 +74,77 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
41
74
|
|
|
42
75
|
var lines: PackedStringArray = content.split("\n")
|
|
43
76
|
|
|
44
|
-
var declared_class_name: Variant = null
|
|
45
|
-
var extends_name: String = "RefCounted"
|
|
46
|
-
var signals: Array[Dictionary] = []
|
|
47
|
-
var variables: Array[Dictionary] = []
|
|
48
|
-
var functions: Array[Dictionary] = []
|
|
49
|
-
var constants: Array[Dictionary] = []
|
|
50
|
-
var enums: Array[Dictionary] = []
|
|
51
|
-
var inner_classes: Array[String] = []
|
|
52
77
|
var dependencies: Array[String] = []
|
|
78
|
+
_reading = full_script_path
|
|
79
|
+
var own: GdhMembers = _members(lines, 0, "", dependencies)
|
|
80
|
+
var extends_name: String = own.extends_name
|
|
81
|
+
var functions: Array[Dictionary] = own.functions
|
|
53
82
|
|
|
83
|
+
# After the walk, because it needs the base the walk reads, and a file that declared one after
|
|
84
|
+
# its first function would otherwise be judged against the default.
|
|
85
|
+
_mark_virtuals(functions, extends_name, full_script_path)
|
|
86
|
+
|
|
87
|
+
# A script typed by its class_name is depended on as surely as one preloaded, and the answer
|
|
88
|
+
# listed only the preloads: a `var stats: Stats` left stats.gd out, while project_dependencies
|
|
89
|
+
# named it. The same reading as that tool's, so the two agree.
|
|
90
|
+
var classes_used: Array[Dictionary] = []
|
|
91
|
+
if _dependencies == null:
|
|
92
|
+
_dependencies = GdhDependencies.new(_log)
|
|
93
|
+
var declared_by: Dictionary = _dependencies.classes_declared()
|
|
94
|
+
for used: String in GdhDependencies.classes_used_in(content, declared_by):
|
|
95
|
+
var declaring: String = str(declared_by[used])
|
|
96
|
+
if declaring == full_script_path:
|
|
97
|
+
continue
|
|
98
|
+
classes_used.append({"name": used, "path": declaring})
|
|
99
|
+
if not declaring in dependencies:
|
|
100
|
+
dependencies.append(declaring)
|
|
101
|
+
|
|
102
|
+
# An autoload is named the way a class is, and its script is one this one needs to compile, but
|
|
103
|
+
# the project loads it rather than this script, so it is answered apart from dependencies.
|
|
104
|
+
var global_autoloads: Dictionary = {}
|
|
105
|
+
for property: Dictionary in ProjectSettings.get_property_list():
|
|
106
|
+
var setting: String = str(property.get("name", ""))
|
|
107
|
+
var value: String = (
|
|
108
|
+
str(ProjectSettings.get_setting(setting)) if setting.begins_with("autoload/") else ""
|
|
109
|
+
)
|
|
110
|
+
if value.begins_with("*"):
|
|
111
|
+
global_autoloads[setting.trim_prefix("autoload/")] = value.trim_prefix("*")
|
|
112
|
+
var autoloads_used: Array[Dictionary] = []
|
|
113
|
+
for used: String in GdhDependencies.classes_used_in(content, global_autoloads):
|
|
114
|
+
if str(global_autoloads[used]) != full_script_path:
|
|
115
|
+
autoloads_used.append({"name": used, "path": str(global_autoloads[used])})
|
|
116
|
+
|
|
117
|
+
var answer: Dictionary = {
|
|
118
|
+
"path": script_path,
|
|
119
|
+
"full_path": full_script_path,
|
|
120
|
+
"class_name": own.declared_class_name,
|
|
121
|
+
"extends": extends_name,
|
|
122
|
+
"signals": own.signals,
|
|
123
|
+
"variables": own.variables,
|
|
124
|
+
"functions": functions,
|
|
125
|
+
"constants": own.constants,
|
|
126
|
+
"enums": own.enums,
|
|
127
|
+
"inner_classes": own.inner_classes,
|
|
128
|
+
"dependencies": dependencies,
|
|
129
|
+
"classes_used": classes_used,
|
|
130
|
+
"autoloads_used": autoloads_used,
|
|
131
|
+
"line_count": lines.size() - 1 if content.is_empty() or content.ends_with("\n") else lines.size()
|
|
132
|
+
}
|
|
133
|
+
if GdhRead.as_bool(params.get("include_inherited", false)):
|
|
134
|
+
_add_inherited(answer, [full_script_path])
|
|
135
|
+
return answer
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## What one class body declares: the script's own at the left margin, or an inner class's at the
|
|
139
|
+
## indent [param margin] its body is written at, read from line [param first] to the first line
|
|
140
|
+
## less indented than that. [param dependencies] collects what every line loads, bodies included.
|
|
141
|
+
func _members(
|
|
142
|
+
lines: PackedStringArray, first: int, margin: String, dependencies: Array[String]
|
|
143
|
+
) -> GdhMembers:
|
|
144
|
+
var own: GdhMembers = GdhMembers.new()
|
|
54
145
|
var in_multiline_string: bool = false
|
|
55
146
|
|
|
56
|
-
var i: int = -1
|
|
147
|
+
var i: int = first - 1
|
|
57
148
|
while i + 1 < lines.size():
|
|
58
149
|
i += 1
|
|
59
150
|
# The comment off first, so nothing downstream has to know about them. It was reaching the
|
|
@@ -72,6 +163,12 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
72
163
|
if stripped.is_empty():
|
|
73
164
|
continue
|
|
74
165
|
|
|
166
|
+
# A line back at a shallower indent closes an inner class's body, and is the outer body's
|
|
167
|
+
# to read.
|
|
168
|
+
if not raw.begins_with(margin):
|
|
169
|
+
own.last = i - 1
|
|
170
|
+
return own
|
|
171
|
+
|
|
75
172
|
# An odd count opens a string that runs on; an even one is a string that closed on the line
|
|
76
173
|
# it opened on. Toggling on any count lost `const HELP := """Usage"""`, and everything after
|
|
77
174
|
# it up to the next triple quote, with the state inverted from there to the end of the file.
|
|
@@ -79,10 +176,11 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
79
176
|
if _triple_quotes(stripped) % 2 == 1:
|
|
80
177
|
in_multiline_string = true
|
|
81
178
|
|
|
82
|
-
# Only a line at
|
|
179
|
+
# Only a line at this body's own margin declares one of its members. Deeper, it is a
|
|
83
180
|
# function's local or an inner class's member, and each was answered as the script's own:
|
|
84
181
|
# every `var` in every body, and an inner class's `extends` taken for the script's base.
|
|
85
|
-
|
|
182
|
+
var indented: String = raw.substr(margin.length())
|
|
183
|
+
if indented.begins_with(" ") or indented.begins_with("\t"):
|
|
86
184
|
_collect_dependencies(stripped, dependencies)
|
|
87
185
|
continue
|
|
88
186
|
|
|
@@ -113,86 +211,75 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
|
|
|
113
211
|
header = GdhPatterns.without_annotations(stripped)
|
|
114
212
|
var declaration: RegExMatch = GdhPatterns.declared_class(header)
|
|
115
213
|
if declaration != null:
|
|
116
|
-
declared_class_name = declaration.get_string(1)
|
|
214
|
+
own.declared_class_name = declaration.get_string(1)
|
|
117
215
|
# `class_name X extends Y` is also one line, and taking the rest of it as the name
|
|
118
216
|
# reported `Blade extends Node2D` as the class while `extends` kept its default of
|
|
119
217
|
# `RefCounted`: the name unusable and the base flatly wrong.
|
|
120
218
|
if not declaration.get_string(2).is_empty():
|
|
121
|
-
extends_name = declaration.get_string(2)
|
|
219
|
+
own.extends_name = declaration.get_string(2)
|
|
122
220
|
elif header.begins_with("extends "):
|
|
123
|
-
extends_name = header.substr(8).strip_edges()
|
|
221
|
+
own.extends_name = header.substr(8).strip_edges()
|
|
124
222
|
elif header.begins_with("signal "):
|
|
125
223
|
# `at`, not `i`: a wrapped declaration starts where it starts, and `i` is now sitting on
|
|
126
224
|
# the line that closed it.
|
|
127
|
-
signals.append(_parse_signal(header, at + 1))
|
|
225
|
+
own.signals.append(_parse_signal(header, at + 1))
|
|
128
226
|
elif header.begins_with("const "):
|
|
129
|
-
constants.append(_parse_constant(header, at + 1))
|
|
227
|
+
own.constants.append(_parse_constant(header, at + 1))
|
|
130
228
|
elif header.begins_with("enum "):
|
|
131
|
-
enums.append(_parse_enum(header, at + 1))
|
|
132
|
-
elif header.begins_with("var "):
|
|
229
|
+
own.enums.append(_parse_enum(header, at + 1))
|
|
230
|
+
elif header.begins_with("var ") or header.begins_with("static var "):
|
|
133
231
|
# The whole line here, not the header: which annotations a variable carries is the
|
|
134
232
|
# answer rather than noise in front of it, and `@export_range(0, 1)` is the hint.
|
|
135
|
-
variables.append(_parse_variable(stripped, at + 1))
|
|
233
|
+
own.variables.append(_parse_variable(stripped, at + 1))
|
|
136
234
|
elif header.begins_with("func ") or header.begins_with("static func "):
|
|
137
|
-
functions.append(_parse_function(header, at + 1, stripped))
|
|
235
|
+
own.functions.append(_parse_function(header, at + 1, stripped))
|
|
138
236
|
elif header.begins_with("class "):
|
|
139
|
-
|
|
237
|
+
_collect_dependencies(stripped, dependencies)
|
|
238
|
+
var inner: GdhMembers = _inner_class(lines, i, margin, header, dependencies)
|
|
239
|
+
own.inner_classes.append(inner.as_inner())
|
|
240
|
+
i = inner.last
|
|
241
|
+
continue
|
|
140
242
|
|
|
141
243
|
_collect_dependencies(stripped, dependencies)
|
|
142
244
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
var
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
245
|
+
own.last = lines.size() - 1
|
|
246
|
+
return own
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
## The inner class whose `class` line is [param at], [param header] with its annotations and
|
|
250
|
+
## comment off, read with the members its body declares. Answered as a name alone, its members
|
|
251
|
+
## were in no answer at all.
|
|
252
|
+
func _inner_class(
|
|
253
|
+
lines: PackedStringArray, at: int, margin: String, header: String, dependencies: Array[String]
|
|
254
|
+
) -> GdhMembers:
|
|
255
|
+
var signature: String = _up_to(header.substr(6), ":").strip_edges()
|
|
256
|
+
var based: String = ""
|
|
257
|
+
if " extends " in signature:
|
|
258
|
+
based = signature.get_slice(" extends ", 1).strip_edges()
|
|
259
|
+
# The body is at the indent of its first line, whatever this file indents with.
|
|
260
|
+
var body_margin: String = ""
|
|
261
|
+
for next: int in range(at + 1, lines.size()):
|
|
262
|
+
if GdhPatterns.without_comment(lines[next]).is_empty():
|
|
157
263
|
continue
|
|
158
|
-
|
|
159
|
-
if
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
var answer: Dictionary = {
|
|
178
|
-
"path": script_path,
|
|
179
|
-
"full_path": full_script_path,
|
|
180
|
-
"class_name": declared_class_name,
|
|
181
|
-
"extends": extends_name,
|
|
182
|
-
"signals": signals,
|
|
183
|
-
"variables": variables,
|
|
184
|
-
"functions": functions,
|
|
185
|
-
"constants": constants,
|
|
186
|
-
"enums": enums,
|
|
187
|
-
"inner_classes": inner_classes,
|
|
188
|
-
"dependencies": dependencies,
|
|
189
|
-
"classes_used": classes_used,
|
|
190
|
-
"autoloads_used": autoloads_used,
|
|
191
|
-
"line_count": lines.size() - 1 if content.is_empty() or content.ends_with("\n") else lines.size()
|
|
192
|
-
}
|
|
193
|
-
if GdhRead.as_bool(params.get("include_inherited", false)):
|
|
194
|
-
_add_inherited(answer, [full_script_path])
|
|
195
|
-
return answer
|
|
264
|
+
var indent: String = _indentation(lines[next])
|
|
265
|
+
if indent.length() > margin.length() and lines[next].begins_with(margin):
|
|
266
|
+
body_margin = indent
|
|
267
|
+
break
|
|
268
|
+
var body: GdhMembers = (
|
|
269
|
+
GdhMembers.new() if body_margin.is_empty() else _members(lines, at + 1, body_margin, dependencies)
|
|
270
|
+
)
|
|
271
|
+
if body_margin.is_empty():
|
|
272
|
+
body.last = at
|
|
273
|
+
if not based.is_empty():
|
|
274
|
+
body.extends_name = based
|
|
275
|
+
body.inner_name = signature.get_slice(" ", 0)
|
|
276
|
+
body.line = at + 1
|
|
277
|
+
_mark_virtuals(body.functions, body.extends_name, _reading)
|
|
278
|
+
return body
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
static func _indentation(line: String) -> String:
|
|
282
|
+
return line.substr(0, line.length() - line.strip_edges(true, false).length())
|
|
196
283
|
|
|
197
284
|
|
|
198
285
|
# Which of these functions the engine will call, asked of the engine.
|
|
@@ -340,7 +427,67 @@ func _parse_constant(line: String, line_num: int) -> Dictionary:
|
|
|
340
427
|
else:
|
|
341
428
|
name = const_text
|
|
342
429
|
|
|
343
|
-
|
|
430
|
+
var constant: Dictionary = {"name": name, "value": value, "type": type_hint, "line": line_num}
|
|
431
|
+
# A constant has its value's type whether or not it is written with :=.
|
|
432
|
+
if type_hint.is_empty():
|
|
433
|
+
_infer(constant, value)
|
|
434
|
+
return constant
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
## Gives [param declared] the type GDScript infers from [param value], marked inferred, when the
|
|
438
|
+
## value is a literal or a built-in type's constructor: `const MAX_STACK := 99` read as having no
|
|
439
|
+
## type though the engine holds an int. Any other expression is left without one, since only the
|
|
440
|
+
## engine's analyser knows what it gives.
|
|
441
|
+
static func _infer(declared: Dictionary, value: String) -> void:
|
|
442
|
+
# A variable with a setter or getter ends its line with the colon that opens them.
|
|
443
|
+
var text: String = value.strip_edges().trim_suffix(":").strip_edges()
|
|
444
|
+
var type: String = ""
|
|
445
|
+
if text == "true" or text == "false":
|
|
446
|
+
type = "bool"
|
|
447
|
+
elif text.is_valid_int() or text.is_valid_hex_number(true):
|
|
448
|
+
type = "int"
|
|
449
|
+
elif text.is_valid_float():
|
|
450
|
+
type = "float"
|
|
451
|
+
elif text.begins_with('&"') and text.ends_with('"'):
|
|
452
|
+
type = "StringName"
|
|
453
|
+
elif text.begins_with('^"') and text.ends_with('"'):
|
|
454
|
+
type = "NodePath"
|
|
455
|
+
elif text.length() > 1 and text[0] in ['"', "'"] and text.ends_with(text[0]):
|
|
456
|
+
type = "String"
|
|
457
|
+
elif _whole(text, "[", "]"):
|
|
458
|
+
type = "Array"
|
|
459
|
+
elif _whole(text, "{", "}"):
|
|
460
|
+
type = "Dictionary"
|
|
461
|
+
else:
|
|
462
|
+
var called: String = text.get_slice("(", 0).strip_edges()
|
|
463
|
+
if _whole(text.substr(called.length()), "(", ")") and _built_in_types().has(called):
|
|
464
|
+
type = called
|
|
465
|
+
if not type.is_empty():
|
|
466
|
+
declared["type"] = type
|
|
467
|
+
declared["inferred"] = true
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
## Whether [param text] is one bracketed whole, its first [param opening] closed by its last
|
|
471
|
+
## character, so `[1, 2]` is a list and `[1, 2].size()` is not.
|
|
472
|
+
static func _whole(text: String, opening: String, closing: String) -> bool:
|
|
473
|
+
if not text.begins_with(opening) or not text.ends_with(closing):
|
|
474
|
+
return false
|
|
475
|
+
var depth: int = 0
|
|
476
|
+
for index: int in text.length():
|
|
477
|
+
if text[index] == opening:
|
|
478
|
+
depth += 1
|
|
479
|
+
elif text[index] == closing:
|
|
480
|
+
depth -= 1
|
|
481
|
+
if depth == 0:
|
|
482
|
+
return index == text.length() - 1
|
|
483
|
+
return false
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
static func _built_in_types() -> Dictionary:
|
|
487
|
+
var names: Dictionary = {}
|
|
488
|
+
for type: int in TYPE_MAX:
|
|
489
|
+
names[type_string(type)] = true
|
|
490
|
+
return names
|
|
344
491
|
|
|
345
492
|
|
|
346
493
|
func _parse_enum(line: String, line_num: int) -> Dictionary:
|
|
@@ -412,15 +559,22 @@ func _parse_variable(line: String, line_num: int) -> Dictionary:
|
|
|
412
559
|
if "=" in var_text:
|
|
413
560
|
stated_default = default_value
|
|
414
561
|
|
|
415
|
-
|
|
562
|
+
var variable: Dictionary = {
|
|
416
563
|
"name": name,
|
|
417
564
|
"type": type_hint,
|
|
418
565
|
"default_value": stated_default,
|
|
419
566
|
"is_export": is_export,
|
|
420
567
|
"export_hint": export_hint,
|
|
421
568
|
"is_onready": is_onready,
|
|
569
|
+
# One per class rather than per instance, which a caller reading a counter needs to know.
|
|
570
|
+
"is_static": GdhPatterns.without_annotations(line).begins_with("static "),
|
|
422
571
|
"line": line_num
|
|
423
572
|
}
|
|
573
|
+
# Only := types a variable by its value; one written with = alone holds any type.
|
|
574
|
+
var walrus: bool = "=" in var_text and var_text.split("=", true, 1)[0].strip_edges().ends_with(":")
|
|
575
|
+
if walrus and type_hint.is_empty():
|
|
576
|
+
_infer(variable, default_value)
|
|
577
|
+
return variable
|
|
424
578
|
|
|
425
579
|
|
|
426
580
|
func _parse_function(line: String, line_num: int, annotated: String = "") -> Dictionary:
|