From 7f6c720f8f1555cdd50bb8cfbd55fb97ffdff310 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 21 Feb 2021 08:48:21 -0700 Subject: [PATCH 1/2] Lua table extensions --- language-extensions/index.d.ts | 78 +++++++++++ src/transformation/utils/diagnostics.ts | 8 ++ .../utils/language-extensions.ts | 10 ++ src/transformation/utils/typescript/index.ts | 12 ++ src/transformation/visitors/call.ts | 19 +++ src/transformation/visitors/class/new.ts | 5 + .../visitors/expression-statement.ts | 5 + src/transformation/visitors/identifier.ts | 6 + .../visitors/language-extensions/operators.ts | 36 +---- .../visitors/language-extensions/table.ts | 80 +++++++++++ .../__snapshots__/table.spec.ts.snap | 64 +++++++++ test/unit/language-extensions/table.spec.ts | 130 ++++++++++++++++++ 12 files changed, 422 insertions(+), 31 deletions(-) create mode 100644 src/transformation/visitors/language-extensions/table.ts create mode 100644 test/unit/language-extensions/__snapshots__/table.spec.ts.snap create mode 100644 test/unit/language-extensions/table.spec.ts diff --git a/language-extensions/index.d.ts b/language-extensions/index.d.ts index 79067b1cd..e5d3096a3 100644 --- a/language-extensions/index.d.ts +++ b/language-extensions/index.d.ts @@ -430,3 +430,81 @@ declare type LuaLength = ((operand: TOperand) => TReturn) & L * @param TReturn The resulting (return) type of the operation. */ declare type LuaLengthMethod = (() => TReturn) & LuaExtension<"__luaLengthMethodBrand">; + +/** + * Calls to functions with this type are translated to `table[key]`. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TTable The type to access as a Lua table. + * @param TKey The type of the key to use to access the table. + * @param TValue The type of the value stored in the table. + */ +declare type LuaTableGet = ((table: TTable, key: TKey) => TValue) & + LuaExtension<"__luaTableGetBrand">; + +/** + * Calls to methods with this type are translated to `table[key]`, where `table` is the object with the method. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TKey The type of the key to use to access the table. + * @param TValue The type of the value stored in the table. + */ +declare type LuaTableGetMethod = ((key: TKey) => TValue) & + LuaExtension<"__luaTableGetMethodBrand">; + +/** + * Calls to functions with this type are translated to `table[key] = value`. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TTable The type to access as a Lua table. + * @param TKey The type of the key to use to access the table. + * @param TValue The type of the value to assign to the table. + */ +declare type LuaTableSet = (( + table: TTable, + key: TKey, + value: TValue +) => void) & + LuaExtension<"__luaTableSetBrand">; + +/** + * Calls to methods with this type are translated to `table[key] = value`, where `table` is the object with the method. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TKey The type of the key to use to access the table. + * @param TValue The type of the value to assign to the table. + */ +declare type LuaTableSetMethod = ((key: TKey, value: TValue) => void) & + LuaExtension<"__luaTableSetMethodBrand">; + +/** + * A convenience type for working directly with a Lua table. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TKey The type of the keys used to access the table. + * @param TValue The type of the values stored in the table. + */ +declare interface LuaTable { + length: LuaLengthMethod; + get: LuaTableGetMethod; + set: LuaTableSetMethod; +} + +/** + * A convenience type for working directly with a Lua table. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TKey The type of the keys used to access the table. + * @param TValue The type of the values stored in the table. + */ +declare type LuaTableConstructor = (new () => LuaTable) & + LuaExtension<"__luaTableNewBrand">; + +/** + * A convenience type for working directly with a Lua table. + * For more information see: https://typescripttolua.github.io/docs/advanced/language-extensions + * + * @param TKey The type of the keys used to access the table. + * @param TValue The type of the values stored in the table. + */ +declare const LuaTable: LuaTableConstructor; diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index e8873746a..eed0ba07f 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -174,6 +174,14 @@ export const invalidOperatorMappingUse = createErrorDiagnosticFactory( "This function must always be directly called and cannot be referred to." ); +export const invalidTableExtensionUse = createErrorDiagnosticFactory( + "This function must always be directly called and cannot be referred to." +); + +export const invalidTableSetExpression = createErrorDiagnosticFactory( + "Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement." +); + 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. ` + diff --git a/src/transformation/utils/language-extensions.ts b/src/transformation/utils/language-extensions.ts index efcd7d810..cb17987ad 100644 --- a/src/transformation/utils/language-extensions.ts +++ b/src/transformation/utils/language-extensions.ts @@ -42,6 +42,11 @@ export enum ExtensionKind { BitwiseNotOperatorMethodType = "BitwiseNotOperatorMethodType", LengthOperatorType = "LengthOperatorType", LengthOperatorMethodType = "LengthOperatorMethodType", + TableNewType = "TableNewType", + TableGetType = "TableGetType", + TableGetMethodType = "TableGetMethodType", + TableSetType = "TableSetType", + TableSetMethodType = "TableSetMethodType", } const extensionKindToFunctionName: { [T in ExtensionKind]?: string } = { @@ -90,6 +95,11 @@ const extensionKindToTypeBrand: { [T in ExtensionKind]: string } = { [ExtensionKind.BitwiseNotOperatorMethodType]: "__luaBitwiseNotMethodBrand", [ExtensionKind.LengthOperatorType]: "__luaLengthBrand", [ExtensionKind.LengthOperatorMethodType]: "__luaLengthMethodBrand", + [ExtensionKind.TableNewType]: "__luaTableNewBrand", + [ExtensionKind.TableGetType]: "__luaTableGetBrand", + [ExtensionKind.TableGetMethodType]: "__luaTableGetMethodBrand", + [ExtensionKind.TableSetType]: "__luaTableSetBrand", + [ExtensionKind.TableSetMethodType]: "__luaTableSetMethodBrand", }; export function isExtensionType(type: ts.Type, extensionKind: ExtensionKind): boolean { diff --git a/src/transformation/utils/typescript/index.ts b/src/transformation/utils/typescript/index.ts index 394d67656..46b4992ea 100644 --- a/src/transformation/utils/typescript/index.ts +++ b/src/transformation/utils/typescript/index.ts @@ -79,3 +79,15 @@ export function getAllCallSignatures(type: ts.Type): readonly ts.Signature[] { export function isExpressionWithEvaluationEffect(node: ts.Expression): boolean { return !(ts.isLiteralExpression(node) || ts.isIdentifier(node) || node.kind === ts.SyntaxKind.ThisKeyword); } + +export function getFunctionTypeForCall(context: TransformationContext, node: ts.CallExpression) { + const signature = context.checker.getResolvedSignature(node); + if (!signature || !signature.declaration) { + return; + } + const typeDeclaration = findFirstNodeAbove(signature.declaration, ts.isTypeAliasDeclaration); + if (!typeDeclaration) { + return; + } + return context.checker.getTypeFromTypeNode(typeDeclaration.type); +} diff --git a/src/transformation/visitors/call.ts b/src/transformation/visitors/call.ts index f29cbd4e9..7ea22eb34 100644 --- a/src/transformation/visitors/call.ts +++ b/src/transformation/visitors/call.ts @@ -13,6 +13,13 @@ import { transformElementAccessArgument } from "./access"; import { transformLuaTableCallExpression } from "./lua-table"; import { shouldMultiReturnCallBeWrapped, returnsMultiType } from "./language-extensions/multi"; import { isOperatorMapping, transformOperatorMappingExpression } from "./language-extensions/operators"; +import { + isTableGetCall, + isTableSetCall, + transformTableGetExpression, + transformTableSetExpression, +} from "./language-extensions/table"; +import { invalidTableSetExpression } from "../utils/diagnostics"; export type PropertyCallExpression = ts.CallExpression & { expression: ts.PropertyAccessExpression }; @@ -214,6 +221,18 @@ export const transformCallExpression: FunctionVisitor = (node return transformOperatorMappingExpression(context, node); } + if (isTableGetCall(context, node)) { + return transformTableGetExpression(context, node); + } + + if (isTableSetCall(context, node)) { + context.diagnostics.push(invalidTableSetExpression(node)); + return createImmediatelyInvokedFunctionExpression( + [transformTableSetExpression(context, node)], + lua.createNilLiteral() + ); + } + if (ts.isPropertyAccessExpression(node.expression)) { const result = transformPropertyCall(context, node as PropertyCallExpression); return wrapResult ? wrapInTable(result) : result; diff --git a/src/transformation/visitors/class/new.ts b/src/transformation/visitors/class/new.ts index da1c2f124..bf3935b7d 100644 --- a/src/transformation/visitors/class/new.ts +++ b/src/transformation/visitors/class/new.ts @@ -5,6 +5,7 @@ import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations"; import { annotationInvalidArgumentCount, extensionCannotConstruct } from "../../utils/diagnostics"; import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib"; import { transformArguments } from "../call"; +import { isTableNewCall } from "../language-extensions/table"; import { transformLuaTableNewExpression } from "../lua-table"; const builtinErrorTypeNames = new Set([ @@ -53,6 +54,10 @@ export const transformNewExpression: FunctionVisitor = (node, return luaTableResult; } + if (isTableNewCall(context, node)) { + return lua.createTableExpression(undefined, node); + } + const name = context.transformExpression(node.expression); const signature = context.checker.getResolvedSignature(node); const params = node.arguments diff --git a/src/transformation/visitors/expression-statement.ts b/src/transformation/visitors/expression-statement.ts index 890695be0..1932a6dc2 100644 --- a/src/transformation/visitors/expression-statement.ts +++ b/src/transformation/visitors/expression-statement.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import * as lua from "../../LuaAST"; import { FunctionVisitor } from "../context"; import { transformBinaryExpressionStatement } from "./binary-expression"; +import { isTableSetCall, transformTableSetExpression } from "./language-extensions/table"; import { transformLuaTableExpressionStatement } from "./lua-table"; import { transformUnaryExpressionStatement } from "./unary-expression"; @@ -11,6 +12,10 @@ export const transformExpressionStatement: FunctionVisitor([ extensions.ExtensionKind.BitwiseNotOperatorMethodType, ]); -function getTypeDeclaration(declaration: ts.Declaration) { - return ts.isTypeAliasDeclaration(declaration) - ? declaration - : findFirstNodeAbove(declaration, ts.isTypeAliasDeclaration); -} - function getOperatorMapExtensionKindForCall(context: TransformationContext, node: ts.CallExpression) { - const signature = context.checker.getResolvedSignature(node); - if (!signature || !signature.declaration) { - return; - } - const typeDeclaration = getTypeDeclaration(signature.declaration); - if (!typeDeclaration) { - return; - } - const type = context.checker.getTypeFromTypeNode(typeDeclaration.type); - return operatorMapExtensions.find(extensionKind => extensions.isExtensionType(type, extensionKind)); -} - -function isOperatorMapType(context: TransformationContext, type: ts.Type): boolean { - if (type.isUnionOrIntersection()) { - return type.types.some(t => isOperatorMapType(context, t)); - } else { - return operatorMapExtensions.some(extensionKind => extensions.isExtensionType(type, extensionKind)); - } -} - -function isOperatorMapIdentifier(context: TransformationContext, node: ts.Identifier) { - const type = context.checker.getTypeAtLocation(node); - return isOperatorMapType(context, type); + const type = getFunctionTypeForCall(context, node); + return type && operatorMapExtensions.find(extensionKind => extensions.isExtensionType(type, extensionKind)); } export function isOperatorMapping(context: TransformationContext, node: ts.CallExpression | ts.Identifier) { if (ts.isCallExpression(node)) { return getOperatorMapExtensionKindForCall(context, node) !== undefined; } else { - return isOperatorMapIdentifier(context, node); + const type = context.checker.getTypeAtLocation(node); + return operatorMapExtensions.some(extensionKind => extensions.isExtensionType(type, extensionKind)); } } diff --git a/src/transformation/visitors/language-extensions/table.ts b/src/transformation/visitors/language-extensions/table.ts new file mode 100644 index 000000000..b1c26f2d9 --- /dev/null +++ b/src/transformation/visitors/language-extensions/table.ts @@ -0,0 +1,80 @@ +import * as ts from "typescript"; +import * as lua from "../../../LuaAST"; +import { TransformationContext } from "../../context"; +import * as extensions from "../../utils/language-extensions"; +import { getFunctionTypeForCall } from "../../utils/typescript"; +import { assert } from "../../../utils"; + +const tableGetExtensions = [extensions.ExtensionKind.TableGetType, extensions.ExtensionKind.TableGetMethodType]; + +const tableSetExtensions = [extensions.ExtensionKind.TableSetType, extensions.ExtensionKind.TableSetMethodType]; + +const tableExtensions = [extensions.ExtensionKind.TableNewType, ...tableGetExtensions, ...tableSetExtensions]; + +function getTableExtensionKindForCall( + context: TransformationContext, + node: ts.CallExpression, + validExtensions: extensions.ExtensionKind[] +) { + const type = getFunctionTypeForCall(context, node); + return type && validExtensions.find(extensionKind => extensions.isExtensionType(type, extensionKind)); +} + +export function isTableExtensionIdentifier(context: TransformationContext, node: ts.Identifier) { + const type = context.checker.getTypeAtLocation(node); + return tableExtensions.some(extensionKind => extensions.isExtensionType(type, extensionKind)); +} + +export function isTableGetCall(context: TransformationContext, node: ts.CallExpression) { + return getTableExtensionKindForCall(context, node, tableGetExtensions) !== undefined; +} + +export function isTableSetCall(context: TransformationContext, node: ts.CallExpression) { + return getTableExtensionKindForCall(context, node, tableSetExtensions) !== undefined; +} + +export function isTableNewCall(context: TransformationContext, node: ts.NewExpression) { + const type = context.checker.getTypeAtLocation(node.expression); + return extensions.isExtensionType(type, extensions.ExtensionKind.TableNewType); +} + +export function transformTableGetExpression(context: TransformationContext, node: ts.CallExpression): lua.Expression { + const extensionKind = getTableExtensionKindForCall(context, node, tableGetExtensions); + assert(extensionKind); + + const args = node.arguments.slice(); + if ( + args.length === 1 && + (ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression)) + ) { + args.unshift(node.expression.expression); + } + + return lua.createTableIndexExpression( + context.transformExpression(args[0]), + context.transformExpression(args[1]), + node + ); +} + +export function transformTableSetExpression(context: TransformationContext, node: ts.CallExpression): lua.Statement { + const extensionKind = getTableExtensionKindForCall(context, node, tableSetExtensions); + assert(extensionKind); + + const args = node.arguments.slice(); + if ( + args.length === 2 && + (ts.isPropertyAccessExpression(node.expression) || ts.isElementAccessExpression(node.expression)) + ) { + args.unshift(node.expression.expression); + } + + return lua.createAssignmentStatement( + lua.createTableIndexExpression( + context.transformExpression(args[0]), + context.transformExpression(args[1]), + node + ), + context.transformExpression(args[2]) + ); +} diff --git a/test/unit/language-extensions/__snapshots__/table.spec.ts.snap b/test/unit/language-extensions/__snapshots__/table.spec.ts.snap new file mode 100644 index 000000000..8e7d3a434 --- /dev/null +++ b/test/unit/language-extensions/__snapshots__/table.spec.ts.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = [setTable({}, \\"foo\\", 3)];"): code 1`] = ` +"foo = { + (function() + ({}).foo = 3 + return nil + end)() +}" +`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = [setTable({}, \\"foo\\", 3)];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = \`\${setTable({}, \\"foo\\", 3)}\`;"): code 1`] = ` +"foo = tostring( + (function() + ({}).foo = 3 + return nil + end)() +)" +`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = \`\${setTable({}, \\"foo\\", 3)}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = setTable({}, \\"foo\\", 3);"): code 1`] = ` +"foo = (function() + ({}).foo = 3 + return nil +end)()" +`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("const foo = setTable({}, \\"foo\\", 3);"): diagnostics 1`] = `"main.ts(3,25): error TSTL: Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("declare function foo(arg: any): void; foo(setTable({}, \\"foo\\", 3));"): code 1`] = ` +"foo( + _G, + (function() + ({}).foo = 3 + return nil + end)() +)" +`; + +exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as expression ("declare function foo(arg: any): void; foo(setTable({}, \\"foo\\", 3));"): diagnostics 1`] = `"main.ts(3,55): error TSTL: Table set extension can only be called as a stand-alone statement. It cannot be used as an expression in another statement."`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = (getTable as any)(1, 2);"): code 1`] = `"foo = getTable(_G, 1, 2)"`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = (getTable as any)(1, 2);"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must always be directly called and cannot be referred to."`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = [getTable];"): code 1`] = `"foo = {getTable}"`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = [getTable];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must always be directly called and cannot be referred to."`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = \`\${getTable}\`;"): code 1`] = `"foo = tostring(getTable)"`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = \`\${getTable}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: This function must always be directly called and cannot be referred to."`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown = getTable;"): code 1`] = `"foo = getTable"`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown = getTable;"): diagnostics 1`] = `"main.ts(3,34): error TSTL: This function must always be directly called and cannot be referred to."`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): code 1`] = `"foo(_G, getTable)"`; + +exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): diagnostics 1`] = `"main.ts(3,88): error TSTL: This function must always be directly called and cannot be referred to."`; diff --git a/test/unit/language-extensions/table.spec.ts b/test/unit/language-extensions/table.spec.ts new file mode 100644 index 000000000..2f1f61339 --- /dev/null +++ b/test/unit/language-extensions/table.spec.ts @@ -0,0 +1,130 @@ +import * as path from "path"; +import * as util from "../../util"; +import * as tstl from "../../../src"; +import { invalidTableExtensionUse, invalidTableSetExpression } from "../../../src/transformation/utils/diagnostics"; + +const tableProjectOptions: tstl.CompilerOptions = { + types: [path.resolve(__dirname, "../../../language-extensions")], +}; + +describe("LuaTableGet & LuaTableSet extensions", () => { + test("stand-alone function", () => { + util.testModule` + declare const getTable: LuaTableGet<{}, string, number>; + declare const setTable: LuaTableSet<{}, string, number>; + const tbl = {}; + setTable(tbl, "foo", 3); + const result = getTable(tbl, "foo"); + export { result } + ` + .setOptions(tableProjectOptions) + .setReturnExport("result") + .expectToEqual(3); + }); + + test("namespace function", () => { + util.testModule` + declare namespace Table { + export const get: LuaTableGet<{}, string, number>; + export const set: LuaTableSet<{}, string, number>; + } + const tbl = {}; + Table.set(tbl, "foo", 3); + const result = Table.get(tbl, "foo"); + export { result } + ` + .setOptions(tableProjectOptions) + .setReturnExport("result") + .expectToEqual(3); + }); + + test("method", () => { + util.testModule` + interface Table { + get: LuaTableGetMethod; + set: LuaTableSetMethod; + } + const tbl = {} as Table; + tbl.set("foo", 3); + const result = tbl.get("foo"); + export { result } + ` + .setOptions(tableProjectOptions) + .setReturnExport("result") + .expectToEqual(3); + }); + + test.each([ + "const foo: unknown = getTable;", + "const foo = `${getTable}`;", + "declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);", + "const foo = (getTable as any)(1, 2);", + "const foo = [getTable];", + ])("invalid use (%p)", statement => { + util.testModule` + declare const getTable: LuaTableGet<{}, string, number>; + ${statement} + ` + .setOptions(tableProjectOptions) + .expectDiagnosticsToMatchSnapshot([invalidTableExtensionUse.code]); + }); + + test.each([ + 'const foo = setTable({}, "foo", 3);', + 'const foo = `${setTable({}, "foo", 3)}`;', + 'declare function foo(arg: any): void; foo(setTable({}, "foo", 3));', + 'const foo = [setTable({}, "foo", 3)];', + ])("LuaTableSet invalid use as expression (%p)", expression => { + util.testModule` + declare const setTable: LuaTableSet<{}, string, number>; + ${expression} + ` + .setOptions(tableProjectOptions) + .expectDiagnosticsToMatchSnapshot([invalidTableSetExpression.code]); + }); +}); + +describe("LuaTable extension interface", () => { + test("untyped table", () => { + util.testFunction` + const tbl = new LuaTable(); + tbl.set("foo", 3); + return tbl.get("foo"); + ` + .setOptions(tableProjectOptions) + .expectToEqual(3); + }); + + test("typed table", () => { + util.testFunction` + const tbl = new LuaTable(); + tbl.set("foo", 3); + return tbl.get("foo"); + ` + .setOptions(tableProjectOptions) + .expectToEqual(3); + }); + + test("object keyed table", () => { + util.testFunction` + interface Key { keyStr: string } + const key: Key = {keyStr: "foo"}; + const tbl = new LuaTable(); + tbl.set(key, 3); + return tbl.get(key); + ` + .setOptions(tableProjectOptions) + .expectToEqual(3); + }); + + test("table length", () => { + util.testFunction` + const tbl = new LuaTable(); + tbl.set(1, "foo"); + tbl.set(3, "bar"); + return tbl.length(); + ` + .setOptions(tableProjectOptions) + .expectToEqual(1); + }); +}); From 4dbf821acdfdcd98245f9054b8b69fe8fc01a85b Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 21 Feb 2021 10:01:32 -0700 Subject: [PATCH 2/2] addressing feedback --- src/transformation/utils/diagnostics.ts | 2 +- .../__snapshots__/table.spec.ts.snap | 10 +++++----- test/unit/language-extensions/table.spec.ts | 7 +++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/transformation/utils/diagnostics.ts b/src/transformation/utils/diagnostics.ts index eed0ba07f..9b34e9547 100644 --- a/src/transformation/utils/diagnostics.ts +++ b/src/transformation/utils/diagnostics.ts @@ -175,7 +175,7 @@ export const invalidOperatorMappingUse = createErrorDiagnosticFactory( ); export const invalidTableExtensionUse = createErrorDiagnosticFactory( - "This function must always be directly called and cannot be referred to." + "This function must be called directly and cannot be referred to." ); export const invalidTableSetExpression = createErrorDiagnosticFactory( diff --git a/test/unit/language-extensions/__snapshots__/table.spec.ts.snap b/test/unit/language-extensions/__snapshots__/table.spec.ts.snap index 8e7d3a434..9f6113df6 100644 --- a/test/unit/language-extensions/__snapshots__/table.spec.ts.snap +++ b/test/unit/language-extensions/__snapshots__/table.spec.ts.snap @@ -45,20 +45,20 @@ exports[`LuaTableGet & LuaTableSet extensions LuaTableSet invalid use as express exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = (getTable as any)(1, 2);"): code 1`] = `"foo = getTable(_G, 1, 2)"`; -exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = (getTable as any)(1, 2);"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = (getTable as any)(1, 2);"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must be called directly and cannot be referred to."`; exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = [getTable];"): code 1`] = `"foo = {getTable}"`; -exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = [getTable];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = [getTable];"): diagnostics 1`] = `"main.ts(3,26): error TSTL: This function must be called directly and cannot be referred to."`; exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = \`\${getTable}\`;"): code 1`] = `"foo = tostring(getTable)"`; -exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = \`\${getTable}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo = \`\${getTable}\`;"): diagnostics 1`] = `"main.ts(3,28): error TSTL: This function must be called directly and cannot be referred to."`; exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown = getTable;"): code 1`] = `"foo = getTable"`; -exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown = getTable;"): diagnostics 1`] = `"main.ts(3,34): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`LuaTableGet & LuaTableSet extensions invalid use ("const foo: unknown = getTable;"): diagnostics 1`] = `"main.ts(3,34): error TSTL: This function must be called directly and cannot be referred to."`; exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): code 1`] = `"foo(_G, getTable)"`; -exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): diagnostics 1`] = `"main.ts(3,88): error TSTL: This function must always be directly called and cannot be referred to."`; +exports[`LuaTableGet & LuaTableSet extensions invalid use ("declare function foo(getTable: LuaTableGet<{}, string, number>): void; foo(getTable);"): diagnostics 1`] = `"main.ts(3,88): error TSTL: This function must be called directly and cannot be referred to."`; diff --git a/test/unit/language-extensions/table.spec.ts b/test/unit/language-extensions/table.spec.ts index 2f1f61339..8d3d0fde1 100644 --- a/test/unit/language-extensions/table.spec.ts +++ b/test/unit/language-extensions/table.spec.ts @@ -127,4 +127,11 @@ describe("LuaTable extension interface", () => { .setOptions(tableProjectOptions) .expectToEqual(1); }); + + test.each(['new LuaTable().get("foo");', 'new LuaTable().set("foo", "bar");'])( + "table immediate access (%p)", + statement => { + util.testFunction(statement).setOptions(tableProjectOptions).expectToHaveNoDiagnostics(); + } + ); });