Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
sjrd opened this issue Nov 29, 2023 · 0 comments

Comments

@sjrd
Copy link

sjrd commented Nov 29, 2023

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 ([email protected]): Principal Engineer at the Scala Center

Expected outcome

  • A core library that takes a tasty-query Classpath and emits a set of .d.ts files describing the exported Scala.js API
  • The core library should at least handle the following constructs:
    • @JSExportTopLevel JavaScript classes (extending js.Any), objects, defs and vals
    • def, val, var and lazy val members of the above
    • classes and traits transitively mentioned in the types of the above
    • @JSExportStatic members of companions of exported classes
    • all shapes of types that have a direct, straightforward translation to TypeScript (for example, union types should be handled; type lambdas should not)
  • Stretch goals are:
    • @JSExportTopLevel Scala classes (not extending js.Any) and objects
    • @JSExport members of Scala classes
    • Inner JavaScript classes and objects
    • An sbt plugin to invoke the core library for a Scala.js project as a dedicated task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant