scalacenter / student-projects

The list of the available projects at the Scala Center for bachelor and master students.
8 stars 0 forks source link

Emit TypeSript .d.ts definition files from a Scala.js project using tasty-query #16

Open sjrd opened 12 months ago

sjrd commented 12 months ago

Summary

For a given Scala.js project (and all its transitive dependencies) with @JSExport* exports to JavaScript, emit .d.ts files that describe the exported API for consumption by TypeScript. We will use tasty-query to analyze the API of the project and its dependencies.

Description

In hybrid projects with both Scala.js and TypeScript code, we want to effectively communicate between the two codebases. Since both languages are statically typed, ideally we want to do that using shared type definitions. Using TypeScript code from Scala.js can be done using ScalablyTyped: a tool that generates .scala facade types for Scala.js from TypeScript files. For the other direction, we currently have no good automated tool to produce TypeScript types out of a Scala.js codebase. We have to write them by hand, or call Scala.js as if it were a dynamically typed JavaScript library.

In order to address this issue, we want to automatically generate .d.ts TypeScript type definition files for the "public" API of a Scala.js codebase. By public, we mean what is visible from JavaScript code (not from other Scala.js code depending on it). This includes @JSExportTopLevel entities, as well as their transitive dependencies.

For example, given the following Scala.js code:

class Foo extends js.Object {
  def foo(): Int = 1
}

@JSExportTopLevel("Bar")
class Bar extends js.Object {
  def bar(): String = "hello"
  def newFoo(): Foo = new Foo()
}

class Other extends js.Object

we want to generate the following TypeScript type definitions:

interface Foo {
  foo(): number
}

export class Bar {
  bar(): string
  newFoo(): Foo
}

Note that Foo became an interface in TypeScript, because it is not itself exported. That means that TypeScript cannot instantiate it. However, it can see Bar, instantiate it, and call newFoo() to get a Foo. Therefore we need to characterize the API of an already-created Foo instance. Other, on the other hand, completely disappears, as it does not appear in the public API of something that is (transitively) exported.


Existing approaches to this problem, notably Scala-TS, use a compiler plugin to get semantic access to the Scala.js types. This approach faces some challenges: Scala.js generates a set of JavaScript modules for an entire classpath at a time, whereas compiler plugins only see individual compilation units (source files). This makes the transitive aspect of exports challenging to handle, as well as the production of .d.ts files matching the eventual JavaScript modules.

An approach working on the entire classpath of the Scala.js project would be better able to tackle those challenges. However, until recently, it was difficult to semantically load and examine a full Scala classpath.

This is where tasty-query comes in: it is specifically designed to give access to all the Scala type system information about an entire classpath.

Supervisor

Sébastien Doeraene (sebastien.doeraene@epfl.ch): Principal Engineer at the Scala Center

Expected outcome