Commit 52b1d260 authored by Loïc Correnson's avatar Loïc Correnson

Merge branch 'feature/dome/dome-ts' into 'master'

[dome] port Dome to TS

Closes #938 and #919

See merge request frama-c/frama-c!2760
parents 8b8c483f 3fb27b9f
......@@ -17,7 +17,7 @@ module.exports = {
},
settings: {
// Electron is in devDependencies because of its special build system
"import/core-modules": [ "electron" ]
"import/core-modules": [ 'electron', 'react-hot-loader' ]
},
rules: {
// Do not enforce a displayName
......@@ -102,7 +102,7 @@ module.exports = {
"default-case": "off",
"consistent-return": "off",
// Allow modify properties of object passed in parameter
"no-param-reassign": "error", //[ "error", { "props": false } ],
"no-param-reassign": [ "error", { "props": false } ],
// Disallow the use of var in favor of let and const
"no-var": "error",
// Do not favor default import
......
......@@ -5,6 +5,7 @@ DOME_ARGS=--command $$(dirname $$0)/../../bin/frama-c
DOME_DEV=-server-debug 1
DOME_CLI=./bin/frama-c-gui
DOME_API=./src/frama-c
DOME_CUSTOM_ENTRIES= yes
COPYRIGHT=CEA LIST / LSL
# --------------------------------------------------------------------------
......@@ -21,10 +22,10 @@ lint: dome-pkg dome-templ
yarn run typecheck
yarn run lint
fixlint: dome-pkg dome-templ
@echo "[Ivette] running typechecker & linter (fix mode)"
tsc: dome-pkg dome-templ
@echo "[Ivette] running typechecker & linter (with cache & fix mode)"
yarn run typecheck
yarn run lint --fix
yarn run lint --fix --cache --cache-location .eslint-cache
# --------------------------------------------------------------------------
# --- Frama-C API
......
......@@ -3,7 +3,7 @@
/**
Ast Services
@packageDocumentation
@module api/kernel/ast
@module frama-c/api/kernel/ast
*/
//@ts-ignore
......@@ -16,21 +16,21 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byTag } from 'api/kernel/data';
import { byTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { byText } from 'api/kernel/data';
import { byText } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTag } from 'api/kernel/data';
import { jTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTagSafe } from 'api/kernel/data';
import { jTagSafe } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jText } from 'api/kernel/data';
import { jText } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTextSafe } from 'api/kernel/data';
import { jTextSafe } from 'frama-c/api/kernel/data';
//@ts-ignore
import { tag } from 'api/kernel/data';
import { tag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { text } from 'api/kernel/data';
import { text } from 'frama-c/api/kernel/data';
const compute_internal: Server.ExecRequest<null,null> = {
kind: Server.RqKind.EXEC,
......
......@@ -3,7 +3,7 @@
/**
Informations
@packageDocumentation
@module api/kernel/data
@module frama-c/api/kernel/data
*/
//@ts-ignore
......
......@@ -3,7 +3,7 @@
/**
Project Management
@packageDocumentation
@module api/kernel/project
@module frama-c/api/kernel/project
*/
//@ts-ignore
......
......@@ -3,7 +3,7 @@
/**
Property Services
@packageDocumentation
@module api/kernel/properties
@module frama-c/api/kernel/properties
*/
//@ts-ignore
......@@ -16,21 +16,21 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byTag } from 'api/kernel/data';
import { byTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTag } from 'api/kernel/data';
import { jTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTagSafe } from 'api/kernel/data';
import { jTagSafe } from 'frama-c/api/kernel/data';
//@ts-ignore
import { tag } from 'api/kernel/data';
import { tag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { bySource } from 'api/kernel/services';
import { bySource } from 'frama-c/api/kernel/services';
//@ts-ignore
import { jSource } from 'api/kernel/services';
import { jSource } from 'frama-c/api/kernel/services';
//@ts-ignore
import { jSourceSafe } from 'api/kernel/services';
import { jSourceSafe } from 'frama-c/api/kernel/services';
//@ts-ignore
import { source } from 'api/kernel/services';
import { source } from 'frama-c/api/kernel/services';
/** Property Kinds */
export enum propKind {
......
......@@ -3,7 +3,7 @@
/**
Kernel Services
@packageDocumentation
@module api/kernel/services
@module frama-c/api/kernel/services
*/
//@ts-ignore
......@@ -16,13 +16,13 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byTag } from 'api/kernel/data';
import { byTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTag } from 'api/kernel/data';
import { jTag } from 'frama-c/api/kernel/data';
//@ts-ignore
import { jTagSafe } from 'api/kernel/data';
import { jTagSafe } from 'frama-c/api/kernel/data';
//@ts-ignore
import { tag } from 'api/kernel/data';
import { tag } from 'frama-c/api/kernel/data';
const getConfig_internal: Server.GetRequest<
null,
......
......@@ -3,7 +3,7 @@
/**
Dive Services
@packageDocumentation
@module api/plugins/dive
@module frama-c/api/plugins/dive
*/
//@ts-ignore
......@@ -16,21 +16,21 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byLocation } from 'api/kernel/ast';
import { byLocation } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { byMarker } from 'api/kernel/ast';
import { byMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jLocation } from 'api/kernel/ast';
import { jLocation } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jLocationSafe } from 'api/kernel/ast';
import { jLocationSafe } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarker } from 'api/kernel/ast';
import { jMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarkerSafe } from 'api/kernel/ast';
import { jMarkerSafe } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { location } from 'api/kernel/ast';
import { location } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { marker } from 'api/kernel/ast';
import { marker } from 'frama-c/api/kernel/ast';
/** Parametrization of the exploration range. */
export interface range {
......
......@@ -3,7 +3,7 @@
/**
Eva General Services
@packageDocumentation
@module api/plugins/eva/general
@module frama-c/api/plugins/eva/general
*/
//@ts-ignore
......@@ -16,13 +16,13 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byMarker } from 'api/kernel/ast';
import { byMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarker } from 'api/kernel/ast';
import { jMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarkerSafe } from 'api/kernel/ast';
import { jMarkerSafe } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { marker } from 'api/kernel/ast';
import { marker } from 'frama-c/api/kernel/ast';
const getCallers_internal: Server.GetRequest<
Json.key<'#fct'>,
......
......@@ -3,7 +3,7 @@
/**
Eva Values
@packageDocumentation
@module api/plugins/eva/values
@module frama-c/api/plugins/eva/values
*/
//@ts-ignore
......@@ -16,13 +16,13 @@ import * as Server from 'frama-c/server';
import * as State from 'frama-c/states';
//@ts-ignore
import { byMarker } from 'api/kernel/ast';
import { byMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarker } from 'api/kernel/ast';
import { jMarker } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { jMarkerSafe } from 'api/kernel/ast';
import { jMarkerSafe } from 'frama-c/api/kernel/ast';
//@ts-ignore
import { marker } from 'api/kernel/ast';
import { marker } from 'frama-c/api/kernel/ast';
/** CallStack */
export interface callstack {
......
......@@ -15,12 +15,20 @@ module TSC = Self.Action
let help = "Generate TypeScript API"
end)
module API = Self.String
(struct
let option_name = "-server-tsc-pkg"
let arg_name = "dir"
let default = "frama-c/api"
let help = Printf.sprintf "Output package (default is '%s')" default
end)
module OUT = Self.String
(struct
let option_name = "-server-tsc-out"
let arg_name = "dir"
let default = "api"
let help = "Output directory (default is './api')"
let default = "api/generated"
let help = Printf.sprintf "Output directory (default is '%s')" default
end)
module Md = Markdown
......@@ -474,12 +482,13 @@ let makeIgnore fmt msg =
let makePackage pkg name fmt =
begin
let open Pkg in
let framac = API.get () in
Format.fprintf fmt "/* --- Generated Frama-C Server API --- */@\n@\n" ;
Format.fprintf fmt "/**@\n %s@\n" pkg.p_title ;
if pkg.p_descr <> [] then
Format.fprintf fmt "@\n @[<hov 0>%a@]@\n@\n" pp_descr pkg.p_descr ;
Format.fprintf fmt " @@packageDocumentation@\n" ;
Format.fprintf fmt " @@module api/%s@\n" name ;
Format.fprintf fmt " @@module %s/%s@\n" framac name ;
Format.fprintf fmt "*/@\n@." ;
let names = Pkg.resolve ~keywords pkg in
makeIgnore fmt "import * as Json from 'dome/data/json';@\n" ;
......@@ -494,11 +503,11 @@ let makePackage pkg name fmt =
then
let pkg = Pkg.name_of_pkg ~sep:"/" id.plugin id.package in
if id.name = name then
makeIgnore fmt "import { %s } from 'api/%s';@\n"
name pkg
makeIgnore fmt "import { %s } from '%s/%s';@\n"
name framac pkg
else
makeIgnore fmt "import { %s: %s } from 'api/%s';@\n"
id.name name pkg
makeIgnore fmt "import { %s: %s } from '%s/%s';@\n"
id.name name framac pkg
) names ;
List.iter
(makeDeclaration fmt names)
......
......@@ -6,7 +6,7 @@
"author": "Loïc Correnson <loic.correnson@cea.fr>",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .ts,.tsx --format=stylish ./src",
"lint": "eslint --ext .ts,.tsx --format=compact ./src",
"typecheck": "tsc --noEmit",
"typecheck:watch": "yarn run typecheck -- --watch",
"build": "tsc"
......
......@@ -48,4 +48,24 @@
(add-to-list 'auto-mode-alist '("\\.ts$" . typescript-mode))
(add-to-list 'auto-mode-alist '("\\.tsx$" . web-mode))
;; Column mode
(safe-require fill-column-indicator
(setq-default fill-column 80)
(setq fci-rule-color "#8f8f8f")
(add-hook 'typescript-mode-hook 'fci-mode t)
(add-hook 'web-mode-hook 'fci-mode t))
;; Compilation mode
(require 'compile)
;;; TSC output
(add-to-list 'compilation-error-regexp-alist
'("^\\([a-zA-Z0-9_/.-]+.tsx?\\):\\([0-9]+\\):\\([0-9]+\\) - error" 1 2 3))
;;; ES-Lint output
(add-to-list 'compilation-error-regexp-alist
'("^\\([a-zA-Z0-9_/.-]+.tsx?\\): line \\([0-9]+\\), col \\([0-9]+\\), Error" 1 2 3))
;;; -------------------------------------------------------------------------
......@@ -85,15 +85,10 @@ your data flow.
- **Global States** are necessary to implement the unidirectional data-flow. These
data are stored in the renderer process, but outside of the view hierarchy of
**React** components actually mounted in the DOM. Hence, it remains consistent whatever
the evolution of the view. See `Dome.State` class and the associated custom **React** hooks
the evolution of the view. See `dome/state` module and the associated custom **React** hooks
to implement global states. You may also use global JavaScript variables and emit events
on your own.
- **Local States** are necessary to maintain local states associated
to views. We strongly encourage the use of the `Dome.useState()` hook for this
purpose, since it generalizes `React.useState()` with persistent window settings
(see below).
- **View Updates** to make your views listening for updates of the various data
sources, we encourage to use the **React** hooks we provide, since they
transparently make your components to re-render when required. However,
......@@ -103,19 +98,26 @@ your data flow.
`Dome.useEvent()` hooks can be used to make your components being notified by
events.
- **Window Settings** are stored in the user's home directory but remain
generally unnoticed by most users, although they are responsible for a good user
experience. They typically include the window's position and dimension,
resizable items position, fold/unfold states, presentation options, etc. Most
- **Window Settings** are stored a local file at the root of user's project, and
remains generally unnoticed by most users. They typically include the window's
position and dimension, resizable items position, fold/unfold states,
presentation options, etc. Most
**Dome** components with presentation options can be assigned a `settings` key
to make their state persistent. Contrary to Global Settings, however, they are
not shared across several windows. You may also access these data by using
`Dome.setWindowSetting()` and `Dome.getWindowSetting()`, or the **React** hook
`Dome.useWindowSetting()`.
`Settings.setWindowSetting()` and `Settings.getWindowSetting()`, or the **React** hook
`Settings.useWindowSetting()`. See also helpers `Dome.useXxxSettings()`.
It is possible, from the application main menu, to reset all the window settings to their
default values.
- **Local Storage** are stored in the same file than window settings, although
they are not automatically reset to their initial values.
This is very convenient to store persistent user data on a per-project basis.
See `Settings.xxxLocalStorage()` functions for more details.
- **Global Settings** are stored in the user's home directory and automatically
saved and load with your application; they are typically modified _via_ the
Settings Window, which is accessible from the application menubar. In **Dome**,
you access these data by using `Dome.setGlobalSetting()` and
`Dome.getGlobalSetting()`, or the **React** hook `Dome.useGlobalSetting()`.
Settings must be JSON serializable JavaScript values.
you can shall define a global settings by creating an instance of
`Settings.GlobalSettings` class and use it with
the `Settings.useGlobalSettings()` hook.
<!-- Application Design -->
This tutorial introduces how to design a **Dome** application within the
**Electron** & **React** frameworks. You shall be reasonably familiar with
**React** concepts, but no knowledge of **Electron** is required.
A desktop **Dome** application looks like a native application, but is actually
a static web page rendered in a **Chrome** web browser. This is what provides
the **Electron** framework by default. **Dome** is simply a library of **React**
components and development templates tuned together to enable professional
application development.
This tutorial provides an overview of this environment and how to design a typical
**Dome** application.
## System Architecture
Following the **Electron** framework design, your application will consists of
two different processes interacting with each others.
One is responsible for the management of the main GUI resources
(windows, menu-bar, desktop interaction, user settings, etc.)
and is named the `Main` process.
The second one will be responsible for running the
web page holding the main application window, and is named the `Renderer` process.
Both kind of process communicates through the message-passing API provided by
the **Electron** framework.
When several instances of your application are running simultaneously, each invokation
have its own window, running its owan, separate `Renderer` process. However, the
**Dome** framework automatically makes them sharing the same `Main` process.
Hence, each application instance has its own working directory and command-line arguments,
depending on how its has been launched by the user. The **Dome** framework build
both command-line and desktop entry points, depending on each target platform.
## Event Driven Design
On the renderer process side, the **React** framework induces a natural design where
_Application State_ is separated from _Application Rendering_. Moreover,
following a popular design introduced with **Redux**, application rendering tend
to be written like a pure function (or _view_) on the state, where user
actions are just state updater callbacks. Each time the state is modified, the
entire application rendering is recomputed, and **React** computes a minimal
diff to apply on the web page displayed in the main application window.
Putting everything together, its is recommended to design **Dome** application
into two different parts, both running in the `Renderer` process of each
application instance:
- **Application Internals (State)**
holding your application state and data and updating it in response to user or
system events;
- **Application Components (View)**
responsible for rendering the application main window and
binding callbacks to application services.
<img src="dataflow.png" style="float: right; width: 30em"/>
Such an architecture is typical of a _Model-View-Controller_ design, but
revisited to scale. In particular, data flow between those three different
layers shall follow a unique-direction pattern in order to avoid the
combinatorial explosion of interactions between components that is generally
observed in most Model-View-Controller designs.
Hence, data-flow shall follow one of the following routes between these
three layers, illustrated above:
- from `State` to `View` : your rendering components shall only access the
current application state and data;
- from `View` to `State` or `System` : user action callbacks shall only trigger
state update or system operation, not any other view direct modification.
- from `System` to `State` : upon completion, system services shall trigger state
updates that would in turn trigger new requests and view re-rendering.
## Data Management
To implement the recommended data-flow described above, you may use a full
featured [Redux](https://redux.js.org) platform or any other framework of your
choice. However, **Dome** provides you with many facilities for implementing
your data flow.
- **Global States** are necessary to implement the unidirectional data-flow. These
data are stored in the renderer process, but outside of the view hierarchy of
**React** components actually mounted in the DOM. Hence, it remains consistent whatever
the evolution of the view. See `Dome.State` class and the associated custom **React** hooks
to implement global states. You may also use global JavaScript variables and emit events
on your own.
- **Local States** are necessary to maintain local states associated
to views. We strongly encourage the use of the `Dome.useState()` hook for this
purpose, since it generalizes `React.useState()` with persistent window settings
(see below).
- **View Updates** to make your views listening for updates of the various data
sources, we encourage to use the **React** hooks we provide, since they
transparently make your components to re-render when required. However,
sometimes you will need to respond to special events by hand. For this purpose,
you can use **Dome** as a central event emitter, by using `Dome.emit()`,
`Dome.on()` and `Dome.off()` functions. Moreover, the `Dome.useUpdate()` and
`Dome.useEvent()` hooks can be used to make your components being notified by
events.
- **Window Settings** are stored in the user's home directory but remain
generally unnoticed by most users, although they are responsible for a good user
experience. They typically include the window's position and dimension,
resizable items position, fold/unfold states, presentation options, etc. Most
**Dome** components with presentation options can be assigned a `settings` key
to make their state persistent. Contrary to Global Settings, however, they are
not shared across several windows. You may also access these data by using
`Dome.setWindowSetting()` and `Dome.getWindowSetting()`, or the **React** hook
`Dome.useWindowSetting()`.
- **Global Settings** are stored in the user's home directory and automatically
saved and load with your application; they are typically modified _via_ the
Settings Window, which is accessible from the application menubar. In **Dome**,
you access these data by using `Dome.setGlobalSetting()` and
`Dome.getGlobalSetting()`, or the **React** hook `Dome.useGlobalSetting()`.
Settings must be JSON serializable JavaScript values.
<!-- Application Development -->
The [Quick Start](tutorial-quickstart.html) tutorial introduces how to setup a new application project,
and [Application Design](tutorial-application.html) draw a high-level view of how it works.
This tutorial goes into more details about the development environment provided by the **Dome**
framework.
## HTML & JavaScript Environment
The Web environment for designing your application is based on the following
framework:
- **Electron** [v5.0+](https://electronjs.org) runtime environment (packaging Chrome and Node)
- **Node** [v12.0+](https://nodejs.org/dist/latest-v12.x/docs/api) provided by Electron
- **Chromium** [v73.0+](https://www.chromium.org/Home) HTML rendering engine provided by Electron
You will write your application in a an environment setup with the following
JavaScript features:
- **Webpack** for packaging the application from sources
- **Babel** to parse language extensions
- **Babel Presets Env** to use ECMA modules and JavaScript strict mode
- **Babel Presets React** to use JSX react notation in all your `*.js` files
- **Babel Object Rest Spread** to use object spread syntax `{ ...props }`
- **React v16** with its latest features (including Hooks)
- **React Hot Loader** for [live-editing](tutorial-hotreload.html) your application
- **CSS Loader** to load and install `*.css` files
## Available Packages
The implementation and internals of the **Dome** framework rely on
high-quality and mature popular packages. They are automatically packed
into your dependencies by the provided template makefile. We list them here
since you may re-use them for your own purpose:
- [**Lodash**](https://lodash.com) convenient JavaScript utilities
- [**React-Virtualized**](http://bvaughn.github.io/react-virtualized)
for layout of large data set in list, tables and grids
- [**React-Draggable**](https://github.com/mzabriskie/react-draggable)
for straightforward drag-and-drop features
- [**CodeMirror**](https://codemirror.net) for rich text capabilities and edition
that scales on large document
- [**ZeroMQ**](http://zeromq.org) for inter-process and distant communications
(see also [System Services](tutorial-services.html)).
## Project Files Organization
The main directory holding your application project with **Dome** consists of
several files and directories. Each is created and/or managed by various
entities : yourself the **Developer**, the **Dome** template makefile, the
**Yarn** JavaScript package manager and the **Electron** suite of
utilities. Parts of those files are generated and updated on demand and shall or
shall _not_ be tracked in your source version control system, be it based on `git`,
`svn` or anything else.
Most files are actually created and used by **Dome**, **Electron** and **Yarn**.
However, that are _in fine_ under the responsibility of the **Developer**
since you can edit and extend them to fit your needs.
Those generated files generally contains comments to indicate how you
can extend them. You shall put those files under version control only if you edit them.
As depicted in the [Quick Start](tutorial-quickstart.html) tutorial, the only file you
need to create by yourself is the main `Makefile`, with a minimal content
limited to:
```makefile
DOME="..." ;; path to your dome installation
include $(DOME)/template/makefile
```
The following table lists the main files and directories of your project, with
the associated entity responsible for it, and whether they should be tracked by
your source version control system (VCS).
Optional files are tagged with « (Opt.) » in the corresponding `Entity` column,
and are _not_ generated by default. Similarly, files mainly used by the system
but that can still be configured by the developer are tagged with « (+Dev) ».
Files are tagged with « ✓(*) » in the `VCS` column shall be tracked only if you
actually modified them.
| File or Directory | Description | Entity | VCS |
|:------------------|:------------|:-------:|:---:|
| `./Makefile` | Drives the build system | **Developer** | ✓ |
| `./src/main/` | Sources for the main process | **Developer** | ✓ |
| `./src/main/index.js` | Main process entry point | **Electron** (+Dev) | ✓ |
| `./src/renderer/` | Sources for the renderer process | **Developer** | ✓ |
| `./src/renderer/index.js` | Renderer process entry point | **Electron** (+Dev) | ✓ |