You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Java 20 or higher installed and configured in your `PATH`
8
+
- Maven 3.6 or higher for building the language server
9
+
- Node.js and npm for building and packaging the client
10
+
- Visual Studio Code and the [Language Support for Java(TM) by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) extension installed and enabled
11
+
12
+
### Cloning and Setup
13
+
14
+
To get started, clone the repository and install the client dependencies:
To build the language server, package the extension, and install it in your local VS Code instance, you can run the provided script from the repository root:
26
+
27
+
```bash
28
+
./install.sh <version>
29
+
```
30
+
31
+
Replace `<version>` with the version number in [client/package.json](./client/package.json).
32
+
33
+
### Releasing
34
+
35
+
To create and push a git tag that will trigger the GitHub Actions workflow that automatically publishes the extension in both the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=AlcidesFonseca.liquid-java) and the [Open VSX Registry](https://open-vsx.org/extension/AlcidesFonseca/liquid-java):
36
+
1. Increment the version in [client/package.json](./client/package.json)
37
+
2. Run the release script from the repository root:
38
+
39
+
```bash
40
+
./release.sh <new-version>
41
+
```
42
+
43
+
### Development Mode
44
+
45
+
To run the extension in development mode, follow these steps:
46
+
1. Go to **Run** > **Run Extension** (or press **F5**)
47
+
2. A new VS Code instance will open with the extension installed, which will automatically run the language server in the background and connect to it
48
+
3. Open a Java project using LiquidJava
49
+
50
+
To run the language server manually, follow these steps:
51
+
52
+
1. Run the server in port `50000` (default)
53
+
2. In the client, set the `DEBUG` constant in [client/src/extension.ts](./client/src/extension.ts) to `true`
54
+
3. Run the client which will connect to the server in port `50000`
55
+
56
+
### Project Structure
57
+
-`/server` - Implements the language server in Java using [LSP4J](https://github.com/eclipse/lsp4j)
58
+
-`/client` - Implements the VS Code extension in TypeScript that connects to the language server via LSP
The **LiquidJava VS Code extension** adds support for **refinement types**, extending the Java standard type system directly inside VS Code, using the [LiquidJava](https://github.com/liquid-java/liquidjava) verifier. It provides error diagnostics and syntax highlighting for refinements.
### Extend your Java code with Liquid Types and catch bugs earlier!
6
6
7
-
### GitHub Codespaces
7
+
[LiquidJava](https://github.com/liquid-java/liquidjava) is an additional type checker for Java, based on **liquid types** and **typestates**, which provides stronger safety guarantees to Java programs at compile-time. With this extension, you can use LiquidJava directly in VS Code, with real-time diagnostic reporting, syntax highlighting for refinements, and an interactive webview for displaying details about diagnostics and state machine diagrams.
8
8
9
-
To try out the extension on an example project without setting up your local environment:
10
-
1. Log in to GitHub
11
-
2. Click the button below
12
-
3. Select the `4-core` option
13
-
4. Press `Create codespace`
14
-
15
-
The codespace will open in your browser and automatically install the LiquidJava extension shortly.
16
-
17
-
[](https://codespaces.new/liquid-java/liquidjava-examples)
9
+
```java
10
+
@Refinement("a > 0")
11
+
int a =3; // okay
12
+
a =-8; // type error!
13
+
```
18
14
19
-
### Local Setup
15
+
### Installation
20
16
21
-
To set up the extension locally, install the LiquidJava extension in the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=AlcidesFonseca.liquid-java) or the [Open VSX Marketplace](https://open-vsx.org/extension/AlcidesFonseca/liquid-java)and add the `liquidjava-api` dependency to your Java project.
17
+
To try out the extension, install it from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=AlcidesFonseca.liquid-java) or the [Open VSX Marketplace](https://open-vsx.org/extension/AlcidesFonseca/liquid-java). Additionally, you'll need the [Language Support for Java(TM) by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) VS Code extension, and add the `liquidjava-api` dependency to your Java project:
22
18
23
19
#### Maven
24
20
```xml
@@ -40,61 +36,105 @@ dependencies {
40
36
}
41
37
```
42
38
43
-
## Development
44
-
45
-
### Developer Mode
46
-
47
-
To run the extension in developer mode, which automatically spawns the server in a separate process:
48
-
49
-
1. Open the `client` folder in VS Code
50
-
2. Run `npm install`
51
-
3. Make sure you have the Red Hat extension for [Language Support for Java™](https://github.com/redhat-developer/vscode-java) installed and enabled
52
-
4. Go to `Run` > `Run Extension` (or press `F5`)
53
-
5. A new VS Code instance will start with the LiquidJava extension enabled
54
-
6. Open a Java project containing the `liquid-java-api.jar` in the `lib` folder
55
-
56
-
### Debugging Mode
57
-
58
-
To run the extension in debugging mode by manually starting the server and connecting the client to it:
59
-
60
-
* Run the server:
61
-
- Open the `server` folder in your IDE
62
-
- Run `App.java`, which will start the server on port `50000`
63
-
- View the server logs in the console
64
-
65
-
* Run the client:
66
-
- Open the `client` folder in VS Code
67
-
- Set the `DEBUG` variable to `true` in [`client/src/extension.ts`](./client/src/extension.ts)
68
-
- Go to `Run` > `Run Extension` (or press `F5`)
69
-
- A new VS Code instance will open with the LiquidJava extension enabled, which will connect to the server on port `50000`
70
-
- Open a Java project containing the `liquid-java-api.jar` in the `lib` folder
71
-
- View the client logs in the `LiquidJava` output channel or by clicking the status indicator
72
-
73
-
### Create Server JAR
74
-
75
-
To build the language server, export it as a runnable JAR file named `language-server-liquidjava.jar` and place it in `/client/server`.
76
-
77
-
- In **Eclipse**:
78
-
- Open the `server` folder
79
-
- Select `File > Export > Runnable JAR file`
80
-
- In the launch configuration, choose `main - vscode-liquid-java-server`
81
-
- In the output path, choose the `/client/server` folder of this extension
82
-
- In **VS Code**:
83
-
- Open the `server` folder
84
-
- Use the Export Jar feature (`Ctrl+Shift+P` > `Java: Export Jar`)
85
-
- Select `App` as the main class
86
-
- Select `OK`
87
-
- Copy the generated JAR from the root directory to the `/client/server` folder
88
-
- In **IntelliJ**:
89
-
- Open the `server` folder
90
-
- Go to `File` > `Project Structure` > `Artifacts`
91
-
- Select `Add a new Jar` > `From modules with dependencies`
92
-
- Select `App` as the main class
93
-
- Build the artifact via `Build` > `Build Artifacts` > `Build`
94
-
- Copy the generated JAR from the `out/artifacts` folder to the `/client/server` folder
95
-
96
-
### Project Structure
97
-
-`/server` - Implements the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/) in Java using the [LSP4J](https://github.com/eclipse/lsp4j) library
98
-
-`/client` - Implements the VS Code extension in TypeScript that connects to the language server using LSP
99
-
- It depends on [Language Support for Java™](https://github.com/redhat-developer/vscode-java) for regular Java errors
100
-
-`/lib` - Contains the `liquidjava-api.jar` required for the extension to be activated in a Java project
39
+
A repository with LiquidJava examples is available at [liquidjava-examples](https://github.com/liquid-java/liquidjava-examples). You can try them out without setting up your local environment using [GitHub Codespaces](https://codespaces.new/liquid-java/liquidjava-examples).
40
+
41
+
### What are Liquid Types?
42
+
43
+
Liquid types extend a language with **logical predicates** over the basic types. They allow developers to restrict the values that a variable, parameter or return value can have. These kinds of constraints help to catch more bugs before the program is executed. For example, they allow us to prevent bugs like array index out-of-bounds or division by zero at compile-time.
44
+
45
+
### LiquidJava
46
+
47
+
#### Refinements
48
+
49
+
To refine a variable, field, parameter or return value, use the `@Refinement` annotation with a predicate as an argument. The predicate must be a boolean expression that uses the name of the variable being refined (or `_`) to refer to its value. You can also provide a custom message to be included in the error message when the refinement is violated. Some examples include:
50
+
51
+
```java
52
+
@Refinement("x > 0") // x must be greater than 0
53
+
int x;
54
+
55
+
@Refinement("0 <= _ && _ <= 100") // y must be between 0 and 100
56
+
int y;
57
+
58
+
@Refinement(value="z % 2 == 0 ? z >= 0 : z < 0", msg="z must be positive if even, negative if odd")
59
+
int z;
60
+
61
+
@Refinement("_ >= 0")
62
+
int absDiv(int a, @Refinement(value="b != 0", msg="cannot divide by zero") int b) {
63
+
int res = a / b;
64
+
return res >=0? res :-res;
65
+
}
66
+
```
67
+
68
+
#### Refinement Aliases
69
+
70
+
To simplify the usage of refinements, you can create **predicate aliases** using the `@RefinementAlias` annotation, and apply them inside other refinements:
71
+
72
+
```java
73
+
@RefinementAlias("Percentage(int v) { 0 <= v && v <= 100 }")
74
+
publicclassMyClass {
75
+
76
+
// x must be between 0 and 100
77
+
@Refinement("Percentage(x)")
78
+
int x =25;
79
+
}
80
+
```
81
+
82
+
#### Object State Modeling via Typestates
83
+
84
+
Beyond basic refinements, LiquidJava also supports **object state modeling** via typestates, which allows developers to specify when a method can or cannot be called based on the state of the object. You can also provide a custom error message for when the method precondition is violated. For example:
85
+
86
+
```java
87
+
@StateSet({"open", "closed"})
88
+
publicclassMyFile {
89
+
90
+
@StateRefinement(to="open(this)")
91
+
publicMyFile() {}
92
+
93
+
@StateRefinement(from="open(this)", msg="file must be open to read")
94
+
publicvoidread() {}
95
+
96
+
@StateRefinement(from="open(this)", to="closed(this)", msg="file must be open to close")
97
+
publicvoidclose() {}
98
+
}
99
+
100
+
MyFile f =newMyFile();
101
+
f.read();
102
+
f.close();
103
+
f.read(); // type error: file must be open to read
104
+
```
105
+
106
+
#### Ghost Variables and External Refinements
107
+
108
+
Finally, LiquidJava also provides **ghost variables**, which are used to track additional information about the program state when typestates aren't enough, with the `@Ghost` annotation. Additionally, you can also refine external libraries using the `@ExternalRefinementsFor` annotation. Here is an example of the `java.util.Stack` class refined with LiquidJava, using a `size` ghost variable to track the number of elements in the stack:
109
+
110
+
```java
111
+
@ExternalRefinementsFor("java.util.Stack")
112
+
@Ghost("int size")
113
+
publicinterfaceStackRefinements<E> {
114
+
115
+
publicvoidStack();
116
+
117
+
@StateRefinement(to="size(this) == size(old(this)) + 1") // increments size by 1
118
+
publicbooleanpush(Eelem);
119
+
120
+
@StateRefinement(from="size(this) > 0", to="size(this) == size(old(this)) - 1", msg="cannot pop from an empty stack") // decrements size by 1
121
+
publicEpop();
122
+
123
+
@StateRefinement(from="size(this) > 0", msg="cannot peek from an empty stack")
124
+
publicEpeek();
125
+
}
126
+
127
+
Stack<String> s =newStack<>();
128
+
s.push("hello");
129
+
s.pop();
130
+
s.pop(); // type error: cannot pop from an empty stack
131
+
132
+
```
133
+
134
+
You can find more examples of how to use LiquidJava on the [LiquidJava Website](https://liquid-java.github.io). To learn how to use LiquidJava, you can also follow the [LiquidJava tutorial](https://github.com/liquid-java/liquidjava-tutorial).
135
+
136
+
For more information, check the following repositories:
137
+
-[liquidjava](https://github.com/liquid-java/liquidjava): Includes the API, verifier and some examples
138
+
-[vscode-liquidjava](https://github.com/liquid-java/vscode-liquidjava): Source code of this VS Code extension
139
+
-[liquidjava-examples](https://github.com/liquid-java/liquidjava-examples): Examples of how to use LiquidJava
140
+
-[liquid-java-external-libs](https://github.com/liquid-java/liquid-java-external-libs): Examples of how to use LiquidJava to refine external libraries
### Extend your Java code with Liquid Types and catch bugs earlier!
6
6
7
-
LiquidJava is an additional type checker for Java, based on **liquid types** and **typestates**, which provides stronger safety guarantees to Java programs at compile-time. With this extension, you can use LiquidJava directly in VS Code, with real-time error diagnostics, syntax highlighting for refinements and more!
7
+
[LiquidJava](https://github.com/liquid-java/liquidjava) is an additional type checker for Java, based on **liquid types** and **typestates**, which provides stronger safety guarantees to Java programs at compile-time. With this extension, you can use LiquidJava directly in VS Code, with real-time diagnostic reporting, syntax highlighting for refinements, and an interactive webview for displaying details about diagnostics and state machine diagrams.
8
8
9
9
```java
10
10
@Refinement("a > 0")
@@ -14,8 +14,7 @@ a = -8; // type error!
14
14
15
15
### Installation
16
16
17
-
This extension depends on the [Language Support for Java(TM) by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) VS Code extension.
18
-
Additionally, to use LiquidJava in your project, you'll need the following dependency, which includes the LiquidJava annotations:
17
+
To try out the extension, install it from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=AlcidesFonseca.liquid-java) or the [Open VSX Marketplace](https://open-vsx.org/extension/AlcidesFonseca/liquid-java). Additionally, you'll need the [Language Support for Java(TM) by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java) VS Code extension, and add the `liquidjava-api` dependency to your Java project:
19
18
20
19
#### Maven
21
20
```xml
@@ -37,35 +36,30 @@ dependencies {
37
36
}
38
37
```
39
38
39
+
A repository with LiquidJava examples is available at [liquidjava-examples](https://github.com/liquid-java/liquidjava-examples). You can try them out without setting up your local environment using [GitHub Codespaces](https://codespaces.new/liquid-java/liquidjava-examples).
40
+
40
41
### What are Liquid Types?
41
42
42
-
Liquid types, or refinement types, extend a language with **logical predicates** over the basic types. They allow developers to restrict the values that a variable, parameter or return value can have. These kinds of constraints help to catch more bugs before the program is executed — for examplearray index out-of-bounds or division by zero.
43
+
Liquid typesextend a language with **logical predicates** over the basic types. They allow developers to restrict the values that a variable, parameter or return value can have. These kinds of constraints help to catch more bugs before the program is executed. For example, they allow us to prevent bugs like array index out-of-bounds or division by zero at compile-time.
43
44
44
-
### Usage
45
+
### LiquidJava
45
46
46
47
#### Refinements
47
48
48
-
To refine a variable, parameter or return value, use the `@Refinement` annotation with a predicate as an argument. The predicate must be a boolean expression that uses the name of the variable being refined (or `_`) to refer to its value. Some examples include:
49
+
To refine a variable, field, parameter or return value, use the `@Refinement` annotation with a predicate as an argument. The predicate must be a boolean expression that uses the name of the variable being refined (or `_`) to refer to its value. You can also provide a custom message to be included in the error message when the refinement is violated. Some examples include:
49
50
50
51
```java
51
-
// x must be greater than 0
52
-
@Refinement("x > 0")
52
+
@Refinement("x > 0") // x must be greater than 0
53
53
int x;
54
54
55
-
// y must be between 0 and 100
56
-
@Refinement("0 <= _ && _ <= 100")
55
+
@Refinement("0 <= _ && _ <= 100") // y must be between 0 and 100
57
56
int y;
58
57
59
-
// z must be positive if it is even and negative if it is odd
60
-
@Refinement("z % 2 == 0 ? z >= 0 : z < 0")
58
+
@Refinement(value="z % 2 == 0 ? z >= 0 : z < 0", msg="z must be positive if even, negative if odd")
61
59
int z;
62
-
```
63
60
64
-
Refinements can also be applied to method parameters and return values:
65
-
66
-
```java
67
61
@Refinement("_ >= 0")
68
-
int absDiv(int a, @Refinement("b != 0") int b) {
62
+
int absDiv(int a, @Refinement(value="b != 0", msg="cannot divide by zero") int b) {
69
63
int res = a / b;
70
64
return res >=0? res :-res;
71
65
}
@@ -85,9 +79,9 @@ public class MyClass {
85
79
}
86
80
```
87
81
88
-
#### Object State Modeling with Typestates
82
+
#### Object State Modeling via Typestates
89
83
90
-
Beyond basic refinements, LiquidJava also supports **object state modeling**through typestates, which allows developers to specify when a method can or cannot be called based on the state of the object. For example:
84
+
Beyond basic refinements, LiquidJava also supports **object state modeling**via typestates, which allows developers to specify when a method can or cannot be called based on the state of the object. You can also provide a custom error message for when the method precondition is violated. For example:
91
85
92
86
```java
93
87
@StateSet({"open", "closed"})
@@ -96,22 +90,22 @@ public class MyFile {
96
90
@StateRefinement(to="open(this)")
97
91
publicMyFile() {}
98
92
99
-
@StateRefinement(from="open(this)")
93
+
@StateRefinement(from="open(this)", msg="file must be open to read")
@StateRefinement(from="open(this)", to="closed(this)", msg="file must be open to close")
103
97
publicvoidclose() {}
104
98
}
105
99
106
-
MyFile f =newMyFile();// state(f) == "open"
107
-
f.read();// state(f) == "open" (unchanged)
108
-
f.close();// state(f) == "closed"
109
-
f.read(); // type error!
100
+
MyFile f =newMyFile();
101
+
f.read();
102
+
f.close();
103
+
f.read(); // type error: file must be open to read
110
104
```
111
105
112
106
#### Ghost Variables and External Refinements
113
107
114
-
Finally, LiquidJava also provides **ghost variables**, which are used to track additional information about the program state when states aren't enough, with the `@Ghost` annotation. Additionally, you can also refine external libraries using the `@ExternalRefinementsFor` annotation. Here is an example of the `java.util.Stack` class refined with LiquidJava, using a `size` ghost variable to track the number of elements in the stack:
108
+
Finally, LiquidJava also provides **ghost variables**, which are used to track additional information about the program state when typestates aren't enough, with the `@Ghost` annotation. Additionally, you can also refine external libraries using the `@ExternalRefinementsFor` annotation. Here is an example of the `java.util.Stack` class refined with LiquidJava, using a `size` ghost variable to track the number of elements in the stack:
115
109
116
110
```java
117
111
@ExternalRefinementsFor("java.util.Stack")
@@ -120,20 +114,20 @@ public interface StackRefinements<E> {
0 commit comments