Skip to content

Commit c1e77c9

Browse files
hazzard993Perryvw
authored andcommitted
No self option (#718)
* Implement noSelf option * Test noSelf option * Fix noSelf to follow only noSelfInFile behaviour * Use NotEmittedStatement * Use expectToBeDefined * Neaten up noSelf test names * Correct new transformer structure * Avoid using legacy test utils * Fix noSelfOption testFunction indenting * Update src/NoSelfTransformer.ts Co-Authored-By: ark120202 <ark120202@gmail.com> * Tidy up tests * Add more detailed comment about noSelf to TSHelper * Rename noSelf option to noImplicitSelf * Update TSHelper noImplicitSelf comment * Update noImplicitSelf help description * Update TSHelper noImplicitSelf comment
1 parent 5735093 commit c1e77c9

File tree

7 files changed

+96
-20
lines changed

7 files changed

+96
-20
lines changed

src/CommandLineParser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const optionDeclarations: CommandLineOption[] = [
3838
type: "enum",
3939
choices: Object.values(LuaTarget),
4040
},
41+
{
42+
name: "noImplicitSelf",
43+
description: 'If "this" is implicitly considered an any type, do not generate a self parameter.',
44+
type: "boolean",
45+
},
4146
{
4247
name: "noHeader",
4348
description: "Specify if a header will be added to compiled files.",

src/CompilerOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface TransformerImport {
1818
}
1919

2020
export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
21+
noImplicitSelf?: boolean;
2122
noHeader?: boolean;
2223
luaTarget?: LuaTarget;
2324
luaLibImport?: LuaLibImportKind;

src/LuaTransformer.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,7 +1393,7 @@ export class LuaTransformer {
13931393

13941394
const type = this.checker.getTypeAtLocation(node);
13951395
const context =
1396-
tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void
1396+
tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void
13971397
? this.createSelfIdentifier()
13981398
: undefined;
13991399
const [paramNames, dots, restParamName] = this.transformParameters(node.parameters, context);
@@ -1942,7 +1942,7 @@ export class LuaTransformer {
19421942

19431943
const type = this.checker.getTypeAtLocation(functionDeclaration);
19441944
const context =
1945-
tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void
1945+
tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void
19461946
? this.createSelfIdentifier()
19471947
: undefined;
19481948
const [params, dotsLiteral, restParamName] = this.transformParameters(functionDeclaration.parameters, context);
@@ -4017,7 +4017,7 @@ export class LuaTransformer {
40174017
const type = this.checker.getTypeAtLocation(node);
40184018

40194019
let context: tstl.Identifier | undefined;
4020-
if (tsHelper.getFunctionContextType(type, this.checker) !== tsHelper.ContextType.Void) {
4020+
if (tsHelper.getFunctionContextType(type, this.program) !== tsHelper.ContextType.Void) {
40214021
if (ts.isArrowFunction(node)) {
40224022
// dummy context for arrow functions with parameters
40234023
if (node.parameters.length > 0) {
@@ -4215,7 +4215,7 @@ export class LuaTransformer {
42154215
const signatureDeclaration = signature && signature.getDeclaration();
42164216
if (
42174217
signatureDeclaration &&
4218-
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) === tsHelper.ContextType.Void
4218+
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) === tsHelper.ContextType.Void
42194219
) {
42204220
parameters = this.transformArguments(expression.arguments, signature);
42214221
} else {
@@ -4353,7 +4353,7 @@ export class LuaTransformer {
43534353
const signatureDeclaration = signature && signature.getDeclaration();
43544354
if (
43554355
!signatureDeclaration ||
4356-
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void
4356+
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void
43574357
) {
43584358
// table:name()
43594359
return this.transformContextualCallExpression(node, parameters);
@@ -4392,7 +4392,7 @@ export class LuaTransformer {
43924392
const parameters = this.transformArguments(node.arguments, signature);
43934393
if (
43944394
!signatureDeclaration ||
4395-
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void
4395+
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void
43964396
) {
43974397
// A contextual parameter must be given to this call expression
43984398
return this.transformContextualCallExpression(node, parameters);
@@ -5152,7 +5152,7 @@ export class LuaTransformer {
51525152
protected transformFunctionCallExpression(node: ts.CallExpression): tstl.CallExpression {
51535153
const expression = node.expression as ts.PropertyAccessExpression;
51545154
const callerType = this.checker.getTypeAtLocation(expression.expression);
5155-
if (tsHelper.getFunctionContextType(callerType, this.checker) === tsHelper.ContextType.Void) {
5155+
if (tsHelper.getFunctionContextType(callerType, this.program) === tsHelper.ContextType.Void) {
51565156
throw TSTLErrors.UnsupportedSelfFunctionConversion(node);
51575157
}
51585158
const signature = this.checker.getResolvedSignature(node);
@@ -5278,7 +5278,7 @@ export class LuaTransformer {
52785278
const signatureDeclaration = signature && signature.getDeclaration();
52795279
const useSelfParameter =
52805280
signatureDeclaration &&
5281-
tsHelper.getDeclarationContextType(signatureDeclaration, this.checker) !== tsHelper.ContextType.Void;
5281+
tsHelper.getDeclarationContextType(signatureDeclaration, this.program) !== tsHelper.ContextType.Void;
52825282

52835283
// Argument evaluation.
52845284
const callArguments = this.transformArguments(expressions, signature);
@@ -5690,8 +5690,8 @@ export class LuaTransformer {
56905690
fromTypeCache.add(toType);
56915691

56925692
// Check function assignments
5693-
const fromContext = tsHelper.getFunctionContextType(fromType, this.checker);
5694-
const toContext = tsHelper.getFunctionContextType(toType, this.checker);
5693+
const fromContext = tsHelper.getFunctionContextType(fromType, this.program);
5694+
const toContext = tsHelper.getFunctionContextType(toType, this.program);
56955695

56965696
if (fromContext === tsHelper.ContextType.Mixed || toContext === tsHelper.ContextType.Mixed) {
56975697
throw TSTLErrors.UnsupportedOverloadAssignment(node, toName);
@@ -6172,7 +6172,7 @@ export class LuaTransformer {
61726172
const decoratorExpressions = decorators.map(decorator => {
61736173
const expression = decorator.expression;
61746174
const type = this.checker.getTypeAtLocation(expression);
6175-
const context = tsHelper.getFunctionContextType(type, this.checker);
6175+
const context = tsHelper.getFunctionContextType(type, this.program);
61766176
if (context === tsHelper.ContextType.Void) {
61776177
throw TSTLErrors.InvalidDecoratorContext(decorator);
61786178
}

src/NoImplicitSelfTransformer.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as ts from "typescript";
2+
3+
const transformSourceFile: ts.Transformer<ts.SourceFile> = node => {
4+
const empty = ts.createNotEmittedStatement(undefined!);
5+
ts.addSyntheticLeadingComment(empty, ts.SyntaxKind.MultiLineCommentTrivia, "* @noSelfInFile ", true);
6+
return ts.updateSourceFileNode(node, [empty, ...node.statements], node.isDeclarationFile);
7+
};
8+
9+
export const noImplicitSelfTransformer: ts.TransformerFactory<ts.SourceFile | ts.Bundle> = () => node =>
10+
ts.isBundle(node) ? ts.updateBundle(node, node.sourceFiles.map(transformSourceFile)) : transformSourceFile(node);

src/TSHelper.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as ts from "typescript";
22
import * as path from "path";
3+
import { CompilerOptions } from "./CompilerOptions";
34
import { Decorator, DecoratorKind } from "./Decorator";
45
import * as tstl from "./LuaAST";
56
import * as TSTLErrors from "./TSTLErrors";
@@ -692,8 +693,10 @@ export function hasNoSelfAncestor(declaration: ts.Declaration, checker: ts.TypeC
692693

693694
export function getDeclarationContextType(
694695
signatureDeclaration: ts.SignatureDeclaration,
695-
checker: ts.TypeChecker
696+
program: ts.Program
696697
): ContextType {
698+
const checker = program.getTypeChecker();
699+
697700
const thisParameter = getExplicitThisParameter(signatureDeclaration);
698701
if (thisParameter) {
699702
// Explicit 'this'
@@ -727,7 +730,13 @@ export function getDeclarationContextType(
727730
return ContextType.NonVoid;
728731
}
729732

730-
// Walk up to find @noSelf or @noSelfOnFile
733+
// When using --noImplicitSelf and the signature is defined in a file targeted by the program apply the @noSelf rule.
734+
const options = program.getCompilerOptions() as CompilerOptions;
735+
if (options.noImplicitSelf && program.getRootFileNames().includes(signatureDeclaration.getSourceFile().fileName)) {
736+
return ContextType.Void;
737+
}
738+
739+
// Walk up to find @noSelf or @noSelfInFile
731740
if (hasNoSelfAncestor(signatureDeclaration, checker)) {
732741
return ContextType.Void;
733742
}
@@ -750,21 +759,23 @@ export function reduceContextTypes(contexts: ContextType[]): ContextType {
750759
return contexts.reduce(reducer, ContextType.None);
751760
}
752761

753-
export function getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
762+
export function getFunctionContextType(type: ts.Type, program: ts.Program): ContextType {
763+
const checker = program.getTypeChecker();
764+
754765
if (type.isTypeParameter()) {
755766
type = type.getConstraint() || type;
756767
}
757768

758769
if (type.isUnion()) {
759-
return reduceContextTypes(type.types.map(t => getFunctionContextType(t, checker)));
770+
return reduceContextTypes(type.types.map(t => getFunctionContextType(t, program)));
760771
}
761772

762773
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
763774
if (signatures.length === 0) {
764775
return ContextType.None;
765776
}
766777
const signatureDeclarations = getSignatureDeclarations(signatures, checker);
767-
return reduceContextTypes(signatureDeclarations.map(s => getDeclarationContextType(s, checker)));
778+
return reduceContextTypes(signatureDeclarations.map(s => getDeclarationContextType(s, program)));
768779
}
769780

770781
export function escapeString(text: string): string {

src/TSTransformers.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as resolve from "resolve";
33
import * as ts from "typescript";
44
import { CompilerOptions, TransformerImport } from "./CompilerOptions";
55
import * as diagnosticFactories from "./diagnostics";
6+
import { noImplicitSelfTransformer } from "./NoImplicitSelfTransformer";
67

78
export function getCustomTransformers(
89
program: ts.Program,
@@ -16,11 +17,19 @@ export function getCustomTransformers(
1617
};
1718

1819
const transformersFromOptions = loadTransformersFromOptions(program, diagnostics);
20+
21+
const afterDeclarations = [
22+
...(transformersFromOptions.afterDeclarations || []),
23+
...(customTransformers.afterDeclarations || []),
24+
];
25+
26+
const options = program.getCompilerOptions() as CompilerOptions;
27+
if (options.noImplicitSelf) {
28+
afterDeclarations.unshift(noImplicitSelfTransformer);
29+
}
30+
1931
return {
20-
afterDeclarations: [
21-
...(transformersFromOptions.afterDeclarations || []),
22-
...(customTransformers.afterDeclarations || []),
23-
],
32+
afterDeclarations,
2433
before: [
2534
...(customTransformers.before || []),
2635
...(transformersFromOptions.before || []),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import * as util from "../../util";
2+
3+
test("enables noSelfInFile behaviour for functions", () => {
4+
util.testFunction`
5+
function fooBar() {}
6+
const test: (this: void) => void = fooBar;
7+
`
8+
.setOptions({ noImplicitSelf: true })
9+
.expectToHaveNoDiagnostics();
10+
});
11+
12+
test("enables noSelfInFile behaviour for methods", () => {
13+
util.testFunction`
14+
class FooBar {
15+
fooBar() {}
16+
}
17+
const fooBar = new FooBar();
18+
const test: (this: any) => void = fooBar.fooBar;
19+
`
20+
.setOptions({ noImplicitSelf: true })
21+
.expectToHaveNoDiagnostics();
22+
});
23+
24+
test("generates declaration files with @noSelfInFile", () => {
25+
const builder = util.testModule`
26+
export function bar() {}
27+
`
28+
.setOptions({ declaration: true, noImplicitSelf: true })
29+
.expectToHaveNoDiagnostics();
30+
31+
const declarationFile = builder.getLuaResult().transpiledFiles.find(f => f.declaration);
32+
if (!util.expectToBeDefined(declarationFile) || !util.expectToBeDefined(declarationFile.declaration)) return;
33+
34+
util.testModule`
35+
import { bar } from "./foo.d";
36+
const test: (this: void) => void = bar;
37+
`
38+
.addExtraFile("foo.d.ts", declarationFile.declaration)
39+
.expectToHaveNoDiagnostics();
40+
});

0 commit comments

Comments
 (0)