3

EDIT: This is not supported yet and tracked on Kotlin YouTrack


I'm trying to write a Kotlin external declaration matching the following Typescript interface (this is valid TS to represent JavaScript accesses via headers['content-length']):

export interface Headers {
  'content-length'?: string;
}

Dukat generates the following, which should be considered valid:

external interface Headers {
    var `content-length`: String? get() = definedExternally; set(value) = definedExternally
}

But now the compiler complains with:

Name contains illegal chars that can't appear in JavaScript identifier

It is true that it can't appear in a JS identifier, but it doesn't have to. All Kotlin accesses to this property like:

val length = headers.`content-length`

could be valid if compiled to const length = headers["content-length"].

I tried to use @JsName to work around it the following ways:

  • @JsName("content-length")
  • @JsName("'content-length'")
  • @JsName("\"content-length\"")

But all of these fail because they only allow strings that are valid JS identifiers. Is there a way to work around this?

Joffrey
  • 32,348
  • 6
  • 68
  • 100

3 Answers3

4

The problem is that hyphens - are not valid identifiers in Javascript. This means that you can't declare a variable like this:

var content-length = 4

You can only do this: var contentLength = 4

Kotlin Kultiplatform will not allow you to write common code which can't be compiled to a target platfrom, that's why even if that this is valid Kotlin code:

var `content-length`: String? // ...

you still can't use it in a Multiplatform environment because of the Javascript constraints.

Please also note that while this might be valid Typescript code, Kotlin doesn't have a Typescript target, only a Javascript one, so bear this in mind.

Joffrey
  • 32,348
  • 6
  • 68
  • 100
Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • I understand your point about identifiers, and I understand what the compile error means, but not every declaration has to be compiled to a JS identifier. I certainly would understand that writing `val \`content-length\` = 2` in Kotlin would trigger a compiler error, but `val length = headers.\`content-length\`` should be valid and compiled to `let length = headers["content-type"]`. Here we're talking about a type declaration, which doesn't have to appear in the produced JS at all. In fact, what matters in JS regarding an interface is only property access, and this is valid as I have shown. – Joffrey Jun 04 '19 at 11:49
  • I'm not familiar with the Javascript compiler so I can't speculate any further I'm afraid. :( – Adam Arold Jun 04 '19 at 11:50
  • I'm not familiar either with the details of the Kotlin/JS compiler, unfortunately. But in this case, the JS library already exists, so I know this is possible to use in JS, I would just love to be able to type this properly in Kotlin without resorting to `dynamic`. – Joffrey Jun 04 '19 at 13:41
  • This is a real bug in KJS I believe. 'grapesjs-lory-slider': { } is valid JS, but there is ZERO way to get kotlin to transpile to that. Pretty big problem... it means no one can interface with an javascript packages that use that convention... if I am not mistaken. in KJS I should be able to do `grapesjs-lory-slider` = jsObject { } – gunslingor Aug 22 '20 at 14:07
2

I suggest working around this problem by defining an empty interface to stand in for such objects in Kotlin, plus an extension property to get and set the value:

external interface KHeader // stands in for JavaScript objects with content-length property

var KHeader.contentLength: String
   get() = this.asDynamic()["content-length"]
   set(value) { this.asDynamic()["content-length"] = value }

In this way you can use Header JavaScript objects in Kotlin with camel case (see playground):

fun main() {
    val jsObject = js("{}")
    jsObject["content-length"] = "44"
    val randomHeader = jsObject as KHeader
    println(randomHeader.contentLength) // prints 44
    randomHeader.contentLength = "55"
    println(randomHeader.contentLength)  // prints 55
}
Simon Jacobs
  • 1,147
  • 7
  • 7
1

Hyphens are allowed in JSONs, and I believe they may be allowed in JavaScript if you use ticks (``). I had this same hyphen issue while using jsObject, this was the solution, literally.

My hyphen problem and this solution:

pluginsOpts = jsObject<dynamic> {
        this["grapesjs-tabs"] = jsObject<dynamic> {
            tabsBlock = jsObject<dynamic> {
                category = "Extra"
            }
        }
}

Try this, literally:

export interface Headers {
  this['content-length']?: string;
}

I couldn't get this working in an external, but this was a good hyphen hack that may assist. Here is how you would use it: Rather than defining an external object, you would use a jsObject{} in it's place. You can nest them, but be sure to explicitly include the dynamic identifier in every layer, especially if you have to use multiple this(es) in different layers to overcome the hyphen issue. This is your solution:

val Header = jsObject<dynamic> {
    this["content-length"] = "something"
}

Let "this" be a lesson to you.

gunslingor
  • 1,358
  • 12
  • 34
  • There is nowhere here where the headers' type is defined in Kotlin. The point of the question is about finding a way to statically and properly type this interface/class, not to find ways to construct instances. – Joffrey Apr 17 '21 at 21:26