@defold-typescript/transpiler 0.39.0 → 0.41.0
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/dist/index.d.ts +2 -0
- package/dist/index.js +190 -80
- package/dist/lifecycle-erasure.d.ts +7 -0
- package/dist/text-script-property.d.ts +16 -0
- package/dist/type-application-erasure.d.ts +3 -0
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/lifecycle-erasure.ts +51 -22
- package/src/program-diagnostics.ts +2 -0
- package/src/session.ts +2 -0
- package/src/text-script-property.ts +100 -0
- package/src/transpile.ts +2 -0
- package/src/type-application-erasure.ts +34 -0
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type { SceneMessage } from "./scene-text-format";
|
|
|
32
32
|
export { parseSceneTextFormat, SceneTextFormatError } from "./scene-text-format";
|
|
33
33
|
export type { TranspileSession } from "./session";
|
|
34
34
|
export { createTranspileSession } from "./session";
|
|
35
|
+
export type { TextScriptPropertyFinding } from "./text-script-property";
|
|
36
|
+
export { findTextScriptProperties } from "./text-script-property";
|
|
35
37
|
export { TIMERS_REQUIRE_NAME } from "./timers-runtime";
|
|
36
38
|
export type { TranspileDiagnostic, TranspileProjectInput, TranspileProjectResult, TranspileResult, } from "./transpile";
|
|
37
39
|
export { transpile, transpileProject } from "./transpile";
|
package/dist/index.js
CHANGED
|
@@ -270,11 +270,11 @@ function emitInitMerge(fn, context, property) {
|
|
|
270
270
|
const initFn = createFunctionExpression2(createBlock2([stateDecl, guard]), [createIdentifier2("self")], undefined, NodeFlags2.Declaration, fn);
|
|
271
271
|
return [builderDecl, createAssignmentStatement(createIdentifier2("init"), initFn, property)];
|
|
272
272
|
}
|
|
273
|
-
function
|
|
273
|
+
function propertyMembers(property) {
|
|
274
274
|
if (!ts2.isPropertyAssignment(property) || !ts2.isObjectLiteralExpression(property.initializer)) {
|
|
275
275
|
return [];
|
|
276
276
|
}
|
|
277
|
-
const
|
|
277
|
+
const members = [];
|
|
278
278
|
for (const member of property.initializer.properties) {
|
|
279
279
|
if (!ts2.isPropertyAssignment(member)) {
|
|
280
280
|
continue;
|
|
@@ -283,21 +283,34 @@ function emitPropertyRegistrations(property, context) {
|
|
|
283
283
|
if (!ts2.isIdentifier(key) && !ts2.isStringLiteral(key)) {
|
|
284
284
|
continue;
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
members.push({ name: key.text, initializer: member.initializer });
|
|
287
287
|
}
|
|
288
|
-
return
|
|
288
|
+
return members;
|
|
289
289
|
}
|
|
290
|
-
function
|
|
290
|
+
function factoryHooks(expression, checker) {
|
|
291
291
|
if (!ts2.isCallExpression(expression)) {
|
|
292
292
|
return;
|
|
293
293
|
}
|
|
294
|
-
if (!resolvesToFactoryExport(expression.expression,
|
|
294
|
+
if (!resolvesToFactoryExport(expression.expression, checker)) {
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
297
297
|
const hooks = expression.arguments[0];
|
|
298
298
|
if (hooks === undefined || !ts2.isObjectLiteralExpression(hooks)) {
|
|
299
299
|
return;
|
|
300
300
|
}
|
|
301
|
+
return hooks;
|
|
302
|
+
}
|
|
303
|
+
function isPropertiesHook(property) {
|
|
304
|
+
return hookName(property) === "properties";
|
|
305
|
+
}
|
|
306
|
+
function emitPropertyRegistrations(property, context) {
|
|
307
|
+
return propertyMembers(property).map(({ name, initializer }) => createExpressionStatement(createCallExpression2(createTableIndexExpression(createIdentifier2("go"), createStringLiteral2("property")), [createStringLiteral2(name), context.transformExpression(initializer)])));
|
|
308
|
+
}
|
|
309
|
+
function eraseFactoryCall(expression, context) {
|
|
310
|
+
const hooks = factoryHooks(expression, context.checker);
|
|
311
|
+
if (hooks === undefined) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
301
314
|
const registrations = [];
|
|
302
315
|
const statements = [];
|
|
303
316
|
for (const property of hooks.properties) {
|
|
@@ -2058,7 +2071,7 @@ function computeOutputRel(rel, config, kind) {
|
|
|
2058
2071
|
return baseRel.replace(/\.ts$/, SCRIPT_SUFFIX_BY_KIND[kind]);
|
|
2059
2072
|
}
|
|
2060
2073
|
// src/program-diagnostics.ts
|
|
2061
|
-
import * as
|
|
2074
|
+
import * as ts9 from "typescript";
|
|
2062
2075
|
import * as tstl from "typescript-to-lua";
|
|
2063
2076
|
|
|
2064
2077
|
// src/message-guard-lowering.ts
|
|
@@ -2126,11 +2139,41 @@ var timersLoweringPlugin = {
|
|
|
2126
2139
|
}
|
|
2127
2140
|
};
|
|
2128
2141
|
|
|
2129
|
-
// src/
|
|
2142
|
+
// src/type-application-erasure.ts
|
|
2130
2143
|
import * as ts7 from "typescript";
|
|
2144
|
+
var TYPE_APPLICATION_APIS = new Set(["go.get", "go.set"]);
|
|
2145
|
+
function isTypeApplication(call, checker) {
|
|
2146
|
+
if (call.arguments.length !== 0)
|
|
2147
|
+
return false;
|
|
2148
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
2149
|
+
if (declaration === undefined || !ts7.isFunctionDeclaration(declaration))
|
|
2150
|
+
return false;
|
|
2151
|
+
if (declaration.parameters.length !== 0 || declaration.typeParameters === undefined)
|
|
2152
|
+
return false;
|
|
2153
|
+
if (declaration.name === undefined)
|
|
2154
|
+
return false;
|
|
2155
|
+
const symbol = checker.getSymbolAtLocation(declaration.name);
|
|
2156
|
+
if (symbol === undefined)
|
|
2157
|
+
return false;
|
|
2158
|
+
const fqn = checker.getFullyQualifiedName(symbol);
|
|
2159
|
+
return fqn.startsWith("global.") && TYPE_APPLICATION_APIS.has(fqn.slice("global.".length));
|
|
2160
|
+
}
|
|
2161
|
+
var typeApplicationErasurePlugin = {
|
|
2162
|
+
visitors: {
|
|
2163
|
+
[ts7.SyntaxKind.CallExpression]: (node, context) => {
|
|
2164
|
+
if (isTypeApplication(node, context.checker)) {
|
|
2165
|
+
return context.transformExpression(node.expression);
|
|
2166
|
+
}
|
|
2167
|
+
return context.superTransformExpression(node);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
|
|
2172
|
+
// src/window-event-guard-lowering.ts
|
|
2173
|
+
import * as ts8 from "typescript";
|
|
2131
2174
|
import {
|
|
2132
2175
|
createBinaryExpression as createBinaryExpression4,
|
|
2133
|
-
SyntaxKind as
|
|
2176
|
+
SyntaxKind as SyntaxKind11
|
|
2134
2177
|
} from "typescript-to-lua";
|
|
2135
2178
|
var GUARD_MODULE2 = "@defold-typescript/types";
|
|
2136
2179
|
var GUARD_NAME2 = "isWindowEvent";
|
|
@@ -2139,7 +2182,7 @@ function resolvesToGuardExport2(callee, checker) {
|
|
|
2139
2182
|
if (symbol === undefined) {
|
|
2140
2183
|
return false;
|
|
2141
2184
|
}
|
|
2142
|
-
if (symbol.flags &
|
|
2185
|
+
if (symbol.flags & ts8.SymbolFlags.Alias) {
|
|
2143
2186
|
symbol = checker.getAliasedSymbol(symbol);
|
|
2144
2187
|
}
|
|
2145
2188
|
if (symbol.getName() !== GUARD_NAME2) {
|
|
@@ -2153,12 +2196,12 @@ function resolvesToGuardExport2(callee, checker) {
|
|
|
2153
2196
|
}
|
|
2154
2197
|
var windowEventGuardLoweringPlugin = {
|
|
2155
2198
|
visitors: {
|
|
2156
|
-
[
|
|
2199
|
+
[ts8.SyntaxKind.CallExpression]: (node, context) => {
|
|
2157
2200
|
const [eventArg, , expectedArg] = node.arguments;
|
|
2158
2201
|
if (node.arguments.length === 3 && eventArg !== undefined && expectedArg !== undefined && resolvesToGuardExport2(node.expression, context.checker)) {
|
|
2159
2202
|
const event = context.transformExpression(eventArg);
|
|
2160
2203
|
const expected = context.transformExpression(expectedArg);
|
|
2161
|
-
return createBinaryExpression4(event, expected,
|
|
2204
|
+
return createBinaryExpression4(event, expected, SyntaxKind11.EqualityOperator, node);
|
|
2162
2205
|
}
|
|
2163
2206
|
return context.superTransformExpression(node);
|
|
2164
2207
|
}
|
|
@@ -2171,14 +2214,15 @@ var LUA_PLUGINS = [
|
|
|
2171
2214
|
messageGuardLoweringPlugin,
|
|
2172
2215
|
windowEventGuardLoweringPlugin,
|
|
2173
2216
|
messageDispatchLoweringPlugin,
|
|
2174
|
-
timersLoweringPlugin
|
|
2217
|
+
timersLoweringPlugin,
|
|
2218
|
+
typeApplicationErasurePlugin
|
|
2175
2219
|
];
|
|
2176
2220
|
var noopWriteFile = () => {};
|
|
2177
2221
|
function isAmbient(fileName) {
|
|
2178
2222
|
return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
|
|
2179
2223
|
}
|
|
2180
2224
|
function getProgramDiagnostics(program, sourceFile) {
|
|
2181
|
-
const { diagnostics } = tstl.getProgramTranspileResult(
|
|
2225
|
+
const { diagnostics } = tstl.getProgramTranspileResult(ts9.sys, noopWriteFile, {
|
|
2182
2226
|
program,
|
|
2183
2227
|
plugins: LUA_PLUGINS,
|
|
2184
2228
|
...sourceFile ? { sourceFiles: [sourceFile] } : {}
|
|
@@ -2749,11 +2793,11 @@ function relativeAddressesFrom(index, context) {
|
|
|
2749
2793
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
2750
2794
|
import { createRequire as createRequire2 } from "node:module";
|
|
2751
2795
|
import * as path3 from "node:path";
|
|
2752
|
-
import * as
|
|
2796
|
+
import * as ts13 from "typescript";
|
|
2753
2797
|
import * as tstl3 from "typescript-to-lua";
|
|
2754
2798
|
|
|
2755
2799
|
// src/companion-emit.ts
|
|
2756
|
-
import * as
|
|
2800
|
+
import * as ts10 from "typescript";
|
|
2757
2801
|
import {
|
|
2758
2802
|
createAssignmentStatement as createAssignmentStatement2,
|
|
2759
2803
|
createCallExpression as createCallExpression4,
|
|
@@ -2787,8 +2831,8 @@ function exportsIdentifier() {
|
|
|
2787
2831
|
}
|
|
2788
2832
|
function isRuntimeScriptSource(sourceFile, checker) {
|
|
2789
2833
|
return sourceFile.statements.some((statement) => {
|
|
2790
|
-
const expression =
|
|
2791
|
-
return expression !== undefined &&
|
|
2834
|
+
const expression = ts10.isExpressionStatement(statement) ? statement.expression : ts10.isExportAssignment(statement) && !statement.isExportEquals ? statement.expression : undefined;
|
|
2835
|
+
return expression !== undefined && ts10.isCallExpression(expression) && resolvesToFactoryExport(expression.expression, checker);
|
|
2792
2836
|
});
|
|
2793
2837
|
}
|
|
2794
2838
|
function planFor(sourceFile, checker) {
|
|
@@ -2940,11 +2984,11 @@ function freeNames(statements) {
|
|
|
2940
2984
|
function resolvedRequirePaths(sourceFile, checker) {
|
|
2941
2985
|
const paths = new Map;
|
|
2942
2986
|
for (const statement of sourceFile.statements) {
|
|
2943
|
-
const specifier =
|
|
2944
|
-
if (specifier === undefined || !
|
|
2987
|
+
const specifier = ts10.isImportDeclaration(statement) || ts10.isExportDeclaration(statement) ? statement.moduleSpecifier : undefined;
|
|
2988
|
+
if (specifier === undefined || !ts10.isStringLiteral(specifier)) {
|
|
2945
2989
|
continue;
|
|
2946
2990
|
}
|
|
2947
|
-
const target = checker.getSymbolAtLocation(specifier)?.declarations?.find((declaration) =>
|
|
2991
|
+
const target = checker.getSymbolAtLocation(specifier)?.declarations?.find((declaration) => ts10.isSourceFile(declaration));
|
|
2948
2992
|
if (target === undefined || target.isDeclarationFile) {
|
|
2949
2993
|
continue;
|
|
2950
2994
|
}
|
|
@@ -3108,11 +3152,11 @@ function createCompanionEmitPlugin() {
|
|
|
3108
3152
|
return statements;
|
|
3109
3153
|
}
|
|
3110
3154
|
const visitors = {
|
|
3111
|
-
[
|
|
3112
|
-
[
|
|
3113
|
-
[
|
|
3114
|
-
[
|
|
3115
|
-
[
|
|
3155
|
+
[ts10.SyntaxKind.VariableStatement]: capture,
|
|
3156
|
+
[ts10.SyntaxKind.FunctionDeclaration]: capture,
|
|
3157
|
+
[ts10.SyntaxKind.ClassDeclaration]: capture,
|
|
3158
|
+
[ts10.SyntaxKind.EnumDeclaration]: capture,
|
|
3159
|
+
[ts10.SyntaxKind.ExportDeclaration]: capture
|
|
3116
3160
|
};
|
|
3117
3161
|
let configuredNoResolvePaths = [];
|
|
3118
3162
|
const plugin = {
|
|
@@ -3181,7 +3225,7 @@ function emitCompanion(program, emitHost, file, sourceFile, plan, owned, compani
|
|
|
3181
3225
|
}
|
|
3182
3226
|
|
|
3183
3227
|
// src/editor-script-erasure.ts
|
|
3184
|
-
import * as
|
|
3228
|
+
import * as ts11 from "typescript";
|
|
3185
3229
|
import {
|
|
3186
3230
|
createReturnStatement as createReturnStatement3
|
|
3187
3231
|
} from "typescript-to-lua";
|
|
@@ -3201,7 +3245,7 @@ function resolveEditorFactoryExport(callee, checker) {
|
|
|
3201
3245
|
if (symbol === undefined) {
|
|
3202
3246
|
return;
|
|
3203
3247
|
}
|
|
3204
|
-
if (symbol.flags &
|
|
3248
|
+
if (symbol.flags & ts11.SymbolFlags.Alias) {
|
|
3205
3249
|
symbol = checker.getAliasedSymbol(symbol);
|
|
3206
3250
|
}
|
|
3207
3251
|
const name = symbol.getName();
|
|
@@ -3215,7 +3259,7 @@ function resolveEditorFactoryExport(callee, checker) {
|
|
|
3215
3259
|
return declaration.getSourceFile().fileName.includes(EDITOR_FACTORY_MODULE) ? name : undefined;
|
|
3216
3260
|
}
|
|
3217
3261
|
function lowerEditorFactoryCall(expression, context) {
|
|
3218
|
-
if (!
|
|
3262
|
+
if (!ts11.isCallExpression(expression)) {
|
|
3219
3263
|
return;
|
|
3220
3264
|
}
|
|
3221
3265
|
if (resolveEditorFactoryExport(expression.expression, context.checker) !== EDITOR_SCRIPT_FACTORY_NAME) {
|
|
@@ -3228,7 +3272,7 @@ function lowerEditorFactoryCall(expression, context) {
|
|
|
3228
3272
|
return [createReturnStatement3([context.transformExpression(module)])];
|
|
3229
3273
|
}
|
|
3230
3274
|
function isEditorFactoryOnlyImport(node) {
|
|
3231
|
-
if (!
|
|
3275
|
+
if (!ts11.isStringLiteral(node.moduleSpecifier) || !EDITOR_FACTORY_SPECIFIERS.has(node.moduleSpecifier.text)) {
|
|
3232
3276
|
return false;
|
|
3233
3277
|
}
|
|
3234
3278
|
const clause = node.importClause;
|
|
@@ -3239,7 +3283,7 @@ function isEditorFactoryOnlyImport(node) {
|
|
|
3239
3283
|
return false;
|
|
3240
3284
|
}
|
|
3241
3285
|
const bindings = clause.namedBindings;
|
|
3242
|
-
if (bindings === undefined || !
|
|
3286
|
+
if (bindings === undefined || !ts11.isNamedImports(bindings)) {
|
|
3243
3287
|
return false;
|
|
3244
3288
|
}
|
|
3245
3289
|
const runtime = bindings.elements.filter((element) => !element.isTypeOnly);
|
|
@@ -3250,7 +3294,7 @@ function isEditorFactoryOnlyImport(node) {
|
|
|
3250
3294
|
}
|
|
3251
3295
|
var editorScriptErasurePlugin = {
|
|
3252
3296
|
visitors: {
|
|
3253
|
-
[
|
|
3297
|
+
[ts11.SyntaxKind.ExportAssignment]: (node, context) => {
|
|
3254
3298
|
if (!node.isExportEquals) {
|
|
3255
3299
|
const lowered = lowerEditorFactoryCall(node.expression, context);
|
|
3256
3300
|
if (lowered !== undefined) {
|
|
@@ -3259,14 +3303,14 @@ var editorScriptErasurePlugin = {
|
|
|
3259
3303
|
}
|
|
3260
3304
|
return context.superTransformStatements(node);
|
|
3261
3305
|
},
|
|
3262
|
-
[
|
|
3306
|
+
[ts11.SyntaxKind.CallExpression]: (node, context) => {
|
|
3263
3307
|
const command = node.arguments[0];
|
|
3264
3308
|
if (command !== undefined && resolveEditorFactoryExport(node.expression, context.checker) === EDITOR_COMMAND_FACTORY_NAME) {
|
|
3265
3309
|
return context.transformExpression(command);
|
|
3266
3310
|
}
|
|
3267
3311
|
return context.superTransformExpression(node);
|
|
3268
3312
|
},
|
|
3269
|
-
[
|
|
3313
|
+
[ts11.SyntaxKind.ImportDeclaration]: (node, context) => isEditorFactoryOnlyImport(node) ? [] : context.superTransformStatements(node)
|
|
3270
3314
|
}
|
|
3271
3315
|
};
|
|
3272
3316
|
|
|
@@ -3277,17 +3321,17 @@ import * as path2 from "node:path";
|
|
|
3277
3321
|
import * as tstl2 from "typescript-to-lua";
|
|
3278
3322
|
|
|
3279
3323
|
// src/go-property-direct-call.ts
|
|
3280
|
-
import * as
|
|
3324
|
+
import * as ts12 from "typescript";
|
|
3281
3325
|
var GO_PROPERTY_DIRECT_CALL_MESSAGE = "`go.property` called directly is deprecated; declare the property in `defineScript({ properties })` so it types onto `self` (the transpiler emits the registration for you).";
|
|
3282
3326
|
function findDirectGoPropertyCalls(sourceFile) {
|
|
3283
3327
|
const calls = [];
|
|
3284
3328
|
const visit = (node) => {
|
|
3285
|
-
if (
|
|
3329
|
+
if (ts12.isCallExpression(node) && ts12.isPropertyAccessExpression(node.expression) && node.expression.name.text === "property" && ts12.isIdentifier(node.expression.expression) && node.expression.expression.text === "go") {
|
|
3286
3330
|
calls.push(node);
|
|
3287
3331
|
}
|
|
3288
|
-
|
|
3332
|
+
ts12.forEachChild(node, visit);
|
|
3289
3333
|
};
|
|
3290
|
-
|
|
3334
|
+
ts12.forEachChild(sourceFile, visit);
|
|
3291
3335
|
return calls;
|
|
3292
3336
|
}
|
|
3293
3337
|
|
|
@@ -3467,6 +3511,7 @@ function transpileProject(input) {
|
|
|
3467
3511
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
3468
3512
|
{ plugin: messageDispatchLoweringPlugin },
|
|
3469
3513
|
{ plugin: timersLoweringPlugin },
|
|
3514
|
+
{ plugin: typeApplicationErasurePlugin },
|
|
3470
3515
|
{ plugin: companionEmit.plugin }
|
|
3471
3516
|
]
|
|
3472
3517
|
});
|
|
@@ -3497,6 +3542,7 @@ function compilerOptions(companionEmit) {
|
|
|
3497
3542
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
3498
3543
|
{ plugin: messageDispatchLoweringPlugin },
|
|
3499
3544
|
{ plugin: timersLoweringPlugin },
|
|
3545
|
+
{ plugin: typeApplicationErasurePlugin },
|
|
3500
3546
|
{ plugin: companionEmit.plugin }
|
|
3501
3547
|
]
|
|
3502
3548
|
};
|
|
@@ -3539,7 +3585,7 @@ function createTranspileSession() {
|
|
|
3539
3585
|
if (cached) {
|
|
3540
3586
|
return cached;
|
|
3541
3587
|
}
|
|
3542
|
-
const created =
|
|
3588
|
+
const created = ts13.createSourceFile(fileName, content, ts13.ScriptTarget.Latest, false);
|
|
3543
3589
|
sourceFileCache.set(normalized, created);
|
|
3544
3590
|
return created;
|
|
3545
3591
|
}
|
|
@@ -3558,7 +3604,7 @@ function createTranspileSession() {
|
|
|
3558
3604
|
return cached;
|
|
3559
3605
|
}
|
|
3560
3606
|
const fileContent = readFileSync2(filePath, "utf8");
|
|
3561
|
-
const created =
|
|
3607
|
+
const created = ts13.createSourceFile(filePath, fileContent, ts13.ScriptTarget.Latest, false);
|
|
3562
3608
|
libCache.set(fileName, created);
|
|
3563
3609
|
return created;
|
|
3564
3610
|
}
|
|
@@ -3571,10 +3617,10 @@ function createTranspileSession() {
|
|
|
3571
3617
|
return AMBIENT_FILES[normalized];
|
|
3572
3618
|
}
|
|
3573
3619
|
const host = {
|
|
3574
|
-
fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined ||
|
|
3620
|
+
fileExists: (fileName) => mergedContent(normalizeSlashes(fileName)) !== undefined || ts13.sys.fileExists(fileName),
|
|
3575
3621
|
getCanonicalFileName: (fileName) => fileName,
|
|
3576
3622
|
getCurrentDirectory: () => "",
|
|
3577
|
-
getDefaultLibFileName:
|
|
3623
|
+
getDefaultLibFileName: ts13.getDefaultLibFileName,
|
|
3578
3624
|
readFile: () => "",
|
|
3579
3625
|
getNewLine: () => `
|
|
3580
3626
|
`,
|
|
@@ -3596,15 +3642,15 @@ function createTranspileSession() {
|
|
|
3596
3642
|
const userKeys = new Set(userFiles.keys());
|
|
3597
3643
|
const rootNames = [...Object.keys(AMBIENT_FILES).map(normalizeSlashes), ...userKeys];
|
|
3598
3644
|
const companionEmit = createCompanionEmitPlugin();
|
|
3599
|
-
program =
|
|
3600
|
-
const preEmitDiagnostics =
|
|
3645
|
+
program = ts13.createProgram(rootNames, compilerOptions(companionEmit), host, program);
|
|
3646
|
+
const preEmitDiagnostics = ts13.getPreEmitDiagnostics(program);
|
|
3601
3647
|
const collector = createOutputCollector();
|
|
3602
3648
|
const { diagnostics: transpileDiagnostics } = new tstl3.Transpiler().emit({
|
|
3603
3649
|
program,
|
|
3604
3650
|
writeFile: collector.writeFile
|
|
3605
3651
|
});
|
|
3606
3652
|
const diagnostics = [
|
|
3607
|
-
...
|
|
3653
|
+
...ts13.sortAndDeduplicateDiagnostics([...preEmitDiagnostics, ...transpileDiagnostics])
|
|
3608
3654
|
];
|
|
3609
3655
|
return collectOutputs(collector.files, diagnostics, userKeys, companionEmit.companions);
|
|
3610
3656
|
}
|
|
@@ -3613,8 +3659,71 @@ function createTranspileSession() {
|
|
|
3613
3659
|
getProgram: () => program
|
|
3614
3660
|
};
|
|
3615
3661
|
}
|
|
3662
|
+
// src/text-script-property.ts
|
|
3663
|
+
import * as ts14 from "typescript";
|
|
3664
|
+
function findTextScriptProperties(program, fileNames) {
|
|
3665
|
+
const checker = program.getTypeChecker();
|
|
3666
|
+
const findings = [];
|
|
3667
|
+
for (const file of fileNames) {
|
|
3668
|
+
const sourceFile = program.getSourceFile(file);
|
|
3669
|
+
if (sourceFile === undefined) {
|
|
3670
|
+
continue;
|
|
3671
|
+
}
|
|
3672
|
+
const located = [];
|
|
3673
|
+
const visit = (node) => {
|
|
3674
|
+
if (ts14.isCallExpression(node)) {
|
|
3675
|
+
const hooks = factoryHooks(node, checker);
|
|
3676
|
+
for (const property of hooks?.properties ?? []) {
|
|
3677
|
+
if (!isPropertiesHook(property)) {
|
|
3678
|
+
continue;
|
|
3679
|
+
}
|
|
3680
|
+
for (const { name, initializer } of propertyMembers(property)) {
|
|
3681
|
+
if (mayHoldString(checker.getTypeAtLocation(initializer), checker)) {
|
|
3682
|
+
located.push({ name, value: initializer });
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
ts14.forEachChild(node, visit);
|
|
3688
|
+
};
|
|
3689
|
+
visit(sourceFile);
|
|
3690
|
+
for (const call of findDirectGoPropertyCalls(sourceFile)) {
|
|
3691
|
+
const [nameArgument, value] = call.arguments;
|
|
3692
|
+
if (nameArgument === undefined || value === undefined) {
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
if (mayHoldString(checker.getTypeAtLocation(value), checker)) {
|
|
3696
|
+
const name = ts14.isStringLiteralLike(nameArgument) ? nameArgument.text : nameArgument.getText(sourceFile);
|
|
3697
|
+
located.push({ name, value });
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
located.sort((left, right) => left.value.getStart(sourceFile) - right.value.getStart(sourceFile));
|
|
3701
|
+
for (const { name, value } of located) {
|
|
3702
|
+
const position = sourceFile.getLineAndCharacterOfPosition(value.getStart(sourceFile));
|
|
3703
|
+
findings.push({ name, file, line: position.line + 1, column: position.character + 1 });
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
return findings;
|
|
3707
|
+
}
|
|
3708
|
+
function mayHoldString(type, checker, visited = new Set) {
|
|
3709
|
+
if (visited.has(type)) {
|
|
3710
|
+
return false;
|
|
3711
|
+
}
|
|
3712
|
+
visited.add(type);
|
|
3713
|
+
if (type.flags & ts14.TypeFlags.StringLike) {
|
|
3714
|
+
return true;
|
|
3715
|
+
}
|
|
3716
|
+
if (type.isUnionOrIntersection()) {
|
|
3717
|
+
return type.types.some((member) => mayHoldString(member, checker, visited));
|
|
3718
|
+
}
|
|
3719
|
+
if (type.flags & ts14.TypeFlags.Instantiable) {
|
|
3720
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
3721
|
+
return constraint !== undefined && constraint !== type ? mayHoldString(constraint, checker, visited) : false;
|
|
3722
|
+
}
|
|
3723
|
+
return false;
|
|
3724
|
+
}
|
|
3616
3725
|
// src/url-address-slots.ts
|
|
3617
|
-
import * as
|
|
3726
|
+
import * as ts15 from "typescript";
|
|
3618
3727
|
var ADDRESS_CLASSES = {
|
|
3619
3728
|
"game-object": true,
|
|
3620
3729
|
component: true,
|
|
@@ -3633,7 +3742,7 @@ function isFragmentCaret(slot, position) {
|
|
|
3633
3742
|
return slot.fragmentStart !== -1 && position >= slot.fragmentStart;
|
|
3634
3743
|
}
|
|
3635
3744
|
function tableKey(checker, callee) {
|
|
3636
|
-
const symbol = checker.getSymbolAtLocation(
|
|
3745
|
+
const symbol = checker.getSymbolAtLocation(ts15.isIdentifier(callee) ? callee : callee.name);
|
|
3637
3746
|
if (!symbol)
|
|
3638
3747
|
return;
|
|
3639
3748
|
const fqn = checker.getFullyQualifiedName(symbol);
|
|
@@ -3641,43 +3750,43 @@ function tableKey(checker, callee) {
|
|
|
3641
3750
|
}
|
|
3642
3751
|
function parameterName(checker, call, index) {
|
|
3643
3752
|
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
3644
|
-
if (!declaration || !
|
|
3753
|
+
if (!declaration || !ts15.isFunctionLike(declaration))
|
|
3645
3754
|
return;
|
|
3646
3755
|
const parameter = declaration.parameters[index];
|
|
3647
|
-
if (!parameter || parameter.dotDotDotToken || !
|
|
3756
|
+
if (!parameter || parameter.dotDotDotToken || !ts15.isIdentifier(parameter.name))
|
|
3648
3757
|
return;
|
|
3649
3758
|
return parameter.name.text;
|
|
3650
3759
|
}
|
|
3651
3760
|
var EQUALITY_OPERATORS = new Set([
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3761
|
+
ts15.SyntaxKind.EqualsEqualsEqualsToken,
|
|
3762
|
+
ts15.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
3763
|
+
ts15.SyntaxKind.EqualsEqualsToken,
|
|
3764
|
+
ts15.SyntaxKind.ExclamationEqualsToken
|
|
3656
3765
|
]);
|
|
3657
3766
|
function comparisonSatisfied(checker, entry, call) {
|
|
3658
3767
|
if (entry.comparedParameter === undefined)
|
|
3659
3768
|
return true;
|
|
3660
3769
|
let node = call;
|
|
3661
|
-
while (
|
|
3770
|
+
while (ts15.isParenthesizedExpression(node.parent))
|
|
3662
3771
|
node = node.parent;
|
|
3663
3772
|
const comparison = node.parent;
|
|
3664
|
-
if (!
|
|
3773
|
+
if (!ts15.isBinaryExpression(comparison))
|
|
3665
3774
|
return false;
|
|
3666
3775
|
if (!EQUALITY_OPERATORS.has(comparison.operatorToken.kind))
|
|
3667
3776
|
return false;
|
|
3668
3777
|
const other = comparison.left === node ? comparison.right : comparison.left;
|
|
3669
3778
|
const symbol = checker.getSymbolAtLocation(other);
|
|
3670
|
-
return (symbol?.declarations ?? []).some((declaration) =>
|
|
3779
|
+
return (symbol?.declarations ?? []).some((declaration) => ts15.isParameter(declaration) && ts15.isIdentifier(declaration.name) && declaration.name.text === entry.comparedParameter);
|
|
3671
3780
|
}
|
|
3672
3781
|
function classifiedEntryOfArgument(checker, table, argument) {
|
|
3673
3782
|
const call = argument.parent;
|
|
3674
|
-
if (!
|
|
3783
|
+
if (!ts15.isCallExpression(call))
|
|
3675
3784
|
return;
|
|
3676
3785
|
const argumentIndex = call.arguments.indexOf(argument);
|
|
3677
3786
|
if (argumentIndex === -1)
|
|
3678
3787
|
return;
|
|
3679
3788
|
const callee = call.expression;
|
|
3680
|
-
if (!
|
|
3789
|
+
if (!ts15.isPropertyAccessExpression(callee) && !ts15.isIdentifier(callee))
|
|
3681
3790
|
return;
|
|
3682
3791
|
const fqn = tableKey(checker, callee);
|
|
3683
3792
|
if (fqn === undefined)
|
|
@@ -3694,10 +3803,10 @@ function classifiedEntryOfArgument(checker, table, argument) {
|
|
|
3694
3803
|
}
|
|
3695
3804
|
function slotThroughHashedCall(checker, table, literal) {
|
|
3696
3805
|
const call = literal.parent;
|
|
3697
|
-
if (!
|
|
3806
|
+
if (!ts15.isCallExpression(call))
|
|
3698
3807
|
return;
|
|
3699
3808
|
const callee = call.expression;
|
|
3700
|
-
if (!
|
|
3809
|
+
if (!ts15.isPropertyAccessExpression(callee) && !ts15.isIdentifier(callee))
|
|
3701
3810
|
return;
|
|
3702
3811
|
if (tableKey(checker, callee) !== "hash")
|
|
3703
3812
|
return;
|
|
@@ -3715,24 +3824,24 @@ function addressTextOf(checker, entry, call) {
|
|
|
3715
3824
|
if (entry.addressParameter === undefined)
|
|
3716
3825
|
return;
|
|
3717
3826
|
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
3718
|
-
if (!declaration || !
|
|
3827
|
+
if (!declaration || !ts15.isFunctionLike(declaration))
|
|
3719
3828
|
return;
|
|
3720
|
-
const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken &&
|
|
3829
|
+
const index = declaration.parameters.findIndex((parameter) => !parameter.dotDotDotToken && ts15.isIdentifier(parameter.name) && parameter.name.text === entry.addressParameter);
|
|
3721
3830
|
if (index === -1)
|
|
3722
3831
|
return;
|
|
3723
3832
|
const argument = call.arguments[index];
|
|
3724
|
-
return argument &&
|
|
3833
|
+
return argument && ts15.isStringLiteralLike(argument) ? argument.text : undefined;
|
|
3725
3834
|
}
|
|
3726
3835
|
function innermostLiteralAt(sourceFile, position) {
|
|
3727
3836
|
let found;
|
|
3728
3837
|
const visit = (node) => {
|
|
3729
3838
|
if (position < node.getStart(sourceFile) || position > node.getEnd())
|
|
3730
3839
|
return;
|
|
3731
|
-
if (
|
|
3840
|
+
if (ts15.isStringLiteralLike(node))
|
|
3732
3841
|
found = node;
|
|
3733
|
-
|
|
3842
|
+
ts15.forEachChild(node, visit);
|
|
3734
3843
|
};
|
|
3735
|
-
|
|
3844
|
+
ts15.forEachChild(sourceFile, visit);
|
|
3736
3845
|
return found;
|
|
3737
3846
|
}
|
|
3738
3847
|
function resolveClassifiedSlotAtPosition(input) {
|
|
@@ -3766,23 +3875,23 @@ function resolveClassifiedSlotAtPosition(input) {
|
|
|
3766
3875
|
};
|
|
3767
3876
|
}
|
|
3768
3877
|
// src/url-cross-world-address.ts
|
|
3769
|
-
import * as
|
|
3878
|
+
import * as ts17 from "typescript";
|
|
3770
3879
|
|
|
3771
3880
|
// src/url-address-literals.ts
|
|
3772
|
-
import * as
|
|
3881
|
+
import * as ts16 from "typescript";
|
|
3773
3882
|
function isAmbient2(fileName) {
|
|
3774
3883
|
return /[\\/]node_modules[\\/]/.test(fileName) || /(^|[\\/])lib\.[^\\/]*\.d\.ts$/.test(fileName);
|
|
3775
3884
|
}
|
|
3776
3885
|
function referenceTargetSymbol(type) {
|
|
3777
|
-
if ((type.flags &
|
|
3886
|
+
if ((type.flags & ts16.TypeFlags.Object) === 0)
|
|
3778
3887
|
return;
|
|
3779
|
-
if ((type.objectFlags &
|
|
3888
|
+
if ((type.objectFlags & ts16.ObjectFlags.Reference) === 0)
|
|
3780
3889
|
return;
|
|
3781
3890
|
return type.target.symbol;
|
|
3782
3891
|
}
|
|
3783
3892
|
function ambientHashAliasTargets(checker, anchor) {
|
|
3784
3893
|
const targets = new Set;
|
|
3785
|
-
for (const symbol of checker.getSymbolsInScope(anchor,
|
|
3894
|
+
for (const symbol of checker.getSymbolsInScope(anchor, ts16.SymbolFlags.Type)) {
|
|
3786
3895
|
if (symbol.name !== "Hash")
|
|
3787
3896
|
continue;
|
|
3788
3897
|
const target = referenceTargetSymbol(checker.getDeclaredTypeOfSymbol(symbol));
|
|
@@ -3798,7 +3907,7 @@ function canonicalHashSymbols(checker, program) {
|
|
|
3798
3907
|
if (!anchor)
|
|
3799
3908
|
return canonical;
|
|
3800
3909
|
const aliased = ambientHashAliasTargets(checker, anchor);
|
|
3801
|
-
for (const symbol of checker.getSymbolsInScope(anchor,
|
|
3910
|
+
for (const symbol of checker.getSymbolsInScope(anchor, ts16.SymbolFlags.Function)) {
|
|
3802
3911
|
if (symbol.name !== "hash")
|
|
3803
3912
|
continue;
|
|
3804
3913
|
const declaration = symbol.declarations?.[0];
|
|
@@ -3820,7 +3929,7 @@ function hashedSourceOfType(checker, type, canonical) {
|
|
|
3820
3929
|
return source?.isStringLiteral() ? source.value : undefined;
|
|
3821
3930
|
}
|
|
3822
3931
|
function staticAddressTextOf(checker, node, canonical) {
|
|
3823
|
-
return
|
|
3932
|
+
return ts16.isStringLiteralLike(node) ? node.text : hashedSourceOfType(checker, checker.getTypeAtLocation(node), canonical);
|
|
3824
3933
|
}
|
|
3825
3934
|
|
|
3826
3935
|
// src/url-cross-world-address.ts
|
|
@@ -3844,7 +3953,7 @@ function checkCrossWorldAddresses(input) {
|
|
|
3844
3953
|
if (worlds.length === 0)
|
|
3845
3954
|
continue;
|
|
3846
3955
|
const visit = (node) => {
|
|
3847
|
-
if (
|
|
3956
|
+
if (ts17.isExpression(node)) {
|
|
3848
3957
|
const entry = addressEntryOfArgument(checker, table, node);
|
|
3849
3958
|
if (entry !== undefined && slotRejectsForeignSocket(entry)) {
|
|
3850
3959
|
const address = staticAddressTextOf(checker, node, canonical);
|
|
@@ -3861,14 +3970,14 @@ function checkCrossWorldAddresses(input) {
|
|
|
3861
3970
|
}
|
|
3862
3971
|
}
|
|
3863
3972
|
}
|
|
3864
|
-
|
|
3973
|
+
ts17.forEachChild(node, visit);
|
|
3865
3974
|
};
|
|
3866
|
-
|
|
3975
|
+
ts17.forEachChild(sourceFile, visit);
|
|
3867
3976
|
}
|
|
3868
3977
|
return findings;
|
|
3869
3978
|
}
|
|
3870
3979
|
// src/url-fragment-reachability.ts
|
|
3871
|
-
import * as
|
|
3980
|
+
import * as ts18 from "typescript";
|
|
3872
3981
|
function pathOfKey(key) {
|
|
3873
3982
|
const socket = socketOfAddress(key);
|
|
3874
3983
|
return socket === undefined ? key : key.slice(socket.length + 1);
|
|
@@ -3913,7 +4022,7 @@ function checkUrlFragmentReachability(input) {
|
|
|
3913
4022
|
if (isAmbient2(sourceFile.fileName))
|
|
3914
4023
|
continue;
|
|
3915
4024
|
const visit = (node) => {
|
|
3916
|
-
if (
|
|
4025
|
+
if (ts18.isExpression(node) && isAddressClass(addressClassOfArgument(checker, table, node))) {
|
|
3917
4026
|
const text = staticAddressTextOf(checker, node, canonical);
|
|
3918
4027
|
if (text !== undefined) {
|
|
3919
4028
|
const hash = text.indexOf("#");
|
|
@@ -3932,9 +4041,9 @@ function checkUrlFragmentReachability(input) {
|
|
|
3932
4041
|
}
|
|
3933
4042
|
}
|
|
3934
4043
|
}
|
|
3935
|
-
|
|
4044
|
+
ts18.forEachChild(node, visit);
|
|
3936
4045
|
};
|
|
3937
|
-
|
|
4046
|
+
ts18.forEachChild(sourceFile, visit);
|
|
3938
4047
|
}
|
|
3939
4048
|
return { kind: "checked", findings };
|
|
3940
4049
|
}
|
|
@@ -3979,6 +4088,7 @@ export {
|
|
|
3979
4088
|
displayPathOf,
|
|
3980
4089
|
findEmittedRequires,
|
|
3981
4090
|
findMainEntryFactoryImports,
|
|
4091
|
+
findTextScriptProperties,
|
|
3982
4092
|
getProgramDiagnostics,
|
|
3983
4093
|
guiScriptResourceOf,
|
|
3984
4094
|
guiScriptResourcesOf,
|
|
@@ -3,5 +3,12 @@ import { type Plugin } from "typescript-to-lua";
|
|
|
3
3
|
export declare const FACTORY_MODULE = "@defold-typescript/types";
|
|
4
4
|
export declare const FACTORY_NAMES: Set<string>;
|
|
5
5
|
export declare function resolvesToFactoryExport(callee: ts.Expression, checker: ts.TypeChecker): boolean;
|
|
6
|
+
export interface PropertyMember {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly initializer: ts.Expression;
|
|
9
|
+
}
|
|
10
|
+
export declare function propertyMembers(property: ts.ObjectLiteralElementLike): PropertyMember[];
|
|
11
|
+
export declare function factoryHooks(expression: ts.Expression, checker: ts.TypeChecker): ts.ObjectLiteralExpression | undefined;
|
|
12
|
+
export declare function isPropertiesHook(property: ts.ObjectLiteralElementLike): boolean;
|
|
6
13
|
export declare function isFactoryOnlyImport(node: ts.ImportDeclaration): boolean;
|
|
7
14
|
export declare const lifecycleErasurePlugin: Plugin;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export interface TextScriptPropertyFinding {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly file: string;
|
|
5
|
+
/** 1-based line of the default's initializer or the direct call's value. */
|
|
6
|
+
readonly line: number;
|
|
7
|
+
/** 1-based column of the default's initializer or the direct call's value. */
|
|
8
|
+
readonly column: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function findTextScriptProperties(program: ts.Program, fileNames: readonly string[]): TextScriptPropertyFinding[];
|
|
11
|
+
/**
|
|
12
|
+
* Whether a value of `type` may be a string at runtime: a string-like type, a
|
|
13
|
+
* union or intersection with such a member, or a type parameter or other
|
|
14
|
+
* instantiable type whose constraint may hold one.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mayHoldString(type: ts.Type, checker: ts.TypeChecker, visited?: Set<ts.Type>): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/transpiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "TypeScript-to-Lua build pipeline tuned for Defold's runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@defold-typescript/types": "0.
|
|
38
|
+
"@defold-typescript/types": "0.41.0",
|
|
39
39
|
"@typescript-to-lua/language-extensions": "1.19.0",
|
|
40
40
|
"typescript-to-lua": "^1.36.0"
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -86,6 +86,8 @@ export type { SceneMessage } from "./scene-text-format";
|
|
|
86
86
|
export { parseSceneTextFormat, SceneTextFormatError } from "./scene-text-format";
|
|
87
87
|
export type { TranspileSession } from "./session";
|
|
88
88
|
export { createTranspileSession } from "./session";
|
|
89
|
+
export type { TextScriptPropertyFinding } from "./text-script-property";
|
|
90
|
+
export { findTextScriptProperties } from "./text-script-property";
|
|
89
91
|
export { TIMERS_REQUIRE_NAME } from "./timers-runtime";
|
|
90
92
|
export type {
|
|
91
93
|
TranspileDiagnostic,
|
package/src/lifecycle-erasure.ts
CHANGED
|
@@ -208,18 +208,19 @@ function emitInitMerge(
|
|
|
208
208
|
return [builderDecl, createAssignmentStatement(createIdentifier("init"), initFn, property)];
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
export interface PropertyMember {
|
|
212
|
+
readonly name: string;
|
|
213
|
+
readonly initializer: ts.Expression;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// The `key: default` members of a factory's `properties` field that register as
|
|
217
|
+
// script properties: a property assignment keyed by an identifier or a string
|
|
218
|
+
// literal. Shared by the emitter and every analysis of what it registers.
|
|
219
|
+
export function propertyMembers(property: ts.ObjectLiteralElementLike): PropertyMember[] {
|
|
219
220
|
if (!ts.isPropertyAssignment(property) || !ts.isObjectLiteralExpression(property.initializer)) {
|
|
220
221
|
return [];
|
|
221
222
|
}
|
|
222
|
-
const
|
|
223
|
+
const members: PropertyMember[] = [];
|
|
223
224
|
for (const member of property.initializer.properties) {
|
|
224
225
|
if (!ts.isPropertyAssignment(member)) {
|
|
225
226
|
continue;
|
|
@@ -228,32 +229,60 @@ function emitPropertyRegistrations(
|
|
|
228
229
|
if (!ts.isIdentifier(key) && !ts.isStringLiteral(key)) {
|
|
229
230
|
continue;
|
|
230
231
|
}
|
|
231
|
-
|
|
232
|
-
createExpressionStatement(
|
|
233
|
-
createCallExpression(
|
|
234
|
-
createTableIndexExpression(createIdentifier("go"), createStringLiteral("property")),
|
|
235
|
-
[createStringLiteral(key.text), context.transformExpression(member.initializer)],
|
|
236
|
-
),
|
|
237
|
-
),
|
|
238
|
-
);
|
|
232
|
+
members.push({ name: key.text, initializer: member.initializer });
|
|
239
233
|
}
|
|
240
|
-
return
|
|
234
|
+
return members;
|
|
241
235
|
}
|
|
242
236
|
|
|
243
|
-
|
|
237
|
+
// The hooks object literal of a call to a lifecycle factory, or `undefined` when
|
|
238
|
+
// the expression is not one the erasure rewrites.
|
|
239
|
+
export function factoryHooks(
|
|
244
240
|
expression: ts.Expression,
|
|
245
|
-
|
|
246
|
-
):
|
|
241
|
+
checker: ts.TypeChecker,
|
|
242
|
+
): ts.ObjectLiteralExpression | undefined {
|
|
247
243
|
if (!ts.isCallExpression(expression)) {
|
|
248
244
|
return undefined;
|
|
249
245
|
}
|
|
250
|
-
if (!resolvesToFactoryExport(expression.expression,
|
|
246
|
+
if (!resolvesToFactoryExport(expression.expression, checker)) {
|
|
251
247
|
return undefined;
|
|
252
248
|
}
|
|
253
249
|
const hooks = expression.arguments[0];
|
|
254
250
|
if (hooks === undefined || !ts.isObjectLiteralExpression(hooks)) {
|
|
255
251
|
return undefined;
|
|
256
252
|
}
|
|
253
|
+
return hooks;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function isPropertiesHook(property: ts.ObjectLiteralElementLike): boolean {
|
|
257
|
+
return hookName(property) === "properties";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The value-keyed `properties` field maps each `key: default` to a chunk-scope
|
|
261
|
+
// `go.property("key", default)` the editor reads. Emitted before the hook
|
|
262
|
+
// assignments and after the source's `local` consts, so a default referencing a
|
|
263
|
+
// module const resolves.
|
|
264
|
+
function emitPropertyRegistrations(
|
|
265
|
+
property: ts.ObjectLiteralElementLike,
|
|
266
|
+
context: TransformationContext,
|
|
267
|
+
): Statement[] {
|
|
268
|
+
return propertyMembers(property).map(({ name, initializer }) =>
|
|
269
|
+
createExpressionStatement(
|
|
270
|
+
createCallExpression(
|
|
271
|
+
createTableIndexExpression(createIdentifier("go"), createStringLiteral("property")),
|
|
272
|
+
[createStringLiteral(name), context.transformExpression(initializer)],
|
|
273
|
+
),
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function eraseFactoryCall(
|
|
279
|
+
expression: ts.Expression,
|
|
280
|
+
context: TransformationContext,
|
|
281
|
+
): Statement[] | undefined {
|
|
282
|
+
const hooks = factoryHooks(expression, context.checker);
|
|
283
|
+
if (hooks === undefined) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
257
286
|
const registrations: Statement[] = [];
|
|
258
287
|
const statements: Statement[] = [];
|
|
259
288
|
for (const property of hooks.properties) {
|
|
@@ -4,6 +4,7 @@ import { lifecycleErasurePlugin } from "./lifecycle-erasure";
|
|
|
4
4
|
import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
5
5
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
6
6
|
import { timersLoweringPlugin } from "./timers-lowering";
|
|
7
|
+
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
7
8
|
import { windowEventGuardLoweringPlugin } from "./window-event-guard-lowering";
|
|
8
9
|
|
|
9
10
|
// The same lua plugins the build path lists (transpile.ts / session.ts), so the
|
|
@@ -14,6 +15,7 @@ const LUA_PLUGINS: tstl.Plugin[] = [
|
|
|
14
15
|
windowEventGuardLoweringPlugin,
|
|
15
16
|
messageDispatchLoweringPlugin,
|
|
16
17
|
timersLoweringPlugin,
|
|
18
|
+
typeApplicationErasurePlugin,
|
|
17
19
|
];
|
|
18
20
|
|
|
19
21
|
const noopWriteFile: ts.WriteFileCallback = () => {};
|
package/src/session.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
|
10
10
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
11
11
|
import { timersLoweringPlugin } from "./timers-lowering";
|
|
12
12
|
import { AMBIENT_FILES, collectOutputs, type TranspileProjectResult } from "./transpile";
|
|
13
|
+
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
13
14
|
import { windowEventGuardLoweringPlugin } from "./window-event-guard-lowering";
|
|
14
15
|
|
|
15
16
|
export interface TranspileSession {
|
|
@@ -43,6 +44,7 @@ function compilerOptions(companionEmit: CompanionEmitter): tstl.CompilerOptions
|
|
|
43
44
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
44
45
|
{ plugin: messageDispatchLoweringPlugin },
|
|
45
46
|
{ plugin: timersLoweringPlugin },
|
|
47
|
+
{ plugin: typeApplicationErasurePlugin },
|
|
46
48
|
{ plugin: companionEmit.plugin },
|
|
47
49
|
],
|
|
48
50
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import { findDirectGoPropertyCalls } from "./go-property-direct-call";
|
|
3
|
+
import { factoryHooks, isPropertiesHook, propertyMembers } from "./lifecycle-erasure";
|
|
4
|
+
|
|
5
|
+
export interface TextScriptPropertyFinding {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly file: string;
|
|
8
|
+
/** 1-based line of the default's initializer or the direct call's value. */
|
|
9
|
+
readonly line: number;
|
|
10
|
+
/** 1-based column of the default's initializer or the direct call's value. */
|
|
11
|
+
readonly column: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The script properties whose default may hold a string, declared either in a
|
|
15
|
+
// `properties` field or by a direct `go.property` call. Defold registers a
|
|
16
|
+
// string `go.property` only from 1.13.2, so a build for an older target must
|
|
17
|
+
// refuse them; typed rather than literal-driven, so a const holding a string
|
|
18
|
+
// counts.
|
|
19
|
+
export function findTextScriptProperties(
|
|
20
|
+
program: ts.Program,
|
|
21
|
+
fileNames: readonly string[],
|
|
22
|
+
): TextScriptPropertyFinding[] {
|
|
23
|
+
const checker = program.getTypeChecker();
|
|
24
|
+
const findings: TextScriptPropertyFinding[] = [];
|
|
25
|
+
|
|
26
|
+
for (const file of fileNames) {
|
|
27
|
+
const sourceFile = program.getSourceFile(file);
|
|
28
|
+
if (sourceFile === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const located: { name: string; value: ts.Node }[] = [];
|
|
32
|
+
const visit = (node: ts.Node): void => {
|
|
33
|
+
if (ts.isCallExpression(node)) {
|
|
34
|
+
const hooks = factoryHooks(node, checker);
|
|
35
|
+
for (const property of hooks?.properties ?? []) {
|
|
36
|
+
if (!isPropertiesHook(property)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
for (const { name, initializer } of propertyMembers(property)) {
|
|
40
|
+
if (mayHoldString(checker.getTypeAtLocation(initializer), checker)) {
|
|
41
|
+
located.push({ name, value: initializer });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
ts.forEachChild(node, visit);
|
|
47
|
+
};
|
|
48
|
+
visit(sourceFile);
|
|
49
|
+
for (const call of findDirectGoPropertyCalls(sourceFile)) {
|
|
50
|
+
const [nameArgument, value] = call.arguments;
|
|
51
|
+
if (nameArgument === undefined || value === undefined) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (mayHoldString(checker.getTypeAtLocation(value), checker)) {
|
|
55
|
+
const name = ts.isStringLiteralLike(nameArgument)
|
|
56
|
+
? nameArgument.text
|
|
57
|
+
: nameArgument.getText(sourceFile);
|
|
58
|
+
located.push({ name, value });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
located.sort(
|
|
62
|
+
(left, right) => left.value.getStart(sourceFile) - right.value.getStart(sourceFile),
|
|
63
|
+
);
|
|
64
|
+
for (const { name, value } of located) {
|
|
65
|
+
const position = sourceFile.getLineAndCharacterOfPosition(value.getStart(sourceFile));
|
|
66
|
+
findings.push({ name, file, line: position.line + 1, column: position.character + 1 });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return findings;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether a value of `type` may be a string at runtime: a string-like type, a
|
|
75
|
+
* union or intersection with such a member, or a type parameter or other
|
|
76
|
+
* instantiable type whose constraint may hold one.
|
|
77
|
+
*/
|
|
78
|
+
export function mayHoldString(
|
|
79
|
+
type: ts.Type,
|
|
80
|
+
checker: ts.TypeChecker,
|
|
81
|
+
visited: Set<ts.Type> = new Set(),
|
|
82
|
+
): boolean {
|
|
83
|
+
if (visited.has(type)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
visited.add(type);
|
|
87
|
+
if (type.flags & ts.TypeFlags.StringLike) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (type.isUnionOrIntersection()) {
|
|
91
|
+
return type.types.some((member) => mayHoldString(member, checker, visited));
|
|
92
|
+
}
|
|
93
|
+
if (type.flags & ts.TypeFlags.Instantiable) {
|
|
94
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
95
|
+
return constraint !== undefined && constraint !== type
|
|
96
|
+
? mayHoldString(constraint, checker, visited)
|
|
97
|
+
: false;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
package/src/transpile.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { messageDispatchLoweringPlugin } from "./message-dispatch-lowering";
|
|
|
14
14
|
import { messageGuardLoweringPlugin } from "./message-guard-lowering";
|
|
15
15
|
import { importsTimersModule, timersLoweringPlugin } from "./timers-lowering";
|
|
16
16
|
import { TIMERS_RUNTIME } from "./timers-runtime";
|
|
17
|
+
import { typeApplicationErasurePlugin } from "./type-application-erasure";
|
|
17
18
|
import { windowEventGuardLoweringPlugin } from "./window-event-guard-lowering";
|
|
18
19
|
|
|
19
20
|
export interface TranspileResult {
|
|
@@ -321,6 +322,7 @@ export function transpileProject(input: TranspileProjectInput): TranspileProject
|
|
|
321
322
|
{ plugin: windowEventGuardLoweringPlugin },
|
|
322
323
|
{ plugin: messageDispatchLoweringPlugin },
|
|
323
324
|
{ plugin: timersLoweringPlugin },
|
|
325
|
+
{ plugin: typeApplicationErasurePlugin },
|
|
324
326
|
{ plugin: companionEmit.plugin },
|
|
325
327
|
],
|
|
326
328
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type { Expression, Plugin } from "typescript-to-lua";
|
|
3
|
+
|
|
4
|
+
// The Defold functions declared with a curried type-application overload,
|
|
5
|
+
// `go.get<P>()(url, property)`: the empty call only fixes `P` for the checker.
|
|
6
|
+
export const TYPE_APPLICATION_APIS: ReadonlySet<string> = new Set(["go.get", "go.set"]);
|
|
7
|
+
|
|
8
|
+
// Keyed by the declaration's fully qualified name: only a symbol declared in
|
|
9
|
+
// an ambient `declare global` block carries the `global.` prefix, so a
|
|
10
|
+
// project's own `go` never matches.
|
|
11
|
+
function isTypeApplication(call: ts.CallExpression, checker: ts.TypeChecker): boolean {
|
|
12
|
+
if (call.arguments.length !== 0) return false;
|
|
13
|
+
const declaration = checker.getResolvedSignature(call)?.declaration;
|
|
14
|
+
if (declaration === undefined || !ts.isFunctionDeclaration(declaration)) return false;
|
|
15
|
+
if (declaration.parameters.length !== 0 || declaration.typeParameters === undefined) return false;
|
|
16
|
+
if (declaration.name === undefined) return false;
|
|
17
|
+
const symbol = checker.getSymbolAtLocation(declaration.name);
|
|
18
|
+
if (symbol === undefined) return false;
|
|
19
|
+
const fqn = checker.getFullyQualifiedName(symbol);
|
|
20
|
+
return fqn.startsWith("global.") && TYPE_APPLICATION_APIS.has(fqn.slice("global.".length));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// `go.get<P>()` becomes `go.get`, so the curried call reaches the engine as
|
|
24
|
+
// `go.get(url, property)` and a stored `go.get<P>()` is the function itself.
|
|
25
|
+
export const typeApplicationErasurePlugin: Plugin = {
|
|
26
|
+
visitors: {
|
|
27
|
+
[ts.SyntaxKind.CallExpression]: (node, context): Expression => {
|
|
28
|
+
if (isTypeApplication(node, context.checker)) {
|
|
29
|
+
return context.transformExpression(node.expression);
|
|
30
|
+
}
|
|
31
|
+
return context.superTransformExpression(node);
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|