Skip to content
Closed
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,8 +56,9 @@ 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."
);

export const emitPathCollision = createDiagnosticFactory(
Expand Down
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