├── .gitignore ├── .haxerc ├── .travis.yml ├── .vscode ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── extraParams.hxml ├── haxe_libraries ├── ctl.hxml ├── hxnodejs.hxml └── travix.hxml ├── haxelib.json ├── src └── cytotoxic │ ├── Data.hx │ └── TCell.hx ├── tests.hxml └── tests ├── Example.hx └── RunTests.hx /.gitignore: -------------------------------------------------------------------------------- 1 | /report.json 2 | /whatever.n 3 | /bin 4 | /*.n 5 | -------------------------------------------------------------------------------- /.haxerc: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.4.7", 3 | "resolveLibs": "scoped" 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: haxe 5 | 6 | os: 7 | - linux 8 | 9 | haxe: 10 | - "3.4.0" 11 | - development 12 | 13 | matrix: 14 | allow_failures: 15 | - haxe: development 16 | 17 | install: 18 | - haxelib install travix 19 | - haxelib run travix install 20 | 21 | script: 22 | - haxe tests.hxml 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "haxe.displayConfigurations": [ 3 | ["tests.hxml", "-lib", "travix", "-lib", "ctl", "--interp"] 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "haxe", 4 | "args": ["tests.hxml"], 5 | "problemMatcher": "$haxe" 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cytotoxic T Cells for your codebase * 2 | *(Well, it's really just a fancy dead code reporter to be honest) 3 | 4 | Usage: `-lib ctl` 5 | 6 | Adding this library to your build will generate DCE reports, i.e. which classes are entirely eliminated, and on partially eliminated classes, which fields are eliminated. 7 | 8 | Configuration: 9 | 10 | - `-D ctl-skip`: Skips reporting. Reporting is also skipped unless you use `-dce full`. 11 | - `-D ctl-warn`: Rather than producing a report, the lib will raise warnings at the relevant types/fields 12 | - `-D ctl-out=`: Redirect reporting to file. 13 | - `-D ctl-format=`: Outputs as either JSON string or haxe serialized string of type `cytotoxic.Data`: 14 | 15 | ```haxe 16 | package cytotoxic; 17 | 18 | typedef Item = { 19 | name:String, 20 | pos:String, 21 | } 22 | 23 | typedef Data = { 24 | deadTypes: Array, 25 | deadFields: Array<{ 26 | type: Item, 27 | fields: Array 28 | }>, 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /extraParams.hxml: -------------------------------------------------------------------------------- 1 | --macro cytotoxic.TCell.unleash() -------------------------------------------------------------------------------- /haxe_libraries/ctl.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | extraParams.hxml -------------------------------------------------------------------------------- /haxe_libraries/hxnodejs.hxml: -------------------------------------------------------------------------------- 1 | # @install: lix --silent download "haxelib:hxnodejs#4.0.9" into hxnodejs/4.0.9/haxelib 2 | -D hxnodejs=4.0.9 3 | -cp ${HAXESHIM_LIBCACHE}/hxnodejs/4.0.9/haxelib/src 4 | -D nodejs 5 | --macro allowPackage('sys') 6 | --macro _hxnodejs.VersionWarning.include() 7 | -------------------------------------------------------------------------------- /haxe_libraries/travix.hxml: -------------------------------------------------------------------------------- 1 | # @run: haxelib run-dir travix ${HAXESHIM_LIBCACHE}/travix/0.10.5/haxelib 2 | # @install: lix --silent download "haxelib:travix#0.10.5" into travix/0.10.5/haxelib 3 | -D travix=0.10.5 4 | -cp ${HAXESHIM_LIBCACHE}/travix/0.10.5/haxelib/src 5 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ctl", 3 | "classPath": "src", 4 | "dependencies": {}, 5 | "url": "https://github.com/back2dos/ctl/", 6 | "contributors": [ 7 | "back2dos" 8 | ], 9 | "version": "0.1.0", 10 | "releasenote": "Initial version.", 11 | "tags": [ 12 | "dce" 13 | ], 14 | "license": "MIT" 15 | } -------------------------------------------------------------------------------- /src/cytotoxic/Data.hx: -------------------------------------------------------------------------------- 1 | package cytotoxic; 2 | 3 | typedef Item = { 4 | name:String, 5 | pos:String, 6 | } 7 | 8 | typedef Data = { 9 | deadTypes: Array, 10 | deadFields: Array<{ 11 | type: Item, 12 | fields: Array 13 | }>, 14 | } -------------------------------------------------------------------------------- /src/cytotoxic/TCell.hx: -------------------------------------------------------------------------------- 1 | package cytotoxic; 2 | 3 | import haxe.macro.*; 4 | import haxe.macro.Expr; 5 | import haxe.macro.Type; 6 | using haxe.macro.Context; 7 | using StringTools; 8 | 9 | class TCell { 10 | static function fieldsOf(cl:ClassType) { 11 | var ret = new Map(); 12 | for (f in cl.statics.get().concat(cl.fields.get())) 13 | switch f.kind { 14 | case FMethod(MethInline | MethMacro): 15 | case FMethod(_): ret[f.name] = f; 16 | default: 17 | } 18 | return ret; 19 | } 20 | 21 | static function name(b:BaseType) 22 | return b.pack.concat([b.name]).join('.'); 23 | 24 | static function unleash() { 25 | #if (dce == "full") 26 | if (!Context.defined('ctl-skip')) Context.onGenerate(function (types) { 27 | var cwd = Sys.getCwd(); 28 | 29 | function inCwd(b:BaseType) 30 | return !haxe.io.Path.isAbsolute(b.pos.getPosInfos().file); 31 | 32 | var oldFields = new Map(), 33 | generics = new Map(); 34 | 35 | for (t in types) switch t { 36 | 37 | case TInst(_.get() => cl = { isExtern: false }, _) if (inCwd(cl)): 38 | 39 | switch cl.kind { 40 | case KGenericInstance(_.get() => cl, _): 41 | generics[name(cl)] = true; 42 | default: 43 | } 44 | 45 | switch fieldsOf(cl) { 46 | case _.iterator().hasNext() => false: 47 | case v: oldFields[name(cl)] = v; 48 | } 49 | 50 | default: 51 | } 52 | 53 | Context.onAfterGenerate(function () { 54 | 55 | var deadTypes = [], 56 | deadFields = []; 57 | 58 | function isDead(t:BaseType, ?owner:BaseType) 59 | return 60 | if (owner == null) isDead(t, t); 61 | else if (oldFields.exists(name(t)) && !t.meta.has(':used')) 62 | deadTypes.push(owner) > 0; 63 | else false; 64 | 65 | function classFields(cl:ClassType, ?owner:BaseType) { 66 | if (owner == null) owner = cl; 67 | 68 | switch oldFields[name(cl)] { 69 | case null: 70 | case old: 71 | var nu = fieldsOf(cl); 72 | switch [for (f in old) if (!nu.exists(f.name)) f] { 73 | case []: 74 | case dead: 75 | deadFields.push({ 76 | type: owner, 77 | fields: dead, 78 | }); 79 | } 80 | } 81 | } 82 | 83 | for (t in types) switch t { 84 | 85 | case TInst(_.get() => cl = { isExtern: false, kind: KGeneric }, _) if (inCwd(cl))://TODO: deal with generics 86 | 87 | if (!generics[name(cl)]) deadTypes.push(cl); 88 | 89 | case TInst(ref = _.get() => cl = { isExtern: false, kind: KNormal | KGenericInstance(_, _) }, _) if (inCwd(cl)): 90 | 91 | if (!isDead(cl)) classFields(cl); 92 | 93 | case TInst(_.get() => cl = { isExtern: false, kind: KAbstractImpl(_.get() => a) }, _) if (inCwd(cl)): 94 | 95 | if (!isDead(cl, a)) classFields(cl, a); 96 | 97 | default: 98 | } 99 | 100 | if (Context.defined('ctl-warn')) { 101 | for (t in deadTypes) 102 | 'Unused type'.warning(t.pos); 103 | for (f in deadFields) 104 | for (f in f.fields) 105 | 'Unused field'.warning(f.pos); 106 | return; 107 | } 108 | 109 | var output = switch Context.definedValue('ctl-out') { 110 | case null: Sys.print; 111 | case v: sys.io.File.saveContent.bind(v); 112 | } 113 | 114 | function render(renderer:Data->String) { 115 | function pos(p:Position) { 116 | var s = Std.string(p); 117 | return s.substring(5, s.length - 1); 118 | } 119 | 120 | function type(t) 121 | return { name: name(t), pos: pos(t.pos) }; 122 | 123 | output(renderer({ 124 | deadTypes: [for (t in deadTypes) type(t)], 125 | deadFields: [for (f in deadFields) { 126 | type: type(f.type), 127 | fields: [for (f in f.fields) { 128 | name: f.name, 129 | pos: pos(f.pos), 130 | }] 131 | }], 132 | })); 133 | } 134 | 135 | deadTypes.sort(function (a, b) return Reflect.compare(name(a), name(b))); 136 | deadFields.sort(function (a, b) return Reflect.compare(name(a.type), name(b.type))); 137 | 138 | switch Context.definedValue('ctl-format') { 139 | case null: 140 | var indent = ' '; 141 | var lines = []; 142 | function out(s:String) 143 | lines.push(s); 144 | 145 | function pos(o) { 146 | var ret = Std.string(o.pos).substr(5); 147 | return ret.substring(0, ret.lastIndexOf(':')); 148 | } 149 | 150 | function type(t:BaseType) 151 | return '$indent${name(t)} @ ${pos(t)}'; 152 | if (deadTypes.length > 0) 153 | out('Dead Types:\n\n'+[for (t in deadTypes) type(t)].join('\n')); 154 | if (deadFields.length > 0) { 155 | out('\nPartially Dead Types:'); 156 | for (d in deadFields) { 157 | out('\n'+type(d.type)+'\n'); 158 | for (f in d.fields) 159 | out('$indent$indent${f.name} @ ${pos(f)}'); 160 | } 161 | } 162 | output(lines.join('\n') + '\n'); 163 | case 'json': 164 | render(haxe.Json.stringify.bind(_, null, ' ')); 165 | case 'hx': 166 | render(haxe.Serializer.run); 167 | case v: 'Unsupported format $v'.fatalError(Context.currentPos()); 168 | } 169 | 170 | }); 171 | 172 | 173 | }); 174 | #end 175 | } 176 | } -------------------------------------------------------------------------------- /tests.hxml: -------------------------------------------------------------------------------- 1 | -cp tests 2 | -cp src 3 | --run RunTests -------------------------------------------------------------------------------- /tests/Example.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | 3 | class Example { 4 | static function main() { 5 | new Alive().alive(); 6 | new AliveGeneric().alive(); 7 | new AliveGeneric().dead(); 8 | new AliveAbstract().alive(); 9 | } 10 | } 11 | 12 | class Alive { 13 | public function new() {} 14 | public function alive() trace("alive"); 15 | public function dead() trace("dead"); 16 | } 17 | 18 | class Dead { 19 | public function new() {} 20 | public function alive() trace("alive"); 21 | public function dead() trace("dead"); 22 | } 23 | 24 | abstract DeadAbstract(String) { 25 | public function alive() trace("alive"); 26 | public function dead() trace("dead"); 27 | } 28 | 29 | abstract AliveAbstract(String) { 30 | public function new() this = "alive"; 31 | public function alive() trace("alive"); 32 | public function dead() trace("dead"); 33 | 34 | } 35 | 36 | 37 | @:generic class DeadGeneric { 38 | public function new() {} 39 | public function alive() trace("alive"); 40 | public function dead() trace("dead"); 41 | } 42 | 43 | @:generic class AliveGeneric { 44 | public function new() {} 45 | public function alive() trace("alive"); 46 | public function dead() trace("dead"); 47 | } 48 | -------------------------------------------------------------------------------- /tests/RunTests.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | 3 | class RunTests { 4 | static function main() { 5 | var report = 'report.json'; 6 | 7 | switch Sys.command('haxe -dce full -cp tests -lib ctl -main Example -neko whatever.n -D ctl-format=json -D ctl-out=$report') { 8 | case 0: 9 | var data:cytotoxic.Data = haxe.Json.parse(sys.io.File.getContent(report)); 10 | switch data { 11 | case { 12 | deadTypes: [ 13 | { name: 'Dead' }, 14 | { name: 'DeadAbstract' }, 15 | { name: 'DeadGeneric' } 16 | ], 17 | deadFields: [ 18 | { type: { name: 'Alive' }, fields: [{ name: 'dead' }] }, 19 | { type: { name: 'AliveAbstract' }, fields: [{ name: 'dead' }] }, 20 | { type: { name: 'AliveGeneric_Int' }, fields: [{ name: 'dead' }] }, 21 | { type: { name: 'AliveGeneric_String' }, fields: [{ name: 'alive' }] }, 22 | ] 23 | }: 24 | Sys.println('All good!'); 25 | default: 26 | Sys.println('Report did not match expectations'); 27 | Sys.exit(500); 28 | } 29 | case v: Sys.exit(v); 30 | } 31 | } 32 | 33 | } 34 | --------------------------------------------------------------------------------