Javascript to Scala.js converter.
This tools makes using JS examples from Web in Scala.js easier, with some effort it can assist in porting complete JS projects.
Live version - beta version, use at your own risk.
Current version understands almost all common Javascript code and emits corresponding Scala counterparts. Most Harmony (ES 2015) features are already supported:
let
/const
scoped variables- arrow functions
- classes
Many Typescript constructs are also accepted:
- variable and parameter type declarations
enum
,namespaces
,interfaces
Unsupported constructs (like break
and continue
) are emitted as they are, with a comment. Some JS or Typescript keywords
or constructs are accepted as input, but ignored (eg. async
)
Argument types for functions are read from JSDoc @param
comments. The tools also tries to infer types of arguments and
variables.
Besides of converting JS syntax to Scala the tools performs severals transformations on the AST:
- increment / decrement unary operator handling
- join variable initialization with declaration
- remove Immediately-invoked function expression
- class detection
- infer types for many symbols (variables, functions and arguments)
- remove trailing
return
statements - detect
val
variables - many other transformation converting JS constructs into more natural Scala ones
The converter detects some common ways how JS objects are written using prototypes and converts them to Scala classes. When not known otherwise, class types are derived from members used.
Types can be read from d.ts files. To do so, the file must be referenced in the settings file as:
var ScalaFromJS_settings = {
types: "input.d.ts"
};
Similar to js files, d.ts files can import or export other d.ts files.
Number, string and boolean types are detected, class types are inferred and guessed.
Multiple files may be converted, referencing each other with export
/ import
statements.
When an import is preceded with a comment containing @example
, it is considered as an usage example, and the
corresponding file is read, but not output. This can be used to help type inference by providing examples of how
functions are used.
Source file may contain a variable called ScalaFromJS_settings
. This variable may configure the conversion. Currently
it is possible to define following transformation rules:
var ScalaFromJS_settings = {
members: [
{
cls: ".*",
name: "is(.*)",
operation: "instanceof"
},{
cls: ".*",
name: "name",
operation: "make-property"
},{
cls: ".*",
name: "exotic.*",
operation: "delete"
},
]
};
Calls matching the name pattern, where the first matching group matches the class name, are converted to "isInstanceOf[]".
Definitions of the member functions or values matching the rule are deleted (usages of the member are left intact)
Value member matching the rule are replaced with a property. Note: this transformation may change code semantics, as the property value is evaluated when the property is accessed. If expression defining the property depends on any mutable values, the result may be different.
Scala port of Esprima is used as a JS parser . The port was assisted by this tool. UglifyJS was used previously, running on Node.js. If you are
interested in that version, see tag uglify-scala.js
.
java -jar ScalaFromJS.jar temp/esprima/esprima-convert.ts temp/esprima/scala/esprima-convert.scala
The conversion works in several phases:
- js files are read, imports and exports resolved by concatenation, creating one large source file
- the resulting file is parsed to create AST
- AST is transformed by many transformation steps described in variable
transforms
incom.github.opengrabeso.scalafromjs.Transform.apply
- import transformation steps:
- preprocess regex substitution:
com.github.opengrabeso.scalafromjs.ConvertProject.RegexPreprocessRule
- prototype to class conversion:
com.github.opengrabeso.scalafromjs.transform.classes.transforms
- type imports:
com.github.opengrabeso.scalafromjs.transform.TypesRule
- type inference:
com.github.opengrabeso.scalafromjs.transform.InferTypes.multipass
- postprocess regex substitution
com.github.opengrabeso.scalafromjs.ConvertProject.RegexPostprocessRule
- preprocess regex substitution:
- import transformation steps:
There are two operations usually used to implement AST transformations: walk
and transform
. Both of them exist in a
variant handling a ScopeContext
, which is needed for proper variable scope resolution.
One usually uses walk
to gather any necessary information and transform
to perform any work needed. There are two
flavors of transform
: transformBefore
and transformAfter
, which differ in a traversal order: transformAfter
traverses all children first, while transformBefore
is given a descend
function and can determine the traversal order
as required.
Type information is stored outside of the AST, in NodeExtended.types
. It is first constructed from TypeScript type
information (when available) and then type inference fills the rest.
Each symbol is made unique by pairing with its scope ID (symbols.SymId
). The scope ID is the source code range of the
corresponding block (see symbols.ScopeContext#getNodeId
). The processing can be see in enterScope
.
There are some important cases:
- scope ID of the class is the scope ID of its body
- scope ID of the function is its corresponding Node.FunctionExpression or other
AnyFunEx
matching node, not of the function body, so that parameters have the same scope as the body - scope ID of the for loop is the for statement, not the loop body, so that loop variable has the same scope as the body
- scope ID of Node.MethodDefinition is ID of its value (most often
Node.FunctionExpression
)
Classed are represented using ClassDeclaration
. Member functions are listed in the class body as MethodDefinition
s.
There is a special method called "inline^" which corresponds to the Scala primary constructor.
Class member variables are represented using MethodDefinition
(see newValue
) with kind = "value"
.
They are also listed using VariableDeclaration
/ VariableDeclarator
in the "inline^" method.