Support new inline function format in JS DCE tool
This commit is contained in:
@@ -31,4 +31,6 @@ interface AnalysisResult {
|
|||||||
val functionsToEnter: Set<JsFunction>
|
val functionsToEnter: Set<JsFunction>
|
||||||
|
|
||||||
val invocationsToSkip: Set<JsInvocation>
|
val invocationsToSkip: Set<JsInvocation>
|
||||||
|
|
||||||
|
val functionsToSkip: Set<Context.Node>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.dce
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.dce.Context.Node
|
import org.jetbrains.kotlin.js.dce.Context.Node
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.collectDefinedNames
|
||||||
import org.jetbrains.kotlin.js.inline.util.collectLocalVariables
|
import org.jetbrains.kotlin.js.inline.util.collectLocalVariables
|
||||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
private val invocationsToSkip = mutableSetOf<JsInvocation>()
|
private val invocationsToSkip = mutableSetOf<JsInvocation>()
|
||||||
val moduleMapping = mutableMapOf<JsStatement, String>()
|
val moduleMapping = mutableMapOf<JsStatement, String>()
|
||||||
private val functionsToEnter = mutableSetOf<JsFunction>()
|
private val functionsToEnter = mutableSetOf<JsFunction>()
|
||||||
|
private val functionsToSkip = mutableSetOf<Context.Node>()
|
||||||
|
|
||||||
val analysisResult = object : AnalysisResult {
|
val analysisResult = object : AnalysisResult {
|
||||||
override val nodeMap: Map<JsNode, Node> get() = this@Analyzer.nodeMap
|
override val nodeMap: Map<JsNode, Node> get() = this@Analyzer.nodeMap
|
||||||
@@ -41,6 +43,8 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
override val functionsToEnter: Set<JsFunction> get() = this@Analyzer.functionsToEnter
|
override val functionsToEnter: Set<JsFunction> get() = this@Analyzer.functionsToEnter
|
||||||
|
|
||||||
override val invocationsToSkip: Set<JsInvocation> get() = this@Analyzer.invocationsToSkip
|
override val invocationsToSkip: Set<JsInvocation> get() = this@Analyzer.invocationsToSkip
|
||||||
|
|
||||||
|
override val functionsToSkip: Set<Context.Node> get() = this@Analyzer.functionsToSkip
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitVars(x: JsVars) {
|
override fun visitVars(x: JsVars) {
|
||||||
@@ -87,13 +91,13 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object.defineProperty()
|
|
||||||
when {
|
when {
|
||||||
|
// Object.defineProperty()
|
||||||
context.isObjectDefineProperty(function) ->
|
context.isObjectDefineProperty(function) ->
|
||||||
handleObjectDefineProperty(x, expression.arguments.getOrNull(0), expression.arguments.getOrNull(1),
|
handleObjectDefineProperty(x, expression.arguments.getOrNull(0), expression.arguments.getOrNull(1),
|
||||||
expression.arguments.getOrNull(2))
|
expression.arguments.getOrNull(2))
|
||||||
|
|
||||||
// Kotlin.defineModule()
|
// Kotlin.defineModule()
|
||||||
context.isDefineModule(function) ->
|
context.isDefineModule(function) ->
|
||||||
// (just remove it)
|
// (just remove it)
|
||||||
astNodesToEliminate += x
|
astNodesToEliminate += x
|
||||||
@@ -227,13 +231,25 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// lhs = Kotlin.defineInlineFunction('fqn', function() { ... })
|
// lhs = Kotlin.defineInlineFunction('fqn', <function declaration>)
|
||||||
|
// where <function declaration> is one of
|
||||||
|
// - function() { ... }
|
||||||
|
// - wrapFunction(function() { ... })
|
||||||
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
|
if (context.isDefineInlineFunction(function) && rhs.arguments.size == 2) {
|
||||||
leftNode.functions += rhs.arguments[1] as JsFunction
|
tryExtractFunction(rhs.arguments[1])?.let { (inlineableFunction, additionalDeps) ->
|
||||||
val defineInlineFunctionNode = context.extractNode(function)
|
leftNode.functions += inlineableFunction
|
||||||
if (defineInlineFunctionNode != null) {
|
val defineInlineFunctionNode = context.extractNode(function)
|
||||||
leftNode.dependencies += defineInlineFunctionNode
|
if (defineInlineFunctionNode != null) {
|
||||||
|
leftNode.dependencies += defineInlineFunctionNode
|
||||||
|
}
|
||||||
|
leftNode.dependencies += additionalDeps
|
||||||
|
return leftNode
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryExtractFunction(rhs)?.let { (functionBody, additionalDeps) ->
|
||||||
|
leftNode.functions += functionBody
|
||||||
|
leftNode.dependencies += additionalDeps
|
||||||
return leftNode
|
return leftNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +263,7 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
val reassignValue = reassignment.arg2
|
val reassignValue = reassignment.arg2
|
||||||
if (reassignNode == secondNode && reassignNode != null && reassignValue is JsObjectLiteral &&
|
if (reassignNode == secondNode && reassignNode != null && reassignValue is JsObjectLiteral &&
|
||||||
reassignValue.propertyInitializers.isEmpty()
|
reassignValue.propertyInitializers.isEmpty()
|
||||||
) {
|
) {
|
||||||
return processAssignment(node, lhs, rhs.arg1)
|
return processAssignment(node, lhs, rhs.arg1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,6 +294,31 @@ class Analyzer(private val context: Context) : JsVisitor() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryExtractFunction(expression: JsExpression): Pair<JsFunction, List<Context.Node>>? {
|
||||||
|
when (expression) {
|
||||||
|
is JsFunction -> return Pair(expression, emptyList())
|
||||||
|
is JsInvocation -> {
|
||||||
|
if (context.isWrapFunction(expression.qualifier)) {
|
||||||
|
(expression.arguments.getOrNull(0) as? JsFunction)?.let { wrapper ->
|
||||||
|
val statementsWithoutBody = wrapper.body.statements.filter { it !is JsReturn }
|
||||||
|
JsBlock(statementsWithoutBody).let {
|
||||||
|
context.addNodesForLocalVars(collectDefinedNames(it))
|
||||||
|
accept(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val wrapperNode = context.extractNode(expression.qualifier)?.also {
|
||||||
|
functionsToSkip += it
|
||||||
|
}
|
||||||
|
val body = wrapper.body.statements.filterIsInstance<JsReturn>().first().expression as JsFunction
|
||||||
|
return Pair(body, listOfNotNull(wrapperNode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleObjectCreate(target: Node, arg: JsExpression?) {
|
private fun handleObjectCreate(target: Node, arg: JsExpression?) {
|
||||||
if (arg == null) return
|
if (arg == null) return
|
||||||
|
|
||||||
|
|||||||
@@ -135,15 +135,18 @@ class ReachabilityTracker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (expr in node.functions) {
|
if (node !in analysisResult.functionsToSkip) {
|
||||||
reportAndNest("traverse: function", expr) {
|
for (expr in node.functions) {
|
||||||
expr.collectLocalVariables().let {
|
reportAndNest("traverse: function", expr) {
|
||||||
context.addNodesForLocalVars(it)
|
expr.collectLocalVariables().let {
|
||||||
context.namesOfLocalVars += it
|
context.addNodesForLocalVars(it)
|
||||||
|
context.namesOfLocalVars += it
|
||||||
|
}
|
||||||
|
withErasedThis { expr.body.accept(this) }
|
||||||
}
|
}
|
||||||
withErasedThis { expr.body.accept(this) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (expr in node.expressions) {
|
for (expr in node.expressions) {
|
||||||
reportAndNest("traverse: value", expr) {
|
reportAndNest("traverse: value", expr) {
|
||||||
expr.accept(this)
|
expr.accept(this)
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ fun Context.isDefineModule(function: JsExpression): Boolean = isKotlinFunction(f
|
|||||||
|
|
||||||
fun Context.isDefineInlineFunction(function: JsExpression): Boolean = isKotlinFunction(function, "defineInlineFunction")
|
fun Context.isDefineInlineFunction(function: JsExpression): Boolean = isKotlinFunction(function, "defineInlineFunction")
|
||||||
|
|
||||||
|
fun Context.isWrapFunction(function: JsExpression): Boolean = isKotlinFunction(function, "wrapFunction")
|
||||||
|
|
||||||
fun Context.isObjectFunction(function: JsExpression, functionName: String): Boolean {
|
fun Context.isObjectFunction(function: JsExpression, functionName: String): Boolean {
|
||||||
if (function !is JsNameRef) return false
|
if (function !is JsNameRef) return false
|
||||||
if (function.ident != functionName) return false
|
if (function.ident != functionName) return false
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ import org.jetbrains.kotlin.js.dce.DeadCodeElimination
|
|||||||
import org.jetbrains.kotlin.js.dce.InputFile
|
import org.jetbrains.kotlin.js.dce.InputFile
|
||||||
import org.jetbrains.kotlin.js.facade.*
|
import org.jetbrains.kotlin.js.facade.*
|
||||||
import org.jetbrains.kotlin.js.parser.parse
|
import org.jetbrains.kotlin.js.parser.parse
|
||||||
import org.jetbrains.kotlin.js.parser.sourcemaps.*
|
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapError
|
||||||
|
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapLocationRemapper
|
||||||
|
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapParser
|
||||||
|
import org.jetbrains.kotlin.js.parser.sourcemaps.SourceMapSuccess
|
||||||
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
||||||
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
||||||
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
|
import org.jetbrains.kotlin.incremental.js.TranslationResultValue
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 997
|
||||||
var global = ""
|
var global = ""
|
||||||
|
|
||||||
fun log(message: String) {
|
fun log(message: String) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 996
|
||||||
var global = ""
|
var global = ""
|
||||||
|
|
||||||
fun log(message: String) {
|
fun log(message: String) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 999
|
||||||
// MODULE: lib
|
// MODULE: lib
|
||||||
// FILE: lib.kt
|
// FILE: lib.kt
|
||||||
|
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 996
|
||||||
// MODULE: lib
|
// MODULE: lib
|
||||||
// FILE: lib.kt
|
// FILE: lib.kt
|
||||||
fun baz(x: String) = "($x)"
|
fun baz(x: String) = "($x)"
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 992
|
||||||
// MODULE: lib
|
// MODULE: lib
|
||||||
// FILE: lib.kt
|
// FILE: lib.kt
|
||||||
fun foo() = "OK"
|
fun foo() = "OK"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// EXPECTED_REACHABLE_NODES: 994
|
||||||
// MODULE: lib1
|
// MODULE: lib1
|
||||||
// FILE: lib1.kt
|
// FILE: lib1.kt
|
||||||
package lib1
|
package lib1
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 509
|
// EXPECTED_REACHABLE_NODES: 1012
|
||||||
// MODULE: lib
|
// MODULE: lib
|
||||||
// FILE: lib.kt
|
// FILE: lib.kt
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1695
|
// EXPECTED_REACHABLE_NODES: 990
|
||||||
// PROPERTY_WRITE_COUNT: name=publishedTopLevel_61zpoe$ count=1
|
// PROPERTY_WRITE_COUNT: name=publishedTopLevel_61zpoe$ count=1
|
||||||
// PROPERTY_WRITE_COUNT: name=published_61zpoe$ count=1
|
// PROPERTY_WRITE_COUNT: name=published_61zpoe$ count=1
|
||||||
// PROPERTY_WRITE_COUNT: name=B count=1
|
// PROPERTY_WRITE_COUNT: name=B count=1
|
||||||
|
|||||||
Reference in New Issue
Block a user