Skip to content

Commit 8b2a3aa

Browse files
committed
Added session reset message and a message for unresolved dependencies while code snippet is executed
1 parent 5fc6e25 commit 8b2a3aa

File tree

2 files changed

+269
-7
lines changed

2 files changed

+269
-7
lines changed

nbcode/notebooks/src/org/netbeans/modules/nbcode/java/notebook/CodeEval.java

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@
2828
import java.util.logging.Logger;
2929
import java.util.regex.Matcher;
3030
import java.util.regex.Pattern;
31+
import jdk.jshell.DeclarationSnippet;
3132
import jdk.jshell.Diag;
3233
import jdk.jshell.EvalException;
3334
import jdk.jshell.JShell;
3435
import jdk.jshell.JShellException;
36+
import jdk.jshell.Snippet.SubKind;
3537
import jdk.jshell.SourceCodeAnalysis;
3638
import jdk.jshell.SnippetEvent;
39+
import org.eclipse.lsp4j.MessageParams;
40+
import org.eclipse.lsp4j.MessageType;
3741
import org.netbeans.modules.java.lsp.server.notebook.CellExecutionResult;
3842
import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams;
3943
import org.netbeans.modules.java.lsp.server.notebook.NotebookCellExecutionProgressResultParams.Builder;
@@ -48,13 +52,28 @@
4852
*/
4953
@NbBundle.Messages({
5054
"MSG_InterruptCodeCellExecSuccess=Code execution stopped successfully",
51-
"MSG_InterruptCodeCellInfo=Code execution was interrupted"
55+
"MSG_InterruptCodeCellInfo=Code execution was interrupted",
56+
"MSG_NotebookRestartSession=Notebook session unavailable. Please restart the notebook kernel.",
57+
"LBL_method=method",
58+
"LBL_variable=variable",
59+
"LBL_class=class",
60+
"LBL_enum=enum",
61+
"LBL_interface=interface",
62+
"LBL_annotation=annotation interface",
63+
"# {0} - declaration type and snippet name combination",
64+
"# {1} - unresolved dependencies list",
65+
"MSG_UnresolvedDepsRecoverableDefined=Created {0}. However, it cannot be invoked or used or instantiated until {1} is declared",
66+
"# {0} - declaration type and snippet name combination",
67+
"# {1} - unresolved dependencies list",
68+
"MSG_UnresolvedDepsRecoverableNotDefined=Created {0}. However, it cannot be referenced until {1} is declared",
69+
"MSG_ListCombine=, "
5270
})
5371
public class CodeEval {
5472

5573
private static final Logger LOG = Logger.getLogger(CodeEval.class.getName());
5674
private static final String CODE_EXEC_INTERRUPT_SUCCESS_MESSAGE = Bundle.MSG_InterruptCodeCellExecSuccess();
5775
private static final String CODE_EXEC_INTERRUPTED_MESSAGE = Bundle.MSG_InterruptCodeCellInfo();
76+
private static final String RESTART_NOTEBOOK_SESSION_MESSAGE = Bundle.MSG_NotebookRestartSession();
5877
private static final Pattern LINEBREAK = Pattern.compile("\\R");
5978

6079
private final Map<String, RequestProcessor> codeExecMap = new ConcurrentHashMap<>();
@@ -146,6 +165,10 @@ public CompletableFuture<Boolean> evaluate(List<Object> arguments) {
146165
CompletableFuture<JShell> sessionFuture = NotebookSessionManager.getInstance().getSessionFuture(notebookId);
147166
if (sessionFuture == null) {
148167
LOG.warning("notebook session not found");
168+
NbCodeLanguageClient client = LanguageClientInstance.getInstance().getClient();
169+
if (client != null) {
170+
client.showMessage(new MessageParams(MessageType.Error, RESTART_NOTEBOOK_SESSION_MESSAGE));
171+
}
149172
return CompletableFuture.completedFuture(false);
150173
}
151174

@@ -218,16 +241,70 @@ private RequestProcessor getCodeExec(String notebookId) {
218241
return new RequestProcessor("Jshell Code Evaluator for notebookId: " + id, 1, true, true);
219242
});
220243
}
221-
222-
private List<String> getCompilationErrors(JShell jshell, SnippetEvent event) {
244+
245+
// Made package-private for easy unit test
246+
List<String> getCompilationErrors(JShell jshell, SnippetEvent event) {
223247
List<String> compilationErrors = new ArrayList<>();
224248
jshell.diagnostics(event.snippet()).forEach(diag -> {
225249
compilationErrors.addAll(displayableDiagnostic(event.snippet().source(), diag));
226250
});
227251

252+
if (event.snippet() instanceof DeclarationSnippet) {
253+
DeclarationSnippet declSnippet = (DeclarationSnippet) event.snippet();
254+
List<String> unresolvedDeps = jshell.unresolvedDependencies(declSnippet).toList();
255+
if (!unresolvedDeps.isEmpty()) {
256+
String msg = getUnresolvedDependencyMsg(event, declSnippet, unresolvedDeps);
257+
compilationErrors.add(msg);
258+
}
259+
}
260+
228261
return compilationErrors;
229262
}
230263

264+
private String getUnresolvedDependencyMsg(SnippetEvent event, DeclarationSnippet snippet, List<String> unresolvedDeps) {
265+
String declarationType = getDeclarationType(snippet);
266+
String prefix = declarationType.isEmpty()
267+
? snippet.name()
268+
: declarationType + " " + snippet.name();
269+
270+
String dependencies = String.join(Bundle.MSG_ListCombine(), unresolvedDeps);
271+
272+
if (event.status().isDefined()) {
273+
return Bundle.MSG_UnresolvedDepsRecoverableDefined(prefix, dependencies);
274+
}
275+
return Bundle.MSG_UnresolvedDepsRecoverableNotDefined(prefix, dependencies);
276+
}
277+
278+
private String getDeclarationType(DeclarationSnippet snippet) {
279+
switch (snippet.kind()) {
280+
case METHOD:
281+
return Bundle.LBL_method();
282+
case VAR:
283+
return Bundle.LBL_variable();
284+
case TYPE_DECL:
285+
return getTypeDeclarationType(snippet.subKind());
286+
default:
287+
LOG.warning("Cannot find declaration type of the snippet");
288+
return "";
289+
}
290+
}
291+
292+
private String getTypeDeclarationType(SubKind subKind) {
293+
switch (subKind) {
294+
case CLASS_SUBKIND:
295+
return Bundle.LBL_class();
296+
case INTERFACE_SUBKIND:
297+
return Bundle.LBL_interface();
298+
case ENUM_SUBKIND:
299+
return Bundle.LBL_enum();
300+
case ANNOTATION_TYPE_SUBKIND:
301+
return Bundle.LBL_annotation();
302+
default:
303+
LOG.warning("Cannot find declaration sub-type of the snippet");
304+
return "";
305+
}
306+
}
307+
231308
private List<String> getRuntimeErrors(SnippetEvent event) {
232309
List<String> runtimeErrors = new ArrayList<>();
233310
JShellException jshellException = event.exception();
@@ -300,7 +377,7 @@ private StringBuilder correctExceptionName(StringBuilder output, int startIndex,
300377
}
301378
return output;
302379
}
303-
380+
304381
private StringBuilder printStackTrace(StringBuilder output, Throwable exception) {
305382
if (exception == null) {
306383
return output != null ? output : new StringBuilder(0);
@@ -311,17 +388,17 @@ private StringBuilder printStackTrace(StringBuilder output, Throwable exception)
311388
public void write(char[] cbuf, int off, int len) {
312389
sb.append(cbuf, off, len);
313390
}
314-
391+
315392
@Override
316393
public void flush() {
317394
}
318-
395+
319396
@Override
320397
public void close() {
321398
}
322399
});
323400
exception.printStackTrace(stackWriter);
324-
401+
325402
return sb;
326403
}
327404

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.netbeans.modules.nbcode.java.notebook;
17+
18+
import org.junit.Test;
19+
import org.junit.Before;
20+
import org.junit.After;
21+
import static org.junit.Assert.*;
22+
23+
import jdk.jshell.JShell;
24+
import jdk.jshell.Snippet;
25+
import jdk.jshell.SnippetEvent;
26+
27+
import java.util.List;
28+
29+
public class CodeEvalTest {
30+
31+
private JShell jshell;
32+
private CodeEval instance;
33+
34+
@Before
35+
public void setUp() {
36+
jshell = JShell.create();
37+
instance = CodeEval.getInstance();
38+
}
39+
40+
@After
41+
public void tearDown() {
42+
if (jshell != null) {
43+
jshell.close();
44+
}
45+
}
46+
47+
@Test
48+
public void testGetCompilationErrors_WithSyntaxError() {
49+
List<SnippetEvent> events = jshell.eval("int x = ;");
50+
SnippetEvent event = events.get(0);
51+
52+
List<String> errors = instance.getCompilationErrors(jshell, event);
53+
54+
assertFalse("Should have compilation errors", errors.isEmpty());
55+
}
56+
57+
@Test
58+
public void testGetCompilationErrors_WithValidCode() {
59+
List<SnippetEvent> events = jshell.eval("int x = 5;");
60+
SnippetEvent event = events.get(0);
61+
62+
List<String> errors = instance.getCompilationErrors(jshell, event);
63+
64+
assertTrue("Should not have compilation errors for valid code", errors.isEmpty());
65+
}
66+
67+
@Test
68+
public void testGetCompilationErrors_WithUnresolvedDependency() {
69+
List<SnippetEvent> events = jshell.eval("void testMethod() { System.out.println(undefinedVar); }");
70+
SnippetEvent event = events.get(0);
71+
72+
List<String> errors = instance.getCompilationErrors(jshell, event);
73+
74+
assertFalse("Should have unresolved dependency error", errors.isEmpty());
75+
boolean hasCannotBeMessage = false;
76+
for (String error : errors) {
77+
if (error.contains("cannot be")) {
78+
hasCannotBeMessage = true;
79+
break;
80+
}
81+
}
82+
assertTrue("Error message should mention that it cannot be invoked/used", hasCannotBeMessage);
83+
}
84+
85+
@Test
86+
public void testGetCompilationErrors_MethodWithUnresolvedDependency() {
87+
List<SnippetEvent> events = jshell.eval("int calculate() { return undefinedMethod(); }");
88+
SnippetEvent event = events.get(0);
89+
90+
List<String> errors = instance.getCompilationErrors(jshell, event);
91+
92+
assertFalse("Should have errors for undefined method", errors.isEmpty());
93+
boolean hasMethodReference = false;
94+
for (String error : errors) {
95+
if (error.contains("method") || error.contains("calculate")) {
96+
hasMethodReference = true;
97+
break;
98+
}
99+
}
100+
assertTrue("Error should reference the method", hasMethodReference);
101+
}
102+
103+
@Test
104+
public void testGetCompilationErrors_ClassWithUnresolvedDependency() {
105+
List<SnippetEvent> events = jshell.eval("class MyClass { UndefinedType field; }");
106+
SnippetEvent event = events.get(0);
107+
108+
List<String> errors = instance.getCompilationErrors(jshell, event);
109+
110+
assertFalse("Should have errors for undefined type", errors.isEmpty());
111+
}
112+
113+
@Test
114+
public void testGetCompilationErrors_VariableDeclaration() {
115+
List<SnippetEvent> events = jshell.eval("String name = \"test\";");
116+
SnippetEvent event = events.get(0);
117+
118+
List<String> errors = instance.getCompilationErrors(jshell, event);
119+
120+
assertTrue("Valid variable declaration should have no errors", errors.isEmpty());
121+
}
122+
123+
@Test
124+
public void testGetCompilationErrors_MultipleErrors() {
125+
List<SnippetEvent> events = jshell.eval("int x = ; int y = ;");
126+
127+
for (SnippetEvent event : events) {
128+
List<String> errors = instance.getCompilationErrors(jshell, event);
129+
if (event.snippet().kind() == Snippet.Kind.VAR) {
130+
assertFalse("Should have compilation errors", errors.isEmpty());
131+
}
132+
}
133+
}
134+
135+
@Test
136+
public void testGetCompilationErrors_InterfaceDeclaration() {
137+
List<SnippetEvent> events = jshell.eval("interface MyInterface { void method(); }");
138+
SnippetEvent event = events.get(0);
139+
140+
List<String> errors = instance.getCompilationErrors(jshell, event);
141+
142+
assertTrue("Valid interface should have no errors", errors.isEmpty());
143+
}
144+
145+
@Test
146+
public void testGetCompilationErrors_EnumDeclaration() {
147+
List<SnippetEvent> events = jshell.eval("enum Color { RED, GREEN, BLUE }");
148+
SnippetEvent event = events.get(0);
149+
150+
List<String> errors = instance.getCompilationErrors(jshell, event);
151+
152+
assertTrue("Valid enum should have no errors", errors.isEmpty());
153+
}
154+
155+
@Test
156+
public void testGetCompilationErrors_MultipleUnresolvedDependencies() {
157+
List<SnippetEvent> events = jshell.eval(
158+
"void process() { undefinedVar1.toString(); undefinedVar2.toString(); }");
159+
SnippetEvent event = events.get(0);
160+
161+
List<String> errors = instance.getCompilationErrors(jshell, event);
162+
163+
assertFalse("Should have errors for multiple dependencies", errors.isEmpty());
164+
}
165+
166+
@Test
167+
public void testGetCompilationErrors_MethodDeclaration() {
168+
List<SnippetEvent> events = jshell.eval("int add(int a, int b) { return a + b; }");
169+
SnippetEvent event = events.get(0);
170+
171+
List<String> errors = instance.getCompilationErrors(jshell, event);
172+
173+
assertTrue("Valid method declaration should have no errors", errors.isEmpty());
174+
}
175+
176+
@Test
177+
public void testGetCompilationErrors_ClassDeclaration() {
178+
List<SnippetEvent> events = jshell.eval("class Person { String name; int age; }");
179+
SnippetEvent event = events.get(0);
180+
181+
List<String> errors = instance.getCompilationErrors(jshell, event);
182+
183+
assertTrue("Valid class declaration should have no errors", errors.isEmpty());
184+
}
185+
}

0 commit comments

Comments
 (0)