xray16 1.6.2 → 1.6.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.
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFoldedExpression = createFoldedExpression;
4
+ exports.transformAccessExpression = transformAccessExpression;
5
+ exports.transformIdentifierExpression = transformIdentifierExpression;
6
+ exports.transformImportDeclaration = transformImportDeclaration;
7
+ const ts = require("typescript");
8
+ const lua = require("typescript-to-lua");
9
+ const ast_1 = require("./ast");
10
+ const evaluation_1 = require("./evaluation");
11
+ const virtual_1 = require("./virtual");
12
+ const NUMERIC_KEY_PATTERN = /^(0|[1-9][0-9]*)$/;
13
+ const TREE_LUA_OPERATORS = {
14
+ "+": lua.SyntaxKind.AdditionOperator,
15
+ "-": lua.SyntaxKind.SubtractionOperator,
16
+ "*": lua.SyntaxKind.MultiplicationOperator,
17
+ "/": lua.SyntaxKind.DivisionOperator,
18
+ "**": lua.SyntaxKind.PowerOperator,
19
+ };
20
+ const fileBindingUsageCache = new WeakMap();
21
+ /**
22
+ * Create a Lua expression for the provided folded value.
23
+ * Literals become Lua literals; expression trees with engine references become global access
24
+ * expressions and arithmetic over them.
25
+ *
26
+ * @param value - Folded value to create expression for.
27
+ * @param node - Original TypeScript node.
28
+ * @returns Lua expression.
29
+ */
30
+ function createFoldedExpression(value, node) {
31
+ if (typeof value === "string") {
32
+ return lua.createStringLiteral(value, node);
33
+ }
34
+ if (typeof value === "boolean") {
35
+ return lua.createBooleanLiteral(value, node);
36
+ }
37
+ if (typeof value === "number") {
38
+ return value < 0
39
+ ? lua.createUnaryExpression(lua.createNumericLiteral(Math.abs(value), node), lua.SyntaxKind.NegationOperator, node)
40
+ : lua.createNumericLiteral(value, node);
41
+ }
42
+ switch (value.kind) {
43
+ case "engine-ref": {
44
+ let expression = lua.createIdentifier(value.path[0]);
45
+ for (const member of value.path.slice(1)) {
46
+ expression = lua.createTableIndexExpression(expression, lua.createStringLiteral(member), node);
47
+ }
48
+ return expression;
49
+ }
50
+ case "binary":
51
+ return lua.createBinaryExpression(createFoldedExpression(value.left, node), createFoldedExpression(value.right, node), TREE_LUA_OPERATORS[value.operator], node);
52
+ case "negate":
53
+ return lua.createUnaryExpression(createFoldedExpression(value.operand, node), lua.SyntaxKind.NegationOperator, node);
54
+ }
55
+ }
56
+ /**
57
+ * Create a Lua table literal that replaces an object spread of a `@virtual` object.
58
+ * Returns null when the referenced symbol is not a virtual object or an entry cannot be computed.
59
+ *
60
+ * @param checker - Program type checker.
61
+ * @param symbol - Referenced symbol of the spread expression.
62
+ * @param node - Original TypeScript node.
63
+ * @returns Lua table literal or null.
64
+ */
65
+ function tryCreateVirtualSpreadTable(checker, symbol, node) {
66
+ if ((0, virtual_1.getVirtualDeclaration)(checker, symbol) === null) {
67
+ return null;
68
+ }
69
+ const entries = (0, virtual_1.getVirtualObjectEntries)(checker, symbol);
70
+ if (entries === null) {
71
+ return null;
72
+ }
73
+ const fields = entries.map(([name, value]) => {
74
+ const key = NUMERIC_KEY_PATTERN.test(name)
75
+ ? lua.createNumericLiteral(Number(name))
76
+ : lua.createStringLiteral(name);
77
+ return lua.createTableFieldExpression(createFoldedExpression(value, node), key);
78
+ });
79
+ return lua.createTableExpression(fields, node);
80
+ }
81
+ /**
82
+ * Transform property and element access expressions by inlining literal values from tagged declarations.
83
+ * Object spreads of `@virtual` objects accessed through namespaces are expanded to table literals.
84
+ *
85
+ * @param node - Access expression node to transform.
86
+ * @param context - Transformation context.
87
+ * @returns Lua literal when the access resolves to a tagged constant, default transformation otherwise.
88
+ */
89
+ function transformAccessExpression(node, context) {
90
+ const symbol = (0, evaluation_1.resolveMemberSymbol)(context.checker, node);
91
+ if (symbol === null) {
92
+ return context.superTransformExpression(node);
93
+ }
94
+ const value = (0, evaluation_1.tryGetInlineValue)(context.checker, symbol);
95
+ if (value !== null) {
96
+ return createFoldedExpression(value, node);
97
+ }
98
+ if (node.parent !== undefined && ts.isSpreadAssignment(node.parent)) {
99
+ const table = tryCreateVirtualSpreadTable(context.checker, symbol, node);
100
+ if (table !== null) {
101
+ return table;
102
+ }
103
+ }
104
+ return context.superTransformExpression(node);
105
+ }
106
+ /**
107
+ * Transform identifier expressions by inlining literal values from tagged scalar constants
108
+ * and expand object spreads of `@virtual` objects into table literals.
109
+ *
110
+ * @param node - Identifier node to transform.
111
+ * @param context - Transformation context.
112
+ * @returns Lua literal when the identifier resolves to a tagged constant, default transformation otherwise.
113
+ */
114
+ function transformIdentifierExpression(node, context) {
115
+ if (node.parent !== undefined && ts.isSpreadAssignment(node.parent)) {
116
+ const symbol = context.checker.getSymbolAtLocation(node);
117
+ const table = symbol === undefined ? null : tryCreateVirtualSpreadTable(context.checker, symbol, node);
118
+ if (table !== null) {
119
+ return table;
120
+ }
121
+ return context.superTransformExpression(node);
122
+ }
123
+ if ((0, ast_1.isValueUsagePosition)(node)) {
124
+ let symbol = context.checker.getSymbolAtLocation(node);
125
+ if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
126
+ symbol = context.checker.getAliasedSymbol(symbol);
127
+ }
128
+ if (symbol?.valueDeclaration !== undefined && ts.isVariableDeclaration(symbol.valueDeclaration)) {
129
+ const value = (0, evaluation_1.tryGetInlineValue)(context.checker, symbol);
130
+ if (value !== null) {
131
+ return createFoldedExpression(value, node);
132
+ }
133
+ }
134
+ }
135
+ return context.superTransformExpression(node);
136
+ }
137
+ /**
138
+ * Get a map of import binding symbols to whether each binding has usages that block removal in the file.
139
+ * Bindings with only erasable references (inlined accesses, expanded spreads, type positions) can be stripped.
140
+ *
141
+ * @param checker - Program type checker.
142
+ * @param sourceFile - Source file to analyze.
143
+ * @returns Map of import alias symbols to whether each alias has a blocking usage.
144
+ */
145
+ function getFileBindingUsage(checker, sourceFile) {
146
+ const cached = fileBindingUsageCache.get(sourceFile);
147
+ if (cached !== undefined) {
148
+ return cached;
149
+ }
150
+ const usage = new Map();
151
+ visitNode(sourceFile);
152
+ fileBindingUsageCache.set(sourceFile, usage);
153
+ return usage;
154
+ function markImportBindingUsage(symbol, isBlocking) {
155
+ const declaration = symbol?.declarations?.[0];
156
+ if (symbol === undefined || declaration === undefined || !ts.isImportSpecifier(declaration)) {
157
+ return;
158
+ }
159
+ if (isBlocking) {
160
+ usage.set(symbol, true);
161
+ }
162
+ else if (!usage.has(symbol)) {
163
+ usage.set(symbol, false);
164
+ }
165
+ }
166
+ function visitNode(node) {
167
+ // References inside erased virtual statements never survive to emitted output:
168
+ if ((ts.isVariableStatement(node) || ts.isEnumDeclaration(node)) && (0, ast_1.hasVirtualTag)(node)) {
169
+ return;
170
+ }
171
+ if (ts.isIdentifier(node) && node.parent !== undefined && !ts.isImportSpecifier(node.parent)) {
172
+ // Re-exports and shorthand properties emit direct references to import bindings:
173
+ if (ts.isExportSpecifier(node.parent)) {
174
+ markImportBindingUsage(checker.getExportSpecifierLocalTargetSymbol(node.parent), true);
175
+ }
176
+ else if (ts.isShorthandPropertyAssignment(node.parent)) {
177
+ markImportBindingUsage(checker.getShorthandAssignmentValueSymbol(node.parent), true);
178
+ }
179
+ else {
180
+ const symbol = checker.getSymbolAtLocation(node);
181
+ if (symbol !== undefined) {
182
+ const isVirtual = (0, virtual_1.getVirtualDeclaration)(checker, symbol) !== null;
183
+ markImportBindingUsage(symbol, !(0, virtual_1.isErasableReference)(checker, node, symbol, isVirtual));
184
+ }
185
+ }
186
+ }
187
+ node.forEachChild(visitNode);
188
+ }
189
+ }
190
+ /**
191
+ * Check whether an import specifier resolves to a declaration tagged with `@inline` or `@virtual`.
192
+ *
193
+ * @param checker - Program type checker.
194
+ * @param symbol - Import alias symbol to check.
195
+ * @returns Whether the specifier target is tagged.
196
+ */
197
+ function isTaggedImportTarget(checker, symbol) {
198
+ const declaration = (0, virtual_1.resolveAliasedSymbol)(checker, symbol).valueDeclaration;
199
+ if (declaration === undefined) {
200
+ return false;
201
+ }
202
+ if (ts.isEnumDeclaration(declaration)) {
203
+ return (0, ast_1.hasInlineTag)(declaration);
204
+ }
205
+ if (ts.isVariableDeclaration(declaration)) {
206
+ const statement = (0, ast_1.getContainingVariableStatement)(declaration);
207
+ return statement !== null && (0, ast_1.hasInlineTag)(statement);
208
+ }
209
+ return false;
210
+ }
211
+ /**
212
+ * Transform import declarations by stripping bindings for tagged declarations with no remaining runtime usages.
213
+ * When no runtime bindings remain, the require is dropped for provably pure modules
214
+ * or kept as a side-effect import otherwise.
215
+ *
216
+ * @param statement - Import declaration to transform.
217
+ * @param context - Transformation context.
218
+ * @returns Transformed statements.
219
+ */
220
+ function transformImportDeclaration(statement, context) {
221
+ const clause = statement.importClause;
222
+ if (clause === undefined ||
223
+ clause.isTypeOnly ||
224
+ clause.name !== undefined ||
225
+ clause.namedBindings === undefined ||
226
+ !ts.isNamedImports(clause.namedBindings)) {
227
+ return context.superTransformStatements(statement);
228
+ }
229
+ const checker = context.checker;
230
+ const usage = getFileBindingUsage(checker, context.sourceFile);
231
+ const kept = [];
232
+ let hasRuntimeBindings = false;
233
+ for (const specifier of clause.namedBindings.elements) {
234
+ if (specifier.isTypeOnly) {
235
+ kept.push(specifier);
236
+ continue;
237
+ }
238
+ const symbol = checker.getSymbolAtLocation(specifier.name);
239
+ if (symbol === undefined) {
240
+ kept.push(specifier);
241
+ hasRuntimeBindings = true;
242
+ continue;
243
+ }
244
+ // Pure type imports produce no runtime bindings:
245
+ if (((0, virtual_1.resolveAliasedSymbol)(checker, symbol).flags & ts.SymbolFlags.Value) === 0) {
246
+ kept.push(specifier);
247
+ continue;
248
+ }
249
+ // Tagged targets with only erased usages and bindings with no surviving usages at all are droppable:
250
+ const isDroppable = (isTaggedImportTarget(checker, symbol) || !usage.has(symbol)) && usage.get(symbol) !== true;
251
+ if (isDroppable) {
252
+ continue;
253
+ }
254
+ kept.push(specifier);
255
+ hasRuntimeBindings = true;
256
+ }
257
+ if (kept.length === clause.namedBindings.elements.length) {
258
+ return context.superTransformStatements(statement);
259
+ }
260
+ if (!hasRuntimeBindings) {
261
+ const target = (0, virtual_1.resolveModuleSourceFile)(checker, statement.moduleSpecifier);
262
+ if (target !== null && (0, virtual_1.isPureConstantsModule)(checker, target)) {
263
+ return [];
264
+ }
265
+ // Side effects are possible, keep the module require without bindings:
266
+ return context.superTransformStatements(ts.factory.updateImportDeclaration(statement, statement.modifiers, undefined, statement.moduleSpecifier, undefined));
267
+ }
268
+ return context.superTransformStatements(ts.factory.updateImportDeclaration(statement, statement.modifiers, ts.factory.updateImportClause(clause, clause.isTypeOnly, clause.name, ts.factory.updateNamedImports(clause.namedBindings, kept)), statement.moduleSpecifier, undefined));
269
+ }
@@ -0,0 +1,26 @@
1
+ import * as ts from "typescript";
2
+ import type * as lua from "typescript-to-lua";
3
+ /**
4
+ * Validate `@inline` tagged variable statement and push diagnostics for not supported shapes.
5
+ * Tagged statements must be module-level `const` declarations with compile-time computable scalars
6
+ * or flat `as const` object literals.
7
+ *
8
+ * @param statement - Tagged variable statement to validate.
9
+ * @param context - Transformation context.
10
+ */
11
+ export declare function validateVariableStatement(statement: ts.VariableStatement, context: lua.TransformationContext): void;
12
+ /**
13
+ * Validate `@inline` tagged enum declaration and push diagnostics for members without computable values.
14
+ * Members may be constant for TypeScript or initialized with foldable engine references.
15
+ *
16
+ * @param declaration - Tagged enum declaration to validate.
17
+ * @param context - Transformation context.
18
+ */
19
+ export declare function validateEnumDeclaration(declaration: ts.EnumDeclaration, context: lua.TransformationContext): void;
20
+ /**
21
+ * Scan program source files for `@inline` tags on unsupported declaration kinds.
22
+ *
23
+ * @param program - Program to scan.
24
+ * @returns List of produced diagnostics.
25
+ */
26
+ export declare function validateTopLevelTags(program: ts.Program): Array<ts.Diagnostic>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateVariableStatement = validateVariableStatement;
4
+ exports.validateEnumDeclaration = validateEnumDeclaration;
5
+ exports.validateTopLevelTags = validateTopLevelTags;
6
+ const ts = require("typescript");
7
+ const ast_1 = require("./ast");
8
+ const errors_1 = require("./errors");
9
+ const evaluation_1 = require("./evaluation");
10
+ /**
11
+ * Validate `@inline` tagged variable statement and push diagnostics for not supported shapes.
12
+ * Tagged statements must be module-level `const` declarations with compile-time computable scalars
13
+ * or flat `as const` object literals.
14
+ *
15
+ * @param statement - Tagged variable statement to validate.
16
+ * @param context - Transformation context.
17
+ */
18
+ function validateVariableStatement(statement, context) {
19
+ if (!ts.isSourceFile(statement.parent)) {
20
+ context.diagnostics.push((0, errors_1.createNotModuleLevelError)(statement));
21
+ }
22
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
23
+ context.diagnostics.push((0, errors_1.createNotConstError)(statement));
24
+ return;
25
+ }
26
+ for (const declaration of statement.declarationList.declarations) {
27
+ const name = declaration.name.getText();
28
+ const initializer = (0, ast_1.unwrapInitializer)(declaration.initializer);
29
+ if (initializer !== null && ts.isObjectLiteralExpression(initializer)) {
30
+ if (!(0, ast_1.hasAsConstAssertion)(declaration.initializer)) {
31
+ context.diagnostics.push((0, errors_1.createNotAsConstObjectError)(declaration, name));
32
+ continue;
33
+ }
34
+ const type = context.checker.getTypeAtLocation(declaration);
35
+ for (const property of type.getProperties()) {
36
+ const propertyDeclaration = property.valueDeclaration;
37
+ const sourceStatement = propertyDeclaration === undefined ? null : (0, ast_1.getContainingVariableStatement)(propertyDeclaration);
38
+ if (sourceStatement !== statement && (sourceStatement === null || !(0, ast_1.hasInlineTag)(sourceStatement))) {
39
+ context.diagnostics.push((0, errors_1.createForeignPropertyError)(propertyDeclaration ?? declaration, name, property.name));
40
+ }
41
+ else if ((0, evaluation_1.getComputedDeclarationValue)(context.checker, property, new Set()) === null) {
42
+ context.diagnostics.push((0, errors_1.createNotLiteralPropertyError)(propertyDeclaration ?? declaration, name, property.name));
43
+ }
44
+ }
45
+ }
46
+ else {
47
+ const symbol = context.checker.getSymbolAtLocation(declaration.name);
48
+ if (symbol === undefined || (0, evaluation_1.getComputedDeclarationValue)(context.checker, symbol, new Set()) === null) {
49
+ context.diagnostics.push((0, errors_1.createNotLiteralConstantError)(declaration, name));
50
+ }
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * Validate `@inline` tagged enum declaration and push diagnostics for members without computable values.
56
+ * Members may be constant for TypeScript or initialized with foldable engine references.
57
+ *
58
+ * @param declaration - Tagged enum declaration to validate.
59
+ * @param context - Transformation context.
60
+ */
61
+ function validateEnumDeclaration(declaration, context) {
62
+ for (const member of declaration.members) {
63
+ if (!(0, evaluation_1.isComputableEnumMember)(context.checker, member)) {
64
+ context.diagnostics.push((0, errors_1.createNotConstantEnumMemberError)(member, member.name.getText()));
65
+ }
66
+ }
67
+ }
68
+ /**
69
+ * Scan program source files for `@inline` tags on unsupported declaration kinds.
70
+ *
71
+ * @param program - Program to scan.
72
+ * @returns List of produced diagnostics.
73
+ */
74
+ function validateTopLevelTags(program) {
75
+ const diagnostics = [];
76
+ for (const sourceFile of program.getSourceFiles()) {
77
+ if (sourceFile.isDeclarationFile) {
78
+ continue;
79
+ }
80
+ for (const statement of sourceFile.statements) {
81
+ if ((0, ast_1.hasInlineTag)(statement) && !ts.isVariableStatement(statement) && !ts.isEnumDeclaration(statement)) {
82
+ diagnostics.push((0, errors_1.createUnsupportedDeclarationError)(statement));
83
+ }
84
+ }
85
+ }
86
+ return diagnostics;
87
+ }
@@ -0,0 +1,74 @@
1
+ import * as ts from "typescript";
2
+ import { type TFoldedValue } from "./constants";
3
+ /**
4
+ * Resolve a symbol through import aliases.
5
+ *
6
+ * @param checker - Program type checker.
7
+ * @param symbol - Symbol to resolve.
8
+ * @returns Resolved symbol.
9
+ */
10
+ export declare function resolveAliasedSymbol(checker: ts.TypeChecker, symbol: ts.Symbol): ts.Symbol;
11
+ /**
12
+ * Get the `@virtual` declaration behind the provided symbol, if any.
13
+ *
14
+ * @param checker - Program type checker.
15
+ * @param symbol - Symbol to check.
16
+ * @returns Virtual tagged variable statement, enum declaration, or null.
17
+ */
18
+ export declare function getVirtualDeclaration(checker: ts.TypeChecker, symbol: ts.Symbol): ts.VariableStatement | ts.EnumDeclaration | null;
19
+ /**
20
+ * Check whether an identifier reference to a tagged declaration is erasable.
21
+ * Erasable references are inlined, expanded, or stripped during transformation.
22
+ *
23
+ * @param checker - Program type checker.
24
+ * @param node - Identifier reference to check.
25
+ * @param symbol - Resolved symbol of the identifier.
26
+ * @param isVirtualSpreadAllowed - Whether object spread positions count as erasable.
27
+ * @returns Whether the reference is erasable.
28
+ */
29
+ export declare function isErasableReference(checker: ts.TypeChecker, node: ts.Identifier, symbol: ts.Symbol, isVirtualSpreadAllowed: boolean): boolean;
30
+ /**
31
+ * Resolve the source file for a module referenced by an import or export declaration specifier.
32
+ *
33
+ * @param checker - Program type checker.
34
+ * @param moduleSpecifier - Module specifier expression.
35
+ * @returns Resolved source file or null.
36
+ */
37
+ export declare function resolveModuleSourceFile(checker: ts.TypeChecker, moduleSpecifier: ts.Expression): ts.SourceFile | null;
38
+ /**
39
+ * Check whether a module has no side effects on load. This silent analysis decides whether a require can be dropped.
40
+ * Pure modules contain only type-only imports, tagged constants, constant enums, types and
41
+ * imports or re-exports of other pure modules, including barrel files.
42
+ *
43
+ * @param checker - Program type checker.
44
+ * @param sourceFile - Module source file to check.
45
+ * @param seen - Modules on current resolution path, guards from circular imports.
46
+ * @returns Whether module load has no side effects.
47
+ */
48
+ export declare function isPureConstantsModule(checker: ts.TypeChecker, sourceFile: ts.SourceFile, seen?: Set<ts.SourceFile>): boolean;
49
+ /**
50
+ * Validate strict purity for a module containing `@virtual` declarations.
51
+ * This is stricter than silent analysis: value imports and value re-exports are rejected even from pure modules,
52
+ * so erasing requires to this module can never silently drop transitive loads.
53
+ *
54
+ * @param checker - Program type checker.
55
+ * @param sourceFile - Module source file to validate.
56
+ * @returns List of produced diagnostics.
57
+ */
58
+ export declare function validateVirtualModulePurity(checker: ts.TypeChecker, sourceFile: ts.SourceFile): Array<ts.Diagnostic>;
59
+ /**
60
+ * Scan the program for references to `@virtual` declarations that cannot be erased, and for impure modules
61
+ * containing `@virtual` declarations.
62
+ *
63
+ * @param program - Program to scan.
64
+ * @returns List of produced diagnostics.
65
+ */
66
+ export declare function collectVirtualDiagnostics(program: ts.Program): Array<ts.Diagnostic>;
67
+ /**
68
+ * Get entries of a virtual object declaration for spread expansion.
69
+ *
70
+ * @param checker - Program type checker.
71
+ * @param symbol - Virtual object symbol.
72
+ * @returns List of property name/value pairs, or null when an entry is not computable.
73
+ */
74
+ export declare function getVirtualObjectEntries(checker: ts.TypeChecker, symbol: ts.Symbol): Array<[string, TFoldedValue]> | null;