Skip to content

Commit d165163

Browse files
committed
Fix a bunch of things
* add template literals * fix a bug where two generated functions would have the same name * add a header to all generated files * properly type load() and preload() PackedScenes * fix a bug where object literals wouldnt have a trailing newline
1 parent 8077fe8 commit d165163

16 files changed

+298
-119
lines changed

README.md

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
Why use ts2gd?
44

5-
* Incrementally compiles to GDScript in under a tenth of a second.
6-
* Provides insanely good autocomplete and documentation.
7-
* Use all of TS's extremely powerful type system.
5+
- Incrementally compiles to GDScript in under a tenth of a second.
6+
- Provides insanely good autocomplete and documentation.
7+
- Use all of TS's extremely powerful type system.
88

99
## Install:
1010

@@ -37,6 +37,18 @@ Now, run the compiler on tsgd.json:
3737

3838
## Details and Differences
3939

40+
### load/preload
41+
42+
Sure, you _could_ do preload("YourScriptFile.tscn)... but why would you? ts2gd automatically creates globals for your scenes that you can
43+
import directly. e.g. if you want to instance, "YourScriptFile.tscn", just type YourScriptFileTscn and allow TS to auto-import it. You can
44+
then instance() it as normal. e.g., this:
45+
46+
`const new_obj = preload("res://MyScene.tscn).instance()`
47+
48+
is equivalent to this:
49+
50+
`const new_obj = MySceneTscn.instance()`
51+
4052
### Enums
4153

4254
Godot decides to put a bunch of enum values into global scope. I think this clutters things up: the global scope has tons of mostly useless enum values in it, and it's impossible to tell what property belongs to which enum. So we move them into `EnumName.PropertyName` instead. This is extra nice because now if you type `EnumName` you get autocomplete of all the types in that Enum.

generate_library_defs/generate_library.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ ${Object.keys(enums)
483483
// let fileName = 'KinematicBody2D.xml';
484484
// const result = await parseFile(godotDocumentationPath + fileName);
485485

486-
// console.log(result);
486+
// console.info(result);
487487
// }
488488

489489
await main()

main.ts

+29-25
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
11
// VERY USEFUL
22

3-
// TODO: Windows build.
3+
// [ ]: strongly typed RPCs
44

5-
// TODO: This doesnt work:
6-
// enem.connect("on_die", () => { this.enemies.erase(enem) });
7-
// b/c it captures "this.enemies"
5+
// [ ]: Windows build.
6+
// [ ]: Could gitignore compiled files?
7+
// [ ]: Yield autocompletion stopped working
88

9-
// TODO: Import constants from other files.
9+
// [ ]: Parse input actions
10+
11+
// [ ]: Add a test to make sure that signals on classes typecheck
12+
13+
// [ ]: Import constants from other files.
1014
// - we'd have to extract these into a standard global autoload class, and point all references to constants to that global autoload.
1115

12-
// TODO:
16+
// [ ]:
1317
// print([1, 3, 2, 4].sort_custom((a, b) => a - b))
14-
// This also doesn't work ^
18+
// This also doesn't work ^
1519

1620
// USEFUL
1721

18-
// TODO: Handle string interpolation
19-
// TODO: Have a github action that auto publishes an html5 build
20-
// TODO: https://gist.github.com/tmaybe/4c9d94712711229cd506 use this strategy to avoid conflicts in the /compiled folder
21-
// TODO make load/preload() work and return proper string
22-
// TODO make FooTscn return proper type of root node (without script, not just Node)
23-
// TODO: Merge conflict markers in project.godot cause a ts2gd crash.
24-
// TODO: Better print() output, with spacing
25-
// TODO: check for E_OK
26-
// TODO: Deleting a scene can cause a "I dont know the type of that thing." error.
27-
// TODO: Every file should export something - show an error otherwise
22+
// [x] Handle string interpolation
23+
// [ ]: Have a github action that auto publishes an html5 build
24+
// [ ]: https://gist.github.com/tmaybe/4c9d94712711229cd506 use this strategy to avoid conflicts in the /compiled folder
25+
// [ ] make FooTscn return proper type of root node (without script, not just Node)
26+
// [ ]: Merge conflict markers in project.godot cause a ts2gd crash.
27+
// [x] make load/preload() work and return proper string
28+
// [x]: Better print() output, with spacing (there is prints, nevermind)
29+
// [ ]: check for E_OK
30+
// [ ]: Deleting a scene can cause a "I dont know the type of that thing." error.
31+
// [ ]: Every file should export something - show an error otherwise
2832

29-
// TODO: Ensure that there aren't any bugs with _ prefixes.
33+
// [ ]: Ensure that there aren't any bugs with _ prefixes.
3034

3135
// HIGH
3236

33-
// TODO: Rename ParsedArgs to ParsedFlags
34-
// TODO: It would be extremely useful for some things - like the project settings and ParsedArgs - to be singletons.
37+
// [ ]: Rename ParsedArgs to ParsedFlags
38+
// [ ]: It would be extremely useful for some things - like the project settings and ParsedArgs - to be singletons.
3539

36-
// TODO: It might be handy to keep ParseNodeTypes around for subnodes etc and return an entire tree of them. this would help code in parse_call_express that wants to inspect child nodes to see what they are etc
40+
// [ ]: It might be handy to keep ParseNodeTypes around for subnodes etc and return an entire tree of them. this would help code in parse_call_express that wants to inspect child nodes to see what they are etc
3741

38-
// TODO: Refactor error handling strategy.
42+
// [x]: Refactor error handling strategy.
3943
// TODO: change_scene_to takes a PackedScene but since it's a <T> it's treated as an any.
4044
// TODO: Make a testing harness for project-related stuff.
4145
// TODO: I need to abstract over the TS and chokidar file watcher interface thingy.
@@ -46,7 +50,7 @@
4650
// TODO: Taking in funcrefs and calling them.
4751
// specifically for mapping over my 2d board.
4852
// TODO: new assets aren't immediately imported.
49-
// TODO: There are bugs when you have both a constructor and an _ready() method.
53+
// TODO: There are bugs when you have both a constructor and a _ready() method.
5054
// TODO: Inline gdscript
5155
// TODO: Resolve node paths even through instances.
5256

@@ -154,11 +158,11 @@ const setup = (tsgdJson: Paths) => {
154158

155159
const host = ts.createWatchCompilerHost(
156160
tsgdJson.tsconfigPath,
157-
{ },
161+
{},
158162
ts.sys,
159163
ts.createEmitAndSemanticDiagnosticsBuilderProgram,
160164
reportDiagnostic,
161-
reportWatchStatusChanged,
165+
reportWatchStatusChanged
162166
)
163167
watchProgram = ts.createWatchProgram(host)
164168
const configFile = ts.readJsonConfigFile(

parse_node.ts

+23-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { parseSourceFile } from "./parse_node/parse_source_file"
66
import {
77
generatePrecedingNewlines,
88
syntaxKindToString as kindToString,
9+
syntaxKindToString,
910
} from "./ts_utils"
1011
import { parseTypeReference } from "./parse_node/parse_type_reference"
1112
import { parseNumericLiteral } from "./parse_node/parse_numeric_literal"
@@ -43,7 +44,10 @@ import { parseYieldExpression } from "./parse_node/parse_yield_expression"
4344
import { parseReturnStatement } from "./parse_node/parse_return_statement"
4445
import { parseBreakStatement } from "./parse_node/parse_break_statement"
4546
import { parseBlock } from "./parse_node/parse_block"
46-
import { LibraryFunctionName, parseCallExpression } from "./parse_node/parse_call_expression"
47+
import {
48+
LibraryFunctionName,
49+
parseCallExpression,
50+
} from "./parse_node/parse_call_expression"
4751
import { parseVariableStatement } from "./parse_node/parse_variable_statement"
4852
import { parseSetAccessor } from "./parse_node/parse_set_accessor"
4953
import { parseGetAccessor } from "./parse_node/parse_get_accessor"
@@ -58,6 +62,7 @@ import { parseTypeofExpression } from "./parse_node/parse_typeof_expression"
5862
import { TsGdProjectClass } from "./project/project"
5963
import { Scope } from "./scope"
6064
import { ErrorName, TsGdError } from "./errors"
65+
import { parseTemplateExpression } from "./parse_node/parse_template_expression"
6166

6267
export type ParseState = {
6368
isConstructor: boolean
@@ -129,8 +134,10 @@ export function combine(args: {
129134
}): ParseNodeType {
130135
let { parent, nodes, props, parsedStrings, parsedObjs, addIndent } = args
131136

132-
if (!parsedStrings && !parsedObjs) {
133-
throw new Error("Need at least one of parsedStrings or parsedObjs")
137+
if ((!parsedStrings && !parsedObjs) || (parsedStrings && parsedObjs)) {
138+
throw new Error(
139+
"Need at least one of parsedStrings or parsedObjs, but not both."
140+
)
134141
}
135142

136143
if (Array.isArray(nodes)) {
@@ -174,7 +181,7 @@ export function combine(args: {
174181
})
175182

176183
for (const parsedNode of parsedNodes) {
177-
const { node, content, enums, extraLines } = parsedNode
184+
const { node, content, extraLines } = parsedNode
178185

179186
if (!node) {
180187
continue
@@ -223,7 +230,7 @@ export function combine(args: {
223230
}
224231
}
225232

226-
if (isStandAloneLine && lines.length === 1) {
233+
if (isStandAloneLine) {
227234
formattedContent = formattedContent + "\n"
228235
}
229236

@@ -250,9 +257,11 @@ export function combine(args: {
250257
hoistedEnumImports: parsedNodes.flatMap(
251258
(node) => node.hoistedEnumImports ?? []
252259
),
253-
hoistedLibraryFunctions: new Set(parsedNodes.flatMap(
254-
(node) => [...(node.hoistedLibraryFunctions?.keys() ?? [])]
255-
)),
260+
hoistedLibraryFunctions: new Set(
261+
parsedNodes.flatMap((node) => [
262+
...(node.hoistedLibraryFunctions?.keys() ?? []),
263+
])
264+
),
256265
hoistedArrowFunctions: parsedNodes.flatMap(
257266
(node) => node.hoistedArrowFunctions ?? []
258267
),
@@ -355,6 +364,11 @@ export const parseNode = (
355364
return parseReturnStatement(genericNode as ts.ReturnStatement, props)
356365
case SyntaxKind.StringLiteral:
357366
return parseStringLiteral(genericNode as ts.StringLiteral, props)
367+
case SyntaxKind.TemplateExpression:
368+
return parseTemplateExpression(
369+
genericNode as ts.TemplateExpression,
370+
props
371+
)
358372
case SyntaxKind.BreakStatement:
359373
return parseBreakStatement(genericNode as ts.BreakStatement, props)
360374
case SyntaxKind.ContinueStatement:
@@ -478,6 +492,7 @@ export const parseNode = (
478492
return { content: "null" }
479493

480494
default:
495+
console.error(syntaxKindToString(genericNode.kind))
481496
props.addError({
482497
error: ErrorName.UnknownTsSyntax,
483498
location: genericNode,

parse_node/parse_arrow_function.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ const getFreeVariables = (
2727
node.kind === SyntaxKind.Identifier ||
2828
node.kind === SyntaxKind.PropertyAccessExpression
2929
) {
30-
if (node.kind === SyntaxKind.PropertyAccessExpression) {
31-
// In cases like "a.b.c", only return "a".
30+
// In cases like "a.b.c", only return "a".
31+
while (node.kind === SyntaxKind.PropertyAccessExpression) {
3232
const pae = node as ts.PropertyAccessExpression
3333
node = pae.expression
3434
}
@@ -83,23 +83,30 @@ export const getCapturedScope = (
8383
const uniqueFreeVariables = freeVariables.filter(
8484
(item, index) => freeVariables.indexOf(item) === index
8585
)
86-
8786
// We don't want to capture `this` as part of our scope. There's no reason to
8887
// do it: lambdas are only ever executed in the current class, so `this` will
8988
// never be different. Plus, we'd have to rewrite all `this` access in the
9089
// function to be `_self`, which would be confusing, and look stupid, and
9190
// basically be a completely pointless workaround.
92-
const freeVariablesWithoutThis = uniqueFreeVariables.filter(v => v.getText() !== "this");
91+
const freeVariablesWithoutThis = uniqueFreeVariables.filter(
92+
(v) => v.getText() !== "this"
93+
)
94+
95+
const getNodeName = (node: ts.Node) => {
96+
const text = node.getText()
97+
98+
return text
99+
}
93100

94101
const capturedScopeObject =
95102
"{" +
96103
freeVariablesWithoutThis
97-
.map((freeVar) => `"${freeVar.getText()}": ${freeVar.getText()}`)
104+
.map((freeVar) => `"${getNodeName(freeVar)}": ${getNodeName(freeVar)}`)
98105
.join(", ") +
99106
"}"
100107

101108
const unwrapCapturedScope = freeVariablesWithoutThis
102-
.map((v) => ` var ${v.getText()} = captures.${v.getText()}\n`)
109+
.map((v) => ` var ${getNodeName(v)} = captures.${getNodeName(v)}\n`)
103110
.join("")
104111

105112
return {

parse_node/parse_block.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const parseBlock = (
1111
parent: node,
1212
nodes: node.statements,
1313
props,
14-
parsedStrings: (...parsed) => parsed.join(""),
14+
parsedStrings: (...parsed) => {
15+
return parsed.join("")
16+
},
1517
})
1618
} else {
1719
return combine({

0 commit comments

Comments
 (0)