Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"lib": ["esnext"],
// Dev types are JIT
"types": ["lua-types/jit", "@typescript-to-lua/language-extensions"],
"moduleResolution": "node",
"moduleResolution": "bundler",
"outDir": "dist",
"rootDir": "src",
"strict": true,
Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ export default tseslint.config(
{
ignores: [
".github/scripts/create_benchmark_check.js",
"coverage/",
"dist/",
"eslint.config.mjs",
"jest.config.js",
Expand Down
659 changes: 292 additions & 367 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"lint:prettier": "prettier --check . || (echo 'Run `npm run fix:prettier` to fix it.' && exit 1)",
"lint:eslint": "eslint .",
"fix:prettier": "prettier --write .",
"check:language-extensions": "tsc --strict language-extensions/index.d.ts",
"check:language-extensions": "tsc -p language-extensions/tsconfig.json",
"preversion": "npm run build && npm test",
"postversion": "git push && git push --tags"
},
Expand All @@ -42,7 +42,7 @@
"node": ">=16.10.0"
},
"peerDependencies": {
"typescript": "5.9.3"
"typescript": "6.0.2"
},
"dependencies": {
"@typescript-to-lua/language-extensions": "1.19.0",
Expand All @@ -67,9 +67,12 @@
"lua-types": "^2.14.1",
"lua-wasm-bindings": "^0.5.3",
"prettier": "^2.8.8",
"ts-jest": "^29.2.5",
"ts-jest": "^29.4.9",
"ts-node": "^10.9.2",
"typescript": "5.9.3",
"typescript-eslint": "^8.46.3"
"typescript": "6.0.2",
"typescript-eslint": "^8.58.0"
},
"overrides": {
"typescript": "$typescript"
}
}
4 changes: 2 additions & 2 deletions src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export function validateOptions(options: CompilerOptions): ts.Diagnostic[] {
diagnostics.push(diagnosticFactories.unsupportedJsxEmit());
}

if (options.paths && !options.baseUrl) {
diagnostics.push(diagnosticFactories.pathsWithoutBaseUrl());
if (options.paths && Object.keys(options.paths).length > 0 && !options.baseUrl && !options.configFilePath) {
diagnostics.push(diagnosticFactories.pathsWithoutBaseDirectory());
}

return diagnostics;
Expand Down
4 changes: 1 addition & 3 deletions src/transformation/utils/function-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ function computeDeclarationContextType(context: TransformationContext, signature
const thisParameter = getExplicitThisParameter(signatureDeclaration);
if (thisParameter) {
// Explicit 'this'
return thisParameter.type && thisParameter.type.kind === ts.SyntaxKind.VoidKeyword
? ContextType.Void
: ContextType.NonVoid;
return thisParameter.type?.kind === ts.SyntaxKind.VoidKeyword ? ContextType.Void : ContextType.NonVoid;
}

// noSelf declaration on function signature
Expand Down
2 changes: 1 addition & 1 deletion src/transformation/visitors/variable-declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function transformBindingPattern(
}

// Build the path to the table
const tableExpression = propertyAccessStack.reduce<lua.Expression>(
const tableExpression = propertyAccessStack.reduce(
(path, property) => lua.createTableIndexExpression(path, transformPropertyName(context, property)),
table
);
Expand Down
5 changes: 3 additions & 2 deletions src/transpilation/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const cannotBundleLibrary = createDiagnosticFactory(

export const unsupportedJsxEmit = createDiagnosticFactory(() => 'JSX is only supported with "react" jsx option.');

export const pathsWithoutBaseUrl = createDiagnosticFactory(
() => "When configuring 'paths' in tsconfig.json, the option 'baseUrl' must also be provided."
export const pathsWithoutBaseDirectory = createDiagnosticFactory(
() =>
"When using 'paths' without 'baseUrl', a tsconfig.json must be present so paths can be resolved relative to it."
);
24 changes: 11 additions & 13 deletions src/transpilation/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { couldNotReadDependency, couldNotResolveRequire } from "./diagnostics";
import { BuildMode, CompilerOptions } from "../CompilerOptions";
import { findLuaRequires, LuaRequire } from "./find-lua-requires";
import { Plugin } from "./plugins";
import * as picomatch from "picomatch";
import picomatch from "picomatch";

const resolver = resolve.ResolverFactory.createResolver({
extensions: [".lua"],
Expand All @@ -28,6 +28,7 @@ interface ResolutionResult {

class ResolutionContext {
private noResolvePaths: picomatch.Matcher[];
private pathsBase: string | undefined;

public diagnostics: ts.Diagnostic[] = [];
public resolvedFiles = new Map<string, ProcessedFile>();
Expand All @@ -39,8 +40,8 @@ class ResolutionContext {
private readonly plugins: Plugin[]
) {
const unique = [...new Set(options.noResolvePaths)];
const matchers = unique.map(x => picomatch(x));
this.noResolvePaths = matchers;
this.noResolvePaths = unique.map(x => picomatch(x));
this.pathsBase = options.baseUrl ?? (options.configFilePath ? path.dirname(options.configFilePath) : undefined);
}

public addAndResolveDependencies(file: ProcessedFile): void {
Expand Down Expand Up @@ -210,21 +211,18 @@ class ResolutionContext {
if (resolvedNodeModulesFile) return resolvedNodeModulesFile;
}

// Bare specifiers: check paths mappings first, matching TypeScript's resolution order.
// TS never applies paths to relative imports, so skip for those.
if (!ts.isExternalModuleNameRelative(dependencyPath) && this.options.paths && this.pathsBase) {
const fileFromPaths = this.tryGetModuleNameFromPaths(dependencyPath, this.options.paths, this.pathsBase);
if (fileFromPaths) return fileFromPaths;
}

// Check if file is a file in the project
const resolvedPath = this.formatPathToFile(dependencyPath, requiringFile);
const fileFromPath = this.getFileFromPath(resolvedPath);
if (fileFromPath) return fileFromPath;

if (this.options.paths && this.options.baseUrl) {
// If no file found yet and paths are present, try to find project file via paths mappings
const fileFromPaths = this.tryGetModuleNameFromPaths(
dependencyPath,
this.options.paths,
this.options.baseUrl
);
if (fileFromPaths) return fileFromPaths;
}

// Not a TS file in our project sources, use resolver to check if we can find dependency
try {
const resolveResult = resolver.resolveSync({}, fileDirectory, dependencyPath);
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function cast<TOriginal, TCast extends TOriginal>(
}

export function assert(value: any, message?: string | Error): asserts value {
nativeAssert(value, message);
nativeAssert.ok(value, message);
}

export function assertNever(_value: never): never {
Expand Down
2 changes: 1 addition & 1 deletion test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ declare global {

expect.extend({
toHaveDiagnostics(diagnostics: ts.Diagnostic[], expected?: number[]): jest.CustomMatcherResult {
assert(Array.isArray(diagnostics));
assert.ok(Array.isArray(diagnostics));
// @ts-ignore
const matcherHint = this.utils.matcherHint("toHaveDiagnostics", undefined, "", this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ exports[`supports complicated paths configuration 1`] = `

exports[`supports paths configuration 1`] = `
[
"/paths-simple/myprogram/dist/main.lua",
"/paths-simple/myprogram/dist/mypackage/bar.lua",
"/paths-simple/myprogram/dist/mypackage/index.lua",
"/paths-simple/myprogram/dist/myprogram/main.lua",
]
`;
22 changes: 18 additions & 4 deletions test/transpile/module-resolution.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as util from "../util";
import * as ts from "typescript";
import { BuildMode } from "../../src";
import { normalizeSlashes } from "../../src/utils";
import { pathsWithoutBaseUrl } from "../../src/transpilation/diagnostics";

describe("basic module resolution", () => {
const projectPath = path.resolve(__dirname, "module-resolution", "project-with-node-modules");
Expand Down Expand Up @@ -471,6 +470,8 @@ describe("module resolution should not try to resolve modules in noResolvePaths"
export function foo(): void;
}`
)
// TS 6.0 noUncheckedSideEffectImports requires declaration for "preload" module
.addExtraFile("preload.d.ts", `declare module "preload" {}`)
.setOptions({ noResolvePaths: ["ignore*"] })
.expectToHaveNoDiagnostics()
.expectToEqual({ result: "foo" });
Expand Down Expand Up @@ -598,7 +599,7 @@ test("module resolution uses baseURL to resolve imported files", () => {
return { baz = function() return "baz" end }
`
)
.setOptions({ baseUrl: "./myproject/mydeps" })
.setOptions({ baseUrl: "./myproject/mydeps", ignoreDeprecations: "6.0" })
.expectToEqual({
fooResult: "foo",
barResult: "bar",
Expand Down Expand Up @@ -707,8 +708,21 @@ test("supports complicated paths configuration", () => {
.expectToEqual({ foo: 314, bar: 271 });
});

test("paths without baseUrl is error", () => {
util.testFunction``.setOptions({ paths: {} }).expectToHaveDiagnostics([pathsWithoutBaseUrl.code]);
test("paths without baseUrl is not an error", () => {
util.testFunction``.setOptions({ paths: {} }).expectToHaveNoDiagnostics();
});

test("supports paths configuration without baseUrl", () => {
const baseProjectPath = path.resolve(__dirname, "module-resolution", "paths-no-baseurl");
const projectPath = path.join(baseProjectPath, "myprogram");
const projectTsConfig = path.join(projectPath, "tsconfig.json");
const mainFile = path.join(projectPath, "main.ts");

// Bundle to have all files required to execute and check result
util.testProject(projectTsConfig)
.setMainFileName(mainFile)
.setOptions({ luaBundle: "bundle.lua", luaBundleEntry: mainFile })
.expectToEqual({ foo: 314, bar: 271 });
});

test("module resolution using plugin", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"compilerOptions": {
"rootDir": ".",
"baseUrl": ".",
"ignoreDeprecations": "6.0",
"paths": {
"mypackage": ["packages/mypackage/src/index.ts"],
"mypackage/*": ["packages/mypackage/src/*"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const bar = 271;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const foo = 314;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { foo } from "myOtherPackage";
import { bar } from "myOtherPackage/bar";

export { foo, bar };
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"rootDir": "..",
"outDir": "dist",
"paths": {
"myOtherPackage": ["../mypackage"],
"myOtherPackage/*": ["../mypackage/*"]
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"ignoreDeprecations": "6.0",
"rootDir": "..",
"outDir": "dist",
"paths": {
"myOtherPackage": ["../mypackage"],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"target": "esnext",
"lib": ["esnext"],
"types": [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"target": "esnext",
"lib": ["esnext"],
"types": [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"noUnusedLocals": true,
"noUnusedParameters": true,
"target": "esnext",
"lib": ["esnext"],
"types": [],
"rootDir": "."
"rootDir": ".",
"noUncheckedSideEffectImports": false
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"target": "esnext",
"lib": ["esnext"],
"types": [],
Expand Down
2 changes: 1 addition & 1 deletion test/transpile/transformers/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const compilerOptions =
(options: tstl.CompilerOptions): ts.TransformerFactory<ts.SourceFile> =>
context =>
file => {
assert(options.plugins?.length === 1);
assert.ok(options.plugins?.length === 1);
return visitAndReplace(context, file, node => {
if (!ts.isReturnStatement(node)) return;
return ts.factory.updateReturnStatement(node, ts.factory.createTrue());
Expand Down
3 changes: 1 addition & 2 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
"types": ["node", "jest"],
"baseUrl": "."
"types": ["node", "jest"]
},
"include": [".", "../src"],
"exclude": [
Expand Down
8 changes: 2 additions & 6 deletions test/unit/__snapshots__/identifiers.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,14 @@ exports[`ambient identifier must be a valid lua identifier ("enum $$ {}"): code

exports[`ambient identifier must be a valid lua identifier ("enum $$ {}"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;

exports[`ambient identifier must be a valid lua identifier ("function $$();"): code 1`] = `"local ____ = _____24_24_24"`;
exports[`ambient identifier must be a valid lua identifier ("function $$(): void;"): code 1`] = `"local ____ = _____24_24_24"`;

exports[`ambient identifier must be a valid lua identifier ("function $$();"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;
exports[`ambient identifier must be a valid lua identifier ("function $$(): void;"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;

exports[`ambient identifier must be a valid lua identifier ("let $$: any;"): code 1`] = `"local ____ = _____24_24_24"`;

exports[`ambient identifier must be a valid lua identifier ("let $$: any;"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;

exports[`ambient identifier must be a valid lua identifier ("module $$ { export const bar: any; }"): code 1`] = `"local ____ = _____24_24_24"`;

exports[`ambient identifier must be a valid lua identifier ("module $$ { export const bar: any; }"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;

exports[`ambient identifier must be a valid lua identifier ("namespace $$ { export const bar: any; }"): code 1`] = `"local ____ = _____24_24_24"`;

exports[`ambient identifier must be a valid lua identifier ("namespace $$ { export const bar: any; }"): diagnostics 1`] = `"main.ts(3,9): error TSTL: Invalid ambient identifier name '$$$'. Ambient identifiers must be valid lua identifiers."`;
Expand Down
4 changes: 2 additions & 2 deletions test/unit/annotations/customConstructor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ test("CustomCreate", () => {
const tsHeader = `
/** @customConstructor Point2DCreate */
class Point2D {
public x: number;
public y: number;
public x!: number;
public y!: number;
constructor(x: number, y: number) {
// No values assigned
}
Expand Down
6 changes: 3 additions & 3 deletions test/unit/assignments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ test.each([

test("local variable declaration referencing self indirectly", () => {
util.testFunction`
let cb: () => void;
let cb!: () => void;

function foo(newCb: () => void) {
cb = newCb;
Expand All @@ -360,7 +360,7 @@ test("local variable declaration referencing self indirectly", () => {

test("local multiple variable declaration referencing self indirectly", () => {
util.testFunction`
let cb: () => void;
let cb!: () => void;

function foo(newCb: () => void) {
cb = newCb;
Expand Down Expand Up @@ -395,7 +395,7 @@ describe.each(["x &&= y", "x ||= y"])("boolean compound assignment (%p)", assign

test.each([undefined, 3])("nullish coalescing compound assignment", initialValue => {
util.testFunction`
let x: number = ${util.formatCode(initialValue)};
let x: number | undefined = ${util.formatCode(initialValue)};
x ??= 5;
return x;
`.expectToMatchJsResult();
Expand Down
Loading
Loading