decembrist-revolt / decembrist-kotlin2js-reflection

Plugin for kotlin js annotation processing
21 stars 3 forks source link

Wondering if may be used for mocking library #1

Open oleksiyp opened 5 years ago

oleksiyp commented 5 years ago

Last time I was trying to port MockK to Kotlin JS, the problem was in Kotlin JS reflection. Just've trapped on a post in discuss about your plugin and thinking if I can give a second try to this direction.

Just a few questions (I didn't want to explore it myself, because hope will have correct and up-to-date answers through communication rather than code):

decembrist-revolt commented 5 years ago

Last time I was trying to port MockK to Kotlin JS, the problem was in Kotlin JS reflection. Just've trapped on a post in discuss about your plugin and thinking if I can give a second try to this direction.

Just a few questions (I didn't want to explore it myself, because hope will have correct and up-to-date answers through communication rather than code):

Hello!

  • how mature is a plugin? production ready enough?
  • It was just a first try to reach an annotation availability, I was not sure that somebody except me needed this feature that was reached this way
  • who supports it?
  • Only me, you can find my contact info in github profile
  • are there any known issues?
  • You gave the first feedback that someone interested in this, thus answer is No
  • does it play well with multiplatform projects?
  • Answer can be yes, I'm working on checking that possibility and readme to explain how
  • does it need to be applied to all modules separately or just finally at the bundling stage?
  • You can't use annotations from one library inside others right now, as I mentioned in readme (Cross-project annotations will be added in upcoming patches)
  • how big is additional space needed?
  • This plugin creates one addition .kt file for each package that contains annotations
  • is it using Kotlin reflection API or separate API?
  • This plugin parses raw kotlin code through antlr
  • changing Kotlin metadata?
  • Maybe I misunderstood somthing but I think No
  • does it support Kotlin standard library classes?
  • I'm not sure what you mean but right now plugin supports only datatypes that were mentioned in readme
  • fallback for native Javascript?
  • Plugin overrides few kotlin.js functions look at KotlinPatch.kt After antlr recognotion. plugin creates .kt file per package with annotation data, that should be compiled with sources
oleksiyp commented 5 years ago

Okay, I see... such approach then is limited to your own classes then, without the possibility to get metadata for classes that you linked as libraries, is it?

decembrist-revolt commented 5 years ago

Right now it's true. But it is not so difficult to make it possible. I'm going to add this feature.

oleksiyp commented 5 years ago

And then I need function parameter types, list of functions in a class.

If that is possible I can go back to JS implementation and tell what is needed else.

decembrist-revolt commented 5 years ago

You already can get all class methods. Look at JsClassReflect interface in the readme. Or maybe you mean something else?

oleksiyp commented 5 years ago

And the method parameter types?

decembrist-revolt commented 5 years ago

I'll add this also

oleksiyp commented 5 years ago

Ok, if all that possible I think we can give it a try. I will recover JS branch. It will not happen very fast, one or two weeks. And then I'll come back with more detailed requirements on what is needed

oleksiyp commented 5 years ago

Actually recovered branch :smile: Just didn't know how outdated is it.

Here are two main points where meta information is needed:

In JS everything is potentially callable and you can get function reference at one place and call it a totally different place. That's why both things get intercepted: getting function and applying that function.

Internal metadata format is following: image

Hopefully all this is easy to retrive with your anotation processor.

Besides this information about method call, is there an ability to retrieve mangled name somewhere? image

decembrist-revolt commented 5 years ago

If I understand correctly, do you need to fill in all the MethodDescription fields to achieve your goal?

On the right is the result js file You should override the function 1) kotlin.js (getCallableRef) to get the function 2) as an object after you should do toString for the function object 2) and regexp the name of the function 3)

I did something like this for JsFunctionReflect.jsName

oleksiyp commented 5 years ago

Yes, all these fields are from Java/Kotlin Reflection in JVM version.

I see. Unmangling sounds like a hack... as all mocking library itself :smiley: But the question was if it is possible to do on the level of annotation processing. Is it?

decembrist-revolt commented 5 years ago

I need to figure it out, but I think yes

oleksiyp commented 5 years ago

Tried to run it as Gradle plugin and have following error:

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.decembrist.parser.KotlinLexer
    at org.decembrist.parsers.KtFileParser.parse(KtFileParser.kt:17)
    at org.decembrist.parsers.SourceParser.parse(SourceParser.kt:27)
    at org.decembrist.tasks.ProcessReflectionTask.run(ProcessReflectionTask.kt:27)
    at org.decembrist.Kotlin2jsReflectionPlugin.executeReflectionProcessing(Kotlin2jsReflectionPlugin.kt:50)
    at org.decembrist.Kotlin2jsReflectionPlugin.access$executeReflectionProcessing(Kotlin2jsReflectionPlugin.kt:16)
    at org.decembrist.Kotlin2jsReflectionPlugin$setUpReflectionTask$1.invoke(Kotlin2jsReflectionPlugin.kt:57)
    at org.decembrist.Kotlin2jsReflectionPlugin$setUpReflectionTask$1.invoke(Kotlin2jsReflectionPlugin.kt:16)
    at org.decembrist.Kotlin2jsReflectionPlugin$sam$org_gradle_api_Action$0.execute(Kotlin2jsReflectionPlugin.kt)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:801)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:768)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:131)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:300)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:292)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:174)
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
    at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:120)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:99)
decembrist-revolt commented 5 years ago

I think I found the source of the problem. I will try to fix it

oleksiyp commented 5 years ago

I tried to build it on my own. See #2

The current problem(after resolving the issue with ANTLR4 clash and shadowing it) is I believe the syntax level of Kotlin parser. It is not able to understand the actual keyword.

1.2.71 does not equal recommended Kotlin2JsPlugin version
recommended version is 1.2.51
line 11:0 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 14:4 no viable alternative at input 'JsCounter()\n\nactual'
line 14:4 extraneous input 'actual' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 16:4 no viable alternative at input 'timeCounter.next()\n\nactual'
line 16:4 extraneous input 'actual' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 18:4 no viable alternative at input 'CommonRef(obj)\n\nactual'
line 18:4 extraneous input 'actual' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 20:4 no viable alternative at input 'JsHexLongHelper.toHexString(InternalPlatformDsl.identityHashCode(obj).toLong())\n\nactual'
line 20:4 extraneous input 'actual' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 35:4 extraneous input 'actual' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 35:32 mismatched input '<' expecting {NL, '('}
line 37:8 no viable alternative at input 'get(key)\nreturn'
line 37:8 extraneous input 'return' expecting {NL, '}', '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'constructor', 'companion', 'init', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 38:12 extraneous input 'val' expecting {NL, '}'}
line 39:12 no viable alternative at input 'valueFunc(key)\nput'
line 39:12 extraneous input 'put' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 53:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 55:4 no viable alternative at input 'ex\n\nactual'
line 55:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 57:4 no viable alternative at input 'CommonIdentityHashMapOf()\n\nactual'
line 57:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 59:4 no viable alternative at input 'CommonIdentityHashMapOf()\n\nactual'
line 59:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 61:4 no viable alternative at input 'mutableListOf()\n\nactual'
line 61:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 64:4 no viable alternative at input 'hashMapOf()\n\n@Suppress("NAME_SHADOWING","UNUSED_VARIABLE")\nactual'
line 64:4 no viable alternative at input '@Suppress("NAME_SHADOWING","UNUSED_VARIABLE")\nactual'
line 71:4 extraneous input 'actual' expecting {<EOF>, '@', '@file', 'class', 'interface', 'fun', 'object', 'val', 'var', 'typealias', 'in', 'out', '@field', '@property', '@get', '@set', '@receiver', '@param', '@setparam', '@delegate', 'public', 'private', 'protected', 'internal', 'enum', 'sealed', 'annotation', 'data', 'inner', 'tailrec', 'operator', 'inline', 'infix', 'external', 'suspend', 'override', 'abstract', 'final', 'open', 'const', 'lateinit', 'vararg', 'noinline', 'crossinline', 'reified', LabelReference}
line 73:4 no viable alternative at input '{listOf<StackElement>()}\n\nactual'
oleksiyp commented 5 years ago

Then I upgraded to newest Kotlin grammar and was finally able to generate some reflection metadata(as Kotlin code) :tada: