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
5 changes: 5 additions & 0 deletions src/CommandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const optionDeclarations: CommandLineOption[] = [
type: "enum",
choices: Object.values(LuaTarget),
},
{
name: "noImplicitSelf",
description: 'If "this" is implicitly considered an any type, do not generate a self parameter.',
type: "boolean",
},
{
name: "noHeader",
description: "Specify if a header will be added to compiled files.",
Expand Down
1 change: 1 addition & 0 deletions src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface TransformerImport {
}

export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
noImplicitSelf?: boolean;
noHeader?: boolean;
luaTarget?: LuaTarget;
luaLibImport?: LuaLibImportKind;
Expand Down
22 changes: 11 additions & 11 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,7 +1393,7 @@ export class LuaTransformer {

const type = this.checker.getTypeAtLocation(node);
const context =
tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void
tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void
? this.createSelfIdentifier()
: undefined;
const [paramNames, dots, restParamName] = this.transformParameters(node.parameters, context);
Expand Down Expand Up @@ -1942,7 +1942,7 @@ export class LuaTransformer {

const type = this.checker.getTypeAtLocation(functionDeclaration);
const context =
tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void
tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void
? this.createSelfIdentifier()
: undefined;
const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context);
Expand Down Expand Up @@ -4017,7 +4017,7 @@ export class LuaTransformer {
const type = this.checker.getTypeAtLocation(node);

let context: tstl.Identifier | undefined;
if (tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void) {
if (tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void) {
if (ts.isArrowFunction(node)) {
// dummy context for arrow functions with parameters
if (node.parameters.length > 0) {
Expand Down Expand Up @@ -4215,7 +4215,7 @@ export class LuaTransformer {
const signatureDeclaration = signature && signature.getDeclaration();
if (
signatureDeclaration &&
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === tsHelper.ContextType.Void
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) === tsHelper.ContextType.Void
) {
parameters = this.transformArguments(expression.arguments, signature);
} else {
Expand Down Expand Up @@ -4353,7 +4353,7 @@ export class LuaTransformer {
const signatureDeclaration = signature && signature.getDeclaration();
if (
!signatureDeclaration ||
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void
) {
// table:name()
return this.transformContextualCallExpression(node, parameters);
Expand Down Expand Up @@ -4392,7 +4392,7 @@ export class LuaTransformer {
const parameters = this.transformArguments(node.arguments, signature);
if (
!signatureDeclaration ||
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void
) {
// A contextual parameter must be given to this call expression
return this.transformContextualCallExpression(node, parameters);
Expand Down Expand Up @@ -5152,7 +5152,7 @@ export class LuaTransformer {
protected transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression {
const expression = node.expression as ts.PropertyAccessExpression;
const callerType = this.checker.getTypeAtLocation(expression.expression);
if (tsHelper.getFunctionContextType(callerType, this.checker) === tsHelper.ContextType.Void) {
if (tsHelper.getFunctionContextType(callerType, this.program) === tsHelper.ContextType.Void) {
throw TSTLErrors.UnsupportedSelfFunctionConversion(node);
}
const signature = this.checker.getResolvedSignature(node);
Expand Down Expand Up @@ -5278,7 +5278,7 @@ export class LuaTransformer {
const signatureDeclaration = signature && signature.getDeclaration();
const useSelfParameter =
signatureDeclaration &&
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void;
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void;

// Argument evaluation.
const callArguments = this.transformArguments(expressions, signature);
Expand Down Expand Up @@ -5690,8 +5690,8 @@ export class LuaTransformer {
fromTypeCache.add(toType);

// Check function assignments
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker);
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
const fromContext = tsHelper.getFunctionContextType(fromType, this.program);
const toContext = tsHelper.getFunctionContextType(toType, this.program);

if (fromContext === tsHelper.ContextType.Mixed || toContext === tsHelper.ContextType.Mixed) {
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
Expand Down Expand Up @@ -6172,7 +6172,7 @@ export class LuaTransformer {
const decoratorExpressions = decorators.map(decorator => {
const expression = decorator.expression;
const type = this.checker.getTypeAtLocation(expression);
const context = tsHelper.getFunctionContextType(type, this.checker);
const context = tsHelper.getFunctionContextType(type, this.program);
if (context === tsHelper.ContextType.Void) {
throw TSTLErrors.InvalidDecoratorContext(decorator);
}
Expand Down
10 changes: 10 additions & 0 deletions src/NoImplicitSelfTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as ts from "typescript";

const transformSourceFile: ts.Transformer<ts.SourceFile> = node => {
const empty = ts.createNotEmittedStatement(undefined!);
ts.addSyntheticLeadingComment(empty, ts.SyntaxKind.MultiLineCommentTrivia, "* @noSelfInFile ", true);
return ts.updateSourceFileNode(node, [empty, ...node.statements], node.isDeclarationFile);
};

export const noImplicitSelfTransformer: ts.TransformerFactory<ts.SourceFile | ts.Bundle> = () => node =>
ts.isBundle(node) ? ts.updateBundle(node, node.sourceFiles.map(transformSourceFile)) : transformSourceFile(node);
21 changes: 16 additions & 5 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as ts from "typescript";
import * as path from "path";
import { CompilerOptions } from "./CompilerOptions";
import { Decorator, DecoratorKind } from "./Decorator";
import * as tstl from "./LuaAST";
import * as TSTLErrors from "./TSTLErrors";
Expand Down Expand Up @@ -692,8 +693,10 @@ export function hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeC

export function getDeclarationContextType(
signatureDeclaration: ts.SignatureDeclaration,
checker: ts.TypeChecker
program: ts.Program
): ContextType {
const checker = program.getTypeChecker();

const thisParameter = getExplicitThisParameter(signatureDeclaration);
if (thisParameter) {
// Explicit 'this'
Expand Down Expand Up @@ -727,7 +730,13 @@ export function getDeclarationContextType(
return ContextType.NonVoid;
}

// Walk up to find @noSelf or @noSelfOnFile
// When using --noImplicitSelf and the signature is defined in a file targeted by the program apply the @noSelf rule.
const options = program.getCompilerOptions() as CompilerOptions;
if (options.noImplicitSelf && program.getRootFileNames().includes(signatureDeclaration.getSourceFile().fileName)) {
return ContextType.Void;
}

// Walk up to find @noSelf or @noSelfInFile
if (hasNoSelfAncestor(signatureDeclaration, checker)) {
return ContextType.Void;
}
Expand All @@ -750,21 +759,23 @@ export function reduceContextTypes(contexts: ContextType[]): ContextType {
return contexts.reduce(reducer, ContextType.None);
}

export function getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
export function getFunctionContextType(type: ts.Type, program: ts.Program): ContextType {
const checker = program.getTypeChecker();

if (type.isTypeParameter()) {
type = type.getConstraint() || type;
}

if (type.isUnion()) {
return reduceContextTypes(type.types.map(t => getFunctionContextType(t, checker)));
return reduceContextTypes(type.types.map(t => getFunctionContextType(t, program)));
}

const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
if (signatures.length === 0) {
return ContextType.None;
}
const signatureDeclarations = getSignatureDeclarations(signatures, checker);
return reduceContextTypes(signatureDeclarations.map(s => getDeclarationContextType(s, checker)));
return reduceContextTypes(signatureDeclarations.map(s => getDeclarationContextType(s, program)));
}

export function escapeString(text: string): string {
Expand Down
17 changes: 13 additions & 4 deletions src/TSTransformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as resolve from "resolve";
import * as ts from "typescript";
import { CompilerOptions, TransformerImport } from "./CompilerOptions";
import * as diagnosticFactories from "./diagnostics";
import { noImplicitSelfTransformer } from "./NoImplicitSelfTransformer";

export function getCustomTransformers(
program: ts.Program,
Expand All @@ -16,11 +17,19 @@ export function getCustomTransformers(
};

const transformersFromOptions = loadTransformersFromOptions(program, diagnostics);

const afterDeclarations = [
...(transformersFromOptions.afterDeclarations || []),
...(customTransformers.afterDeclarations || []),
];

const options = program.getCompilerOptions() as CompilerOptions;
if (options.noImplicitSelf) {
afterDeclarations.unshift(noImplicitSelfTransformer);
}

return {
afterDeclarations: [
...(transformersFromOptions.afterDeclarations || []),
...(customTransformers.afterDeclarations || []),
],
afterDeclarations,
before: [
...(customTransformers.before || []),
...(transformersFromOptions.before || []),
Expand Down
40 changes: 40 additions & 0 deletions test/unit/functions/noImplicitSelfOption.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as util from "../../util";

test("enables noSelfInFile behaviour for functions", () => {
util.testFunction`
function fooBar() {}
const test: (this: void) => void = fooBar;
`
.setOptions({ noImplicitSelf: true })
.expectToHaveNoDiagnostics();
});

test("enables noSelfInFile behaviour for methods", () => {
util.testFunction`
class FooBar {
fooBar() {}
}
const fooBar = new FooBar();
const test: (this: any) => void = fooBar.fooBar;
`
.setOptions({ noImplicitSelf: true })
.expectToHaveNoDiagnostics();
});

test("generates declaration files with @noSelfInFile", () => {
const builder = util.testModule`
export function bar() {}
`
.setOptions({ declaration: true, noImplicitSelf: true })
.expectToHaveNoDiagnostics();

const declarationFile = builder.getLuaResult().transpiledFiles.find(f => f.declaration);
if (!util.expectToBeDefined(declarationFile) || !util.expectToBeDefined(declarationFile.declaration)) return;

util.testModule`
import { bar } from "./foo.d";
const test: (this: void) => void = bar;
`
.addExtraFile("foo.d.ts", declarationFile.declaration)
.expectToHaveNoDiagnostics();
});