X Tutup
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.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"scripts": {
"build": "tsc -p tsconfig.json && npm run build-lualib",
"build-lualib": "ts-node ./build_lualib.ts",
"test": "tslint -p . && tslint -c ./tslint.json src/lualib/*.ts && ts-node ./test/runner.ts",
"test": "tslint -p . && tslint -c ./tslint.json src/lualib/*.ts && npm run build-lualib && ts-node ./test/runner.ts",
"coverage": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov",
"coverage-html": "nyc npm test && nyc report --reporter=html",
"test-threaded": "tslint -p . && npm run build && ts-node ./test/threaded_runner.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function getYargOptionsWithoutDefaults(options: YargsOptions): [YargsOptions, ya
// options is a deep object, Object.assign or {...options} still keeps the referece
const copy = JSON.parse(JSON.stringify(options));

const optionDefaults: yargs.Arguments = {_: null, $0: null};
const optionDefaults: yargs.Arguments = {_: undefined, $0: undefined};
for (const optionName in copy) {
const section = copy[optionName];

Expand Down
2 changes: 1 addition & 1 deletion src/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export function transpileString(str: string,

useCaseSensitiveFileNames: () => false,
// Don't write output
writeFile: (name, text, writeByteOrderMark) => null,
writeFile: (name, text, writeByteOrderMark) => undefined,
};
const program = ts.createProgram(["file.ts"], options, compilerHost);

Expand Down
38 changes: 38 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,42 @@ export class TSTLErrors {

public static UnsupportedObjectLiteralElement = (elementKind: ts.SyntaxKind, node: ts.Node) =>
new TranspileError(`Unsupported object literal element: ${elementKind}.`, node)

public static UnsupportedFunctionConversion = (node: ts.Node, name?: string) => {
if (name) {
return new TranspileError(`Unsupported conversion from method to function "${name}". `
+ `To fix, wrap the method in an arrow function.`,
node);
} else {
return new TranspileError(`Unsupported conversion from method to function. `
+ `To fix, wrap the method in an arrow function.`,
node);
}
}

public static UnsupportedMethodConversion = (node: ts.Node, name?: string) => {
if (name) {
return new TranspileError(`Unsupported conversion from function to method "${name}". `
+ `To fix, wrap the function in an arrow function or declare the function with`
+ ` an explicit 'this' parameter.`,
node);
} else {
return new TranspileError(`Unsupported conversion from function to method. `
+ `To fix, wrap the function in an arrow function or declare the function with`
+ ` an explicit 'this' parameter.`,
node);
}
}

public static UnsupportedOverloadAssignment = (node: ts.Node, name?: string) => {
if (name) {
return new TranspileError(`Unsupported assignment of mixed function/method overload to "${name}". `
+ `Overloads should either be all functions or all methods, but not both.`,
node);
} else {
return new TranspileError(`Unsupported assignment of mixed function/method overload. `
+ `Overloads should either be all functions or all methods, but not both.`,
node);
}
}
}
219 changes: 174 additions & 45 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import * as ts from "typescript";
import { Decorator, DecoratorKind } from "./Decorator";

export enum ContextType {
None,
Void,
NonVoid,
Mixed,
}

const defaultArrayCallMethodNames = new Set<string>([
"concat",
"push",
Expand All @@ -27,7 +34,7 @@ const defaultArrayPropertyNames = new Set<string>([
export class TSHelper {

// Reverse lookup of enum key by value
public static enumName(needle, haystack): string {
public static enumName<T>(needle: T, haystack: any): string {
for (const name in haystack) {
if (haystack[name] === needle) {
return name;
Expand All @@ -37,7 +44,7 @@ export class TSHelper {
}

// Breaks down a mask into all flag names.
public static enumNames(mask, haystack): string[] {
public static enumNames<T>(mask: number, haystack: any): string[] {
const result = [];
for (const name in haystack) {
if ((mask & haystack[name]) !== 0 && mask >= haystack[name]) {
Expand Down Expand Up @@ -80,23 +87,27 @@ export class TSHelper {
}

public static isInDestructingAssignment(node: ts.Node): boolean {
return node.parent && ((ts.isVariableDeclaration(node.parent) && ts.isArrayBindingPattern(node.parent.name))
return node.parent && (
(ts.isVariableDeclaration(node.parent) && ts.isArrayBindingPattern(node.parent.name))
|| (ts.isBinaryExpression(node.parent) && ts.isArrayLiteralExpression(node.parent.left)));
}

// iterate over a type and its bases until the callback returns true.
public static forTypeOrAnySupertype(type: ts.Type, checker: ts.TypeChecker, callback: (type: ts.Type) => boolean):
boolean {
if (callback(type)) {
public static forTypeOrAnySupertype(
type: ts.Type,
checker: ts.TypeChecker,
predicate: (type: ts.Type) => boolean
): boolean {
if (predicate(type)) {
return true;
}
if (!type.isClassOrInterface() && type.symbol) {
type = checker.getDeclaredTypeOfSymbol(type.symbol);
}
const baseTypes = type.getBaseTypes();
if (baseTypes) {
for (const baseType of baseTypes) {
if (this.forTypeOrAnySupertype(baseType, checker, callback)) {
const superTypes = type.getBaseTypes();
if (superTypes) {
for (const superType of superTypes) {
if (this.forTypeOrAnySupertype(superType, checker, predicate)) {
return true;
}
}
Expand All @@ -122,6 +133,11 @@ export class TSHelper {
return typeNode && this.isArrayTypeNode(typeNode);
}

public static isFunctionType(type: ts.Type, checker: ts.TypeChecker): boolean {
const typeNode = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias);
return typeNode && ts.isFunctionTypeNode(typeNode);
}

public static isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean {
return this.forTypeOrAnySupertype(type, checker, t => this.isExplicitArrayType(t, checker));
}
Expand Down Expand Up @@ -151,40 +167,54 @@ export class TSHelper {
}
}

public static getCustomDecorators(type: ts.Type, checker: ts.TypeChecker): Map<DecoratorKind, Decorator> {
if (type.symbol) {
const comments = type.symbol.getDocumentationComment(checker);
const decorators =
comments.filter(comment => comment.kind === "text")
.map(comment => comment.text.split("\n"))
.reduce((a, b) => a.concat(b), [])
.map(line => line.trim())
.filter(comment => comment[0] === "!");

const decMap = new Map<DecoratorKind, Decorator>();

decorators.forEach(decStr => {
const [decoratorName, ...decoratorArguments] = decStr.split(" ");
if (Decorator.isValid(decoratorName.substr(1))) {
const dec = new Decorator(decoratorName.substr(1), decoratorArguments);
decMap.set(dec.kind, dec);
console.warn(`[Deprecated] Decorators with ! are being deprecated, `
+ `use @${decStr.substr(1)} instead`);
} else {
console.warn(`Encountered unknown decorator ${decStr}.`);
}
});
public static getContainingFunctionReturnType(node: ts.Node, checker: ts.TypeChecker): ts.Type {
const declaration = this.findFirstNodeAbove(node, ts.isFunctionLike);
if (declaration) {
const signature = checker.getSignatureFromDeclaration(declaration);
return checker.getReturnTypeOfSignature(signature);
}
return undefined;
}

type.symbol.getJsDocTags().forEach(tag => {
if (Decorator.isValid(tag.name)) {
const dec = new Decorator(tag.name, tag.text ? tag.text.split(" ") : []);
decMap.set(dec.kind, dec);
}
});
public static collectCustomDecorators(symbol: ts.Symbol, checker: ts.TypeChecker,
decMap: Map<DecoratorKind, Decorator>): void {
const comments = symbol.getDocumentationComment(checker);
const decorators =
comments.filter(comment => comment.kind === "text")
.map(comment => comment.text.split("\n"))
.reduce((a, b) => a.concat(b), [])
.map(line => line.trim())
.filter(comment => comment[0] === "!");

decorators.forEach(decStr => {
const [decoratorName, ...decoratorArguments] = decStr.split(" ");
if (Decorator.isValid(decoratorName.substr(1))) {
const dec = new Decorator(decoratorName.substr(1), decoratorArguments);
decMap.set(dec.kind, dec);
console.warn(`[Deprecated] Decorators with ! are being deprecated, `
+ `use @${decStr.substr(1)} instead`);
} else {
console.warn(`Encountered unknown decorator ${decStr}.`);
}
});

return decMap;
symbol.getJsDocTags().forEach(tag => {
if (Decorator.isValid(tag.name)) {
const dec = new Decorator(tag.name, tag.text ? tag.text.split(" ") : []);
decMap.set(dec.kind, dec);
}
});
}

public static getCustomDecorators(type: ts.Type, checker: ts.TypeChecker): Map<DecoratorKind, Decorator> {
const decMap = new Map<DecoratorKind, Decorator>();
if (type.symbol) {
this.collectCustomDecorators(type.symbol, checker, decMap);
}
if (type.aliasSymbol) {
this.collectCustomDecorators(type.aliasSymbol, checker, decMap);
}
return new Map<DecoratorKind, Decorator>();
return decMap;
}

// Search up until finding a node satisfying the callback
Expand All @@ -197,7 +227,7 @@ export class TSHelper {
current = current.parent;
}
}
return null;
return undefined;
}

public static hasExplicitGetAccessor(type: ts.Type, name: ts.__String): boolean {
Expand Down Expand Up @@ -260,7 +290,7 @@ export class TSHelper {
return [true, ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken];
}

return [false, null];
return [false, undefined];
}

public static isExpressionStatement(node: ts.Expression): boolean {
Expand Down Expand Up @@ -305,15 +335,114 @@ export class TSHelper {
} else if (ts.isPropertyAccessExpression(node) && this.isExpressionWithEvaluationEffect(node.expression)) {
return [true, node.expression, ts.createStringLiteral(node.name.text)];
}
return [false, null, null];
return [false, undefined, undefined];
}

public static isDefaultArrayCallMethodName(methodName: string): boolean {
return defaultArrayCallMethodNames.has(methodName);
}

public static getExplicitThisParameter(signatureDeclaration: ts.SignatureDeclaration): ts.ParameterDeclaration {
return signatureDeclaration.parameters
.find(param => ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
}

public static getSignatureDeclarations(signatures: ts.Signature[], checker: ts.TypeChecker)
: ts.SignatureDeclaration[] {
const signatureDeclarations: ts.SignatureDeclaration[] = [];
for (const signature of signatures) {
const signatureDeclaration = signature.getDeclaration();
if ((ts.isFunctionExpression(signatureDeclaration) || ts.isArrowFunction(signatureDeclaration))
&& !this.getExplicitThisParameter(signatureDeclaration)) {
// Function expressions: get signatures of type being assigned to, unless 'this' was explicit
let declType: ts.Type;
if (ts.isCallExpression(signatureDeclaration.parent)) {
// Function expression being passed as argument to another function
const i = signatureDeclaration.parent.arguments.indexOf(signatureDeclaration);
if (i >= 0) {
const parentSignature = checker.getResolvedSignature(signatureDeclaration.parent);
const parentSignatureDeclaration = parentSignature.getDeclaration();
declType = checker.getTypeAtLocation(parentSignatureDeclaration.parameters[i]);
}
} else if (ts.isReturnStatement(signatureDeclaration.parent)) {
declType = this.getContainingFunctionReturnType(signatureDeclaration.parent, checker);
} else {
// Function expression being assigned
declType = checker.getTypeAtLocation(signatureDeclaration.parent);
}
if (declType) {
const declSignatures = declType.getCallSignatures();
if (declSignatures.length > 0) {
declSignatures.map(s => s.getDeclaration()).forEach(decl => signatureDeclarations.push(decl));
continue;
}
}
}
signatureDeclarations.push(signatureDeclaration);
}
return signatureDeclarations;
}

public static getDeclarationContextType(signatureDeclaration: ts.SignatureDeclaration,
checker: ts.TypeChecker): ContextType {
const thisParameter = this.getExplicitThisParameter(signatureDeclaration);
if (thisParameter) {
// Explicit 'this'
return thisParameter.type && thisParameter.type.kind === ts.SyntaxKind.VoidKeyword
? ContextType.Void : ContextType.NonVoid;
}
if ((ts.isMethodDeclaration(signatureDeclaration) || ts.isMethodSignature(signatureDeclaration))
&& !(ts.getCombinedModifierFlags(signatureDeclaration) & ts.ModifierFlags.Static)) {
// Non-static method
return ContextType.NonVoid;
}
if ((ts.isPropertySignature(signatureDeclaration.parent)
|| ts.isPropertyDeclaration(signatureDeclaration.parent)
|| ts.isPropertyAssignment(signatureDeclaration.parent))
&& !(ts.getCombinedModifierFlags(signatureDeclaration.parent) & ts.ModifierFlags.Static)) {
// Non-static lambda property
return ContextType.NonVoid;
}
if (ts.isBinaryExpression(signatureDeclaration.parent)) {
// Function expression: check type being assigned to
return this.getFunctionContextType(checker.getTypeAtLocation(signatureDeclaration.parent.left), checker);
}
return ContextType.Void;
}

public static reduceContextTypes(contexts: ContextType[]): ContextType {
const reducer = (a: ContextType, b: ContextType) => {
if (a === ContextType.None) {
return b;
} else if (b === ContextType.None) {
return a;
} else if (a !== b) {
return ContextType.Mixed;
} else {
return a;
}
};
return contexts.reduce(reducer, ContextType.None);
}

public static getFunctionContextType(type: ts.Type, checker: ts.TypeChecker): ContextType {
if (type.isTypeParameter()) {
type = type.getConstraint() || type;
}

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

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

public static isDefaultArrayPropertyName(methodName: string): boolean {
return defaultArrayPropertyNames.has(methodName);
}

}
Loading
X Tutup