Skip to content

Commit f05c01c

Browse files
authored
SONARIAC-1475 Should not throw ClassCastException when decorator contains a dot (#1548)
1 parent b0d8bd0 commit f05c01c

File tree

5 files changed

+122
-4
lines changed

5 files changed

+122
-4
lines changed

iac-extensions/arm/src/main/java/org/sonar/iac/arm/checks/elementsorder/ElementsOrderResourceCheckBicep.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@
2121

2222
import java.util.HashMap;
2323
import java.util.Map;
24+
import java.util.Optional;
25+
import javax.annotation.CheckForNull;
2426
import org.sonar.iac.arm.checks.ElementsOrderResourceCheck;
27+
import org.sonar.iac.arm.tree.ArmTreeUtils;
2528
import org.sonar.iac.arm.tree.api.FunctionCall;
29+
import org.sonar.iac.arm.tree.api.Identifier;
2630
import org.sonar.iac.arm.tree.api.Property;
31+
import org.sonar.iac.arm.tree.api.Variable;
2732
import org.sonar.iac.arm.tree.api.bicep.Decorator;
33+
import org.sonar.iac.arm.tree.api.bicep.MemberExpression;
2834
import org.sonar.iac.arm.tree.impl.bicep.ResourceDeclarationImpl;
2935
import org.sonar.iac.common.api.checks.CheckContext;
3036
import org.sonar.iac.common.api.checks.IacCheck;
3137
import org.sonar.iac.common.api.checks.InitContext;
38+
import org.sonar.iac.common.api.tree.impl.TextRange;
3239
import org.sonar.iac.common.api.tree.impl.TextRanges;
3340

3441
/**
@@ -63,7 +70,9 @@ public class ElementsOrderResourceCheckBicep implements IacCheck {
6370
ELEMENTS_ORDER.put("properties", 100);
6471

6572
DECORATORS_ORDER.put("description", 1);
73+
DECORATORS_ORDER.put("sys.description", 1);
6674
DECORATORS_ORDER.put("batchSize", 2);
75+
DECORATORS_ORDER.put("sys.batchSize", 2);
6776
}
6877

6978
@Override
@@ -87,16 +96,51 @@ private static void checkResource(CheckContext checkContext, ResourceDeclaration
8796
private static void checkResourceDecorators(CheckContext checkContext, ResourceDeclarationImpl resourceDeclaration) {
8897
var prevIndex = 0;
8998
for (Decorator decorator : resourceDeclaration.decorators()) {
90-
var identifier = ((FunctionCall) decorator.expression()).name();
91-
var index = DECORATORS_ORDER.getOrDefault(identifier.value(), DEFAULT_ORDER_FOR_UNKNOWN_PROPERTY);
99+
var valueAndHighlight = toValueAndHighlight(decorator);
100+
if (valueAndHighlight == null) {
101+
continue;
102+
}
103+
var index = DECORATORS_ORDER.getOrDefault(valueAndHighlight.value, DEFAULT_ORDER_FOR_UNKNOWN_PROPERTY);
92104
if (index < prevIndex) {
93105
var textRange = TextRanges.merge(
94106
decorator.keyword().textRange(),
95-
identifier.textRange());
107+
valueAndHighlight.highlight);
96108
checkContext.reportIssue(textRange, MESSAGE_DECORATOR);
97109
break;
98110
}
99111
prevIndex = index;
100112
}
101113
}
114+
115+
@CheckForNull
116+
private static ValueAndHighlight toValueAndHighlight(Decorator decorator) {
117+
if (decorator.expression() instanceof FunctionCall functionCall) {
118+
var identifier = ArmTreeUtils.functionCallNameOrNull(functionCall);
119+
return new ValueAndHighlight(identifier.value(), identifier.textRange());
120+
} else if (decorator.expression() instanceof MemberExpression memberExpression) {
121+
var prefix = Optional.ofNullable(memberExpression.memberAccess())
122+
.filter(Variable.class::isInstance)
123+
.map(it -> ((Variable) it).identifier())
124+
.filter(Identifier.class::isInstance)
125+
.map(it -> ((Identifier) it).value())
126+
.map(it -> it + memberExpression.separatingToken().value())
127+
.orElse(null);
128+
129+
var identifier = Optional.ofNullable(memberExpression.expression())
130+
.map(ArmTreeUtils::functionCallNameOrNull)
131+
.orElse(null);
132+
133+
if (identifier != null && prefix != null) {
134+
var highlight = TextRanges.merge(
135+
memberExpression.memberAccess().textRange(),
136+
memberExpression.separatingToken().textRange(),
137+
identifier.textRange());
138+
return new ValueAndHighlight(prefix + identifier.value(), highlight);
139+
}
140+
}
141+
return null;
142+
}
143+
144+
record ValueAndHighlight(String value, TextRange highlight) {
145+
}
102146
}

iac-extensions/arm/src/main/java/org/sonar/iac/arm/tree/ArmTreeUtils.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.sonar.iac.arm.tree.api.ArrayExpression;
3535
import org.sonar.iac.arm.tree.api.Expression;
3636
import org.sonar.iac.arm.tree.api.File;
37+
import org.sonar.iac.arm.tree.api.FunctionCall;
3738
import org.sonar.iac.arm.tree.api.HasIdentifier;
3839
import org.sonar.iac.arm.tree.api.Identifier;
3940
import org.sonar.iac.arm.tree.api.ParameterDeclaration;
@@ -132,4 +133,14 @@ public static <T extends ArmTree> Stream<T> findAllNodes(ArmTree tree, Class<T>
132133
.filter(type::isInstance)
133134
.map(type::cast));
134135
}
136+
137+
/**
138+
* If the provided expression is a function call, return the function name, otherwise return null.
139+
*/
140+
public static Identifier functionCallNameOrNull(Expression expression) {
141+
if (expression instanceof FunctionCall functionCall) {
142+
return functionCall.name();
143+
}
144+
return null;
145+
}
135146
}

iac-extensions/arm/src/test/java/org/sonar/iac/arm/checks/ElementsOrderResourceCheckTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,11 @@ void shouldVerifyUnexpectedResourceBicep(String filename) {
9393

9494
@ParameterizedTest
9595
@ValueSource(strings = {"decoratorExpected.bicep",
96+
"decoratorExpectedFullyQualified.bicep",
9697
"decoratorExpectedAndOthers.bicep",
9798
"decoratorExpectedBatchSizeOnly.bicep",
98-
"decoratorExpectedDescriptionOnly.bicep"})
99+
"decoratorExpectedDescriptionOnly.bicep"
100+
})
99101
void shouldVerifyDecorator(String filename) {
100102
BicepVerifier.verifyNoIssue(DIR + filename, CHECK);
101103
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SonarQube IaC Plugin
3+
* Copyright (C) 2021-2024 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.iac.arm.tree;
21+
22+
import org.assertj.core.api.Assertions;
23+
import org.junit.jupiter.api.Test;
24+
import org.sonar.iac.arm.parser.BicepParser;
25+
import org.sonar.iac.arm.parser.bicep.BicepLexicalGrammar;
26+
import org.sonar.iac.arm.tree.api.Expression;
27+
import org.sonar.iac.arm.tree.api.Identifier;
28+
29+
class ArmTreeUtilsTest {
30+
@Test
31+
void shouldReturnFunctionNameForFunctionCall() {
32+
var code = "foo()";
33+
var expression = (Expression) BicepParser.create(BicepLexicalGrammar.EXPRESSION).parse(code);
34+
var functionName = ArmTreeUtils.functionCallNameOrNull(expression);
35+
36+
Assertions.assertThat(functionName)
37+
.isNotNull()
38+
.extracting(Identifier::value)
39+
.isEqualTo("foo");
40+
}
41+
42+
@Test
43+
void shouldReturnNullForNonFunctionCall() {
44+
var code = "bar";
45+
var expression = (Expression) BicepParser.create(BicepLexicalGrammar.EXPRESSION).parse(code);
46+
var functionName = ArmTreeUtils.functionCallNameOrNull(expression);
47+
48+
Assertions.assertThat(functionName).isNull();
49+
}
50+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@sys.description('some description')
2+
@batchSize(5)
3+
resource resourceName 'type@version' = {
4+
}
5+
6+
// Compliant, because it's invalid syntax for decorators; the rule does nothing
7+
@foo::sys.description('some description')
8+
@foo.sys().batchSize(5)
9+
@foo.bar!.baz()
10+
resource resourceName1 'type@version' = {
11+
}

0 commit comments

Comments
 (0)