Skip to content

Commit 8a9f68f

Browse files
rudy-regazzoni-sonarsourcesonartech
authored andcommitted
SONARIAC-2441 Reshape docker grammar and parse Shell code (#616)
GitOrigin-RevId: 449faa5e8e3ac7879b9fe88c74ee70552cf74b83
1 parent 8290479 commit 8a9f68f

File tree

93 files changed

+2692
-3498
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2692
-3498
lines changed

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/LongRunInstructionCheck.java

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.function.Predicate;
28+
import java.util.regex.Pattern;
2729
import java.util.stream.Collectors;
2830
import java.util.stream.IntStream;
2931
import org.sonar.check.Rule;
@@ -36,6 +38,7 @@
3638
import org.sonar.iac.common.extension.visitors.TreeContext;
3739
import org.sonar.iac.common.extension.visitors.TreeVisitor;
3840
import org.sonar.iac.docker.tree.api.Argument;
41+
import org.sonar.iac.docker.tree.api.ArgumentList;
3942
import org.sonar.iac.docker.tree.api.ExpandableStringCharacters;
4043
import org.sonar.iac.docker.tree.api.ExpandableStringLiteral;
4144
import org.sonar.iac.docker.tree.api.Expression;
@@ -49,6 +52,7 @@ public class LongRunInstructionCheck implements IacCheck {
4952
public static final int DEFAULT_MAX_LENGTH = 120;
5053
public static final int MIN_WORD_TO_TRIGGER = 7;
5154
private static final String MESSAGE = "Split this RUN instruction line into multiple lines.";
55+
private static final Predicate<String> HAS_URL = Pattern.compile("\\b((https?|ftp)://|(www|ftp)\\.)\\S++").asPredicate();
5256

5357
@RuleProperty(
5458
key = "maxLength",
@@ -115,10 +119,36 @@ private static Map<Integer, Integer> countWordsPerLine(RunInstruction runInstruc
115119
Map<Integer, Integer> wordsPerLine = new HashMap<>();
116120
incrementWordCountOnLine(wordsPerLine, runInstruction.keyword());
117121
runInstruction.options().forEach(flag -> incrementWordCountOnLine(wordsPerLine, flag));
118-
runInstruction.arguments().forEach(argument -> incrementWordCountOnLine(wordsPerLine, argument));
122+
if (runInstruction.code() instanceof SyntaxToken syntaxToken) {
123+
incrementWordCountOnLine(wordsPerLine, syntaxToken.textRange().start().line(), countWordsInString(syntaxToken.value()));
124+
} else if (runInstruction.code() instanceof ArgumentList argumentList) {
125+
argumentList.arguments().forEach(argument -> incrementWordCountOnLine(wordsPerLine, argument));
126+
}
119127
return wordsPerLine;
120128
}
121129

130+
private static int countWordsInString(String text) {
131+
// 1. Fast fail for null or empty
132+
if (text.isEmpty()) {
133+
return 0;
134+
}
135+
int count = 0;
136+
boolean isWord = false;
137+
int length = text.length();
138+
for (int i = 0; i < length; i++) {
139+
char c = text.charAt(i);
140+
// 2. Check if the character is whitespace (equivalent to \s)
141+
if (Character.isWhitespace(c)) {
142+
isWord = false;
143+
} else if (!isWord) {
144+
// 3. If we weren't in a word, and now we are, increment count
145+
count++;
146+
isWord = true;
147+
}
148+
}
149+
return count;
150+
}
151+
122152
private static void incrementWordCountOnLine(Map<Integer, Integer> wordsPerLine, Tree tree) {
123153
if (tree.textRange().start().line() != tree.textRange().end().line()) {
124154
incrementWordCountOnLine(wordsPerLine, tree.textRange().start().line());
@@ -130,15 +160,26 @@ private static void incrementWordCountOnLine(Map<Integer, Integer> wordsPerLine,
130160
wordsPerLine.merge(line, 1, Integer::sum);
131161
}
132162

163+
private static void incrementWordCountOnLine(Map<Integer, Integer> wordsPerLine, int line, int wordCount) {
164+
wordsPerLine.merge(line, wordCount, Integer::sum);
165+
}
166+
133167
private static Set<Integer> getLinesWithUrl(RunInstruction runInstruction) {
134-
return runInstruction.arguments().stream()
135-
.filter(LongRunInstructionCheck::isArgumentUrl)
136-
.flatMap((Argument urlArgument) -> {
137-
int startLine = urlArgument.textRange().start().line();
138-
int endLine = urlArgument.textRange().end().line();
139-
return IntStream.rangeClosed(startLine, endLine).boxed();
140-
})
141-
.collect(Collectors.toSet());
168+
if (runInstruction.code() instanceof SyntaxToken syntaxToken) {
169+
if (HAS_URL.test(syntaxToken.value())) {
170+
return Set.of(syntaxToken.textRange().start().line());
171+
}
172+
} else if (runInstruction.code() instanceof ArgumentList argumentList) {
173+
return argumentList.arguments().stream()
174+
.filter(LongRunInstructionCheck::isArgumentUrl)
175+
.flatMap((Argument urlArgument) -> {
176+
int startLine = urlArgument.textRange().start().line();
177+
int endLine = urlArgument.textRange().end().line();
178+
return IntStream.rangeClosed(startLine, endLine).boxed();
179+
})
180+
.collect(Collectors.toSet());
181+
}
182+
return Set.of();
142183
}
143184

144185
private static boolean isArgumentUrl(Argument argument) {

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/MalformedJsonInExecCheck.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import org.sonar.iac.common.api.checks.InitContext;
2828
import org.sonar.iac.docker.symbols.ArgumentResolution;
2929
import org.sonar.iac.docker.tree.api.Argument;
30+
import org.sonar.iac.docker.tree.api.ShellCode;
3031
import org.sonar.iac.docker.tree.api.ShellForm;
32+
import org.sonar.iac.docker.tree.api.SyntaxToken;
3133

3234
@Rule(key = "S7030")
3335
public class MalformedJsonInExecCheck implements IacCheck {
@@ -41,6 +43,7 @@ public class MalformedJsonInExecCheck implements IacCheck {
4143
@Override
4244
public void initialize(InitContext init) {
4345
init.register(ShellForm.class, MalformedJsonInExecCheck::checkShellForm);
46+
init.register(ShellCode.class, MalformedJsonInExecCheck::checkShellCode);
4447
}
4548

4649
private static void checkShellForm(CheckContext ctx, ShellForm shellForm) {
@@ -49,8 +52,18 @@ private static void checkShellForm(CheckContext ctx, ShellForm shellForm) {
4952
}
5053
}
5154

55+
private static void checkShellCode(CheckContext ctx, ShellCode<?> shellCode) {
56+
if (shellCode.code() instanceof SyntaxToken syntaxToken && looksLikeExecForm(syntaxToken.value())) {
57+
ctx.reportIssue(syntaxToken, MESSAGE);
58+
}
59+
}
60+
5261
private static boolean isMalformedExec(ShellForm shellForm) {
5362
String command = resolveFullCommand(shellForm.arguments());
63+
return looksLikeExecForm(command);
64+
}
65+
66+
private static boolean looksLikeExecForm(@Nullable String command) {
5467
return command != null && command.startsWith("[") && !EXCLUDE_TOO_LONG_AFTER_OBJECT.test(command) && !EXCLUDE_NO_QUOTES.test(command);
5568
}
5669

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/PosixPermissionCheck.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.sonar.iac.docker.checks.utils.ArgumentChmod;
2929
import org.sonar.iac.docker.symbols.ArgumentResolution;
3030
import org.sonar.iac.docker.tree.api.Argument;
31+
import org.sonar.iac.docker.tree.api.ArgumentList;
3132
import org.sonar.iac.docker.tree.api.RunInstruction;
3233
import org.sonar.iac.docker.tree.api.TransferInstruction;
3334

@@ -43,10 +44,12 @@ public void initialize(InitContext init) {
4344
}
4445

4546
private static void checkRunChmodPermission(CheckContext ctx, RunInstruction runInstruction) {
46-
for (ArgumentChmod chmod : ArgumentChmod.extractChmodsFromArguments(runInstruction.arguments())) {
47-
if (chmod.hasPermission("o+w") || chmod.hasPermission("g+s") || chmod.hasPermission("u+s")) {
48-
TextRange textRange = TextRanges.merge(List.of(chmod.chmodArg.textRange(), chmod.permissionsArg.textRange()));
49-
ctx.reportIssue(textRange, MESSAGE);
47+
if (runInstruction.code() instanceof ArgumentList argumentList) {
48+
for (ArgumentChmod chmod : ArgumentChmod.extractChmodsFromArguments(argumentList.arguments())) {
49+
if (chmod.hasPermission("o+w") || chmod.hasPermission("g+s") || chmod.hasPermission("u+s")) {
50+
TextRange textRange = TextRanges.merge(List.of(chmod.chmodArg.textRange(), chmod.permissionsArg.textRange()));
51+
ctx.reportIssue(textRange, MESSAGE);
52+
}
5053
}
5154
}
5255
}

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/ShellExpansionsInCommandCheck.java

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.sonar.iac.docker.checks;
1818

1919
import java.util.Collection;
20-
import java.util.EnumSet;
2120
import java.util.List;
2221
import java.util.Set;
2322
import org.sonar.check.Rule;
@@ -29,13 +28,7 @@
2928
import org.sonar.iac.docker.checks.utils.CommandDetector;
3029
import org.sonar.iac.docker.checks.utils.command.SeparatedList;
3130
import org.sonar.iac.docker.symbols.ArgumentResolution;
32-
import org.sonar.iac.docker.tree.api.CmdInstruction;
33-
import org.sonar.iac.docker.tree.api.CommandInstruction;
34-
import org.sonar.iac.docker.tree.api.DockerTree;
35-
import org.sonar.iac.docker.tree.api.EntrypointInstruction;
36-
import org.sonar.iac.docker.tree.api.RunInstruction;
37-
38-
import static org.sonar.iac.docker.checks.utils.CheckUtils.ignoringSpecificForms;
31+
import org.sonar.iac.docker.tree.api.HasArguments;
3932

4033
@Rule(key = "S6573")
4134
public class ShellExpansionsInCommandCheck implements IacCheck {
@@ -47,21 +40,13 @@ public class ShellExpansionsInCommandCheck implements IacCheck {
4740
"--",
4841
// pattern matching in for loops is not a subject to the issue
4942
"for");
50-
/**
51-
* We don't run the check on heredoc form or exec form.
52-
* Here document are not supported with CommandDetector, so we don't run the check on them.
53-
* Unlike the shell form, the exec form does not invoke a command shell. This means that normal shell processing does not happen.
54-
*/
55-
private static final Set<DockerTree.Kind> EXCLUDED_FORMS = EnumSet.of(DockerTree.Kind.EXEC_FORM, DockerTree.Kind.HEREDOCUMENT);
5643

5744
@Override
5845
public void initialize(InitContext init) {
59-
init.register(RunInstruction.class, ignoringSpecificForms(EXCLUDED_FORMS, ShellExpansionsInCommandCheck::check));
60-
init.register(CmdInstruction.class, ignoringSpecificForms(EXCLUDED_FORMS, ShellExpansionsInCommandCheck::check));
61-
init.register(EntrypointInstruction.class, ignoringSpecificForms(EXCLUDED_FORMS, ShellExpansionsInCommandCheck::check));
46+
// TODO SONARIAC-2462
6247
}
6348

64-
private static void check(CheckContext ctx, CommandInstruction cmd) {
49+
private static void check(CheckContext ctx, HasArguments cmd) {
6550
SeparatedList<List<ArgumentResolution>, String> splitCommands = ArgumentResolutionSplitter.splitCommands(CheckUtils.resolveInstructionArguments(cmd));
6651
CommandDetector shellExpansionDetector = CommandDetector.builder()
6752
.with(ShellExpansionsInCommandCheck::isShellExpansion)

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/ShellFormOverExecFormCheck.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,10 @@
3030
import org.sonar.iac.docker.symbols.ArgumentResolution;
3131
import org.sonar.iac.docker.tree.api.Argument;
3232
import org.sonar.iac.docker.tree.api.Body;
33-
import org.sonar.iac.docker.tree.api.CmdInstruction;
34-
import org.sonar.iac.docker.tree.api.CommandInstruction;
3533
import org.sonar.iac.docker.tree.api.DockerImage;
3634
import org.sonar.iac.docker.tree.api.DockerTree;
37-
import org.sonar.iac.docker.tree.api.EntrypointInstruction;
3835
import org.sonar.iac.docker.tree.api.ShellInstruction;
36+
import org.sonar.iac.docker.tree.api.TransferInstruction;
3937
import org.sonar.iac.docker.tree.api.Variable;
4038

4139
import static org.sonar.iac.docker.tree.TreeUtils.getDockerImageName;
@@ -46,9 +44,6 @@ public class ShellFormOverExecFormCheck implements IacCheck {
4644

4745
private static final String DEFAULT_MESSAGE = "Replace this shell form with exec form.";
4846
private static final String WRAPPING_SCRIPT_MESSAGE = "Consider wrapping this instruction in a script file and call it with exec form.";
49-
private static final Set<Class<? extends CommandInstruction>> classes = Set.of(
50-
CmdInstruction.class,
51-
EntrypointInstruction.class);
5247
private static final Pattern UNSUPPORTED_FEATURES_IN_EXEC_FORM = Pattern.compile("&&|\\|\\||\\||;");
5348

5449
private final ShellInstructionsInfo checkContext = new ShellInstructionsInfo();
@@ -59,7 +54,7 @@ public void initialize(InitContext init) {
5954
init.register(Body.class, this::initFileAnalysis);
6055
init.register(DockerImage.class, this::resetShellInstruction);
6156
init.register(ShellInstruction.class, this::tagDockerImageWithShellInstruction);
62-
classes.forEach(klass -> init.register(klass, this::checkCommandInstructionForm));
57+
// TODO SONARIAC-2460
6358
}
6459

6560
private void initFileAnalysis(CheckContext ctx, Body body) {
@@ -76,23 +71,23 @@ private void tagDockerImageWithShellInstruction(CheckContext ctx, ShellInstructi
7671
getDockerImageName(shellInstruction).ifPresent(checkContext.imageNameWithShellInstruction::add);
7772
}
7873

79-
private void checkCommandInstructionForm(CheckContext ctx, CommandInstruction commandInstruction) {
80-
if (commandInstruction.getKindOfArgumentList() == DockerTree.Kind.SHELL_FORM && !checkContext.hasShellInstructionInCurrentImage
81-
&& !hasAnyParentDockerImageWithShellInstruction(commandInstruction)
82-
&& !commandInstruction.parent().is(DockerTree.Kind.HEALTHCHECK)) {
83-
var firstArg = commandInstruction.arguments().get(0);
84-
var lastArg = commandInstruction.arguments().get(commandInstruction.arguments().size() - 1);
74+
private void checkCommandInstructionForm(CheckContext ctx, TransferInstruction tr) {
75+
if (tr.srcsAndDest().getKind() == DockerTree.Kind.SHELL_FORM && !checkContext.hasShellInstructionInCurrentImage
76+
&& !hasAnyParentDockerImageWithShellInstruction(tr)
77+
&& !tr.parent().is(DockerTree.Kind.HEALTHCHECK)) {
78+
var firstArg = tr.srcsAndDest().arguments().get(0);
79+
var lastArg = tr.srcsAndDest().arguments().get(tr.srcsAndDest().arguments().size() - 1);
8580
var textRange = TextRanges.mergeElementsWithTextRange(List.of(firstArg, lastArg));
8681
var message = DEFAULT_MESSAGE;
87-
if (containFeatureNotSupportedByExecForm(commandInstruction)) {
82+
if (containFeatureNotSupportedByExecForm(tr)) {
8883
message = WRAPPING_SCRIPT_MESSAGE;
8984
}
9085
ctx.reportIssue(textRange, message);
9186
}
9287
}
9388

94-
private boolean hasAnyParentDockerImageWithShellInstruction(CommandInstruction commandInstruction) {
95-
return getParentDockerImageName(commandInstruction)
89+
private boolean hasAnyParentDockerImageWithShellInstruction(TransferInstruction transferInstruction) {
90+
return getParentDockerImageName(transferInstruction)
9691
.map((String parentDockerImageName) -> {
9792
if (hasShellInstruction(parentDockerImageName)) {
9893
return true;
@@ -107,8 +102,8 @@ private boolean hasShellInstruction(String imageName) {
107102
return checkContext.imageNameWithShellInstruction.contains(imageName);
108103
}
109104

110-
private static boolean containFeatureNotSupportedByExecForm(CommandInstruction commandInstruction) {
111-
for (Argument arg : commandInstruction.arguments()) {
105+
private static boolean containFeatureNotSupportedByExecForm(TransferInstruction transferInstruction) {
106+
for (Argument arg : transferInstruction.srcsAndDest().arguments()) {
112107
if (hasVariableReference(arg)) {
113108
return true;
114109
}

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/SortedArgumentListCheck.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.sonar.iac.common.api.checks.InitContext;
2525
import org.sonar.iac.docker.checks.utils.CommandDetector;
2626
import org.sonar.iac.docker.symbols.ArgumentResolution;
27-
import org.sonar.iac.docker.tree.api.DockerTree;
27+
import org.sonar.iac.docker.tree.api.ArgumentList;
2828
import org.sonar.iac.docker.tree.api.RunInstruction;
2929

3030
@Rule(key = "S7018")
@@ -65,18 +65,14 @@ public void initialize(InitContext init) {
6565
}
6666

6767
private static void checkRunInstruction(CheckContext ctx, RunInstruction runInstruction) {
68-
if (runInstruction.getKindOfArgumentList() == DockerTree.Kind.HEREDOCUMENT) {
69-
// TODO SONARIAC-1557 Heredoc should be treated as multiple instructions
70-
// Otherwise, it's not clear where to stop the matcher, and the next command can be captured.
71-
return;
68+
if (runInstruction.code() instanceof ArgumentList argumentList) {
69+
var argumentResolutions = argumentList.arguments().stream().map(ArgumentResolution::of).toList();
70+
COMMAND_DETECTORS.stream()
71+
.map(it -> it.search(argumentResolutions))
72+
.filter(it -> !it.isEmpty())
73+
.flatMap(List::stream)
74+
.forEach(command -> checkInstallationCommand(ctx, command));
7275
}
73-
74-
var argumentResolutions = runInstruction.arguments().stream().map(ArgumentResolution::of).toList();
75-
COMMAND_DETECTORS.stream()
76-
.map(it -> it.search(argumentResolutions))
77-
.filter(it -> !it.isEmpty())
78-
.flatMap(List::stream)
79-
.forEach(command -> checkInstallationCommand(ctx, command));
8076
}
8177

8278
private static void checkInstallationCommand(CheckContext ctx, CommandDetector.Command command) {

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/UnencryptedProtocolCheck.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@
2424
import org.sonar.iac.docker.symbols.ArgumentResolution;
2525
import org.sonar.iac.docker.tree.api.AddInstruction;
2626
import org.sonar.iac.docker.tree.api.Argument;
27-
import org.sonar.iac.docker.tree.api.CommandInstruction;
27+
import org.sonar.iac.docker.tree.api.CodeInstruction;
2828

2929
import static org.sonar.iac.common.checks.network.UrlUtils.isUnencryptedUrl;
30+
import static org.sonar.iac.docker.checks.utils.CheckUtils.codeToArgumentList;
3031
import static org.sonar.iac.docker.tree.api.DockerTree.Kind.ADD;
3132
import static org.sonar.iac.docker.tree.api.DockerTree.Kind.CMD;
3233
import static org.sonar.iac.docker.tree.api.DockerTree.Kind.ENTRYPOINT;
@@ -39,11 +40,11 @@ public class UnencryptedProtocolCheck implements IacCheck {
3940

4041
@Override
4142
public void initialize(InitContext init) {
42-
init.register(CommandInstruction.class, (ctx, commandInstruction) -> {
43+
init.register(CodeInstruction.class, (ctx, commandInstruction) -> {
4344
if (!commandInstruction.is(ADD, ENTRYPOINT, CMD, RUN)) {
4445
return;
4546
}
46-
checkUnencryptedProtocols(ctx, commandInstruction.arguments());
47+
codeToArgumentList(commandInstruction).ifPresent(argumentList -> checkUnencryptedProtocols(ctx, argumentList.arguments()));
4748
});
4849

4950
init.register(AddInstruction.class, (ctx, add) -> {

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/UnsecureConnectionCheck.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.sonar.iac.docker.symbols.ArgumentResolution;
2727
import org.sonar.iac.docker.tree.api.RunInstruction;
2828

29+
import static org.sonar.iac.docker.checks.utils.CheckUtils.codeToArgumentList;
30+
2931
@Rule(key = "S4830")
3032
public class UnsecureConnectionCheck implements IacCheck {
3133

@@ -49,9 +51,11 @@ public void initialize(InitContext init) {
4951
}
5052

5153
private static void checkRun(CheckContext ctx, RunInstruction runInstruction) {
52-
List<ArgumentResolution> resolvedArgument = runInstruction.arguments().stream().map(ArgumentResolution::of).toList();
54+
codeToArgumentList(runInstruction).ifPresent(argumentList -> {
55+
List<ArgumentResolution> resolvedArgument = argumentList.arguments().stream().map(ArgumentResolution::of).toList();
5356

54-
SENSITIVE_CURL_COMMAND.search(resolvedArgument).forEach(command -> ctx.reportIssue(command, MESSAGE));
55-
SENSITIVE_WGET_COMMAND.search(resolvedArgument).forEach(command -> ctx.reportIssue(command, MESSAGE));
57+
SENSITIVE_CURL_COMMAND.search(resolvedArgument).forEach(command -> ctx.reportIssue(command, MESSAGE));
58+
SENSITIVE_WGET_COMMAND.search(resolvedArgument).forEach(command -> ctx.reportIssue(command, MESSAGE));
59+
});
5660
}
5761
}

iac-extensions/docker/src/main/java/org/sonar/iac/docker/checks/VariableReferenceOutsideOfQuotesCheck.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
import org.sonar.iac.common.api.checks.InitContext;
2323
import org.sonar.iac.docker.symbols.ArgumentResolution;
2424
import org.sonar.iac.docker.tree.api.Argument;
25+
import org.sonar.iac.docker.tree.api.ArgumentList;
2526
import org.sonar.iac.docker.tree.api.CmdInstruction;
26-
import org.sonar.iac.docker.tree.api.CommandInstruction;
27+
import org.sonar.iac.docker.tree.api.CodeInstruction;
2728
import org.sonar.iac.docker.tree.api.DockerTree;
2829
import org.sonar.iac.docker.tree.api.EntrypointInstruction;
2930
import org.sonar.iac.docker.tree.api.Expression;
@@ -43,8 +44,14 @@ public void initialize(InitContext init) {
4344
init.register(EntrypointInstruction.class, VariableReferenceOutsideOfQuotesCheck::checkVariableReference);
4445
}
4546

46-
private static void checkVariableReference(CheckContext ctx, CommandInstruction commandInstruction) {
47-
for (Argument argument : commandInstruction.arguments()) {
47+
private static void checkVariableReference(CheckContext ctx, CodeInstruction codeInstruction) {
48+
if (codeInstruction.code() instanceof ArgumentList argumentList) {
49+
checkVariableReference(ctx, argumentList);
50+
}
51+
}
52+
53+
private static void checkVariableReference(CheckContext ctx, ArgumentList argumentList) {
54+
for (Argument argument : argumentList.arguments()) {
4855
for (Expression expression : argument.expressions()) {
4956
if (expression.is(DockerTree.Kind.REGULAR_VARIABLE) && !isVariableDefinedWithDoubleQuotes((RegularVariable) expression)) {
5057
ctx.reportIssue(expression, MESSAGE);

0 commit comments

Comments
 (0)