Skip to content

Commit

Permalink
Improve support for abstracts (#305)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebthom authored Feb 27, 2023
1 parent 7df1c91 commit 1f18aff
Show file tree
Hide file tree
Showing 11 changed files with 401 additions and 38 deletions.
21 changes: 12 additions & 9 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ jobs:

- run: npm install

- name: Generate XMLs
- name: Run unit tests
run: npx haxe tests.hxml

- name: Test generation of XML files
run: npx haxe xml.hxml

- name: Test [eval]
- name: Test cli [eval]
run: npx haxe -D eval-stack runBase.hxml --run dox.Dox --help

- name: Test [neko]
- name: Test cli [neko]
run: |
set -eux
npx haxe runBase.hxml -neko run.n
Expand All @@ -59,7 +62,7 @@ jobs:
with:
python-version: 3.11

- name: Test [python]
- name: Test cli [python]
run: |
set -eux
npx haxe runBase.hxml -python bin/dox.py
Expand All @@ -73,7 +76,7 @@ jobs:
php-version: 7.4
extensions: mbstring, xml

- name: Test [php]
- name: Test cli [php]
run: |
set -eux
npx haxe runBase.hxml -php bin/dox
Expand All @@ -85,19 +88,19 @@ jobs:
distribution: 'temurin'
java-version: 11

- name: Test [java]
- name: Test cli [java]
run: |
set -eux
npx haxe runBase.hxml -java bin/java
java -jar bin/java/Dox.jar
- name: Test [jvm]
- name: Test cli [jvm]
run: |
set -eux
npx haxe runBase.hxml -java bin/jvm
java -jar bin/jvm/Dox.jar
- name: Test [node]
- name: Test cli [node]
run: |
set -eux
npx haxe runBase.hxml -lib hxnodejs -js bin/dox.js
Expand All @@ -118,7 +121,7 @@ jobs:
popd
npx lix dev hxcpp $(npx haxelib config)/hxcpp/git
- name: Test [cpp]
- name: Test cli [cpp]
run: |
set -eux
npx haxe runBase.hxml -cpp bin/cpp -D HXCPP_SILENT
Expand Down
5 changes: 5 additions & 0 deletions haxe_libraries/hscript.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @install: lix --silent download "haxelib:/hscript#2.5.0" into hscript/2.5.0/haxelib
# @run: haxelib run-dir hscript "${HAXE_LIBCACHE}/hscript/2.5.0/haxelib"
-cp ${HAXE_LIBCACHE}/hscript/2.5.0/haxelib/
-D hscript=2.5.0
--macro keep('IntIterator')
5 changes: 5 additions & 0 deletions haxe_libraries/utest.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# @install: lix --silent download "haxelib:/utest#1.13.2" into utest/1.13.2/haxelib
-cp ${HAXE_LIBCACHE}/utest/1.13.2/haxelib/src
-D utest=1.13.2
--macro utest.utils.Macro.checkHaxe()
--macro utest.utils.Macro.importEnvSettings()
205 changes: 180 additions & 25 deletions src/dox/Processor.hx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dox;
import haxe.Serializer;
import haxe.rtti.CType;

@:allow(dox.test)
class Processor {
public var infos:Infos;

Expand Down Expand Up @@ -51,25 +52,25 @@ class Processor {
throw 'Could not find toplevel package ${config.toplevelPackage}';
}
}
function filter(root, tree):Void {
function filter(parent:Array<TypeTree>, tree:TypeTree):Void {
return switch (tree) {
case TPackage(name, full, subs):
var acc = [];
subs.iter(filter.bind(acc));
if (acc.length > 0 && !isPathFiltered(full)) {
root.push(TPackage(name, full, acc));
parent.push(TPackage(name, full, acc));
}
case TClassdecl(t):
t.fields = filterFields(t.fields);
t.statics = filterFields(t.statics);
if (!isTypeFiltered(t)) {
root.push(tree);
parent.push(tree);
infos.addType(t.path, t);
}
case TEnumdecl(t):
if (!isTypeFiltered(t)) {
t.constructors = filterEnumFields(t.constructors);
root.push(tree);
parent.push(tree);
infos.addType(t.path, t);
}
case TTypedecl(t):
Expand All @@ -79,33 +80,15 @@ class Processor {
t.type = CAnonymous(filterFields(fields));
default:
}
root.push(tree);
parent.push(tree);
infos.addType(t.path, t);
}
case TAbstractdecl(t):
if (t.impl != null) {
var fields = new Array<ClassField>();
var statics = new Array<ClassField>();
t.impl.statics.iter(function(cf) {
if (hasMeta(cf.meta, ":impl")) {
if (cf.name == "_new")
cf.name = "new";
else
switch (cf.type) {
case CFunction(args, _):
args.shift();
case _:
}
fields.push(cf);
} else {
statics.push(cf);
}
});
t.impl.fields = filterFields(fields);
t.impl.statics = filterFields(statics);
populateFieldsOfAbstract(t, root);
}
if (!isTypeFiltered(t)) {
root.push(tree);
parent.push(tree);
infos.addType(t.path, t);
}
}
Expand All @@ -114,6 +97,143 @@ class Processor {
return newRoot;
}

function populateFieldsOfAbstract(theAbstract:Abstractdef, root:TypeRoot) {
if (hasDoxMetadata(theAbstract.impl.meta, "is-populated")) {
return; // nothing to do
}

var statics = new Array<ClassField>();
var fields = new Array<ClassField>();

// collect direct members
for (cf in theAbstract.impl.statics) {
switch (cf.type) {
// handling functions
case CFunction(args, _):
if (cf.name == "_new") { // constructor
cf.name = "new";
// the Haxe compiler automatically adds a ":noCompletion"
// so we remove the first auto-generated occurrence
var noCompletionMeta = cf.meta.find(m -> m.name == ":noCompletion");
if (noCompletionMeta != null) cf.meta.remove(noCompletionMeta);
fields.push(cf);
} else if (args.length == 0 || args[0].name != "this") {
statics.push(cf);
} else
fields.push(cf);
// handling variables (declared with get and/or set accessor)
case CAbstract(name, params):
var isStatic = true;
switch(cf.get) {
case RCall("accessor"):
final accessor = theAbstract.impl.statics.find(f -> f.name == "get_" + cf.name);
if (accessor != null) {
switch (accessor.type) {
case CFunction(args, _):
if (args.length > 0 && args[0].name == "this")
isStatic = false;
case _:
}
}
case _:
switch(cf.set) {
case RCall("accessor"):
final accessor = theAbstract.impl.statics.find(f -> f.name == "set_" + cf.name);
if (accessor != null) {
switch (accessor.type) {
case CFunction(args, _):
if (args.length > 0 && args[0].name == "this")
isStatic = false;
case _:
}
}
case _:
}
}
if (isStatic)
statics.push(cf);
else {
fields.push(cf);
}
case _:
statics.push(cf);
}
}

// collect forwarded static fields
final forwardStaticsMeta = findMeta(theAbstract.meta, ":forwardStatics");
if (forwardStaticsMeta != null) {
switch (theAbstract.athis) {
case CClass(name, params):
switch (findInTrees(name, root)) {
case TClassdecl(realType):
if (forwardStaticsMeta.params == null || forwardStaticsMeta.params.length == 0) {
statics = statics.concat(realType.statics);
} else {
for (classStatic in realType.statics) {
if (forwardStaticsMeta.params.contains(classStatic.name))
statics.push(classStatic);
}
}
case _:
}
case CAbstract(name, _):
switch (findInTrees(name, root)) {
case TAbstractdecl(realType):
populateFieldsOfAbstract(realType, root);
if (forwardStaticsMeta.params == null || forwardStaticsMeta.params.length == 0) {
statics = statics.concat(realType.impl.statics);
} else {
for (classStatic in realType.impl.statics) {
if (forwardStaticsMeta.params.contains(classStatic.name))
statics.push(classStatic);
}
}
case _:
}
case _:
}
}

// collect forwarded instance fields
final forwardMeta = findMeta(theAbstract.meta, ":forward");
if (forwardMeta != null) {
switch (theAbstract.athis) {
case CClass(name, _):
switch (findInTrees(name, root)) {
case TClassdecl(realType):
if (forwardMeta.params == null || forwardMeta.params.length == 0) {
fields = fields.concat(realType.fields);
} else {
for (classField in realType.fields) {
if (forwardMeta.params.contains(classField.name))
fields.push(classField);
}
}
case _:
}
case CAbstract(name, _):
switch (findInTrees(name, root)) {
case TAbstractdecl(realType):
populateFieldsOfAbstract(realType, root);
if (forwardMeta.params == null || forwardMeta.params.length == 0) {
fields = fields.concat(realType.impl.fields);
} else {
for (classField in realType.impl.fields) {
if (forwardMeta.params.contains(classField.name))
fields.push(classField);
}
}
case _:
}
case _:
}
}
theAbstract.impl.statics = filterFields(statics);
theAbstract.impl.fields = filterFields(fields);
setMetaParam(theAbstract.impl.meta, ":dox", "is-populated");
}

function filterFields(fields:Array<ClassField>) {
return fields.filter(function(cf) {
if (cf.overloads != null) {
Expand All @@ -129,6 +249,29 @@ class Processor {
return fields.filter(ef -> !hasHideMetadata(ef.meta) || hasShowMetadata(ef.meta));
}

/** Searches for a TClassdecl or TAbstractdecl in the given trees */
static function findInTrees(path:String, trees:Array<TypeTree>):Null<TypeTree> {
for (tree in trees) {
final result = findInTree(path, tree);
if (result != null) return result;
}
return null;
}

/** Searches for a TClassdecl or TAbstractdecl in the given tree */
static function findInTree(path:String, tree:TypeTree):Null<TypeTree> {
switch (tree) {
case TPackage(_, full, subs):
return findInTrees(path, subs);
case TClassdecl(t):
if (t.path == path) return tree;
case TAbstractdecl(t):
if (t.path == path) return tree;
case _: return null;
}
return null;
}

function sort(root:TypeRoot) {
function getName(t:TypeTree) {
return switch (t) {
Expand Down Expand Up @@ -355,6 +498,10 @@ class Processor {
return hasInclusionFilter;
}

function findMeta(meta:MetaData, name:String):Null<{name:String, params:Array<String>}> {
return meta.find(meta -> meta.name == name);
}

function hasMeta(meta:MetaData, name:String) {
return meta.exists(meta -> meta.name == name);
}
Expand All @@ -370,4 +517,12 @@ class Processor {
function hasHideMetadata(meta:MetaData):Bool {
return hasDoxMetadata(meta, "hide") || hasMeta(meta, ":compilerGenerated") || hasMeta(meta, ":noCompletion");
}

function setMetaParam(meta:MetaData, name:String, param:String) {
var doxMeta = findMeta(meta, name);
if (doxMeta == null)
meta.push({name: name, params: [param]});
else if (!doxMeta.params.contains(param))
doxMeta.params.push(param);
}
}
4 changes: 2 additions & 2 deletions test/dox/DoxTest.hx → test/dox/sample/DoxTest.hx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dox;
package dox.sample;

/**
* <p>A node in the entity hierarchy, and a collection of components.</p>
Expand Down Expand Up @@ -399,7 +399,7 @@ typedef PlatformConditionalized = #if cpp {
} #elseif cs(a:String, b:Int) -> Void #elseif neko Array<Int> #else {} #end;

/**
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in reprehenderit
**/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dox.emptyPackage;
package dox.sample.emptyPackage;

@:dox(hide)
class HiddenClass {}
14 changes: 14 additions & 0 deletions test/dox/test/TestRunner.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dox.test;

import dox.test.processor.ProcessorTest;
import utest.ui.Report;
import utest.Runner;

class TestRunner {
public static function main():Void {
var runner = new Runner();
runner.addCase(new ProcessorTest());
Report.create(runner);
runner.run();
}
}
Loading

0 comments on commit 1f18aff

Please sign in to comment.