├── .gitignore ├── .travis.yml ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── samples ├── abstract.d.ts ├── abstract.d.ts.scala ├── assets │ └── module.d.ts ├── booleanlit.d.ts ├── booleanlit.d.ts.scala ├── comma.d.ts ├── comma.d.ts.scala ├── duplicateliteraltypes.d.ts ├── duplicateliteraltypes.d.ts.scala ├── enum.d.ts ├── enum.d.ts.scala ├── export.d.ts ├── export.d.ts.scala ├── exportidentifier.d.ts ├── exportidentifier.d.ts.scala ├── extendsintersection.d.ts ├── extendsintersection.d.ts.scala ├── extendsobject.d.ts ├── extendsobject.d.ts.scala ├── generics.d.ts ├── generics.d.ts.scala ├── import.d.ts ├── import.d.ts.scala ├── indexabletypes.d.ts ├── indexabletypes.d.ts.scala ├── intersectiontype.d.ts ├── intersectiontype.d.ts.scala ├── jsglobal.d.ts ├── jsglobal.d.ts.scala ├── keyof.d.ts ├── keyof.d.ts.scala ├── modifiers.d.ts ├── modifiers.d.ts.scala ├── nametranslation.d.ts ├── nametranslation.d.ts.scala ├── nestedobjectliteraltypes.d.ts ├── nestedobjectliteraltypes.d.ts.scala ├── never.d.ts ├── never.d.ts.scala ├── numberlit.d.ts ├── numberlit.d.ts.scala ├── objectlit.d.ts ├── objectlit.d.ts.scala ├── overrides.d.ts ├── overrides.d.ts.scala ├── stringlit.d.ts ├── stringlit.d.ts.scala ├── then.d.ts ├── then.d.ts.scala ├── thistype.d.ts ├── thistype.d.ts.scala ├── uniontype.d.ts └── uniontype.d.ts.scala └── src ├── main └── scala │ └── org │ └── scalajs │ └── tools │ └── tsimporter │ ├── Config.scala │ ├── Importer.scala │ ├── Main.scala │ ├── Trees.scala │ ├── Utils.scala │ ├── parser │ ├── TSDefLexical.scala │ └── TSDefParser.scala │ └── sc │ ├── Definitions.scala │ └── Printer.scala └── test └── scala └── org └── scalajs └── tools └── tsimporter └── ImporterSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | /bin/ 3 | .cache 4 | .cache-main 5 | .classpath 6 | .project 7 | .settings/ 8 | /node_modules 9 | /package-lock.json 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: scala 3 | sudo: false 4 | 5 | scala: 2.12.8 6 | 7 | jdk: 8 | - openjdk8 9 | - openjdk11 10 | 11 | script: 12 | - sbt ++$TRAVIS_SCALA_VERSION samples/compile test 13 | - $(npm bin)/tsc samples/*.d.ts 14 | 15 | before_cache: 16 | # See http://www.scala-sbt.org/0.13/docs/Travis-CI-with-sbt.html 17 | # Tricks to avoid unnecessary cache updates 18 | - find $HOME/.sbt -name "*.lock" | xargs rm 19 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm 20 | 21 | install: 22 | - . $HOME/.nvm/nvm.sh 23 | - nvm install 8 24 | - nvm use 8 25 | - npm install typescript@2.6.2 26 | 27 | cache: 28 | directories: 29 | - $HOME/.ivy2/cache 30 | - $HOME/.sbt/boot 31 | - node_modules 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Importer from TypeScript type definitions to Scala.js 2 | 3 | This tool reads type definitions files written for 4 | [TypeScript](http://www.typescriptlang.org/) (.d.ts files) and rewrites them to 5 | .scala files usable with 6 | [Scala.js](http://www.scala-js.org/). 7 | 8 | The process is not 100 % accurate, so manual editing is often needed 9 | afterwards. This can be improved, but not to perfection, because the features 10 | offered by the type systems of TypeScript and Scala.js differ in some subtle 11 | ways. 12 | 13 | ## Usage 14 | 15 | $ sbt 'run somelib.d.ts SomeLib.scala' 16 | 17 | ## License 18 | 19 | The TypeScript Importer for Scala.js is distributed under the 20 | [Scala License](http://www.scala-lang.org/license.html). 21 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | inThisBuild(Def.settings( 2 | organization := "org.scalajs.tools", 3 | version := "0.1-SNAPSHOT", 4 | scalaVersion := "2.12.8", 5 | scalacOptions ++= Seq( 6 | "-deprecation", 7 | "-unchecked", 8 | "-feature", 9 | "-encoding", "utf8" 10 | ) 11 | )) 12 | 13 | val `scala-js-ts-importer` = project.in(file(".")) 14 | .settings( 15 | description := "TypeScript importer for Scala.js", 16 | mainClass := Some("org.scalajs.tools.tsimporter.Main"), 17 | libraryDependencies ++= Seq( 18 | "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", 19 | "com.github.scopt" %% "scopt" % "3.7.1", 20 | "org.scalatest" %% "scalatest" % "3.0.5" % Test 21 | ) 22 | ) 23 | 24 | val samples = project 25 | .enablePlugins(ScalaJSPlugin) 26 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.2.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.27") 2 | -------------------------------------------------------------------------------- /samples/abstract.d.ts: -------------------------------------------------------------------------------- 1 | declare abstract class AbstractClass { 2 | abstract abstractField: String; 3 | abstract abstractMethod(): void; 4 | } 5 | -------------------------------------------------------------------------------- /samples/abstract.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package `abstract` { 7 | 8 | @js.native 9 | @JSGlobal 10 | abstract class AbstractClass extends js.Object { 11 | var abstractField: String 12 | def abstractMethod(): Unit 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /samples/assets/module.d.ts: -------------------------------------------------------------------------------- 1 | declare module "module" { 2 | const value: any; 3 | export const Single: any; 4 | export const First: any; 5 | export const Second: any; 6 | export const Third: any; 7 | export const Fourth: any; 8 | export const Fifth: any; 9 | export default value; 10 | } 11 | -------------------------------------------------------------------------------- /samples/booleanlit.d.ts: -------------------------------------------------------------------------------- 1 | declare module booleanlit { 2 | 3 | export type True = true; 4 | 5 | export interface TruthMachine { 6 | update(input: true): void; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/booleanlit.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package booleanlit { 7 | 8 | package booleanlit { 9 | 10 | @js.native 11 | trait TruthMachine extends js.Object { 12 | def update(input: Boolean): Unit = js.native 13 | } 14 | 15 | @js.native 16 | @JSGlobal("booleanlit") 17 | object Booleanlit extends js.Object { 18 | type True = Boolean 19 | } 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /samples/comma.d.ts: -------------------------------------------------------------------------------- 1 | export class Foo { 2 | foo( 3 | options: { 4 | key1: string, 5 | key2: string, 6 | }, 7 | key3: string, 8 | ): void; 9 | } 10 | 11 | export interface Bar { 12 | key1: string, 13 | key2: string, 14 | } 15 | 16 | export type Callback = ( 17 | value: R 18 | ) => void; 19 | 20 | export type Handler = ( 21 | event: T, 22 | callback: Callback, 23 | ) => void; 24 | -------------------------------------------------------------------------------- /samples/comma.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package comma { 7 | 8 | @js.native 9 | @JSGlobal 10 | class Foo extends js.Object { 11 | def foo(options: js.Any, key3: String): Unit = js.native 12 | } 13 | 14 | @js.native 15 | trait Bar extends js.Object { 16 | var key1: String = js.native 17 | var key2: String = js.native 18 | } 19 | 20 | @js.native 21 | @JSGlobalScope 22 | object Comma extends js.Object { 23 | type Callback[R] = js.Function1[R, Unit] 24 | type Handler[T, R] = js.Function2[T, Callback[R], Unit] 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /samples/duplicateliteraltypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module duplicateliteraltypes { 2 | export interface duplicateliteraltypes { 3 | duplicate(input: true): void; 4 | duplicate(input: false): void; 5 | 6 | duplicate(input: "hello"): void; 7 | duplicate(input: string): void; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/duplicateliteraltypes.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package duplicateliteraltypes { 7 | 8 | package duplicateliteraltypes { 9 | 10 | @js.native 11 | trait duplicateliteraltypes extends js.Object { 12 | def duplicate(input: Boolean): Unit = js.native 13 | def duplicate(input: String): Unit = js.native 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /samples/enum.d.ts: -------------------------------------------------------------------------------- 1 | declare module enumtype { 2 | export enum Color { 3 | Red, Green, Blue 4 | } 5 | 6 | export enum Button { 7 | Submit = "submit", 8 | Reset = "reset", 9 | Button = "button" 10 | } 11 | 12 | export enum Mixed { 13 | EMPTY, NUMERIC = 2, STRING = "string", NEGATIVE = -1 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /samples/enum.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package enum { 7 | 8 | package enumtype { 9 | 10 | @js.native 11 | sealed trait Color extends js.Object { 12 | } 13 | 14 | @js.native 15 | @JSGlobal("enumtype.Color") 16 | object Color extends js.Object { 17 | var Red: Color = js.native 18 | var Green: Color = js.native 19 | var Blue: Color = js.native 20 | @JSBracketAccess 21 | def apply(value: Color): String = js.native 22 | } 23 | 24 | @js.native 25 | sealed trait Button extends js.Object { 26 | } 27 | 28 | @js.native 29 | @JSGlobal("enumtype.Button") 30 | object Button extends js.Object { 31 | var Submit: Button = js.native 32 | var Reset: Button = js.native 33 | var Button: Button = js.native 34 | @JSBracketAccess 35 | def apply(value: Button): String = js.native 36 | } 37 | 38 | @js.native 39 | sealed trait Mixed extends js.Object { 40 | } 41 | 42 | @js.native 43 | @JSGlobal("enumtype.Mixed") 44 | object Mixed extends js.Object { 45 | var EMPTY: Mixed = js.native 46 | var NUMERIC: Mixed = js.native 47 | var STRING: Mixed = js.native 48 | var NEGATIVE: Mixed = js.native 49 | @JSBracketAccess 50 | def apply(value: Mixed): String = js.native 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /samples/export.d.ts: -------------------------------------------------------------------------------- 1 | export const numberRegexp: string; 2 | 3 | export interface StringValidator { 4 | isAcceptable(s: string): boolean; 5 | } 6 | 7 | export namespace Hoge { 8 | export class Fuga { 9 | name: string 10 | } 11 | } 12 | 13 | declare namespace PIXI { 14 | const VERSION: string; 15 | } 16 | 17 | declare module "pixi.js" { 18 | export = PIXI; 19 | } 20 | 21 | export as namespace asNamespace; 22 | 23 | export default Hoge 24 | -------------------------------------------------------------------------------- /samples/export.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package export { 7 | 8 | @js.native 9 | trait StringValidator extends js.Object { 10 | def isAcceptable(s: String): Boolean = js.native 11 | } 12 | 13 | package Hoge { 14 | 15 | @js.native 16 | @JSGlobal("Hoge.Fuga") 17 | class Fuga extends js.Object { 18 | var name: String = js.native 19 | } 20 | 21 | } 22 | 23 | package PIXI { 24 | 25 | @js.native 26 | @JSGlobal("PIXI") 27 | object PIXI extends js.Object { 28 | val VERSION: String = js.native 29 | } 30 | 31 | } 32 | 33 | @js.native 34 | @JSGlobalScope 35 | object Export extends js.Object { 36 | val numberRegexp: String = js.native 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /samples/exportidentifier.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for abs 1.3 2 | // Project: https://github.com/IonicaBizau/node-abs 3 | // Definitions by: Aya Morisawa 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | /** 7 | * Compute the absolute path of an input. 8 | * @param input The input path. 9 | */ 10 | declare function Abs(input: string): string; 11 | export = Abs; 12 | 13 | -------------------------------------------------------------------------------- /samples/exportidentifier.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package exportidentifier { 7 | 8 | @js.native 9 | @JSGlobalScope 10 | object Exportidentifier extends js.Object { 11 | def Abs(input: String): String = js.native 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /samples/extendsintersection.d.ts: -------------------------------------------------------------------------------- 1 | declare module M { 2 | 3 | interface A {} 4 | interface B {} 5 | 6 | function f(t: T); 7 | } 8 | -------------------------------------------------------------------------------- /samples/extendsintersection.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package extendsintersection { 7 | 8 | package M { 9 | 10 | @js.native 11 | trait A extends js.Object { 12 | } 13 | 14 | @js.native 15 | trait B extends js.Object { 16 | } 17 | 18 | @js.native 19 | @JSGlobal("M") 20 | object M extends js.Object { 21 | def f[T <: A with B](t: T): js.Dynamic = js.native 22 | } 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /samples/extendsobject.d.ts: -------------------------------------------------------------------------------- 1 | // copy from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/index.d.ts 2 | declare interface LoDashStatic { 3 | at(); 4 | } 5 | -------------------------------------------------------------------------------- /samples/extendsobject.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package extendsobject { 7 | 8 | @js.native 9 | trait LoDashStatic extends js.Object { 10 | def at[T <: js.Object](): js.Dynamic = js.native 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /samples/generics.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace generics { 2 | class Thing { 3 | name: String 4 | } 5 | export class Container { 6 | t: T 7 | } 8 | export class ContainerWithUpperBound { 9 | t: T 10 | } 11 | export class ContainerWithDefault { 12 | t: T 13 | } 14 | export class ContainerWithUpperBoundAndDefault { 15 | t: T 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/generics.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package generics { 7 | 8 | package generics { 9 | 10 | @js.native 11 | @JSGlobal("generics.Thing") 12 | class Thing extends js.Object { 13 | var name: String = js.native 14 | } 15 | 16 | @js.native 17 | @JSGlobal("generics.Container") 18 | class Container[T] extends js.Object { 19 | var t: T = js.native 20 | } 21 | 22 | @js.native 23 | @JSGlobal("generics.ContainerWithUpperBound") 24 | class ContainerWithUpperBound[T <: Thing] extends js.Object { 25 | var t: T = js.native 26 | } 27 | 28 | @js.native 29 | @JSGlobal("generics.ContainerWithDefault") 30 | class ContainerWithDefault[T] extends js.Object { 31 | var t: T = js.native 32 | } 33 | 34 | @js.native 35 | @JSGlobal("generics.ContainerWithUpperBoundAndDefault") 36 | class ContainerWithUpperBoundAndDefault[T <: Thing] extends js.Object { 37 | var t: T = js.native 38 | } 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /samples/import.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // Import variants from https://www.typescriptlang.org/docs/handbook/modules.html#import 3 | 4 | import Default from "module"; 5 | import { Single } from "module"; 6 | import { Single as Renamed } from "module"; 7 | import { First, Second } from "module"; 8 | import { Third as T, Fourth } from "module"; 9 | import * as validator from "module"; 10 | import "./assets/module"; 11 | 12 | declare const hello: String; 13 | 14 | declare module mod { 15 | export function f(x: Number): String; 16 | } 17 | -------------------------------------------------------------------------------- /samples/import.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package `import` { 7 | 8 | package mod { 9 | 10 | @js.native 11 | @JSGlobal("mod") 12 | object Mod extends js.Object { 13 | def f(x: Number): String = js.native 14 | } 15 | 16 | } 17 | 18 | @js.native 19 | @JSGlobalScope 20 | object Import extends js.Object { 21 | val hello: String = js.native 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /samples/indexabletypes.d.ts: -------------------------------------------------------------------------------- 1 | // extract example from https://www.typescriptlang.org/docs/handbook/interfaces.html#indexable-types 2 | interface NumberDictionary { 3 | [index: string]: number; 4 | } 5 | interface ReadonlyStringArray { 6 | readonly [index: number]: string; 7 | } 8 | -------------------------------------------------------------------------------- /samples/indexabletypes.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package indexabletypes { 7 | 8 | @js.native 9 | trait NumberDictionary extends js.Object { 10 | @JSBracketAccess 11 | def apply(index: String): Double = js.native 12 | @JSBracketAccess 13 | def update(index: String, v: Double): Unit = js.native 14 | } 15 | 16 | @js.native 17 | trait ReadonlyStringArray extends js.Object { 18 | @JSBracketAccess 19 | def apply(index: Double): String = js.native 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /samples/intersectiontype.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace intersectiontype { 2 | 3 | type ArchiverOptions = CoreOptions & ExtraOptions & MoreExtraOptions; 4 | type UnionOfIntersection = CoreOptions | CoreOptions & ExtraOptions | CoreOptions & MoreExtraOptions; 5 | type Duplicates = CoreOptions & CoreOptions & ExtraOptions & MoreExtraOptions; 6 | 7 | interface CoreOptions { 8 | statConcurrency: number; 9 | } 10 | 11 | interface ExtraOptions { 12 | allowHalfOpen: boolean; 13 | } 14 | 15 | interface MoreExtraOptions { 16 | store: boolean; 17 | } 18 | 19 | export function test(v : CoreOptions & ExtraOptions): CoreOptions & MoreExtraOptions; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /samples/intersectiontype.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package intersectiontype { 7 | 8 | package intersectiontype { 9 | 10 | @js.native 11 | trait CoreOptions extends js.Object { 12 | var statConcurrency: Double = js.native 13 | } 14 | 15 | @js.native 16 | trait ExtraOptions extends js.Object { 17 | var allowHalfOpen: Boolean = js.native 18 | } 19 | 20 | @js.native 21 | trait MoreExtraOptions extends js.Object { 22 | var store: Boolean = js.native 23 | } 24 | 25 | @js.native 26 | @JSGlobal("intersectiontype") 27 | object Intersectiontype extends js.Object { 28 | type ArchiverOptions = CoreOptions with ExtraOptions with MoreExtraOptions 29 | type UnionOfIntersection = CoreOptions | CoreOptions with ExtraOptions | CoreOptions with MoreExtraOptions 30 | type Duplicates = CoreOptions with ExtraOptions with MoreExtraOptions 31 | def test(v: CoreOptions with ExtraOptions): CoreOptions with MoreExtraOptions = js.native 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /samples/jsglobal.d.ts: -------------------------------------------------------------------------------- 1 | export class Point { 2 | constructor(x: number, y: number); 3 | readonly x: number; 4 | readonly y: number; 5 | static isPoint(thing: any): boolean; 6 | } 7 | 8 | declare module nested { 9 | type Line = Array; 10 | 11 | export class Circle { 12 | constructor(center: Point, radius: number); 13 | readonly center: Point; 14 | readonly radius: number; 15 | static isCirce(thing: any): boolean; 16 | } 17 | } 18 | 19 | declare const globalConst: String; 20 | declare let globalVar: String; 21 | declare function globalFunc(): String; 22 | -------------------------------------------------------------------------------- /samples/jsglobal.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package jsglobal { 7 | 8 | @js.native 9 | @JSGlobal 10 | class Point protected () extends js.Object { 11 | def this(x: Double, y: Double) = this() 12 | def x: Double = js.native 13 | def y: Double = js.native 14 | } 15 | 16 | @js.native 17 | @JSGlobal 18 | object Point extends js.Object { 19 | def isPoint(thing: js.Any): Boolean = js.native 20 | } 21 | 22 | package nested { 23 | 24 | @js.native 25 | @JSGlobal("nested.Circle") 26 | class Circle protected () extends js.Object { 27 | def this(center: Point, radius: Double) = this() 28 | def center: Point = js.native 29 | def radius: Double = js.native 30 | } 31 | 32 | @js.native 33 | @JSGlobal("nested.Circle") 34 | object Circle extends js.Object { 35 | def isCirce(thing: js.Any): Boolean = js.native 36 | } 37 | 38 | @js.native 39 | @JSGlobal("nested") 40 | object Nested extends js.Object { 41 | type Line = js.Array[Point] 42 | } 43 | 44 | } 45 | 46 | @js.native 47 | @JSGlobalScope 48 | object Jsglobal extends js.Object { 49 | val globalConst: String = js.native 50 | def globalVar: String = js.native 51 | def globalFunc(): String = js.native 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /samples/keyof.d.ts: -------------------------------------------------------------------------------- 1 | // copy example from https://github.com/Microsoft/TypeScript/pull/11929 2 | interface Thing { 3 | name: string; 4 | width: number; 5 | height: number; 6 | inStock: boolean; 7 | } 8 | 9 | type K1 = keyof Thing; // "name" | "width" | "height" | "inStock" 10 | type K2 = keyof Thing[]; // "length" | "push" | "pop" | "concat" | ... 11 | type K3 = keyof { [x: string]: Thing }; // string 12 | 13 | type P1 = Thing["name"]; // string 14 | type P2 = Thing["width" | "height"]; // number 15 | type P3 = Thing["name" | "inStock"]; // string | boolean 16 | type P4 = string["charAt"]; // (pos: number) => string 17 | type P5 = string[]["push"]; // (...items: string[]) => number 18 | 19 | // following line will work after merged https://github.com/sjrd/scala-js-ts-importer/pull/47 20 | // type P6 = string[][0]; // string 21 | 22 | // extract example from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/lodash/index.d.ts 23 | interface LoDashStatic { 24 | at( 25 | object: T | null | undefined, 26 | ...props: Array 27 | ): Array; 28 | } 29 | -------------------------------------------------------------------------------- /samples/keyof.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package keyof { 7 | 8 | @js.native 9 | trait Thing extends js.Object { 10 | var name: String = js.native 11 | var width: Double = js.native 12 | var height: Double = js.native 13 | var inStock: Boolean = js.native 14 | } 15 | 16 | @js.native 17 | trait LoDashStatic extends js.Object { 18 | def at[T](`object`: T | Null | Unit, props: String*): js.Array[js.Any] = js.native 19 | } 20 | 21 | @js.native 22 | @JSGlobalScope 23 | object Keyof extends js.Object { 24 | type K1 = String 25 | type K2 = String 26 | type K3 = String 27 | type P1 = js.Any 28 | type P2 = js.Any 29 | type P3 = js.Any 30 | type P4 = js.Any 31 | type P5 = js.Any 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples/modifiers.d.ts: -------------------------------------------------------------------------------- 1 | declare module modifiers { 2 | 3 | const id: string; 4 | 5 | let name: string; 6 | 7 | interface IEvent {} 8 | 9 | export class Emitter { 10 | constructor(); 11 | public readonly event: IEvent; 12 | fire(event?: T): void; 13 | dispose(): void; 14 | } 15 | 16 | export var EditorType: { 17 | ICodeEditor: string; 18 | IDiffEditor: string; 19 | }; 20 | 21 | export let EditorType2: { 22 | ICodeEditor: string; 23 | IDiffEditor: string; 24 | }; 25 | 26 | export const CursorMoveByUnit: { 27 | Line: string; 28 | WrappedLine: string; 29 | Character: string; 30 | HalfLine: string; 31 | }; 32 | 33 | export class Uri { 34 | static isUri(thing: any): boolean; 35 | public static parse(value: string): Uri; 36 | protected constructor(); 37 | readonly scheme: string; 38 | readonly authority: string; 39 | readonly path: string; 40 | private cache; 41 | private ignoreMe: number; 42 | private static ignoreMe2: number; 43 | private updateCache(); 44 | private static resolve(): String; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /samples/modifiers.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package modifiers { 7 | 8 | package modifiers { 9 | 10 | @js.native 11 | trait IEvent[T] extends js.Object { 12 | } 13 | 14 | @js.native 15 | @JSGlobal("modifiers.Emitter") 16 | class Emitter[T] extends js.Object { 17 | def event: IEvent[T] = js.native 18 | def fire(event: T = ???): Unit = js.native 19 | def dispose(): Unit = js.native 20 | } 21 | 22 | @js.native 23 | @JSGlobal("modifiers.EditorType") 24 | object EditorType extends js.Object { 25 | var ICodeEditor: String = js.native 26 | var IDiffEditor: String = js.native 27 | } 28 | 29 | @js.native 30 | @JSGlobal("modifiers.EditorType2") 31 | object EditorType2 extends js.Object { 32 | var ICodeEditor: String = js.native 33 | var IDiffEditor: String = js.native 34 | } 35 | 36 | @js.native 37 | @JSGlobal("modifiers.CursorMoveByUnit") 38 | object CursorMoveByUnit extends js.Object { 39 | var Line: String = js.native 40 | var WrappedLine: String = js.native 41 | var Character: String = js.native 42 | var HalfLine: String = js.native 43 | } 44 | 45 | @js.native 46 | @JSGlobal("modifiers.Uri") 47 | class Uri extends js.Object { 48 | def scheme: String = js.native 49 | def authority: String = js.native 50 | def path: String = js.native 51 | } 52 | 53 | @js.native 54 | @JSGlobal("modifiers.Uri") 55 | object Uri extends js.Object { 56 | def isUri(thing: js.Any): Boolean = js.native 57 | def parse(value: String): Uri = js.native 58 | } 59 | 60 | @js.native 61 | @JSGlobal("modifiers") 62 | object Modifiers extends js.Object { 63 | val id: String = js.native 64 | def name: String = js.native 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /samples/nametranslation.d.ts: -------------------------------------------------------------------------------- 1 | declare const float32Array: Float32Array; 2 | declare const float64Array: Float64Array; 3 | declare const uint8Array: Uint8Array; 4 | declare const uint16Array: Uint16Array; 5 | declare const uint32Array: Uint32Array; 6 | declare const int8Array: Int8Array; 7 | declare const int16Array: Int16Array; 8 | declare const int32Array: Int32Array; 9 | declare const uint8ClampedArray: Uint8ClampedArray; 10 | 11 | declare const arrayBuffer: ArrayBuffer; 12 | declare const arrayBufferView: ArrayBufferView; 13 | declare const dataView: DataView; 14 | 15 | declare const readonlyArray: ReadonlyArray; 16 | 17 | declare const promiseLike: PromiseLike; 18 | -------------------------------------------------------------------------------- /samples/nametranslation.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package nametranslation { 7 | 8 | @js.native 9 | @JSGlobalScope 10 | object Nametranslation extends js.Object { 11 | val float32Array: js.typedarray.Float32Array = js.native 12 | val float64Array: js.typedarray.Float64Array = js.native 13 | val uint8Array: js.typedarray.Uint8Array = js.native 14 | val uint16Array: js.typedarray.Uint16Array = js.native 15 | val uint32Array: js.typedarray.Uint32Array = js.native 16 | val int8Array: js.typedarray.Int8Array = js.native 17 | val int16Array: js.typedarray.Int16Array = js.native 18 | val int32Array: js.typedarray.Int32Array = js.native 19 | val uint8ClampedArray: js.typedarray.Uint8ClampedArray = js.native 20 | val arrayBuffer: js.typedarray.ArrayBuffer = js.native 21 | val arrayBufferView: js.typedarray.ArrayBufferView = js.native 22 | val dataView: js.typedarray.DataView = js.native 23 | val readonlyArray: js.Array[_ <: Boolean] = js.native 24 | val promiseLike: js.Thenable[String] = js.native 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /samples/nestedobjectliteraltypes.d.ts: -------------------------------------------------------------------------------- 1 | declare module A { 2 | interface Info { 3 | settings: { 4 | state: { 5 | enable: boolean; 6 | }; 7 | }; 8 | } 9 | 10 | export let objectInfo: Info; 11 | } 12 | -------------------------------------------------------------------------------- /samples/nestedobjectliteraltypes.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package nestedobjectliteraltypes { 7 | 8 | package A { 9 | 10 | @js.native 11 | trait Info extends js.Object { 12 | var settings: Info.Settings = js.native 13 | } 14 | 15 | object Info { 16 | 17 | @js.native 18 | trait Settings extends js.Object { 19 | var state: Settings.State = js.native 20 | } 21 | 22 | object Settings { 23 | 24 | @js.native 25 | trait State extends js.Object { 26 | var enable: Boolean = js.native 27 | } 28 | } 29 | } 30 | 31 | @js.native 32 | @JSGlobal("A") 33 | object A extends js.Object { 34 | def objectInfo: Info = js.native 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /samples/never.d.ts: -------------------------------------------------------------------------------- 1 | declare module nevertype { 2 | 3 | export class RangeQuery { 4 | never: never; 5 | 6 | value(queryVal: string | number): string; 7 | 8 | method(foo: never): Array; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /samples/never.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package never { 7 | 8 | package nevertype { 9 | 10 | @js.native 11 | @JSGlobal("nevertype.RangeQuery") 12 | class RangeQuery extends js.Object { 13 | var never: Nothing = js.native 14 | def value(queryVal: String | Double): String = js.native 15 | def method(foo: Nothing): js.Array[Nothing] = js.native 16 | } 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /samples/numberlit.d.ts: -------------------------------------------------------------------------------- 1 | declare module numberlit { 2 | 3 | export type HttpStatuscode = 200 | 404 | 503 ; 4 | 5 | // 1 to Int, 1.0 (which is valid-int) to Double 6 | export function floating(prob: 0.1 | 0.5 | 1.0): 0.0 | 1 7 | 8 | export interface Machine { 9 | state?: 0 | 1; 10 | 11 | setState(flag: 0 | 1 | boolean): 0 | 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/numberlit.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package numberlit { 7 | 8 | package numberlit { 9 | 10 | @js.native 11 | trait Machine extends js.Object { 12 | var state: Int = js.native 13 | def setState(flag: Int | Boolean): Int = js.native 14 | } 15 | 16 | @js.native 17 | @JSGlobal("numberlit") 18 | object Numberlit extends js.Object { 19 | type HttpStatuscode = Int 20 | def floating(prob: Double): Double | Int = js.native 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /samples/objectlit.d.ts: -------------------------------------------------------------------------------- 1 | declare var ObjectType: { name: string; age: number }; 2 | 3 | declare var NumericKeyObjectType: { 0: number; 1: number; 2.1: number }; 4 | -------------------------------------------------------------------------------- /samples/objectlit.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package objectlit { 7 | 8 | @js.native 9 | @JSGlobal 10 | object ObjectType extends js.Object { 11 | var name: String = js.native 12 | var age: Double = js.native 13 | } 14 | 15 | @js.native 16 | @JSGlobal 17 | object NumericKeyObjectType extends js.Object { 18 | var `0`: Double = js.native 19 | var `1`: Double = js.native 20 | var `2.1`: Double = js.native 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /samples/overrides.d.ts: -------------------------------------------------------------------------------- 1 | declare module overrides { 2 | 3 | export class A { 4 | equals(other: A): boolean; 5 | clone(): A; 6 | toString(): string; 7 | } 8 | 9 | interface BLike { 10 | toString(): string; 11 | } 12 | 13 | export class B implements BLike { 14 | equals(other: any): boolean; 15 | clone(): BLike; 16 | toString(): string; 17 | } 18 | 19 | interface C { 20 | equals(other: any): boolean; 21 | clone(): C; 22 | toString(): string; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/overrides.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package overrides { 7 | 8 | package overrides { 9 | 10 | @js.native 11 | @JSGlobal("overrides.A") 12 | class A extends js.Object { 13 | def equals(other: A): Boolean = js.native 14 | override def clone(): A = js.native 15 | override def toString(): String = js.native 16 | } 17 | 18 | @js.native 19 | trait BLike extends js.Object { 20 | override def toString(): String = js.native 21 | } 22 | 23 | @js.native 24 | @JSGlobal("overrides.B") 25 | class B extends BLike { 26 | def equals(other: js.Any): Boolean = js.native 27 | override def clone(): BLike = js.native 28 | override def toString(): String = js.native 29 | } 30 | 31 | @js.native 32 | trait C extends js.Object { 33 | def equals(other: js.Any): Boolean = js.native 34 | override def clone(): C = js.native 35 | override def toString(): String = js.native 36 | } 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /samples/stringlit.d.ts: -------------------------------------------------------------------------------- 1 | declare module stringlit { 2 | 3 | export type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black'; 4 | 5 | export interface IEditorOptions { 6 | ariaLabel?: string; 7 | rulers?: number[]; 8 | selectionClipboard?: boolean; 9 | lineNumbers?: 'on' | 'off' | 'relative' | ((lineNumber: number) => string); 10 | readable?: 'yes' | boolean | 'restricted'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/stringlit.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package stringlit { 7 | 8 | package stringlit { 9 | 10 | @js.native 11 | trait IEditorOptions extends js.Object { 12 | var ariaLabel: String = js.native 13 | var rulers: js.Array[Double] = js.native 14 | var selectionClipboard: Boolean = js.native 15 | var lineNumbers: String | js.Function1[Double, String] = js.native 16 | var readable: String | Boolean = js.native 17 | } 18 | 19 | @js.native 20 | @JSGlobal("stringlit") 21 | object Stringlit extends js.Object { 22 | type BuiltinTheme = String 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /samples/then.d.ts: -------------------------------------------------------------------------------- 1 | declare module then { 2 | 3 | interface Thenable { 4 | then( 5 | onfulfilled?: (value: T) => TResult | Thenable, 6 | onrejected?: (reason: any) => TResult | Thenable): Thenable; 7 | } 8 | 9 | class then {} 10 | } 11 | -------------------------------------------------------------------------------- /samples/then.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package `then` { 7 | 8 | package `then` { 9 | 10 | @js.native 11 | trait Thenable[T] extends js.Object { 12 | def `then`[TResult](onfulfilled: js.Function1[T, TResult | Thenable[TResult]] = ???, onrejected: js.Function1[js.Any, TResult | Thenable[TResult]] = ???): Thenable[TResult] = js.native 13 | } 14 | 15 | @js.native 16 | @JSGlobal("then.then") 17 | class `then` extends js.Object { 18 | } 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /samples/thistype.d.ts: -------------------------------------------------------------------------------- 1 | declare module thistype { 2 | 3 | export class A { 4 | merge(other: this): this; 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /samples/thistype.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package thistype { 7 | 8 | package thistype { 9 | 10 | @js.native 11 | @JSGlobal("thistype.A") 12 | class A extends js.Object { 13 | def merge(other: this.type): this.type = js.native 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /samples/uniontype.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace uniontype { 2 | export type NumberOrString = number | string; 3 | export type LeadingPipe = | number | string; 4 | export type MultilineLeadingPipe = 5 | | number 6 | | string; 7 | } 8 | -------------------------------------------------------------------------------- /samples/uniontype.d.ts.scala: -------------------------------------------------------------------------------- 1 | 2 | import scala.scalajs.js 3 | import js.annotation._ 4 | import js.| 5 | 6 | package uniontype { 7 | 8 | package uniontype { 9 | 10 | @js.native 11 | @JSGlobal("uniontype") 12 | object Uniontype extends js.Object { 13 | type NumberOrString = Double | String 14 | type LeadingPipe = Double | String 15 | type MultilineLeadingPipe = Double | String 16 | } 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/Config.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.tools.tsimporter 2 | 3 | case class Config( 4 | inputFileName: String = "", 5 | outputFileName: String = "", 6 | packageName: String = "importedjs" 7 | ) 8 | 9 | object Config { 10 | final val parser = new scopt.OptionParser[Config]("scalajs-ts-importer") { 11 | arg[String]("").required() 12 | .text("TypeScript type definition file to be read") 13 | .action((i, config) => config.copy(inputFileName = i)) 14 | 15 | arg[String]("").required() 16 | .text("Output Scala.js file") 17 | .action((o, config) => config.copy(outputFileName = o)) 18 | 19 | arg[String]("").optional() 20 | .text("Package name for the output (defaults to \"importedjs\")") 21 | .action((pn, config) => config.copy(packageName = pn)) 22 | 23 | help("help").abbr("h") 24 | .text("prints help") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/Importer.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter 7 | 8 | import Trees.{ TypeRef => TypeRefTree, _ } 9 | import sc._ 10 | 11 | /** The meat and potatoes: the importer 12 | * It reads the TypeScript AST and produces (hopefully) equivalent Scala 13 | * code. 14 | */ 15 | class Importer(val output: java.io.PrintWriter) { 16 | import Importer._ 17 | 18 | /** Entry point */ 19 | def apply(declarations: List[DeclTree], outputPackage: String) { 20 | val rootPackage = new PackageSymbol(Name.EMPTY) 21 | 22 | for (declaration <- declarations) 23 | processDecl(rootPackage, declaration) 24 | 25 | new Printer(output, outputPackage).printSymbol(rootPackage) 26 | } 27 | 28 | private def processDecl(owner: ContainerSymbol, declaration: DeclTree) { 29 | declaration match { 30 | case ModuleDecl(PropertyNameName(name), innerDecls) => 31 | assert(owner.isInstanceOf[PackageSymbol], 32 | s"Found package $name in non-package $owner") 33 | val sym = owner.asInstanceOf[PackageSymbol].getPackageOrCreate(name) 34 | 35 | for (innerDecl <- innerDecls) 36 | processDecl(sym, innerDecl) 37 | 38 | case TopLevelExportDecl(IdentName(name)) => 39 | // print nothing, since the value specified by the identifier is printed elsewhere. 40 | 41 | case VarDecl(IdentName(name), Some(tpe @ ObjectType(members))) => 42 | val sym = owner.getModuleOrCreate(name) 43 | processMembersDecls(owner, sym, members) 44 | 45 | case ConstDecl(IdentName(name), Some(tpe @ ObjectType(members))) => 46 | val sym = owner.getModuleOrCreate(name) 47 | processMembersDecls(owner, sym, members) 48 | 49 | case LetDecl(IdentName(name), Some(tpe @ ObjectType(members))) => 50 | val sym = owner.getModuleOrCreate(name) 51 | processMembersDecls(owner, sym, members) 52 | 53 | case TypeDecl(TypeNameName(name), tpe @ ObjectType(members)) => 54 | val sym = owner.getClassOrCreate(name) 55 | processMembersDecls(owner, sym, members) 56 | 57 | case EnumDecl(TypeNameName(name), members) => 58 | // Type 59 | val tsym = owner.getClassOrCreate(name) 60 | tsym.isSealed = true 61 | 62 | // Module 63 | val sym = owner.getModuleOrCreate(name) 64 | for (IdentName(name) <- members) { 65 | val m = sym.newField(name, Set.empty) 66 | m.protectName() 67 | m.tpe = TypeRef(tsym.name) 68 | } 69 | val applySym = sym.newMethod(Name("apply"), Set.empty[Modifier]) 70 | applySym.params += new ParamSymbol(Name("value"), TypeRef(tsym.name)) 71 | applySym.resultType = TypeRef.String 72 | applySym.isBracketAccess = true 73 | 74 | case ClassDecl(TypeNameName(name), tparams, parent, implements, members, isAbstract) => 75 | val sym = owner.getClassOrCreate(name) 76 | sym.isAbstract = isAbstract 77 | sym.isTrait = false 78 | parent.foreach(sym.parents += typeToScala(_)) 79 | for { 80 | parent <- implements.map(typeToScala) 81 | if !sym.parents.contains(parent) 82 | } { 83 | sym.parents += parent 84 | } 85 | sym.tparams ++= typeParamsToScala(tparams) 86 | processMembersDecls(owner, sym, members) 87 | if (!sym.members.exists(_.name == Name.CONSTRUCTOR)) { 88 | processDefDecl(sym, Name.CONSTRUCTOR, 89 | FunSignature(Nil, Nil, Some(TypeRefTree(CoreType("void")))), Set.empty[Modifier]) 90 | } 91 | 92 | case InterfaceDecl(TypeNameName(name), tparams, inheritance, members) => 93 | val sym = owner.getClassOrCreate(name) 94 | for { 95 | parent <- inheritance.map(typeToScala) 96 | if !sym.parents.contains(parent) 97 | } { 98 | sym.parents += parent 99 | } 100 | sym.tparams ++= typeParamsToScala(tparams) 101 | processMembersDecls(owner, sym, members) 102 | 103 | case TypeAliasDecl(TypeNameName(name), tparams, alias) => 104 | val sym = owner.newTypeAlias(name) 105 | sym.tparams ++= typeParamsToScala(tparams) 106 | sym.alias = typeToScala(alias) 107 | 108 | case VarDecl(IdentName(name), TypeOrAny(tpe)) => 109 | val sym = owner.newField(name, Set.empty) 110 | sym.tpe = typeToScala(tpe) 111 | 112 | case ConstDecl(IdentName(name), TypeOrAny(tpe)) => 113 | val sym = owner.newField(name, Set(Modifier.Const)) 114 | sym.tpe = typeToScala(tpe) 115 | 116 | case LetDecl(IdentName(name), TypeOrAny(tpe)) => 117 | val sym = owner.newField(name, Set(Modifier.ReadOnly)) 118 | sym.tpe = typeToScala(tpe) 119 | 120 | case FunctionDecl(IdentName(name), signature) => 121 | processDefDecl(owner, name, signature, Set.empty[Modifier]) 122 | 123 | case ImportDecl => // Ignore imports 124 | 125 | case _ => 126 | owner.members += new CommentSymbol("??? "+declaration) 127 | } 128 | } 129 | 130 | private def processMembersDecls(enclosing: ContainerSymbol, 131 | owner: ContainerSymbol, members: List[MemberTree]) { 132 | 133 | val OwnerName = owner.name 134 | 135 | lazy val companionClassRef = { 136 | val tparams = enclosing.findClass(OwnerName) match { 137 | case Some(clazz) => 138 | clazz.tparams.toList.map(tp => TypeRefTree(TypeNameName(tp.name), Nil)) 139 | case _ => Nil 140 | } 141 | TypeRefTree(TypeNameName(OwnerName), tparams) 142 | } 143 | 144 | for (member <- members) member match { 145 | case CallMember(signature) => 146 | processDefDecl(owner, Name("apply"), signature, Set.empty[Modifier], protectName = false) 147 | 148 | case ConstructorMember(sig @ FunSignature(tparamsIgnored, params, Some(resultType))) 149 | if owner.isInstanceOf[ModuleSymbol] && resultType == companionClassRef => 150 | val classSym = enclosing.getClassOrCreate(owner.name) 151 | classSym.isTrait = false 152 | processDefDecl(classSym, Name.CONSTRUCTOR, 153 | FunSignature(Nil, params, Some(TypeRefTree(CoreType("void")))), Set.empty[Modifier]) 154 | 155 | case PropertyMember(PropertyNameName(name), opt, tpe, mods) if mods(Modifier.Static) => 156 | assert(owner.isInstanceOf[ClassSymbol], 157 | s"Cannot process static member $name in module definition") 158 | val module = enclosing.getModuleOrCreate(owner.name) 159 | processPropertyDecl(enclosing, module, name, tpe, mods) 160 | 161 | case PropertyMember(PropertyNameName(name), opt, tpe, mods) => 162 | processPropertyDecl(enclosing, owner, name, tpe, mods) 163 | 164 | case FunctionMember(PropertyName("constructor"), _, signature, modifiers) 165 | if owner.isInstanceOf[ClassSymbol] && !modifiers(Modifier.Static) => 166 | owner.asInstanceOf[ClassSymbol].isTrait = false 167 | processDefDecl(owner, Name.CONSTRUCTOR, 168 | FunSignature(Nil, signature.params, Some(TypeRefTree(CoreType("void")))), modifiers) 169 | 170 | case FunctionMember(PropertyNameName(name), opt, signature, modifiers) 171 | if modifiers(Modifier.Static) => 172 | assert(owner.isInstanceOf[ClassSymbol], 173 | s"Cannot process static member $name in module definition") 174 | val module = enclosing.getModuleOrCreate(owner.name) 175 | processDefDecl(module, name, signature, modifiers) 176 | 177 | case FunctionMember(PropertyNameName(name), opt, signature, modifiers) => 178 | processDefDecl(owner, name, signature, modifiers) 179 | 180 | case IndexMember(IdentName(indexName), indexType, valueType, modifiers) => 181 | val indexTpe = typeToScala(indexType) 182 | val valueTpe = typeToScala(valueType) 183 | 184 | val getterSym = owner.newMethod(Name("apply"), Set.empty[Modifier]) 185 | getterSym.params += new ParamSymbol(indexName, indexTpe) 186 | getterSym.resultType = valueTpe 187 | getterSym.isBracketAccess = true 188 | 189 | if (!modifiers(Modifier.ReadOnly)){ 190 | val setterSym = owner.newMethod(Name("update"), Set.empty[Modifier]) 191 | setterSym.params += new ParamSymbol(indexName, indexTpe) 192 | setterSym.params += new ParamSymbol(Name("v"), valueTpe) 193 | setterSym.resultType = TypeRef.Unit 194 | setterSym.isBracketAccess = true 195 | } 196 | 197 | case PrivateMember => // ignore 198 | 199 | case _ => 200 | owner.members += new CommentSymbol("??? "+member) 201 | } 202 | } 203 | 204 | private def processPropertyDecl(enclosing: ContainerSymbol, owner: ContainerSymbol, name: Name, 205 | tpe: TypeTree, modifiers: Modifiers, protectName: Boolean = true) { 206 | if (name.name != "prototype") { 207 | tpe match { 208 | case ObjectType(members) if members.forall(_.isInstanceOf[CallMember]) => 209 | // alternative notation for overload methods - #3 210 | for (CallMember(signature) <- members) 211 | processDefDecl(owner, name, signature, modifiers, protectName) 212 | case ObjectType(members) => 213 | val module = enclosing.getModuleOrCreate(owner.name) 214 | module.isGlobal = false 215 | val classSym = module.getClassOrCreate(name.capitalize) 216 | processMembersDecls(module, classSym, members) 217 | val sym = owner.newField(name, modifiers) 218 | sym.tpe = TypeRef(QualifiedName(module.name, classSym.name)) 219 | case _ => 220 | val sym = owner.newField(name, modifiers) 221 | if (protectName) 222 | sym.protectName() 223 | sym.tpe = typeToScala(tpe) 224 | } 225 | } 226 | } 227 | 228 | private def processDefDecl(owner: ContainerSymbol, name: Name, 229 | signature: FunSignature, modifiers: Modifiers, protectName: Boolean = true) { 230 | val sym = owner.newMethod(name, modifiers) 231 | if (protectName) 232 | sym.protectName() 233 | 234 | sym.tparams ++= typeParamsToScala(signature.tparams) 235 | 236 | for (FunParam(IdentName(paramName), opt, TypeOrAny(tpe)) <- signature.params) { 237 | val paramSym = new ParamSymbol(paramName) 238 | paramSym.optional = opt 239 | tpe match { 240 | case RepeatedType(tpe0) => 241 | paramSym.tpe = TypeRef.Repeated(typeToScala(tpe0)) 242 | case _ => 243 | paramSym.tpe = typeToScala(tpe) 244 | } 245 | sym.params += paramSym 246 | } 247 | 248 | sym.resultType = typeToScala(signature.resultType.orDynamic, true) 249 | 250 | owner.removeIfDuplicate(sym) 251 | } 252 | 253 | private def typeParamsToScala(tparams: List[TypeParam]): List[TypeParamSymbol] = { 254 | for (TypeParam(TypeNameName(tparam), upperBound) <- tparams) yield 255 | new TypeParamSymbol(tparam, upperBound map typeToScala) 256 | } 257 | 258 | private def typeToScala(tpe: TypeTree): TypeRef = 259 | typeToScala(tpe, false) 260 | 261 | private def typeToScala(tpe: TypeTree, anyAsDynamic: Boolean): TypeRef = { 262 | tpe match { 263 | case TypeRefTree(tpe: CoreType, Nil) => 264 | coreTypeToScala(tpe, anyAsDynamic) 265 | 266 | case TypeRefTree(TypeName("ReadonlyArray"), List(arrayType)) => 267 | TypeRef(QualifiedName.JSArray, List(Wildcard(Some(typeToScala(arrayType))))) 268 | 269 | case TypeRefTree(base, targs) => 270 | val baseTypeRef = base match { 271 | case TypeName("Array") => QualifiedName.Array 272 | case TypeName("Function") => QualifiedName.FunctionBase 273 | case TypeName("object") => QualifiedName.Object 274 | case TypeName("PromiseLike") => QualifiedName.Thenable 275 | case TypeName("Float32Array") => QualifiedName.Float32Array 276 | case TypeName("Float64Array") => QualifiedName.Float64Array 277 | case TypeName("Int8Array") => QualifiedName.Int8Array 278 | case TypeName("Int16Array") => QualifiedName.Int16Array 279 | case TypeName("Int32Array") => QualifiedName.Int32Array 280 | case TypeName("Uint8Array") => QualifiedName.Uint8Array 281 | case TypeName("Uint16Array") => QualifiedName.Uint16Array 282 | case TypeName("Uint32Array") => QualifiedName.Uint32Array 283 | case TypeName("Uint8ClampedArray") => QualifiedName.Uint8ClampedArray 284 | case TypeName("ArrayBuffer") => QualifiedName.ArrayBuffer 285 | case TypeName("ArrayBufferView") => QualifiedName.ArrayBufferView 286 | case TypeName("DataView") => QualifiedName.DataView 287 | case TypeNameName(name) => QualifiedName(name) 288 | case QualifiedTypeName(qualifier, TypeNameName(name)) => 289 | val qual1 = qualifier map (x => Name(x.name)) 290 | QualifiedName((qual1 :+ name): _*) 291 | case _: CoreType => throw new MatchError(base) 292 | } 293 | TypeRef(baseTypeRef, targs map typeToScala) 294 | 295 | case ConstantType(StringLiteral(_)) => 296 | TypeRef.String 297 | 298 | case ConstantType(IntLiteral(i)) => 299 | TypeRef.Int 300 | 301 | case ConstantType(DoubleLiteral(d)) => 302 | TypeRef.Double 303 | 304 | case ConstantType(BooleanLiteral(_)) => 305 | TypeRef.Boolean 306 | 307 | case ObjectType(List(IndexMember(_, TypeRefTree(CoreType("string"), _), valueType, _))) => 308 | val valueTpe = typeToScala(valueType) 309 | TypeRef(QualifiedName.Dictionary, List(valueTpe)) 310 | 311 | case ObjectType(members) => 312 | // ??? 313 | TypeRef.Any 314 | 315 | case FunctionType(FunSignature(tparams, params, Some(resultType))) => 316 | if (!tparams.isEmpty) { 317 | // Type parameters in function types are not supported 318 | TypeRef.Function 319 | } else if (params.exists(_.tpe.exists(_.isInstanceOf[RepeatedType]))) { 320 | // Repeated params in function types are not supported 321 | TypeRef.Function 322 | } else { 323 | val paramTypes = 324 | for (FunParam(_, _, TypeOrAny(tpe)) <- params) 325 | yield typeToScala(tpe) 326 | val resType = resultType match { 327 | case TypeRefTree(CoreType("any"), Nil) => TypeRef.ScalaAny 328 | case _ => typeToScala(resultType) 329 | } 330 | val targs = paramTypes :+ resType 331 | 332 | TypeRef(QualifiedName.Function(params.size), targs) 333 | } 334 | 335 | case IntersectionType(left, right) => 336 | def visit(tpe: TypeTree, visited: List[TypeRef]): List[TypeRef] = { 337 | tpe match { 338 | case IntersectionType(left, right) => 339 | visit(left, visit(right, visited)) 340 | case _ => 341 | typeToScala(tpe) :: visited 342 | } 343 | } 344 | TypeRef.Intersection(visit(tpe, Nil).distinct) 345 | 346 | case UnionType(left, right) => 347 | def visit(tpe: TypeTree, visited: List[TypeRef]): List[TypeRef] = { 348 | tpe match { 349 | case UnionType(left, right) => 350 | visit(left, visit(right, visited)) 351 | case _ => 352 | typeToScala(tpe) :: visited 353 | } 354 | } 355 | 356 | TypeRef.Union(visit(tpe, Nil).distinct) 357 | 358 | case TypeQuery(expr) => 359 | TypeRef.Singleton(QualifiedName((expr.qualifier :+ expr.name).map( 360 | ident => Name(ident.name)): _*)) 361 | 362 | case TupleType(targs) => 363 | TypeRef(QualifiedName.Tuple(targs.length), targs map typeToScala) 364 | 365 | case RepeatedType(underlying) => 366 | TypeRef(Name.REPEATED, List(typeToScala(underlying))) 367 | 368 | case IndexedQueryType(_) => 369 | TypeRef.String 370 | 371 | case PolymorphicThisType => 372 | TypeRef.This 373 | 374 | case _ => 375 | // ??? 376 | TypeRef.Any 377 | } 378 | } 379 | 380 | private def coreTypeToScala(tpe: CoreType, 381 | anyAsDynamic: Boolean = false): TypeRef = { 382 | 383 | tpe.name match { 384 | case "any" => if (anyAsDynamic) TypeRef.Dynamic else TypeRef.Any 385 | case "dynamic" => TypeRef.Dynamic 386 | case "void" => TypeRef.Unit 387 | case "number" => TypeRef.Double 388 | case "bool" => TypeRef.Boolean 389 | case "boolean" => TypeRef.Boolean 390 | case "string" => TypeRef.String 391 | case "null" => TypeRef.Null 392 | case "undefined" => TypeRef.Unit 393 | case "never" => TypeRef.Nothing 394 | } 395 | } 396 | } 397 | 398 | object Importer { 399 | private val AnyType = TypeRefTree(CoreType("any")) 400 | private val DynamicType = TypeRefTree(CoreType("dynamic")) 401 | 402 | private implicit class OptType(val optType: Option[TypeTree]) extends AnyVal { 403 | @inline def orAny: TypeTree = optType.getOrElse(AnyType) 404 | @inline def orDynamic: TypeTree = optType.getOrElse(DynamicType) 405 | } 406 | 407 | private object TypeOrAny { 408 | @inline def unapply(optType: Option[TypeTree]) = Some(optType.orAny) 409 | } 410 | 411 | private object IdentName { 412 | @inline def unapply(ident: Ident) = 413 | Some(Name(ident.name)) 414 | } 415 | 416 | private object TypeNameName { 417 | @inline def apply(typeName: Name) = 418 | TypeName(typeName.name) 419 | @inline def unapply(typeName: TypeName) = 420 | Some(Name(typeName.name)) 421 | } 422 | 423 | private object PropertyNameName { 424 | @inline def unapply(propName: PropertyName) = 425 | Some(Name(propName.name)) 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/Main.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter 7 | 8 | import java.io.{ Console => _, Reader => _, _ } 9 | 10 | import Trees._ 11 | 12 | import scala.util.parsing.input._ 13 | import parser.TSDefParser 14 | 15 | /** Entry point for the TypeScript importer of Scala.js */ 16 | object Main { 17 | def main(args: Array[String]) { 18 | for (config <- Config.parser.parse(args, Config())) { 19 | val outputPackage = config.packageName 20 | 21 | importTsFile(config.inputFileName, config.outputFileName, outputPackage) match { 22 | case Right(()) => 23 | () 24 | case Left(message) => 25 | Console.err.println(message) 26 | System.exit(2) 27 | } 28 | } 29 | } 30 | 31 | def importTsFile(inputFileName: String, outputFileName: String, outputPackage: String): Either[String, Unit] = { 32 | val javaReader = new BufferedReader(new FileReader(inputFileName)) 33 | try { 34 | val reader = new PagedSeqReader(PagedSeq.fromReader(javaReader)) 35 | parseDefinitions(reader).map { definitions => 36 | val output = new PrintWriter(new BufferedWriter(new FileWriter(outputFileName))) 37 | try { 38 | process(definitions, output, outputPackage) 39 | Right(()) 40 | } finally { 41 | output.close() 42 | } 43 | } 44 | } finally { 45 | javaReader.close() 46 | } 47 | } 48 | 49 | private def process(definitions: List[DeclTree], output: PrintWriter, 50 | outputPackage: String) { 51 | new Importer(output)(definitions, outputPackage) 52 | } 53 | 54 | private def parseDefinitions(reader: Reader[Char]): Either[String, List[DeclTree]] = { 55 | val parser = new TSDefParser 56 | parser.parseDefinitions(reader) match { 57 | case parser.Success(rawCode, _) => 58 | Right(rawCode) 59 | 60 | case parser.NoSuccess(msg, next) => 61 | Left( 62 | "Parse error at %s\n".format(next.pos.toString) + 63 | msg + "\n" + 64 | next.pos.longString) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/Trees.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter 7 | 8 | import scala.util.parsing.input.Positional 9 | 10 | object Trees { 11 | // Tree 12 | 13 | abstract sealed class Tree extends Positional { 14 | /*override def toString() = { 15 | val baos = new java.io.ByteArrayOutputStream() 16 | val writer = new java.io.PrintWriter(baos) 17 | val printer = new TreePrinter(writer) 18 | printer.printTree(this) 19 | writer.close() 20 | baos.toString() 21 | }*/ 22 | } 23 | 24 | sealed trait DeclTree extends Tree 25 | sealed trait TermTree extends Tree 26 | sealed trait TypeTree extends Tree 27 | sealed trait MemberTree extends Tree 28 | 29 | // Modifiers 30 | 31 | abstract sealed class Modifier 32 | 33 | object Modifier { 34 | case object Public extends Modifier 35 | case object Protected extends Modifier 36 | case object Static extends Modifier 37 | case object ReadOnly extends Modifier 38 | case object Const extends Modifier 39 | case object Abstract extends Modifier 40 | } 41 | 42 | type Modifiers = Set[Modifier] 43 | 44 | // Identifiers and properties 45 | 46 | sealed trait PropertyName extends TermTree { 47 | def name: String 48 | } 49 | 50 | object PropertyName { 51 | def apply(name: String): PropertyName = { 52 | if (Ident.isValidIdentifier(name)) Ident(name) 53 | else StringLiteral(name) 54 | } 55 | 56 | def unapply(tree: PropertyName): Some[String] = 57 | Some(tree.name) 58 | } 59 | 60 | case class Ident(name: String) extends Tree with PropertyName { 61 | Ident.requireValidIdent(name) 62 | } 63 | 64 | object Ident extends (String => Ident) { 65 | final def isValidIdentifier(name: String): Boolean = { 66 | val c = name.head 67 | (c == '$' || c == '_' || c.isUnicodeIdentifierStart) && 68 | name.tail.forall(c => c == '$' || c.isUnicodeIdentifierPart) 69 | } 70 | 71 | @inline final def requireValidIdent(name: String) { 72 | require(isValidIdentifier(name), s"${name} is not a valid identifier") 73 | } 74 | } 75 | 76 | case class QualifiedIdent(qualifier: List[Ident], name: Ident) extends Tree 77 | 78 | // Declarations 79 | 80 | case class ModuleDecl(name: PropertyName, members: List[DeclTree]) extends DeclTree 81 | 82 | case class VarDecl(name: Ident, tpe: Option[TypeTree]) extends DeclTree 83 | 84 | case class ConstDecl(name: Ident, tpe: Option[TypeTree]) extends DeclTree 85 | 86 | case class LetDecl(name: Ident, tpe: Option[TypeTree]) extends DeclTree 87 | 88 | case class FunctionDecl(name: Ident, signature: FunSignature) extends DeclTree 89 | 90 | case object ImportDecl extends DeclTree 91 | 92 | case class TopLevelExportDecl(name: Ident) extends DeclTree 93 | 94 | // Function signature 95 | 96 | case class FunSignature(tparams: List[TypeParam], params: List[FunParam], 97 | resultType: Option[TypeTree]) extends Tree 98 | 99 | case class FunParam(name: Ident, optional: Boolean, tpe: Option[TypeTree]) extends Tree 100 | 101 | // Type parameters 102 | 103 | case class TypeParam(name: TypeName, upperBound: Option[TypeTree]) extends Tree 104 | 105 | // Literals 106 | 107 | sealed trait Literal extends TermTree 108 | 109 | case class Undefined() extends Literal 110 | 111 | case class Null() extends Literal 112 | 113 | case class BooleanLiteral(value: Boolean) extends Literal 114 | 115 | sealed trait NumberLiteral extends Literal with PropertyName 116 | 117 | case class IntLiteral(value: Int) extends NumberLiteral { 118 | override def name = value.toString 119 | } 120 | 121 | case class DoubleLiteral(value: Double) extends NumberLiteral { 122 | override def name = value.toString 123 | } 124 | 125 | case class StringLiteral(value: String) extends Literal with PropertyName { 126 | override def name = value 127 | } 128 | 129 | // Type descriptions 130 | 131 | case class TypeDecl(name: TypeName, tpe: TypeTree) extends DeclTree 132 | 133 | case class EnumDecl(name: TypeName, members: List[Ident]) extends DeclTree 134 | 135 | case class ClassDecl(name: TypeName, tparams: List[TypeParam], 136 | parent: Option[TypeRef], implements: List[TypeRef], 137 | members: List[MemberTree], isAbstract: Boolean) extends DeclTree 138 | 139 | case class InterfaceDecl(name: TypeName, tparams: List[TypeParam], 140 | inheritance: List[TypeRef], members: List[MemberTree]) extends DeclTree 141 | 142 | case class TypeAliasDecl(name: TypeName, tparams: List[TypeParam], 143 | alias: TypeTree) extends DeclTree 144 | 145 | case class TypeRef(name: BaseTypeRef, tparams: List[TypeTree] = Nil) extends TypeTree 146 | 147 | sealed abstract class BaseTypeRef extends Tree 148 | 149 | case class CoreType(name: String) extends BaseTypeRef 150 | 151 | case class TypeName(name: String) extends BaseTypeRef { 152 | Ident.requireValidIdent(name) 153 | } 154 | 155 | case class QualifiedTypeName(qualifier: List[Ident], name: TypeName) extends BaseTypeRef 156 | 157 | case class ConstantType(literal: Literal) extends TypeTree 158 | 159 | case class ObjectType(members: List[MemberTree]) extends TypeTree 160 | 161 | case class FunctionType(signature: FunSignature) extends TypeTree 162 | 163 | case class UnionType(left: TypeTree, right: TypeTree) extends TypeTree 164 | 165 | case class IntersectionType(left: TypeTree, right: TypeTree) extends TypeTree 166 | 167 | case class TupleType(tparams: List[TypeTree]) extends TypeTree 168 | 169 | case class TypeQuery(expr: QualifiedIdent) extends TypeTree 170 | 171 | case class RepeatedType(underlying: TypeTree) extends TypeTree 172 | 173 | case class IndexedQueryType(underlying: TypeTree) extends TypeTree 174 | case class IndexedAccessType(objectType: TypeTree, name: TypeTree) extends TypeTree 175 | 176 | object PolymorphicThisType extends TypeTree 177 | 178 | // Type members 179 | 180 | case class CallMember(signature: FunSignature) extends MemberTree 181 | 182 | case class ConstructorMember(signature: FunSignature) extends MemberTree 183 | 184 | case class IndexMember(indexName: Ident, indexType: TypeTree, valueType: TypeTree, modifiers: Modifiers) extends MemberTree 185 | 186 | case class PropertyMember(name: PropertyName, optional: Boolean, 187 | tpe: TypeTree, modifiers: Modifiers) extends MemberTree 188 | 189 | case class FunctionMember(name: PropertyName, optional: Boolean, 190 | signature: FunSignature, modifiers: Modifiers) extends MemberTree 191 | 192 | case object PrivateMember extends MemberTree 193 | } 194 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/Utils.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter 7 | 8 | object Utils { 9 | 10 | def scalaEscape(ident: String): String = 11 | if (needsEscaping(ident)) "`" + ident + "`" 12 | else ident 13 | 14 | def needsEscaping(ident: String): Boolean = ( 15 | ident.isEmpty || 16 | (!ident.head.isUnicodeIdentifierStart && ident.head != '_') || 17 | !ident.tail.forall(_.isUnicodeIdentifierPart) || 18 | isScalaKeyword(ident) 19 | ) 20 | 21 | val isScalaKeyword: Set[String] = Set( 22 | "abstract", "case", "class", "catch", "def", "do", "else", "extends", 23 | "false", "final", "finally", "for", "forSome", "if", "implicit", 24 | "import", "lazy", "match", "new", "null", "object", "override", "package", 25 | "private", "protected", "return", "sealed", "super", "then", "this", 26 | "throw", "trait", "true", "try", "type", "val", "var", "with", "while", 27 | "yield", ".", "_", ":", "=", "=>", "<-", "<:", "<%", ">:", "#", "@") 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/parser/TSDefLexical.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter.parser 7 | 8 | import scala.util.parsing.input.CharArrayReader.EofCh 9 | import scala.util.parsing.combinator._ 10 | import scala.util.parsing.combinator.lexical._ 11 | import scala.util.parsing.combinator.token._ 12 | import collection.mutable.HashSet 13 | 14 | class TSDefLexical extends Lexical with StdTokens with ImplicitConversions { 15 | // see `token' in `Scanners' 16 | def token: Parser[Token] = ( 17 | identifier 18 | | numericLiteral 19 | | stringLiteral 20 | | EofCh ^^^ EOF 21 | | delim 22 | | failure("illegal character") 23 | ) 24 | 25 | def identifier = 26 | stringOf1(identifierStart, identifierPart) ^^ { 27 | x => if (reserved contains x) Keyword(x) else Identifier(x) 28 | } 29 | 30 | def identifierStart = 31 | elem("", isIdentifierStart) | (pseudoChar filter isIdentifierStart) 32 | def identifierPart = 33 | elem("", isIdentifierPart) | (pseudoChar filter isIdentifierPart) 34 | 35 | def numericLiteral = ( 36 | '0' ~> ( 37 | (elem('x') | 'X') ~> rep1(hexDigit) ^^ { 38 | digits => digits.foldLeft(0L)(_ * 16 + _).toString 39 | } 40 | | rep1(octalDigit) ^^ { 41 | // not standard, but I guess it could happen nevertheless 42 | digits => digits.foldLeft(0L)(_ * 8 + _).toString 43 | } 44 | ) 45 | | opt('-') ~ stringOf1(digit) ~ opt(stringOf1('.', digit)) ^^ { 46 | case sign ~ part1 ~ part2 => sign.getOrElse("") + part1 + (part2.getOrElse("")) 47 | } 48 | ) ^^ NumericLit 49 | 50 | def stringLiteral = 51 | (quoted('\"') | quoted('\'')) ^^ StringLit 52 | 53 | def quoted(quoteChar: Char) = 54 | quoteChar ~> stringOf(inQuoteChar(quoteChar)) <~ quoteChar 55 | 56 | def inQuoteChar(quoteChar: Char) = 57 | chrExcept('\\', quoteChar, EofCh) | pseudoChar 58 | 59 | def pseudoChar = '\\' ~> ( 60 | 'x' ~> hexDigit ~ hexDigit ^^ { 61 | case d1 ~ d0 => (16*d1 + d0).toChar 62 | } 63 | | 'u' ~> hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ { 64 | case d3 ~ d2 ~ d1 ~ d0 => (4096*d3 + 256*d2 + 16*d1 + d0).toChar 65 | } 66 | | elem("", _ => true) ^^ { 67 | case '0' => '\u0000' 68 | case 'b' => '\u0008' 69 | case 't' => '\u0009' 70 | case 'n' => '\u000A' 71 | case 'v' => '\u000B' 72 | case 'f' => '\u000C' 73 | case 'r' => '\u000D' 74 | case c => c // including ' " \ 75 | } 76 | ) 77 | 78 | def octalDigit = 79 | elem("octal digit", c => '0' <= c && c <= '7') ^^ (_ - '0') 80 | 81 | def hexDigit = accept("hex digit", { 82 | case c @ ('0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9') => (c - '0') 83 | case c @ ('A'|'B'|'C'|'D'|'E'|'F') => (c - 'A' + 10) 84 | case c @ ('a'|'b'|'c'|'d'|'e'|'f') => (c - 'a' + 10) 85 | }) 86 | 87 | // legal identifier chars 88 | def isIdentifierStart(c: Char): Boolean = 89 | c == '$' || c == '_' || c.isUnicodeIdentifierStart 90 | def isIdentifierPart(c: Char): Boolean = 91 | c == '$' || c.isUnicodeIdentifierPart 92 | 93 | // see `whitespace in `Scanners' 94 | override def whitespace: Parser[Any] = rep( 95 | whitespaceChar 96 | | '/' ~ '/' ~ rep(chrExcept(EofCh, '\n')) 97 | | '/' ~ '*' ~ rep(not('*' ~ '/') ~> chrExcept(EofCh)) ~ '*' ~ '/' 98 | | '/' ~ '*' ~ failure("unclosed comment") 99 | ) 100 | 101 | // utils 102 | 103 | def stringOf(p: => Parser[Char]): Parser[String] = rep(p) ^^ chars2string 104 | def stringOf1(p: => Parser[Char]): Parser[String] = rep1(p) ^^ chars2string 105 | def stringOf1(first: => Parser[Char], p: => Parser[Char]): Parser[String] = 106 | rep1(first, p) ^^ chars2string 107 | 108 | private def chars2string(chars: List[Char]) = chars mkString "" 109 | 110 | // reserved words and delimiters 111 | 112 | /** The set of reserved identifiers: these will be returned as `Keyword's */ 113 | val reserved = new HashSet[String] 114 | 115 | /** The set of delimiters (ordering does not matter) */ 116 | val delimiters = new HashSet[String] 117 | 118 | private lazy val _delim: Parser[Token] = { 119 | /* construct parser for delimiters by |'ing together the parsers for the 120 | * individual delimiters, starting with the longest one -- otherwise a 121 | * delimiter D will never be matched if there is another delimiter that is 122 | * a prefix of D 123 | */ 124 | def parseDelim(s: String): Parser[Token] = 125 | accept(s.toList) ^^ { x => Keyword(s) } 126 | 127 | val d = new Array[String](delimiters.size) 128 | delimiters.copyToArray(d, 0) 129 | scala.util.Sorting.quickSort(d) 130 | (d.toList map parseDelim).foldRight( 131 | failure("no matching delimiter"): Parser[Token])((x, y) => y | x) 132 | } 133 | 134 | protected def delim: Parser[Token] = _delim 135 | } 136 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/parser/TSDefParser.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter.parser 7 | 8 | import org.scalajs.tools.tsimporter.Trees._ 9 | 10 | import scala.util.parsing.combinator._ 11 | import scala.util.parsing.combinator.token._ 12 | import scala.util.parsing.combinator.syntactical._ 13 | import scala.util.parsing.input._ 14 | 15 | class TSDefParser extends StdTokenParsers with ImplicitConversions { 16 | 17 | type Tokens = StdTokens 18 | val lexical: TSDefLexical = new TSDefLexical 19 | 20 | lexical.reserved ++= List( 21 | // Value keywords 22 | "true", "false", 23 | 24 | // Current JavaScript keywords 25 | "break", "case", "catch", "continue", "debugger", "default", "delete", 26 | "do", "else", "finally", "for", "function", "if", "in", "instanceof", 27 | "new", "return", "switch", "this", "throw", "try", "typeof", "var", 28 | "void", "while", "with", 29 | 30 | // Future reserved keywords - some used in TypeScript 31 | "class", "const", "enum", "export", "extends", "import", "super", 32 | "readonly", 33 | 34 | // Future reserved keywords in Strict mode - some used in TypeScript 35 | "implements", "interface", "let", "package", "private", "protected", 36 | "public", "static", "yield", 37 | 38 | // Additional keywords of TypeScript 39 | "declare", "module", "type", "namespace", "keyof" 40 | ) 41 | 42 | lexical.delimiters ++= List( 43 | "{", "}", "(", ")", "[", "]", "<", ">", 44 | ".", ";", ",", "?", ":", "=", "|", "&", "*", 45 | // TypeScript-specific 46 | "...", "=>" 47 | ) 48 | 49 | def parseDefinitions(input: Reader[Char]) = 50 | phrase(ambientDeclarations)(new lexical.Scanner(input)) 51 | 52 | lazy val ambientDeclarations: Parser[List[DeclTree]] = 53 | rep(ambientDeclaration).map(_.flatMap(_.toList)) 54 | 55 | lazy val ambientDeclaration: Parser[Option[DeclTree]] = (( 56 | opt("declare") ~> opt("export") ~> moduleElementDecl1 57 | | opt("export") ~> opt("declare") ~> moduleElementDecl1 58 | ).map(Some(_)) 59 | | "export" ~> lexical.Identifier("as") ~> "namespace" ~> identifier <~ opt(";") ^^^ None 60 | | "export" ~> "default" ~> identifier <~ opt(";") ^^^ None 61 | ) 62 | 63 | lazy val ambientModuleDecl: Parser[DeclTree] = 64 | ("module" | "namespace") ~> rep1sep(propertyName, ".") ~ moduleBody ^^ { 65 | case nameParts ~ body => 66 | nameParts.init.foldRight(ModuleDecl(nameParts.last, body)) { 67 | (name, inner) => ModuleDecl(name, inner :: Nil) 68 | } 69 | } 70 | 71 | lazy val moduleBody: Parser[List[DeclTree]] = 72 | "{" ~> rep(moduleElementDecl) <~ "}" ^^ (_.flatten) 73 | 74 | lazy val topLevelExportDecl: Parser[DeclTree] = 75 | "=" ~> identifier <~ ";" ^^ TopLevelExportDecl 76 | 77 | lazy val moduleElementDecl: Parser[Option[DeclTree]] = ( 78 | "export" ~> ( 79 | moduleElementDecl1 ^^ (Some(_)) 80 | | "=" ~> identifier <~ ";" ^^^ None) 81 | | moduleElementDecl1 ^^ (Some(_)) 82 | ) 83 | 84 | lazy val moduleElementDecl1: Parser[DeclTree] = ( 85 | ambientModuleDecl | ambientVarDecl | ambientFunctionDecl 86 | | ambientEnumDecl | ambientClassDecl | ambientInterfaceDecl 87 | | ambientConstDecl | ambientLetDecl | typeAliasDecl 88 | | importDecl 89 | | topLevelExportDecl 90 | ) 91 | 92 | lazy val ambientVarDecl: Parser[DeclTree] = 93 | "var" ~> identifier ~ optTypeAnnotation <~ opt(";") ^^ VarDecl 94 | 95 | lazy val ambientLetDecl: Parser[DeclTree] = 96 | "let" ~> identifier ~ optTypeAnnotation <~ opt(";") ^^ LetDecl 97 | 98 | lazy val ambientConstDecl: Parser[DeclTree] = 99 | "const" ~> identifier ~ optTypeAnnotation <~ opt(";") ^^ ConstDecl 100 | 101 | lazy val ambientFunctionDecl: Parser[DeclTree] = 102 | "function" ~> identifier ~ functionSignature <~ opt(";") ^^ FunctionDecl 103 | 104 | lazy val ambientEnumDecl: Parser[DeclTree] = 105 | "enum" ~> typeName ~ ("{" ~> ambientEnumBody <~ "}") ^^ EnumDecl 106 | 107 | lazy val ambientEnumBody: Parser[List[Ident]] = 108 | repsep(identifier <~ opt("=" ~ (numericLit | stringLit) ), ",") <~ opt(",") 109 | 110 | lazy val ambientClassDecl: Parser[DeclTree] = 111 | (abstractModifier <~ "class") ~ typeName ~ tparams ~ classParent ~ classImplements ~ memberBlock <~ opt(";") ^^ { 112 | case am ~ tn ~ tp ~ cp ~ ci ~ mb => ClassDecl(tn, tp, cp, ci, mb, am) 113 | } 114 | 115 | lazy val ambientInterfaceDecl: Parser[DeclTree] = 116 | "interface" ~> typeName ~ tparams ~ intfInheritance ~ memberBlock <~ opt(";") ^^ InterfaceDecl 117 | 118 | lazy val typeAliasDecl: Parser[DeclTree] = 119 | "type" ~> typeName ~ tparams ~ ("=" ~> typeDesc) <~ opt(";") ^^ TypeAliasDecl 120 | 121 | lazy val importDecl: Parser[DeclTree] = 122 | "import" ~> opt( 123 | ( 124 | identifier 125 | | "{" ~ importIdentifierSeq ~ "}" 126 | | "*" ~ lexical.Identifier("as") ~ identifier 127 | ) ~ lexical.Identifier("from") 128 | ) ~ stringLiteral <~ ";" ^^^ ImportDecl 129 | 130 | lazy val importIdentifierSeq = 131 | rep1sep(identifier ~ opt(lexical.Identifier("as") ~ identifier), ",") 132 | 133 | lazy val abstractModifier = 134 | opt(lexical.Identifier("abstract")) ^^ (_.isDefined) 135 | 136 | lazy val tparams = ( 137 | "<" ~> rep1sep(typeParam, ",") <~ ">" 138 | | success(Nil) 139 | ) 140 | 141 | lazy val typeParam: Parser[TypeParam] = 142 | typeName ~ opt("extends" ~> typeDesc) <~ opt("=" ~> typeDesc) ^^ TypeParam 143 | 144 | lazy val classParent = 145 | opt("extends" ~> typeRef) 146 | 147 | lazy val classImplements = ( 148 | "implements" ~> repsep(typeRef, ",") 149 | | success(Nil) 150 | ) 151 | 152 | lazy val intfInheritance = ( 153 | "extends" ~> repsep(typeRef, ",") 154 | | success(Nil) 155 | ) 156 | 157 | lazy val functionSignature = 158 | tparams ~ ("(" ~> repsep(functionParam, ",") <~ opt(",") <~ ")") ~ optResultType ^^ FunSignature 159 | 160 | lazy val functionParam = 161 | repeatedParamMarker ~ identifier ~ optionalMarker ~ optParamType ^^ { 162 | case false ~ i ~ o ~ t => 163 | FunParam(i, o, t) 164 | case _ ~ i ~ o ~ Some(ArrayType(t)) => 165 | FunParam(i, o, Some(RepeatedType(t))) 166 | case _ ~ i ~ o ~ t => 167 | Console.err.println( 168 | s"Warning: Dropping repeated marker of param $i because its type $t is not an array type") 169 | FunParam(i, o, t) 170 | } 171 | 172 | lazy val repeatedParamMarker = 173 | opt("...") ^^ (_.isDefined) 174 | 175 | lazy val optionalMarker = 176 | opt("?") ^^ (_.isDefined) 177 | 178 | lazy val optParamType = 179 | opt(":" ~> paramType) 180 | 181 | lazy val paramType: Parser[TypeTree] = ( 182 | typeDesc 183 | | stringLiteral ^^ ConstantType 184 | | numberLiteral ^^ ConstantType 185 | | booleanLiteral ^^ ConstantType 186 | ) 187 | 188 | lazy val optResultType = 189 | opt(":" ~> resultType) 190 | 191 | lazy val resultType: Parser[TypeTree] = ( 192 | ("void" ^^^ TypeRef(CoreType("void"))) 193 | | typeDesc 194 | ) 195 | 196 | lazy val optTypeAnnotation = 197 | opt(typeAnnotation) 198 | 199 | lazy val typeAnnotation = 200 | ":" ~> typeDesc 201 | 202 | lazy val typeDesc: Parser[TypeTree] = 203 | unionTypeDesc 204 | 205 | lazy val unionTypeDesc: Parser[TypeTree] = 206 | opt("|") ~> rep1sep(intersectionTypeDesc, "|") ^^ { 207 | _.reduceLeft(UnionType) 208 | } 209 | 210 | lazy val intersectionTypeDesc: Parser[TypeTree] = 211 | rep1sep(singleTypeDesc, "&") ^^ { 212 | _.reduceLeft(IntersectionType) 213 | } 214 | 215 | lazy val singleTypeDesc: Parser[TypeTree] = 216 | baseTypeDesc ~ rep("[" ~> opt(typeDesc) <~ "]") ^^ { 217 | case base ~ arrayDims => 218 | (base /: arrayDims) { 219 | case (elem, None) => ArrayType(elem) 220 | case (elem, Some(index)) => IndexedAccessType(elem, index) 221 | } 222 | } 223 | 224 | lazy val baseTypeDesc: Parser[TypeTree] = ( 225 | typeRef 226 | | objectType 227 | | functionType 228 | | stringType 229 | | numberType 230 | | booleanType 231 | | typeQuery 232 | | tupleType 233 | | thisType 234 | | indexTypeQuery 235 | | "(" ~> typeDesc <~ ")" 236 | ) 237 | 238 | lazy val typeRef: Parser[TypeRef] = 239 | baseTypeRef ~ opt(typeArgs) ^^ { 240 | case base ~ optTargs => 241 | TypeRef(base, optTargs getOrElse Nil) 242 | } 243 | 244 | lazy val baseTypeRef: Parser[BaseTypeRef] = 245 | rep1sep("void" | ident, ".") ^^ { parts => 246 | if (parts.tail.isEmpty) typeNameToTypeRef(parts.head) 247 | else QualifiedTypeName(parts.init map Ident, TypeName(parts.last)) 248 | } 249 | 250 | lazy val typeArgs: Parser[List[TypeTree]] = 251 | "<" ~> rep1sep(typeDesc, ",") <~ ">" 252 | 253 | lazy val functionType: Parser[TypeTree] = 254 | tparams ~ ("(" ~> repsep(functionParam, ",") <~ opt(",") <~ ")") ~ ("=>" ~> resultType) ^^ { 255 | case tparams ~ params ~ resultType => 256 | FunctionType(FunSignature(tparams, params, Some(resultType))) 257 | } 258 | 259 | lazy val stringType: Parser[TypeTree] = 260 | stringLiteral ^^ ConstantType 261 | 262 | lazy val numberType: Parser[TypeTree] = 263 | numberLiteral ^^ ConstantType 264 | 265 | lazy val booleanType: Parser[TypeTree] = 266 | booleanLiteral ^^ ConstantType 267 | 268 | lazy val thisType: Parser[TypeTree] = 269 | "this" ^^^ PolymorphicThisType 270 | 271 | lazy val indexTypeQuery: Parser[TypeTree] = 272 | "keyof" ~> typeDesc ^^ IndexedQueryType 273 | 274 | lazy val typeQuery: Parser[TypeTree] = 275 | "typeof" ~> rep1sep(ident, ".") ^^ { parts => 276 | TypeQuery(QualifiedIdent(parts.init.map(Ident), Ident(parts.last))) 277 | } 278 | 279 | lazy val tupleType: Parser[TypeTree] = 280 | "[" ~> rep1sep(typeDesc, ",") <~ "]" ^^ { parts => 281 | TupleType(parts) 282 | } 283 | 284 | lazy val objectType: Parser[TypeTree] = 285 | memberBlock ^^ ObjectType 286 | 287 | lazy val memberBlock: Parser[List[MemberTree]] = 288 | "{" ~> rep(typeMember <~ opt(";" | ",")) <~ "}" 289 | 290 | lazy val typeMember: Parser[MemberTree] = 291 | callMember | constructorMember | indexMember | namedMember | privateMember 292 | 293 | lazy val callMember: Parser[MemberTree] = 294 | functionSignature ^^ CallMember 295 | 296 | lazy val constructorMember: Parser[MemberTree] = 297 | "new" ~> functionSignature ^^ ConstructorMember 298 | 299 | lazy val indexMember: Parser[MemberTree] = 300 | modifiers ~ ("[" ~> identifier ~ typeAnnotation <~ "]") ~ typeAnnotation ^^ { 301 | case mods ~ (indexName ~ indexType) ~ valueType => 302 | IndexMember(indexName, indexType, valueType, mods) 303 | } 304 | 305 | lazy val namedMember: Parser[MemberTree] = 306 | modifiers ~ propertyName ~ optionalMarker >> { 307 | case mods ~ name ~ optional => ( 308 | functionSignature ^^ (FunctionMember(name, optional, _, mods)) 309 | | typeAnnotation ^^ (PropertyMember(name, optional, _, mods)) 310 | ) 311 | } 312 | 313 | lazy val privateMember = 314 | "private" ~> opt("static") ~> propertyName ~ opt(functionSignature | typeAnnotation) ^^^ PrivateMember 315 | 316 | lazy val modifiers: Parser[Modifiers] = 317 | rep(modifier).map(_.toSet) 318 | 319 | lazy val modifier: Parser[Modifier] = ( 320 | "static" ^^^ Modifier.Static 321 | | "public" ^^^ Modifier.Public 322 | | "readonly" ^^^ Modifier.ReadOnly 323 | | "protected" ^^^ Modifier.Protected 324 | | lexical.Identifier("abstract") ^^^ Modifier.Abstract 325 | ) 326 | 327 | lazy val identifier = 328 | identifierName ^^ Ident 329 | 330 | lazy val typeName = 331 | identifierName ^^ TypeName 332 | 333 | lazy val identifierName = accept("IdentifierName", { 334 | case lexical.Identifier(chars) => chars 335 | case lexical.Keyword(chars) if chars.forall(Character.isLetter) => chars 336 | }) 337 | 338 | lazy val propertyName: Parser[PropertyName] = 339 | identifier | stringLiteral | numberLiteral 340 | 341 | lazy val stringLiteral: Parser[StringLiteral] = 342 | stringLit ^^ StringLiteral 343 | 344 | lazy val numberLiteral: Parser[NumberLiteral] = 345 | numericLit ^^ { s => 346 | val d = s.toDouble 347 | if (!s.contains(".") && d.isValidInt) { 348 | IntLiteral(d.toInt) 349 | } else { 350 | DoubleLiteral(d) 351 | } 352 | } 353 | 354 | lazy val booleanLiteral: Parser[BooleanLiteral] = ( 355 | "true" ^^^ BooleanLiteral(true) 356 | | "false" ^^^ BooleanLiteral(false) 357 | ) 358 | 359 | private val isCoreTypeName = 360 | Set("any", "void", "number", "bool", "boolean", "string", "null", "undefined", "never") 361 | 362 | def typeNameToTypeRef(name: String): BaseTypeRef = 363 | if (isCoreTypeName(name)) CoreType(name) 364 | else TypeName(name) 365 | 366 | object ArrayType { 367 | def apply(elem: TypeTree): TypeRef = 368 | TypeRef(TypeName("Array"), List(elem)) 369 | 370 | def unapply(typeRef: TypeRef): Option[TypeTree] = typeRef match { 371 | case TypeRef(TypeName("Array"), List(elem)) => Some(elem) 372 | case _ => None 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/sc/Definitions.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter.sc 7 | 8 | import scala.language.implicitConversions 9 | 10 | import scala.collection.mutable.ListBuffer 11 | 12 | import org.scalajs.tools.tsimporter.Utils 13 | import org.scalajs.tools.tsimporter.Trees.{ Modifier, Modifiers } 14 | 15 | case class Name(name: String) { 16 | override def toString() = Utils.scalaEscape(name) 17 | def capitalize = Name(name.capitalize) 18 | } 19 | 20 | object Name { 21 | val scala = Name("scala") 22 | val scalajs = Name("scalajs") 23 | val js = Name("js") 24 | val java = Name("java") 25 | val lang = Name("lang") 26 | val typedarray = Name("typedarray") 27 | 28 | val EMPTY = Name("") 29 | val CONSTRUCTOR = Name("") 30 | val REPEATED = Name("*") 31 | val SINGLETON = Name("") 32 | val THIS = Name("") 33 | val INTERSECTION = Name("") 34 | } 35 | 36 | case class QualifiedName(parts: Name*) { 37 | def isRoot = parts.isEmpty 38 | 39 | override def toString() = 40 | if (isRoot) "_root_" 41 | else parts.mkString(".") 42 | 43 | def dot(name: Name) = QualifiedName((parts :+ name):_*) 44 | def init = QualifiedName(parts.init:_*) 45 | def last = parts.last 46 | } 47 | 48 | object QualifiedName { 49 | implicit def fromName(name: Name) = QualifiedName(name) 50 | 51 | val Root = QualifiedName() 52 | val scala = Root dot Name.scala 53 | val scala_js = scala dot Name.scalajs dot Name.js 54 | val java_lang = Root dot Name.java dot Name.lang 55 | val jstypedarray = Root dot Name.js dot Name.typedarray 56 | 57 | val Array = scala_js dot Name("Array") 58 | val Dictionary = scala_js dot Name("Dictionary") 59 | val FunctionBase = scala_js dot Name("Function") 60 | val Object = scala_js dot Name("Object") 61 | val Thenable = scala_js dot Name("Thenable") 62 | val JSArray = scala_js dot Name("Array") 63 | val Float32Array = jstypedarray dot Name("Float32Array") 64 | val Float64Array = jstypedarray dot Name("Float64Array") 65 | val Int8Array = jstypedarray dot Name("Int8Array") 66 | val Int16Array = jstypedarray dot Name("Int16Array") 67 | val Int32Array = jstypedarray dot Name("Int32Array") 68 | val Uint8Array = jstypedarray dot Name("Uint8Array") 69 | val Uint16Array = jstypedarray dot Name("Uint16Array") 70 | val Uint32Array = jstypedarray dot Name("Uint32Array") 71 | val Uint8ClampedArray = jstypedarray dot Name("Uint8ClampedArray") 72 | val ArrayBuffer = jstypedarray dot Name("ArrayBuffer") 73 | val ArrayBufferView = jstypedarray dot Name("ArrayBufferView") 74 | val DataView = jstypedarray dot Name("DataView") 75 | def Function(arity: Int) = scala_js dot Name("Function"+arity) 76 | def Tuple(arity: Int) = scala_js dot Name("Tuple"+arity) 77 | val Union = scala_js dot Name("|") 78 | val Intersection = QualifiedName(Name.INTERSECTION) 79 | } 80 | 81 | class Symbol(val name: Name) { 82 | override def toString() = 83 | s"${this.getClass.getSimpleName}($name)}" 84 | } 85 | 86 | trait JSNameable extends Symbol { 87 | var jsName: Option[String] = None 88 | 89 | def protectName(): Unit = { 90 | val n = name.name 91 | if (jsName.isEmpty && (n.contains("$") || n == "apply")) 92 | jsName = Some(n) 93 | } 94 | 95 | protected def jsNameStr = 96 | jsName.fold("")(n => s"""@JSName("$n") """) 97 | } 98 | 99 | class CommentSymbol(val text: String) extends Symbol(Name("")) { 100 | override def toString() = 101 | s"/* $text */" 102 | } 103 | 104 | class ContainerSymbol(nme: Name) extends Symbol(nme) { 105 | val members = new ListBuffer[Symbol] 106 | 107 | private var _anonMemberCounter = 0 108 | def newAnonMemberName() = { 109 | _anonMemberCounter += 1 110 | "anon$" + _anonMemberCounter 111 | } 112 | 113 | def findClass(name: Name): Option[ClassSymbol] = { 114 | members.collectFirst { 115 | case sym: ClassSymbol if sym.name == name => sym 116 | } 117 | } 118 | 119 | def findModule(name: Name): Option[ModuleSymbol] = { 120 | members.collectFirst { 121 | case sym: ModuleSymbol if sym.name == name => sym 122 | } 123 | } 124 | 125 | def getClassOrCreate(name: Name): ClassSymbol = { 126 | findClass(name) getOrElse { 127 | val result = new ClassSymbol(name) 128 | members += result 129 | findModule(name) foreach { companion => 130 | result.companionModule = companion 131 | companion.companionClass = result 132 | } 133 | result 134 | } 135 | } 136 | 137 | def getModuleOrCreate(name: Name): ModuleSymbol = { 138 | findModule(name) getOrElse { 139 | val result = new ModuleSymbol(name) 140 | members += result 141 | findClass(name) foreach { companion => 142 | result.companionClass = companion 143 | companion.companionModule = result 144 | } 145 | result 146 | } 147 | } 148 | 149 | def newTypeAlias(name: Name): TypeAliasSymbol = { 150 | val result = new TypeAliasSymbol(name) 151 | members += result 152 | result 153 | } 154 | 155 | def newField(name: Name, modifiers: Modifiers): FieldSymbol = { 156 | val result = new FieldSymbol(name, modifiers) 157 | members += result 158 | result 159 | } 160 | 161 | def newMethod(name: Name, modifiers: Modifiers): MethodSymbol = { 162 | val result = new MethodSymbol(name, modifiers) 163 | members += result 164 | result 165 | } 166 | 167 | def removeIfDuplicate(sym: MethodSymbol): Unit = { 168 | val isDuplicate = members.exists(s => (s ne sym) && (s == sym)) 169 | if (isDuplicate) 170 | members.remove(members.indexWhere(_ eq sym)) 171 | } 172 | } 173 | 174 | class PackageSymbol(nme: Name) extends ContainerSymbol(nme) { 175 | override def toString() = s"package $name" 176 | 177 | def findPackage(name: Name): Option[PackageSymbol] = { 178 | members.collectFirst { 179 | case sym: PackageSymbol if sym.name == name => sym 180 | } 181 | } 182 | 183 | def getPackageOrCreate(name: Name): PackageSymbol = { 184 | findPackage(name) getOrElse { 185 | val result = new PackageSymbol(name) 186 | members += result 187 | result 188 | } 189 | } 190 | } 191 | 192 | class ClassSymbol(nme: Name) extends ContainerSymbol(nme) { 193 | val tparams = new ListBuffer[TypeParamSymbol] 194 | val parents = new ListBuffer[TypeRef] 195 | var companionModule: ModuleSymbol = _ 196 | var isTrait: Boolean = true 197 | var isSealed: Boolean = false 198 | var isAbstract: Boolean = false 199 | 200 | override def toString() = ( 201 | (if (isSealed) "sealed " else "") + 202 | (if (isAbstract) "abstract" else "") + 203 | (if (isTrait) s"trait $name" else s"class $name") + 204 | (if (tparams.isEmpty) "" else tparams.mkString("<", ", ", ">"))) 205 | } 206 | 207 | class ModuleSymbol(nme: Name) extends ContainerSymbol(nme) { 208 | var companionClass: ClassSymbol = _ 209 | var isGlobal: Boolean = true 210 | 211 | override def toString() = s"object $name" 212 | } 213 | 214 | class TypeAliasSymbol(nme: Name) extends Symbol(nme) { 215 | val tparams = new ListBuffer[TypeParamSymbol] 216 | var alias: TypeRef = TypeRef.Any 217 | 218 | override def toString() = ( 219 | (s"type $name") + 220 | (if (tparams.isEmpty) "" else tparams.mkString("<", ", ", ">"))) 221 | } 222 | 223 | class FieldSymbol(nme: Name, val modifiers: Modifiers) extends Symbol(nme) with JSNameable { 224 | var tpe: TypeRef = TypeRef.Any 225 | 226 | override def toString() = s"${jsNameStr}${if (modifiers(Modifier.ReadOnly)) "val" else "var"} $name: $tpe" 227 | } 228 | 229 | class MethodSymbol(nme: Name, val modifiers: Modifiers) extends Symbol(nme) with JSNameable { 230 | val tparams = new ListBuffer[TypeParamSymbol] 231 | val params = new ListBuffer[ParamSymbol] 232 | var resultType: TypeRef = TypeRef.Dynamic 233 | 234 | var isBracketAccess: Boolean = false 235 | 236 | override def toString() = { 237 | val bracketAccessStr = 238 | if (isBracketAccess) "@JSBracketAccess " else "" 239 | val tparamsStr = 240 | if (tparams.isEmpty) "" 241 | else tparams.mkString("[", ", ", "]") 242 | s"${jsNameStr}${bracketAccessStr}def $name$tparamsStr(${params.mkString(", ")}): $resultType" 243 | } 244 | 245 | def paramTypes = params.map(_.tpe) 246 | 247 | def needsOverride: Boolean = { 248 | def noParams = tparams.isEmpty && params.isEmpty 249 | name match { 250 | case Name("toString") => noParams // Any return type will trigger the error 251 | case Name("clone") => noParams // Any return type will trigger the error 252 | case _ => false 253 | } 254 | } 255 | 256 | override def equals(that: Any): Boolean = that match { 257 | case that: MethodSymbol => 258 | (this.name == that.name && 259 | this.tparams == that.tparams && 260 | this.paramTypes == that.paramTypes && 261 | this.resultType == that.resultType) 262 | case _ => 263 | false 264 | } 265 | } 266 | 267 | class TypeParamSymbol(nme: Name, val upperBound: Option[TypeRef]) extends Symbol(nme) { 268 | override def toString() = { 269 | nme.toString + upperBound.fold("")(bound => s" <: $bound") 270 | } 271 | 272 | override def equals(that: Any): Boolean = that match { 273 | case that: TypeParamSymbol => 274 | (this.name == that.name && 275 | this.upperBound == that.upperBound) 276 | case _ => 277 | false 278 | } 279 | } 280 | 281 | class ParamSymbol(nme: Name) extends Symbol(nme) { 282 | def this(nme: Name, tpe: TypeRef) = { 283 | this(nme) 284 | this.tpe = tpe 285 | } 286 | 287 | var optional: Boolean = false 288 | var tpe: TypeRef = TypeRef.Any 289 | 290 | override def toString() = 291 | s"$name: $tpe" + (if (optional) " = _" else "") 292 | 293 | override def equals(that: Any): Boolean = that match { 294 | case that: ParamSymbol => 295 | (this.name == that.name && 296 | this.tpe == that.tpe) 297 | case _ => 298 | false 299 | } 300 | } 301 | 302 | sealed trait TypeRefOrWildcard 303 | 304 | case class Wildcard(upperBound: Option[TypeRefOrWildcard]) extends TypeRefOrWildcard { 305 | override def toString() = { 306 | "_" + upperBound.fold("")(bound => s" <: $bound") 307 | } 308 | } 309 | 310 | case class TypeRef(typeName: QualifiedName, targs: List[TypeRefOrWildcard] = Nil) extends TypeRefOrWildcard { 311 | override def toString() = { 312 | if (targs.isEmpty) 313 | typeName.toString 314 | else 315 | s"$typeName[${targs.mkString(", ")}]" 316 | } 317 | } 318 | 319 | object TypeRef { 320 | import QualifiedName.{ scala, scala_js, java_lang } 321 | 322 | val ScalaAny = TypeRef(scala dot Name("Any")) 323 | 324 | val Any = TypeRef(scala_js dot Name("Any")) 325 | val Dynamic = TypeRef(scala_js dot Name("Dynamic")) 326 | val Double = TypeRef(scala dot Name("Double")) 327 | val Int = TypeRef(scala dot Name("Int")) 328 | val Boolean = TypeRef(scala dot Name("Boolean")) 329 | val String = TypeRef(java_lang dot Name("String")) 330 | val Object = TypeRef(scala_js dot Name("Object")) 331 | val Function = TypeRef(scala_js dot Name("Function")) 332 | val Unit = TypeRef(scala dot Name("Unit")) 333 | val Null = TypeRef(scala dot Name("Null")) 334 | val Nothing = TypeRef(scala dot Name("Nothing")) 335 | val This = Singleton(QualifiedName(Name.THIS)) 336 | 337 | object Union { 338 | def apply(types: List[TypeRef]): TypeRef = 339 | TypeRef(QualifiedName.Union, types) 340 | 341 | def unapply(typeRef: TypeRef): Option[List[TypeRefOrWildcard]] = typeRef match { 342 | case TypeRef(QualifiedName.Union, types) => 343 | Some(types) 344 | 345 | case _ => None 346 | } 347 | } 348 | 349 | object Intersection { 350 | def apply(types: List[TypeRef]): TypeRef = 351 | TypeRef(QualifiedName.Intersection, types) 352 | 353 | def unapply(typeRef: TypeRef): Option[List[TypeRefOrWildcard]] = typeRef match { 354 | case TypeRef(QualifiedName.Intersection, types) => 355 | Some(types) 356 | 357 | case _ => None 358 | } 359 | } 360 | 361 | object Singleton { 362 | def apply(underlying: QualifiedName): TypeRef = 363 | TypeRef(QualifiedName(Name.SINGLETON), List(TypeRef(underlying))) 364 | 365 | def unapply(typeRef: TypeRef): Option[QualifiedName] = typeRef match { 366 | case TypeRef(QualifiedName(Name.SINGLETON), List(TypeRef(underlying, Nil))) => 367 | Some(underlying) 368 | 369 | case _ => None 370 | } 371 | } 372 | 373 | object Repeated { 374 | def apply(underlying: TypeRef): TypeRef = 375 | TypeRef(QualifiedName(Name.REPEATED), List(underlying)) 376 | 377 | def unapply(typeRef: TypeRef) = typeRef match { 378 | case TypeRef(QualifiedName(Name.REPEATED), List(underlying)) => 379 | Some(underlying) 380 | 381 | case _ => None 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/main/scala/org/scalajs/tools/tsimporter/sc/Printer.scala: -------------------------------------------------------------------------------- 1 | /* TypeScript importer for Scala.js 2 | * Copyright 2013-2014 LAMP/EPFL 3 | * @author Sébastien Doeraene 4 | */ 5 | 6 | package org.scalajs.tools.tsimporter.sc 7 | 8 | import java.io.PrintWriter 9 | import org.scalajs.tools.tsimporter.Trees.Modifier 10 | 11 | class Printer(private val output: PrintWriter, outputPackage: String) { 12 | import Printer._ 13 | 14 | private implicit val self = this 15 | 16 | private var currentJSNamespace = "" 17 | 18 | def printSymbol(sym: Symbol) { 19 | val name = sym.name 20 | sym match { 21 | case comment: CommentSymbol => 22 | pln"/* ${comment.text} */" 23 | 24 | case sym: PackageSymbol => 25 | val isRootPackage = name == Name.EMPTY 26 | 27 | val parentPackage :+ thisPackage = 28 | if (isRootPackage) outputPackage.split("\\.").toList.map(Name(_)) 29 | else List(name) 30 | 31 | if (!parentPackage.isEmpty) { 32 | pln"package ${parentPackage.mkString(".")}" 33 | } 34 | 35 | if (isRootPackage) { 36 | pln""; 37 | pln"import scala.scalajs.js" 38 | pln"import js.annotation._" 39 | pln"import js.|" 40 | } 41 | 42 | val oldJSNamespace = currentJSNamespace 43 | if (!isRootPackage) 44 | currentJSNamespace += name.name + "." 45 | 46 | if (!sym.members.isEmpty) { 47 | val (topLevels, packageObjectMembers) = 48 | sym.members.partition(canBeTopLevel) 49 | 50 | pln""; 51 | pln"package $thisPackage {" 52 | 53 | for (sym <- topLevels) 54 | printSymbol(sym) 55 | 56 | if (!packageObjectMembers.isEmpty) { 57 | val packageObjectName = 58 | Name(thisPackage.name.head.toUpper + thisPackage.name.tail) 59 | 60 | pln""; 61 | if (currentJSNamespace.isEmpty) { 62 | pln"@js.native" 63 | pln"@JSGlobalScope" 64 | pln"object $packageObjectName extends js.Object {" 65 | } else { 66 | val jsName = currentJSNamespace.init 67 | pln"@js.native" 68 | pln"""@JSGlobal("$jsName")""" 69 | pln"object $packageObjectName extends js.Object {" 70 | } 71 | for (sym <- packageObjectMembers) 72 | printSymbol(sym) 73 | pln"}" 74 | } 75 | 76 | pln""; 77 | pln"}" 78 | } 79 | 80 | currentJSNamespace = oldJSNamespace 81 | 82 | case sym: ClassSymbol => 83 | val sealedKw = if (sym.isSealed) "sealed " else "" 84 | val abstractKw = if (sym.isAbstract) "abstract " else "" 85 | val kw = if (sym.isTrait) "trait" else "class" 86 | val constructorStr = 87 | if (sym.isTrait) "" 88 | else if (sym.members.exists(isParameterlessConstructor)) "" 89 | else " protected ()" 90 | val parents = 91 | if (sym.parents.isEmpty) List(TypeRef.Object) 92 | else sym.parents.toList 93 | 94 | pln""; 95 | pln"@js.native" 96 | if (!sym.isTrait) { 97 | if (currentJSNamespace.isEmpty) 98 | pln"@JSGlobal" 99 | else 100 | pln"""@JSGlobal("$currentJSNamespace${name.name}")""" 101 | } 102 | p"$sealedKw$abstractKw$kw $name" 103 | if (!sym.tparams.isEmpty) 104 | p"[${sym.tparams}]" 105 | 106 | { 107 | implicit val withSep = ListElemSeparator.WithKeyword 108 | pln"$constructorStr extends $parents {" 109 | } 110 | 111 | printMemberDecls(sym) 112 | pln"}" 113 | 114 | case sym: ModuleSymbol => 115 | pln""; 116 | if (sym.isGlobal) { 117 | pln"@js.native" 118 | if (currentJSNamespace.isEmpty) 119 | pln"@JSGlobal" 120 | else 121 | pln"""@JSGlobal("$currentJSNamespace${name.name}")""" 122 | pln"object $name extends js.Object {" 123 | } else { 124 | pln"object $name {" 125 | } 126 | printMemberDecls(sym) 127 | pln"}" 128 | 129 | case sym: TypeAliasSymbol => 130 | p" type $name" 131 | if (!sym.tparams.isEmpty) 132 | p"[${sym.tparams}]" 133 | pln" = ${sym.alias}" 134 | 135 | case sym: FieldSymbol => 136 | sym.jsName foreach { jsName => 137 | pln""" @JSName("$jsName")""" 138 | } 139 | val access = 140 | if (sym.modifiers(Modifier.Protected)) "protected " 141 | else "" 142 | val decl = 143 | if (sym.modifiers(Modifier.Const)) "val" 144 | else if (sym.modifiers(Modifier.ReadOnly)) "def" 145 | else "var" 146 | p" $access$decl $name: ${sym.tpe}" 147 | if (!sym.modifiers(Modifier.Abstract)) 148 | p" = js.native" 149 | pln"" 150 | 151 | case sym: MethodSymbol => 152 | val params = sym.params 153 | 154 | if (name == Name.CONSTRUCTOR) { 155 | if (!params.isEmpty) 156 | pln" def this($params) = this()" 157 | } else { 158 | sym.jsName foreach { jsName => 159 | pln""" @JSName("$jsName")""" 160 | } 161 | if (sym.isBracketAccess) 162 | pln""" @JSBracketAccess""" 163 | val modifiers = 164 | if (sym.needsOverride) "override " else "" 165 | p" ${modifiers}def $name" 166 | if (!sym.tparams.isEmpty) 167 | p"[${sym.tparams}]" 168 | p"($params): ${sym.resultType}" 169 | if (!sym.modifiers(Modifier.Abstract)) 170 | p" = js.native" 171 | pln"" 172 | } 173 | 174 | case sym: ParamSymbol => 175 | p"$name: ${sym.tpe}${if (sym.optional) " = ???" else ""}" 176 | 177 | case sym: TypeParamSymbol => 178 | p"$name" 179 | sym.upperBound.foreach(bound => p" <: $bound") 180 | } 181 | } 182 | 183 | private def printMemberDecls(owner: ContainerSymbol) { 184 | val (constructors, others) = 185 | owner.members.toList.partition(_.name == Name.CONSTRUCTOR) 186 | for (sym <- constructors ++ others) 187 | printSymbol(sym) 188 | } 189 | 190 | private def canBeTopLevel(sym: Symbol): Boolean = 191 | sym.isInstanceOf[ContainerSymbol] 192 | 193 | private def isParameterlessConstructor(sym: Symbol): Boolean = { 194 | sym match { 195 | case sym: MethodSymbol => 196 | sym.name == Name.CONSTRUCTOR && sym.params.isEmpty 197 | case _ => 198 | false 199 | } 200 | } 201 | 202 | def printTypeRef(tpe: TypeRef) { 203 | tpe match { 204 | case TypeRef(typeName, Nil) => 205 | p"$typeName" 206 | 207 | case TypeRef.Union(types) => 208 | implicit val withPipe = ListElemSeparator.Pipe 209 | p"$types" 210 | 211 | case TypeRef.Intersection(types) => 212 | implicit val withWith = ListElemSeparator.WithKeyword 213 | p"$types" 214 | 215 | case TypeRef.This => 216 | p"this.type" 217 | 218 | case TypeRef.Singleton(termRef) => 219 | p"$termRef.type" 220 | 221 | case TypeRef.Repeated(underlying) => 222 | p"$underlying*" 223 | 224 | case TypeRef(typeName, targs) => 225 | p"$typeName[$targs]" 226 | } 227 | } 228 | 229 | def printWildcard(wc: Wildcard): Unit = { 230 | wc match { 231 | case Wildcard(None) => 232 | p"_" 233 | 234 | case Wildcard(Some(typeRefOrWildcard)) => 235 | p"_ <: $typeRefOrWildcard" 236 | } 237 | } 238 | 239 | private def print(x: Any) { 240 | x match { 241 | case x: Symbol => printSymbol(x) 242 | case x: TypeRef => printTypeRef(x) 243 | case x: Wildcard => printWildcard(x) 244 | case QualifiedName(Name.scala, Name.scalajs, Name.js, name) => 245 | output.print("js.") 246 | output.print(name) 247 | case QualifiedName(Name.scala, name) => output.print(name) 248 | case QualifiedName(Name.java, Name.lang, name) => output.print(name) 249 | case _ => output.print(x) 250 | } 251 | } 252 | } 253 | 254 | object Printer { 255 | private class ListElemSeparator(val s: String) extends AnyVal 256 | 257 | private object ListElemSeparator { 258 | val Comma = new ListElemSeparator(", ") 259 | val Pipe = new ListElemSeparator(" | ") 260 | val WithKeyword = new ListElemSeparator(" with ") 261 | } 262 | 263 | private implicit class OutputHelper(val sc: StringContext) extends AnyVal { 264 | def p(args: Any*)(implicit printer: Printer, 265 | sep: ListElemSeparator = ListElemSeparator.Comma) { 266 | val strings = sc.parts.iterator 267 | val expressions = args.iterator 268 | 269 | val output = printer.output 270 | output.print(strings.next()) 271 | while (strings.hasNext) { 272 | expressions.next() match { 273 | case seq: Seq[_] => 274 | val iter = seq.iterator 275 | if (iter.hasNext) { 276 | printer.print(iter.next()) 277 | while (iter.hasNext) { 278 | output.print(sep.s) 279 | printer.print(iter.next()) 280 | } 281 | } 282 | 283 | case expr => 284 | printer.print(expr) 285 | } 286 | output.print(strings.next()) 287 | } 288 | } 289 | 290 | def pln(args: Any*)(implicit printer: Printer, 291 | sep: ListElemSeparator = ListElemSeparator.Comma) { 292 | p(args:_*) 293 | printer.output.println() 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /src/test/scala/org/scalajs/tools/tsimporter/ImporterSpec.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.tools.tsimporter 2 | 3 | import java.io.{ File, FilenameFilter } 4 | import org.scalatest.FunSpec 5 | import scala.io.Source 6 | 7 | class ImporterSpec extends FunSpec { 8 | describe("Main.main") { 9 | val inputDirectory = new File("samples") 10 | 11 | val outputDir = new File("target/tsimporter-test") 12 | Option(outputDir.listFiles()).foreach(_.foreach(_.delete())) 13 | outputDir.mkdirs() 14 | 15 | def contentOf(file: File) = 16 | Source.fromFile(file).getLines.mkString("\n") 17 | 18 | for (input <- inputDirectory.listFiles() if input.getName.endsWith(".ts")) { 19 | it(s"should import ${input.getName}") { 20 | val expected = new File(inputDirectory, input.getName + ".scala") 21 | val output = new File(outputDir, input.getName + ".scala") 22 | 23 | assert(Right(()) == Main.importTsFile(input.getAbsolutePath, output.getAbsolutePath, input.getName.takeWhile(_ != '.'))) 24 | 25 | assert(output.exists()) 26 | assert(contentOf(output) == contentOf(expected)) 27 | } 28 | } 29 | } 30 | } 31 | --------------------------------------------------------------------------------