X Tutup
Skip to content

Commit ce74fe6

Browse files
authored
Feature/jsdoc annotations (#285)
* Added support for JSDoc tags as decorators * Fixed invalid jsdoc failing a test * Added deprecation warning for ! decorators * Refactored decorator creation, made decorators case-insensitive * Fixed jsdoc tests * Fixed test runner not failing
2 parents b9838b0 + 3d994fa commit ce74fe6

20 files changed

+305
-80
lines changed

src/Decorator.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
export class Decorator {
2+
3+
public static isValid(decoratorKindString: string): boolean {
4+
return this.getDecoratorKind(decoratorKindString) !== undefined;
5+
}
6+
7+
public static getDecoratorKind(decoratorKindString: string): DecoratorKind {
8+
switch (decoratorKindString.toLowerCase()) {
9+
case "extension": return DecoratorKind.Extension;
10+
case "metaextension": return DecoratorKind.MetaExtension;
11+
case "customconstructor": return DecoratorKind.CustomConstructor;
12+
case "compilemembersonly": return DecoratorKind.CompileMembersOnly;
13+
case "pureabstract": return DecoratorKind.PureAbstract;
14+
case "phantom": return DecoratorKind.Phantom;
15+
case "tuplereturn": return DecoratorKind.TupleReturn;
16+
case "noclassor": return DecoratorKind.NoClassOr;
17+
}
18+
19+
return undefined;
20+
}
21+
222
public kind: DecoratorKind;
323
public args: string[];
424

5-
constructor(raw: string) {
6-
let nameEnd = raw.indexOf(" ");
7-
if (nameEnd === -1) {
8-
nameEnd = raw.length;
9-
}
10-
this.kind = DecoratorKind[raw.substring(1, nameEnd)];
11-
this.args = raw.split(" ").slice(1);
25+
constructor(name: string, args: string[]) {
26+
this.kind = Decorator.getDecoratorKind(name);
27+
this.args = args;
1228
}
1329
}
1430

src/TSHelper.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,32 @@ export class TSHelper {
109109
const comments = type.symbol.getDocumentationComment(checker);
110110
const decorators =
111111
comments.filter(comment => comment.kind === "text")
112-
.map(comment => comment.text.trim().split("\n"))
112+
.map(comment => comment.text.split("\n"))
113113
.reduce((a, b) => a.concat(b), [])
114+
.map(line => line.trim())
114115
.filter(comment => comment[0] === "!");
116+
115117
const decMap = new Map<DecoratorKind, Decorator>();
118+
116119
decorators.forEach(decStr => {
117-
const dec = new Decorator(decStr);
118-
decMap.set(dec.kind, dec);
120+
const [decoratorName, ...decoratorArguments] = decStr.split(" ");
121+
if (Decorator.isValid(decoratorName.substr(1))) {
122+
const dec = new Decorator(decoratorName.substr(1), decoratorArguments);
123+
decMap.set(dec.kind, dec);
124+
console.warn(`[Deprecated] Decorators with ! are being deprecated, `
125+
+ `use @${decStr.substr(1)} instead`);
126+
} else {
127+
console.warn(`Encountered unknown decorator ${decStr}.`);
128+
}
119129
});
130+
131+
type.symbol.getJsDocTags().forEach(tag => {
132+
if (Decorator.isValid(tag.name)) {
133+
const dec = new Decorator(tag.name, tag.text ? tag.text.split(" ") : []);
134+
decMap.set(dec.kind, dec);
135+
}
136+
});
137+
120138
return decMap;
121139
}
122140
return new Map<DecoratorKind, Decorator>();

src/lualib/StringReplace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
declare namespace string {
2-
/** !TupleReturn */
2+
/** @tupleReturn */
33
function gsub(source: string, searchValue: string, replaceValue: string): [string, number];
44
}
55

test/runner.ts

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { TestRunner, TestSet } from "alsatian";
2-
import { TapBark } from "tap-bark";
1+
import { TestRunner, TestSet, TestOutcome } from "alsatian";
32

43
import * as fs from "fs";
54
import * as path from "path";
@@ -21,21 +20,44 @@ fs.copyFileSync(
2120

2221
// setup the output
2322
testRunner.outputStream
24-
// this will use alsatian's default output if you remove this
25-
// you'll get TAP or you can add your favourite TAP reporter in it's place
26-
.pipe(TapBark.create().getPipeable())
27-
// pipe to the console
28-
.pipe(process.stdout);
23+
// pipe to the console
24+
.pipe(process.stdout);
25+
26+
let success = 0;
27+
let ignored = 0;
28+
let run = 0;
29+
testRunner.onTestComplete(test => {
30+
run++;
31+
32+
if (test.outcome === TestOutcome.Pass) {
33+
success++;
34+
} else if (test.outcome === TestOutcome.Skip) {
35+
ignored++;
36+
}
37+
});
2938

3039
// run the test set
3140
testRunner.run(testSet)
32-
// this will be called after all tests have been run
33-
.then(result => {
34-
// Remove lualib bundle again
35-
fs.unlinkSync("lualib_bundle.lua");
36-
})
37-
// this will be called if there was a problem
38-
.catch(error => {
39-
// Remove lualib bundle again
40-
fs.unlinkSync("lualib_bundle.lua");
41-
});
41+
// this will be called after all tests have been run
42+
.then(result => {
43+
// Remove lualib bundle again
44+
fs.unlinkSync("lualib_bundle.lua");
45+
46+
const nonIgnoredTests = run - ignored;
47+
const failedTests = nonIgnoredTests - success;
48+
console.log(`Ignored ${ignored}/${run} tests.`);
49+
console.log(`Failed ${failedTests}/${nonIgnoredTests} tests.`);
50+
console.log(`Passed ${success}/${nonIgnoredTests} tests.`);
51+
52+
if (failedTests > 0) {
53+
process.exit(1);
54+
}
55+
})
56+
// this will be called if there was a problem
57+
.catch(error => {
58+
// Remove lualib bundle again
59+
fs.unlinkSync("lualib_bundle.lua");
60+
61+
console.error(error);
62+
process.exit(1);
63+
});

test/src/util.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,54 @@ export function transpileAndExecute(tsStr: string): any {
6767
return executeLua(transpileString(tsStr));
6868
}
6969

70+
export function parseTypeScript(typescript: string, target: LuaTarget = LuaTarget.Lua53)
71+
: [ts.SourceFile, ts.TypeChecker] {
72+
const compilerHost = {
73+
directoryExists: () => true,
74+
fileExists: (fileName): boolean => true,
75+
getCanonicalFileName: fileName => fileName,
76+
getCurrentDirectory: () => "",
77+
getDefaultLibFileName: () => "lib.es6.d.ts",
78+
getDirectories: () => [],
79+
getNewLine: () => "\n",
80+
81+
getSourceFile: (filename, languageVersion) => {
82+
if (filename === "file.ts") {
83+
return ts.createSourceFile(filename, typescript, ts.ScriptTarget.Latest, false);
84+
}
85+
if (filename === "lib.es6.d.ts") {
86+
const libPath = path.join(path.dirname(require.resolve("typescript")), "lib.es6.d.ts");
87+
const libSource = fs.readFileSync(libPath).toString();
88+
return ts.createSourceFile(filename, libSource, ts.ScriptTarget.Latest, false);
89+
}
90+
return undefined;
91+
},
92+
93+
readFile: () => "",
94+
95+
useCaseSensitiveFileNames: () => false,
96+
// Don't write output
97+
writeFile: (name, text, writeByteOrderMark) => null,
98+
};
99+
100+
const program = ts.createProgram(["file.ts"], { luaTarget: target }, compilerHost);
101+
return [program.getSourceFile("file.ts"), program.getTypeChecker()];
102+
}
103+
104+
export function findFirstChild(node: ts.Node, predicate: (node: ts.Node) => boolean): ts.Node | undefined {
105+
for (const child of node.getChildren()) {
106+
if (predicate(child)) {
107+
return child;
108+
}
109+
110+
const childChild = findFirstChild(child, predicate);
111+
if (childChild !== undefined) {
112+
return childChild;
113+
}
114+
}
115+
return undefined;
116+
}
117+
70118
const jsonlib = fs.readFileSync("test/src/json.lua") + "\n";
71119

72120
export const minimalTestLib = jsonlib;

test/translation/ts/assignments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ declare function getIndex(): number;
1010
declare let xTup: [number, number];
1111
declare let yTup: [number, number];
1212
declare function getTup(): [number, number];
13-
/** !TupleReturn */
13+
/** @tupleReturn */
1414
declare function getTupRet(): [number, number];
1515
x = y;
1616
x = obj.prop;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** !Extension */
1+
/** @extension */
22
class MyClass {
33
public myFunction() {}
44
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
/** !Extension */
1+
/** @extension */
22
class TestClass {
33
}
44

5-
/** !Extension */
5+
/** @extension */
66
class MyClass extends TestClass {
77
public myFunction() {}
88
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
/** !Extension RenamedTestClass */
1+
/** @extension RenamedTestClass */
22
class TestClass {
33
public myFunction() {}
44
}
55

6-
/** !Extension RenamedMyClass */
6+
/** @extension RenamedMyClass */
77
class MyClass extends TestClass {
88
public myFunction() {}
99
}

test/translation/ts/classExtension4.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** !Extension */
1+
/** @extension */
22
class MyClass {
33
public test: string = "test";
44
private testP: string = "testP";

0 commit comments

Comments
 (0)
X Tutup