Skip to content

Commit 2c84ffc

Browse files
authored
Merge pull request #3449 from 1c-syntax/autofix/fix-empty-parentheses
fix: handle empty parentheses in expression tree building
2 parents 064e270 + e1a225f commit 2c84ffc

File tree

11 files changed

+254
-46
lines changed

11 files changed

+254
-46
lines changed

.github/workflows/sentry.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,24 @@ jobs:
2525
- name: Get version from Gradle
2626
id: get_version
2727
run: echo "RELEASE_VERSION=$(./gradlew version -q)" >> $GITHUB_ENV
28+
- name: Get Sentry environment from project version
29+
id: get_env
30+
run: |
31+
if [[ "${{ env.RELEASE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
32+
echo "ENVIRONMENT=production" >> $GITHUB_ENV
33+
elif [[ "${{ env.RELEASE_VERSION }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-r[ca][0-9]+$ ]]; then
34+
echo "ENVIRONMENT=pre-release" >> $GITHUB_ENV
35+
elif [[ "${{ env.RELEASE_VERSION }}" == develop-* && ! "${{ env.RELEASE_VERSION }}" == *-DIRTY-* ]]; then
36+
echo "ENVIRONMENT=develop" >> $GITHUB_ENV
37+
else
38+
echo "ENVIRONMENT=feature" >> $GITHUB_ENV
39+
fi
2840
- name: Create Sentry release
2941
uses: getsentry/action-release@v3
3042
env:
3143
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
3244
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
3345
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
3446
with:
35-
environment: production
47+
environment: ${{ env.ENVIRONMENT }}
3648
version: ${{ env.RELEASE_VERSION }}

src/main/java/com/github/_1c_syntax/bsl/languageserver/ParentProcessWatcher.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public void watch() {
7070
.orElse(false);
7171

7272
if (!processIsAlive) {
73-
LOGGER.info("Parent process with pid {} is not found. Closing application...", parentProcessId);
73+
//noinspection StringConcatenationArgumentToLogCall - форматирование через {} не работает из-за NoClassDefException
74+
LOGGER.info("Parent process with pid " + parentProcessId + " is not found. Closing application...");
7475
languageServer.exit();
7576
}
7677
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/aop/sentry/SentryScopeConfigurer.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import jakarta.annotation.PostConstruct;
2929
import lombok.RequiredArgsConstructor;
3030
import org.eclipse.lsp4j.ServerInfo;
31+
import org.jetbrains.annotations.NotNull;
3132
import org.springframework.beans.factory.annotation.Value;
3233
import org.springframework.stereotype.Component;
3334

@@ -50,16 +51,13 @@ public class SentryScopeConfigurer {
5051
@Value("${sentry.dsn:}")
5152
private final String dsn;
5253

53-
@Value("${sentry.environment:dummy}")
54-
private final String environment;
55-
5654
@PostConstruct
5755
public void init() {
5856
if (dsn != null && !dsn.isEmpty()) {
5957
Sentry.init(options -> {
6058
options.setDsn(dsn);
61-
options.setEnvironment(environment);
62-
options.setRelease(serverInfo.getVersion());
59+
options.setEnvironment(getEnvironment());
60+
options.setRelease(getVersion());
6361
options.setAttachServerName(false);
6462
options.setServerName(getServerName());
6563
options.setBeforeSend(beforeSendCallback);
@@ -74,6 +72,26 @@ public void init() {
7472
});
7573
}
7674

75+
private String getEnvironment() {
76+
String version = getVersion();
77+
String environment;
78+
79+
if (version.matches("\\d+\\.\\d+\\.\\d+")) {
80+
environment = "production";
81+
} else if (version.matches("\\d+\\.\\d+\\.\\d+-r[ca]\\d+")) {
82+
environment = "pre-release";
83+
} else if (version.startsWith("develop-") && !version.contains("-DIRTY-")) {
84+
environment = "develop";
85+
} else {
86+
environment = "feature";
87+
}
88+
return environment;
89+
}
90+
91+
private String getVersion() {
92+
return serverInfo.getVersion();
93+
}
94+
7795
@Nullable
7896
private String getServerName() {
7997
try {

src/main/java/com/github/_1c_syntax/bsl/languageserver/cfg/CfgBuildingParseTreeVisitor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ public ParseTree visitIfStatement(BSLParser.IfStatementContext ctx) {
114114

115115
// тело true
116116
blocks.enterBlock();
117-
ctx.ifBranch().codeBlock().accept(this);
117+
if (ctx.ifBranch().codeBlock() != null) {
118+
ctx.ifBranch().codeBlock().accept(this);
119+
}
118120
var truePart = blocks.leaveBlock();
119121

120122
graph.addEdge(conditionStatement, truePart.begin(), CfgEdgeType.TRUE_BRANCH);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.utils.expressiontree;
23+
24+
import lombok.EqualsAndHashCode;
25+
import lombok.ToString;
26+
import lombok.Value;
27+
import lombok.experimental.NonFinal;
28+
import org.antlr.v4.runtime.tree.ParseTree;
29+
30+
@Value
31+
@NonFinal
32+
@EqualsAndHashCode(callSuper = true)
33+
@ToString(callSuper = true)
34+
public class ErrorExpressionNode extends BslExpression {
35+
36+
protected ErrorExpressionNode() {
37+
super(ExpressionNodeType.ERROR);
38+
}
39+
40+
protected ErrorExpressionNode(ParseTree sourceCodeOperator) {
41+
super(ExpressionNodeType.ERROR, sourceCodeOperator, null);
42+
}
43+
44+
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionNodeType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ public enum ExpressionNodeType {
2828
UNARY_OP,
2929
CALL,
3030
TERNARY_OP,
31-
SKIPPED_CALL_ARG
31+
SKIPPED_CALL_ARG,
32+
ERROR
3233
}

src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionTreeBuildingVisitor.java

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
*/
2222
package com.github._1c_syntax.bsl.languageserver.utils.expressiontree;
2323

24+
import com.github._1c_syntax.bsl.languageserver.utils.Trees;
2425
import com.github._1c_syntax.bsl.parser.BSLLexer;
2526
import com.github._1c_syntax.bsl.parser.BSLParser;
2627
import com.github._1c_syntax.bsl.parser.BSLParserBaseVisitor;
27-
import lombok.Value;
28+
import org.antlr.v4.runtime.tree.ErrorNode;
2829
import org.antlr.v4.runtime.tree.ParseTree;
2930
import org.antlr.v4.runtime.tree.TerminalNode;
3031

@@ -40,11 +41,10 @@
4041
*/
4142
public final class ExpressionTreeBuildingVisitor extends BSLParserBaseVisitor<ParseTree> {
4243

43-
@Value
44-
private static class OperatorInCode {
45-
BslOperator operator;
46-
ParseTree actualSourceCode; // ИЛИ vs OR в диагностических сообщениях, как написано в коде
47-
44+
/**
45+
* @param actualSourceCode ИЛИ vs OR в диагностических сообщениях, как написано в коде
46+
*/
47+
private record OperatorInCode(BslOperator operator, ParseTree actualSourceCode) {
4848
public int getPriority() {
4949
return operator.getPriority();
5050
}
@@ -85,6 +85,16 @@ public ParseTree visitExpression(BSLParser.ExpressionContext ctx) {
8585
var nestingCount = operatorsInFly.size();
8686
recursionLevel++;
8787

88+
if (Trees.nodeContainsErrors(ctx) || (ctx.getChildCount() == 0 || ctx.children.stream().anyMatch(ErrorNode.class::isInstance))) {
89+
var errorExpressionNode = new ErrorExpressionNode(ctx);
90+
if (recursionLevel > 0) {
91+
operands.push(errorExpressionNode);
92+
} else {
93+
resultExpression = errorExpressionNode;
94+
}
95+
return ctx;
96+
}
97+
8898
visitMember(ctx.member(0));
8999
var count = ctx.getChildCount();
90100

@@ -110,6 +120,7 @@ public ParseTree visitExpression(BSLParser.ExpressionContext ctx) {
110120
}
111121

112122
var operation = operands.peek();
123+
113124
// В случае ошибок парсинга выражения, operation может быть null
114125
if (operation == null) {
115126
recursionLevel--;
@@ -132,7 +143,8 @@ public ParseTree visitExpression(BSLParser.ExpressionContext ctx) {
132143
public ParseTree visitMember(BSLParser.MemberContext ctx) {
133144

134145
// В случае ошибки парсинга member может быть пустой.
135-
if (ctx.getChildCount() == 0) {
146+
if (Trees.nodeContainsErrors(ctx) || ctx.getChildCount() == 0 || ctx.children.stream().anyMatch(ErrorNode.class::isInstance)) {
147+
operands.push(new ErrorExpressionNode(ctx));
136148
return ctx;
137149
}
138150

@@ -146,26 +158,26 @@ public ParseTree visitMember(BSLParser.MemberContext ctx) {
146158

147159
var unaryModifier = ctx.unaryModifier();
148160
var childIndex = 0;
161+
149162
if (unaryModifier != null) {
150163
visitUnaryModifier(unaryModifier);
151-
childIndex = 1;
164+
childIndex = ctx.children.indexOf(unaryModifier) + 1;
152165
}
153166

154167
if (ctx.waitExpression() != null) {
155168
return visitWaitExpression(ctx.waitExpression());
156169
}
157170

158171
var dispatchChild = ctx.getChild(childIndex);
159-
if (dispatchChild instanceof TerminalNode terminalNode) {
172+
if (dispatchChild instanceof ErrorNode) {
173+
operands.push(new ErrorExpressionNode(dispatchChild));
174+
} else if (dispatchChild instanceof TerminalNode terminalNode) {
160175
var token = terminalNode.getSymbol().getType();
161176

162177
// ручная диспетчеризация
163178
switch (token) {
164-
case BSLLexer.LPAREN:
165-
visitParenthesis(ctx.expression(), ctx.modifier());
166-
break;
167-
default:
168-
throw new IllegalStateException("Unexpected rule " + dispatchChild);
179+
case BSLLexer.LPAREN -> visitParenthesis(ctx.expression(), ctx.modifier());
180+
default -> operands.push(new ErrorExpressionNode(dispatchChild));
169181
}
170182
} else {
171183
dispatchChild.accept(this);
@@ -177,7 +189,18 @@ public ParseTree visitMember(BSLParser.MemberContext ctx) {
177189
private void visitParenthesis(BSLParser.ExpressionContext expression,
178190
List<? extends BSLParser.ModifierContext> modifiers) {
179191

192+
// Handle the case where expression is empty (empty parentheses)
193+
if (expression == null || expression.getTokens().isEmpty()) {
194+
operands.push(new ErrorExpressionNode(expression));
195+
return;
196+
}
197+
180198
var subExpr = makeSubexpression(expression);
199+
200+
if (subExpr == null) {
201+
subExpr = new ErrorExpressionNode(expression);
202+
}
203+
181204
operands.push(subExpr);
182205

183206
for (var modifier : modifiers) {
@@ -314,6 +337,11 @@ public ParseTree visitGlobalMethodCall(BSLParser.GlobalMethodCallContext ctx) {
314337

315338
@Override
316339
public ParseTree visitNewExpression(BSLParser.NewExpressionContext ctx) {
340+
if (Trees.nodeContainsErrors(ctx)) {
341+
operands.push(new ErrorExpressionNode(ctx));
342+
return ctx;
343+
}
344+
317345
var typeName = ctx.typeName();
318346

319347
List<? extends BSLParser.CallParamContext> args;
@@ -327,6 +355,10 @@ public ParseTree visitNewExpression(BSLParser.NewExpressionContext ctx) {
327355
if (typeName == null) {
328356
// function style
329357
var typeNameArg = args.get(0);
358+
if (typeNameArg.expression() == null) {
359+
operands.push(new ErrorExpressionNode(ctx));
360+
return ctx;
361+
}
330362
args = args.stream().skip(1).toList();
331363
callNode = ConstructorCallNode.createDynamic(makeSubexpression(typeNameArg.expression()));
332364
} else {
@@ -407,18 +439,18 @@ private void buildOperation() {
407439
}
408440

409441
var operator = operatorsInFly.pop();
410-
if (Objects.requireNonNull(operator.getOperator()) == BslOperator.UNARY_MINUS
411-
|| operator.getOperator() == BslOperator.NOT
412-
|| operator.getOperator() == BslOperator.UNARY_PLUS) {
442+
if (Objects.requireNonNull(operator.operator()) == BslOperator.UNARY_MINUS
443+
|| operator.operator() == BslOperator.NOT
444+
|| operator.operator() == BslOperator.UNARY_PLUS) {
413445

414446
var operand = operands.pop();
415-
var operation = UnaryOperationNode.create(operator.getOperator(), operand, operator.getActualSourceCode());
447+
var operation = UnaryOperationNode.create(operator.operator(), operand, operator.actualSourceCode());
416448
operand.setParent(operation);
417449
operands.push(operation);
418450
} else {
419451
var right = operands.pop();
420452
var left = operands.pop();
421-
var binaryOp = BinaryOperationNode.create(operator.getOperator(), left, right, operator.getActualSourceCode());
453+
var binaryOp = BinaryOperationNode.create(operator.operator(), left, right, operator.actualSourceCode());
422454

423455
left.setParent(binaryOp);
424456
right.setParent(binaryOp);

src/main/java/com/github/_1c_syntax/bsl/languageserver/utils/expressiontree/ExpressionTreeVisitor.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,12 @@ public class ExpressionTreeVisitor {
2828

2929
private void visit(BslExpression node) {
3030
switch (node.getNodeType()) {
31-
case CALL:
32-
visitAbstractCall((AbstractCallNode) node);
33-
break;
34-
case UNARY_OP:
35-
visitUnaryOperation((UnaryOperationNode) node);
36-
break;
37-
case TERNARY_OP:
38-
var ternary = (TernaryOperatorNode) node;
39-
visitTernaryOperator(ternary);
40-
break;
41-
case BINARY_OP:
42-
visitBinaryOperation((BinaryOperationNode)node);
43-
break;
44-
45-
default:
46-
break; // для спокойствия сонара
31+
case CALL -> visitAbstractCall(node.cast());
32+
case UNARY_OP -> visitUnaryOperation(node.cast());
33+
case TERNARY_OP -> visitTernaryOperator(node.cast());
34+
case BINARY_OP -> visitBinaryOperation(node.cast());
35+
default -> { // для спокойствия сонара
36+
}
4737
}
4838
}
4939

src/main/resources/application.properties

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ logging.level.org.eclipse.lsp4j.jsonrpc.RemoteEndpoint=fatal
1919

2020
app.websocket.lsp-path=/lsp
2121
sentry.dsn=https://[email protected]/5790531
22-
sentry.environment=production
2322
sentry.use-git-commit-id-as-release=false
2423
picocli.disable.closures=true

0 commit comments

Comments
 (0)