Compare commits
17 Commits
0.17.5
...
no-reflect
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8890b62b46 | ||
![]() |
cce63becd6 | ||
![]() |
a101378a4c | ||
![]() |
b48b300cbc | ||
![]() |
27c9b20330 | ||
![]() |
b4fa5f4fe0 | ||
![]() |
e59fab499d | ||
![]() |
3356172b7f | ||
![]() |
4fd03ed667 | ||
![]() |
4cb5ee4196 | ||
![]() |
888481094a | ||
![]() |
7686fe3e35 | ||
![]() |
747d07bfc5 | ||
![]() |
00640d8328 | ||
![]() |
77973f6007 | ||
![]() |
0df9c28ace | ||
![]() |
d90fb966d2 |
2
.haxerc
2
.haxerc
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "4.0.0-rc.1",
|
||||
"version": "4.0.5",
|
||||
"resolveLibs": "scoped"
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
|
||||
stages:
|
||||
- test
|
||||
@@ -17,7 +17,7 @@ env:
|
||||
- HAXE_VERSION=edge
|
||||
|
||||
install:
|
||||
- npm i -g lix
|
||||
- npm i -g lix@15.5.4
|
||||
- lix install haxe $HAXE_VERSION
|
||||
- lix download
|
||||
|
||||
|
@@ -1,3 +1,3 @@
|
||||
# @install: lix --silent download "haxelib:tink_core#1.16.1" into tink_core/1.16.1/haxelib
|
||||
-D tink_core=1.16.1
|
||||
-cp ${HAXESHIM_LIBCACHE}/tink_core/1.16.1/haxelib/src
|
||||
-D tink_core=1.24.0
|
||||
# @install: lix --silent download "haxelib:/tink_core#1.24.0" into tink_core/1.24.0/haxelib
|
||||
-cp ${HAXE_LIBCACHE}/tink_core/1.24.0/haxelib/src
|
||||
|
@@ -11,8 +11,8 @@
|
||||
"contributors": [
|
||||
"back2dos"
|
||||
],
|
||||
"releasenote": "Various fixes",
|
||||
"version": "0.17.5",
|
||||
"releasenote": "Compat with haxe 4 rc4 onward.",
|
||||
"version": "0.18.0",
|
||||
"url": "http://haxetink.org/tink_macro",
|
||||
"dependencies": {
|
||||
"tink_core": ""
|
||||
|
@@ -29,7 +29,20 @@ typedef TypeResolution = Ref<Either<String, TypeDefinition>>;
|
||||
|
||||
class MacroApi {
|
||||
|
||||
static var idCounter = 0;
|
||||
static var MAIN_CANDIDATES = ['-main', '-x', '--run'];
|
||||
static public function getMainClass():Option<String> {
|
||||
var args = Sys.args();
|
||||
|
||||
for (c in MAIN_CANDIDATES)
|
||||
switch args.indexOf(c) {
|
||||
case -1:
|
||||
case v: return Some(args[v+1]);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
@:persistent static var idCounter = 0;
|
||||
|
||||
@:noUsing static public inline function tempName(?prefix:String = 'tmp'):String
|
||||
return '__tink_' + prefix + Std.string(idCounter++);
|
||||
|
@@ -33,7 +33,7 @@ typedef BuildContext3 = {>BuildContext2,
|
||||
|
||||
class BuildCache {
|
||||
|
||||
static var cache = new Map();
|
||||
@:persistent static var cache = new Map();
|
||||
|
||||
static public function getType3(name, ?types, ?pos:Position, build:BuildContext3->TypeDefinition) {
|
||||
if (types == null)
|
||||
|
@@ -95,10 +95,33 @@ class Constructor {
|
||||
case Value(e): e;
|
||||
}
|
||||
|
||||
var tmp = MacroApi.tempName();
|
||||
var member = owner.memberByName(name).sure();
|
||||
var member = owner.memberByName(name).sure(),
|
||||
initStatement = macro @:pos(pos) this.$name = $e;
|
||||
|
||||
if (options.bypass && member.kind.match(FProp(_, 'never' | 'set', _, _))) {
|
||||
var bypass = switch options.bypass {
|
||||
case null:
|
||||
switch member.kind {
|
||||
case FProp(_, 'default' | 'null', _):
|
||||
false;
|
||||
#if haxe4
|
||||
case FProp('default', 'never', _):
|
||||
member.isFinal = true;
|
||||
false;
|
||||
case FProp(_, 'set', _):
|
||||
member.addMeta(':isVar');
|
||||
initStatement = macro @:pos(pos) @:bypassAccessor this.$name = $e;
|
||||
false;
|
||||
#end
|
||||
case FProp(_):
|
||||
true;
|
||||
case FFun(_):
|
||||
pos.error('cannot rebind function');
|
||||
default: false;
|
||||
}
|
||||
case v: v;
|
||||
}
|
||||
|
||||
if (bypass && member.kind.match(FProp(_, 'never' | 'set', _, _))) {
|
||||
|
||||
member.addMeta(':isVar');
|
||||
|
||||
@@ -130,8 +153,14 @@ class Constructor {
|
||||
macro @:pos(pos) this.$name = $e;
|
||||
case FVar(AccNo | AccNormal, AccNever):
|
||||
setDirectly(Context.typeExpr(macro @:pos(pos) this.$name));
|
||||
#if haxe4
|
||||
case FVar(AccCall, _):
|
||||
var target = Context.storeTypedExpr(Context.typeExpr(macro @:pos(pos) @:bypassAccessor this.$name));
|
||||
macro @:pos(pos) $target = $e;
|
||||
#else
|
||||
case FVar(AccCall, AccNever):
|
||||
setDirectly(fields['get_$name'].expr());
|
||||
#end
|
||||
case FVar(_, AccCall):
|
||||
setDirectly(fields['set_$name'].expr());
|
||||
default:
|
||||
@@ -141,7 +170,10 @@ class Constructor {
|
||||
}).bounce(), options.prepend);
|
||||
}
|
||||
else
|
||||
addStatement(macro @:pos(pos) this.$name = $e, options.prepend);
|
||||
addStatement(
|
||||
initStatement,
|
||||
options.prepend
|
||||
);
|
||||
}
|
||||
public inline function publish()
|
||||
if (isPublic == null)
|
||||
|
@@ -1,12 +1,11 @@
|
||||
package tink.macro;
|
||||
|
||||
import Type in Inspect;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Type;
|
||||
import haxe.PosInfos;
|
||||
import haxe.macro.Printer;
|
||||
import haxe.DynamicAccess as Dict;
|
||||
|
||||
using Lambda;
|
||||
using StringTools;
|
||||
@@ -20,6 +19,7 @@ typedef VarDecl = { name : String, type : ComplexType, expr : Null<Expr> };
|
||||
typedef ParamSubst = {
|
||||
var exists(default, null):String->Bool;
|
||||
var get(default, null):String->ComplexType;
|
||||
function iterator():Iterator<ComplexType>;
|
||||
}
|
||||
|
||||
private class Heureka { public function new() {} }
|
||||
@@ -48,20 +48,21 @@ class Exprs {
|
||||
static public inline function as(e:Expr, c:ComplexType)
|
||||
return ECheckType(e, c).at(e.pos);
|
||||
|
||||
static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dynamic<String>, ?skipFields = false, ?callPos:PosInfos) {
|
||||
static public function finalize(e:Expr, ?nuPos:Position, ?rules:Dict<String>, ?skipFields = false, ?callPos:PosInfos) {
|
||||
if (nuPos == null)
|
||||
nuPos = Context.currentPos();
|
||||
if (rules == null)
|
||||
rules = { };
|
||||
function replace(s:String)
|
||||
return {
|
||||
if (Reflect.hasField(rules, s))
|
||||
Reflect.field(rules, s)
|
||||
else if (s.startsWith('tmp')) {
|
||||
Reflect.setField(rules, s, MacroApi.tempName(s.substr(3)));
|
||||
replace(s);
|
||||
}
|
||||
else s;
|
||||
|
||||
function replace(s:String):String
|
||||
return switch rules[s] {
|
||||
case null:
|
||||
if (s.startsWith('tmp')) {
|
||||
rules[s] = MacroApi.tempName(s.substr(3));
|
||||
replace(s);
|
||||
}
|
||||
else s;
|
||||
case v: v;
|
||||
}
|
||||
|
||||
return e.transform(function (e:Expr) {
|
||||
@@ -110,18 +111,17 @@ class Exprs {
|
||||
static public function getPrivate(e:Expr, field:String, ?pos:Position)
|
||||
return macro @:pos(pos.sanitize()) @:privateAccess $e.$field;
|
||||
|
||||
static public function substitute(source:Expr, vars:Dynamic<Expr>, ?pos)
|
||||
static public function substitute(source:Expr, vars:Dict<Expr>, ?pos)
|
||||
return
|
||||
transform(source, function (e:Expr) {
|
||||
return
|
||||
switch (e.getIdent()) {
|
||||
case Success(name):
|
||||
if (Reflect.hasField(vars, name))
|
||||
Reflect.field(vars, name);
|
||||
else
|
||||
e;
|
||||
default: e;
|
||||
}
|
||||
return switch e {
|
||||
case macro $i{name}:
|
||||
switch vars[name] {
|
||||
case null: e;
|
||||
case v: v;
|
||||
}
|
||||
default: e;
|
||||
}
|
||||
}, pos);
|
||||
|
||||
static public inline function ifNull(e:Expr, fallback:Expr)
|
||||
@@ -131,44 +131,68 @@ class Exprs {
|
||||
default: e;
|
||||
}
|
||||
|
||||
static public function substParams(source:Expr, subst:ParamSubst, ?pos):Expr
|
||||
return crawl(
|
||||
source,
|
||||
function (e)
|
||||
return switch e.expr {
|
||||
case ENew({ pack: [], name: name }, args) if (subst.exists(name)):
|
||||
switch subst.get(name) {
|
||||
case TPath(p):
|
||||
ENew(p, args).at(e.pos);
|
||||
default: e;//TODO: report an error?
|
||||
}
|
||||
case EConst(CIdent(name)) if (subst.exists(name)):
|
||||
static public function substParams(source:Expr, subst:ParamSubst, ?pos:Position):Expr {
|
||||
if (!subst.iterator().hasNext())
|
||||
return source;
|
||||
|
||||
function replace(ct:ComplexType)
|
||||
return switch ct {
|
||||
case TPath({ pack: [], name: name }) if (subst.exists(name)):
|
||||
subst.get(name);
|
||||
default: ct;
|
||||
}
|
||||
|
||||
return source
|
||||
.transform(
|
||||
function (e) return switch e {
|
||||
case macro $i{name} if (subst.exists(name)):
|
||||
switch subst.get(name) {
|
||||
case TPath({ pack: pack, name: name }):
|
||||
pack.concat([name]).drill(e.pos);
|
||||
default: e;//TODO: report an error?
|
||||
}
|
||||
default: e;
|
||||
},
|
||||
function (c:ComplexType)
|
||||
return
|
||||
switch (c) {
|
||||
case TPath({ pack: [], name: name }) if (subst.exists(name)):
|
||||
subst.get(name);
|
||||
default: c;
|
||||
})
|
||||
.mapTypes(replace);
|
||||
}
|
||||
|
||||
static public function mapTypes(e:Expr, transformer:ComplexType->ComplexType, ?pos:Position) {
|
||||
return e.transform(
|
||||
function (e) return switch e {
|
||||
case { expr: ENew(p, args) }:
|
||||
switch TPath(p).map(transformer) {
|
||||
case TPath(nu):
|
||||
ENew(nu, args).at(e.pos);
|
||||
case v:
|
||||
pos.error(v.toString() + ' cannot be instantiated in ${e.pos}');
|
||||
}
|
||||
, pos);
|
||||
case macro cast($v, $ct):
|
||||
ct = ct.map(transformer);
|
||||
(macro @:pos(e.pos) cast($v, $ct));
|
||||
case { expr: ECheckType(v, ct) }: // cannot use reification, because that's `EParentheses(ECheckType)` and macros can (and apparently do) generate a non-wrapped one
|
||||
ECheckType(v, ct.map(transformer)).at(e.pos);
|
||||
case { expr: EVars(vars) }:
|
||||
EVars([for (v in vars) {
|
||||
name: v.name,
|
||||
type: v.type.map(transformer),
|
||||
expr: v.expr,
|
||||
}]).at(e.pos);
|
||||
case { expr: EFunction(kind, f) }:
|
||||
EFunction(kind, Functions.mapSignature(f, transformer)).at(e.pos);
|
||||
default: e;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr
|
||||
return crawl(source, transformer, function (t) return t, pos);
|
||||
static public function transform(source:Expr, transformer:Expr->Expr, ?pos):Expr {
|
||||
function apply(e:Expr)
|
||||
return switch e {
|
||||
case null | { expr: null }: e;
|
||||
default: transformer(haxe.macro.ExprTools.map(e, apply));
|
||||
}
|
||||
|
||||
static function crawlArray(a:Array<Dynamic>, transformer:Expr->Expr, retyper:ComplexType-> ComplexType, pos:Position):Array<Dynamic>
|
||||
return
|
||||
if (a == null) a;
|
||||
else
|
||||
[for (v in a)
|
||||
crawl(v, transformer, retyper, pos)
|
||||
];
|
||||
return apply(source);
|
||||
}
|
||||
|
||||
static public function getIterType(target:Expr)
|
||||
return
|
||||
@@ -201,10 +225,11 @@ class Exprs {
|
||||
,ETernary(econd, eif, eelse):
|
||||
EIf(econd, rec(eif), rec(eelse)).at(e.pos);
|
||||
case ESwitch(e, cases, edef):
|
||||
cases = Reflect.copy(cases);//not exactly pretty, but does the job
|
||||
for (c in cases)
|
||||
c.expr = rec(c.expr);
|
||||
ESwitch(e, cases, rec(edef)).at(e.pos);
|
||||
ESwitch(e, [for (c in cases) {
|
||||
expr: rec(c.expr),
|
||||
guard: c.guard,
|
||||
values: c.values,
|
||||
}], rec(edef)).at(e.pos);
|
||||
case EFor(it, expr) if (loops):
|
||||
EFor(it, rec(expr)).at(e.pos);
|
||||
case EWhile(cond, body, normal) if (loops):
|
||||
@@ -219,36 +244,12 @@ class Exprs {
|
||||
}
|
||||
}
|
||||
|
||||
static function crawl(target:Dynamic, transformer:Expr->Expr, retyper:ComplexType->ComplexType, pos:Position):Dynamic
|
||||
return
|
||||
if (Std.is(target, Array))
|
||||
crawlArray(target, transformer, retyper, pos);
|
||||
else
|
||||
switch (Inspect.typeof(target)) {
|
||||
case TNull, TInt, TFloat, TBool, TFunction, TUnknown, TClass(_): target;
|
||||
case TEnum(e):
|
||||
var ret:Dynamic = Inspect.createEnumIndex(e, Inspect.enumIndex(target), crawlArray(Inspect.enumParameters(target), transformer, retyper, pos));
|
||||
if (Inspect.getEnum(ret) == ComplexType)
|
||||
retyper(ret);
|
||||
else
|
||||
ret;
|
||||
case TObject:
|
||||
var ret:Dynamic = { };
|
||||
for (field in Reflect.fields(target))
|
||||
Reflect.setField(ret, field, crawl(Reflect.field(target, field), transformer, retyper, pos));
|
||||
if (Std.is(ret.expr, ExprDef)) {
|
||||
ret = transformer(ret);
|
||||
if (pos != null) ret.pos = pos;
|
||||
}
|
||||
ret;
|
||||
}
|
||||
|
||||
static public inline function iterate(target:Expr, body:Expr, ?loopVar:String = 'i', ?pos:Position)
|
||||
return macro @:pos(pos.sanitize()) for ($i{loopVar} in $target) $body;
|
||||
|
||||
static public function toFields(object:Dynamic<Expr>, ?pos:Position)
|
||||
return EObjectDecl([for (field in Reflect.fields(object))
|
||||
{ field:field, expr: untyped Reflect.field(object, field) }
|
||||
static public function toFields(object:Dict<Expr>, ?pos:Position)
|
||||
return EObjectDecl([for (field in object.keys())
|
||||
{ field:field, expr: object[field] }
|
||||
]).at(pos);
|
||||
|
||||
static public inline function log(e:Expr, ?pos:PosInfos):Expr {
|
||||
|
@@ -4,9 +4,18 @@ import haxe.macro.Expr;
|
||||
|
||||
using tink.macro.Exprs;
|
||||
|
||||
#if haxe4
|
||||
private abstract Kind(FunctionKind) from FunctionKind to FunctionKind {
|
||||
@:from static function ofString(s:String):Kind
|
||||
return FNamed(s);
|
||||
}
|
||||
#else
|
||||
private typedef Kind = String;
|
||||
#end
|
||||
|
||||
class Functions {
|
||||
static public inline function asExpr(f:Function, ?name, ?pos)
|
||||
return EFunction(name, f).at(pos);
|
||||
static public inline function asExpr(f:Function, ?kind:Kind, ?pos)
|
||||
return EFunction(kind, f).at(pos);
|
||||
|
||||
static public inline function toArg(name:String, ?t, ?opt = false, ?value = null):FunctionArg {
|
||||
return {
|
||||
@@ -24,6 +33,22 @@ class Functions {
|
||||
expr: if (makeReturn) EReturn(e).at(e.pos) else e
|
||||
}
|
||||
}
|
||||
|
||||
static public function mapSignature(f:Function, transform):Function
|
||||
return {
|
||||
ret: Types.map(f.ret, transform),
|
||||
args: [for (a in f.args) {
|
||||
name: a.name,
|
||||
value: a.value,
|
||||
type: Types.map(a.type, transform),
|
||||
#if haxe4
|
||||
meta: a.meta,
|
||||
#end
|
||||
}],
|
||||
expr: f.expr,
|
||||
params: Types.mapTypeParamDecls(f.params, transform),
|
||||
}
|
||||
|
||||
static public function getArgIdents(f:Function):Array<Expr> {
|
||||
var ret = [];
|
||||
for (arg in f.args)
|
||||
|
@@ -3,6 +3,7 @@ package tink.macro;
|
||||
import haxe.macro.Expr;
|
||||
|
||||
class Metadatas {
|
||||
static var printer = new haxe.macro.Printer();
|
||||
static public function toMap(m:Metadata) {
|
||||
var ret = new Map<String,Array<Array<Expr>>>();
|
||||
if (m != null)
|
||||
@@ -18,4 +19,8 @@ class Metadatas {
|
||||
return
|
||||
if (m == null) [];
|
||||
else [for (meta in m) if (meta.name == name) meta.params];
|
||||
|
||||
static public inline function toString(m:MetadataEntry) {
|
||||
return printer.printMetadata(m);
|
||||
}
|
||||
}
|
@@ -70,6 +70,79 @@ class Types {
|
||||
getDeclaredFields(t.superClass.t.get(), out, marker);
|
||||
}
|
||||
|
||||
static public function mapTypeParams(a:Array<TypeParam>, f)
|
||||
return
|
||||
if (a == null) null;
|
||||
else [for (p in a) switch p {
|
||||
case TPType(t): TPType(t.map(f));
|
||||
default: p;
|
||||
}];
|
||||
|
||||
static public function mapTypeParamDecls(a:Array<TypeParamDecl>, f:ComplexType->ComplexType):Array<TypeParamDecl>
|
||||
return
|
||||
if (a == null) null;
|
||||
else [for (p in a) {
|
||||
name: p.name,
|
||||
meta: p.meta,
|
||||
params: mapTypeParamDecls(p.params, f),
|
||||
constraints: switch p.constraints {
|
||||
case null: null;
|
||||
case a: [for (t in a) map(t, f)];
|
||||
}
|
||||
}];
|
||||
|
||||
static public function mapTypePath(p:TypePath, transform):TypePath
|
||||
return {
|
||||
name: p.name,
|
||||
pack: p.pack,
|
||||
sub: p.sub,
|
||||
params: mapTypeParams(p.params, transform),
|
||||
}
|
||||
|
||||
static public function map(ct:ComplexType, transform:ComplexType->ComplexType) {
|
||||
if (ct == null)
|
||||
return null;
|
||||
inline function rec(ct)
|
||||
return map(transform(ct), transform);
|
||||
|
||||
function mapFields(fields:Array<Field>):Array<Field>
|
||||
return [for (f in fields) {
|
||||
name: f.name,
|
||||
pos: f.pos,
|
||||
kind: switch f.kind {
|
||||
case FVar(t, e): FVar(rec(t), e);
|
||||
case FProp(get, set, t, e): FProp(get, set, rec(t), e);
|
||||
case FFun(f): FFun(Functions.mapSignature(f, transform));
|
||||
},
|
||||
access: f.access,
|
||||
meta: switch f.meta {
|
||||
case null: null;
|
||||
case a: [for (m in a) {
|
||||
name: m.name,
|
||||
pos: m.pos,
|
||||
params: [for (e in m.params) e.mapTypes(transform)],
|
||||
}];
|
||||
},
|
||||
doc: f.doc,
|
||||
}];
|
||||
return transform(switch ct {
|
||||
case TParent(t): TParent(rec(t));
|
||||
#if haxe4
|
||||
case TNamed(n, t): TNamed(n, rec(t));
|
||||
case TIntersection(tl): TIntersection([for (t in tl) rec(t)]);
|
||||
#end
|
||||
case TOptional(t): TOptional(rec(t));
|
||||
case TAnonymous(fields):
|
||||
TAnonymous(mapFields(fields));
|
||||
case TPath(p):
|
||||
TPath(mapTypePath(p, transform));
|
||||
case TExtend(paths, fields):
|
||||
TExtend([for (p in paths) mapTypePath(p, transform)], mapFields(fields));
|
||||
case TFunction(args, ret):
|
||||
TFunction([for (a in args) rec(a)], rec(ret));
|
||||
});
|
||||
}
|
||||
|
||||
static var fieldsCache = new Map();
|
||||
static public function getFields(t:Type, ?substituteParams = true)
|
||||
return
|
||||
@@ -84,12 +157,28 @@ class Types {
|
||||
}
|
||||
var ret = fieldsCache.get(id);
|
||||
if (substituteParams && ret.isSuccess()) {
|
||||
var fields = Reflect.copy(ret.sure());
|
||||
|
||||
for (field in fields)
|
||||
field.type = haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params);
|
||||
ret = Success([
|
||||
for (field in ret.sure()) ({
|
||||
name: field.name,
|
||||
type: haxe.macro.TypeTools.applyTypeParameters(field.type, c.params, params),
|
||||
pos: field.pos,
|
||||
meta: field.meta,
|
||||
doc: field.doc,
|
||||
expr: function () return field.expr(),
|
||||
isPublic: field.isPublic,
|
||||
params: field.params,
|
||||
kind: field.kind,
|
||||
overloads: field.overloads,
|
||||
#if haxe4
|
||||
isExtern: field.isExtern,
|
||||
isFinal: field.isFinal,
|
||||
#end
|
||||
}:ClassField)
|
||||
]);
|
||||
}
|
||||
#if !haxe4
|
||||
fieldsCache.remove(id);//TODO: find a proper solution to avoid stale cache
|
||||
#end
|
||||
ret;
|
||||
case TAnonymous(anon): Success(anon.get().fields);
|
||||
default: Context.currentPos().makeFailure('type $t has no fields');
|
||||
|
11
tests/Functions.hx
Normal file
11
tests/Functions.hx
Normal file
@@ -0,0 +1,11 @@
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
using tink.MacroApi;
|
||||
|
||||
class Functions extends Base {
|
||||
function test() {
|
||||
var f:Function = (macro function () {}).getFunction().sure();
|
||||
f.asExpr('foo');
|
||||
assertTrue(true);
|
||||
}
|
||||
}
|
10
tests/Misc.hx
Normal file
10
tests/Misc.hx
Normal file
@@ -0,0 +1,10 @@
|
||||
import tink.MacroApi;
|
||||
import haxe.unit.TestCase;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Misc extends TestCase {
|
||||
function testMain() {
|
||||
assertEquals('Run', MacroApi.getMainClass().force());
|
||||
}
|
||||
}
|
@@ -12,6 +12,8 @@ class Run {
|
||||
new Types(),
|
||||
new Positions(),
|
||||
new TypeMapTest(),
|
||||
new Functions(),
|
||||
new Misc(),
|
||||
];
|
||||
#end
|
||||
macro static function test() {
|
||||
|
Reference in New Issue
Block a user