Skip to content
Closed
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
40 changes: 35 additions & 5 deletions src/transformation/visitors/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { getOptionalContinuationData, transformOptionalChain } from "./optional-
import { transformImportExpression } from "./modules/import";
import { transformLanguageExtensionCallExpression } from "./language-extensions/call-extension";
import { getCustomNameFromSymbol } from "./identifier";
import { AnnotationKind, getFileAnnotations } from "../utils/annotations";

export function validateArguments(
context: TransformationContext,
Expand Down Expand Up @@ -267,7 +268,9 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node

let callPath: lua.Expression;
let parameters: lua.Expression[];
const isContextualCall = isContextualCallExpression(context, signature);

const isContextualCall = isContextualCallExpression(context, calledExpression, signature);

if (!isContextualCall) {
[callPath, parameters] = transformCallAndArguments(context, calledExpression, node.arguments, signature);
} else {
Expand All @@ -290,12 +293,39 @@ export const transformCallExpression: FunctionVisitor<ts.CallExpression> = (node
return wrapResultInTable ? wrapInTable(callExpression) : callExpression;
};

function isContextualCallExpression(context: TransformationContext, signature: ts.Signature | undefined): boolean {
function isContextualCallExpression(
context: TransformationContext,
calledExpression: ts.Expression,
signature?: ts.Signature
): boolean {
const symbol = context.checker.getSymbolAtLocation(calledExpression);
let hasNoSelfInFile = false;

if (symbol?.declarations) {
hasNoSelfInFile =
symbol.declarations.some(d => getFileAnnotations(d.getSourceFile()).has(AnnotationKind.NoSelfInFile)) ??
false;
}

const declaration = signature?.getDeclaration();
if (!declaration) {
return !context.options.noImplicitSelf;

if (declaration) {
const contextTypeCheck = getDeclarationContextType(context, declaration) !== ContextType.Void;

// respect implicit self over noSelfInFile
// look at "explicit this parameter respected over @noSelf" test
if (hasNoSelfInFile && contextTypeCheck !== true) {
return false;
}

return contextTypeCheck;
}
return getDeclarationContextType(context, declaration) !== ContextType.Void;

if (hasNoSelfInFile) {
return false;
}

return !context.options.noImplicitSelf;
}

export function getCalledExpression(node: ts.CallExpression): ts.Expression {
Expand Down
45 changes: 45 additions & 0 deletions test/unit/functions/noSelfAnnotation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import path = require("path");
import * as util from "../../util";

const methodHolders = ["class", "interface"];
Expand Down Expand Up @@ -62,3 +63,47 @@ test("explicit this parameter respected over @noSelf", () => {
export const result = foo(1);
`.expectToMatchJsResult();
});

test("respect noSelfInFile over noImplicitSelf", () => {
const result = util.testModule`
/** @noSelfInFile **/
const func: Function = () => 1;
export const result = func(1);
`
.expectToMatchJsResult()
.getLuaResult();

expect(result.transpiledFiles).not.toHaveLength(0);

const mainFile = result.transpiledFiles.find(f => f.outPath === "main.lua");
expect(mainFile).toBeDefined();

// avoid ts error "not defined", even though toBeDefined is being checked above
if (!mainFile) return;

expect(mainFile.lua).toBeDefined();
expect(mainFile.lua).toContain("func(1)");
expect(mainFile.lua).not.toContain("_G");
});

const projectPath = path.resolve(__dirname, "noSelfAnnotationRespect");

const projectFile = util
.testProject(path.join(projectPath, "tsconfig.json"))
.setMainFileName(path.join(projectPath, "main.ts"));

test("respect noSelfInFile over noImplicitSelf (func declared in other file)", () => {
const result = projectFile.getLuaResult();

expect(result.transpiledFiles).not.toHaveLength(0);

const mainFile = result.transpiledFiles.find(f => f.outPath.includes("main.lua"));
expect(mainFile).toBeDefined();

// avoid ts error "not defined", even though toBeDefined is being checked above
if (!mainFile) return;

expect(mainFile.lua).toBeDefined();
expect(mainFile.lua).toContain("func(1)");
expect(mainFile.lua).not.toContain("_G");
});
6 changes: 6 additions & 0 deletions test/unit/functions/noSelfAnnotationRespect/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* eslint-disable spaced-comment */
/* eslint-disable @typescript-eslint/ban-types */

/** @noSelfInFile **/
export const func: Function = () => 1;
export const result = func(1);
4 changes: 4 additions & 0 deletions test/unit/functions/noSelfAnnotationRespect/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { func, result } from "./functions";

export const result1 = result;
export const result2 = func(1);
12 changes: 12 additions & 0 deletions test/unit/functions/noSelfAnnotationRespect/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "Node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"target": "esnext",
"lib": ["esnext"],
"types": [],
"rootDir": "."
}
}