Skip to content
Merged
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 package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 49 additions & 28 deletions src/transformation/utils/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,126 +3,147 @@ import { LuaTarget } from "../../CompilerOptions";
import { createSerialDiagnosticFactory } from "../../utils";
import { AnnotationKind } from "./annotations";

const createDiagnosticFactory = <TArgs extends any[]>(message: string | ((...args: TArgs) => string)) =>
type MessageProvider<TArgs extends any[]> = string | ((...args: TArgs) => string);

const createDiagnosticFactory = <TArgs extends any[]>(
category: ts.DiagnosticCategory,
message: MessageProvider<TArgs>
) =>
createSerialDiagnosticFactory((node: ts.Node, ...args: TArgs) => ({
file: node.getSourceFile(),
start: node.getStart(),
length: node.getWidth(),
messageText: typeof message === "string" ? message : message(...args),
category,
}));

export const unsupportedNodeKind = createDiagnosticFactory(
const createErrorDiagnosticFactory = <TArgs extends any[]>(message: MessageProvider<TArgs>) =>
createDiagnosticFactory(ts.DiagnosticCategory.Error, message);
const createWarningDiagnosticFactory = <TArgs extends any[]>(message: MessageProvider<TArgs>) =>
createDiagnosticFactory(ts.DiagnosticCategory.Warning, message);

export const unsupportedNodeKind = createErrorDiagnosticFactory(
(kind: ts.SyntaxKind) => `Unsupported node kind ${ts.SyntaxKind[kind]}`
);

export const forbiddenForIn = createDiagnosticFactory("Iterating over arrays with 'for ... in' is not allowed.");
export const forbiddenForIn = createErrorDiagnosticFactory("Iterating over arrays with 'for ... in' is not allowed.");

export const unsupportedNoSelfFunctionConversion = createDiagnosticFactory((name?: string) => {
export const unsupportedNoSelfFunctionConversion = createErrorDiagnosticFactory((name?: string) => {
const nameReference = name ? ` '${name}'` : "";
return (
`Unable to convert function with a 'this' parameter to function${nameReference} with no 'this'. ` +
"To fix, wrap in an arrow function, or declare with 'this: void'."
);
});

export const unsupportedSelfFunctionConversion = createDiagnosticFactory((name?: string) => {
export const unsupportedSelfFunctionConversion = createErrorDiagnosticFactory((name?: string) => {
const nameReference = name ? ` '${name}'` : "";
return (
`Unable to convert function with no 'this' parameter to function${nameReference} with 'this'. ` +
"To fix, wrap in an arrow function, or declare with 'this: any'."
);
});

export const unsupportedOverloadAssignment = createDiagnosticFactory((name?: string) => {
export const unsupportedOverloadAssignment = createErrorDiagnosticFactory((name?: string) => {
const nameReference = name ? ` to '${name}'` : "";
return (
`Unsupported assignment of function with different overloaded types for 'this'${nameReference}. ` +
"Overloads should all have the same type for 'this'."
);
});

export const decoratorInvalidContext = createDiagnosticFactory("Decorator function cannot have 'this: void'.");
export const decoratorInvalidContext = createErrorDiagnosticFactory("Decorator function cannot have 'this: void'.");

export const annotationInvalidArgumentCount = createDiagnosticFactory(
export const annotationInvalidArgumentCount = createErrorDiagnosticFactory(
(kind: AnnotationKind, got: number, expected: number) => `'@${kind}' expects ${expected} arguments, but got ${got}.`
);

export const extensionCannotConstruct = createDiagnosticFactory(
export const extensionCannotConstruct = createErrorDiagnosticFactory(
"Cannot construct classes with '@extension' or '@metaExtension' annotation."
);

export const extensionCannotExtend = createDiagnosticFactory(
export const extensionCannotExtend = createErrorDiagnosticFactory(
"Cannot extend classes with '@extension' or '@metaExtension' annotation."
);

export const extensionCannotExport = createDiagnosticFactory(
export const extensionCannotExport = createErrorDiagnosticFactory(
"Cannot export classes with '@extension' or '@metaExtension' annotation."
);

export const extensionInvalidInstanceOf = createDiagnosticFactory(
export const extensionInvalidInstanceOf = createErrorDiagnosticFactory(
"Cannot use instanceof on classes with '@extension' or '@metaExtension' annotation."
);

export const extensionAndMetaExtensionConflict = createDiagnosticFactory(
export const extensionAndMetaExtensionConflict = createErrorDiagnosticFactory(
"Cannot use both '@extension' and '@metaExtension' annotations on the same class."
);

export const metaExtensionMissingExtends = createDiagnosticFactory(
export const metaExtensionMissingExtends = createErrorDiagnosticFactory(
"'@metaExtension' annotation requires the extension of the metatable class."
);

export const invalidForRangeCall = createDiagnosticFactory((message: string) => `Invalid @forRange call: ${message}.`);
export const invalidForRangeCall = createErrorDiagnosticFactory(
(message: string) => `Invalid @forRange call: ${message}.`
);

export const luaTableMustBeAmbient = createDiagnosticFactory(
export const luaTableMustBeAmbient = createErrorDiagnosticFactory(
"Classes with the '@luaTable' annotation must be ambient."
);

export const luaTableCannotBeExtended = createDiagnosticFactory(
export const luaTableCannotBeExtended = createErrorDiagnosticFactory(
"Cannot extend classes with the '@luaTable' annotation."
);

export const luaTableInvalidInstanceOf = createDiagnosticFactory(
export const luaTableInvalidInstanceOf = createErrorDiagnosticFactory(
"The instanceof operator cannot be used with a '@luaTable' class."
);

export const luaTableCannotBeAccessedDynamically = createDiagnosticFactory("@luaTable cannot be accessed dynamically.");
export const luaTableCannotBeAccessedDynamically = createErrorDiagnosticFactory(
"@luaTable cannot be accessed dynamically."
);

export const luaTableForbiddenUsage = createDiagnosticFactory(
export const luaTableForbiddenUsage = createErrorDiagnosticFactory(
(description: string) => `Invalid @luaTable usage: ${description}.`
);

export const luaIteratorForbiddenUsage = createDiagnosticFactory(
export const luaIteratorForbiddenUsage = createErrorDiagnosticFactory(
"Unsupported use of lua iterator with '@tupleReturn' annotation in for...of statement. " +
"You must use a destructuring statement to catch results from a lua iterator with " +
"the '@tupleReturn' annotation."
);

export const unsupportedAccessorInObjectLiteral = createDiagnosticFactory(
export const unsupportedAccessorInObjectLiteral = createErrorDiagnosticFactory(
"Accessors in object literal are not supported."
);

export const unsupportedRightShiftOperator = createDiagnosticFactory(
export const unsupportedRightShiftOperator = createErrorDiagnosticFactory(
"Right shift operator is not supported for target Lua 5.3. Use `>>>` instead."
);

const getLuaTargetName = (version: LuaTarget) => (version === LuaTarget.LuaJIT ? "LuaJIT" : `Lua ${version}`);
export const unsupportedForTarget = createDiagnosticFactory(
export const unsupportedForTarget = createErrorDiagnosticFactory(
(functionality: string, version: Exclude<LuaTarget, LuaTarget.Universal>) =>
`${functionality} is/are not supported for target ${getLuaTargetName(version)}.`
);

export const unsupportedProperty = createDiagnosticFactory(
export const unsupportedProperty = createErrorDiagnosticFactory(
(parentName: string, property: string) => `${parentName}.${property} is unsupported.`
);

export const invalidAmbientIdentifierName = createDiagnosticFactory(
export const invalidAmbientIdentifierName = createErrorDiagnosticFactory(
(text: string) => `Invalid ambient identifier name '${text}'. Ambient identifiers must be valid lua identifiers.`
);

export const unresolvableRequirePath = createDiagnosticFactory(
export const unresolvableRequirePath = createErrorDiagnosticFactory(
(path: string) => `Cannot create require path. Module '${path}' does not exist within --rootDir.`
);

export const unsupportedVarDeclaration = createDiagnosticFactory(
export const unsupportedVarDeclaration = createErrorDiagnosticFactory(
"`var` declarations are not supported. Use `let` or `const` instead."
);

export const annotationDeprecated = createWarningDiagnosticFactory(
(kind: AnnotationKind) =>
`'@${kind}' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. ` +
`See https://typescripttolua.github.io/docs/advanced/compiler-annotations#${kind.toLowerCase()} for more information.`
);
8 changes: 8 additions & 0 deletions src/transformation/visitors/class/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getOrUpdate } from "../../../utils";
import { FunctionVisitor, TransformationContext } from "../../context";
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
import {
annotationDeprecated,
extensionAndMetaExtensionConflict,
extensionCannotExport,
extensionCannotExtend,
Expand Down Expand Up @@ -94,6 +95,13 @@ function transformClassLikeDeclaration(
const isExtension = extensionDirective !== undefined;
const isMetaExtension = annotations.has(AnnotationKind.MetaExtension);

if (isExtension) {
context.diagnostics.push(annotationDeprecated(classDeclaration, AnnotationKind.Extension));
}
if (isMetaExtension) {
context.diagnostics.push(annotationDeprecated(classDeclaration, AnnotationKind.MetaExtension));
}

if (isExtension && isMetaExtension) {
context.diagnostics.push(extensionAndMetaExtensionConflict(classDeclaration));
}
Expand Down
5 changes: 5 additions & 0 deletions src/transformation/visitors/class/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as ts from "typescript";
import { TransformationContext } from "../../context";
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
import { annotationDeprecated } from "../../utils/diagnostics";

export function isStaticNode(node: ts.Node): boolean {
return (node.modifiers ?? []).some(m => m.kind === ts.SyntaxKind.StaticKeyword);
Expand All @@ -22,6 +23,10 @@ export function getExtendedNode(
if (!annotations.has(AnnotationKind.PureAbstract)) {
return extendsClause.types[0];
}

if (annotations.has(AnnotationKind.PureAbstract)) {
context.diagnostics.push(annotationDeprecated(extendsClause, AnnotationKind.PureAbstract));
}
}

export function getExtendedType(
Expand Down
2 changes: 2 additions & 0 deletions src/transformation/visitors/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as ts from "typescript";
import * as lua from "../../LuaAST";
import { FunctionVisitor, TransformationContext } from "../context";
import { AnnotationKind, getTypeAnnotations } from "../utils/annotations";
import { annotationDeprecated } from "../utils/diagnostics";
import { addExportToIdentifier, createExportedIdentifier, getIdentifierExportScope } from "../utils/export";
import {
createHoistableVariableDeclarationStatement,
Expand Down Expand Up @@ -53,6 +54,7 @@ export const transformModuleDeclaration: FunctionVisitor<ts.ModuleDeclaration> =
const annotations = getTypeAnnotations(context.checker.getTypeAtLocation(node));
// If phantom namespace elide the declaration and return the body
if (annotations.has(AnnotationKind.Phantom) && node.body && ts.isModuleBlock(node.body)) {
context.diagnostics.push(annotationDeprecated(node, AnnotationKind.Phantom));
return context.transformStatements(node.body.statements);
}

Expand Down
9 changes: 6 additions & 3 deletions test/legacy-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export function transpileString(
const { diagnostics, file } = transpileStringResult(str, options);
expect(file.lua).toBeDefined();

const errors = diagnostics.filter(d => !ignoreDiagnostics || d.source === "typescript-to-lua");
const errors = diagnostics.filter(
d => (!ignoreDiagnostics || d.source === "typescript-to-lua") && d.category === ts.DiagnosticCategory.Error
);
expect(errors).not.toHaveDiagnostics();

return file.lua!.trim();
Expand Down Expand Up @@ -86,14 +88,15 @@ export function transpileAndExecute(
tsStr: string,
compilerOptions?: tstl.CompilerOptions,
luaHeader?: string,
tsHeader?: string
tsHeader?: string,
ignoreDiagnostics = false
): any {
const wrappedTsString = `${tsHeader ?? ""}
declare function JSONStringify(this: void, p: any): string;
function __runTest(this: void): any {${tsStr}}`;

const lua = `${luaHeader ?? ""}
${transpileString(wrappedTsString, compilerOptions, false)}
${transpileString(wrappedTsString, compilerOptions, ignoreDiagnostics)}
return __runTest();`;

return executeLua(lua);
Expand Down
2 changes: 2 additions & 0 deletions test/translation/transformation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as path from "path";
import * as tstl from "../../src";
import { annotationDeprecated } from "../../src/transformation/utils/diagnostics";
import * as util from "../util";

const fixturesPath = path.join(__dirname, "./transformation");
Expand All @@ -13,6 +14,7 @@ const fixtures = fs
test.each(fixtures)("Transformation (%s)", (_name, content) => {
util.testModule(content)
.setOptions({ luaLibImport: tstl.LuaLibImportKind.Require })
.ignoreDiagnostics([annotationDeprecated.code])
.disableSemanticCheck()
.expectLuaToMatchSnapshot();
});
26 changes: 26 additions & 0 deletions test/unit/annotations/__snapshots__/deprecated.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`extension deprecation: code 1`] = `""`;

exports[`extension deprecation: code 2`] = `"local __meta__A = debug.getregistry().A"`;

exports[`extension deprecation: diagnostics 1`] = `"main.ts(4,9): warning TSTL: '@extension' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. See https://typescripttolua.github.io/docs/advanced/compiler-annotations#extension for more information."`;

exports[`extension deprecation: diagnostics 2`] = `"main.ts(4,9): warning TSTL: '@metaExtension' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. See https://typescripttolua.github.io/docs/advanced/compiler-annotations#metaextension for more information."`;

exports[`phantom deprecation: code 1`] = `
"function nsMember(self)
end"
`;

exports[`phantom deprecation: diagnostics 1`] = `"main.ts(3,9): warning TSTL: '@phantom' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. See https://typescripttolua.github.io/docs/advanced/compiler-annotations#phantom for more information."`;

exports[`pureAbstract deprecation: code 1`] = `
"require(\\"lualib_bundle\\");
ClassB = __TS__Class()
ClassB.name = \\"ClassB\\"
function ClassB.prototype.____constructor(self)
end"
`;

exports[`pureAbstract deprecation: diagnostics 1`] = `"main.ts(4,22): warning TSTL: '@pureAbstract' is deprecated and will be removed in a future update. Please update your code before upgrading to the next release, otherwise your project will no longer compile. See https://typescripttolua.github.io/docs/advanced/compiler-annotations#pureabstract for more information."`;
Loading