Skip to content

Commit 7da36e3

Browse files
authored
Merge pull request #3629 from 1c-syntax/feature/feat203
Диагностика бессмысленного тернарного оператора
2 parents 0454275 + 46783cd commit 7da36e3

File tree

10 files changed

+368
-1
lines changed

10 files changed

+368
-1
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Бесполезный тернарный оператор (UselessTernaryOperator)
2+
3+
<!-- Блоки выше заполняются автоматически, не трогать -->
4+
## Описание диагностики
5+
Размещение в тернарном операторе булевых констант "Истина" или "Ложь" указывает на плохую продуманность кода.
6+
7+
## Примеры
8+
Бессмысленные операторы
9+
10+
```Bsl
11+
А = ?(Б = 1, Истина, Ложь);
12+
```
13+
```Bsl
14+
А = ?(Б = 0, False, True);
15+
```
16+
17+
Подозрительные операторы
18+
19+
```Bsl
20+
А = ?(Б = 1, True, Истина);
21+
```
22+
```Bsl
23+
А = ?(Б = 0, 0, False);
24+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Useless ternary operator (UselessTernaryOperator)
2+
3+
<!-- Блоки выше заполняются автоматически, не трогать -->
4+
## Description
5+
The placement of Boolean constants "True" or "False" in the ternary operator indicates poor code thoughtfulness.
6+
7+
## Examples
8+
Useless operators
9+
10+
```Bsl
11+
A = ?(B = 1, True, False);
12+
```
13+
```Bsl
14+
A = ?(B = 0, False, True);
15+
```
16+
17+
Suspicious operators
18+
19+
```Bsl
20+
A = ?(B = 1, True, True);
21+
```
22+
```Bsl
23+
A = ?(B = 0, 0, False);
24+
```

src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DiagnosticStorage.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
import com.github._1c_syntax.bsl.languageserver.context.symbol.SourceDefinedSymbol;
2525
import com.github._1c_syntax.bsl.languageserver.utils.Ranges;
26-
import org.jspecify.annotations.Nullable;
2726
import org.antlr.v4.runtime.ParserRuleContext;
2827
import org.antlr.v4.runtime.Token;
2928
import org.antlr.v4.runtime.tree.ParseTree;
@@ -32,6 +31,7 @@
3231
import org.eclipse.lsp4j.DiagnosticCodeDescription;
3332
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
3433
import org.eclipse.lsp4j.Range;
34+
import org.jspecify.annotations.Nullable;
3535

3636
import java.util.ArrayList;
3737
import java.util.List;
@@ -74,6 +74,17 @@ protected void addDiagnostic(ParserRuleContext node) {
7474
);
7575
}
7676

77+
protected void addDiagnostic(ParserRuleContext node, DiagnosticAdditionalData data) {
78+
if (node.exception != null) {
79+
return;
80+
}
81+
82+
addDiagnostic(
83+
Ranges.create(node),
84+
data
85+
);
86+
}
87+
7788
protected void addDiagnostic(ParserRuleContext node, String diagnosticMessage) {
7889
if (node.exception != null) {
7990
return;
@@ -98,6 +109,14 @@ protected void addDiagnostic(Range range) {
98109
);
99110
}
100111

112+
protected void addDiagnostic(Range range, DiagnosticAdditionalData data) {
113+
addDiagnostic(
114+
range,
115+
data,
116+
diagnostic.getInfo().getMessage()
117+
);
118+
}
119+
101120
protected void addDiagnostic(Range range, String diagnosticMessage) {
102121
addDiagnostic(
103122
range,
@@ -106,6 +125,15 @@ protected void addDiagnostic(Range range, String diagnosticMessage) {
106125
);
107126
}
108127

128+
protected void addDiagnostic(Range range, DiagnosticAdditionalData data, String diagnosticMessage) {
129+
addDiagnostic(
130+
range,
131+
data,
132+
diagnosticMessage,
133+
null
134+
);
135+
}
136+
109137
protected void addDiagnostic(Token token) {
110138
addDiagnostic(
111139
Ranges.create(token)
@@ -202,6 +230,20 @@ public void addDiagnostic(
202230
String diagnosticMessage,
203231
@Nullable List<DiagnosticRelatedInformation> relatedInformation
204232
) {
233+
addDiagnostic(
234+
range,
235+
null,
236+
diagnosticMessage,
237+
relatedInformation
238+
);
239+
}
240+
241+
public void addDiagnostic(
242+
Range range,
243+
@Nullable DiagnosticAdditionalData data,
244+
String diagnosticMessage,
245+
@Nullable List<DiagnosticRelatedInformation> relatedInformation
246+
) {
205247

206248
if (Ranges.isEmpty(range)) {
207249
return;
@@ -210,6 +252,7 @@ public void addDiagnostic(
210252
diagnosticList.add(createDiagnostic(
211253
diagnostic,
212254
range,
255+
data,
213256
diagnosticMessage,
214257
relatedInformation
215258
));
@@ -234,9 +277,20 @@ protected void addDiagnostic(SourceDefinedSymbol sourceDefinedSymbol) {
234277
addDiagnostic(sourceDefinedSymbol.getSelectionRange());
235278
}
236279

280+
/**
281+
* Создает доп данные для диагностики на основании строки
282+
*
283+
* @param string Некая строка для помещения в доп данные диагностики
284+
* @return Допданные диагностики
285+
*/
286+
public static DiagnosticAdditionalData createAdditionalData(String string) {
287+
return new DiagnosticAdditionalData(string);
288+
}
289+
237290
private static Diagnostic createDiagnostic(
238291
BSLDiagnostic bslDiagnostic,
239292
Range range,
293+
@Nullable DiagnosticAdditionalData data,
240294
String diagnosticMessage,
241295
@Nullable List<DiagnosticRelatedInformation> relatedInformation
242296
) {
@@ -258,6 +312,21 @@ private static Diagnostic createDiagnostic(
258312
if (relatedInformation != null) {
259313
diagnostic.setRelatedInformation(relatedInformation);
260314
}
315+
316+
if (data != null) {
317+
diagnostic.setData(data);
318+
}
261319
return diagnostic;
262320
}
321+
322+
/**
323+
* Служебный класс для хранения вспомогательной информации диагностики, которая может использоваться
324+
* например в квикфиксах.
325+
* Пока реализация примитивная под конкретную задачу
326+
*
327+
* @param string Некая строка
328+
*/
329+
public record DiagnosticAdditionalData(String string) {
330+
331+
}
263332
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> 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.diagnostics;
23+
24+
import com.github._1c_syntax.bsl.languageserver.context.DocumentContext;
25+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
26+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope;
27+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
28+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
29+
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
30+
import com.github._1c_syntax.bsl.languageserver.providers.CodeActionProvider;
31+
import com.github._1c_syntax.bsl.parser.BSLParser;
32+
import org.antlr.v4.runtime.tree.ParseTree;
33+
import org.eclipse.lsp4j.CodeAction;
34+
import org.eclipse.lsp4j.CodeActionParams;
35+
import org.eclipse.lsp4j.Diagnostic;
36+
import org.eclipse.lsp4j.TextEdit;
37+
38+
import java.util.ArrayList;
39+
import java.util.List;
40+
import java.util.Optional;
41+
42+
@DiagnosticMetadata(
43+
type = DiagnosticType.CODE_SMELL,
44+
severity = DiagnosticSeverity.INFO,
45+
scope = DiagnosticScope.BSL,
46+
minutesToFix = 1,
47+
tags = {
48+
DiagnosticTag.BADPRACTICE,
49+
DiagnosticTag.SUSPICIOUS
50+
}
51+
)
52+
public class UselessTernaryOperatorDiagnostic extends AbstractVisitorDiagnostic implements QuickFixProvider {
53+
54+
private static final int SKIPPED_RULE_INDEX = 0;
55+
56+
@Override
57+
public ParseTree visitTernaryOperator(BSLParser.TernaryOperatorContext ctx) {
58+
var exp = ctx.expression();
59+
60+
if (exp != null && exp.size() >= 3) {
61+
var condition = getBooleanToken(exp.get(0));
62+
var trueBranch = getBooleanToken(exp.get(1));
63+
var falseBranch = getBooleanToken(exp.get(2));
64+
65+
if (condition != SKIPPED_RULE_INDEX) {
66+
diagnosticStorage.addDiagnostic(ctx);
67+
} else if (trueBranch == BSLParser.TRUE && falseBranch == BSLParser.FALSE) {
68+
diagnosticStorage.addDiagnostic(ctx, DiagnosticStorage.createAdditionalData(exp.get(0).getText()));
69+
} else if (trueBranch == BSLParser.FALSE && falseBranch == BSLParser.TRUE) {
70+
diagnosticStorage.addDiagnostic(ctx,
71+
DiagnosticStorage.createAdditionalData(getAdaptedText(exp.get(0).getText())));
72+
} else if (trueBranch != SKIPPED_RULE_INDEX || falseBranch != SKIPPED_RULE_INDEX) {
73+
diagnosticStorage.addDiagnostic(ctx);
74+
}
75+
}
76+
77+
return super.visitTernaryOperator(ctx);
78+
}
79+
80+
@Override
81+
public List<CodeAction> getQuickFixes(
82+
List<Diagnostic> diagnostics,
83+
CodeActionParams params,
84+
DocumentContext documentContext
85+
) {
86+
87+
List<TextEdit> textEdits = new ArrayList<>();
88+
89+
diagnostics.forEach((Diagnostic diagnostic) -> {
90+
var range = diagnostic.getRange();
91+
var data = diagnostic.getData();
92+
if (data instanceof DiagnosticStorage.DiagnosticAdditionalData additionalData) {
93+
var textEdit = new TextEdit(range, additionalData.string());
94+
textEdits.add(textEdit);
95+
}
96+
});
97+
98+
return CodeActionProvider.createCodeActions(
99+
textEdits,
100+
info.getResourceString("quickFixMessage"),
101+
documentContext.getUri(),
102+
diagnostics
103+
);
104+
105+
}
106+
107+
private String getAdaptedText(String text) {
108+
return info.getResourceString("quickFixAdaptedText", text);
109+
}
110+
111+
private int getBooleanToken(BSLParser.ExpressionContext expCtx) {
112+
113+
var tmpCtx = Optional.of(expCtx)
114+
.filter(ctx -> ctx.children.size() == 1)
115+
.map(ctx -> ctx.member(0))
116+
.map(ctx -> ctx.getChild(0))
117+
.filter(BSLParser.ConstValueContext.class::isInstance)
118+
.map(BSLParser.ConstValueContext.class::cast);
119+
120+
return tmpCtx
121+
.map(ctx -> ctx.getToken(BSLParser.TRUE, 0))
122+
.map(ctx -> BSLParser.TRUE)
123+
.or(() -> tmpCtx
124+
.map(ctx -> ctx.getToken(BSLParser.FALSE, 0))
125+
.map(ctx -> BSLParser.FALSE)
126+
)
127+
.orElse(SKIPPED_RULE_INDEX);
128+
}
129+
}

src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,16 @@
21142114
"title": "Use of system information",
21152115
"$id": "#/definitions/UseSystemInformation"
21162116
},
2117+
"UselessTernaryOperator": {
2118+
"description": "Useless ternary operator",
2119+
"default": true,
2120+
"type": [
2121+
"boolean",
2122+
"object"
2123+
],
2124+
"title": "Useless ternary operator",
2125+
"$id": "#/definitions/UselessTernaryOperator"
2126+
},
21172127
"UsingCancelParameter": {
21182128
"description": "Using parameter \"Cancel\"",
21192129
"default": true,

src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,9 @@
491491
"UseSystemInformation": {
492492
"$ref": "parameters-schema.json#/definitions/UseSystemInformation"
493493
},
494+
"UselessTernaryOperator": {
495+
"$ref": "parameters-schema.json#/definitions/UselessTernaryOperator"
496+
},
494497
"UsingCancelParameter": {
495498
"$ref": "parameters-schema.json#/definitions/UsingCancelParameter"
496499
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
diagnosticMessage=Useless ternary operator
2+
diagnosticName=Useless ternary operator
3+
quickFixMessage=Fix some useless ternary operators
4+
quickFixAdaptedText=NOT (%s)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
diagnosticMessage=Бесполезный тернарный оператор
2+
diagnosticName=Бесполезный тернарный оператор
3+
quickFixMessage=Исправить некоторые бесполезные тернарные операторы
4+
quickFixAdaptedText=НЕ (%s)

0 commit comments

Comments
 (0)