TypeScript support for Moose (Pharo)

Posted by Christopher Fuhrman on September 15, 2023 · 13 mins read

It’s now possible to model and analyze TypeScript projects in Moose.

Thanks to excellent collaboration with summer 2023 interns Eya Bdah (ISSAT of Sousse University) and Maël Paul (ENSEIRB-MATMECA Bordeaux), several former students of MGL843 who contributed to the open-source elements of this project, and last but not least, Inria’s EVREF (formerly RMoD) group, there is a stable Famix metamodel and importer (aka parser) for TypeScript. The importer ts2famix is also an npmjs package, which means you can easily install and run it if you use npm.

Assumptions

In this post, I’ll show you how to use these tools to analyze a TypeScript project using Moose 10. We assume the reader is familiar with cloning GitHub projects and running npm commands (since we’re analyzing TypeScript), as well as using the git bash command-line tool. We tested this on Windows 10 (again using git bash), but it should work on Linux and Mac, too.

Moose needs to grok TypeScript

The Moose software needs a metamodel to understand TypeScript programs. Our project provides this metamodel at https://github.com/fuhrmanator/FamixTypeScript.

The following instructions are for people familiar with Moose and Pharo. If you need help with Pharo images, check out the Pharo Mooc.

  • Create and run a Moose 10 (stable) image.
  • Open a Playground and run this script to load the FamixTypeScript metamodel:
      Metacello new 
          githubUser: 'fuhrmanator' project: 'FamixTypeScript' commitish: 'master' path: 'src';
          baseline: 'FamixTypeScript';
          load
    

Create the Moose (Famix) model of a TypeScript project

  • Let’s clone the project https://github.com/Chuzzy/Emojiopoly.
  • Next, we’ll install ts2famix (it’s an npm command, so you need to have npm running on your machine): npm i -g ts2famix.
  • Let’s move into the Emojiopoly clone’s location, and create a Famix model of the project:
     cd path/to/Emojiopoly
     ts2famix -i tsconfig.json -o emojiopoly-model.json
    

    This will create the model emojiopoly-model.json in the same directory as the Emojiopoly clone.

Import the model into Moose

  • If you’re running Windows (maybe this works on a Mac, too?), you can drag the emojiopoly-model.json file and drop it on the running window of Moose. You’ll see a dialog allowing you to confirm the import. Drag and drop the model file

  • If you’re not able to drag the file, move the emojiopoly-model.json file generated by ts2famix to the same folder as the Moose image. The folder has a path like Documents/Pharo/images/[Moose image name]. Tip: you can find the folder of the image by right-clicking on the image in Pharo Launcher and selecting Show in folder.
  • In the Moose image, open a Playground (CTRL-O CTRL-W) and run the following script:
    'emojiopoly-model.json' asFileReference readStreamDo:
        [ :stream | model := FamixTypeScriptModel new 
          importFromJSONStream: stream. model install ].
    

    This will load the model into Moose.

  • Open the menu Moose > Models browser to verify that the model of Emojiopoly has been loaded into Moose. You should see emojiopoly-model (or whatever the name you used for the .json file) in the list of Models.

Do some analyses on the model

You can do any analysis you want on the model, but here is how to get started:

  • From the Models browser window, select emojiopoly-model from the list.
  • Click on the Inspect button (icon with glasses).
  • A new Moose Inspector window will appear.
  • Click on Card in the left-hand list.
  • You will see a sub-window a FamixTypeScriptClass (Card) on the left.
  • Click on the SourceText tab at the top of this sub-window to view the source code of the class.
  • Click on the Moose Properties tab to view the Moose properties of this class.

    Inspecting a FamixTypeScriptClass

Simple Queries

In a Playground window, execute the following code:

"Get the emojiopoly model (first in Moose panel)"
tsModel := MooseModel root first.
"Find all classes that have 100 or more lines of code"
bigClasses := tsModel allModelClasses 
    select: [ :each | 
        each numberOfLinesOfCode >= 100 ]

The result should generally include only one class, MonopolyGame`. You can select it and view its source code to verify it has more than 100 lines.

In the Navigation tab, you can also see its methods, attributes, and more.

To get a list of long methods (containing 20 lines or more) in the project, execute this script:

"Get the emojiopoly model (first in Moose panel)"
tsModel := MooseModel root first.
"Find all methods that have 20 or more lines of code"
longMethods := tsModel allMethods 
    select: [ :each | 
        each numberOfLinesOfCode >= 20 ]

To find out the class to which each method belongs, check the parentType property in the navigation.

TypeScript-specific Queries

What if you want to find all Decorators (a TypeScript-specific element) in a project? Sadly, there is not (yet) a tsModel allDecorators method for TypeScript models. The solution is to use the tsModel allMatching: FamixTypeScriptDecorator. Indeed, the allMatching: method allows you to find all elements of a specific Entity. You can see the TypeScript metamodel element names in the SVG visualization from the metamodel repository. The elements are shown as UML classes in light blue, e.g., Module, Decorator, etc. Note that you need to add the prefix FamixTypeScript to the name of the element, e.g., FamixTypeScriptModule, FamixTypeScriptDecorator, etc.

Visualizations with Roassal 3

Roassal 3 a powerful (and complex) visualization library in Pharo. We can draw inspiration from an example of a Roassal 3 visualization on GitHub to represent classes visually in a Moose model:

"The variable classes contains the classes we would like to visualize"
classes := MooseModel root first allModelClasses.
"A canvas is a container of graphical shapes"
c := RSCanvas new.
"Each class is represented as a box"
classes do: [ :aClass | c add: (RSBox new model: aClass) ].
"The width of each class indicates the number of variables defined in the class"
RSNormalizer width shapes: c shapes; from: 6; to: 20;
    normalize: #numberOfAttributes.
"Height of each class represents the number of methods"
RSNormalizer height shapes: c shapes; normalize: #numberOfMethods.
"A class color goes from gray to red, indicating the number of lines of code"
RSNormalizer color shapes: c shapes;
    from: Color gray; to: Color red; normalize: #numberOfLinesOfCode.
"Vertical lines indicate the inheritance relationship"
RSLineBuilder orthoVertical
    canvas: c; withVerticalAttachPoint; color: Color lightGray;
    connectFrom: #superclass.
"Use a tree layout to adequately locate the classes"
RSTreeLayout on: c nodes.
"We make all the classes draggable and with a contextual popup window"
c nodes @ RSDraggable @ RSPopup.
"The whole visualization is zoomable, draggable, and shapes may be searched in it"
c @ RSCanvasController.

This visualization represents classes as rectangles. Each rectangle has three dimensions:

  • The color of each rectangle represents the number of lines of code. Gray signifies a relatively low number of lines of code, while red indicates a relatively high number of lines of code. The color variation is handled by the RSNormalizer class.
  • The height of each rectangle represents the number of methods.
  • The width of each rectangle represents the number of attributes.

Tip: The visualization obtains data from each Moose element through properties, for example, #numberOfAttributes, #numberOfMethods, and #numberOfLinesOfCode. These are methods (accessors) of Famix elements, such as FamixTypeScriptClass, which provide the values. You can find other properties in the Moose Properties tab of these elements.

Roassal visualization of Emojiopoly

With the Emojiopoly project, you can see that the MonopolyGame class has many methods (its height) and also a significant amount of code (its red color).

Pharo Tip: The Pharo syntax with the hashtag used on line 9, normalize: #numberOfAttributes, is a shorthand syntax for a longer block expression: normalize: [ :element | element numberOfAttributes ]. Sometimes, you may want to perform a calculation on the value used in the visualization, for example, combining it with another attribute like the number of receivingInvocations. In that case, you would do normalize: [ :element | element numberOfAttributes + element receivingInvocations size ].

The layout (RSTreeLayout) also helps visualize the class hierarchy in terms of inheritance. However, the Emojiopoly project, because TypeScript allows type composition, does not use inheritance. Therefore, no hierarchy is visible in the visualization.

Check out Moose 10’s new visualization tools

The latest version of Moose has some built-in features that make it fun to explore projects. Check out Gabriel Ullmann’s excellent video on using Moose 10. Please note that his demo is done with a Java project (model), but Moose analyses work almost the same way with a TypeScript project (once you’ve loaded and imported a model of a TypeScript project).

What’s next?

The FamixTypeScriptImporter (ts2famix) as well as the FamixTypeScript metamodel are still in development, so don’t forget to check for updates. Please report any issues you find on the respective GitHub repositories.

Let us know if you are using (or want to use) FamixTypeScript to model your TypeScript projects!